mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Converge platform table value primitives
This commit is contained in:
@@ -2613,25 +2613,34 @@ Platform table byte-size formatting follows the same rule.
|
||||
`formatPlatformTableBytesValue` owns the repeated positive-byte formatting plus
|
||||
canonical empty-cell marker behavior for dense platform table cells. Docker /
|
||||
Podman native table helpers, Kubernetes node capacity cells, and TrueNAS
|
||||
system/VM byte cells must compose that helper instead of declaring local
|
||||
`formatBytes` wrappers or reimplementing byte-unit precision in table files.
|
||||
system, VM, storage-topology, and protection byte cells must compose that
|
||||
helper instead of declaring local `formatBytes` wrappers, importing the generic
|
||||
formatter in table files, or reimplementing byte-unit precision there.
|
||||
Platform table numeric fallback rendering is registry-backed too.
|
||||
`PlatformTableNumberValue` owns finite-number checking, tabular-number styling,
|
||||
custom empty-marker support, and caller-owned number formatting for dense
|
||||
optional numeric table cells. Docker / Podman native count helpers, Kubernetes
|
||||
optional count cells, Docker Swarm service desired/running counts, Kubernetes
|
||||
Deployment replica counts, and Proxmox Mail Gateway count columns must compose
|
||||
that primitive instead of declaring local `numberValue`, `numericValue`,
|
||||
`replicaCount`, or `countCell` helpers. If a scheduler or service-domain count
|
||||
is intentionally zero-defaulted, the consuming table owns that field/default
|
||||
choice and still renders through `PlatformTableNumberValue`.
|
||||
Deployment replica counts, Proxmox Mail Gateway count columns, and TrueNAS
|
||||
system share/service and storage-topology disk count cells must compose that
|
||||
primitive instead of declaring local `numberValue`, `numericValue`,
|
||||
`replicaCount`, `countCell`, `diskCountLabel`, or cell-level `tabular-nums`
|
||||
variants. If a scheduler, service-domain, or inventory count is intentionally
|
||||
zero-defaulted, the consuming table owns that field/default choice and still
|
||||
renders through `PlatformTableNumberValue`.
|
||||
`PlatformTableCountRatioValue` owns the companion healthy/total or ready/total
|
||||
count-ratio skeleton: numerator, slash, muted denominator, tabular styling, and
|
||||
empty marker behavior. Kubernetes cluster child counts compose that primitive
|
||||
instead of keeping a table-local `childCountCell` renderer; the table owns only
|
||||
which current/total values and warning tone apply.
|
||||
One-decimal percent and positive Celsius cells are also shared platform-table
|
||||
value primitives. `PlatformTablePercentValue` owns percent formatting,
|
||||
tabular-number styling, and empty markers, while
|
||||
`PlatformTableTemperatureValue` owns finite positive Celsius validation,
|
||||
one-decimal `°C` formatting, tabular-number styling, and empty markers. Docker
|
||||
/ Podman host and TrueNAS system tables must compose those primitives instead
|
||||
of carrying local `formatPercent` or `formatTemperature` helpers.
|
||||
/ Podman host and TrueNAS system/storage-topology tables must compose those
|
||||
primitives instead of carrying local `formatPercent`, `formatTemperature`, or
|
||||
temperature label helpers.
|
||||
Platform table metric fallback rendering is also shared.
|
||||
`PlatformTableMetricFallback` owns the centered muted empty marker used in
|
||||
metric bar cells plus optional caller-owned fallback label/title text, and
|
||||
|
||||
@@ -249,20 +249,25 @@ consumers own whether a capacity, memory, disk, or requested-size field is the
|
||||
right source-specific value, while dense platform table cells must use
|
||||
`formatPlatformTableBytesValue` for positive byte formatting and unknown /
|
||||
non-positive empty-cell markers instead of carrying table-local `formatBytes`
|
||||
wrappers.
|
||||
wrappers or direct generic byte-formatter imports.
|
||||
Optional numeric table cells follow the same split: unified-resource consumers
|
||||
own which count or replica field is meaningful, whether the domain should
|
||||
zero-default an absent scheduler/service count, and whether a domain-specific
|
||||
formatter is needed, while dense platform table cells must use
|
||||
`PlatformTableNumberValue` for finite-number validation, tabular styling, and
|
||||
unknown empty-cell markers instead of carrying local `numberValue`,
|
||||
`numericValue`, `replicaCount`, or `countCell` helpers.
|
||||
zero-default an absent scheduler/service/inventory count, whether a
|
||||
domain-specific formatter is needed, and which current/total fields belong in a
|
||||
grouped health ratio, while dense platform table cells must use
|
||||
`PlatformTableNumberValue` for scalar count validation, tabular styling, and
|
||||
unknown empty-cell markers, and `PlatformTableCountRatioValue` for
|
||||
healthy/total or ready/total count-ratio skeletons. Tables must not carry local
|
||||
`numberValue`, `numericValue`, `replicaCount`, `countCell`, `childCountCell`,
|
||||
`diskCountLabel`, or cell-level `tabular-nums` variants for grouped
|
||||
child/share/service/storage counts.
|
||||
Percent and temperature table cells follow the same split: unified-resource
|
||||
consumers own which usage percentage or temperature source is meaningful for
|
||||
the row, while dense platform table rendering must use
|
||||
`PlatformTablePercentValue` and `PlatformTableTemperatureValue` for one-decimal
|
||||
formatting, positive Celsius validation, tabular styling, and empty markers
|
||||
instead of local `formatPercent` or `formatTemperature` helpers.
|
||||
instead of local `formatPercent`, `formatTemperature`, or temperature label
|
||||
helpers.
|
||||
Metric bar fallbacks follow that split as well: unified-resource consumers own
|
||||
which CPU or memory value is selected, plus any source-specific fallback reason
|
||||
such as outdated standalone agent telemetry, while platform tables must use
|
||||
|
||||
@@ -1371,9 +1371,6 @@ const HELPER_RULES = [
|
||||
// The kind-based wrappers internally delegate to the align-based
|
||||
// helpers; that's the one legitimate call site.
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
// KubernetesClustersTable is pending migration by another agent.
|
||||
// Remove this entry once that work lands.
|
||||
'src/features/kubernetes/KubernetesClustersTable.tsx',
|
||||
]),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1347,6 +1347,8 @@
|
||||
"requiredConsumers": [
|
||||
{ "path": "src/features/docker/DockerNativeTableShared.tsx" },
|
||||
{ "path": "src/features/kubernetes/KubernetesNodesTable.tsx" },
|
||||
{ "path": "src/features/truenas/TrueNASProtectionTable.tsx" },
|
||||
{ "path": "src/features/truenas/TrueNASStorageTopologyTable.tsx" },
|
||||
{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" },
|
||||
{ "path": "src/features/truenas/TrueNASVirtualMachinesTable.tsx" }
|
||||
],
|
||||
@@ -1359,6 +1361,18 @@
|
||||
"path": "src/features/kubernetes/KubernetesNodesTable.tsx",
|
||||
"patterns": ["const formatBytes", "value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)"]
|
||||
},
|
||||
{
|
||||
"path": "src/features/truenas/TrueNASProtectionTable.tsx",
|
||||
"patterns": ["formatBytes(point.sizeBytes)"]
|
||||
},
|
||||
{
|
||||
"path": "src/features/truenas/TrueNASStorageTopologyTable.tsx",
|
||||
"patterns": [
|
||||
"formatBytes(size)",
|
||||
"formatBytes(row.resource.disk.used)",
|
||||
"formatBytes(row.resource.disk.total)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/truenas/TrueNASSystemsTable.tsx",
|
||||
"patterns": ["const formatBytes", "value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)"]
|
||||
@@ -1391,7 +1405,9 @@
|
||||
{ "path": "src/features/kubernetes/KubernetesDeploymentsTable.tsx" },
|
||||
{ "path": "src/features/kubernetes/KubernetesEventsTable.tsx" },
|
||||
{ "path": "src/features/kubernetes/KubernetesPodsTable.tsx" },
|
||||
{ "path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx" }
|
||||
{ "path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx" },
|
||||
{ "path": "src/features/truenas/TrueNASStorageTopologyTable.tsx" },
|
||||
{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" }
|
||||
],
|
||||
"forbiddenPatterns": [
|
||||
{
|
||||
@@ -1446,6 +1462,35 @@
|
||||
{
|
||||
"path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx",
|
||||
"patterns": ["const countCell", "value.toLocaleString() : '—'"]
|
||||
},
|
||||
{
|
||||
"path": "src/features/truenas/TrueNASSystemsTable.tsx",
|
||||
"patterns": ["hidden text-base-content tabular-nums lg:table-cell"]
|
||||
},
|
||||
{
|
||||
"path": "src/features/truenas/TrueNASStorageTopologyTable.tsx",
|
||||
"patterns": ["const diskCountLabel"]
|
||||
}
|
||||
],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-count-ratio-value",
|
||||
"category": "platform-table-text-value",
|
||||
"summary": "Platform tables must use the shared count-ratio primitive for healthy/total or ready/total count cells instead of recreating local numerator/slash/denominator markup.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "PlatformTableCountRatioValue"
|
||||
},
|
||||
"requiredConsumers": [{ "path": "src/features/kubernetes/KubernetesClustersTable.tsx" }],
|
||||
"forbiddenPatterns": [
|
||||
{
|
||||
"path": "src/features/kubernetes/KubernetesClustersTable.tsx",
|
||||
"patterns": ["const childCountCell", "<span class=\"text-muted\">/{count.total}</span>"]
|
||||
}
|
||||
],
|
||||
"proof": [
|
||||
@@ -1488,6 +1533,7 @@
|
||||
},
|
||||
"requiredConsumers": [
|
||||
{ "path": "src/features/docker/DockerHostsTable.tsx" },
|
||||
{ "path": "src/features/truenas/TrueNASStorageTopologyTable.tsx" },
|
||||
{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" }
|
||||
],
|
||||
"forbiddenPatterns": [
|
||||
@@ -1498,6 +1544,10 @@
|
||||
"<span class=\"tabular-nums\">{celsius.toFixed(1)}°C</span>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/truenas/TrueNASStorageTopologyTable.tsx",
|
||||
"patterns": ["const temperatureLabel", "${Math.round(value)}C"]
|
||||
},
|
||||
{
|
||||
"path": "src/features/truenas/TrueNASSystemsTable.tsx",
|
||||
"patterns": [
|
||||
@@ -2108,6 +2158,26 @@
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-local-format-bytes-import",
|
||||
"category": "platform-table-text-value",
|
||||
"summary": "Platform feature tables must not import the generic byte formatter directly for dense table byte cells.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "formatPlatformTableBytesValue"
|
||||
},
|
||||
"scopes": ["src/features/truenas"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["import { formatBytes } from '@/utils/format';", "formatBytes("],
|
||||
"legacyReason": "Retired migration debt. Dense platform table byte cells must use formatPlatformTableBytesValue so positive-byte formatting and empty markers stay shared.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": [],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-local-number-value-helper",
|
||||
"category": "platform-table-text-value",
|
||||
@@ -2188,6 +2258,46 @@
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-local-count-ratio-helper",
|
||||
"category": "platform-table-text-value",
|
||||
"summary": "Platform tables must not recreate local healthy/total count-ratio helpers.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "PlatformTableCountRatioValue"
|
||||
},
|
||||
"scopes": ["src/features/kubernetes"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["const childCountCell", "<span class=\"text-muted\">/{count.total}</span>"],
|
||||
"legacyReason": "Retired migration debt. Healthy/total and ready/total table count cells must compose PlatformTableCountRatioValue so ratio markup, tabular styling, muted denominator styling, and empty markers stay shared.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": [],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-local-truenas-count-cell-class",
|
||||
"category": "platform-table-text-value",
|
||||
"summary": "TrueNAS table scalar value cells must not put tabular-number styling on table cells instead of shared value primitives.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "PlatformTableNumberValue"
|
||||
},
|
||||
"scopes": ["src/features/truenas"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["TrueNAS", "hidden text-base-content tabular-nums lg:table-cell"],
|
||||
"legacyReason": "Retired migration debt. TrueNAS share/service and adjacent scalar value cells must render through shared platform value primitives so finite-number checks, tabular styling, and empty markers stay shared.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": [],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-local-percent-value-helper",
|
||||
"category": "platform-table-text-value",
|
||||
@@ -2234,6 +2344,26 @@
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-local-temperature-label-helper",
|
||||
"category": "platform-table-text-value",
|
||||
"summary": "Platform tables must not recreate local temperatureLabel helpers for positive Celsius cells.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "PlatformTableTemperatureValue"
|
||||
},
|
||||
"scopes": ["src/features/truenas"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["const temperatureLabel", "${Math.round(value)}C"],
|
||||
"legacyReason": "Retired migration debt. Positive Celsius table cells must compose PlatformTableTemperatureValue so validity, formatting, tabular styling, and empty markers stay shared.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": [],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-local-metric-fallback-helper",
|
||||
"category": "platform-table-metric",
|
||||
|
||||
@@ -3689,9 +3689,14 @@ describe('shared primitive guardrails', () => {
|
||||
const localHelperGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'platform-table-local-bytes-helper',
|
||||
);
|
||||
const localFormatBytesImportGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'platform-table-local-format-bytes-import',
|
||||
);
|
||||
const platformByteValueConsumers: Array<[string, string]> = [
|
||||
['src/features/docker/DockerNativeTableShared.tsx', dockerNativeTableSharedSource],
|
||||
['src/features/kubernetes/KubernetesNodesTable.tsx', kubernetesNodesTableSource],
|
||||
['src/features/truenas/TrueNASProtectionTable.tsx', truenasProtectionTableSource],
|
||||
['src/features/truenas/TrueNASStorageTopologyTable.tsx', truenasStorageTopologyTableSource],
|
||||
['src/features/truenas/TrueNASSystemsTable.tsx', truenasSystemsTableSource],
|
||||
['src/features/truenas/TrueNASVirtualMachinesTable.tsx', truenasVirtualMachinesTableSource],
|
||||
];
|
||||
@@ -3717,6 +3722,18 @@ describe('shared primitive guardrails', () => {
|
||||
path: 'src/features/kubernetes/KubernetesNodesTable.tsx',
|
||||
patterns: localFormatBytesHelperPatterns,
|
||||
},
|
||||
{
|
||||
path: 'src/features/truenas/TrueNASProtectionTable.tsx',
|
||||
patterns: ['formatBytes(point.sizeBytes)'],
|
||||
},
|
||||
{
|
||||
path: 'src/features/truenas/TrueNASStorageTopologyTable.tsx',
|
||||
patterns: [
|
||||
'formatBytes(size)',
|
||||
'formatBytes(row.resource.disk.used)',
|
||||
'formatBytes(row.resource.disk.total)',
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'src/features/truenas/TrueNASSystemsTable.tsx',
|
||||
patterns: localFormatBytesHelperPatterns,
|
||||
@@ -3738,6 +3755,17 @@ describe('shared primitive guardrails', () => {
|
||||
]);
|
||||
expect(localHelperGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(localHelperGuard?.ignoredPaths ?? []).toHaveLength(0);
|
||||
expect(localFormatBytesImportGuard?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(localFormatBytesImportGuard?.canonical?.export).toBe('formatPlatformTableBytesValue');
|
||||
expect(localFormatBytesImportGuard?.allPatterns).toEqual([
|
||||
"import { formatBytes } from '@/utils/format';",
|
||||
'formatBytes(',
|
||||
]);
|
||||
expect(localFormatBytesImportGuard?.scopes).toEqual(['src/features/truenas']);
|
||||
expect(localFormatBytesImportGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(localFormatBytesImportGuard?.ignoredPaths ?? []).toHaveLength(0);
|
||||
|
||||
expect(sharedPlatformPageSource).toContain('export const formatPlatformTableBytesValue');
|
||||
expect(sharedPlatformPageSource).toContain('formatBytes(bytes)');
|
||||
@@ -3787,6 +3815,9 @@ describe('shared primitive guardrails', () => {
|
||||
const localSwarmCountGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'platform-table-local-swarm-count-spans',
|
||||
);
|
||||
const localTrueNASCountGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'platform-table-local-truenas-count-cell-class',
|
||||
);
|
||||
const platformNumberValueConsumers: Array<[string, string]> = [
|
||||
['src/features/docker/DockerNativeTableShared.tsx', dockerNativeTableSharedSource],
|
||||
['src/features/docker/DockerServicesTable.tsx', dockerServicesTableSource],
|
||||
@@ -3797,6 +3828,8 @@ describe('shared primitive guardrails', () => {
|
||||
['src/features/kubernetes/KubernetesEventsTable.tsx', kubernetesEventsTableSource],
|
||||
['src/features/kubernetes/KubernetesPodsTable.tsx', kubernetesPodsTableSource],
|
||||
['src/features/proxmox/ProxmoxMailGatewayTable.tsx', proxmoxMailGatewayTableSource],
|
||||
['src/features/truenas/TrueNASStorageTopologyTable.tsx', truenasStorageTopologyTableSource],
|
||||
['src/features/truenas/TrueNASSystemsTable.tsx', truenasSystemsTableSource],
|
||||
];
|
||||
const platformNumberValueConsumerPaths = platformNumberValueConsumers.map(([path]) => path);
|
||||
const optionalNumberMarkupPattern =
|
||||
@@ -3849,6 +3882,14 @@ describe('shared primitive guardrails', () => {
|
||||
path: 'src/features/proxmox/ProxmoxMailGatewayTable.tsx',
|
||||
patterns: ['const countCell', "value.toLocaleString() : '—'"],
|
||||
},
|
||||
{
|
||||
path: 'src/features/truenas/TrueNASSystemsTable.tsx',
|
||||
patterns: ['hidden text-base-content tabular-nums lg:table-cell'],
|
||||
},
|
||||
{
|
||||
path: 'src/features/truenas/TrueNASStorageTopologyTable.tsx',
|
||||
patterns: ['const diskCountLabel'],
|
||||
},
|
||||
]);
|
||||
expect(localNumberGuard?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
@@ -3898,6 +3939,17 @@ describe('shared primitive guardrails', () => {
|
||||
expect(localSwarmCountGuard?.scopes).toEqual(['src/components/Docker']);
|
||||
expect(localSwarmCountGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(localSwarmCountGuard?.ignoredPaths ?? []).toHaveLength(0);
|
||||
expect(localTrueNASCountGuard?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(localTrueNASCountGuard?.canonical?.export).toBe('PlatformTableNumberValue');
|
||||
expect(localTrueNASCountGuard?.allPatterns).toEqual([
|
||||
'TrueNAS',
|
||||
'hidden text-base-content tabular-nums lg:table-cell',
|
||||
]);
|
||||
expect(localTrueNASCountGuard?.scopes).toEqual(['src/features/truenas']);
|
||||
expect(localTrueNASCountGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(localTrueNASCountGuard?.ignoredPaths ?? []).toHaveLength(0);
|
||||
|
||||
expect(sharedPlatformPageSource).toContain('export function PlatformTableNumberValue');
|
||||
expect(sharedPlatformPageSource).toContain('Number.isFinite(value)');
|
||||
@@ -3914,6 +3966,71 @@ describe('shared primitive guardrails', () => {
|
||||
expect(proxmoxMailGatewayTableSource).toContain('format={formatLocaleCount}');
|
||||
});
|
||||
|
||||
it('keeps platform table count ratios on the shared primitive', () => {
|
||||
const registry = JSON.parse(sharedTemplateRegistrySource) as {
|
||||
rules?: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
requiredConsumers?: Array<{ path?: string }>;
|
||||
forbiddenPatterns?: Array<{ path?: string; patterns?: string[] }>;
|
||||
}>;
|
||||
patternGuards?: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
allPatterns?: string[];
|
||||
scopes?: string[];
|
||||
allowedPaths?: string[];
|
||||
ignoredPaths?: string[];
|
||||
}>;
|
||||
};
|
||||
const registeredRule = registry.rules?.find(
|
||||
(rule) => rule.id === 'platform-table-count-ratio-value',
|
||||
);
|
||||
const localCountRatioGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'platform-table-local-count-ratio-helper',
|
||||
);
|
||||
const platformCountRatioConsumers: Array<[string, string]> = [
|
||||
['src/features/kubernetes/KubernetesClustersTable.tsx', kubernetesClustersTableSource],
|
||||
];
|
||||
const platformCountRatioConsumerPaths = platformCountRatioConsumers.map(([path]) => path);
|
||||
|
||||
expect(registeredRule?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(registeredRule?.canonical?.export).toBe('PlatformTableCountRatioValue');
|
||||
expect(registeredRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual(
|
||||
platformCountRatioConsumerPaths,
|
||||
);
|
||||
expect(registeredRule?.forbiddenPatterns).toEqual([
|
||||
{
|
||||
path: 'src/features/kubernetes/KubernetesClustersTable.tsx',
|
||||
patterns: ['const childCountCell', '<span class="text-muted">/{count.total}</span>'],
|
||||
},
|
||||
]);
|
||||
expect(localCountRatioGuard?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(localCountRatioGuard?.canonical?.export).toBe('PlatformTableCountRatioValue');
|
||||
expect(localCountRatioGuard?.allPatterns).toEqual([
|
||||
'const childCountCell',
|
||||
'<span class="text-muted">/{count.total}</span>',
|
||||
]);
|
||||
expect(localCountRatioGuard?.scopes).toEqual(['src/features/kubernetes']);
|
||||
expect(localCountRatioGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(localCountRatioGuard?.ignoredPaths ?? []).toHaveLength(0);
|
||||
|
||||
expect(sharedPlatformPageSource).toContain('export function PlatformTableCountRatioValue');
|
||||
|
||||
for (const [path, source] of platformCountRatioConsumers) {
|
||||
expect(source).toContain('PlatformTableCountRatioValue');
|
||||
const forbiddenPatterns =
|
||||
registeredRule?.forbiddenPatterns?.find((entry) => entry.path === path)?.patterns ?? [];
|
||||
for (const pattern of forbiddenPatterns) {
|
||||
expect(source).not.toContain(pattern);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps platform table scalar unit values on shared primitives', () => {
|
||||
const registry = JSON.parse(sharedTemplateRegistrySource) as {
|
||||
rules?: Array<{
|
||||
@@ -3943,11 +4060,15 @@ describe('shared primitive guardrails', () => {
|
||||
const localTemperatureGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'platform-table-local-temperature-value-helper',
|
||||
);
|
||||
const localTemperatureLabelGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'platform-table-local-temperature-label-helper',
|
||||
);
|
||||
const percentConsumers: Array<[string, string]> = [
|
||||
['src/features/truenas/TrueNASSystemsTable.tsx', truenasSystemsTableSource],
|
||||
];
|
||||
const temperatureConsumers: Array<[string, string]> = [
|
||||
['src/features/docker/DockerHostsTable.tsx', dockerHostsTableSource],
|
||||
['src/features/truenas/TrueNASStorageTopologyTable.tsx', truenasStorageTopologyTableSource],
|
||||
['src/features/truenas/TrueNASSystemsTable.tsx', truenasSystemsTableSource],
|
||||
];
|
||||
const localPercentPatterns = [
|
||||
@@ -3983,6 +4104,10 @@ describe('shared primitive guardrails', () => {
|
||||
path: 'src/features/docker/DockerHostsTable.tsx',
|
||||
patterns: localTemperaturePatterns,
|
||||
},
|
||||
{
|
||||
path: 'src/features/truenas/TrueNASStorageTopologyTable.tsx',
|
||||
patterns: ['const temperatureLabel', '${Math.round(value)}C'],
|
||||
},
|
||||
{
|
||||
path: 'src/features/truenas/TrueNASSystemsTable.tsx',
|
||||
patterns: localTemperaturePatterns,
|
||||
@@ -4005,6 +4130,17 @@ describe('shared primitive guardrails', () => {
|
||||
expect(localTemperatureGuard?.scopes).toEqual(['src/features/docker', 'src/features/truenas']);
|
||||
expect(localTemperatureGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(localTemperatureGuard?.ignoredPaths ?? []).toHaveLength(0);
|
||||
expect(localTemperatureLabelGuard?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(localTemperatureLabelGuard?.canonical?.export).toBe('PlatformTableTemperatureValue');
|
||||
expect(localTemperatureLabelGuard?.allPatterns).toEqual([
|
||||
'const temperatureLabel',
|
||||
'${Math.round(value)}C',
|
||||
]);
|
||||
expect(localTemperatureLabelGuard?.scopes).toEqual(['src/features/truenas']);
|
||||
expect(localTemperatureLabelGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(localTemperatureLabelGuard?.ignoredPaths ?? []).toHaveLength(0);
|
||||
|
||||
expect(sharedPlatformPageSource).toContain('export function PlatformTablePercentValue');
|
||||
expect(sharedPlatformPageSource).toContain('export function PlatformTableTemperatureValue');
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
import {
|
||||
PLATFORM_HEALTH_FILTER_OPTIONS,
|
||||
PlatformTableCountRatioValue,
|
||||
PlatformTableMetricFallback,
|
||||
PlatformTableToolbar,
|
||||
PlatformTableEmptyState,
|
||||
@@ -30,7 +31,6 @@ import {
|
||||
buildKubernetesClusterChildCounts,
|
||||
emptyKubernetesClusterChildCounts,
|
||||
filterKubernetesResources,
|
||||
type KubernetesClusterChildCount,
|
||||
type KubernetesResourceStatusFilter,
|
||||
} from './kubernetesPageModel';
|
||||
|
||||
@@ -44,18 +44,6 @@ import {
|
||||
// CPU/Memory utilisation. It reuses the same shared primitives every
|
||||
// other platform-page table uses.
|
||||
|
||||
// v5-style healthy/total fraction: the healthy share turns amber as soon as
|
||||
// any child needs attention, so a cluster with a NotReady node reads "2/3"
|
||||
// instead of a flat "3".
|
||||
const childCountCell = (count: KubernetesClusterChildCount) => (
|
||||
<>
|
||||
<span class={count.attention > 0 ? 'text-amber-700 dark:text-amber-300' : ''}>
|
||||
{count.total - count.attention}
|
||||
</span>
|
||||
<span class="text-muted">/{count.total}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
export const KubernetesClustersTable: Component<{
|
||||
clusters: Resource[];
|
||||
// All Kubernetes-tagged resources from the same query, so the table can
|
||||
@@ -231,19 +219,33 @@ export const KubernetesClustersTable: Component<{
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
>
|
||||
{childCountCell(counts().nodes)}
|
||||
<PlatformTableCountRatioValue
|
||||
current={counts().nodes.total - counts().nodes.attention}
|
||||
total={counts().nodes.total}
|
||||
currentTone={counts().nodes.attention > 0 ? 'warning' : undefined}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content md:table-cell`}
|
||||
>
|
||||
{childCountCell(counts().pods)}
|
||||
<PlatformTableCountRatioValue
|
||||
current={counts().pods.total - counts().pods.attention}
|
||||
total={counts().pods.total}
|
||||
currentTone={counts().pods.attention > 0 ? 'warning' : undefined}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content md:table-cell`}
|
||||
>
|
||||
{childCountCell(counts().deployments)}
|
||||
<PlatformTableCountRatioValue
|
||||
current={counts().deployments.total - counts().deployments.attention}
|
||||
total={counts().deployments.total}
|
||||
currentTone={
|
||||
counts().deployments.attention > 0 ? 'warning' : undefined
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('metric-bar')} w-[20%] md:w-auto`}
|
||||
|
||||
@@ -73,6 +73,54 @@ describe('KubernetesClustersTable', () => {
|
||||
expect(screen.queryByText('Pending uninstall')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders child counts through the shared count-ratio primitive', () => {
|
||||
const cluster = makeClusterResource();
|
||||
|
||||
const { container } = render(() => (
|
||||
<KubernetesClustersTable
|
||||
clusters={[cluster]}
|
||||
scope={[
|
||||
cluster,
|
||||
{
|
||||
...makeClusterResource({
|
||||
id: 'node-ready',
|
||||
type: 'k8s-node',
|
||||
name: 'node-ready',
|
||||
status: 'online',
|
||||
kubernetes: {
|
||||
clusterId: 'prod-west',
|
||||
clusterName: 'prod-west',
|
||||
ready: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
...makeClusterResource({
|
||||
id: 'node-not-ready',
|
||||
type: 'k8s-node',
|
||||
name: 'node-not-ready',
|
||||
status: 'offline',
|
||||
kubernetes: {
|
||||
clusterId: 'prod-west',
|
||||
clusterName: 'prod-west',
|
||||
ready: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
]}
|
||||
emptyIcon={<span />}
|
||||
emptyTitle="No clusters"
|
||||
emptyDescription="No clusters"
|
||||
showToolbar={false}
|
||||
/>
|
||||
));
|
||||
|
||||
const row = container.querySelector('[data-kubernetes-cluster-row="cluster:prod-west"]');
|
||||
expect(row?.textContent).toContain('1/2');
|
||||
expect(row?.querySelector('.text-amber-700')?.textContent).toBe('1');
|
||||
expect(row?.querySelectorAll('.tabular-nums').length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('badges clusters whose agent is pending uninstall', () => {
|
||||
const cluster = makeClusterResource({
|
||||
kubernetes: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { createRoot, createSignal } from 'solid-js';
|
||||
import type { Resource } from '@/types/resource';
|
||||
import {
|
||||
PlatformTableCountRatioValue,
|
||||
PlatformTableMetricFallback,
|
||||
PlatformTableNumberValue,
|
||||
PlatformTablePercentValue,
|
||||
@@ -333,6 +334,37 @@ describe('PlatformTableNumberValue', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('PlatformTableCountRatioValue', () => {
|
||||
it('renders count ratios through shared tabular number styling', () => {
|
||||
const { container } = render(() =>
|
||||
PlatformTableCountRatioValue({
|
||||
current: 2,
|
||||
total: 3,
|
||||
currentTone: 'warning',
|
||||
suffix: 'ready',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(container.textContent).toBe('2/3 ready');
|
||||
expect(container.querySelectorAll('.tabular-nums')).toHaveLength(2);
|
||||
expect(screen.getByText('2').parentElement?.classList.contains('text-amber-700')).toBe(true);
|
||||
expect(screen.getByText('/').classList.contains('text-muted')).toBe(true);
|
||||
expect(screen.getByText('3').parentElement?.classList.contains('text-muted')).toBe(true);
|
||||
});
|
||||
|
||||
it('uses the shared empty marker when neither side is finite', () => {
|
||||
render(() =>
|
||||
PlatformTableCountRatioValue({
|
||||
current: undefined,
|
||||
total: Number.NaN,
|
||||
emptyText: '-',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PlatformTablePercentValue', () => {
|
||||
it('renders finite percentages with one decimal place and shared tabular styling', () => {
|
||||
const { container } = render(() => PlatformTablePercentValue({ value: 42.345 }));
|
||||
|
||||
@@ -221,6 +221,41 @@ export function PlatformTableNumberValue(props: {
|
||||
return <span class="tabular-nums">{label()}</span>;
|
||||
}
|
||||
|
||||
export function PlatformTableCountRatioValue(props: {
|
||||
current: number | undefined;
|
||||
total: number | undefined;
|
||||
currentTone?: 'warning';
|
||||
emptyText?: string;
|
||||
suffix?: string;
|
||||
}) {
|
||||
const hasAnyValue = () =>
|
||||
[props.current, props.total].some(
|
||||
(value) => typeof value === 'number' && Number.isFinite(value),
|
||||
);
|
||||
const currentClass = () =>
|
||||
props.currentTone === 'warning' ? 'text-amber-700 dark:text-amber-300' : '';
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={hasAnyValue()}
|
||||
fallback={<PlatformTableNumberValue value={undefined} emptyText={props.emptyText} />}
|
||||
>
|
||||
<span class="inline-flex items-baseline whitespace-nowrap">
|
||||
<span class={currentClass()}>
|
||||
<PlatformTableNumberValue value={props.current} emptyText={props.emptyText} />
|
||||
</span>
|
||||
<span class="text-muted">/</span>
|
||||
<span class="text-muted">
|
||||
<PlatformTableNumberValue value={props.total} emptyText={props.emptyText} />
|
||||
</span>
|
||||
<Show when={props.suffix}>
|
||||
{(suffix) => <span class="ml-1 text-muted"> {suffix()}</span>}
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
|
||||
const formatOneDecimalPercent = (value: number): string => `${value.toFixed(1)}%`;
|
||||
const formatOneDecimalCelsius = (value: number): string => `${value.toFixed(1)}°C`;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { For, Show, createMemo, type Component, type JSX } from 'solid-js';
|
||||
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { formatBytes } from '@/utils/format';
|
||||
import {
|
||||
getRecoveryOutcomeBadgeClass,
|
||||
getRecoveryOutcomeLabel,
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
PlatformTableLoadingState,
|
||||
PlatformTableToolbar,
|
||||
createPlatformTableFilterState,
|
||||
formatPlatformTableBytesValue,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
type PlatformTableFilterOption,
|
||||
@@ -174,7 +174,7 @@ const formatPointTime = (point: RecoveryPoint): string => {
|
||||
};
|
||||
|
||||
const sizeLabel = (point: RecoveryPoint): string =>
|
||||
typeof point.sizeBytes === 'number' && point.sizeBytes > 0 ? formatBytes(point.sizeBytes) : '-';
|
||||
formatPlatformTableBytesValue(point.sizeBytes ?? undefined, '-');
|
||||
|
||||
type ProtectionDetailTone = DetailValueTone;
|
||||
type ProtectionDetailSection = DetailSection;
|
||||
@@ -483,7 +483,7 @@ export const TrueNASProtectionTable: Component<{
|
||||
{formatPointTime(point)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums lg:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content lg:table-cell`}
|
||||
>
|
||||
{sizeLabel(point)}
|
||||
</TableCell>
|
||||
|
||||
@@ -3,14 +3,16 @@ import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { ResponsiveMetricCell } from '@/components/shared/responsive';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import { formatBytes } from '@/utils/format';
|
||||
import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
import { buildMetricKeyForUnifiedResource } from '@/utils/metricsKeys';
|
||||
import {
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableNumberValue,
|
||||
PlatformTableTemperatureValue,
|
||||
PlatformTableToolbar,
|
||||
createPlatformTableFilterState,
|
||||
formatPlatformTableBytesValue,
|
||||
formatPlatformTableTitleCaseValue,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
@@ -88,12 +90,12 @@ const capacityPercent = (resource: Resource): number | undefined => {
|
||||
|
||||
const diskSizeLabel = (row: TrueNASStorageTopologyRow): string => {
|
||||
const size = row.resource.physicalDisk?.sizeBytes;
|
||||
return typeof size === 'number' && size > 0 ? formatBytes(size) : '-';
|
||||
return formatPlatformTableBytesValue(size, '-');
|
||||
};
|
||||
|
||||
const capacitySublabel = (row: TrueNASStorageTopologyRow): string | undefined => {
|
||||
if (typeof row.resource.disk?.used === 'number' && typeof row.resource.disk?.total === 'number') {
|
||||
return `${formatBytes(row.resource.disk.used)} / ${formatBytes(row.resource.disk.total)}`;
|
||||
return `${formatPlatformTableBytesValue(row.resource.disk.used, '-')} / ${formatPlatformTableBytesValue(row.resource.disk.total, '-')}`;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -160,17 +162,6 @@ const RiskPill: Component<{ row: TrueNASStorageTopologyRow }> = (props) => (
|
||||
</span>
|
||||
);
|
||||
|
||||
const temperatureLabel = (row: TrueNASStorageTopologyRow): string => {
|
||||
if (row.kind !== 'disk') return '-';
|
||||
const value = row.resource.physicalDisk?.temperature;
|
||||
return typeof value === 'number' && Number.isFinite(value) ? `${Math.round(value)}C` : '-';
|
||||
};
|
||||
|
||||
const diskCountLabel = (row: TrueNASStorageTopologyRow): string => {
|
||||
if (row.kind === 'pool') return String(row.counts.disks);
|
||||
return '-';
|
||||
};
|
||||
|
||||
export const getTrueNASStorageTopologyIndentClass = (depth: number): string => {
|
||||
if (depth <= 0) return '';
|
||||
if (depth === 1) return 'pl-5 sm:pl-7';
|
||||
@@ -328,14 +319,24 @@ export const TrueNASStorageTopologyTable: Component<{
|
||||
<CapacityCell row={row} />
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content md:table-cell`}
|
||||
>
|
||||
{diskCountLabel(row)}
|
||||
<PlatformTableNumberValue
|
||||
value={row.kind === 'pool' ? row.counts.disks : undefined}
|
||||
emptyText="-"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums lg:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content lg:table-cell`}
|
||||
>
|
||||
{temperatureLabel(row)}
|
||||
<PlatformTableTemperatureValue
|
||||
value={
|
||||
row.kind === 'disk'
|
||||
? row.resource.physicalDisk?.temperature
|
||||
: undefined
|
||||
}
|
||||
emptyText="-"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell class={getPlatformTableCellClassForKind('badge')}>
|
||||
<RiskPill row={row} />
|
||||
|
||||
@@ -9,6 +9,7 @@ import { buildMetricKeyForUnifiedResource } from '@/utils/metricsKeys';
|
||||
import {
|
||||
PLATFORM_HEALTH_FILTER_OPTIONS,
|
||||
PlatformTableMetricFallback,
|
||||
PlatformTableNumberValue,
|
||||
PlatformTablePercentValue,
|
||||
PlatformTableTemperatureValue,
|
||||
PlatformTableToolbar,
|
||||
@@ -322,9 +323,9 @@ export const TrueNASSystemsTable: Component<{
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums lg:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content lg:table-cell`}
|
||||
>
|
||||
{c().shares}
|
||||
<PlatformTableNumberValue value={c().shares} />
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden whitespace-normal text-base-content lg:table-cell`}
|
||||
@@ -338,9 +339,9 @@ export const TrueNASSystemsTable: Component<{
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums lg:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content lg:table-cell`}
|
||||
>
|
||||
{c().services}
|
||||
<PlatformTableNumberValue value={c().services} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<PlatformResourceDetailTableRow
|
||||
|
||||
@@ -62,6 +62,36 @@ describe('Resource Type Guards', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps Kubernetes native API sections in the governed platform projection', () => {
|
||||
expect(SOURCE_PLATFORM_CANONICAL_PROJECTIONS.kubernetes).toEqual([
|
||||
'k8s-cluster',
|
||||
'k8s-node',
|
||||
'pod',
|
||||
'k8s-deployment',
|
||||
'k8s-replicaset',
|
||||
'k8s-statefulset',
|
||||
'k8s-daemonset',
|
||||
'k8s-service',
|
||||
'k8s-job',
|
||||
'k8s-cronjob',
|
||||
'k8s-ingress',
|
||||
'k8s-endpoint-slice',
|
||||
'k8s-network-policy',
|
||||
'k8s-persistent-volume',
|
||||
'k8s-persistent-volume-claim',
|
||||
'k8s-storage-class',
|
||||
'k8s-namespace',
|
||||
'k8s-configmap',
|
||||
'k8s-secret',
|
||||
'k8s-serviceaccount',
|
||||
'k8s-resource-quota',
|
||||
'k8s-limit-range',
|
||||
'k8s-pod-disruption-budget',
|
||||
'k8s-horizontal-pod-autoscaler',
|
||||
'k8s-event',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('isInfrastructure', () => {
|
||||
const infrastructureTypes: ResourceType[] = [
|
||||
'agent',
|
||||
|
||||
Reference in New Issue
Block a user