mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Fix platform UI consistency regressions
This commit is contained in:
@@ -895,7 +895,9 @@ describe('GUEST_COLUMNS', () => {
|
|||||||
expect(nameColumn?.width).toBe('200px');
|
expect(nameColumn?.width).toBe('200px');
|
||||||
expect(nameColumn?.minWidth).toBe('180px');
|
expect(nameColumn?.minWidth).toBe('180px');
|
||||||
expect(nameColumn?.maxWidth).toBe('220px');
|
expect(nameColumn?.maxWidth).toBe('220px');
|
||||||
expect(runtimeColumn?.width).toBe('84px');
|
expect(runtimeColumn?.width).toBe('104px');
|
||||||
|
expect(runtimeColumn?.minWidth).toBe('96px');
|
||||||
|
expect(runtimeColumn?.maxWidth).toBe('112px');
|
||||||
expect(typeColumn?.width).toBe('60px');
|
expect(typeColumn?.width).toBe('60px');
|
||||||
expect(netIoColumn?.width).toBe('170px');
|
expect(netIoColumn?.width).toBe('170px');
|
||||||
expect(netIoColumn?.minWidth).toBe('170px');
|
expect(netIoColumn?.minWidth).toBe('170px');
|
||||||
@@ -985,7 +987,7 @@ describe('GUEST_COLUMNS', () => {
|
|||||||
expect(
|
expect(
|
||||||
getGuestColumnWidthStyle('update', false, 'compact', compactDockerRuntimeColumnIds),
|
getGuestColumnWidthStyle('update', false, 'compact', compactDockerRuntimeColumnIds),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
width: '8.2569%',
|
width: '8.9286%',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -166,9 +166,9 @@ export const GUEST_COLUMNS: ColumnDef[] = [
|
|||||||
{
|
{
|
||||||
id: 'runtime',
|
id: 'runtime',
|
||||||
label: 'Runtime',
|
label: 'Runtime',
|
||||||
width: '84px',
|
width: '104px',
|
||||||
minWidth: '74px',
|
minWidth: '96px',
|
||||||
maxWidth: '96px',
|
maxWidth: '112px',
|
||||||
toggleable: true,
|
toggleable: true,
|
||||||
kind: 'text',
|
kind: 'text',
|
||||||
},
|
},
|
||||||
@@ -386,7 +386,7 @@ const GUEST_COLUMN_RESPONSIVE_WEIGHTS: Record<
|
|||||||
},
|
},
|
||||||
compact: {
|
compact: {
|
||||||
name: 26,
|
name: 26,
|
||||||
runtime: 8,
|
runtime: 10,
|
||||||
type: 7,
|
type: 7,
|
||||||
info: 7,
|
info: 7,
|
||||||
vmid: 7,
|
vmid: 7,
|
||||||
@@ -398,7 +398,7 @@ const GUEST_COLUMN_RESPONSIVE_WEIGHTS: Record<
|
|||||||
image: 18,
|
image: 18,
|
||||||
namespace: 11,
|
namespace: 11,
|
||||||
context: 13,
|
context: 13,
|
||||||
update: 9,
|
update: 10,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ export const DockerHostsTable: Component<{
|
|||||||
>
|
>
|
||||||
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
||||||
<TableCardHeader title={props.title ?? 'Hosts'} />
|
<TableCardHeader title={props.title ?? 'Hosts'} />
|
||||||
<Table class="min-w-full table-fixed text-xs md:min-w-[1080px]">
|
<Table class="min-w-full table-fixed text-xs md:min-w-[1240px]">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||||
{/*
|
{/*
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useWorkloadsState } from '@/components/Workloads/useWorkloadsState';
|
|||||||
import { useUnifiedResources } from '@/hooks/useUnifiedResources';
|
import { useUnifiedResources } from '@/hooks/useUnifiedResources';
|
||||||
import {
|
import {
|
||||||
PlatformErrorState,
|
PlatformErrorState,
|
||||||
|
PlatformSectionTabs,
|
||||||
PlatformTableEmptyState,
|
PlatformTableEmptyState,
|
||||||
} from '@/features/platformPage/sharedPlatformPage';
|
} from '@/features/platformPage/sharedPlatformPage';
|
||||||
import { DockerHostsTable } from './DockerHostsTable';
|
import { DockerHostsTable } from './DockerHostsTable';
|
||||||
@@ -34,6 +35,7 @@ const DOCKER_WORKLOAD_STATUS_OPTIONS: readonly WorkloadsStatusOption[] = [
|
|||||||
{ value: 'degraded', label: 'Attention' },
|
{ value: 'degraded', label: 'Attention' },
|
||||||
{ value: 'stopped', label: 'Stopped' },
|
{ value: 'stopped', label: 'Stopped' },
|
||||||
];
|
];
|
||||||
|
const DOCKER_TAB_SPECS = [{ id: 'overview', label: 'Overview', path: '/docker/overview' }] as const;
|
||||||
|
|
||||||
const dockerIcon = () => <ContainerIcon class="h-6 w-6 text-slate-400" />;
|
const dockerIcon = () => <ContainerIcon class="h-6 w-6 text-slate-400" />;
|
||||||
|
|
||||||
@@ -90,6 +92,12 @@ export function DockerPageSurface() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="docker-page" class="space-y-3">
|
<div data-testid="docker-page" class="space-y-3">
|
||||||
|
<PlatformSectionTabs
|
||||||
|
tabs={DOCKER_TAB_SPECS}
|
||||||
|
active="overview"
|
||||||
|
ariaLabel="Docker sections"
|
||||||
|
/>
|
||||||
|
|
||||||
<Show
|
<Show
|
||||||
when={!loading() || model().resources.length > 0}
|
when={!loading() || model().resources.length > 0}
|
||||||
fallback={
|
fallback={
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export const DockerServicesTable: Component<{
|
|||||||
>
|
>
|
||||||
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
||||||
<TableCardHeader title={props.title ?? 'Swarm Services'} />
|
<TableCardHeader title={props.title ?? 'Swarm Services'} />
|
||||||
<Table class="min-w-full table-fixed text-xs md:min-w-[900px]">
|
<Table class="min-w-full table-fixed text-xs md:min-w-[1080px]">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||||
{/*
|
{/*
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ vi.mock('@/components/Workloads/WorkloadsSurface', () => ({
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/features/platformPage/sharedPlatformPage', async () => {
|
||||||
|
const actual = await vi.importActual<typeof import('@/features/platformPage/sharedPlatformPage')>(
|
||||||
|
'@/features/platformPage/sharedPlatformPage',
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
PlatformSectionTabs: () => <div data-testid="docker-section-tabs" />,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const makeDockerHost = (overrides: Partial<Resource> = {}): Resource => ({
|
const makeDockerHost = (overrides: Partial<Resource> = {}): Resource => ({
|
||||||
id: 'agent:docker-01',
|
id: 'agent:docker-01',
|
||||||
name: 'docker-01',
|
name: 'docker-01',
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import {
|
|||||||
PlatformTableEmptyState,
|
PlatformTableEmptyState,
|
||||||
createPlatformTableFilterState,
|
createPlatformTableFilterState,
|
||||||
filterPlatformResources,
|
filterPlatformResources,
|
||||||
getPlatformTableCellClass,
|
getPlatformTableCellClassForKind,
|
||||||
getPlatformTableHeadClass,
|
getPlatformTableHeadClassForKind,
|
||||||
type PlatformResourceStatusFilter,
|
type PlatformResourceStatusFilter,
|
||||||
} from '@/features/platformPage/sharedPlatformPage';
|
} from '@/features/platformPage/sharedPlatformPage';
|
||||||
import {
|
import {
|
||||||
@@ -144,25 +144,33 @@ export const KubernetesClustersTable: Component<{
|
|||||||
>
|
>
|
||||||
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
||||||
<TableCardHeader title={props.title ?? 'Clusters'} />
|
<TableCardHeader title={props.title ?? 'Clusters'} />
|
||||||
<Table class="min-w-full table-fixed text-xs md:min-w-[860px]">
|
<Table class="min-w-full table-fixed text-xs md:min-w-[1040px]">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||||
<TableHead class={getPlatformTableHeadClass()}>Cluster</TableHead>
|
<TableHead class={`${getPlatformTableHeadClassForKind('name')} md:w-[17%]`}>
|
||||||
<TableHead class={`${getPlatformTableHeadClass()} hidden md:table-cell`}>
|
Cluster
|
||||||
|
</TableHead>
|
||||||
|
<TableHead class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[15%]`}>
|
||||||
Context
|
Context
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class={`${getPlatformTableHeadClass()} hidden md:table-cell`}>
|
<TableHead class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[10%]`}>
|
||||||
Version
|
Version
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class={getPlatformTableHeadClass('right')}>Nodes</TableHead>
|
<TableHead class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[8%]`}>
|
||||||
<TableHead class={`${getPlatformTableHeadClass('right')} hidden md:table-cell`}>
|
Nodes
|
||||||
|
</TableHead>
|
||||||
|
<TableHead class={`${getPlatformTableHeadClassForKind('numeric-value')} hidden md:table-cell md:w-[8%]`}>
|
||||||
Pods
|
Pods
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class={`${getPlatformTableHeadClass('right')} hidden md:table-cell`}>
|
<TableHead class={`${getPlatformTableHeadClassForKind('numeric-value')} hidden md:table-cell md:w-[12%]`}>
|
||||||
Deployments
|
Deployments
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class={getPlatformTableHeadClass('right')}>CPU</TableHead>
|
<TableHead class={`${getPlatformTableHeadClassForKind('metric-bar')} md:w-[15%]`}>
|
||||||
<TableHead class={getPlatformTableHeadClass('right')}>Memory</TableHead>
|
CPU
|
||||||
|
</TableHead>
|
||||||
|
<TableHead class={`${getPlatformTableHeadClassForKind('metric-bar')} md:w-[15%]`}>
|
||||||
|
Memory
|
||||||
|
</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody class={PLATFORM_TABLE_BODY_CLASS}>
|
<TableBody class={PLATFORM_TABLE_BODY_CLASS}>
|
||||||
@@ -194,7 +202,7 @@ export const KubernetesClustersTable: Component<{
|
|||||||
onKeyDown={drawer.handleActivationKey(cluster)}
|
onKeyDown={drawer.handleActivationKey(cluster)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<TableCell class={getPlatformTableCellClass()}>
|
<TableCell class={getPlatformTableCellClassForKind('name')}>
|
||||||
<div class="flex min-w-0 items-center gap-2">
|
<div class="flex min-w-0 items-center gap-2">
|
||||||
<StatusDot
|
<StatusDot
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -211,37 +219,37 @@ export const KubernetesClustersTable: Component<{
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
class={`${getPlatformTableCellClass()} hidden text-base-content md:table-cell`}
|
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||||
>
|
>
|
||||||
{context()}
|
{context()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
class={`${getPlatformTableCellClass()} hidden font-mono text-[11px] text-base-content md:table-cell`}
|
class={`${getPlatformTableCellClassForKind('text')} hidden font-mono text-[11px] text-base-content md:table-cell`}
|
||||||
>
|
>
|
||||||
{version()}
|
{version()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
class={`${getPlatformTableCellClass('right')} text-base-content tabular-nums`}
|
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||||
>
|
>
|
||||||
{counts().nodes}
|
{counts().nodes}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
class={`${getPlatformTableCellClass('right')} hidden text-base-content tabular-nums md:table-cell`}
|
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums md:table-cell`}
|
||||||
>
|
>
|
||||||
{counts().pods}
|
{counts().pods}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
class={`${getPlatformTableCellClass('right')} hidden text-base-content tabular-nums md:table-cell`}
|
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums md:table-cell`}
|
||||||
>
|
>
|
||||||
{counts().deployments}
|
{counts().deployments}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
class={`${getPlatformTableCellClass('right')} text-base-content`}
|
class={`${getPlatformTableCellClassForKind('metric-bar')} text-base-content`}
|
||||||
>
|
>
|
||||||
{formatPercent(cluster.cpu?.current)}
|
{formatPercent(cluster.cpu?.current)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
class={`${getPlatformTableCellClass('right')} text-base-content`}
|
class={`${getPlatformTableCellClassForKind('metric-bar')} text-base-content`}
|
||||||
>
|
>
|
||||||
{formatPercent(cluster.memory?.current)}
|
{formatPercent(cluster.memory?.current)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const KubernetesDeploymentsTable: Component<{
|
|||||||
>
|
>
|
||||||
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
||||||
<TableCardHeader title={props.title ?? 'Deployments'} />
|
<TableCardHeader title={props.title ?? 'Deployments'} />
|
||||||
<Table class="min-w-full table-fixed text-xs md:min-w-[820px]">
|
<Table class="min-w-full table-fixed text-xs md:min-w-[1080px]">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||||
{/*
|
{/*
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export const KubernetesNodesTable: Component<{
|
|||||||
>
|
>
|
||||||
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
||||||
<TableCardHeader title={props.title ?? 'Nodes'} />
|
<TableCardHeader title={props.title ?? 'Nodes'} />
|
||||||
<Table class="min-w-full table-fixed text-xs md:min-w-[1000px]">
|
<Table class="min-w-full table-fixed text-xs md:min-w-[1280px]">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||||
{/*
|
{/*
|
||||||
|
|||||||
@@ -73,8 +73,18 @@ export function PatrolIntelligenceSummary(props: { state: PatrolIntelligenceStat
|
|||||||
runtimeState === 'blocked' || runtimeState === 'disabled' || runtimeState === 'unavailable'
|
runtimeState === 'blocked' || runtimeState === 'disabled' || runtimeState === 'unavailable'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
const hasLoadedPatrolEvidence = createMemo(
|
||||||
|
() =>
|
||||||
|
summaryStats().hasAnyPatrolFindings ||
|
||||||
|
(state.patrolRunHistory.value()?.length ?? 0) > 0 ||
|
||||||
|
Boolean(state.patrolStatus()),
|
||||||
|
);
|
||||||
const showLoadingSummary = createMemo(
|
const showLoadingSummary = createMemo(
|
||||||
() => !showRuntimeSummary() && !state.intelligenceSummary() && !state.initialSurfaceReady(),
|
() =>
|
||||||
|
!showRuntimeSummary() &&
|
||||||
|
!state.intelligenceSummary() &&
|
||||||
|
!state.initialSurfaceReady() &&
|
||||||
|
!hasLoadedPatrolEvidence(),
|
||||||
);
|
);
|
||||||
const assessment = createMemo(() =>
|
const assessment = createMemo(() =>
|
||||||
getPatrolAssessmentPresentation({
|
getPatrolAssessmentPresentation({
|
||||||
|
|||||||
@@ -112,6 +112,18 @@ describe('PatrolIntelligenceSummary', () => {
|
|||||||
expect(screen.queryByTestId('patrol-recommended-next-step-action')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('patrol-recommended-next-step-action')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId('patrol-summary-details-toggle')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('patrol-summary-details-toggle')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not leave the loading shell behind once patrol evidence has rendered', () => {
|
||||||
|
const patrolState = {
|
||||||
|
...createPatrolState(),
|
||||||
|
initialSurfaceReady: () => false,
|
||||||
|
intelligenceSummary: () => null,
|
||||||
|
} satisfies PatrolIntelligenceState;
|
||||||
|
|
||||||
|
render(() => <PatrolIntelligenceSummary state={patrolState} />);
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('patrol-summary-loading')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createPatrolState(): PatrolIntelligenceState {
|
function createPatrolState(): PatrolIntelligenceState {
|
||||||
|
|||||||
+8
-10
@@ -70,7 +70,7 @@ describe('platform overview layout guardrails', () => {
|
|||||||
expect(source).toContain('PLATFORM_HEALTH_FILTER_OPTIONS');
|
expect(source).toContain('PLATFORM_HEALTH_FILTER_OPTIONS');
|
||||||
expect(source).not.toContain("from '@/components/shared/SearchInput'");
|
expect(source).not.toContain("from '@/components/shared/SearchInput'");
|
||||||
expect(source).not.toContain("from '@/components/shared/FilterButtonGroup'");
|
expect(source).not.toContain("from '@/components/shared/FilterButtonGroup'");
|
||||||
expect(source).not.toContain('createSignal');
|
expect(source).not.toContain('const [search');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ describe('platform overview layout guardrails', () => {
|
|||||||
expect(vmwarePageSurfaceSource).toContain('<VsphereHostsTable');
|
expect(vmwarePageSurfaceSource).toContain('<VsphereHostsTable');
|
||||||
expect(vmwarePageSurfaceSource).toContain('<WorkloadsSurface');
|
expect(vmwarePageSurfaceSource).toContain('<WorkloadsSurface');
|
||||||
expect(vmwarePageSurfaceSource).toContain('<StorageSurface');
|
expect(vmwarePageSurfaceSource).toContain('<StorageSurface');
|
||||||
expect(vmwarePageSurfaceSource).toContain('forcedView="pools"');
|
expect(vmwarePageSurfaceSource).not.toContain('forcedView="pools"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps secondary overview tables from rendering duplicate standalone toolbars', () => {
|
it('keeps secondary overview tables from rendering duplicate standalone toolbars', () => {
|
||||||
@@ -113,10 +113,8 @@ describe('platform overview layout guardrails', () => {
|
|||||||
|
|
||||||
it('keeps mobile host tables focused on useful operational columns', () => {
|
it('keeps mobile host tables focused on useful operational columns', () => {
|
||||||
// Assertions use the canonical kind-based helpers
|
// Assertions use the canonical kind-based helpers
|
||||||
// (getPlatformTableHeadClassForKind('<kind>')) for files that have been
|
// (getPlatformTableHeadClassForKind('<kind>')) so the platform overview
|
||||||
// migrated. KubernetesClustersTable still uses the legacy align-based
|
// tables keep aligned metric and numeric columns across providers.
|
||||||
// helper because another agent has it mid-edit; the assertions below
|
|
||||||
// match that legacy form until its migration lands.
|
|
||||||
expect(dockerHostsTableSource).toMatch(
|
expect(dockerHostsTableSource).toMatch(
|
||||||
/getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Host/,
|
/getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Host/,
|
||||||
);
|
);
|
||||||
@@ -130,11 +128,11 @@ describe('platform overview layout guardrails', () => {
|
|||||||
/getPlatformTableHeadClassForKind\('metric-bar'\)[\s\S]{0,200}?Disk/,
|
/getPlatformTableHeadClassForKind\('metric-bar'\)[\s\S]{0,200}?Disk/,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(kubernetesClustersTableSource).toContain(
|
expect(kubernetesClustersTableSource).toMatch(
|
||||||
'<TableHead class={getPlatformTableHeadClass()}>Cluster',
|
/getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Cluster/,
|
||||||
);
|
);
|
||||||
expect(kubernetesClustersTableSource).toContain(
|
expect(kubernetesClustersTableSource).toMatch(
|
||||||
"<TableHead class={getPlatformTableHeadClass('right')}>Nodes",
|
/getPlatformTableHeadClassForKind\('numeric-value'\)[\s\S]{0,200}?Nodes/,
|
||||||
);
|
);
|
||||||
expect(kubernetesNodesTableSource).toMatch(
|
expect(kubernetesNodesTableSource).toMatch(
|
||||||
/getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Node/,
|
/getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Node/,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { StatusDot } from '@/components/shared/StatusDot';
|
import { StatusDot } from '@/components/shared/StatusDot';
|
||||||
import { ResponsiveMetricCell } from '@/components/shared/responsive';
|
import { ResponsiveMetricCell } from '@/components/shared/responsive';
|
||||||
import { TableCard } from '@/components/shared/TableCard';
|
import { TableCard } from '@/components/shared/TableCard';
|
||||||
|
import { TableCardHeader } from '@/components/shared/TableCardHeader';
|
||||||
import { NodeDrawer } from '@/components/Workloads/NodeDrawer';
|
import { NodeDrawer } from '@/components/Workloads/NodeDrawer';
|
||||||
import { StackedMemoryBar } from '@/components/Workloads/StackedMemoryBar';
|
import { StackedMemoryBar } from '@/components/Workloads/StackedMemoryBar';
|
||||||
import { StackedDiskBar } from '@/components/Workloads/StackedDiskBar';
|
import { StackedDiskBar } from '@/components/Workloads/StackedDiskBar';
|
||||||
@@ -165,6 +166,7 @@ export const ProxmoxNodesTable: Component<{
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
||||||
|
<TableCardHeader title="Nodes" />
|
||||||
<Table class={`${getProxmoxHostTableMinWidthClass(layoutMode())} table-fixed text-xs`}>
|
<Table class={`${getProxmoxHostTableMinWidthClass(layoutMode())} table-fixed text-xs`}>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<For each={visibleColumns()}>
|
<For each={visibleColumns()}>
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ describe('proxmoxHostTableModel', () => {
|
|||||||
|
|
||||||
expect(ids).toEqual(['node', 'cpu', 'memory', 'disk']);
|
expect(ids).toEqual(['node', 'cpu', 'memory', 'disk']);
|
||||||
expect(getProxmoxHostTableMinWidthClass('mobile')).toBe('min-w-full');
|
expect(getProxmoxHostTableMinWidthClass('mobile')).toBe('min-w-full');
|
||||||
expect(getProxmoxHostColumnWidthStyle('node', 'mobile', ids)).toEqual({ width: '44%' });
|
expect(getProxmoxHostColumnWidthStyle('node', 'mobile', ids)).toEqual({ width: '40%' });
|
||||||
expect(getProxmoxHostColumnWidthStyle('cpu', 'mobile', ids)).toEqual({ width: '23%' });
|
expect(getProxmoxHostColumnWidthStyle('cpu', 'mobile', ids)).toEqual({ width: '20%' });
|
||||||
expect(getProxmoxHostColumnWidthStyle('memory', 'mobile', ids)).toEqual({ width: '23%' });
|
expect(getProxmoxHostColumnWidthStyle('memory', 'mobile', ids)).toEqual({ width: '20%' });
|
||||||
expect(getProxmoxHostColumnWidthStyle('disk', 'mobile', ids)).toEqual({ width: '10%' });
|
expect(getProxmoxHostColumnWidthStyle('disk', 'mobile', ids)).toEqual({ width: '20%' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds temperature and guest counts before slower-changing metadata on tablet', () => {
|
it('adds temperature and guest counts before slower-changing metadata on tablet', () => {
|
||||||
@@ -47,10 +47,10 @@ describe('proxmoxHostTableModel', () => {
|
|||||||
'cts',
|
'cts',
|
||||||
'cluster',
|
'cluster',
|
||||||
]);
|
]);
|
||||||
expect(getProxmoxHostTableMinWidthClass('compact')).toBe('min-w-[1080px]');
|
expect(getProxmoxHostTableMinWidthClass('compact')).toBe('min-w-[1240px]');
|
||||||
expect(getProxmoxHostTableMinWidthClass('wide')).toBe('min-w-[1080px]');
|
expect(getProxmoxHostTableMinWidthClass('wide')).toBe('min-w-[1240px]');
|
||||||
expect(getProxmoxHostColumnWidthStyle('cluster', 'compact', compactIds)).toEqual({
|
expect(getProxmoxHostColumnWidthStyle('cluster', 'compact', compactIds)).toEqual({
|
||||||
width: '13.0435%',
|
width: '10.989%',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ describe('proxmoxPageModel', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides Proxmox sub-tabs when the connected estate has no matching resource evidence', () => {
|
it('keeps Replication available for a PVE estate without recent replication events', () => {
|
||||||
const model = buildProxmoxPageModel([
|
const model = buildProxmoxPageModel([
|
||||||
makeResource({
|
makeResource({
|
||||||
id: 'pve-node-1',
|
id: 'pve-node-1',
|
||||||
@@ -148,7 +148,10 @@ describe('proxmoxPageModel', () => {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(buildVisibleProxmoxTabSpecs(model).map((tab) => tab.id)).toEqual(['overview']);
|
expect(buildVisibleProxmoxTabSpecs(model).map((tab) => tab.id)).toEqual([
|
||||||
|
'overview',
|
||||||
|
'replication',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves Proxmox suite scope from canonical platform hints', () => {
|
it('resolves Proxmox suite scope from canonical platform hints', () => {
|
||||||
|
|||||||
@@ -122,5 +122,5 @@ export const getProxmoxHostColumnWidthStyle = (
|
|||||||
|
|
||||||
export const getProxmoxHostTableMinWidthClass = (
|
export const getProxmoxHostTableMinWidthClass = (
|
||||||
layoutMode: WorkloadTableLayoutMode,
|
layoutMode: WorkloadTableLayoutMode,
|
||||||
): 'min-w-full' | 'min-w-[1080px]' =>
|
): 'min-w-full' | 'min-w-[1240px]' =>
|
||||||
layoutMode === 'mobile' || layoutMode === 'tablet' ? 'min-w-full' : 'min-w-[1080px]';
|
layoutMode === 'mobile' || layoutMode === 'tablet' ? 'min-w-full' : 'min-w-[1240px]';
|
||||||
|
|||||||
@@ -373,11 +373,12 @@ export function buildProxmoxPageModel(resources: Resource[]): ProxmoxPageModel {
|
|||||||
|
|
||||||
export function buildVisibleProxmoxTabSpecs(model: ProxmoxPageModel): ProxmoxTabSpec[] {
|
export function buildVisibleProxmoxTabSpecs(model: ProxmoxPageModel): ProxmoxTabSpec[] {
|
||||||
const visible = new Set<ProxmoxPageTabId>(['overview']);
|
const visible = new Set<ProxmoxPageTabId>(['overview']);
|
||||||
|
const hasPveEstate = model.pveNodes.length > 0 || model.guests.length > 0;
|
||||||
|
|
||||||
if (model.storage.length > 0 || model.physicalDisks.length > 0) {
|
if (model.storage.length > 0 || model.physicalDisks.length > 0) {
|
||||||
visible.add('storage');
|
visible.add('storage');
|
||||||
}
|
}
|
||||||
if (model.replicationChanges.length > 0) {
|
if (hasPveEstate || model.replicationChanges.length > 0) {
|
||||||
visible.add('replication');
|
visible.add('replication');
|
||||||
}
|
}
|
||||||
if (model.resources.some(hasBackupSignal) || model.pbs.length > 0) {
|
if (model.resources.some(hasBackupSignal) || model.pbs.length > 0) {
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ export const TrueNASSystemsTable: Component<{
|
|||||||
>
|
>
|
||||||
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
||||||
<TableCardHeader title={props.title ?? 'Systems'} />
|
<TableCardHeader title={props.title ?? 'Systems'} />
|
||||||
<Table class="min-w-full table-fixed text-xs md:min-w-[1040px]">
|
<Table class="min-w-full table-fixed text-xs md:min-w-[1300px]">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||||
{/*
|
{/*
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ export function VmwarePageSurface() {
|
|||||||
tableOnly
|
tableOnly
|
||||||
showFilterToolbar
|
showFilterToolbar
|
||||||
forcedSourceFilter={VMWARE_PLATFORM_FILTER}
|
forcedSourceFilter={VMWARE_PLATFORM_FILTER}
|
||||||
forcedView="pools"
|
|
||||||
filterAriaLabel="vSphere datastore filters"
|
filterAriaLabel="vSphere datastore filters"
|
||||||
filterSearchPlaceholder="Search vSphere datastores by name, host, or capacity group"
|
filterSearchPlaceholder="Search vSphere datastores by name, host, or capacity group"
|
||||||
filterSearchEmptyMessage="Recent vSphere datastore searches appear here."
|
filterSearchEmptyMessage="Recent vSphere datastore searches appear here."
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export const VsphereHostsTable: Component<{
|
|||||||
>
|
>
|
||||||
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
||||||
<TableCardHeader title={props.title ?? 'Hosts'} />
|
<TableCardHeader title={props.title ?? 'Hosts'} />
|
||||||
<Table class="min-w-full table-fixed text-xs md:min-w-[960px]">
|
<Table class="min-w-full table-fixed text-xs md:min-w-[1240px]">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||||
{/*
|
{/*
|
||||||
|
|||||||
Reference in New Issue
Block a user