diff --git a/k8s-scheduler-game/src/ui.js b/k8s-scheduler-game/src/ui.js index 7098bcf..182dd61 100644 --- a/k8s-scheduler-game/src/ui.js +++ b/k8s-scheduler-game/src/ui.js @@ -46,12 +46,14 @@ export class UI { nodeSel.innerHTML = Object.values(INSTANCE_TYPES) .map((t) => { const gpu = t.gpu ? `, ${t.gpu} GPU` : ""; + const price = + t.family === "spot" ? `~$${(t.cost * 0.45).toFixed(2)}/hr spot` : `$${t.cost.toFixed(2)}/hr`; return ``; + )}${gpu} · ${price}`; }) .join(""); - nodeSel.value = "general-large"; + nodeSel.value = "c5.2xlarge"; } wire() { @@ -285,7 +287,7 @@ export class UI { { l: "Running", v: g.runningCount(), cls: "" }, { l: "Nodes", v: `${ready}/${s.nodes.length}`, cls: "" }, { l: "Cost", v: `$${g.hourlyCost().toFixed(2)}/hr`, cls: "" }, - { l: "Spot price", v: `${spot.toFixed(2)}×`, cls: spot >= 1.5 ? "bad" : spot <= 0.8 ? "good" : "" }, + { l: "spot vs on-dmd", v: `${spot.toFixed(2)}×`, cls: spot <= 0.5 ? "good" : spot >= 0.75 ? "bad" : "" }, { l: s.upgradePending ? `upgrade: ${outdated} left` : "k8s version", v: `v1.${s.clusterMinor}`, @@ -346,7 +348,9 @@ export class UI { .map((t) => `⛔ ${t.key}=${t.value}:${t.effect}`) .join(""); const spotTag = node.spot - ? `⚡ spot $${(node.cost * s.spotPrice).toFixed(2)}/hr` + ? `⚡ spot $${(node.cost * s.spotPrice).toFixed(2)}/hr · −${Math.round( + (1 - s.spotPrice) * 100 + )}%` : ""; const cpuBar = this.meter("CPU", used.cpu, node.cpu, meterPods, "cpu"); diff --git a/k8s-scheduler-game/styles.css b/k8s-scheduler-game/styles.css index 0068665..1f28f92 100644 --- a/k8s-scheduler-game/styles.css +++ b/k8s-scheduler-game/styles.css @@ -271,7 +271,7 @@ button:focus-visible { grid-template-columns: 1fr 340px; gap: 16px; padding: 16px 22px; - align-items: start; + align-items: stretch; min-height: 0; } .panel { @@ -292,11 +292,22 @@ button:focus-visible { letter-spacing: 0.3px; } +/* The node and queue panels are flex columns whose lists scroll internally, + so a long cluster never overflows its row and covers the event log. */ +.nodes, +.queue { + display: flex; + flex-direction: column; + min-height: 0; +} .nodegrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; padding: 16px; + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; } /* --------------------------------------------------------------- node card */ @@ -543,10 +554,6 @@ button:focus-visible { } /* --------------------------------------------------------------- queue */ -.queue { - position: sticky; - top: 16px; -} .hint { margin: 0; padding: 8px 16px; @@ -559,7 +566,8 @@ button:focus-visible { display: flex; flex-direction: column; gap: 8px; - max-height: calc(100vh - 360px); + flex: 1 1 auto; + min-height: 0; overflow-y: auto; } .empty-state {