github.com/hernad/nomad@v1.6.112/ui/app/components/topo-viz/node.js (about) 1 /** 2 * Copyright (c) HashiCorp, Inc. 3 * SPDX-License-Identifier: MPL-2.0 4 */ 5 6 import Component from '@glimmer/component'; 7 import { tracked } from '@glimmer/tracking'; 8 import { action } from '@ember/object'; 9 import { guidFor } from '@ember/object/internals'; 10 11 export default class TopoVizNode extends Component { 12 @tracked data = { cpu: [], memory: [] }; 13 @tracked dimensionsWidth = 0; 14 @tracked padding = 5; 15 @tracked activeAllocation = null; 16 17 get height() { 18 return this.args.heightScale 19 ? this.args.heightScale(this.args.node.memory) 20 : 15; 21 } 22 23 get labelHeight() { 24 return this.height / 2; 25 } 26 27 get paddingLeft() { 28 const labelWidth = 20; 29 return this.padding + labelWidth; 30 } 31 32 // Since strokes are placed centered on the perimeter of fills, The width of the stroke needs to be removed from 33 // the height of the fill to match unstroked height and avoid clipping. 34 get selectedHeight() { 35 return this.height - 1; 36 } 37 38 // Since strokes are placed centered on the perimeter of fills, half the width of the stroke needs to be added to 39 // the yOffset to match heights with unstroked shapes. 40 get selectedYOffset() { 41 return this.height + 2.5; 42 } 43 44 get yOffset() { 45 return this.height + 2; 46 } 47 48 get maskHeight() { 49 return this.height + this.yOffset; 50 } 51 52 get totalHeight() { 53 return this.maskHeight + this.padding * 2; 54 } 55 56 get maskId() { 57 return `topo-viz-node-mask-${guidFor(this)}`; 58 } 59 60 get count() { 61 return this.allocations.length; 62 } 63 64 get allocations() { 65 // Sort by the delta between memory and cpu percent. This creates the least amount of 66 // drift between the positional alignment of an alloc's cpu and memory representations. 67 return this.args.node.allocations 68 .filterBy('allocation.isScheduled') 69 .sort((a, b) => { 70 const deltaA = Math.abs(a.memoryPercent - a.cpuPercent); 71 const deltaB = Math.abs(b.memoryPercent - b.cpuPercent); 72 return deltaA - deltaB; 73 }); 74 } 75 76 @action 77 async reloadNode() { 78 if (this.args.node.isPartial) { 79 await this.args.node.reload(); 80 this.data = this.computeData(this.dimensionsWidth); 81 } 82 } 83 84 @action 85 render(svg) { 86 this.dimensionsWidth = svg.clientWidth - this.padding - this.paddingLeft; 87 this.data = this.computeData(this.dimensionsWidth); 88 } 89 90 @action 91 updateRender(svg) { 92 // Only update all data when the width changes 93 const newWidth = svg.clientWidth - this.padding - this.paddingLeft; 94 if (newWidth !== this.dimensionsWidth) { 95 this.dimensionsWidth = newWidth; 96 this.data = this.computeData(this.dimensionsWidth); 97 } 98 } 99 100 @action 101 highlightAllocation(allocation, { target }) { 102 this.activeAllocation = allocation; 103 this.args.onAllocationFocus && 104 this.args.onAllocationFocus(allocation, target); 105 } 106 107 @action 108 allocationBlur() { 109 this.args.onAllocationBlur && this.args.onAllocationBlur(); 110 } 111 112 @action 113 clearHighlight() { 114 this.activeAllocation = null; 115 } 116 117 @action 118 selectNode() { 119 if (this.args.isDense && this.args.onNodeSelect) { 120 this.args.onNodeSelect(this.args.node.isSelected ? null : this.args.node); 121 } 122 } 123 124 @action 125 selectAllocation(allocation) { 126 if (this.args.onAllocationSelect) this.args.onAllocationSelect(allocation); 127 } 128 129 containsActiveTaskGroup() { 130 return this.args.node.allocations.some( 131 (allocation) => 132 allocation.taskGroupName === this.args.activeTaskGroup && 133 allocation.belongsTo('job').id() === this.args.activeJobId 134 ); 135 } 136 137 computeData(width) { 138 const allocations = this.allocations; 139 let cpuOffset = 0; 140 let memoryOffset = 0; 141 142 const cpu = []; 143 const memory = []; 144 for (const allocation of allocations) { 145 const { cpuPercent, memoryPercent, isSelected } = allocation; 146 const isFirst = allocation === allocations[0]; 147 148 let cpuWidth = cpuPercent * width - 1; 149 let memoryWidth = memoryPercent * width - 1; 150 if (isFirst) { 151 cpuWidth += 0.5; 152 memoryWidth += 0.5; 153 } 154 if (isSelected) { 155 cpuWidth--; 156 memoryWidth--; 157 } 158 159 cpu.push({ 160 allocation, 161 offset: cpuOffset * 100, 162 percent: cpuPercent * 100, 163 width: Math.max(cpuWidth, 0), 164 x: cpuOffset * width + (isFirst ? 0 : 0.5) + (isSelected ? 0.5 : 0), 165 className: allocation.allocation.clientStatus, 166 }); 167 memory.push({ 168 allocation, 169 offset: memoryOffset * 100, 170 percent: memoryPercent * 100, 171 width: Math.max(memoryWidth, 0), 172 x: memoryOffset * width + (isFirst ? 0 : 0.5) + (isSelected ? 0.5 : 0), 173 className: allocation.allocation.clientStatus, 174 }); 175 176 cpuOffset += cpuPercent; 177 memoryOffset += memoryPercent; 178 } 179 180 const cpuRemainder = { 181 x: cpuOffset * width + 0.5, 182 width: Math.max(width - cpuOffset * width, 0), 183 }; 184 const memoryRemainder = { 185 x: memoryOffset * width + 0.5, 186 width: Math.max(width - memoryOffset * width, 0), 187 }; 188 189 return { 190 cpu, 191 memory, 192 cpuRemainder, 193 memoryRemainder, 194 cpuLabel: { x: -this.paddingLeft / 2, y: this.height / 2 + this.yOffset }, 195 memoryLabel: { x: -this.paddingLeft / 2, y: this.height / 2 }, 196 }; 197 } 198 }