118 lines
3.0 KiB
Plaintext
118 lines
3.0 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Node Balancer WebUI</title>
|
|
<style>
|
|
body {
|
|
font-family: sans-serif;
|
|
margin: 20px;
|
|
}
|
|
svg {
|
|
border: 1px solid #ccc;
|
|
width: 100%;
|
|
height: 300px;
|
|
background: #f9f9f9;
|
|
}
|
|
.node {
|
|
fill: #4CAF50;
|
|
stroke: #333;
|
|
stroke-width: 2;
|
|
}
|
|
.pod {
|
|
fill: #2196F3;
|
|
stroke: #000;
|
|
stroke-width: 1;
|
|
}
|
|
.pod text {
|
|
font-size: 10px;
|
|
fill: #fff;
|
|
text-anchor: middle;
|
|
dominant-baseline: middle;
|
|
}
|
|
.label {
|
|
font-size: 14px;
|
|
text-anchor: middle;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h2>Node Balancer</h2>
|
|
<svg id="canvas"></svg>
|
|
|
|
<script>
|
|
const svg = document.getElementById("canvas");
|
|
const nodeNames = ["worker-1","worker-2","worker-3"];
|
|
const nodePositions = {};
|
|
const nodeY = 50;
|
|
const nodeWidth = 100;
|
|
const nodeHeight = 40;
|
|
|
|
// draw nodes
|
|
nodeNames.forEach((name,i)=>{
|
|
const x = 50 + i*200;
|
|
nodePositions[name] = {x: x + nodeWidth/2, y: nodeY + nodeHeight/2};
|
|
const rect = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
|
rect.setAttribute("x", x);
|
|
rect.setAttribute("y", nodeY);
|
|
rect.setAttribute("width", nodeWidth);
|
|
rect.setAttribute("height", nodeHeight);
|
|
rect.setAttribute("class", "node");
|
|
svg.appendChild(rect);
|
|
|
|
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
|
label.setAttribute("x", x + nodeWidth/2);
|
|
label.setAttribute("y", nodeY + nodeHeight/2 + 25);
|
|
label.setAttribute("class","label");
|
|
label.textContent = name;
|
|
svg.appendChild(label);
|
|
});
|
|
|
|
// animate a pod
|
|
function flyPod(podName, fromNode, toNode){
|
|
const g = document.createElementNS("http://www.w3.org/2000/svg","g");
|
|
const circle = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
|
circle.setAttribute("r", 15);
|
|
circle.setAttribute("class","pod");
|
|
g.appendChild(circle);
|
|
|
|
const text = document.createElementNS("http://www.w3.org/2000/svg","text");
|
|
text.textContent = podName;
|
|
g.appendChild(text);
|
|
|
|
svg.appendChild(g);
|
|
|
|
let start = nodePositions[fromNode];
|
|
let end = nodePositions[toNode];
|
|
let progress = 0;
|
|
const duration = 2000; // ms
|
|
const steps = 60;
|
|
const dx = (end.x - start.x)/steps;
|
|
const dy = (end.y - start.y)/steps;
|
|
|
|
const anim = setInterval(()=>{
|
|
progress++;
|
|
const cx = start.x + dx*progress;
|
|
const cy = start.y + dy*progress;
|
|
circle.setAttribute("cx", cx);
|
|
circle.setAttribute("cy", cy);
|
|
text.setAttribute("x", cx);
|
|
text.setAttribute("y", cy);
|
|
if(progress >= steps){
|
|
clearInterval(anim);
|
|
// leave pod at destination for a short moment then remove
|
|
setTimeout(()=>svg.removeChild(g),1000);
|
|
}
|
|
}, duration/steps);
|
|
}
|
|
|
|
// SSE stream
|
|
const evtSource = new EventSource("/api/stream");
|
|
evtSource.onmessage = e => {
|
|
const evt = JSON.parse(e.data);
|
|
flyPod(evt.pod, evt.fromNode, evt.toNode);
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|