my-homelab-configs/apps/demos-static/public/architecture-simulator/architecture-simulator.js

66 lines
2.5 KiB
JavaScript

const canvas = document.getElementById('arch-canvas');
const ctx = canvas.getContext('2d');
const state = {
users: 120,
databaseLoad: 45,
servers: [
{ name: 'web-1', healthy: true, load: 34 },
{ name: 'web-2', healthy: true, load: 28 },
{ name: 'web-3', healthy: true, load: 31 },
],
};
function box(x, y, width, height, fill, stroke, label) {
ctx.fillStyle = fill;
ctx.strokeStyle = stroke;
ctx.beginPath();
ctx.roundRect(x, y, width, height, 8);
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#102a43';
ctx.fillText(label, x + 12, y + height / 2 + 5);
}
function draw() {
const healthy = state.servers.filter((server) => server.healthy);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#f8fafc';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = '14px Arial';
ctx.fillStyle = '#102a43';
ctx.fillText(`${state.users} simulated users`, 30, 36);
box(240, 95, 110, 64, '#dbeafe', '#2563eb', 'Load balancer');
state.servers.forEach((server, index) => {
const x = 440;
const y = 40 + index * 68;
server.load = server.healthy && healthy.length ? Math.min(100, Math.round(state.users / healthy.length / 6)) : 0;
ctx.strokeStyle = '#9fb3c8';
ctx.beginPath();
ctx.moveTo(350, 127);
ctx.lineTo(x, y + 24);
ctx.stroke();
box(x, y, 120, 48, server.healthy ? '#e8fff1' : '#ffe8e8', server.healthy ? '#2f855a' : '#c53030', `${server.name} ${server.healthy ? server.load + '%' : 'down'}`);
});
state.databaseLoad = Math.min(100, state.databaseLoad + state.users / 400);
ctx.strokeStyle = '#9fb3c8';
ctx.beginPath();
ctx.moveTo(560, 127);
ctx.lineTo(620, 127);
ctx.stroke();
box(620, 95, 72, 64, state.databaseLoad > 80 ? '#fff5d6' : '#edf2f7', state.databaseLoad > 80 ? '#d69e2e' : '#52606d', `DB ${Math.round(state.databaseLoad)}%`);
document.getElementById('arch-status').textContent = healthy.length ? `Traffic split across ${healthy.length} healthy web nodes.` : 'All web nodes are down. Auto-scaling is needed.';
}
document.getElementById('add-users').addEventListener('click', () => { state.users += 220; state.databaseLoad += 10; draw(); });
document.getElementById('server-crash').addEventListener('click', () => {
const server = state.servers.find((item) => item.healthy);
if (server) server.healthy = false;
draw();
});
document.getElementById('auto-scale').addEventListener('click', () => {
state.servers.push({ name: `web-${state.servers.length + 1}`, healthy: true, load: 10 });
state.databaseLoad = Math.max(35, state.databaseLoad - 20);
draw();
});
draw();