245 lines
4.4 KiB
HTML
245 lines
4.4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Fester Debug Cockpit</title>
|
|
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
font-family: sans-serif;
|
|
background: #0b0f14;
|
|
color: #d0d0d0;
|
|
}
|
|
|
|
#layout {
|
|
display: grid;
|
|
grid-template-columns: 320px 1fr 320px;
|
|
height: 100vh;
|
|
}
|
|
|
|
/* -------------------------
|
|
CONTROL PANEL
|
|
------------------------- */
|
|
#controls {
|
|
padding: 12px;
|
|
border-right: 1px solid #1f2a38;
|
|
background: #0e141d;
|
|
}
|
|
|
|
select, button {
|
|
width: 100%;
|
|
margin: 6px 0;
|
|
padding: 6px;
|
|
background: #1a2432;
|
|
color: #ddd;
|
|
border: 1px solid #2c3b4d;
|
|
}
|
|
|
|
/* -------------------------
|
|
GRAPH
|
|
------------------------- */
|
|
#graph {
|
|
position: relative;
|
|
overflow: auto;
|
|
background: #05070b;
|
|
}
|
|
|
|
/* -------------------------
|
|
DEBUG PANEL
|
|
------------------------- */
|
|
#debug {
|
|
padding: 12px;
|
|
border-left: 1px solid #1f2a38;
|
|
background: #0e141d;
|
|
font-size: 12px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.node {
|
|
position: absolute;
|
|
width: 130px;
|
|
padding: 8px;
|
|
border-radius: 6px;
|
|
text-align: center;
|
|
font-size: 11px;
|
|
background: #1b2430;
|
|
border: 1px solid #333;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* states */
|
|
.node.running { border-color: #ffb300; }
|
|
.node.done { border-color: #66bb6a; }
|
|
.node.failed { border-color: #ef5350; }
|
|
.node.scheduled { border-color: #42a5f5; }
|
|
|
|
.highlight {
|
|
outline: 2px solid #ffd54f;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="layout">
|
|
|
|
<!-- LEFT -->
|
|
<div id="controls">
|
|
|
|
<h3>Build Control</h3>
|
|
|
|
<select id="arch">
|
|
<option>x86_64</option>
|
|
<option>arm64</option>
|
|
<option>riscv64</option>
|
|
</select>
|
|
|
|
<select id="target">
|
|
<option>linux-glibc</option>
|
|
<option>linux-musl</option>
|
|
<option>openwrt</option>
|
|
</select>
|
|
|
|
<select id="toolchain">
|
|
<option>gcc</option>
|
|
<option>clang</option>
|
|
</select>
|
|
|
|
<button onclick="startBuild()">Start Build</button>
|
|
|
|
</div>
|
|
|
|
<!-- CENTER -->
|
|
<div id="graph"></div>
|
|
|
|
<!-- RIGHT DEBUG -->
|
|
<div id="debug">
|
|
<h3>Node Debugger</h3>
|
|
<div id="trace">Select a node</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
|
|
const ws = new WebSocket("ws://localhost:8080/ws");
|
|
|
|
const nodes = {};
|
|
const traces = {};
|
|
const deps = {};
|
|
|
|
// -------------------------
|
|
// POSITIONING
|
|
// -------------------------
|
|
let index = 0;
|
|
|
|
function pos(name) {
|
|
if (nodes[name]) return nodes[name].pos;
|
|
|
|
const p = {
|
|
x: 80 + (index % 4) * 200,
|
|
y: 80 + Math.floor(index / 4) * 120
|
|
};
|
|
|
|
index++;
|
|
return p;
|
|
}
|
|
|
|
// -------------------------
|
|
// NODE CLICK DEBUGGER
|
|
// -------------------------
|
|
function showTrace(name) {
|
|
|
|
const trace = traces[name];
|
|
|
|
const panel = document.getElementById("trace");
|
|
|
|
if (!trace) {
|
|
panel.innerHTML = "No trace data yet";
|
|
return;
|
|
}
|
|
|
|
panel.innerHTML = `
|
|
<b>${name}</b><br><br>
|
|
|
|
<b>State:</b> ${trace.state}<br>
|
|
<b>Cache:</b> ${trace.cache || "unknown"}<br>
|
|
<b>Node:</b> ${trace.node || "n/a"}<br>
|
|
|
|
<hr>
|
|
|
|
<b>Scheduler Score Breakdown</b><br>
|
|
CPU: ${trace.score?.cpu || 0}<br>
|
|
Thermal: ${trace.score?.thermal || 0}<br>
|
|
Stability: ${trace.score?.instability || 0}<br>
|
|
Final: ${trace.score?.final || 0}<br>
|
|
|
|
<hr>
|
|
|
|
<b>Deps:</b><br>
|
|
${(trace.deps || []).join(", ")}
|
|
`;
|
|
}
|
|
|
|
// -------------------------
|
|
// WEBSOCKET
|
|
// -------------------------
|
|
ws.onmessage = (msg) => {
|
|
|
|
const event = JSON.parse(msg.data);
|
|
|
|
if (event.type !== "node") return;
|
|
|
|
const n = event.data.node;
|
|
|
|
traces[n] = {
|
|
...traces[n],
|
|
...event.data
|
|
};
|
|
|
|
// create node
|
|
if (!nodes[n]) {
|
|
|
|
const el = document.createElement("div");
|
|
el.className = "node";
|
|
el.innerText = n;
|
|
|
|
const p = pos(n);
|
|
el.style.left = p.x + "px";
|
|
el.style.top = p.y + "px";
|
|
|
|
el.onclick = () => showTrace(n);
|
|
|
|
document.getElementById("graph").appendChild(el);
|
|
|
|
nodes[n] = { el, pos: p };
|
|
}
|
|
|
|
const el = nodes[n].el;
|
|
|
|
el.classList.remove("running","done","failed","scheduled");
|
|
el.classList.add(event.data.state);
|
|
};
|
|
|
|
// -------------------------
|
|
// START BUILD
|
|
// -------------------------
|
|
function startBuild() {
|
|
|
|
fetch("/api/build", {
|
|
method: "POST",
|
|
headers: {"Content-Type":"application/json"},
|
|
body: JSON.stringify({
|
|
arch: arch.value,
|
|
target: target.value,
|
|
toolchain: toolchain.value
|
|
})
|
|
});
|
|
}
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|