fester/ui/live_dag.html

170 lines
3.6 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>Fester Live DAG + Cause Graph</title>
<style>
body {
font-family: monospace;
background: #0e0f12;
color: #d0d0d0;
margin: 0;
overflow: hidden;
}
#graph {
position: relative;
width: 100vw;
height: 100vh;
}
.node {
position: absolute;
padding: 8px 10px;
border-radius: 6px;
font-size: 12px;
min-width: 140px;
transition: all 0.2s ease;
border: 1px solid #333;
}
.edge {
position: absolute;
height: 2px;
background: #444;
transform-origin: left center;
}
.tooltip {
position: absolute;
background: #111;
border: 1px solid #333;
padding: 6px;
font-size: 11px;
display: none;
max-width: 300px;
}
</style>
</head>
<body>
<div id="graph"></div>
<div id="tooltip" class="tooltip"></div>
<script>
const ws = new WebSocket("ws://localhost:8080/ws");
const nodes = {};
const edges = {};
const graph = document.getElementById("graph");
const tooltip = document.getElementById("tooltip");
function getColor(state) {
return {
"scheduled": "#2b6cb0",
"running": "#dd6b20",
"done": "#2f855a",
"failed": "#c53030",
"hit": "#805ad5"
}[state] || "#444";
}
function drawNode(id, label, state, x, y) {
if (!nodes[id]) {
const el = document.createElement("div");
el.className = "node";
graph.appendChild(el);
nodes[id] = el;
el.onmousemove = (e) => {
tooltip.style.display = "block";
tooltip.style.left = e.pageX + 10 + "px";
tooltip.style.top = e.pageY + 10 + "px";
};
el.onmouseleave = () => {
tooltip.style.display = "none";
};
}
const el = nodes[id];
el.innerText = `${label}\n${state}`;
el.style.background = getColor(state);
el.style.left = x + "px";
el.style.top = y + "px";
}
function drawEdge(id, x1, y1, x2, y2) {
if (!edges[id]) {
const el = document.createElement("div");
el.className = "edge";
graph.appendChild(el);
edges[id] = el;
}
const el = edges[id];
const dx = x2 - x1;
const dy = y2 - y1;
const length = Math.sqrt(dx*dx + dy*dy);
el.style.width = length + "px";
el.style.left = x1 + "px";
el.style.top = y1 + "px";
el.style.transform = `rotate(${Math.atan2(dy, dx)}rad)`;
}
ws.onmessage = (msg) => {
const event = JSON.parse(msg.data);
// ----------------------------
// NODE UPDATE
// ----------------------------
if (event.type === "task_update") {
const key = event.node + ":" + event.action;
const x = Math.random() * window.innerWidth * 0.8;
const y = Math.random() * window.innerHeight * 0.8;
drawNode(
key,
event.action,
event.state,
x,
y
);
}
// ----------------------------
// FAILURE HIGHLIGHT
// ----------------------------
if (event.type === "failure") {
const key = event.node + ":" + event.action;
if (nodes[key]) {
nodes[key].style.boxShadow = "0 0 12px red";
}
}
// ----------------------------
// CAUSE GRAPH HOOK (NEW)
// ----------------------------
if (event.type === "debug") {
tooltip.innerText = JSON.stringify(event, null, 2);
}
};
</script>
</body>
</html>