fester/ui/live_dag.html

124 lines
3.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fester Live DAG</title>
<link rel="stylesheet" href="../style.css">
<style>
body { font-family: sans-serif; margin: 0; overflow: hidden; }
#graph { position: relative; width: 100vw; height: 100vh; background: #111; }
.node {
position: absolute;
width: 120px;
height: 40px;
text-align: center;
line-height: 40px;
border-radius: 6px;
color: white;
font-weight: bold;
cursor: pointer;
transition: background 0.3s, transform 0.2s;
}
svg.edge {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
pointer-events: none;
}
.heatmap {
border: 2px solid yellow;
}
</style>
</head>
<body>
<div id="graph"></div>
<svg id="edges" class="edge"></svg>
<script src="../cockpit/fester-module/targets.js"></script>
<script>
const graphEl = document.getElementById('graph');
const edgesSVG = document.getElementById('edges');
function createNodeElement(node) {
const el = document.createElement('div');
el.className = 'node';
el.id = `node-${node.id}`;
el.style.left = `${node.x || Math.random() * (window.innerWidth-150)}px`;
el.style.top = `${node.y || Math.random() * (window.innerHeight-50)}px`;
el.innerText = node.name || node.id;
graphEl.appendChild(el);
return el;
}
function updateNode(node) {
let el = document.getElementById(`node-${node.id}`);
if (!el) {
el = createNodeElement(node);
}
el.innerText = node.name || node.id;
// color based on state
switch(node.state) {
case 'done': el.style.background = 'green'; break;
case 'running': el.style.background = 'orange'; break;
case 'failed': el.style.background = 'red'; break;
default: el.style.background = 'gray';
}
// heatmap border
if (node.heatmap) {
el.classList.add('heatmap');
} else {
el.classList.remove('heatmap');
}
return el;
}
function drawEdges(edges, nodes) {
edgesSVG.innerHTML = '';
edges.forEach(edge => {
const fromEl = document.getElementById(`node-${edge.from}`);
const toEl = document.getElementById(`node-${edge.to}`);
if (!fromEl || !toEl) return;
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
const fromRect = fromEl.getBoundingClientRect();
const toRect = toEl.getBoundingClientRect();
line.setAttribute('x1', fromRect.left + fromRect.width/2);
line.setAttribute('y1', fromRect.top + fromRect.height/2);
line.setAttribute('x2', toRect.left + toRect.width/2);
line.setAttribute('y2', toRect.top + toRect.height/2);
line.setAttribute('stroke', 'white');
line.setAttribute('stroke-width', '2');
edgesSVG.appendChild(line);
});
}
// subscribe to FesterTargets updates
FesterTargets.onUpdate(snapshot => {
snapshot.nodes.forEach(updateNode);
drawEdges(snapshot.edges, snapshot.nodes);
});
// connect to WebSocket
const ws = new WebSocket("ws://localhost:8080/ws");
ws.onmessage = msg => {
const event = JSON.parse(msg.data);
if (event.type === 'node') {
const n = event.data.node;
const state = event.data.state;
FesterTargets.update({ nodes: [{ id: n, name: n, state: state }] });
}
if (event.type === 'pipeline') {
console.log('Pipeline event:', event.data);
}
if (event.type === 'heatmap') {
// { id: 'node1', heatmap: true }
FesterTargets.update({ nodes: [event.data] });
}
};
</script>
</body>
</html>