mirror of
https://github.com/imjasonh/terraform-playground
synced 2026-07-07 23:35:16 +00:00
UI: spot savings multiplier, AWS pool prices, scrollable node grid
- Show the spot price as a <1× multiplier of on-demand (e.g. 0.42×) with a 'spot vs on-dmd' label, colored by how much you're saving; node spot tags show the effective $/hr and savings %. - Node-pool dropdown shows realistic specs and prices (spot priced ~0.45×). - Make the node and queue panels flex columns whose lists scroll internally, so a large cluster no longer overflows its row and covers the event log. Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
This commit is contained in:
parent
3338b31e1a
commit
80538d1509
2 changed files with 22 additions and 10 deletions
|
|
@ -46,12 +46,14 @@ export class UI {
|
||||||
nodeSel.innerHTML = Object.values(INSTANCE_TYPES)
|
nodeSel.innerHTML = Object.values(INSTANCE_TYPES)
|
||||||
.map((t) => {
|
.map((t) => {
|
||||||
const gpu = t.gpu ? `, ${t.gpu} GPU` : "";
|
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 `<option value="${t.key}">${t.key} — ${fmtCpu(t.cpu)} vCPU / ${fmtMem(
|
return `<option value="${t.key}">${t.key} — ${fmtCpu(t.cpu)} vCPU / ${fmtMem(
|
||||||
t.mem
|
t.mem
|
||||||
)}${gpu} · $${t.cost.toFixed(2)}/hr</option>`;
|
)}${gpu} · ${price}</option>`;
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
nodeSel.value = "general-large";
|
nodeSel.value = "c5.2xlarge";
|
||||||
}
|
}
|
||||||
|
|
||||||
wire() {
|
wire() {
|
||||||
|
|
@ -285,7 +287,7 @@ export class UI {
|
||||||
{ l: "Running", v: g.runningCount(), cls: "" },
|
{ l: "Running", v: g.runningCount(), cls: "" },
|
||||||
{ l: "Nodes", v: `${ready}/${s.nodes.length}`, cls: "" },
|
{ l: "Nodes", v: `${ready}/${s.nodes.length}`, cls: "" },
|
||||||
{ l: "Cost", v: `$${g.hourlyCost().toFixed(2)}/hr`, 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",
|
l: s.upgradePending ? `upgrade: ${outdated} left` : "k8s version",
|
||||||
v: `v1.${s.clusterMinor}`,
|
v: `v1.${s.clusterMinor}`,
|
||||||
|
|
@ -346,7 +348,9 @@ export class UI {
|
||||||
.map((t) => `<span class="tag taint">⛔ ${t.key}=${t.value}:${t.effect}</span>`)
|
.map((t) => `<span class="tag taint">⛔ ${t.key}=${t.value}:${t.effect}</span>`)
|
||||||
.join("");
|
.join("");
|
||||||
const spotTag = node.spot
|
const spotTag = node.spot
|
||||||
? `<span class="tag spot">⚡ spot $${(node.cost * s.spotPrice).toFixed(2)}/hr</span>`
|
? `<span class="tag spot">⚡ spot $${(node.cost * s.spotPrice).toFixed(2)}/hr · −${Math.round(
|
||||||
|
(1 - s.spotPrice) * 100
|
||||||
|
)}%</span>`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const cpuBar = this.meter("CPU", used.cpu, node.cpu, meterPods, "cpu");
|
const cpuBar = this.meter("CPU", used.cpu, node.cpu, meterPods, "cpu");
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ button:focus-visible {
|
||||||
grid-template-columns: 1fr 340px;
|
grid-template-columns: 1fr 340px;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
padding: 16px 22px;
|
padding: 16px 22px;
|
||||||
align-items: start;
|
align-items: stretch;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
.panel {
|
.panel {
|
||||||
|
|
@ -292,11 +292,22 @@ button:focus-visible {
|
||||||
letter-spacing: 0.3px;
|
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 {
|
.nodegrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------- node card */
|
/* --------------------------------------------------------------- node card */
|
||||||
|
|
@ -543,10 +554,6 @@ button:focus-visible {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------- queue */
|
/* --------------------------------------------------------------- queue */
|
||||||
.queue {
|
|
||||||
position: sticky;
|
|
||||||
top: 16px;
|
|
||||||
}
|
|
||||||
.hint {
|
.hint {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
|
|
@ -559,7 +566,8 @@ button:focus-visible {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
max-height: calc(100vh - 360px);
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.empty-state {
|
.empty-state {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue