mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-19 16:27:37 +00:00
Clarify infrastructure system identity badges
This commit is contained in:
parent
62d3fa3e58
commit
bb7b607ca5
24 changed files with 413 additions and 93 deletions
|
|
@ -66,11 +66,11 @@
|
|||
"assistant_read": "supported",
|
||||
"assistant_control": "supported"
|
||||
},
|
||||
"ui_label": "Containers",
|
||||
"ui_label": "Docker",
|
||||
"ui_tone": "bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-400",
|
||||
"aliases": [],
|
||||
"display_tokens": [
|
||||
"Containers",
|
||||
"Container runtime",
|
||||
"Docker"
|
||||
],
|
||||
"storage_family": "container"
|
||||
|
|
|
|||
|
|
@ -414,12 +414,14 @@ work extends shared components instead of creating new local variants.
|
|||
return.
|
||||
Frontend infrastructure feature surfaces inherit that same source/platform
|
||||
vocabulary. `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
|
||||
must label the operator-facing resource filter and table column as
|
||||
`Platform`, not `Source`, while lower-level unified-resource contracts
|
||||
preserve merged-source detail for tooltips, accessibility metadata, and
|
||||
routing. Collection methods such as Pulse Agent may appear as option or
|
||||
detail labels, but they must not become the primary top-level platform
|
||||
wording when a provider/API platform owns the resource.
|
||||
must keep the operator-facing resource filter on `Platform`, not `Source`,
|
||||
while the infrastructure table labels its primary identity column as
|
||||
`System`. Lower-level unified-resource contracts preserve merged-source
|
||||
detail for tooltips, accessibility metadata, and routing. Collection methods
|
||||
such as Pulse Agent and runtime capabilities such as Docker may appear as
|
||||
option or detail labels, but they must not become the primary top-level
|
||||
system wording when a provider/API platform or reported host OS/appliance
|
||||
identity better explains what the operator is looking at.
|
||||
6. Keep Proxmox deep-link route selection on the shared settings-navigation boundary. `frontend-modern/src/components/Settings/settingsNavigationModel.ts` and `frontend-modern/src/components/Settings/useSettingsNavigation.ts` must treat the canonical PBS and PMG Proxmox deep links as agent-selection authority even though those URLs resolve to the shared `infrastructure-operations` tab. Reloading or remounting on a PBS or PMG deep link must not silently fall back to the PVE selector state.
|
||||
7. Keep shared storage feature presenters on canonical platform truth. When reusable storage presenters under `frontend-modern/src/features/storageBackups/` classify canonical resources for the shared storage route, API-backed virtualization datastores such as VMware must stay inventory-only datastores instead of inheriting PBS-specific backup-repository or protected-target copy from older fallback branches.
|
||||
8. Keep shared source/platform vocabulary on the governed manifest boundary. `frontend-modern/src/utils/platformSupportManifest.generated.ts` must be the tracked frontend projection of `docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json`, `frontend-modern/src/utils/platformSupportManifest.ts`, `frontend-modern/src/utils/sourcePlatforms.ts`, and `frontend-modern/src/utils/sourcePlatformOptions.ts` must consume that generated projection instead of embedding divergent future-label lists, setup/onboarding path allowlists, or presentation-only guesses, and `frontend-modern/scripts/canonical-platform-audit.mjs` must fail when the generated projection drifts from the governed manifest.
|
||||
|
|
|
|||
|
|
@ -677,13 +677,16 @@ matching, and row titles on the infrastructure page must use the canonical
|
|||
local instance identity rather than governed AI-summary text, so performance
|
||||
work cannot “optimize” the table into ambiguous labels that collapse multiple
|
||||
resources into the same visible name.
|
||||
The same protected table path treats the visible platform column as
|
||||
platform-first presentation over canonical merged-source data. Sort derivation
|
||||
for that column must use the normalized infrastructure platform key, while the
|
||||
render path may keep full merged-source detail in tooltips. When a row contains
|
||||
both `agent` and a provider/API platform such as Proxmox, the table must render
|
||||
the provider platform as the compact visible badge rather than adding extra
|
||||
Agent badge width or sorting primarily by the telemetry method.
|
||||
The same protected table path treats the visible system column as
|
||||
identity-first presentation over canonical merged-source data. Sort derivation
|
||||
for that column must use the same displayed system identity as the render path,
|
||||
while the render path may keep full merged-source detail in tooltips. When a
|
||||
row contains both `agent` and a provider/API platform such as Proxmox, the table
|
||||
must render the provider platform as the compact visible badge rather than
|
||||
adding extra Agent badge width or sorting primarily by the telemetry method.
|
||||
When a row is only known through an agent or container runtime, the table must
|
||||
prefer reported OS/appliance identity before falling back to Docker/runtime
|
||||
capability labels.
|
||||
That derived workload owner now also routes grouped row windowing through
|
||||
`frontend-modern/src/components/Dashboard/useGroupedTableWindowing.ts`, which
|
||||
owns row-window thresholds, overscan behavior, reveal-index clamping, and
|
||||
|
|
|
|||
|
|
@ -420,6 +420,11 @@ this health-state projection: it must derive counts only from the resources
|
|||
accessor already available to the summary state, not from a separate API call or
|
||||
a re-projection of websocket state outside the shared summary pipeline.
|
||||
|
||||
`resourceBadgePresentation.ts` now owns the Infrastructure table system
|
||||
identity resolver. That resolver must prefer provider/API platform identity,
|
||||
then reported host OS or appliance identity, before falling back to Docker or
|
||||
other runtime capability labels.
|
||||
|
||||
This subsystem now sits under the dedicated core monitoring runtime lane so
|
||||
canonical resource identity, discovery normalization, and platform-runtime
|
||||
coverage stay governed as a first-class Pulse product surface, including the
|
||||
|
|
@ -1800,15 +1805,19 @@ sources, `frontend-modern/src/utils/sourcePlatforms.ts` must still resolve the
|
|||
platform as `truenas` and the source mode as `hybrid`, so workload and
|
||||
infrastructure consumers do not collapse API-backed TrueNAS systems or apps
|
||||
back onto the generic agent path just because host telemetry is also present.
|
||||
That same boundary also owns the infrastructure table's operator-facing
|
||||
platform vocabulary. `frontend-modern/src/utils/resourceBadgePresentation.ts`,
|
||||
That same boundary also owns the infrastructure table's operator-facing system
|
||||
identity vocabulary. `frontend-modern/src/utils/resourceBadgePresentation.ts`,
|
||||
`frontend-modern/src/components/Infrastructure/resourceBadges.ts`, and the
|
||||
unified resource table sections may preserve full merged-source detail in
|
||||
tooltips and accessibility metadata, but visible table headers, filters, sort
|
||||
keys, and row badges must present the owning infrastructure platform first.
|
||||
Agent telemetry is collection-method detail when a stronger platform source is
|
||||
present, not a peer platform label that should crowd the table or drive the
|
||||
primary platform sort.
|
||||
tooltips and accessibility metadata, but visible table headers, sort keys, and
|
||||
row badges must answer what system the operator is looking at. Provider/API
|
||||
platforms such as Proxmox, TrueNAS, VMware, and Kubernetes outrank collection
|
||||
methods; reported agent OS or appliance identity such as Unraid or Ubuntu
|
||||
outranks a generic container-runtime capability; and Docker should appear as
|
||||
the primary visible system label only when the container runtime is the best
|
||||
available identity. Agent telemetry is collection-method detail when a stronger
|
||||
platform or host identity is present, not a peer platform label that should
|
||||
crowd the table or drive the primary system sort.
|
||||
The route file `frontend-modern/src/pages/Infrastructure.tsx` is now only the
|
||||
navigation boundary for that surface; canonical infrastructure filter, search,
|
||||
deep-link, and expansion state now live behind the dedicated infrastructure
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ describe('dashboardWorkloadFilterConfigModel', () => {
|
|||
isWorkloadsRoute: true,
|
||||
selectedPlatform: 'truenas',
|
||||
platformOptions: [
|
||||
{ value: 'docker', label: 'Containers' },
|
||||
{ value: 'docker', label: 'Docker' },
|
||||
{ value: 'truenas', label: 'TrueNAS' },
|
||||
],
|
||||
onChange,
|
||||
|
|
@ -122,7 +122,7 @@ describe('dashboardWorkloadFilterConfigModel', () => {
|
|||
value: 'truenas',
|
||||
options: [
|
||||
{ value: '', label: 'All platforms' },
|
||||
{ value: 'docker', label: 'Containers' },
|
||||
{ value: 'docker', label: 'Docker' },
|
||||
{ value: 'truenas', label: 'TrueNAS' },
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -143,8 +143,8 @@ describe('dashboardWorkloadRouteModel', () => {
|
|||
'app-container',
|
||||
),
|
||||
).toEqual([
|
||||
{ value: 'docker', label: 'Containers' },
|
||||
{ value: 'truenas', label: 'TrueNAS' },
|
||||
{ value: 'docker', label: 'Docker' },
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ import { StackedDiskBar } from '@/components/Dashboard/StackedDiskBar';
|
|||
import { StackedMemoryBar } from '@/components/Dashboard/StackedMemoryBar';
|
||||
import { buildMetricKeyForUnifiedResource } from '@/utils/metricsKeys';
|
||||
import {
|
||||
getInfrastructurePlatformBadges,
|
||||
dedupeResourceBadges,
|
||||
getInfrastructureSystemIdentityBadges,
|
||||
getPlatformBadge,
|
||||
getSourceBadge,
|
||||
getUnifiedSourceBadges,
|
||||
|
|
@ -304,8 +305,11 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
|||
const sourceBadge = createMemo(() => getSourceBadge(resource.sourceType));
|
||||
const unifiedSources = createMemo(() => table.getUnifiedSources(resource));
|
||||
const sourceBadges = createMemo(() => getUnifiedSourceBadges(unifiedSources()));
|
||||
const platformBadges = createMemo(() =>
|
||||
getInfrastructurePlatformBadges(unifiedSources()),
|
||||
const systemBadges = createMemo(() =>
|
||||
getInfrastructureSystemIdentityBadges(resource),
|
||||
);
|
||||
const systemTitleBadges = createMemo(() =>
|
||||
dedupeResourceBadges([...systemBadges(), ...sourceBadges()]),
|
||||
);
|
||||
const policyBadges = createMemo(() =>
|
||||
getResourcePolicyTableBadges(resource.policy),
|
||||
|
|
@ -589,10 +593,10 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
|||
classList={{ hidden: table.isMobile() || !table.isVisible('secondary') }}
|
||||
>
|
||||
<UnifiedResourceSourceBadgeCell
|
||||
unifiedBadges={platformBadges()}
|
||||
unifiedBadges={systemBadges()}
|
||||
platformBadge={platformBadge()}
|
||||
sourceBadge={sourceBadge()}
|
||||
titleBadges={sourceBadges()}
|
||||
titleBadges={systemTitleBadges()}
|
||||
layoutMode={table.layoutMode()}
|
||||
/>
|
||||
</TableCell>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import {
|
|||
TableRow,
|
||||
} from '@/components/shared/Table';
|
||||
import {
|
||||
getInfrastructurePlatformBadges,
|
||||
dedupeResourceBadges,
|
||||
getInfrastructureSystemIdentityBadges,
|
||||
getPlatformBadge,
|
||||
getSourceBadge,
|
||||
getUnifiedSourceBadges,
|
||||
|
|
@ -121,8 +122,11 @@ export const UnifiedResourcePBSTableSection: Component<UnifiedResourcePBSTableSe
|
|||
const sourceBadge = createMemo(() => getSourceBadge(resource.sourceType));
|
||||
const unifiedSources = createMemo(() => table.getUnifiedSources(resource));
|
||||
const sourceBadges = createMemo(() => getUnifiedSourceBadges(unifiedSources()));
|
||||
const platformBadges = createMemo(() =>
|
||||
getInfrastructurePlatformBadges(unifiedSources()),
|
||||
const systemBadges = createMemo(() =>
|
||||
getInfrastructureSystemIdentityBadges(resource),
|
||||
);
|
||||
const systemTitleBadges = createMemo(() =>
|
||||
dedupeResourceBadges([...systemBadges(), ...sourceBadges()]),
|
||||
);
|
||||
const healthClass = createMemo(
|
||||
() =>
|
||||
|
|
@ -256,10 +260,10 @@ export const UnifiedResourcePBSTableSection: Component<UnifiedResourcePBSTableSe
|
|||
|
||||
<TableCell classList={{ hidden: !table.isServiceVisible('secondary') }}>
|
||||
<UnifiedResourceSourceBadgeCell
|
||||
unifiedBadges={platformBadges()}
|
||||
unifiedBadges={systemBadges()}
|
||||
platformBadge={platformBadge()}
|
||||
sourceBadge={sourceBadge()}
|
||||
titleBadges={sourceBadges()}
|
||||
titleBadges={systemTitleBadges()}
|
||||
layoutMode={table.layoutMode()}
|
||||
/>
|
||||
</TableCell>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import {
|
|||
TableRow,
|
||||
} from '@/components/shared/Table';
|
||||
import {
|
||||
getInfrastructurePlatformBadges,
|
||||
dedupeResourceBadges,
|
||||
getInfrastructureSystemIdentityBadges,
|
||||
getPlatformBadge,
|
||||
getSourceBadge,
|
||||
getUnifiedSourceBadges,
|
||||
|
|
@ -135,8 +136,11 @@ export const UnifiedResourcePMGTableSection: Component<UnifiedResourcePMGTableSe
|
|||
const sourceBadge = createMemo(() => getSourceBadge(resource.sourceType));
|
||||
const unifiedSources = createMemo(() => table.getUnifiedSources(resource));
|
||||
const sourceBadges = createMemo(() => getUnifiedSourceBadges(unifiedSources()));
|
||||
const platformBadges = createMemo(() =>
|
||||
getInfrastructurePlatformBadges(unifiedSources()),
|
||||
const systemBadges = createMemo(() =>
|
||||
getInfrastructureSystemIdentityBadges(resource),
|
||||
);
|
||||
const systemTitleBadges = createMemo(() =>
|
||||
dedupeResourceBadges([...systemBadges(), ...sourceBadges()]),
|
||||
);
|
||||
const healthClass = createMemo(
|
||||
() =>
|
||||
|
|
@ -286,10 +290,10 @@ export const UnifiedResourcePMGTableSection: Component<UnifiedResourcePMGTableSe
|
|||
|
||||
<TableCell classList={{ hidden: !table.isServiceVisible('secondary') }}>
|
||||
<UnifiedResourceSourceBadgeCell
|
||||
unifiedBadges={platformBadges()}
|
||||
unifiedBadges={systemBadges()}
|
||||
platformBadge={platformBadge()}
|
||||
sourceBadge={sourceBadge()}
|
||||
titleBadges={sourceBadges()}
|
||||
titleBadges={systemTitleBadges()}
|
||||
layoutMode={table.layoutMode()}
|
||||
/>
|
||||
</TableCell>
|
||||
|
|
|
|||
|
|
@ -14,14 +14,6 @@ interface UnifiedResourceSourceBadgeCellProps {
|
|||
const getVisibleSourceBadgeLimit = (layoutMode: UnifiedResourceTableLayoutMode): number =>
|
||||
layoutMode === 'wide' ? 3 : 2;
|
||||
|
||||
const compactSourceBadgeLabel = (label: string): string =>
|
||||
label === 'Containers' ? 'Cont' : label;
|
||||
|
||||
const getSourceBadgeDisplayLabel = (
|
||||
label: string,
|
||||
layoutMode: UnifiedResourceTableLayoutMode,
|
||||
): string => (layoutMode === 'wide' ? label : compactSourceBadgeLabel(label));
|
||||
|
||||
export const UnifiedResourceSourceBadgeCell: Component<UnifiedResourceSourceBadgeCellProps> = (
|
||||
props,
|
||||
) => {
|
||||
|
|
@ -40,9 +32,7 @@ export const UnifiedResourceSourceBadgeCell: Component<UnifiedResourceSourceBadg
|
|||
const hiddenBadges = createMemo(() => badges().slice(visibleBadges().length));
|
||||
const hiddenBadgeCount = createMemo(() => Math.max(0, badges().length - visibleBadges().length));
|
||||
const hiddenBadgeLabel = createMemo(() =>
|
||||
hiddenBadgeCount() === 1
|
||||
? `+${compactSourceBadgeLabel(hiddenBadges()[0]?.label ?? '')}`
|
||||
: `+${hiddenBadgeCount()}`,
|
||||
hiddenBadgeCount() === 1 ? `+${hiddenBadges()[0]?.label ?? ''}` : `+${hiddenBadgeCount()}`,
|
||||
);
|
||||
const title = createMemo(() =>
|
||||
titleBadges()
|
||||
|
|
@ -53,7 +43,7 @@ export const UnifiedResourceSourceBadgeCell: Component<UnifiedResourceSourceBadg
|
|||
return (
|
||||
<div
|
||||
class="flex min-w-0 max-w-full items-center justify-center gap-1 overflow-hidden"
|
||||
aria-label={title() ? `Platform: ${title()}` : undefined}
|
||||
aria-label={title() ? `System: ${title()}` : undefined}
|
||||
title={title()}
|
||||
>
|
||||
<For each={visibleBadges()}>
|
||||
|
|
@ -62,16 +52,14 @@ export const UnifiedResourceSourceBadgeCell: Component<UnifiedResourceSourceBadg
|
|||
class={`${badge.classes} min-w-0 max-w-full overflow-hidden px-1`}
|
||||
title={badge.title}
|
||||
>
|
||||
<span class="min-w-0 truncate">
|
||||
{getSourceBadgeDisplayLabel(badge.label, props.layoutMode)}
|
||||
</span>
|
||||
<span class="min-w-0 truncate">{badge.label}</span>
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
<Show when={hiddenBadgeCount() > 0}>
|
||||
<span
|
||||
class="inline-flex min-w-0 max-w-full items-center overflow-hidden rounded bg-surface-alt px-1 py-0.5 text-[10px] font-medium text-muted"
|
||||
aria-label={`Additional sources: ${hiddenBadges()
|
||||
aria-label={`Additional systems: ${hiddenBadges()
|
||||
.map((badge) => badge.title ?? badge.label)
|
||||
.join(', ')}`}
|
||||
title={title()}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ describe('ReportMergeModal', () => {
|
|||
|
||||
expect(screen.getByText('PVE')).toBeInTheDocument();
|
||||
expect(screen.getByText('Agent')).toBeInTheDocument();
|
||||
expect(screen.getByText('Containers')).toBeInTheDocument();
|
||||
expect(screen.getByText('Docker')).toBeInTheDocument();
|
||||
expect(screen.getByText('PBS')).toBeInTheDocument();
|
||||
expect(screen.getByText('PMG')).toBeInTheDocument();
|
||||
expect(screen.getByText('K8s')).toBeInTheDocument();
|
||||
|
|
@ -91,7 +91,7 @@ describe('ReportMergeModal', () => {
|
|||
render(() => <ReportMergeModal {...props} />);
|
||||
|
||||
expect(screen.getByText('PVE')).toBeInTheDocument();
|
||||
expect(screen.getByText('Containers')).toBeInTheDocument();
|
||||
expect(screen.getByText('Docker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows a notes textarea with placeholder', () => {
|
||||
|
|
|
|||
|
|
@ -184,6 +184,39 @@ describe('UnifiedResourceTable performance contract', () => {
|
|||
expect(container.querySelector('[style]')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders reported host identity instead of container runtime as the primary system badge', async () => {
|
||||
const resources = [
|
||||
makeResource(1, {
|
||||
type: 'docker-host',
|
||||
displayName: 'tower',
|
||||
platformType: 'docker',
|
||||
sourceType: 'hybrid',
|
||||
platformData: {
|
||||
sources: ['agent', 'docker'],
|
||||
agent: {
|
||||
platform: 'unraid',
|
||||
osName: 'Unraid',
|
||||
osVersion: '7.1.0',
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
const { getByText, queryByText } = render(() => (
|
||||
<UnifiedResourceTable
|
||||
resources={resources}
|
||||
expandedResourceId={null}
|
||||
onExpandedResourceChange={vi.fn()}
|
||||
groupingMode="flat"
|
||||
/>
|
||||
));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('System')).toBeInTheDocument();
|
||||
expect(getByText('Unraid')).toBeInTheDocument();
|
||||
});
|
||||
expect(queryByText('Docker')).toBeNull();
|
||||
});
|
||||
|
||||
it('adapts host columns from the measured table surface width instead of the window width', async () => {
|
||||
const resources = [
|
||||
makeResource(1, {
|
||||
|
|
@ -210,7 +243,7 @@ describe('UnifiedResourceTable performance contract', () => {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Net').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Plat').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Sys').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('I/O').closest('th')).toHaveClass('hidden');
|
||||
expect(getByText('Up').closest('th')).toHaveClass('hidden');
|
||||
});
|
||||
|
|
@ -223,7 +256,7 @@ describe('UnifiedResourceTable performance contract', () => {
|
|||
await waitFor(() => {
|
||||
expect(getByText('Net').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('I/O').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Plat').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Sys').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Up').closest('th')).toHaveClass('hidden');
|
||||
});
|
||||
|
||||
|
|
@ -232,7 +265,7 @@ describe('UnifiedResourceTable performance contract', () => {
|
|||
await waitFor(() => {
|
||||
expect(getByText('Net I/O').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Disk I/O').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Platform').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('System').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Up').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Temp').closest('th')).not.toHaveClass('hidden');
|
||||
});
|
||||
|
|
@ -241,7 +274,7 @@ describe('UnifiedResourceTable performance contract', () => {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Memory')).toBeInTheDocument();
|
||||
expect(getByText('Platform').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('System').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Uptime').closest('th')).not.toHaveClass('hidden');
|
||||
});
|
||||
});
|
||||
|
|
@ -276,7 +309,7 @@ describe('UnifiedResourceTable performance contract', () => {
|
|||
await waitFor(() => {
|
||||
expect(getByText('Store').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Jobs').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Plat').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Sys').closest('th')).not.toHaveClass('hidden');
|
||||
expect(getByText('Up').closest('th')).toHaveClass('hidden');
|
||||
});
|
||||
expect(getByText('PBS')).toBeInTheDocument();
|
||||
|
|
@ -284,7 +317,7 @@ describe('UnifiedResourceTable performance contract', () => {
|
|||
expect(queryByText('PBS+Agent')).toBeNull();
|
||||
expect(queryByText('+1')).toBeNull();
|
||||
expect(
|
||||
container.querySelectorAll('[aria-label^="Platform:"] .h-1\\.5.w-1\\.5.rounded-full'),
|
||||
container.querySelectorAll('[aria-label^="System:"] .h-1\\.5.w-1\\.5.rounded-full'),
|
||||
).toHaveLength(0);
|
||||
|
||||
emitResizeObserverWidth(660);
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ describe('infrastructureSelectors', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('sorts the visible platform column by infrastructure platform, not telemetry method', () => {
|
||||
it('sorts the visible system column by displayed infrastructure identity', () => {
|
||||
const resources = [
|
||||
makeResource(1, {
|
||||
displayName: 'PVE hybrid',
|
||||
|
|
@ -237,22 +237,32 @@ describe('infrastructureSelectors', () => {
|
|||
sourceType: 'hybrid',
|
||||
}),
|
||||
makeResource(2, {
|
||||
displayName: 'Agent only',
|
||||
platformType: 'agent',
|
||||
sourceType: 'agent',
|
||||
displayName: 'Docker only',
|
||||
type: 'docker-host',
|
||||
platformType: 'docker',
|
||||
sourceType: 'api',
|
||||
platformData: { sources: ['docker'] },
|
||||
}),
|
||||
makeResource(3, {
|
||||
displayName: 'Kubernetes',
|
||||
platformType: 'kubernetes',
|
||||
displayName: 'Unraid runtime',
|
||||
type: 'docker-host',
|
||||
platformType: 'docker',
|
||||
sourceType: 'hybrid',
|
||||
platformData: {
|
||||
sources: ['agent', 'docker'],
|
||||
agent: {
|
||||
platform: 'unraid',
|
||||
osName: 'Unraid',
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const sorted = sortResources(resources, 'source', 'asc');
|
||||
expect(sorted.map((resource) => resource.id)).toEqual([
|
||||
'resource-2',
|
||||
'resource-3',
|
||||
'resource-1',
|
||||
'resource-3',
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ describe('getUnifiedSourceBadges', () => {
|
|||
const badges = getUnifiedSourceBadges(['proxmox-pve', 'docker']);
|
||||
expect(badges).toHaveLength(2);
|
||||
expect(badges[0].label).toBe('PVE');
|
||||
expect(badges[1].label).toBe('Containers');
|
||||
expect(badges[1].label).toBe('Docker');
|
||||
});
|
||||
|
||||
it('includes TrueNAS source badge', () => {
|
||||
|
|
|
|||
|
|
@ -159,11 +159,11 @@ describe('unifiedResourceTableStateModel', () => {
|
|||
expect(wideColumns.ioColumn.width).toBe('12.5%');
|
||||
expect(wideColumns.serviceActionColumn.width).toBe('16%');
|
||||
expect(getUnifiedResourceTableHeaderLabels('wide').memory).toBe('Memory');
|
||||
expect(getUnifiedResourceTableHeaderLabels('wide').source).toBe('Platform');
|
||||
expect(getUnifiedResourceTableHeaderLabels('wide').source).toBe('System');
|
||||
expect(getUnifiedResourceTableHeaderLabels('compact').memory).toBe('Mem');
|
||||
expect(getUnifiedResourceTableHeaderLabels('compact').source).toBe('Platform');
|
||||
expect(getUnifiedResourceTableHeaderLabels('compact').source).toBe('System');
|
||||
expect(getUnifiedResourceTableHeaderLabels('tablet').network).toBe('Net');
|
||||
expect(getUnifiedResourceTableHeaderLabels('tablet').source).toBe('Plat');
|
||||
expect(getUnifiedResourceTableHeaderLabels('tablet').source).toBe('Sys');
|
||||
expect(getUnifiedResourceTableHeaderLabels('mobile').datastores).toBe('Store');
|
||||
expect(shouldShowUnifiedResourceHostTable(0, 0)).toBe(true);
|
||||
expect(shouldShowUnifiedResourceHostTable(0, 2)).toBe(false);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
getPreferredInfrastructureDisplayName,
|
||||
getPreferredResourceDisplayName,
|
||||
} from '@/utils/resourceIdentity';
|
||||
import { getInfrastructureSystemIdentitySortLabel } from '@/utils/resourceBadgePresentation';
|
||||
import { normalizeSourcePlatformKey, type KnownSourcePlatform } from '@/utils/sourcePlatforms';
|
||||
import { getCanonicalStatusLabel, STATUS_SORT_ORDER } from '@/utils/status';
|
||||
import type { SummarySeriesGroupScope } from '@/components/shared/summaryCardInteraction';
|
||||
|
|
@ -89,7 +90,7 @@ const getSortValue = (resource: Resource, key: string): number | string | null =
|
|||
case 'diskio':
|
||||
return resource.diskIO ? resource.diskIO.readRate + resource.diskIO.writeRate : null;
|
||||
case 'source':
|
||||
return resource.platformType ?? '';
|
||||
return getInfrastructureSystemIdentitySortLabel(resource);
|
||||
case 'temp':
|
||||
return resource.temperature ?? null;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
export type { ResourceBadge } from '@/utils/resourceBadgePresentation';
|
||||
export {
|
||||
getContainerRuntimeBadge,
|
||||
getInfrastructureSystemIdentityBadges,
|
||||
getInfrastructureSystemIdentitySortLabel,
|
||||
getInfrastructurePlatformBadges,
|
||||
getPlatformBadge,
|
||||
getSourceBadge,
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export const buildSourceSections = (
|
|||
return [
|
||||
{ id: 'proxmox', label: 'Proxmox', payload: platformData.proxmox },
|
||||
{ id: 'agent', label: 'Agent', payload: platformData.agent },
|
||||
{ id: 'docker', label: 'Containers', payload: platformData.docker },
|
||||
{ id: 'docker', label: 'Docker', payload: platformData.docker },
|
||||
{ id: 'pbs', label: 'PBS', payload: platformData.pbs },
|
||||
{ id: 'pmg', label: 'PMG', payload: platformData.pmg },
|
||||
{ id: 'kubernetes', label: 'Kubernetes', payload: platformData.kubernetes },
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ export const getUnifiedResourceTableHeaderLabels = (
|
|||
disk: 'Disk',
|
||||
network: 'Net I/O',
|
||||
diskIo: 'Disk I/O',
|
||||
source: 'Platform',
|
||||
source: 'System',
|
||||
uptime: 'Uptime',
|
||||
temp: 'Temp',
|
||||
datastores: 'Datastores',
|
||||
|
|
@ -309,7 +309,7 @@ export const getUnifiedResourceTableHeaderLabels = (
|
|||
disk: 'Disk',
|
||||
network: 'Net I/O',
|
||||
diskIo: 'Disk I/O',
|
||||
source: 'Platform',
|
||||
source: 'System',
|
||||
uptime: 'Up',
|
||||
temp: 'Temp',
|
||||
datastores: 'Stores',
|
||||
|
|
@ -330,7 +330,7 @@ export const getUnifiedResourceTableHeaderLabels = (
|
|||
disk: 'Disk',
|
||||
network: 'Net',
|
||||
diskIo: 'I/O',
|
||||
source: 'Plat',
|
||||
source: 'Sys',
|
||||
uptime: 'Up',
|
||||
temp: 'C',
|
||||
datastores: 'Store',
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ describe('sourcePlatformBadges', () => {
|
|||
expect(result?.label).toBe('PMG');
|
||||
});
|
||||
|
||||
it('returns Containers badge for docker', () => {
|
||||
it('returns Docker badge for docker', () => {
|
||||
const result = getSourcePlatformBadge('docker');
|
||||
expect(result?.label).toBe('Containers');
|
||||
expect(result?.label).toBe('Docker');
|
||||
});
|
||||
|
||||
it('returns K8s badge for kubernetes', () => {
|
||||
|
|
@ -108,9 +108,9 @@ describe('sourcePlatformBadges', () => {
|
|||
});
|
||||
|
||||
it('is case-insensitive', () => {
|
||||
expect(getSourcePlatformBadge('DOCKER')?.label).toBe('Containers');
|
||||
expect(getSourcePlatformBadge('DOCKER')?.label).toBe('Docker');
|
||||
expect(getSourcePlatformBadge('Kubernetes')?.label).toBe('K8s');
|
||||
expect(getSourcePlatformBadge('Docker')?.label).toBe('Containers');
|
||||
expect(getSourcePlatformBadge('Docker')?.label).toBe('Docker');
|
||||
});
|
||||
|
||||
it('returns unknown platform badge for unrecognized platforms', () => {
|
||||
|
|
@ -121,7 +121,7 @@ describe('sourcePlatformBadges', () => {
|
|||
|
||||
it('handles whitespace around platform names', () => {
|
||||
const result = getSourcePlatformBadge(' docker ');
|
||||
expect(result?.label).toBe('Containers');
|
||||
expect(result?.label).toBe('Docker');
|
||||
});
|
||||
|
||||
it('handles platform names with underscores', () => {
|
||||
|
|
|
|||
|
|
@ -2,16 +2,33 @@ import { describe, expect, it } from 'vitest';
|
|||
import {
|
||||
dedupeResourceBadges,
|
||||
getInfrastructurePlatformBadges,
|
||||
getInfrastructureSystemIdentityBadges,
|
||||
getInfrastructureSystemIdentitySortLabel,
|
||||
getPlatformBadge,
|
||||
getSourceBadge,
|
||||
getTypeBadge,
|
||||
getUnifiedSourceBadges,
|
||||
} from '@/utils/resourceBadgePresentation';
|
||||
import type { Resource } from '@/types/resource';
|
||||
|
||||
const makeResource = (overrides: Partial<Resource>): Resource => ({
|
||||
id: 'resource-1',
|
||||
type: 'agent',
|
||||
name: 'host-1',
|
||||
displayName: 'host-1',
|
||||
platformId: 'host-1',
|
||||
platformType: 'agent',
|
||||
sourceType: 'agent',
|
||||
status: 'online',
|
||||
lastSeen: 1,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('resourceBadgePresentation', () => {
|
||||
it('returns canonical platform badges via shared platform presentation', () => {
|
||||
expect(getPlatformBadge('proxmox-pve')?.label).toBe('PVE');
|
||||
expect(getPlatformBadge('proxmox-pbs')?.label).toBe('PBS');
|
||||
expect(getPlatformBadge('docker')?.label).toBe('Docker');
|
||||
});
|
||||
|
||||
it('returns source badges for infrastructure source types', () => {
|
||||
|
|
@ -42,6 +59,87 @@ describe('resourceBadgePresentation', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('shows explicit host identity before container runtime capability', () => {
|
||||
expect(
|
||||
getInfrastructureSystemIdentityBadges(
|
||||
makeResource({
|
||||
type: 'docker-host',
|
||||
platformType: 'docker',
|
||||
sourceType: 'hybrid',
|
||||
platformData: {
|
||||
sources: ['agent', 'docker'],
|
||||
agent: {
|
||||
platform: 'unraid',
|
||||
osName: 'Unraid',
|
||||
osVersion: '7.1.0',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).map((badge) => badge.label),
|
||||
).toEqual(['Unraid']);
|
||||
|
||||
expect(
|
||||
getInfrastructureSystemIdentityBadges(
|
||||
makeResource({
|
||||
type: 'docker-host',
|
||||
platformType: 'docker',
|
||||
sourceType: 'api',
|
||||
platformData: { sources: ['docker'] },
|
||||
}),
|
||||
).map((badge) => badge.label),
|
||||
).toEqual(['Docker']);
|
||||
});
|
||||
|
||||
it('keeps API-backed platform identity ahead of reported host OS', () => {
|
||||
const resource = makeResource({
|
||||
platformType: 'proxmox-pve',
|
||||
sourceType: 'hybrid',
|
||||
platformData: {
|
||||
sources: ['agent', 'proxmox-pve'],
|
||||
agent: {
|
||||
platform: 'debian',
|
||||
osName: 'Debian GNU/Linux',
|
||||
osVersion: '12',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(getInfrastructureSystemIdentityBadges(resource).map((badge) => badge.label)).toEqual([
|
||||
'PVE',
|
||||
]);
|
||||
expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('PVE');
|
||||
});
|
||||
|
||||
it('falls back to reported OS identity for agent-only systems', () => {
|
||||
expect(
|
||||
getInfrastructureSystemIdentityBadges(
|
||||
makeResource({
|
||||
platformData: {
|
||||
sources: ['agent'],
|
||||
agent: {
|
||||
platform: 'linux',
|
||||
osName: 'Ubuntu 24.04.2 LTS',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).map((badge) => badge.label),
|
||||
).toEqual(['Ubuntu']);
|
||||
|
||||
expect(
|
||||
getInfrastructureSystemIdentityBadges(
|
||||
makeResource({
|
||||
platformData: {
|
||||
sources: ['agent'],
|
||||
agent: {
|
||||
platform: 'qnap',
|
||||
osName: 'QNAP QTS',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).map((badge) => badge.label),
|
||||
).toEqual(['QNAP']);
|
||||
});
|
||||
|
||||
it('deduplicates repeated header badge labels', () => {
|
||||
const badges = dedupeResourceBadges([
|
||||
getTypeBadge('agent'),
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ describe('sourcePlatforms', () => {
|
|||
|
||||
describe('getSourcePlatformLabel', () => {
|
||||
it('returns label for known platforms', () => {
|
||||
expect(getSourcePlatformLabel('docker')).toBe('Containers');
|
||||
expect(getSourcePlatformLabel('docker')).toBe('Docker');
|
||||
expect(getSourcePlatformLabel('kubernetes')).toBe('K8s');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// This file is generated by scripts/release_control/generate_platform_support_frontend_module.py.
|
||||
// Do not edit by hand.
|
||||
// Source: docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json
|
||||
// Source SHA256: 31ea4c3a28507c3d60b40d747b93aadbbf7c5b4b51b546e9e803fb34eb519acd
|
||||
// Source SHA256: 27bdbf4c2820a60a74c991e28c9f5876062b53811ddbd420f834dbbc757dd715
|
||||
|
||||
export const PLATFORM_SUPPORT_MANIFEST_SOURCE = {
|
||||
path: 'docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json',
|
||||
sha256: '31ea4c3a28507c3d60b40d747b93aadbbf7c5b4b51b546e9e803fb34eb519acd',
|
||||
sha256: '27bdbf4c2820a60a74c991e28c9f5876062b53811ddbd420f834dbbc757dd715',
|
||||
} as const;
|
||||
export const PLATFORM_SUPPORT_MANIFEST = {
|
||||
schemaVersion: 1,
|
||||
|
|
@ -61,10 +61,10 @@ export const PLATFORM_SUPPORT_MANIFEST = {
|
|||
assistantRead: 'supported',
|
||||
assistantControl: 'supported',
|
||||
},
|
||||
uiLabel: 'Containers',
|
||||
uiLabel: 'Docker',
|
||||
uiTone: 'bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-400',
|
||||
aliases: [],
|
||||
displayTokens: ['Containers', 'Docker'],
|
||||
displayTokens: ['Container runtime', 'Docker'],
|
||||
storageFamily: 'container',
|
||||
},
|
||||
{
|
||||
|
|
@ -446,8 +446,8 @@ export const SOURCE_PLATFORM_AUDIT_TOKENS = [
|
|||
] as const;
|
||||
export const SOURCE_PLATFORM_DISPLAY_TOKENS = [
|
||||
'Agent',
|
||||
'Containers',
|
||||
'Docker',
|
||||
'Container runtime',
|
||||
'K8s',
|
||||
'Kubernetes',
|
||||
'PVE',
|
||||
|
|
@ -699,7 +699,7 @@ export const SOURCE_PLATFORM_PRESENTATION = {
|
|||
tone: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-400',
|
||||
},
|
||||
docker: {
|
||||
label: 'Containers',
|
||||
label: 'Docker',
|
||||
tone: 'bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-400',
|
||||
},
|
||||
kubernetes: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { PlatformType, SourceType, ResourceType } from '@/types/resource';
|
||||
import type { PlatformType, SourceType, Resource, ResourceType } from '@/types/resource';
|
||||
import { getSourcePlatformBadge } from '@/components/shared/sourcePlatformBadges';
|
||||
import { getPlatformAgentRecord, getPlatformDataRecord } from '@/utils/agentResources';
|
||||
import { normalizeSourcePlatformKey, type KnownSourcePlatform } from '@/utils/sourcePlatforms';
|
||||
import { getSourceTypePresentation } from '@/utils/sourceTypePresentation';
|
||||
import {
|
||||
|
|
@ -18,6 +19,58 @@ const baseBadge =
|
|||
|
||||
const typeClasses = 'bg-surface-alt text-base-content';
|
||||
|
||||
const PRIMARY_SYSTEM_SOURCE_PRIORITY: KnownSourcePlatform[] = [
|
||||
'proxmox-pve',
|
||||
'proxmox-pbs',
|
||||
'proxmox-pmg',
|
||||
'truenas',
|
||||
'vmware-vsphere',
|
||||
'unraid',
|
||||
'synology-dsm',
|
||||
'microsoft-hyperv',
|
||||
'kubernetes',
|
||||
];
|
||||
|
||||
const knownHostIdentityPlatformPatterns: Array<{
|
||||
pattern: RegExp;
|
||||
source: KnownSourcePlatform;
|
||||
}> = [
|
||||
{ pattern: /\btrue\s*nas\b|\btruenas\b/i, source: 'truenas' },
|
||||
{ pattern: /\bunraid\b/i, source: 'unraid' },
|
||||
{ pattern: /\bsynology\b|\bdiskstation\b|\bdsm\b/i, source: 'synology-dsm' },
|
||||
{ pattern: /\bhyper-?v\b/i, source: 'microsoft-hyperv' },
|
||||
];
|
||||
|
||||
const hostOsLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
||||
{ pattern: /\bqnap\b|\bqts\b|\bquts\b/i, label: 'QNAP' },
|
||||
{ pattern: /\bubuntu\b/i, label: 'Ubuntu' },
|
||||
{ pattern: /\bdebian\b/i, label: 'Debian' },
|
||||
{ pattern: /\bproxmox\b/i, label: 'Proxmox' },
|
||||
{ pattern: /\bfedora\b/i, label: 'Fedora' },
|
||||
{ pattern: /\brocky\b/i, label: 'Rocky' },
|
||||
{ pattern: /\balma\s*linux\b|\balmalinux\b/i, label: 'AlmaLinux' },
|
||||
{ pattern: /\bcentos\b/i, label: 'CentOS' },
|
||||
{ pattern: /\bred\s*hat\b|\brhel\b/i, label: 'RHEL' },
|
||||
{ pattern: /\barch\b/i, label: 'Arch' },
|
||||
{ pattern: /\balpine\b/i, label: 'Alpine' },
|
||||
{ pattern: /\bopen\s*suse\b|\bopensuse\b/i, label: 'openSUSE' },
|
||||
{ pattern: /\bsuse\b/i, label: 'SUSE' },
|
||||
{ pattern: /\bfreebsd\b/i, label: 'FreeBSD' },
|
||||
{ pattern: /\bwindows\b/i, label: 'Windows' },
|
||||
{ pattern: /\bmac\s*os\b|\bmacos\b|\bdarwin\b/i, label: 'macOS' },
|
||||
{ pattern: /\blinux\b/i, label: 'Linux' },
|
||||
];
|
||||
|
||||
const trimString = (value: unknown): string => (typeof value === 'string' ? value.trim() : '');
|
||||
|
||||
const titleFromParts = (...parts: Array<string | undefined>): string | undefined => {
|
||||
const title = parts
|
||||
.map((part) => (part || '').trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
return title || undefined;
|
||||
};
|
||||
|
||||
const normalizeUnifiedSourceKeys = (sources?: string[] | null): KnownSourcePlatform[] => {
|
||||
if (!sources || sources.length === 0) return [];
|
||||
const normalized = sources
|
||||
|
|
@ -83,6 +136,115 @@ export function getInfrastructurePlatformBadges(sources?: string[] | null): Reso
|
|||
return buildUnifiedSourceBadges(platformSources.length > 0 ? platformSources : normalized);
|
||||
}
|
||||
|
||||
const firstSystemSource = (
|
||||
sources: KnownSourcePlatform[],
|
||||
platformType?: PlatformType,
|
||||
): KnownSourcePlatform | null => {
|
||||
const sourceSet = new Set(sources);
|
||||
const normalizedPlatform = normalizeSourcePlatformKey(platformType);
|
||||
if (normalizedPlatform) {
|
||||
sourceSet.add(normalizedPlatform);
|
||||
}
|
||||
|
||||
return PRIMARY_SYSTEM_SOURCE_PRIORITY.find((source) => sourceSet.has(source)) ?? null;
|
||||
};
|
||||
|
||||
const getKnownHostIdentitySource = (...values: string[]): KnownSourcePlatform | null => {
|
||||
for (const value of values) {
|
||||
if (!value) continue;
|
||||
const normalized = normalizeSourcePlatformKey(value);
|
||||
if (
|
||||
normalized &&
|
||||
normalized !== 'agent' &&
|
||||
normalized !== 'docker' &&
|
||||
normalized !== 'generic'
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
const match = knownHostIdentityPlatformPatterns.find(({ pattern }) => pattern.test(value));
|
||||
if (match) return match.source;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getHostOsLabel = (...values: string[]): string | null => {
|
||||
for (const value of values) {
|
||||
if (!value) continue;
|
||||
const match = hostOsLabelPatterns.find(({ pattern }) => pattern.test(value));
|
||||
if (match) return match.label;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getAgentSystemIdentityBadge = (resource: Resource): ResourceBadge | null => {
|
||||
const agent = getPlatformAgentRecord(resource);
|
||||
if (!agent) return null;
|
||||
|
||||
const platform = trimString(agent.platform);
|
||||
const osName = trimString(agent.osName);
|
||||
const osVersion = trimString(agent.osVersion);
|
||||
const knownSource = getKnownHostIdentitySource(platform, osName);
|
||||
if (knownSource) {
|
||||
const badge = getSourcePlatformBadge(knownSource);
|
||||
if (badge) {
|
||||
return {
|
||||
label: badge.label,
|
||||
classes: badge.classes,
|
||||
title: titleFromParts(osName || badge.title, osVersion) ?? badge.title,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const osLabel = getHostOsLabel(osName, platform);
|
||||
if (osLabel) {
|
||||
return {
|
||||
label: osLabel,
|
||||
classes: `${baseBadge} ${typeClasses}`,
|
||||
title: titleFromParts(osName || osLabel, osVersion),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export function getInfrastructureSystemIdentityBadges(resource: Resource): ResourceBadge[] {
|
||||
const platformData = getPlatformDataRecord(resource) as { sources?: string[] } | undefined;
|
||||
const sources = normalizeUnifiedSourceKeys(platformData?.sources);
|
||||
const systemSource = firstSystemSource(sources, resource.platformType);
|
||||
if (systemSource) {
|
||||
return buildUnifiedSourceBadges([systemSource]);
|
||||
}
|
||||
|
||||
const agentIdentityBadge = getAgentSystemIdentityBadge(resource);
|
||||
if (agentIdentityBadge) {
|
||||
return [agentIdentityBadge];
|
||||
}
|
||||
|
||||
if (
|
||||
resource.platformType === 'docker' ||
|
||||
resource.type === 'docker-host' ||
|
||||
sources.includes('docker')
|
||||
) {
|
||||
return buildUnifiedSourceBadges(['docker']);
|
||||
}
|
||||
|
||||
const platformBadge = getPlatformBadge(resource.platformType);
|
||||
if (platformBadge) {
|
||||
return [platformBadge];
|
||||
}
|
||||
|
||||
return getInfrastructurePlatformBadges(platformData?.sources);
|
||||
}
|
||||
|
||||
export function getInfrastructureSystemIdentitySortLabel(resource: Resource): string {
|
||||
return (
|
||||
getInfrastructureSystemIdentityBadges(resource)[0]?.label ||
|
||||
getPlatformBadge(resource.platformType)?.label ||
|
||||
resource.platformType ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
export function dedupeResourceBadges(
|
||||
badges: Array<ResourceBadge | null | undefined>,
|
||||
): ResourceBadge[] {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue