@skyhook-io/k8s-ui 1.14.0 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/gitops/GitOpsTableView.tsx +168 -11
- package/src/components/gitops/applicationset-row.test.ts +214 -0
- package/src/components/gitops/detail-helpers.test.ts +54 -0
- package/src/components/gitops/detail-helpers.ts +13 -3
- package/src/components/gitops/index.ts +1 -0
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +7 -0
- package/src/components/issues/IssuesView.tsx +1 -1
- package/src/components/issues/types.ts +2 -0
- package/src/components/resources/ResourcesView.tsx +382 -141
- package/src/components/resources/column-header-width.test.ts +75 -0
- package/src/components/resources/column-settings-merge.test.ts +104 -0
- package/src/components/resources/istio-gateway-filter.test.ts +5 -3
- package/src/components/resources/istio-policy-accessors.test.ts +193 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +11 -24
- package/src/components/resources/renderers/eso-cells.tsx +0 -5
- package/src/components/resources/renderers/istio-cells.tsx +36 -26
- package/src/components/resources/renderers/kueue-cells.tsx +0 -9
- package/src/components/resources/renderers/kyverno-cells.tsx +1 -1
- package/src/components/resources/renderers/prometheus-cells.tsx +3 -3
- package/src/components/resources/renderers/ray-cells.tsx +12 -4
- package/src/components/resources/renderers/volcano-cells.tsx +0 -9
- package/src/components/resources/resource-utils-cnpg.ts +5 -0
- package/src/components/resources/resource-utils-eso.test.ts +56 -0
- package/src/components/resources/resource-utils-eso.ts +37 -5
- package/src/components/resources/resource-utils-istio.ts +152 -44
- package/src/components/resources/resource-utils-kyverno.ts +3 -6
- package/src/components/resources/resource-utils-prometheus.ts +51 -23
- package/src/components/resources/resource-utils-ray.test.ts +36 -0
- package/src/components/resources/resource-utils-ray.ts +6 -0
- package/src/components/resources/resource-utils.ts +166 -18
- package/src/components/resources/rules-column-width.test.ts +42 -0
- package/src/components/resources/status-accessors.test.ts +363 -0
- package/src/components/resources/status-overclaim.test.ts +158 -0
- package/src/components/shared/ResourceRendererDispatch.test.tsx +75 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +24 -6
- package/src/components/timeline/DiffViewer.test.tsx +26 -0
- package/src/components/timeline/DiffViewer.tsx +16 -12
- package/src/components/topology/GroupNode.tsx +6 -2
- package/src/components/topology/TopologyGraph.tsx +7 -2
- package/src/components/topology/topology.css +4 -1
- package/src/components/ui/Collapse.tsx +18 -3
- package/src/components/ui/ConfirmDialog.test.tsx +37 -0
- package/src/components/ui/ConfirmDialog.tsx +3 -1
- package/src/components/ui/RowActionMenu.tsx +4 -0
- package/src/components/ui/drawer-components.tsx +5 -3
- package/src/components/workload/WorkloadView.tsx +11 -3
- package/src/types/gitops.ts +51 -0
|
@@ -110,6 +110,7 @@ import {
|
|
|
110
110
|
getGatewayClassController,
|
|
111
111
|
getGatewayClassDescription,
|
|
112
112
|
getRouteStatus,
|
|
113
|
+
getRouteStatusReason,
|
|
113
114
|
getRouteParents,
|
|
114
115
|
getRouteHostnames,
|
|
115
116
|
getRouteBackends,
|
|
@@ -227,6 +228,12 @@ export const SKIP_FILTER_COLUMNS = new Set([
|
|
|
227
228
|
'lastUpdated', 'chart', 'events', 'repo',
|
|
228
229
|
'generators', 'applications', 'destinations', 'sources', 'budget', 'healthy', 'allowed',
|
|
229
230
|
'secrets', 'subjects', 'role', 'entrypoint', 'templates',
|
|
231
|
+
// Trivy and Kyverno report counts. These look filterable but cannot be:
|
|
232
|
+
// getCellFilterValue has no case for them and its fallback probes
|
|
233
|
+
// status[key]/spec[key], while the counts live under report.summary /
|
|
234
|
+
// status.summary. Listing them here stops the header reserving room for a
|
|
235
|
+
// button that can never render.
|
|
236
|
+
'critical', 'high', 'medium', 'low', 'pass', 'fail', 'warn', 'error', 'skip',
|
|
230
237
|
])
|
|
231
238
|
|
|
232
239
|
// Namespace/node cardinality scales with cluster size, not kind enum size;
|
|
@@ -240,11 +247,10 @@ export function isColumnFilterableByDistinctCount(colKey: string, distinctCount:
|
|
|
240
247
|
}
|
|
241
248
|
|
|
242
249
|
// Column definitions per resource kind
|
|
243
|
-
interface Column {
|
|
250
|
+
export interface Column {
|
|
244
251
|
key: string
|
|
245
252
|
label: string
|
|
246
253
|
width?: string
|
|
247
|
-
hideOnMobile?: boolean
|
|
248
254
|
tooltip?: string // Explanation of what this column means
|
|
249
255
|
defaultVisible?: boolean // false = hidden by default, shown via column picker
|
|
250
256
|
defaultWidth?: number // default width in px (used for resizable columns)
|
|
@@ -321,7 +327,179 @@ const COMPARE_COLUMN_STYLE: React.CSSProperties = {
|
|
|
321
327
|
maxWidth: COMPARE_COLUMN_WIDTH,
|
|
322
328
|
}
|
|
323
329
|
|
|
324
|
-
|
|
330
|
+
// Built-in sortable keys. Hoisted so getColumnMinWidth can reserve room for the
|
|
331
|
+
// sort icon without the header render and the width math drifting apart.
|
|
332
|
+
const BUILTIN_SORTABLE_COLUMN_KEYS = new Set([
|
|
333
|
+
'name', 'namespace', 'age', 'status', 'ready', 'restarts', 'type', 'version',
|
|
334
|
+
'desired', 'available', 'upToDate', 'lastSeen', 'count', 'reason', 'object',
|
|
335
|
+
'cpu', 'memory', 'containers',
|
|
336
|
+
])
|
|
337
|
+
|
|
338
|
+
// The header row is px-4 with a `truncate` label, under table-layout:fixed with
|
|
339
|
+
// explicit <col> widths — so a column narrower than its own heading clips to
|
|
340
|
+
// "ADDRESS T…" and stays clipped at every viewport size, because only the Name
|
|
341
|
+
// column flexes. Deriving a floor from the label keeps a declared w-* from
|
|
342
|
+
// being narrower than the word it has to show.
|
|
343
|
+
const HEADER_PADDING_PX = 32 // px-4 on both sides
|
|
344
|
+
const HEADER_SORT_AFFORDANCE_PX = 20 // gap-1 + the w-3.5 chevron / ArrowUpDown
|
|
345
|
+
const HEADER_FILTER_AFFORDANCE_PX = 20 // gap-1 + the p-0.5 filter button
|
|
346
|
+
// Deliberately an upper bound on the rendered header font (DM Sans 500, 12px,
|
|
347
|
+
// uppercase, tracking-wide): measured per-character widths across the curated
|
|
348
|
+
// labels ranged 7.2–8.1px, so 8.2 never under-reserves. Over-reserving costs
|
|
349
|
+
// space the flexible Name column has to spare; under-reserving clips again.
|
|
350
|
+
// Printer and host-extra columns take their label from vendor CRD text, which
|
|
351
|
+
// can be long or non-Latin. Cap the floor so one such label can't size a column
|
|
352
|
+
// off the screen; past this a truncated heading (with its tooltip) is better.
|
|
353
|
+
const HEADER_FLOOR_MAX_PX = 320
|
|
354
|
+
|
|
355
|
+
// Must match the rendered header: text-xs (12px) / font-medium (500) / uppercase
|
|
356
|
+
// / tracking-wide, in the app font. measureText does not apply letter-spacing,
|
|
357
|
+
// so it is added per gap.
|
|
358
|
+
const HEADER_FONT = "500 12px 'DM Sans Variable', 'DM Sans', system-ui, sans-serif"
|
|
359
|
+
const HEADER_LETTER_SPACING_PX = 0.3 // tracking-wide = 0.025em at 12px
|
|
360
|
+
// Fallback only — used where there is no canvas (SSR, jsdom). Deliberately an
|
|
361
|
+
// upper bound: measured per-character widths across the curated labels ranged
|
|
362
|
+
// 7.2–8.1px, so this never under-reserves for Latin text.
|
|
363
|
+
const HEADER_CHAR_PX_FALLBACK = 8.2
|
|
364
|
+
const HEADER_SPACE_PX_FALLBACK = 4
|
|
365
|
+
|
|
366
|
+
let headerMeasureCtx: CanvasRenderingContext2D | null | undefined
|
|
367
|
+
const headerLabelWidthCache = new Map<string, number>()
|
|
368
|
+
|
|
369
|
+
// DM Sans is loaded with font-display:swap, so the first tables can render — and
|
|
370
|
+
// be measured — while the browser is still painting the fallback face. Those
|
|
371
|
+
// measurements would otherwise be cached against the wrong glyph metrics for the
|
|
372
|
+
// rest of the session: a wider real face clips a label whose tooltip never
|
|
373
|
+
// activates, a narrower one leaves the table permanently too wide. Drop the
|
|
374
|
+
// cache when the real font arrives and let subscribers recompute.
|
|
375
|
+
let headerFontEpoch = 0
|
|
376
|
+
const headerFontListeners = new Set<() => void>()
|
|
377
|
+
let headerFontWatchStarted = false
|
|
378
|
+
|
|
379
|
+
function watchHeaderFont(): void {
|
|
380
|
+
if (headerFontWatchStarted) return
|
|
381
|
+
headerFontWatchStarted = true
|
|
382
|
+
const fonts = typeof document === 'undefined' ? undefined : (document as Document & { fonts?: FontFaceSet }).fonts
|
|
383
|
+
if (!fonts?.ready) return
|
|
384
|
+
void fonts.ready.then(() => {
|
|
385
|
+
headerLabelWidthCache.clear()
|
|
386
|
+
headerMeasureCtx = undefined // re-created so ctx.font resolves against the loaded face
|
|
387
|
+
headerFontEpoch++
|
|
388
|
+
for (const notify of headerFontListeners) notify()
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Re-renders once the web font has loaded, so column widths measured against the
|
|
394
|
+
* fallback face are recomputed against the real one.
|
|
395
|
+
*/
|
|
396
|
+
export function useHeaderFontEpoch(): number {
|
|
397
|
+
const [epoch, setEpoch] = useState(headerFontEpoch)
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
const notify = () => setEpoch(headerFontEpoch)
|
|
400
|
+
headerFontListeners.add(notify)
|
|
401
|
+
notify() // the font may have loaded between render and effect
|
|
402
|
+
return () => { headerFontListeners.delete(notify) }
|
|
403
|
+
}, [])
|
|
404
|
+
return epoch
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Width of a header label as the browser will actually draw it.
|
|
409
|
+
*
|
|
410
|
+
* Measured rather than estimated because the labels are not a closed set: an
|
|
411
|
+
* uncurated CRD's printer columns take their names from the vendor's own
|
|
412
|
+
* additionalPrinterColumns, so they can be any length, any script, and a
|
|
413
|
+
* per-character constant calibrated on curated ASCII silently stops holding.
|
|
414
|
+
*/
|
|
415
|
+
function headerLabelWidth(label: string): number {
|
|
416
|
+
watchHeaderFont()
|
|
417
|
+
const cached = headerLabelWidthCache.get(label)
|
|
418
|
+
if (cached !== undefined) return cached
|
|
419
|
+
const upper = label.toUpperCase() // the header renders uppercase
|
|
420
|
+
let width: number
|
|
421
|
+
if (headerMeasureCtx === undefined) {
|
|
422
|
+
headerMeasureCtx = typeof document === 'undefined'
|
|
423
|
+
? null
|
|
424
|
+
: document.createElement('canvas').getContext('2d')
|
|
425
|
+
if (headerMeasureCtx) headerMeasureCtx.font = HEADER_FONT
|
|
426
|
+
}
|
|
427
|
+
if (headerMeasureCtx) {
|
|
428
|
+
width = headerMeasureCtx.measureText(upper).width
|
|
429
|
+
+ HEADER_LETTER_SPACING_PX * Math.max(0, upper.length - 1)
|
|
430
|
+
} else {
|
|
431
|
+
width = 0
|
|
432
|
+
for (const ch of upper) width += ch === ' ' ? HEADER_SPACE_PX_FALLBACK : HEADER_CHAR_PX_FALLBACK
|
|
433
|
+
}
|
|
434
|
+
headerLabelWidthCache.set(label, width)
|
|
435
|
+
return width
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Header label that carries its own full text in a tooltip when it truncates.
|
|
440
|
+
*
|
|
441
|
+
* The width floor in getColumnMinWidth keeps a heading from being clipped by its
|
|
442
|
+
* own column, but three cases still truncate: a label past the floor cap, a
|
|
443
|
+
* column the user dragged narrower, and the sort/filter controls sharing the
|
|
444
|
+
* row — whose width depends on their state (an active filter renders padding,
|
|
445
|
+
* an icon and a count). Their rendered width isn't knowable from the column
|
|
446
|
+
* definition, so this reads the element's actual overflow rather than computing
|
|
447
|
+
* it.
|
|
448
|
+
*
|
|
449
|
+
* The Tooltip wrapper stays mounted whether or not the label truncates: swapping
|
|
450
|
+
* it in on overflow would remount the measured span, stranding the
|
|
451
|
+
* ResizeObserver on the detached node so the state could never flip back. It
|
|
452
|
+
* also needs min-w-0 — the wrapper is the flex item in the header row, and an
|
|
453
|
+
* inline-flex box won't shrink below its content unless told to, which would
|
|
454
|
+
* push the sort and filter controls out of the cell.
|
|
455
|
+
*/
|
|
456
|
+
function HeaderLabel({ label, className }: { label: string; className?: string }) {
|
|
457
|
+
const ref = useRef<HTMLSpanElement>(null)
|
|
458
|
+
const [truncated, setTruncated] = useState(false)
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
const el = ref.current
|
|
461
|
+
if (!el) return
|
|
462
|
+
const check = () => setTruncated(el.scrollWidth > el.clientWidth + 0.5)
|
|
463
|
+
check()
|
|
464
|
+
if (typeof ResizeObserver === 'undefined') return
|
|
465
|
+
const ro = new ResizeObserver(check)
|
|
466
|
+
ro.observe(el)
|
|
467
|
+
return () => { ro.disconnect() }
|
|
468
|
+
}, [label])
|
|
469
|
+
return (
|
|
470
|
+
<Tooltip content={label} disabled={!truncated} preserveWrapperWhenDisabled wrapperClassName="min-w-0">
|
|
471
|
+
<span ref={ref} className={clsx('truncate', className)}>{label}</span>
|
|
472
|
+
</Tooltip>
|
|
473
|
+
)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Whether a column's header renders a sort control, which takes width beside the
|
|
478
|
+
* label. Shared by the header render and the width math so the two can't
|
|
479
|
+
* disagree: printer and custom columns are sortable through their own
|
|
480
|
+
* getSortValue even though their keys aren't in the built-in list.
|
|
481
|
+
*/
|
|
482
|
+
export function isColumnSortable(col: Column, extras?: Map<string, ExtraColumn>): boolean {
|
|
483
|
+
return BUILTIN_SORTABLE_COLUMN_KEYS.has(col.key) || !!extras?.get(col.key)?.getSortValue
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export function getColumnMinWidth(col: Column, extras?: Map<string, ExtraColumn>): number {
|
|
487
|
+
const declared = declaredColumnWidth(col)
|
|
488
|
+
// A column-filter button also sits in this row, but whether one renders
|
|
489
|
+
// depends on the distinct values in the data (isColumnFilterableByDistinctCount),
|
|
490
|
+
// which isn't knowable here — those columns stay ~20px tighter than ideal.
|
|
491
|
+
// Both controls are reserved statically. The filter button only renders once
|
|
492
|
+
// the data has a filterable spread of values (isColumnFilterableByDistinctCount),
|
|
493
|
+
// but sizing the column from that would make it change width as rows load or
|
|
494
|
+
// are filtered — the jumping-column problem fixed layout exists to avoid. So
|
|
495
|
+
// reserve for any column that could ever carry one, and keep the width stable.
|
|
496
|
+
const affordance = (isColumnSortable(col, extras) ? HEADER_SORT_AFFORDANCE_PX : 0)
|
|
497
|
+
+ (SKIP_FILTER_COLUMNS.has(col.key) ? 0 : HEADER_FILTER_AFFORDANCE_PX)
|
|
498
|
+
const headerFloor = HEADER_PADDING_PX + headerLabelWidth(col.label) + affordance
|
|
499
|
+
return Math.max(declared, Math.min(Math.ceil(headerFloor), HEADER_FLOOR_MAX_PX))
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function declaredColumnWidth(col: Column): number {
|
|
325
503
|
if (col.minWidth) return col.minWidth
|
|
326
504
|
if (!col.width) return 200 // Name column (no width class) gets wider minimum
|
|
327
505
|
const match = col.width.match(/(?:min-)?w-(\d+)/)
|
|
@@ -372,7 +550,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
372
550
|
{ key: 'restarts', label: 'Restarts', width: 'w-28' },
|
|
373
551
|
{ key: 'gpu', label: 'GPU', width: 'w-20', tooltip: 'Effective GPU request (requests, falling back to limits) summed across containers', defaultVisible: false },
|
|
374
552
|
{ key: 'podIP', label: 'Pod IP', width: 'w-32', defaultVisible: false },
|
|
375
|
-
{ key: 'node', label: 'Node', width: 'w-44'
|
|
553
|
+
{ key: 'node', label: 'Node', width: 'w-44' },
|
|
376
554
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
377
555
|
],
|
|
378
556
|
deployments: [
|
|
@@ -380,20 +558,20 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
380
558
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
381
559
|
{ key: 'status', label: 'Status', width: 'w-40' },
|
|
382
560
|
{ key: 'ready', label: 'Ready', width: 'w-24', tooltip: 'Ready pods / Desired replicas' },
|
|
383
|
-
{ key: 'upToDate', label: 'Up-to-date', width: 'w-32',
|
|
384
|
-
{ key: 'available', label: 'Available', width: 'w-28',
|
|
385
|
-
{ key: 'images', label: 'Images', width: 'w-48'
|
|
561
|
+
{ key: 'upToDate', label: 'Up-to-date', width: 'w-32', tooltip: 'Number of pods running the current pod template' },
|
|
562
|
+
{ key: 'available', label: 'Available', width: 'w-28', tooltip: 'Number of pods available (ready for minReadySeconds)' },
|
|
563
|
+
{ key: 'images', label: 'Images', width: 'w-48' },
|
|
386
564
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
387
565
|
],
|
|
388
566
|
daemonsets: [
|
|
389
567
|
{ key: 'name', label: 'Name' },
|
|
390
568
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
391
569
|
{ key: 'status', label: 'Status', width: 'w-40' },
|
|
392
|
-
{ key: 'desired', label: 'Desired', width: 'w-20', tooltip: '
|
|
570
|
+
{ key: 'desired', label: 'Desired', width: 'w-20', tooltip: 'Nodes eligible for this daemon after node selectors, required node affinity and taints/tolerations are applied' },
|
|
393
571
|
{ key: 'ready', label: 'Ready', width: 'w-20', tooltip: 'Number of pods that are ready (passing readiness probes)' },
|
|
394
|
-
{ key: 'upToDate', label: 'Up-to-date', width: 'w-32',
|
|
395
|
-
{ key: 'available', label: 'Available', width: 'w-28',
|
|
396
|
-
{ key: 'images', label: 'Images', width: 'w-48'
|
|
572
|
+
{ key: 'upToDate', label: 'Up-to-date', width: 'w-32', tooltip: 'Number of pods running the current pod template spec' },
|
|
573
|
+
{ key: 'available', label: 'Available', width: 'w-28', tooltip: 'Number of pods available (ready for minReadySeconds duration)' },
|
|
574
|
+
{ key: 'images', label: 'Images', width: 'w-48' },
|
|
397
575
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
398
576
|
],
|
|
399
577
|
statefulsets: [
|
|
@@ -401,8 +579,8 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
401
579
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
402
580
|
{ key: 'status', label: 'Status', width: 'w-40' },
|
|
403
581
|
{ key: 'ready', label: 'Ready', width: 'w-24', tooltip: 'Ready pods / Desired replicas' },
|
|
404
|
-
{ key: 'upToDate', label: 'Up-to-date', width: 'w-32',
|
|
405
|
-
{ key: 'images', label: 'Images', width: 'w-48'
|
|
582
|
+
{ key: 'upToDate', label: 'Up-to-date', width: 'w-32', tooltip: 'Pods running the current pod template; a rolling-update partition or OnDelete strategy can intentionally leave pods on an older template' },
|
|
583
|
+
{ key: 'images', label: 'Images', width: 'w-48' },
|
|
406
584
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
407
585
|
],
|
|
408
586
|
replicasets: [
|
|
@@ -410,17 +588,17 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
410
588
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
411
589
|
{ key: 'ready', label: 'Ready', width: 'w-24' },
|
|
412
590
|
{ key: 'owner', label: 'Owner', width: 'w-48' },
|
|
413
|
-
{ key: 'status', label: 'Status', width: 'w-24',
|
|
591
|
+
{ key: 'status', label: 'Status', width: 'w-24', tooltip: 'Active means desired replicas are greater than zero; Old means desired replicas are zero, regardless of Deployment revision' },
|
|
414
592
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
415
593
|
],
|
|
416
594
|
services: [
|
|
417
595
|
{ key: 'name', label: 'Name' },
|
|
418
596
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
419
597
|
{ key: 'type', label: 'Type', width: 'w-28' },
|
|
420
|
-
{ key: 'selector', label: 'Selector', width: 'w-48'
|
|
598
|
+
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
421
599
|
{ key: 'endpoints', label: 'Endpoints', width: 'w-24' },
|
|
422
600
|
{ key: 'ports', label: 'Ports', width: 'w-40' },
|
|
423
|
-
{ key: 'externalIP', label: 'External', width: 'w-40'
|
|
601
|
+
{ key: 'externalIP', label: 'External', width: 'w-40' },
|
|
424
602
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
425
603
|
],
|
|
426
604
|
endpointslices: [
|
|
@@ -430,7 +608,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
430
608
|
{ key: 'addressType', label: 'Address Type', width: 'w-28' },
|
|
431
609
|
{ key: 'endpoints', label: 'Endpoints', width: 'w-32' },
|
|
432
610
|
{ key: 'addresses', label: 'Addresses', width: 'w-24' },
|
|
433
|
-
{ key: 'ports', label: 'Ports', width: 'w-40'
|
|
611
|
+
{ key: 'ports', label: 'Ports', width: 'w-40' },
|
|
434
612
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
435
613
|
],
|
|
436
614
|
ingresses: [
|
|
@@ -438,21 +616,21 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
438
616
|
{ key: 'namespace', label: 'Namespace', width: 'w-36 shrink-0' },
|
|
439
617
|
{ key: 'class', label: 'Class', width: 'w-24 shrink-0' },
|
|
440
618
|
{ key: 'hosts', label: 'Hosts', width: 'min-w-48' },
|
|
441
|
-
{ key: 'rules', label: 'Rules', width: 'min-w-56'
|
|
619
|
+
{ key: 'rules', label: 'Rules', width: 'min-w-56' },
|
|
442
620
|
{ key: 'tls', label: 'TLS', width: 'w-14 shrink-0' },
|
|
443
|
-
{ key: 'address', label: 'Address', width: 'min-w-32'
|
|
621
|
+
{ key: 'address', label: 'Address', width: 'min-w-32' },
|
|
444
622
|
{ key: 'age', label: 'Age', width: 'w-24 shrink-0' },
|
|
445
623
|
],
|
|
446
624
|
nodes: [
|
|
447
625
|
{ key: 'name', label: 'Name' },
|
|
448
626
|
{ key: 'status', label: 'Status', width: 'w-44' },
|
|
449
|
-
{ key: 'roles', label: 'Roles', width: 'w-28' },
|
|
627
|
+
{ key: 'roles', label: 'Roles', width: 'w-28', tooltip: 'Node roles from node-role.kubernetes.io/* labels. A node with no role label shows as worker.' },
|
|
450
628
|
{ key: 'cpu', label: 'CPU', width: 'w-40', tooltip: 'Current CPU usage / allocatable' },
|
|
451
629
|
{ key: 'memory', label: 'Memory', width: 'w-40', tooltip: 'Current memory usage / allocatable' },
|
|
452
630
|
{ key: 'pods', label: 'Pods', width: 'w-28', tooltip: 'Pods running / allocatable' },
|
|
453
631
|
{ key: 'gpu', label: 'GPUs', width: 'w-20', tooltip: 'Allocatable GPUs', defaultVisible: false },
|
|
454
|
-
{ key: 'conditions', label: 'Conditions', width: 'w-40'
|
|
455
|
-
{ key: 'taints', label: 'Taints', width: 'w-24'
|
|
632
|
+
{ key: 'conditions', label: 'Conditions', width: 'w-40' },
|
|
633
|
+
{ key: 'taints', label: 'Taints', width: 'w-24' },
|
|
456
634
|
{ key: 'version', label: 'Version', width: 'w-28' },
|
|
457
635
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
458
636
|
],
|
|
@@ -460,7 +638,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
460
638
|
{ key: 'name', label: 'Name' },
|
|
461
639
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
462
640
|
{ key: 'keys', label: 'Keys', width: 'w-48' },
|
|
463
|
-
{ key: 'size', label: 'Size', width: 'w-24' },
|
|
641
|
+
{ key: 'size', label: 'Size', width: 'w-24', tooltip: 'Estimated size of data values, including decoded binary data. Kubernetes rejects ConfigMaps whose combined data exceeds 1 MiB.' },
|
|
464
642
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
465
643
|
],
|
|
466
644
|
secrets: [
|
|
@@ -468,7 +646,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
468
646
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
469
647
|
{ key: 'type', label: 'Type', width: 'w-28' },
|
|
470
648
|
{ key: 'keys', label: 'Keys', width: 'w-20' },
|
|
471
|
-
{ key: 'expires', label: 'Expires', width: 'w-24', tooltip:
|
|
649
|
+
{ key: 'expires', label: 'Expires', width: 'w-24', tooltip: "Days until the certificate in a TLS Secret expires. Blank for Secrets carrying no readable TLS certificate; ! means the expiry lookup itself failed." },
|
|
472
650
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
473
651
|
],
|
|
474
652
|
jobs: [
|
|
@@ -476,15 +654,15 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
476
654
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
477
655
|
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
478
656
|
{ key: 'completions', label: 'Completions', width: 'w-32' },
|
|
479
|
-
{ key: 'duration', label: 'Duration', width: 'w-24'
|
|
657
|
+
{ key: 'duration', label: 'Duration', width: 'w-24' },
|
|
480
658
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
481
659
|
],
|
|
482
660
|
cronjobs: [
|
|
483
661
|
{ key: 'name', label: 'Name' },
|
|
484
662
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
485
|
-
{ key: 'schedule', label: 'Schedule', width: 'w-40' },
|
|
663
|
+
{ key: 'schedule', label: 'Schedule', width: 'w-40', tooltip: "Cron schedule; fires in spec.timeZone when set, otherwise the controller's zone" },
|
|
486
664
|
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
487
|
-
{ key: 'lastRun', label: 'Last Run', width: 'w-28'
|
|
665
|
+
{ key: 'lastRun', label: 'Last Run', width: 'w-28' },
|
|
488
666
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
489
667
|
],
|
|
490
668
|
hpas: [
|
|
@@ -492,7 +670,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
492
670
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
493
671
|
{ key: 'target', label: 'Target', width: 'w-48' },
|
|
494
672
|
{ key: 'replicas', label: 'Replicas', width: 'w-32' },
|
|
495
|
-
{ key: 'metrics', label: 'Metrics', width: 'w-36'
|
|
673
|
+
{ key: 'metrics', label: 'Metrics', width: 'w-36' },
|
|
496
674
|
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
497
675
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
498
676
|
],
|
|
@@ -501,18 +679,18 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
501
679
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
502
680
|
{ key: 'target', label: 'Target', width: 'w-48' },
|
|
503
681
|
{ key: 'replicas', label: 'Replicas', width: 'w-32' },
|
|
504
|
-
{ key: 'metrics', label: 'Metrics', width: 'w-36'
|
|
682
|
+
{ key: 'metrics', label: 'Metrics', width: 'w-36' },
|
|
505
683
|
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
506
684
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
507
685
|
],
|
|
508
686
|
persistentvolumeclaims: [
|
|
509
687
|
{ key: 'name', label: 'Name' },
|
|
510
688
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
511
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
689
|
+
{ key: 'status', label: 'Status', width: 'w-24', tooltip: 'With WaitForFirstConsumer, Pending can mean no pod has triggered binding yet. If a pod is waiting, check PVC events.' },
|
|
512
690
|
{ key: 'capacity', label: 'Capacity', width: 'w-24' },
|
|
513
|
-
{ key: 'storageClass', label: 'Storage Class', width: 'w-40'
|
|
514
|
-
{ key: 'accessModes', label: 'Access', width: 'w-20', tooltip: '
|
|
515
|
-
{ key: 'volume', label: 'Volume', width: 'w-48'
|
|
691
|
+
{ key: 'storageClass', label: 'Storage Class', width: 'w-40' },
|
|
692
|
+
{ key: 'accessModes', label: 'Access', width: 'w-20', tooltip: 'RWO=ReadWriteOnce (one node, possibly several pods), ROX=ReadOnlyMany (read-only on many nodes), RWX=ReadWriteMany (read-write on many nodes), RWOP=ReadWriteOncePod (one pod only)' },
|
|
693
|
+
{ key: 'volume', label: 'Volume', width: 'w-48' },
|
|
516
694
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
517
695
|
],
|
|
518
696
|
rollouts: [
|
|
@@ -521,8 +699,8 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
521
699
|
{ key: 'status', label: 'Status', width: 'w-32' },
|
|
522
700
|
{ key: 'ready', label: 'Ready', width: 'w-24', tooltip: 'Available / Desired replicas' },
|
|
523
701
|
{ key: 'strategy', label: 'Strategy', width: 'w-24' },
|
|
524
|
-
{ key: 'step', label: 'Step', width: 'w-20',
|
|
525
|
-
{ key: 'images', label: 'Images', width: 'w-48'
|
|
702
|
+
{ key: 'step', label: 'Step', width: 'w-20', tooltip: 'Current canary step / Total steps' },
|
|
703
|
+
{ key: 'images', label: 'Images', width: 'w-48' },
|
|
526
704
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
527
705
|
],
|
|
528
706
|
analysisruns: [
|
|
@@ -530,7 +708,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
530
708
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
531
709
|
{ key: 'status', label: 'Phase', width: 'w-28' },
|
|
532
710
|
{ key: 'trigger', label: 'Trigger', width: 'w-32', tooltip: 'Which part of the Rollout started this run' },
|
|
533
|
-
{ key: 'metrics', label: 'Metrics', width: 'w-40',
|
|
711
|
+
{ key: 'metrics', label: 'Metrics', width: 'w-40', tooltip: 'Per-metric verdicts' },
|
|
534
712
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
535
713
|
],
|
|
536
714
|
workflows: [
|
|
@@ -538,8 +716,8 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
538
716
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
539
717
|
{ key: 'status', label: 'Phase', width: 'w-28' },
|
|
540
718
|
{ key: 'duration', label: 'Duration', width: 'w-24' },
|
|
541
|
-
{ key: 'progress', label: 'Progress', width: 'w-24',
|
|
542
|
-
{ key: 'template', label: 'Template', width: 'w-40'
|
|
719
|
+
{ key: 'progress', label: 'Progress', width: 'w-24', tooltip: 'Succeeded steps / Total steps' },
|
|
720
|
+
{ key: 'template', label: 'Template', width: 'w-40' },
|
|
543
721
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
544
722
|
],
|
|
545
723
|
cronworkflows: [
|
|
@@ -547,8 +725,8 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
547
725
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
548
726
|
{ key: 'schedule', label: 'Schedule', width: 'w-40' },
|
|
549
727
|
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
550
|
-
{ key: 'lastRun', label: 'Last Run', width: 'w-28'
|
|
551
|
-
{ key: 'template', label: 'Template', width: 'w-40'
|
|
728
|
+
{ key: 'lastRun', label: 'Last Run', width: 'w-28' },
|
|
729
|
+
{ key: 'template', label: 'Template', width: 'w-40' },
|
|
552
730
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
553
731
|
],
|
|
554
732
|
certificates: [
|
|
@@ -556,25 +734,25 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
556
734
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
557
735
|
{ key: 'status', label: 'Ready', width: 'w-24' },
|
|
558
736
|
{ key: 'domains', label: 'Domains', width: 'w-56' },
|
|
559
|
-
{ key: 'issuer', label: 'Issuer', width: 'w-36',
|
|
737
|
+
{ key: 'issuer', label: 'Issuer', width: 'w-36', tooltip: "Issuer name alone may be ambiguous. Check the certificate's issuer reference for its kind and API group." },
|
|
560
738
|
{ key: 'expires', label: 'Expires', width: 'w-24', tooltip: 'Days until certificate expires' },
|
|
561
739
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
562
740
|
],
|
|
563
741
|
persistentvolumes: [
|
|
564
742
|
{ key: 'name', label: 'Name' },
|
|
565
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
743
|
+
{ key: 'status', label: 'Status', width: 'w-24', tooltip: "Released means the claim is gone but the volume still holds the previous claimant's data, so it will not be re-bound — check Reclaim before reusing it." },
|
|
566
744
|
{ key: 'capacity', label: 'Capacity', width: 'w-24' },
|
|
567
|
-
{ key: 'accessModes', label: 'Access', width: 'w-20', tooltip: 'RWO=ReadWriteOnce, ROX=ReadOnlyMany, RWX=ReadWriteMany' },
|
|
568
|
-
{ key: 'reclaimPolicy', label: 'Reclaim', width: 'w-20' },
|
|
569
|
-
{ key: 'storageClass', label: 'Storage Class', width: 'w-40'
|
|
570
|
-
{ key: 'claim', label: 'Claim', width: 'w-48'
|
|
745
|
+
{ key: 'accessModes', label: 'Access', width: 'w-20', tooltip: 'RWO=ReadWriteOnce (one node, possibly several pods), ROX=ReadOnlyMany (read-only on many nodes), RWX=ReadWriteMany (read-write on many nodes), RWOP=ReadWriteOncePod (one pod only)' },
|
|
746
|
+
{ key: 'reclaimPolicy', label: 'Reclaim', width: 'w-20', tooltip: 'When the claim is deleted, Delete removes the backing storage and its data; Retain leaves it for manual reclamation.' },
|
|
747
|
+
{ key: 'storageClass', label: 'Storage Class', width: 'w-40' },
|
|
748
|
+
{ key: 'claim', label: 'Claim', width: 'w-48' },
|
|
571
749
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
572
750
|
],
|
|
573
751
|
storageclasses: [
|
|
574
752
|
{ key: 'name', label: 'Name' },
|
|
575
753
|
{ key: 'provisioner', label: 'Provisioner', width: 'w-48' },
|
|
576
|
-
{ key: 'reclaimPolicy', label: 'Reclaim', width: 'w-20' },
|
|
577
|
-
{ key: 'bindingMode', label: 'Binding Mode', width: 'w-36' },
|
|
754
|
+
{ key: 'reclaimPolicy', label: 'Reclaim', width: 'w-20', tooltip: 'Default policy for volumes this class provisions — Delete removes the backing storage when the claim goes away; Retain leaves it for manual reclamation.' },
|
|
755
|
+
{ key: 'bindingMode', label: 'Binding Mode', width: 'w-36', tooltip: "When the volume is provisioned — WaitForFirstConsumer waits for a pod so the disk lands in the right zone; Immediate provisions before the pod's placement is known, which can leave the pod unschedulable." },
|
|
578
756
|
{ key: 'expansion', label: 'Expansion', width: 'w-24', tooltip: 'Whether volumes can be expanded after creation' },
|
|
579
757
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
580
758
|
],
|
|
@@ -602,38 +780,40 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
602
780
|
orders: [
|
|
603
781
|
{ key: 'name', label: 'Name' },
|
|
604
782
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
605
|
-
{ key: 'state', label: 'State', width: 'w-24' },
|
|
783
|
+
{ key: 'state', label: 'State', width: 'w-24', tooltip: 'Valid means ACME issuance finished; it does not confirm the TLS Secret is ready. If Pending persists, inspect the challenges and the order reason.' },
|
|
606
784
|
{ key: 'domains', label: 'Domains', width: 'w-48' },
|
|
607
|
-
{ key: 'issuer', label: 'Issuer', width: 'w-36'
|
|
785
|
+
{ key: 'issuer', label: 'Issuer', width: 'w-36' },
|
|
608
786
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
609
787
|
],
|
|
610
788
|
challenges: [
|
|
611
789
|
{ key: 'name', label: 'Name' },
|
|
612
790
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
613
791
|
{ key: 'challengeType', label: 'Type', width: 'w-20' },
|
|
614
|
-
{ key: 'state', label: 'State', width: 'w-24' },
|
|
792
|
+
{ key: 'state', label: 'State', width: 'w-24', tooltip: 'Valid means domain authorization succeeded. If Pending persists, inspect the reason for scheduling, solver, or propagation problems.' },
|
|
615
793
|
{ key: 'domain', label: 'Domain', width: 'w-48' },
|
|
616
|
-
{ key: 'presented', label: 'Presented', width: 'w-24',
|
|
794
|
+
{ key: 'presented', label: 'Presented', width: 'w-24', tooltip: 'While Pending, Yes means the solver presented the token, not that it is reachable. Check the reason for propagation failures; No can also mean queued.' },
|
|
617
795
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
618
796
|
],
|
|
619
797
|
gateways: [
|
|
620
798
|
{ key: 'name', label: 'Name' },
|
|
621
799
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
622
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
800
|
+
{ key: 'status', label: 'Status', width: 'w-28', tooltip: "Programmed = the controller reports configuration should take effect soon; traffic has not been tested. Accepted = the controller accepted this Gateway but has not reported Programmed. If it stays Accepted, check the Gateway's conditions and addresses." },
|
|
623
801
|
{ key: 'class', label: 'Class', width: 'w-36' },
|
|
624
802
|
{ key: 'listeners', label: 'Listeners', width: 'w-40', tooltip: 'Protocol:Port for each listener' },
|
|
625
|
-
|
|
626
|
-
|
|
803
|
+
// attachedRoutes is reported per listener, so a route attached to several
|
|
804
|
+
// listeners is counted once for each — the total is not a route count.
|
|
805
|
+
{ key: 'routes', label: 'Routes', width: 'w-20', tooltip: "Sum of route counts reported by listeners; one route can count more than once. If 0 is unexpected, check the routes' Gateway names, listener permissions, and controller status." },
|
|
806
|
+
{ key: 'addresses', label: 'Addresses', width: 'w-48' },
|
|
627
807
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
628
808
|
],
|
|
629
809
|
httproutes: [
|
|
630
810
|
{ key: 'name', label: 'Name' },
|
|
631
811
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
632
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
812
|
+
{ key: 'status', label: 'Status', width: 'w-28', tooltip: 'Accepted = controllers accepted this route and confirmed it can use the backends and other objects it names. Traffic has not been tested. Degraded or Not Accepted: open the route to see which Gateway or backend failed. Pending or Unknown: check the named Gateways or Services and their controllers.' },
|
|
633
813
|
{ key: 'hostnames', label: 'Hostnames', width: 'w-48' },
|
|
634
814
|
{ key: 'parents', label: 'Gateways', width: 'w-36' },
|
|
635
815
|
{ key: 'backends', label: 'Backends', width: 'w-48', tooltip: 'Backend services receiving traffic' },
|
|
636
|
-
{ key: 'rules', label: 'Rules', width: 'w-
|
|
816
|
+
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
637
817
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
638
818
|
],
|
|
639
819
|
istiogateways: [
|
|
@@ -641,13 +821,13 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
641
821
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
642
822
|
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
643
823
|
{ key: 'servers', label: 'Servers', width: 'w-24' },
|
|
644
|
-
{ key: 'selector', label: 'Selector', width: 'w-56' },
|
|
824
|
+
{ key: 'selector', label: 'Selector', width: 'w-56', tooltip: "Selects gateway workloads that receive this configuration. An omitted selector applies to all workloads in Istio's selection scope; it does not mean none." },
|
|
645
825
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
646
826
|
],
|
|
647
827
|
gatewayclasses: [
|
|
648
828
|
{ key: 'name', label: 'Name' },
|
|
649
829
|
{ key: 'controller', label: 'Controller', width: 'w-64', tooltip: 'Gateway controller implementation (spec.controllerName)' },
|
|
650
|
-
{ key: 'description', label: 'Description', width: 'w-64'
|
|
830
|
+
{ key: 'description', label: 'Description', width: 'w-64' },
|
|
651
831
|
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
652
832
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
653
833
|
],
|
|
@@ -656,18 +836,17 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
656
836
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
657
837
|
{ key: 'nodeClass', label: 'Node Class', width: 'w-36' },
|
|
658
838
|
{ key: 'limits', label: 'Limits', width: 'w-36', tooltip: 'CPU and memory limits' },
|
|
659
|
-
{ key: 'disruption', label: 'Disruption', width: 'w-40'
|
|
839
|
+
{ key: 'disruption', label: 'Disruption', width: 'w-40' },
|
|
660
840
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
661
841
|
],
|
|
662
842
|
nodeclaims: [
|
|
663
843
|
{ key: 'name', label: 'Name' },
|
|
664
|
-
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
665
844
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
666
845
|
{ key: 'instanceType', label: 'Instance Type', width: 'w-32' },
|
|
667
846
|
{ key: 'capacityType', label: 'Capacity', width: 'w-24', tooltip: 'Spot or On-Demand' },
|
|
668
|
-
{ key: 'zone', label: 'Zone', width: 'w-28'
|
|
847
|
+
{ key: 'zone', label: 'Zone', width: 'w-28' },
|
|
669
848
|
{ key: 'nodePool', label: 'Node Pool', width: 'w-32' },
|
|
670
|
-
{ key: 'nodeName', label: 'Node', width: 'w-40'
|
|
849
|
+
{ key: 'nodeName', label: 'Node', width: 'w-40' },
|
|
671
850
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
672
851
|
],
|
|
673
852
|
ec2nodeclasses: [
|
|
@@ -691,7 +870,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
691
870
|
{ key: 'name', label: 'Name' },
|
|
692
871
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
693
872
|
{ key: 'status', label: 'Status', width: 'w-28', tooltip: 'Allocated + reserved, allocated-but-unreserved, or pending' },
|
|
694
|
-
{ key: 'deviceClass', label: 'Device Class', width: 'w-44' },
|
|
873
|
+
{ key: 'deviceClass', label: 'Device Class', width: 'w-44', tooltip: 'DeviceClasses whose selectors constrain eligible devices. A request may list alternative classes; this column does not show which alternative was allocated.' },
|
|
695
874
|
{ key: 'allocated', label: 'Allocated Driver', width: 'w-44' },
|
|
696
875
|
{ key: 'reservedFor', label: 'Reserved For', width: 'w-44' },
|
|
697
876
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -704,15 +883,17 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
704
883
|
],
|
|
705
884
|
deviceclasses: [
|
|
706
885
|
{ key: 'name', label: 'Name' },
|
|
707
|
-
|
|
886
|
+
// Selectors are ANDed, so an empty list restricts nothing rather than
|
|
887
|
+
// matching nothing — the reading most people reach for first.
|
|
888
|
+
{ key: 'selectors', label: 'Selectors', width: 'w-28', tooltip: 'Devices must satisfy every selector. Zero selectors means this class imposes no selector restriction; claim selectors and other allocation requirements still apply.' },
|
|
708
889
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
709
890
|
],
|
|
710
891
|
resourceslices: [
|
|
711
892
|
{ key: 'name', label: 'Name' },
|
|
712
893
|
{ key: 'driver', label: 'Driver', width: 'w-44' },
|
|
713
|
-
{ key: 'pool', label: 'Pool', width: 'w-36' },
|
|
714
|
-
{ key: 'node', label: 'Node', width: 'w-44' },
|
|
715
|
-
{ key: 'devices', label: 'Devices', width: 'w-24' },
|
|
894
|
+
{ key: 'pool', label: 'Pool', width: 'w-36', tooltip: "Pools are identified by driver and pool name. The scheduler needs a complete set of slices at the pool's highest generation before allocating from it." },
|
|
895
|
+
{ key: 'node', label: 'Node', width: 'w-44', tooltip: "Node providing this slice's devices. A dash means node access is described by a node selector, all-nodes access, or per-device node selection." },
|
|
896
|
+
{ key: 'devices', label: 'Devices', width: 'w-24', tooltip: 'Devices published in this slice, including devices already allocated. This is not a count of free devices or the whole pool.' },
|
|
716
897
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
717
898
|
],
|
|
718
899
|
nvidiaclusterpolicies: [
|
|
@@ -758,7 +939,6 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
758
939
|
],
|
|
759
940
|
resourceflavors: [
|
|
760
941
|
{ key: 'name', label: 'Name' },
|
|
761
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
762
942
|
{ key: 'nodeLabels', label: 'Node Labels', width: 'w-28', tooltip: 'Node label selector count' },
|
|
763
943
|
{ key: 'taints', label: 'Taints', width: 'w-24' },
|
|
764
944
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -808,7 +988,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
808
988
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
809
989
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
810
990
|
{ key: 'schedule', label: 'Schedule', width: 'w-32' },
|
|
811
|
-
{ key: '
|
|
991
|
+
{ key: 'timeZone', label: 'Time Zone', width: 'w-36', tooltip: 'IANA zone the schedule is read in; unset follows the local zone of the KubeRay operator pod' },
|
|
812
992
|
{ key: 'lastSchedule', label: 'Last Schedule', width: 'w-28' },
|
|
813
993
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
814
994
|
],
|
|
@@ -935,7 +1115,6 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
935
1115
|
jobtemplates: [
|
|
936
1116
|
{ key: 'name', label: 'Name' },
|
|
937
1117
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
938
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
939
1118
|
{ key: 'tasks', label: 'Tasks', width: 'w-20' },
|
|
940
1119
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
941
1120
|
],
|
|
@@ -1042,7 +1221,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1042
1221
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1043
1222
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1044
1223
|
{ key: 'target', label: 'Job Target', width: 'w-48' },
|
|
1045
|
-
{ key: 'strategy', label: 'Strategy', width: 'w-28' },
|
|
1224
|
+
{ key: 'strategy', label: 'Strategy', width: 'w-28', tooltip: 'Controls how queue length and running or pending Jobs determine how many new Jobs KEDA creates' },
|
|
1046
1225
|
{ key: 'triggerTypes', label: 'Trigger Types', width: 'w-40' },
|
|
1047
1226
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1048
1227
|
],
|
|
@@ -1064,7 +1243,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1064
1243
|
servicemonitors: [
|
|
1065
1244
|
{ key: 'name', label: 'Name' },
|
|
1066
1245
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1067
|
-
{ key: 'status', label: 'Status', width: 'w-
|
|
1246
|
+
{ key: 'status', label: 'Status', width: 'w-36' },
|
|
1068
1247
|
{ key: 'endpoints', label: 'Endpoints', width: 'w-20', tooltip: 'Number of scrape endpoints' },
|
|
1069
1248
|
{ key: 'jobLabel', label: 'Job Label', width: 'w-32' },
|
|
1070
1249
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
@@ -1073,7 +1252,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1073
1252
|
prometheusrules: [
|
|
1074
1253
|
{ key: 'name', label: 'Name' },
|
|
1075
1254
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1076
|
-
{ key: 'status', label: 'Status', width: 'w-
|
|
1255
|
+
{ key: 'status', label: 'Status', width: 'w-36' },
|
|
1077
1256
|
{ key: 'groups', label: 'Groups', width: 'w-20' },
|
|
1078
1257
|
{ key: 'rules', label: 'Rules', width: 'w-20', tooltip: 'Total alert + recording rules' },
|
|
1079
1258
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -1081,7 +1260,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1081
1260
|
podmonitors: [
|
|
1082
1261
|
{ key: 'name', label: 'Name' },
|
|
1083
1262
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1084
|
-
{ key: 'status', label: 'Status', width: 'w-
|
|
1263
|
+
{ key: 'status', label: 'Status', width: 'w-36' },
|
|
1085
1264
|
{ key: 'endpoints', label: 'Endpoints', width: 'w-20', tooltip: 'Number of pod metrics endpoints' },
|
|
1086
1265
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
1087
1266
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -1117,14 +1296,14 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1117
1296
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1118
1297
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1119
1298
|
{ key: 'action', label: 'Action', width: 'w-36', tooltip: 'Enforcement at admission — Enforce blocks, Audit reports; Background only or Inactive when admission is disabled' },
|
|
1120
|
-
{ key: 'rules', label: 'Rules', width: 'w-
|
|
1299
|
+
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
1121
1300
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1122
1301
|
],
|
|
1123
1302
|
clusterpolicies: [
|
|
1124
1303
|
{ key: 'name', label: 'Name' },
|
|
1125
1304
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1126
1305
|
{ key: 'action', label: 'Action', width: 'w-36', tooltip: 'Enforcement at admission — Enforce blocks, Audit reports; Background only or Inactive when admission is disabled' },
|
|
1127
|
-
{ key: 'rules', label: 'Rules', width: 'w-
|
|
1306
|
+
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
1128
1307
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1129
1308
|
],
|
|
1130
1309
|
// Kyverno modern CEL family (policies.kyverno.io). "Enforcement" is the
|
|
@@ -1243,30 +1422,30 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1243
1422
|
grpcroutes: [
|
|
1244
1423
|
{ key: 'name', label: 'Name' },
|
|
1245
1424
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1246
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
1247
|
-
{ key: 'hostnames', label: 'Hostnames', width: 'w-48' },
|
|
1425
|
+
{ key: 'status', label: 'Status', width: 'w-28', tooltip: 'Accepted = controllers accepted this route and confirmed it can use the backends and other objects it names. Traffic has not been tested. Degraded or Not Accepted: open the route to see which Gateway or backend failed. Pending or Unknown: check the named Gateways or Services and their controllers.' },
|
|
1426
|
+
{ key: 'hostnames', label: 'Hostnames', width: 'w-48', tooltip: "gRPC hostnames this route matches. Any means the route adds no hostname restriction; the Gateway listener and the route's other matching rules still apply." },
|
|
1248
1427
|
{ key: 'parents', label: 'Gateways', width: 'w-36' },
|
|
1249
1428
|
{ key: 'backends', label: 'Backends', width: 'w-48', tooltip: 'Backend services receiving traffic' },
|
|
1250
|
-
{ key: 'rules', label: 'Rules', width: 'w-
|
|
1429
|
+
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
1251
1430
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1252
1431
|
],
|
|
1253
1432
|
tcproutes: [
|
|
1254
1433
|
{ key: 'name', label: 'Name' },
|
|
1255
1434
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1256
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
1435
|
+
{ key: 'status', label: 'Status', width: 'w-28', tooltip: 'Accepted = controllers accepted this route and confirmed it can use the backends and other objects it names. Traffic has not been tested. Degraded or Not Accepted: open the route to see which Gateway or backend failed. Pending or Unknown: check the named Gateways or Services and their controllers.' },
|
|
1257
1436
|
{ key: 'parents', label: 'Gateways', width: 'w-36' },
|
|
1258
1437
|
{ key: 'backends', label: 'Backends', width: 'w-48', tooltip: 'Backend services receiving traffic' },
|
|
1259
|
-
{ key: 'rules', label: 'Rules', width: 'w-
|
|
1438
|
+
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
1260
1439
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1261
1440
|
],
|
|
1262
1441
|
tlsroutes: [
|
|
1263
1442
|
{ key: 'name', label: 'Name' },
|
|
1264
1443
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1265
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
1266
|
-
{ key: 'hostnames', label: 'Hostnames', width: 'w-48', tooltip: 'SNI
|
|
1444
|
+
{ key: 'status', label: 'Status', width: 'w-28', tooltip: 'Accepted = controllers accepted this route and confirmed it can use the backends and other objects it names. Traffic has not been tested. Degraded or Not Accepted: open the route to see which Gateway or backend failed. Pending or Unknown: check the named Gateways or Services and their controllers.' },
|
|
1445
|
+
{ key: 'hostnames', label: 'Hostnames', width: 'w-48', tooltip: 'TLS SNI names requested by this route. Any means no route-level hostname restriction; the Gateway listener still applies.' },
|
|
1267
1446
|
{ key: 'parents', label: 'Gateways', width: 'w-36' },
|
|
1268
1447
|
{ key: 'backends', label: 'Backends', width: 'w-48', tooltip: 'Backend services receiving traffic' },
|
|
1269
|
-
{ key: 'rules', label: 'Rules', width: 'w-
|
|
1448
|
+
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
1270
1449
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1271
1450
|
],
|
|
1272
1451
|
sealedsecrets: [
|
|
@@ -1274,7 +1453,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1274
1453
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1275
1454
|
{ key: 'status', label: 'Synced', width: 'w-24' },
|
|
1276
1455
|
{ key: 'keys', label: 'Keys', width: 'w-20' },
|
|
1277
|
-
{ key: 'type', label: 'Type', width: 'w-36'
|
|
1456
|
+
{ key: 'type', label: 'Type', width: 'w-36' },
|
|
1278
1457
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1279
1458
|
],
|
|
1280
1459
|
workflowtemplates: [
|
|
@@ -1295,14 +1474,13 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1295
1474
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1296
1475
|
{ key: 'policyTypes', label: 'Types', width: 'w-28' },
|
|
1297
1476
|
{ key: 'selector', label: 'Pod Selector', width: 'w-48' },
|
|
1298
|
-
{ key: 'rules', label: 'Rules', width: 'w-24', tooltip: 'Ingress / Egress rule
|
|
1477
|
+
{ key: 'rules', label: 'Rules', width: 'w-24', tooltip: 'Ingress / Egress allow-rule counts. For a direction listed in Types, 0 means this policy allows none; other policies can still allow traffic.' },
|
|
1299
1478
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1300
1479
|
],
|
|
1301
1480
|
caliconetworkpolicies: [
|
|
1302
1481
|
{ key: 'name', label: 'Name' },
|
|
1303
1482
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1304
1483
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
1305
|
-
{ key: 'namespaceSelector', label: 'Namespace Selector', width: 'w-48', defaultVisible: false },
|
|
1306
1484
|
{ key: 'serviceAccountSelector', label: 'Service Account Selector', width: 'w-48', defaultVisible: false },
|
|
1307
1485
|
{ key: 'tier', label: 'Tier', width: 'w-28' },
|
|
1308
1486
|
{ key: 'order', label: 'Order', width: 'w-24' },
|
|
@@ -1325,7 +1503,6 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1325
1503
|
{ key: 'name', label: 'Name' },
|
|
1326
1504
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1327
1505
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
1328
|
-
{ key: 'namespaceSelector', label: 'Namespace Selector', width: 'w-48', defaultVisible: false },
|
|
1329
1506
|
{ key: 'serviceAccountSelector', label: 'Service Account Selector', width: 'w-48', defaultVisible: false },
|
|
1330
1507
|
{ key: 'tier', label: 'Tier', width: 'w-28' },
|
|
1331
1508
|
{ key: 'order', label: 'Order', width: 'w-24' },
|
|
@@ -1384,7 +1561,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1384
1561
|
{ key: 'name', label: 'Name' },
|
|
1385
1562
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1386
1563
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1387
|
-
{ key: 'budget', label: 'Budget', width: 'w-36' },
|
|
1564
|
+
{ key: 'budget', label: 'Budget', width: 'w-36', tooltip: 'min keeps at least this many pods available; max unavail permits at most this many unavailable during voluntary evictions; percentages use the expected pod count' },
|
|
1388
1565
|
{ key: 'healthy', label: 'Healthy', width: 'w-24' },
|
|
1389
1566
|
{ key: 'allowed', label: 'Allowed', width: 'w-24', tooltip: 'Number of disruptions currently allowed' },
|
|
1390
1567
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -1392,7 +1569,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1392
1569
|
serviceaccounts: [
|
|
1393
1570
|
{ key: 'name', label: 'Name' },
|
|
1394
1571
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1395
|
-
{ key: 'automount', label: 'Automount', width: 'w-32', tooltip: '
|
|
1572
|
+
{ key: 'automount', label: 'Automount', width: 'w-32', tooltip: 'Default automatic API-token mounting for pods using this ServiceAccount. A pod can override this setting. Disable it for workloads that do not need Kubernetes API credentials.' },
|
|
1396
1573
|
{ key: 'secrets', label: 'Secrets', width: 'w-24' },
|
|
1397
1574
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1398
1575
|
],
|
|
@@ -1430,15 +1607,15 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1430
1607
|
{ key: 'name', label: 'Name' },
|
|
1431
1608
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1432
1609
|
{ key: 'holder', label: 'Holder', width: 'w-48' },
|
|
1433
|
-
{ key: 'renewTime', label: 'Last Renewed', width: 'w-32' },
|
|
1610
|
+
{ key: 'renewTime', label: 'Last Renewed', width: 'w-32', tooltip: 'Time since the recorded renewal; red means it exceeds leaseDurationSeconds. Check holder health and API connectivity' },
|
|
1434
1611
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1435
1612
|
],
|
|
1436
1613
|
priorityclasses: [
|
|
1437
1614
|
{ key: 'name', label: 'Name' },
|
|
1438
|
-
{ key: 'value', label: 'Value', width: 'w-24' },
|
|
1615
|
+
{ key: 'value', label: 'Value', width: 'w-24', tooltip: 'Higher values receive scheduling preference; Pods can still be preempted by Pods with higher priority' },
|
|
1439
1616
|
{ key: 'globalDefault', label: 'Global Default', width: 'w-36' },
|
|
1440
1617
|
{ key: 'preemptionPolicy', label: 'Preemption', width: 'w-32' },
|
|
1441
|
-
{ key: 'description', label: 'Description', width: 'w-64'
|
|
1618
|
+
{ key: 'description', label: 'Description', width: 'w-64' },
|
|
1442
1619
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1443
1620
|
],
|
|
1444
1621
|
runtimeclasses: [
|
|
@@ -1449,15 +1626,15 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1449
1626
|
mutatingwebhookconfigurations: [
|
|
1450
1627
|
{ key: 'name', label: 'Name' },
|
|
1451
1628
|
{ key: 'webhooks', label: 'Webhooks', width: 'w-28' },
|
|
1452
|
-
{ key: 'failurePolicy', label: 'Failure Policy', width: 'w-36' },
|
|
1453
|
-
{ key: 'target', label: 'Target', width: 'w-48'
|
|
1629
|
+
{ key: 'failurePolicy', label: 'Failure Policy', width: 'w-36', tooltip: 'If a matching webhook call errors or times out, Fail rejects the API request; Ignore continues without that webhook. This describes failure handling, not current webhook health.' },
|
|
1630
|
+
{ key: 'target', label: 'Target', width: 'w-48' },
|
|
1454
1631
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1455
1632
|
],
|
|
1456
1633
|
validatingwebhookconfigurations: [
|
|
1457
1634
|
{ key: 'name', label: 'Name' },
|
|
1458
1635
|
{ key: 'webhooks', label: 'Webhooks', width: 'w-28' },
|
|
1459
1636
|
{ key: 'failurePolicy', label: 'Failure Policy', width: 'w-36' },
|
|
1460
|
-
{ key: 'target', label: 'Target', width: 'w-48'
|
|
1637
|
+
{ key: 'target', label: 'Target', width: 'w-48' },
|
|
1461
1638
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1462
1639
|
],
|
|
1463
1640
|
events: [
|
|
@@ -1466,7 +1643,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1466
1643
|
{ key: 'type', label: 'Type', width: 'w-24' },
|
|
1467
1644
|
{ key: 'reason', label: 'Reason', width: 'w-32' },
|
|
1468
1645
|
{ key: 'message', label: 'Message', width: 'w-64' },
|
|
1469
|
-
{ key: 'object', label: 'Object', width: 'w-48'
|
|
1646
|
+
{ key: 'object', label: 'Object', width: 'w-48' },
|
|
1470
1647
|
{ key: 'count', label: 'Count', width: 'w-20' },
|
|
1471
1648
|
{ key: 'lastSeen', label: 'Last Seen', width: 'w-28' },
|
|
1472
1649
|
],
|
|
@@ -1479,7 +1656,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1479
1656
|
{ key: 'url', label: 'URL', width: 'w-64' },
|
|
1480
1657
|
{ key: 'ref', label: 'Ref', width: 'w-32', tooltip: 'Branch, tag, or semver' },
|
|
1481
1658
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1482
|
-
{ key: 'revision', label: 'Revision', width: 'w-24',
|
|
1659
|
+
{ key: 'revision', label: 'Revision', width: 'w-24', tooltip: 'Last fetched commit SHA' },
|
|
1483
1660
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1484
1661
|
],
|
|
1485
1662
|
ocirepositories: [
|
|
@@ -1488,7 +1665,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1488
1665
|
{ key: 'url', label: 'URL', width: 'w-64' },
|
|
1489
1666
|
{ key: 'ref', label: 'Tag', width: 'w-24', tooltip: 'OCI tag or semver' },
|
|
1490
1667
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1491
|
-
{ key: 'revision', label: 'Digest', width: 'w-24'
|
|
1668
|
+
{ key: 'revision', label: 'Digest', width: 'w-24' },
|
|
1492
1669
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1493
1670
|
],
|
|
1494
1671
|
helmrepositories: [
|
|
@@ -1503,9 +1680,9 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1503
1680
|
{ key: 'name', label: 'Name' },
|
|
1504
1681
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1505
1682
|
{ key: 'source', label: 'Source', width: 'w-48', tooltip: 'Source GitRepository or OCIRepository' },
|
|
1506
|
-
{ key: 'path', label: 'Path', width: 'w-36'
|
|
1683
|
+
{ key: 'path', label: 'Path', width: 'w-36' },
|
|
1507
1684
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1508
|
-
{ key: 'revision', label: 'Revision', width: 'w-48',
|
|
1685
|
+
{ key: 'revision', label: 'Revision', width: 'w-48', tooltip: 'Applied git revision' },
|
|
1509
1686
|
{ key: 'inventory', label: 'Resources', width: 'w-24', tooltip: 'Number of managed resources' },
|
|
1510
1687
|
{ key: 'lastUpdated', label: 'Last Updated', width: 'w-28', tooltip: 'Time since last successful reconciliation' },
|
|
1511
1688
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -1516,8 +1693,8 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1516
1693
|
{ key: 'chart', label: 'Chart', width: 'w-40' },
|
|
1517
1694
|
{ key: 'version', label: 'Version', width: 'w-24' },
|
|
1518
1695
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1519
|
-
{ key: 'message', label: 'Message', width: 'w-64',
|
|
1520
|
-
{ key: 'revision', label: 'Rev', width: 'w-16',
|
|
1696
|
+
{ key: 'message', label: 'Message', width: 'w-64', tooltip: 'Last diagnostic message — distinguishes dependency-wait, install/upgrade failure, and test failure' },
|
|
1697
|
+
{ key: 'revision', label: 'Rev', width: 'w-16', tooltip: 'Helm release revision number' },
|
|
1521
1698
|
{ key: 'lastUpdated', label: 'Last Updated', width: 'w-28', tooltip: 'Time since last successful reconciliation' },
|
|
1522
1699
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1523
1700
|
],
|
|
@@ -1538,14 +1715,14 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1538
1715
|
{ key: 'project', label: 'Project', width: 'w-28' },
|
|
1539
1716
|
{ key: 'sync', label: 'Sync', width: 'w-24' },
|
|
1540
1717
|
{ key: 'health', label: 'Health', width: 'w-24' },
|
|
1541
|
-
{ key: 'repo', label: 'Repository', width: 'w-48'
|
|
1718
|
+
{ key: 'repo', label: 'Repository', width: 'w-48' },
|
|
1542
1719
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1543
1720
|
],
|
|
1544
1721
|
applicationsets: [
|
|
1545
1722
|
{ key: 'name', label: 'Name' },
|
|
1546
1723
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1547
1724
|
{ key: 'generators', label: 'Generators', width: 'w-32' },
|
|
1548
|
-
{ key: 'template', label: 'Template', width: 'w-40'
|
|
1725
|
+
{ key: 'template', label: 'Template', width: 'w-40' },
|
|
1549
1726
|
{ key: 'applications', label: 'Apps', width: 'w-20', tooltip: 'Number of generated applications' },
|
|
1550
1727
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1551
1728
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -1656,8 +1833,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1656
1833
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1657
1834
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1658
1835
|
{ key: 'store', label: 'Store', width: 'w-36' },
|
|
1659
|
-
{ key: '
|
|
1660
|
-
{ key: 'refreshInterval', label: 'Refresh', width: 'w-24' },
|
|
1836
|
+
{ key: 'refreshInterval', label: 'Refresh', width: 'w-24', tooltip: 'How often the value is re-pulled from the provider. A zero interval means it is fetched once and not updated afterward.' },
|
|
1661
1837
|
{ key: 'lastSync', label: 'Last Sync', width: 'w-28' },
|
|
1662
1838
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1663
1839
|
],
|
|
@@ -1913,16 +2089,16 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1913
2089
|
destinationrules: [
|
|
1914
2090
|
{ key: 'name', label: 'Name' },
|
|
1915
2091
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1916
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1917
|
-
{ key: 'host', label: 'Host', width: 'w-48' },
|
|
1918
|
-
{ key: 'subsets', label: 'Subsets', width: 'w-20' },
|
|
1919
|
-
{ key: '
|
|
2092
|
+
{ key: 'status', label: 'Status', width: 'w-24', defaultVisible: false },
|
|
2093
|
+
{ key: 'host', label: 'Host', width: 'w-48', tooltip: "The service this rule's traffic policy applies to" },
|
|
2094
|
+
{ key: 'subsets', label: 'Subsets', width: 'w-20', tooltip: 'Named backend groups that routes can select, often for canary releases. Defining subsets does not send traffic to them.' },
|
|
2095
|
+
{ key: 'tlsMode', label: 'Client TLS', width: 'w-28', tooltip: 'Declared client-side TLS mode for this host at the rule level. Subset and port policies can override it; this does not establish the effective mTLS posture.' },
|
|
2096
|
+
{ key: 'loadBalancer', label: 'LB Policy', width: 'w-28', tooltip: "Load balancing algorithm declared by this rule. '-' means none is set, so Istio applies its mesh default (LEAST_REQUEST since 1.14, overridable per mesh). Subset and port policies can differ." },
|
|
1920
2097
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1921
2098
|
],
|
|
1922
2099
|
serviceentries: [
|
|
1923
2100
|
{ key: 'name', label: 'Name' },
|
|
1924
2101
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1925
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1926
2102
|
{ key: 'hosts', label: 'Hosts', width: 'w-48' },
|
|
1927
2103
|
{ key: 'location', label: 'Location', width: 'w-28' },
|
|
1928
2104
|
{ key: 'ports', label: 'Ports', width: 'w-32' },
|
|
@@ -1931,7 +2107,6 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1931
2107
|
peerauthentications: [
|
|
1932
2108
|
{ key: 'name', label: 'Name' },
|
|
1933
2109
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1934
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1935
2110
|
{ key: 'mode', label: 'mTLS Mode', width: 'w-28' },
|
|
1936
2111
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
1937
2112
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -1939,10 +2114,10 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1939
2114
|
authorizationpolicies: [
|
|
1940
2115
|
{ key: 'name', label: 'Name' },
|
|
1941
2116
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1942
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1943
|
-
{ key: 'action', label: 'Action', width: 'w-24' },
|
|
1944
|
-
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
1945
|
-
{ key: 'selector', label: '
|
|
2117
|
+
{ key: 'status', label: 'Status', width: 'w-24', defaultVisible: false },
|
|
2118
|
+
{ key: 'action', label: 'Action', width: 'w-24', tooltip: 'ALLOW contributes permitted matches; DENY blocks matches; AUDIT marks matches for a configured audit plugin; CUSTOM adds an external check. Other policies also affect the decision.' },
|
|
2119
|
+
{ key: 'rules', label: 'Rules', width: 'w-20', tooltip: 'Rules are alternatives — zero rules match nothing, an empty rule matches everything. An ALLOW policy with zero rules permits nothing on its own.' },
|
|
2120
|
+
{ key: 'selector', label: 'Applies to', width: 'w-48', tooltip: 'Declared workload labels or resource attachments. With neither, scope is inherited — the namespace, or the whole mesh when the policy sits in the mesh root namespace.' },
|
|
1946
2121
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1947
2122
|
],
|
|
1948
2123
|
// Knative Serving
|
|
@@ -2116,7 +2291,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
2116
2291
|
{ key: 'namespace', label: 'Namespace', width: 'w-36 shrink-0' },
|
|
2117
2292
|
{ key: 'entrypoints', label: 'Entry Points', width: 'w-32 shrink-0' },
|
|
2118
2293
|
{ key: 'hosts', label: 'Hosts', width: 'min-w-44' },
|
|
2119
|
-
{ key: 'routes', label: 'Routes', width: 'min-w-48'
|
|
2294
|
+
{ key: 'routes', label: 'Routes', width: 'min-w-48' },
|
|
2120
2295
|
{ key: 'tls', label: 'TLS', width: 'w-14 shrink-0' },
|
|
2121
2296
|
{ key: 'middlewares', label: 'MW', width: 'w-14 shrink-0', tooltip: 'Unique middleware count' },
|
|
2122
2297
|
{ key: 'age', label: 'Age', width: 'w-24 shrink-0' },
|
|
@@ -2126,7 +2301,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
2126
2301
|
{ key: 'namespace', label: 'Namespace', width: 'w-36 shrink-0' },
|
|
2127
2302
|
{ key: 'entrypoints', label: 'Entry Points', width: 'w-32 shrink-0' },
|
|
2128
2303
|
{ key: 'hosts', label: 'Hosts', width: 'min-w-44' },
|
|
2129
|
-
{ key: 'routes', label: 'Routes', width: 'min-w-48'
|
|
2304
|
+
{ key: 'routes', label: 'Routes', width: 'min-w-48' },
|
|
2130
2305
|
{ key: 'tls', label: 'TLS', width: 'w-14 shrink-0' },
|
|
2131
2306
|
{ key: 'middlewares', label: 'MW', width: 'w-14 shrink-0', tooltip: 'Unique middleware count' },
|
|
2132
2307
|
{ key: 'age', label: 'Age', width: 'w-24 shrink-0' },
|
|
@@ -2390,15 +2565,15 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
2390
2565
|
{ key: 'name', label: 'Name', width: 'min-w-40' },
|
|
2391
2566
|
{ key: 'namespace', label: 'Namespace', width: 'w-32 shrink-0' },
|
|
2392
2567
|
{ key: 'kind', label: 'Kind', width: 'w-32 shrink-0' },
|
|
2393
|
-
{ key: 'external', label: 'External Name', width: 'min-w-48'
|
|
2394
|
-
{ key: 'provider', label: 'Provider Config', width: 'w-40 shrink-0'
|
|
2568
|
+
{ key: 'external', label: 'External Name', width: 'min-w-48' },
|
|
2569
|
+
{ key: 'provider', label: 'Provider Config', width: 'w-40 shrink-0' },
|
|
2395
2570
|
{ key: 'status', label: 'Status', width: 'w-32 shrink-0' },
|
|
2396
2571
|
{ key: 'age', label: 'Age', width: 'w-24 shrink-0' },
|
|
2397
2572
|
],
|
|
2398
2573
|
providers: [
|
|
2399
2574
|
{ key: 'name', label: 'Name', width: 'min-w-40' },
|
|
2400
2575
|
{ key: 'package', label: 'Package', width: 'min-w-48' },
|
|
2401
|
-
{ key: 'revision', label: 'Revision', width: 'w-36 shrink-0'
|
|
2576
|
+
{ key: 'revision', label: 'Revision', width: 'w-36 shrink-0' },
|
|
2402
2577
|
{ key: 'status', label: 'Status', width: 'w-32 shrink-0' },
|
|
2403
2578
|
{ key: 'age', label: 'Age', width: 'w-24 shrink-0' },
|
|
2404
2579
|
],
|
|
@@ -2412,13 +2587,13 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
2412
2587
|
{ key: 'name', label: 'Name', width: 'min-w-40' },
|
|
2413
2588
|
{ key: 'composite', label: 'Composite Kind', width: 'w-44 shrink-0' },
|
|
2414
2589
|
{ key: 'mode', label: 'Mode', width: 'w-24 shrink-0' },
|
|
2415
|
-
{ key: 'functions', label: 'Functions', width: 'w-28 shrink-0'
|
|
2590
|
+
{ key: 'functions', label: 'Functions', width: 'w-28 shrink-0' },
|
|
2416
2591
|
{ key: 'age', label: 'Age', width: 'w-24 shrink-0' },
|
|
2417
2592
|
],
|
|
2418
2593
|
compositeresourcedefinitions: [
|
|
2419
2594
|
{ key: 'name', label: 'Name', width: 'min-w-40' },
|
|
2420
2595
|
{ key: 'kind', label: 'Kind', width: 'w-40 shrink-0' },
|
|
2421
|
-
{ key: 'claim', label: 'Claim Kind', width: 'w-40 shrink-0'
|
|
2596
|
+
{ key: 'claim', label: 'Claim Kind', width: 'w-40 shrink-0' },
|
|
2422
2597
|
{ key: 'age', label: 'Age', width: 'w-24 shrink-0' },
|
|
2423
2598
|
],
|
|
2424
2599
|
}
|
|
@@ -2961,6 +3136,8 @@ export function mergeSavedVisibleColumns(
|
|
|
2961
3136
|
savedVisible: string[],
|
|
2962
3137
|
extraKeys: string[],
|
|
2963
3138
|
printerColumns: ExtraColumn[],
|
|
3139
|
+
effective?: Column[],
|
|
3140
|
+
known?: string[],
|
|
2964
3141
|
): Set<string> {
|
|
2965
3142
|
const merged = new Set(savedVisible)
|
|
2966
3143
|
for (const k of extraKeys) merged.add(k)
|
|
@@ -2969,6 +3146,24 @@ export function mergeSavedVisibleColumns(
|
|
|
2969
3146
|
if (c.defaultVisible !== false) merged.add(c.key)
|
|
2970
3147
|
}
|
|
2971
3148
|
}
|
|
3149
|
+
// Curated columns the blob predates. Same principle as the two above, but it
|
|
3150
|
+
// needs a record of what the user was actually offered: the blob stores only
|
|
3151
|
+
// what is visible, so a curated key's absence is ambiguous between "hidden on
|
|
3152
|
+
// purpose" and "not offered yet". `known` is that record. Without one the two
|
|
3153
|
+
// cases are indistinguishable, so nothing is inferred and visibility is left
|
|
3154
|
+
// to the saved set alone.
|
|
3155
|
+
//
|
|
3156
|
+
// Printer columns are excluded even when `known` lists none of them. They are
|
|
3157
|
+
// fetched, so `known` can be written during the window before the printer
|
|
3158
|
+
// table arrives; treating their absence as "new" would re-show ones the user
|
|
3159
|
+
// hid. Their own rule above already covers them.
|
|
3160
|
+
if (effective && Array.isArray(known)) {
|
|
3161
|
+
const seen = new Set(known)
|
|
3162
|
+
for (const c of effective) {
|
|
3163
|
+
if (c.key.startsWith(PRINTER_COLUMN_PREFIX)) continue
|
|
3164
|
+
if (!seen.has(c.key) && c.defaultVisible !== false) merged.add(c.key)
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
2972
3167
|
return merged
|
|
2973
3168
|
}
|
|
2974
3169
|
|
|
@@ -3024,6 +3219,10 @@ interface ColumnSettings {
|
|
|
3024
3219
|
visible: string[]
|
|
3025
3220
|
widths: Record<string, number>
|
|
3026
3221
|
custom?: CustomColumnDef[]
|
|
3222
|
+
/** Every column key the user was offered when this was written. Lets a later
|
|
3223
|
+
* load tell a column they hid from one that did not exist yet. Absent on
|
|
3224
|
+
* blobs written before this field. */
|
|
3225
|
+
known?: string[]
|
|
3027
3226
|
}
|
|
3028
3227
|
|
|
3029
3228
|
function loadColumnSettings(kind: string, group?: string): ColumnSettings | null {
|
|
@@ -3601,8 +3800,17 @@ export function ResourcesView({
|
|
|
3601
3800
|
return [...filteredExtras, ...kindColumns, ...builtCustomColumns]
|
|
3602
3801
|
}, [selectedKind.name, selectedKind.group, extraLeadingColumns, builtCustomColumns, builtPrinterColumns])
|
|
3603
3802
|
|
|
3803
|
+
// The offered key set, and a stable signature for it. allColumns is a new
|
|
3804
|
+
// identity on every printer-column refetch; depending on the array itself
|
|
3805
|
+
// would rewrite the settings blob on each poll.
|
|
3806
|
+
const allColumnKeys = useMemo(() => allColumns.map(c => c.key), [allColumns])
|
|
3807
|
+
const allColumnKeysSig = allColumnKeys.join('\u0000')
|
|
3808
|
+
|
|
3604
3809
|
// Map of extra column keys for fast O(1) lookup on each render path
|
|
3605
3810
|
// (cell render, sort, column-filter unique-values).
|
|
3811
|
+
// Recompute header-derived widths once the web font swaps in (see watchHeaderFont).
|
|
3812
|
+
const headerFontEpoch = useHeaderFontEpoch()
|
|
3813
|
+
|
|
3606
3814
|
const extraColumnsByKey = useMemo(() => {
|
|
3607
3815
|
const m = new Map<string, ExtraColumn>()
|
|
3608
3816
|
extraLeadingColumns?.forEach(c => m.set(c.key, c))
|
|
@@ -3614,6 +3822,13 @@ export function ResourcesView({
|
|
|
3614
3822
|
// Guards the save effect from persisting on the initial load of each kind
|
|
3615
3823
|
// (set false by the load effect, flipped true on its first skipped save).
|
|
3616
3824
|
const isColumnSettingsLoaded = useRef(false)
|
|
3825
|
+
// Every column key this kind has ever offered, accumulated. A single save
|
|
3826
|
+
// cannot stand in for it: the offered set shrinks as well as grows — an
|
|
3827
|
+
// uncurated kind's generic Status disappears once its printer columns arrive
|
|
3828
|
+
// — and a shrunk snapshot would forget that a column absent from `visible`
|
|
3829
|
+
// was one the user hid rather than one never shown.
|
|
3830
|
+
const knownColumnKeys = useRef<Set<string>>(new Set())
|
|
3831
|
+
const knownColumnKeysKind = useRef('')
|
|
3617
3832
|
// Whether the user has a persisted column blob for this kind — data-driven
|
|
3618
3833
|
// column defaults (GPU auto-show) must never override explicit user choices.
|
|
3619
3834
|
const hadSavedColumnSettings = useRef(false)
|
|
@@ -3639,6 +3854,15 @@ export function ResourcesView({
|
|
|
3639
3854
|
// (non-array, blank path, bad source) must not crash the later .map or add dead columns.
|
|
3640
3855
|
const savedCustom = sanitizeCustomColumnDefs(saved?.custom)
|
|
3641
3856
|
setCustomColumns(savedCustom)
|
|
3857
|
+
// Reset on a kind change only. This effect also re-runs when the printer
|
|
3858
|
+
// columns arrive, and that is the moment the offered set shrinks — clearing
|
|
3859
|
+
// here would drop the evidence for the column they replaced just before the
|
|
3860
|
+
// next save persists the record without it.
|
|
3861
|
+
const knownKeysIdentity = selectedKindIdentityOf(selectedKind)
|
|
3862
|
+
if (knownColumnKeysKind.current !== knownKeysIdentity) {
|
|
3863
|
+
knownColumnKeys.current = new Set()
|
|
3864
|
+
knownColumnKeysKind.current = knownKeysIdentity
|
|
3865
|
+
}
|
|
3642
3866
|
const kindColumns = columnsForKindWithPrinter(selectedKind.name, selectedKind.group, builtPrinterColumns)
|
|
3643
3867
|
const builtinKeys = new Set(kindColumns.map(c => c.key))
|
|
3644
3868
|
const extras = filterHostExtras(extraLeadingColumns, builtinKeys, selectedKind.name)
|
|
@@ -3655,18 +3879,32 @@ export function ResourcesView({
|
|
|
3655
3879
|
// discard the stale save and use the specialized columns instead. User-added
|
|
3656
3880
|
// custom columns mean the blob isn't a stale pure-defaults save — keep it, or
|
|
3657
3881
|
// the migration would clear them from storage (and un-hide hidden ones).
|
|
3882
|
+
// Persisted JSON: only an array of keys is usable, anything else reads as
|
|
3883
|
+
// no record at all.
|
|
3884
|
+
const savedKnown = Array.isArray(saved.known)
|
|
3885
|
+
? saved.known.filter((k): k is string => typeof k === 'string')
|
|
3886
|
+
: undefined
|
|
3887
|
+
for (const k of savedKnown ?? []) knownColumnKeys.current.add(k)
|
|
3888
|
+
|
|
3658
3889
|
const defaultKeys = DEFAULT_COLUMNS.map(c => c.key)
|
|
3890
|
+
// A blob carrying an offered-key record is not a pre-curation leftover:
|
|
3891
|
+
// its visible set matching the generic defaults means the user hid their
|
|
3892
|
+
// way down to them, and clearing it would undo exactly that.
|
|
3659
3893
|
const isStaleDefaults = kindColumns !== DEFAULT_COLUMNS &&
|
|
3894
|
+
!savedKnown &&
|
|
3660
3895
|
!savedCustom.length &&
|
|
3661
3896
|
saved.visible.length === defaultKeys.length &&
|
|
3662
3897
|
saved.visible.every(v => defaultKeys.includes(v))
|
|
3663
3898
|
if (isStaleDefaults) {
|
|
3664
3899
|
clearColumnSettings(selectedKind.name, selectedKind.group)
|
|
3900
|
+
knownColumnKeys.current = new Set()
|
|
3665
3901
|
hadSavedColumnSettings.current = false
|
|
3666
3902
|
setVisibleColumns(getDefaultVisibleColumns(effective))
|
|
3667
3903
|
setColumnWidths({})
|
|
3668
3904
|
} else {
|
|
3669
|
-
setVisibleColumns(mergeSavedVisibleColumns(
|
|
3905
|
+
setVisibleColumns(mergeSavedVisibleColumns(
|
|
3906
|
+
saved.visible, extraKeys, builtPrinterColumns, effective, savedKnown,
|
|
3907
|
+
))
|
|
3670
3908
|
setColumnWidths(saved.widths || {})
|
|
3671
3909
|
}
|
|
3672
3910
|
} else {
|
|
@@ -3682,6 +3920,7 @@ export function ResourcesView({
|
|
|
3682
3920
|
// Save column settings when they change (skip the initial load of each kind)
|
|
3683
3921
|
useEffect(() => {
|
|
3684
3922
|
if (visibleColumns.size === 0) return // not loaded yet
|
|
3923
|
+
for (const k of allColumnKeys) knownColumnKeys.current.add(k)
|
|
3685
3924
|
if (!isColumnSettingsLoaded.current) {
|
|
3686
3925
|
isColumnSettingsLoaded.current = true
|
|
3687
3926
|
return
|
|
@@ -3690,11 +3929,13 @@ export function ResourcesView({
|
|
|
3690
3929
|
visible: Array.from(visibleColumns),
|
|
3691
3930
|
widths: columnWidths,
|
|
3692
3931
|
custom: customColumns,
|
|
3932
|
+
known: Array.from(knownColumnKeys.current),
|
|
3693
3933
|
})
|
|
3694
3934
|
// A persisted blob blocks GPU auto-show from here on — same rule in-session
|
|
3695
3935
|
// as across sessions, so a later data refresh can't override user choices.
|
|
3696
3936
|
hadSavedColumnSettings.current = true
|
|
3697
|
-
|
|
3937
|
+
// allColumnKeysSig, not allColumnKeys: see the memo above.
|
|
3938
|
+
}, [visibleColumns, columnWidths, customColumns, selectedKind.name, selectedKind.group, allColumnKeysSig])
|
|
3698
3939
|
|
|
3699
3940
|
// Close column picker on outside click or Escape
|
|
3700
3941
|
useEffect(() => {
|
|
@@ -3797,6 +4038,7 @@ export function ResourcesView({
|
|
|
3797
4038
|
// Reset column settings to defaults (also drops user-added custom columns)
|
|
3798
4039
|
const resetColumnSettings = useCallback(() => {
|
|
3799
4040
|
clearColumnSettings(selectedKind.name, selectedKind.group)
|
|
4041
|
+
knownColumnKeys.current = new Set()
|
|
3800
4042
|
setCustomColumns([])
|
|
3801
4043
|
const customKeys = new Set(builtCustomColumns.map(c => c.key))
|
|
3802
4044
|
setVisibleColumns(getDefaultVisibleColumns(allColumns.filter(c => !customKeys.has(c.key))))
|
|
@@ -5248,12 +5490,12 @@ export function ResourcesView({
|
|
|
5248
5490
|
// scroll horizontally when the viewport is too narrow.
|
|
5249
5491
|
const tableMinWidth = useMemo(() => {
|
|
5250
5492
|
const compareColumnWidth = compareMode ? COMPARE_COLUMN_WIDTH : 0
|
|
5251
|
-
const baseMinWidth = columns.reduce((sum, col) => sum + (columnWidths[col.key] || getColumnMinWidth(col)), compareColumnWidth)
|
|
5493
|
+
const baseMinWidth = columns.reduce((sum, col) => sum + (columnWidths[col.key] || getColumnMinWidth(col, extraColumnsByKey)), compareColumnWidth)
|
|
5252
5494
|
const flexibleNameColumn = columns.find(col => col.key === 'name' && !columnWidths[col.key])
|
|
5253
5495
|
|
|
5254
5496
|
if (!hasResizedColumns || !flexibleNameColumn) return baseMinWidth
|
|
5255
|
-
return baseMinWidth + getColumnMinWidth(flexibleNameColumn)
|
|
5256
|
-
}, [columns, columnWidths, compareMode, hasResizedColumns])
|
|
5497
|
+
return baseMinWidth + getColumnMinWidth(flexibleNameColumn, extraColumnsByKey)
|
|
5498
|
+
}, [columns, columnWidths, compareMode, hasResizedColumns, extraColumnsByKey, headerFontEpoch])
|
|
5257
5499
|
|
|
5258
5500
|
// Stable virtuoso components — memoized to avoid remounting the table on every render
|
|
5259
5501
|
const virtuosoComponents = useMemo(() => ({
|
|
@@ -5281,7 +5523,7 @@ export function ResourcesView({
|
|
|
5281
5523
|
style={{
|
|
5282
5524
|
width: columnWidths[col.key]
|
|
5283
5525
|
? `${columnWidths[col.key]}px`
|
|
5284
|
-
: col.key === 'name' ? undefined : `${getColumnMinWidth(col)}px`,
|
|
5526
|
+
: col.key === 'name' ? undefined : `${getColumnMinWidth(col, extraColumnsByKey)}px`,
|
|
5285
5527
|
}}
|
|
5286
5528
|
/>
|
|
5287
5529
|
))}
|
|
@@ -5292,7 +5534,7 @@ export function ResourcesView({
|
|
|
5292
5534
|
)
|
|
5293
5535
|
}),
|
|
5294
5536
|
TableRow: VirtuosoTableRow,
|
|
5295
|
-
}), [columns, columnWidths, hasResizedColumns, compareMode, tableMinWidth, isCheckboxMode])
|
|
5537
|
+
}), [columns, columnWidths, hasResizedColumns, compareMode, tableMinWidth, isCheckboxMode, extraColumnsByKey, headerFontEpoch])
|
|
5296
5538
|
|
|
5297
5539
|
// Calculate filter options with counts based on current resources (before filtering)
|
|
5298
5540
|
const filterOptions = useMemo(() => {
|
|
@@ -6143,8 +6385,7 @@ export function ResourcesView({
|
|
|
6143
6385
|
)}
|
|
6144
6386
|
{columns.map((col, colIdx) => {
|
|
6145
6387
|
// Built-in sortable keys, plus any extra/custom column that carries its own getSortValue.
|
|
6146
|
-
const isSortable =
|
|
6147
|
-
|| !!extraColumnsByKey.get(col.key)?.getSortValue
|
|
6388
|
+
const isSortable = isColumnSortable(col, extraColumnsByKey)
|
|
6148
6389
|
const isSorted = sortColumn === col.key
|
|
6149
6390
|
const isLastCol = colIdx === columns.length - 1
|
|
6150
6391
|
const filterCol = filterableColumnMap.get(col.key)
|
|
@@ -6164,11 +6405,11 @@ export function ResourcesView({
|
|
|
6164
6405
|
>
|
|
6165
6406
|
<div className="flex items-center gap-1 overflow-hidden">
|
|
6166
6407
|
{col.tooltip ? (
|
|
6167
|
-
<Tooltip content={col.tooltip}>
|
|
6408
|
+
<Tooltip content={col.tooltip} wrapperClassName="min-w-0">
|
|
6168
6409
|
<span className="border-b border-dotted border-theme-text-tertiary truncate">{col.label}</span>
|
|
6169
6410
|
</Tooltip>
|
|
6170
6411
|
) : (
|
|
6171
|
-
<
|
|
6412
|
+
<HeaderLabel label={col.label} />
|
|
6172
6413
|
)}
|
|
6173
6414
|
{isSortable && (
|
|
6174
6415
|
<span className="text-theme-text-tertiary shrink-0">
|
|
@@ -8720,11 +8961,11 @@ function RouteCell({ resource, column }: { resource: any; column: string }) {
|
|
|
8720
8961
|
switch (column) {
|
|
8721
8962
|
case 'status': {
|
|
8722
8963
|
const status = getRouteStatus(resource)
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8964
|
+
const reason = getRouteStatusReason(resource)
|
|
8965
|
+
const badge = <span className={clsx('badge', status.color)}>{status.text}</span>
|
|
8966
|
+
// "Degraded" on its own says something is wrong and not what. The
|
|
8967
|
+
// controller's own reason is usually "the backend you named is missing".
|
|
8968
|
+
return reason ? <Tooltip content={reason}>{badge}</Tooltip> : badge
|
|
8728
8969
|
}
|
|
8729
8970
|
case 'hostnames': {
|
|
8730
8971
|
const hostnames = getRouteHostnames(resource)
|