170 lines
3.6 KiB
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>
|