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