From c2633a50c01eec1f1769fc391f3d70ed194f7285 Mon Sep 17 00:00:00 2001 From: sevensolutions <84123899+sevensolutions@users.noreply.github.com> Date: Fri, 17 Apr 2026 17:24:42 +0000 Subject: [PATCH 1/3] feat(ui): show reserved resources in topology view --- ui/app/components/svg-patterns.hbs | 6 +++ ui/app/components/topo-viz.hbs | 16 ++++++ ui/app/components/topo-viz.js | 13 +++++ ui/app/components/topo-viz/datacenter.hbs | 1 + ui/app/components/topo-viz/node.hbs | 40 +++++++++++++++ ui/app/components/topo-viz/node.js | 49 ++++++++++++++++++- ui/app/styles/charts/colors.scss | 11 +++++ ui/app/styles/charts/topo-viz-node.scss | 10 ++++ ui/app/templates/topology.hbs | 8 +++ ui/mirage/scenarios/topo.js | 14 ++++++ .../components/topo-viz/node-test.js | 40 +++++++++++++++ ui/tests/pages/components/topo-viz/node.js | 3 ++ 12 files changed, 209 insertions(+), 2 deletions(-) diff --git a/ui/app/components/svg-patterns.hbs b/ui/app/components/svg-patterns.hbs index a87f153ea67..8f878406bda 100644 --- a/ui/app/components/svg-patterns.hbs +++ b/ui/app/components/svg-patterns.hbs @@ -11,6 +11,12 @@ + {{! Evenly sized diagonal stripes in orange}} + + + + + diff --git a/ui/app/components/topo-viz.hbs b/ui/app/components/topo-viz.hbs index ac57a3638b0..e84b49ee7a7 100644 --- a/ui/app/components/topo-viz.hbs +++ b/ui/app/components/topo-viz.hbs @@ -22,6 +22,7 @@ @onAllocationSelect={{this.associateAllocations}} @onAllocationFocus={{this.showTooltip}} @onAllocationBlur={{this.hideTooltip}} + @onReservedFocus={{this.showReservedTooltip}} @onNodeSelect={{this.showNodeDetails}} /> @@ -50,6 +51,21 @@ {{/let}} +
+ {{#let this.highlightReserved as |reserved|}} +
    +
  1. + Memory + {{format-scheduled-bytes reserved.memory start="MiB"}} reserved +
  2. +
  3. + CPU + {{format-scheduled-hertz reserved.cpu}} reserved +
  4. +
+ {{/let}} +
+ {{#if this.activeAllocation}} diff --git a/ui/app/components/topo-viz.js b/ui/app/components/topo-viz.js index 9cd3687ea8e..50fc019c84d 100644 --- a/ui/app/components/topo-viz.js +++ b/ui/app/components/topo-viz.js @@ -26,6 +26,7 @@ export default class TopoViz extends Component { @tracked viewportColumns = 2; @tracked highlightAllocation = null; + @tracked highlightReserved = null; @tracked tooltipProps = {}; @styleStringProperty('tooltipProps') tooltipStyle; @@ -192,6 +193,17 @@ export default class TopoViz extends Component { @action showTooltip(allocation, element) { const bbox = element.getBoundingClientRect(); this.highlightAllocation = allocation; + this.highlightReserved = null; + this.tooltipProps = { + left: window.scrollX + bbox.left + bbox.width / 2, + top: window.scrollY + bbox.top, + }; + } + + @action showReservedTooltip(reserved, element) { + const bbox = element.getBoundingClientRect(); + this.highlightReserved = reserved; + this.highlightAllocation = null; this.tooltipProps = { left: window.scrollX + bbox.left + bbox.width / 2, top: window.scrollY + bbox.top, @@ -200,6 +212,7 @@ export default class TopoViz extends Component { @action hideTooltip() { this.highlightAllocation = null; + this.highlightReserved = null; } @action diff --git a/ui/app/components/topo-viz/datacenter.hbs b/ui/app/components/topo-viz/datacenter.hbs index a133e71a11f..491957a433d 100644 --- a/ui/app/components/topo-viz/datacenter.hbs +++ b/ui/app/components/topo-viz/datacenter.hbs @@ -24,6 +24,7 @@ @onAllocationSelect={{@onAllocationSelect}} @onAllocationFocus={{@onAllocationFocus}} @onAllocationBlur={{@onAllocationBlur}} + @onReservedFocus={{@onReservedFocus}} @onNodeSelect={{@onNodeSelect}} /> diff --git a/ui/app/components/topo-viz/node.hbs b/ui/app/components/topo-viz/node.hbs index ff2801c9329..3cdec47f5a5 100644 --- a/ui/app/components/topo-viz/node.hbs +++ b/ui/app/components/topo-viz/node.hbs @@ -83,6 +83,25 @@ width="{{this.data.memoryRemainder.width}}px" height="{{this.height}}px" /> {{/if}} + {{#if this.data.memoryReserved}} + + + + + {{/if}} {{#each this.data.memory key="allocation.id" as |memory|}} {{/if}} + {{#if this.data.cpuReserved}} + + + + + {{/if}} {{#each this.data.cpu key="allocation.id" as |cpu|}} 0 + ? { + x: (1 - cpuReservedPercent) * width + 0.5, + width: Math.max(cpuReservedPercent * width - 0.5, 0), + } + : null; + const memoryReserved = + memoryReservedPercent > 0 + ? { + x: (1 - memoryReservedPercent) * width + 0.5, + width: Math.max(memoryReservedPercent * width - 0.5, 0), + } + : null; + return { cpu, memory, cpuRemainder, memoryRemainder, + cpuReserved, + memoryReserved, cpuLabel: { x: -this.paddingLeft / 2, y: this.height / 2 + this.yOffset }, memoryLabel: { x: -this.paddingLeft / 2, y: this.height / 2 }, }; diff --git a/ui/app/styles/charts/colors.scss b/ui/app/styles/charts/colors.scss index 925fc58577f..922ee5d9711 100644 --- a/ui/app/styles/charts/colors.scss +++ b/ui/app/styles/charts/colors.scss @@ -107,6 +107,17 @@ $canceled: $dark; ); } + &.reserved { + $reserved-base: lighten($orange, 20%); + background: repeating-linear-gradient( + -45deg, + $reserved-base, + $reserved-base 3px, + darken($reserved-base, 20%) 3px, + darken($reserved-base, 20%) 6px + ); + } + &.running { background: $running; } diff --git a/ui/app/styles/charts/topo-viz-node.scss b/ui/app/styles/charts/topo-viz-node.scss index 81a21d95e50..67509207bbe 100644 --- a/ui/app/styles/charts/topo-viz-node.scss +++ b/ui/app/styles/charts/topo-viz-node.scss @@ -37,6 +37,16 @@ fill: lighten($grey-lighter, 5%); } + .reserved-segment { + rect.layer-0 { + fill: #fcc187; + } + + rect.layer-1 { + fill: url(#diagonal-stripe-orange); + } + } + .dimensions.is-active { .bar { opacity: 0.2; diff --git a/ui/app/templates/topology.hbs b/ui/app/templates/topology.hbs index 6fe5db9410b..beb62ea49ff 100644 --- a/ui/app/templates/topology.hbs +++ b/ui/app/templates/topology.hbs @@ -117,6 +117,14 @@ Starting +
+
+ +
+
+ Reserved +
+
diff --git a/ui/mirage/scenarios/topo.js b/ui/mirage/scenarios/topo.js index c6a0ae397e6..4e44cea3459 100644 --- a/ui/mirage/scenarios/topo.js +++ b/ui/mirage/scenarios/topo.js @@ -23,6 +23,13 @@ export function topoSmall(server) { nodeResources: genResources(3000, 5192), }); + // Add one node with reserved resources to show reserved node in topology view + server.createList('node', 1, 'reserved', { + datacenter: 'dc1', + status: 'ready', + nodeResources: genResources(8000, 12192), + }); + const jobResources = [ ['M: 2560, C: 150'], ['M: 128, C: 400'], @@ -80,6 +87,13 @@ export function topoMedium(server) { nodeResources: genResources(8000, 12192), }); + // Add some nodes with reserved resources to show reserved node in topology view + server.createList('node', 3, 'reserved', { + datacenter: 'us-east-1', + status: 'ready', + nodeResources: genResources(8000, 12192), + }); + const jobResources = [ ['M: 2560, C: 150'], ['M: 128, C: 400'], diff --git a/ui/tests/integration/components/topo-viz/node-test.js b/ui/tests/integration/components/topo-viz/node-test.js index bb58fff80c5..0bd5d0f119a 100644 --- a/ui/tests/integration/components/topo-viz/node-test.js +++ b/ui/tests/integration/components/topo-viz/node-test.js @@ -443,4 +443,44 @@ module('Integration | Component | TopoViz::Node', function (hooks) { await render(commonTemplate); assert.equal(TopoVizNode.emptyMessage, 'Empty Client'); }); + + test('when the node has reserved resources, reserved segments are shown in the CPU and memory bars', async function (assert) { + const node = nodeGen('Node One', 'dc1', 1000, 1000); + node.node.reserved = { cpu: 100, memory: 200 }; + + this.setProperties( + props({ + node: { + ...node, + allocations: [allocGen(node, 100, 100)], + }, + }) + ); + + await render(commonTemplate); + + assert.ok( + TopoVizNode.reservedMemoryRect, + 'reserved memory segment is rendered' + ); + assert.ok(TopoVizNode.reservedCpuRect, 'reserved cpu segment is rendered'); + }); + + test('when the node has no reserved resources, no reserved segments are shown', async function (assert) { + const node = nodeGen('Node One', 'dc1', 1000, 1000); + + this.setProperties( + props({ + node: { + ...node, + allocations: [allocGen(node, 100, 100)], + }, + }) + ); + + await render(commonTemplate); + + assert.notOk(TopoVizNode.reservedMemoryRect, 'no reserved memory segment'); + assert.notOk(TopoVizNode.reservedCpuRect, 'no reserved cpu segment'); + }); }); diff --git a/ui/tests/pages/components/topo-viz/node.js b/ui/tests/pages/components/topo-viz/node.js index eb146fb6d51..4727a43885e 100644 --- a/ui/tests/pages/components/topo-viz/node.js +++ b/ui/tests/pages/components/topo-viz/node.js @@ -47,6 +47,9 @@ export default (scope) => ({ id: attribute('data-test-cpu-rect'), }), + reservedMemoryRect: isPresent('[data-test-reserved-memory]'), + reservedCpuRect: isPresent('[data-test-reserved-cpu]'), + mouseout: triggerable('mouseout', '[data-test-topo-node-svg]'), emptyMessage: text('[data-test-empty-message]'), From 7c5bf45b45e96788cec39b87f2be0f953b7bf657 Mon Sep 17 00:00:00 2001 From: sevensolutions <84123899+sevensolutions@users.noreply.github.com> Date: Fri, 17 Apr 2026 17:34:19 +0000 Subject: [PATCH 2/3] chore: add changelog --- .changelog/27846.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/27846.txt diff --git a/.changelog/27846.txt b/.changelog/27846.txt new file mode 100644 index 00000000000..8ac30642047 --- /dev/null +++ b/.changelog/27846.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: The topology page now also shows reserved resources (CPU and memory) of every node using an orange bar at the end +``` From 0d1bd4204cecac045b02f94953257b5a22955cf1 Mon Sep 17 00:00:00 2001 From: sevensolutions <84123899+sevensolutions@users.noreply.github.com> Date: Fri, 17 Apr 2026 17:39:43 +0000 Subject: [PATCH 3/3] chore: make linter happy --- ui/app/components/topo-viz/node.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ui/app/components/topo-viz/node.js b/ui/app/components/topo-viz/node.js index 90480a0c7e0..aef60e26373 100644 --- a/ui/app/components/topo-viz/node.js +++ b/ui/app/components/topo-viz/node.js @@ -201,10 +201,7 @@ export default class TopoVizNode extends Component { const cpuRemainder = { x: cpuOffset * width + 0.5, - width: Math.max( - (1 - cpuReservedPercent) * width - cpuOffset * width, - 0 - ), + width: Math.max((1 - cpuReservedPercent) * width - cpuOffset * width, 0), }; const memoryRemainder = { x: memoryOffset * width + 0.5,