Skip to content

Commit 78e61ad

Browse files
authored
fix: align pool/AZ headers with cluster border and improve mock data (#16)
* fix: align node pool type bars with cluster border - Defer pool header rect drawing to after final content width is calculated - Exclude trailing column padding from overallMaxX to get true content edge - Draw rects at exact final width instead of scaling, eliminating stroke distortion - Add symmetric padding (10px) on both left and right sides of cluster - Ensures pool type headers (MASTER, INFRA, WORKER) extend full cluster width * style(cluster): align pool and AZ header borders with node stroke edges Compensate for PIXI.js 2px node stroke extending 1px outward on each side by offsetting header rects by -1 and widening by +2. Also use `left` constant for unassigned pods spacing. * feat(mock): improve mock data with realistic pool/AZ distribution Configure mock clusters with 3 masters (1 per AZ), 3-5 infra nodes (at least 1 per AZ), and 3-7 workers per AZ using deterministic hash-based variation across clusters.
1 parent 2213ef7 commit 78e61ad

3 files changed

Lines changed: 69 additions & 44 deletions

File tree

app/src/cluster.js

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,9 @@ export default class Cluster extends PIXI.Graphics {
141141
totalNodesInPool += pool.azGroups[az].length
142142
}
143143

144-
// Add pool header/label
144+
// Add pool header/label (rect drawn later with correct width)
145145
const poolHeader = new PIXI.Graphics()
146146
poolHeader.allowChildren = true
147-
poolHeader.rect(0, 0, 200, poolHeaderHeight) // Width will be adjusted later
148-
poolHeader.fill({ color: poolColor, alpha: 0.3 })
149-
poolHeader.stroke({ width: 1, color: poolColor, alpha: 0.8 })
150147

151148
const poolLabel = new PIXI.Text({
152149
text: `${poolType.toUpperCase()} (${totalNodesInPool})`,
@@ -157,7 +154,7 @@ export default class Cluster extends PIXI.Graphics {
157154
poolHeader.addChild(poolLabel)
158155
poolHeader.y = currentY
159156
poolHeader.x = left
160-
poolHeaders.push({ header: poolHeader, poolType: poolType })
157+
poolHeaders.push({ header: poolHeader, color: poolColor })
161158

162159
currentY += poolHeaderHeight + 2
163160

@@ -186,7 +183,8 @@ export default class Cluster extends PIXI.Graphics {
186183
if (showAZHeaders) {
187184
const azHeader = new PIXI.Graphics()
188185
azHeader.allowChildren = true
189-
azHeader.rect(0, 0, azWidthPx, azHeaderHeight)
186+
// Offset by -1 and widen by +2 to align with node 2px stroke (extends 1px outward each side)
187+
azHeader.rect(-1, 0, azWidthPx + 2, azHeaderHeight)
190188
azHeader.fill({ color: poolColor, alpha: 0.15 })
191189
azHeader.stroke({ width: 1, color: poolColor, alpha: 0.4 })
192190

@@ -233,28 +231,18 @@ export default class Cluster extends PIXI.Graphics {
233231
azStartX += azWidthPx + azColumnPadding
234232
}
235233

236-
// Track the rightmost point
237-
if (azStartX > overallMaxX) {
238-
overallMaxX = azStartX
234+
// Track the rightmost content edge (exclude trailing azColumnPadding)
235+
const poolRightEdge = azStartX - azColumnPadding
236+
if (poolRightEdge > overallMaxX) {
237+
overallMaxX = poolRightEdge
239238
}
240239

241240
// Move currentY past this pool's content
242241
currentY = poolStartY + poolMaxHeight + 5 // Extra spacing between pools
243242
}
244243

245-
// Update pool header widths and add them
246-
for (const { header } of poolHeaders) {
247-
header.width = overallMaxX - left
248-
this.addChild(header)
249-
}
250-
251-
// Add AZ headers
252-
for (const azHeader of azHeaders) {
253-
this.addChild(azHeader)
254-
}
255-
256244
// Place unassigned pods
257-
let unassignedX = overallMaxX + 10
245+
let unassignedX = overallMaxX + left // Consistent spacing
258246
const unassignedY = top + poolHeaderHeight + 2
259247

260248
for (const pod of Object.values(this.cluster.unassigned_pods)) {
@@ -271,8 +259,23 @@ export default class Cluster extends PIXI.Graphics {
271259
overallMaxX = unassignedX
272260
}
273261

274-
// Draw cluster border
275-
const width = overallMaxX
262+
// Draw pool header rects at correct width (after all content is positioned)
263+
// Add +2 to account for node 2px stroke extending 1px outward on each side
264+
const poolHeaderWidth = overallMaxX - left + 2
265+
for (const { header, color } of poolHeaders) {
266+
header.rect(-1, 0, poolHeaderWidth, poolHeaderHeight)
267+
header.fill({ color: color, alpha: 0.3 })
268+
header.stroke({ width: 1, color: color, alpha: 0.8 })
269+
this.addChild(header)
270+
}
271+
272+
// Add AZ headers
273+
for (const azHeader of azHeaders) {
274+
this.addChild(azHeader)
275+
}
276+
277+
// Draw cluster border (add right padding equal to left)
278+
const width = overallMaxX + left
276279
const height = currentY - padding
277280
this.rect(0, 0, width, height)
278281
this.stroke({ width: 2, color: App.current.theme.primaryColor })

kube_ops_view/mock.py

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,40 +92,62 @@ def query_mock_cluster(cluster):
9292
index = int(cluster.id.split("-")[-1])
9393
nodes = {}
9494

95-
# Define availability zones for realistic distribution
9695
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
9796

98-
for i in range(10):
99-
# add/remove the second to last node every 13 seconds
100-
if i == 8 and int(time.time() / 13) % 2 == 0:
97+
# Node layout per pool:
98+
# Masters: 3 nodes, 1 per AZ (indices 0-2)
99+
# Infra: 3-5 nodes, at least 1 per AZ (indices 3-7)
100+
# Workers: 3-7 nodes per AZ (indices 8+)
101+
#
102+
# Use cluster index to vary infra/worker counts across mock clusters
103+
infra_per_az = [1, 1, 1] # base: 1 per AZ = 3 total
104+
if index >= 1:
105+
infra_per_az[0] = 2 # 4 total for cluster 1+
106+
if index >= 2:
107+
infra_per_az[1] = 2 # 5 total for cluster 2+
108+
109+
worker_per_az = [
110+
3 + hash_int((index + 1) * 101) % 5, # 3-7 for AZ a
111+
3 + hash_int((index + 1) * 202) % 5, # 3-7 for AZ b
112+
3 + hash_int((index + 1) * 303) % 5, # 3-7 for AZ c
113+
]
114+
115+
# Build node list: (role, az_index)
116+
node_specs = []
117+
118+
# Masters: 1 per AZ
119+
for az_i in range(3):
120+
node_specs.append(("master", az_i))
121+
122+
# Infra: variable per AZ
123+
for az_i in range(3):
124+
for _ in range(infra_per_az[az_i]):
125+
node_specs.append(("infra", az_i))
126+
127+
# Workers: 3-7 per AZ
128+
for az_i in range(3):
129+
for _ in range(worker_per_az[az_i]):
130+
node_specs.append(("worker", az_i))
131+
132+
for i, (role, az_i) in enumerate(node_specs):
133+
# add/remove one worker node every 13 seconds for dynamism
134+
if role == "worker" and i == len(node_specs) - 1 and int(time.time() / 13) % 2 == 0:
101135
continue
102-
labels = {}
103136

104-
# Assign AZ based on node index (distribute across zones)
105-
az = availability_zones[i % len(availability_zones)]
106-
labels["topology.kubernetes.io/zone"] = az
137+
labels = {}
138+
labels["topology.kubernetes.io/zone"] = availability_zones[az_i]
107139

108-
# Assign node roles based on index for realistic pool distribution
109-
# Nodes 0-2: master/control-plane nodes
110-
# Nodes 3-4: infra nodes (some with worker role too for multi-role demo)
111-
# Nodes 5-9: worker nodes
112-
if i < 3:
113-
# Master nodes
140+
if role == "master":
114141
if index == 0:
115142
labels["node-role.kubernetes.io/master"] = ""
116143
labels["node-role.kubernetes.io/control-plane"] = ""
117144
elif index == 1:
118145
labels["node-role.kubernetes.io/control-plane"] = ""
119146
else:
120147
labels["kubernetes.io/role"] = "master"
121-
elif i < 5:
122-
# Infra nodes
148+
elif role == "infra":
123149
labels["node-role.kubernetes.io/infra"] = ""
124-
# Node 3 has both infra and worker roles (multi-role demo)
125-
if i == 3:
126-
labels["node-role.kubernetes.io/worker"] = ""
127150
else:
128-
# Worker nodes
129151
labels["node-role.kubernetes.io/worker"] = ""
130152

131153
pods = {}
@@ -158,10 +180,10 @@ def query_mock_cluster(cluster):
158180
"allocatable": {"cpu": "7800m", "memory": "62Gi"},
159181
},
160182
"pods": pods,
161-
# get data from containers (usage)
162183
"usage": {"cpu": f"{usage_cpu}m", "memory": f"{usage_memory}Mi"},
163184
}
164185
nodes[node["name"]] = node
186+
165187
pod = generate_mock_pod(index, 11, index)
166188
unassigned_pods = {"{}/{}".format(pod["namespace"], pod["name"]): pod}
167189
return {

screenshot.png

33 KB
Loading

0 commit comments

Comments
 (0)