eindelijk weer eens een push
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Node Balancer</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 20px; }
|
||||
svg { border:1px solid #ccc; width:100%; height:500px; background:#f9f9f9; }
|
||||
.node { fill:#4CAF50; stroke:#333; stroke-width:2; }
|
||||
.pod { fill:#2196F3; stroke:#000; stroke-width:1; }
|
||||
.label { font-size:14px; text-anchor:middle; dominant-baseline:middle; }
|
||||
.pod-label { font-size:14px; text-anchor:middle; dominant-baseline:middle; fill:#fff; }
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f0f0f0; /* light gray background for the header */
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
header .logo {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: #2196F3; /* blue logo text */
|
||||
margin-right: 15px;
|
||||
padding: 5px 10px;
|
||||
background-color: #333; /* dark background behind logo text */
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
header .title {
|
||||
font-size: 18px;
|
||||
}
|
||||
header .title span {
|
||||
margin-left: 10px;
|
||||
color: #555;
|
||||
font-weight: normal;
|
||||
}
|
||||
header .cluster-label {
|
||||
margin-left: 20px;
|
||||
color: #333;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div class="logo">allarddcs</div>
|
||||
<div class="title">
|
||||
Node Balancer <span id="version">v?</span>
|
||||
<span class="cluster-label">Cluster: <span id="cluster-name">?</span></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<svg id="canvas"></svg>
|
||||
|
||||
<script>
|
||||
const svg = document.getElementById("canvas");
|
||||
const nodePositions = {};
|
||||
const nodeY = 50;
|
||||
const nodeWidth = 100;
|
||||
const nodeHeight = 40;
|
||||
let animationDuration = 2000;
|
||||
|
||||
// Store pod stacks per node
|
||||
const podsPerNode = {};
|
||||
|
||||
// Fetch animation duration from server
|
||||
fetch("/api/config")
|
||||
.then(res => res.json())
|
||||
.then(cfg => {
|
||||
animationDuration = cfg.animationDurationMs;
|
||||
document.getElementById("version").textContent = "v" + cfg.version;
|
||||
document.getElementById("cluster-name").textContent = cfg.clusterName;
|
||||
});
|
||||
|
||||
// Draw nodes
|
||||
fetch("/api/nodes")
|
||||
.then(res => res.json())
|
||||
.then(nodeNames => {
|
||||
nodeNames.forEach((name, i) => {
|
||||
const x = 50 + i*200;
|
||||
|
||||
// Node box
|
||||
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);
|
||||
|
||||
// Node label
|
||||
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||
label.setAttribute("x", x + nodeWidth/2);
|
||||
label.setAttribute("y", nodeY + nodeHeight/2);
|
||||
label.setAttribute("class","label");
|
||||
label.textContent = name;
|
||||
svg.appendChild(label);
|
||||
|
||||
// store position and rect reference
|
||||
nodePositions[name] = {
|
||||
x: x + nodeWidth/2,
|
||||
y: nodeY + nodeHeight/2,
|
||||
rect: rect
|
||||
};
|
||||
|
||||
podsPerNode[name] = [];
|
||||
});
|
||||
|
||||
// Fetch initial pods after nodes are drawn
|
||||
fetch("/api/pods")
|
||||
.then(res => res.json())
|
||||
.then(podsByNode => {
|
||||
for(const nodeName in podsByNode){
|
||||
podsByNode[nodeName].forEach(podName => addPod(podName, nodeName));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add a pod to the stack under a node
|
||||
function addPod(podName, nodeName){
|
||||
if(!nodePositions[nodeName]) return;
|
||||
|
||||
const stack = podsPerNode[nodeName] || [];
|
||||
const index = stack.length;
|
||||
|
||||
const nodeRect = nodePositions[nodeName].rect;
|
||||
|
||||
// Update node box height first
|
||||
const newHeight = nodeHeight + (index + 1)*(20 + 5); // 20 = pod height, 5 = spacing
|
||||
nodeRect.setAttribute("height", newHeight);
|
||||
|
||||
// Pod Y now starts right below node rect
|
||||
const x = nodePositions[nodeName].x - nodeWidth/2;
|
||||
const y = nodeY + newHeight - (index + 1)*(20 + 5) + 5; // stack pods inside the box
|
||||
|
||||
const g = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||
|
||||
const rect = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
||||
rect.setAttribute("x", x);
|
||||
rect.setAttribute("y", y);
|
||||
rect.setAttribute("width", nodeWidth);
|
||||
rect.setAttribute("height", 20);
|
||||
rect.setAttribute("class","pod");
|
||||
g.appendChild(rect);
|
||||
|
||||
const text = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||
text.setAttribute("x", x + nodeWidth/2);
|
||||
text.setAttribute("y", y + 10);
|
||||
text.setAttribute("class","pod-label");
|
||||
const maxChars = Math.floor(nodeWidth / 8);
|
||||
text.textContent = podName.length > maxChars ? podName.substr(0, maxChars) : podName;
|
||||
g.appendChild(text);
|
||||
|
||||
svg.appendChild(g);
|
||||
stack.push(g);
|
||||
podsPerNode[nodeName] = stack;
|
||||
}
|
||||
|
||||
// Animate pod moving from one node to another
|
||||
function flyPod(podName, fromNode, toNode){
|
||||
// Remove pod from fromNode stack
|
||||
if (fromNode && podsPerNode[fromNode]) {
|
||||
podsPerNode[fromNode] = podsPerNode[fromNode].filter(g => {
|
||||
const text = g.querySelector("text");
|
||||
return text && text.textContent !== podName;
|
||||
});
|
||||
redrawPods(fromNode);
|
||||
}
|
||||
|
||||
// Add pod to toNode stack (temporarily for animation)
|
||||
const g = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||
|
||||
const rect = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
||||
rect.setAttribute("width", nodeWidth);
|
||||
rect.setAttribute("height", 20);
|
||||
rect.setAttribute("class","pod");
|
||||
g.appendChild(rect);
|
||||
|
||||
const text = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||
text.textContent = podName;
|
||||
g.appendChild(text);
|
||||
|
||||
svg.appendChild(g);
|
||||
|
||||
let start = nodePositions[fromNode] || nodePositions[toNode];
|
||||
let end = nodePositions[toNode];
|
||||
if(!start || !end) return;
|
||||
|
||||
let progress = 0;
|
||||
const steps = 60;
|
||||
const dx = (end.x - start.x)/steps;
|
||||
const dy = (end.y - start.y)/steps;
|
||||
|
||||
const anim = setInterval(()=>{
|
||||
progress++;
|
||||
const x = start.x - nodeWidth/2 + dx*progress;
|
||||
const y = start.y + dy*progress;
|
||||
|
||||
rect.setAttribute("x", x);
|
||||
rect.setAttribute("y", y);
|
||||
text.setAttribute("x", x + nodeWidth/2);
|
||||
text.setAttribute("y", y + 10);
|
||||
|
||||
if(progress>=steps){
|
||||
clearInterval(anim);
|
||||
svg.removeChild(g);
|
||||
addPod(podName, toNode); // add permanently
|
||||
}
|
||||
}, animationDuration/steps);
|
||||
}
|
||||
|
||||
// Redraw all pods on a node and adjust node box height
|
||||
function redrawPods(nodeName){
|
||||
if(!podsPerNode[nodeName]) return;
|
||||
const stack = podsPerNode[nodeName];
|
||||
const nodeRect = nodePositions[nodeName].rect;
|
||||
|
||||
const newHeight = nodeHeight + stack.length*(20 + 5);
|
||||
nodeRect.setAttribute("height", newHeight);
|
||||
|
||||
stack.forEach((g, idx)=>{
|
||||
const rect = g.querySelector("rect");
|
||||
const text = g.querySelector("text");
|
||||
const x = nodePositions[nodeName].x - nodeWidth/2;
|
||||
const y = nodeY + newHeight - (stack.length - idx)*(20 + 5) + 5; // align pods inside node box
|
||||
rect.setAttribute("x", x);
|
||||
rect.setAttribute("y", y);
|
||||
text.setAttribute("x", x + nodeWidth/2);
|
||||
text.setAttribute("y", y + 10);
|
||||
});
|
||||
}
|
||||
// SSE stream
|
||||
const evtSource = new EventSource("/api/stream");
|
||||
evtSource.onmessage = e => {
|
||||
const evt = JSON.parse(e.data);
|
||||
if(evt.fromNode === evt.toNode){
|
||||
redrawPods(evt.toNode);
|
||||
return;
|
||||
}
|
||||
if(!evt.fromNode){ // new pod appears
|
||||
addPod(evt.pod, evt.toNode);
|
||||
redrawPods(evt.toNode);
|
||||
} else { // pod moves
|
||||
flyPod(evt.pod, evt.fromNode, evt.toNode);
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user