fester/cockpit/fester-module/ui.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>