mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
fix(ui): refine responsive platform tables
Contract-Neutral: responsive presentation and touch-target adjustments only; no API or payload changes
This commit is contained in:
@@ -1,46 +1,115 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "c25d393fff818067d9e75f2fed01ad5f47f41ec2",
|
||||
"verified_at": "2026-08-06T14:51:21Z",
|
||||
"base_sha": "2d9ba270651ed5e1028ae4aa15843ab34303c7c9",
|
||||
"verified_at": "2026-08-06T14:52:48Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/AppLayout.tsx",
|
||||
"frontend-modern/src/components/Brand/PulseBrandMark.tsx",
|
||||
"frontend-modern/src/components/Login.tsx",
|
||||
"frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx"
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx",
|
||||
"frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx",
|
||||
"frontend-modern/src/components/shared/buttonModel.ts",
|
||||
"frontend-modern/src/features/docker/DockerImagesTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerServicesTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerSwarmNodesTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerTasksTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesClustersTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesNetworkingTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesPodsTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesPolicyTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesServicesTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesStorageTable.tsx",
|
||||
"frontend-modern/src/features/standalone/AgentsMachinesTable.tsx",
|
||||
"frontend-modern/src/features/truenas/TrueNASStorageTopologyTable.tsx",
|
||||
"frontend-modern/src/features/vmware/VsphereNetworksTable.tsx"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/AppLayout.tsx": "5f0ee2909e3c08840af0c5b8eac78cd6c833aeb60f12250d770aea9fc319ce68",
|
||||
"frontend-modern/src/components/Brand/PulseBrandMark.tsx": "af8766f84e628b00f22cfc2c5b786a0e0914b8555f2796748e8634265d8b7f0b",
|
||||
"frontend-modern/src/components/Login.tsx": "c81e3c29a7e59f1217945bb373dced6aa28b047c6d6a7929e46681994eb9305f",
|
||||
"frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx": "f3f77aebdc6c26291a731506da5d1a40184dc485079fc60f7b5c57795557bb03"
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx": "c51b9e1530cd64dfe119adf16ec37e0ac1014be8ce52593c34735776ccaf7fbc",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx": "35e44d398281f05e48a770801ece59ef120150bc613e2f17d3bed0f30e349b34",
|
||||
"frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx": "3bfcbffa02f3e1b2512e925e3f33a9ff490b269fffb6b946e0211f31d31854e2",
|
||||
"frontend-modern/src/components/shared/buttonModel.ts": "bd672d8673cab1b467afc72b932a1b1ef62bd25eac28d7c2e40e74477da71e04",
|
||||
"frontend-modern/src/features/docker/DockerImagesTable.tsx": "dc37ef6b662b2fbbecce312894fa5e8c19d2224cf1895683e63fdaca930d6301",
|
||||
"frontend-modern/src/features/docker/DockerServicesTable.tsx": "5e793d307797c96b2496b7c5cef4807890f480fc5fb22cc1c4bc9a506f85383e",
|
||||
"frontend-modern/src/features/docker/DockerSwarmNodesTable.tsx": "bb0b23679387945dd5bab4a86231754549419d0e2350fa1a1aca7f26ee317403",
|
||||
"frontend-modern/src/features/docker/DockerTasksTable.tsx": "201200a74d8da6bcf246e5ec5de1e8df018c05833f5fae66f4f6435768a27cb4",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesClustersTable.tsx": "c50271a335b9008196ce832f5ab18f90b93996ee0516570f731ec036377e84f0",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx": "ce3bd1d75937b4d8a914e09385f71b32ef3e0ac7b898680f2d31c1aab6dd043e",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx": "237338df289a7fddbf3b6c8b889e55e8357fc2c43b8e9195ffd7ddf1c8e78f13",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesNetworkingTable.tsx": "348fd5f8c7b76de1dd971328b7622d46314f2c8c995110883ffb278aab7d26bb",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx": "9dab059b7f6375ece604b760cbf05db93b7bfedd20a1e1e0f345b80e4188c5f3",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesPodsTable.tsx": "fba4eb11395cac6aa8555807bd92f07b15b1013a1d19606b1b70eddffa143813",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesPolicyTable.tsx": "b486880d9147a3af212ba9c1b570959242900d4fd8c7b4c2c7a72d30046e5edb",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesServicesTable.tsx": "2ba5c960a276a25b34992c2c8f59b783220e84852f60b0c6069b277cd9e89efb",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesStorageTable.tsx": "f09c277ca2ee298d585207acf4e1d50fae4a7472b818233df69842a772f53a58",
|
||||
"frontend-modern/src/features/standalone/AgentsMachinesTable.tsx": "3ede12e55a4975563f9f4c7c4db7e9ffced3d4aae9ff79c9806eb443e78da58c",
|
||||
"frontend-modern/src/features/truenas/TrueNASStorageTopologyTable.tsx": "d85fc6739a235a295b483044b9250b2e59a542a875ae3c8b6b46bb507091385c",
|
||||
"frontend-modern/src/features/vmware/VsphereNetworksTable.tsx": "219fe6f2a4726b9bff7f5b8fdd51ef05b622a931743a3fecf9541de9894f2020"
|
||||
},
|
||||
"routes": [
|
||||
"/",
|
||||
"/proxmox/overview"
|
||||
"/docker/overview",
|
||||
"/docker/images",
|
||||
"/docker/storage",
|
||||
"/docker/networks",
|
||||
"/docker/swarm",
|
||||
"/kubernetes/overview",
|
||||
"/kubernetes/nodes",
|
||||
"/kubernetes/workloads",
|
||||
"/kubernetes/services",
|
||||
"/kubernetes/storage",
|
||||
"/kubernetes/configuration",
|
||||
"/kubernetes/events",
|
||||
"/truenas/overview",
|
||||
"/truenas/storage",
|
||||
"/truenas/services",
|
||||
"/truenas/apps",
|
||||
"/truenas/vms",
|
||||
"/truenas/shares",
|
||||
"/truenas/protection",
|
||||
"/vmware/overview",
|
||||
"/vmware/storage",
|
||||
"/vmware/networks",
|
||||
"/vmware/health",
|
||||
"/vmware/activity",
|
||||
"/standalone/machines",
|
||||
"/standalone/availability"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1280,
|
||||
"height": 720
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 375,
|
||||
"height": 812
|
||||
"width": 768,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 390,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 320,
|
||||
"height": 900
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Login screen brand mark in app-dark theme, computed disc #3b82f6 with ring stroke and centre fill both #dbeafe",
|
||||
"Login screen brand mark in app-light theme while the OS colour scheme was dark, computed disc #2563eb with ring stroke and centre fill both #ffffff, confirming the mark follows the app theme class rather than the OS preference",
|
||||
"Authenticated header brand lockup in app-light and app-dark themes at 1280x720, ring stroke and centre fill matching in both",
|
||||
"Authenticated header brand lockup at 375x812 mobile, mark visible at 20x20 with dark palette intact",
|
||||
"SetupWizard WelcomeStep was NOT exercised live: it renders only when the instance has no authentication configured, and the dev backend is configured. Covered by WelcomeStep.test.tsx and by the shared PulseBrandMark component verified on the other two surfaces"
|
||||
"Mock-data inventory tables across Docker, Kubernetes, TrueNAS, vSphere, and standalone Machines and Availability routes",
|
||||
"Phone layouts with operator-priority identity, health, capacity, state, and update columns while secondary context is hidden",
|
||||
"Tablet layouts with namespace, scope, role, host, mode, address, class, kind, disk, and node context restored where space permits",
|
||||
"Desktop layouts with the complete field set and stable platform-page width",
|
||||
"TrueNAS nested storage topology at multiple depths with long resource names and usage and health values",
|
||||
"Expanded machine and Docker image resource drawers with overview, history, discovery, support disclosure, and operator override sections",
|
||||
"Operator maintenance scheduler open and cancelled without saving, plus machine action menu open and closed"
|
||||
],
|
||||
"interactions": [
|
||||
"Signed in with local credentials from the login form to move from the unauthenticated to the authenticated header surface",
|
||||
"Toggled pulseThemePreference between light and dark in localStorage and reloaded, reading getComputedStyle fill and stroke off .pulse-bg, .pulse-ring and .pulse-center each time",
|
||||
"Held the OS colour scheme at dark while forcing the app theme to light to prove the inline mark tracks the app theme, the divergence an <img src=\"/logo.svg\"> cannot represent",
|
||||
"Read getAnimations() on the extracted mark and confirmed pulse-brand-logo, pulse-brand-bg, pulse-brand-ring and pulse-brand-center are all running, proving the .animate-pulse-brand descendant selectors still reach the circles after the SVG moved into a component",
|
||||
"Resized from 1280x720 to 375x812 and re-measured the header mark bounding box and computed ring stroke"
|
||||
"Navigated every listed platform route and inspected table headers, visible information priority, truncation, and document overflow at phone, tablet, and desktop widths",
|
||||
"Added responsive context progressively between 320px, 390px, 768px, and 1280px instead of scaling the application or forcing horizontal table scrolling",
|
||||
"Expanded and collapsed resource rows, switched drawer tabs, opened support and detail disclosures, and measured drawer header, select, schedule, preset, form, and history-range controls",
|
||||
"Verified changed phone controls meet a 44px touch target while returning to the compact treatment above the phone breakpoint",
|
||||
"Opened the machine row action menu at 320px and confirmed its bounds stay inside the viewport",
|
||||
"Pressed Escape on the open machine action menu and confirmed it closes and restores focus to the originating action button",
|
||||
"Closed an expanded machine detail drawer at 320px and confirmed focus returns to the exact row expand button",
|
||||
"Confirmed no horizontal document overflow on the audited pages, including expanded drawer states, then reset the viewport and finalized the browser session"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5373,7 +5373,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-8 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
"inline-flex h-8 min-h-11 min-w-11 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Drawer header Assistant and context-copy actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -5395,7 +5395,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
"inline-flex h-8 w-8 min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Drawer header close and icon actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ export const ResourceDetailDrawerSupportDisclosure: Component<
|
||||
onClick={props.onToggle}
|
||||
class={
|
||||
props.buttonClass ??
|
||||
'inline-flex shrink-0 items-center rounded-md border border-border bg-surface px-2.5 py-1 text-[10px] font-medium text-base-content transition-colors hover:bg-base'
|
||||
'inline-flex min-h-11 shrink-0 items-center rounded-md border border-border bg-surface px-2.5 py-1 text-[10px] font-medium text-base-content transition-colors hover:bg-base sm:min-h-0'
|
||||
}
|
||||
>
|
||||
{props.expanded ? props.hideLabel : props.showLabel}
|
||||
|
||||
@@ -508,7 +508,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={criticality()}
|
||||
onChange={(e) => setCriticality(e.currentTarget.value as ResourceCriticality)}
|
||||
disabled={saving()}
|
||||
class="mt-1 block w-full text-xs rounded border border-border bg-surface px-2 py-1.5 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 disabled:opacity-50"
|
||||
class="mt-1 block min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1.5 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
<option value="">Default</option>
|
||||
<option value="high">High</option>
|
||||
@@ -552,7 +552,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleOpenScheduler}
|
||||
disabled={saving()}
|
||||
class="px-2.5 py-1 text-xs font-medium text-base-content border border-border rounded hover:bg-surface-hover disabled:opacity-50"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-base-content border border-border rounded hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Schedule window
|
||||
</button>
|
||||
@@ -563,7 +563,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleOpenScheduler}
|
||||
disabled={saving()}
|
||||
class="px-2.5 py-1 text-xs font-medium text-base-content border border-border rounded hover:bg-surface-hover disabled:opacity-50"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-base-content border border-border rounded hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Edit window
|
||||
</button>
|
||||
@@ -571,7 +571,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleClearMaintenanceWindow}
|
||||
disabled={saving()}
|
||||
class="px-2.5 py-1 text-xs font-medium text-amber-700 border border-amber-200 rounded hover:bg-amber-50 dark:text-amber-300 dark:border-amber-800 dark:hover:bg-amber-900 disabled:opacity-50"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-amber-700 border border-amber-200 rounded hover:bg-amber-50 dark:text-amber-300 dark:border-amber-800 dark:hover:bg-amber-900 disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Cancel window
|
||||
</button>
|
||||
@@ -590,7 +590,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="datetime-local"
|
||||
value={scheduleStart()}
|
||||
onInput={(e) => setScheduleStart(e.currentTarget.value)}
|
||||
class="mt-0.5 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
disabled={saving()}
|
||||
/>
|
||||
</label>
|
||||
@@ -600,7 +600,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="datetime-local"
|
||||
value={scheduleEnd()}
|
||||
onInput={(e) => setScheduleEnd(e.currentTarget.value)}
|
||||
class="mt-0.5 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
disabled={saving()}
|
||||
/>
|
||||
</label>
|
||||
@@ -612,7 +612,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={() => applyPresetDuration(1)}
|
||||
disabled={saving()}
|
||||
class="px-1.5 py-0.5 rounded border border-border hover:bg-surface-hover disabled:opacity-50"
|
||||
class="min-h-11 min-w-11 px-1.5 py-0.5 rounded border border-border hover:bg-surface-hover disabled:opacity-50 sm:min-h-0 sm:min-w-0"
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
@@ -620,7 +620,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={() => applyPresetDuration(4)}
|
||||
disabled={saving()}
|
||||
class="px-1.5 py-0.5 rounded border border-border hover:bg-surface-hover disabled:opacity-50"
|
||||
class="min-h-11 min-w-11 px-1.5 py-0.5 rounded border border-border hover:bg-surface-hover disabled:opacity-50 sm:min-h-0 sm:min-w-0"
|
||||
>
|
||||
4h
|
||||
</button>
|
||||
@@ -628,7 +628,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={() => applyPresetDuration(24)}
|
||||
disabled={saving()}
|
||||
class="px-1.5 py-0.5 rounded border border-border hover:bg-surface-hover disabled:opacity-50"
|
||||
class="min-h-11 min-w-11 px-1.5 py-0.5 rounded border border-border hover:bg-surface-hover disabled:opacity-50 sm:min-h-0 sm:min-w-0"
|
||||
>
|
||||
24h
|
||||
</button>
|
||||
@@ -641,7 +641,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={scheduleReason()}
|
||||
onInput={(e) => setScheduleReason(e.currentTarget.value)}
|
||||
placeholder="e.g. Q3 storage upgrade, kernel patch reboot"
|
||||
class="mt-0.5 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
disabled={saving()}
|
||||
maxLength={200}
|
||||
/>
|
||||
@@ -656,7 +656,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={() => setSchedulerOpen(false)}
|
||||
disabled={saving()}
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded transition-colors disabled:opacity-50"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded transition-colors disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -664,7 +664,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleScheduleSave}
|
||||
disabled={saving() || Boolean(scheduleValidationError())}
|
||||
class="px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded transition-colors"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded transition-colors sm:min-h-0"
|
||||
>
|
||||
{saving() ? 'Saving…' : 'Save window'}
|
||||
</button>
|
||||
|
||||
+15
@@ -12,6 +12,11 @@ const overviewTabSource = readFileSync(
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const supportDisclosureSource = readFileSync(
|
||||
resolve(__dirname, '..', 'ResourceDetailDrawerSupportDisclosure.tsx'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
describe('ResourceOperatorStateSection', () => {
|
||||
it('exposes operator-set controls bound to the canonical API client', () => {
|
||||
// The section is the operator's window into the per-resource state
|
||||
@@ -160,6 +165,16 @@ describe('ResourceOperatorStateSection', () => {
|
||||
expect(sectionSource).toContain('maintenanceEndAt: undefined,');
|
||||
});
|
||||
|
||||
it('keeps drawer disclosure and operator controls touch-safe on phones', () => {
|
||||
expect(supportDisclosureSource).toContain('inline-flex min-h-11 shrink-0 items-center');
|
||||
expect(sectionSource).toContain('min-h-11 w-full text-xs rounded');
|
||||
expect(sectionSource).toContain('min-h-11 min-w-11 px-1.5 py-0.5');
|
||||
expect(sectionSource).toContain('min-h-11 px-2.5 py-1 text-xs font-medium text-base-content');
|
||||
expect(sectionSource).toContain(
|
||||
'min-h-11 px-2.5 py-1 text-xs font-medium text-white bg-blue-600',
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a maintenance-window-active badge when the persisted window covers now', () => {
|
||||
// The section must gate the active badge on the
|
||||
// now-falls-within-window check, not just on the presence of a
|
||||
|
||||
@@ -104,7 +104,7 @@ export const GuestDrawerHistoryRangeSelect: Component<GuestDrawerHistoryRangeSel
|
||||
label="History range"
|
||||
labelClass="sr-only"
|
||||
fieldBaseClass="contents"
|
||||
selectBaseClass={`${filterSelectClass} h-7 py-0 text-[11px]`}
|
||||
selectBaseClass={`${filterSelectClass} h-7 min-h-11 py-0 text-[11px] sm:min-h-0`}
|
||||
data-testid="guest-history-range-control"
|
||||
value={props.range}
|
||||
onChange={(event) => props.onRangeChange(event.currentTarget.value as HistoryTimeRange)}
|
||||
|
||||
+1
@@ -1425,6 +1425,7 @@ describe('Workloads performance contract', () => {
|
||||
expect(guestDrawerSource).not.toContain('Open related infrastructure');
|
||||
expect(guestDrawerHistorySource).toContain('data-testid="guest-history-hover-time"');
|
||||
expect(guestDrawerHistorySource).toContain('data-testid="guest-history-range-control"');
|
||||
expect(guestDrawerHistorySource).toContain('h-7 min-h-11 py-0 text-[11px] sm:min-h-0');
|
||||
expect(guestDrawerHistorySource).toContain('onPointerMove={handleHoverMove}');
|
||||
expect(guestDrawerHistorySource).toContain('fallbackMetrics');
|
||||
expect(guestDrawerHistorySource).toContain('props.groups ?? GUEST_DRAWER_HISTORY_GROUPS');
|
||||
|
||||
@@ -3815,7 +3815,7 @@ describe('shared primitive guardrails', () => {
|
||||
expect(drawerHeaderActionGuard?.canonical?.path).toBe('src/components/shared/Button.tsx');
|
||||
expect(drawerHeaderActionGuard?.canonical?.export).toBe('DrawerHeaderActionButton');
|
||||
expect(drawerHeaderActionGuard?.allPatterns).toEqual([
|
||||
'inline-flex h-8 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
||||
'inline-flex h-8 min-h-11 min-w-11 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
||||
]);
|
||||
expect(drawerHeaderActionGuard?.scopes).toEqual([
|
||||
'src/components',
|
||||
@@ -3829,7 +3829,7 @@ describe('shared primitive guardrails', () => {
|
||||
expect(drawerHeaderIconGuard?.canonical?.path).toBe('src/components/shared/Button.tsx');
|
||||
expect(drawerHeaderIconGuard?.canonical?.export).toBe('DrawerHeaderIconButton');
|
||||
expect(drawerHeaderIconGuard?.allPatterns).toEqual([
|
||||
'inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
||||
'inline-flex h-8 w-8 min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
||||
]);
|
||||
expect(drawerHeaderIconGuard?.scopes).toEqual(['src/components', 'src/features', 'src/pages']);
|
||||
expect(drawerHeaderIconGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
@@ -4147,10 +4147,10 @@ describe('shared primitive guardrails', () => {
|
||||
expect(drawerSource).toContain('DrawerHeaderActionButton');
|
||||
expect(drawerSource).toContain('DrawerHeaderIconButton');
|
||||
expect(drawerSource).not.toContain(
|
||||
'inline-flex h-8 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
||||
'inline-flex h-8 min-h-11 min-w-11 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
||||
);
|
||||
expect(drawerSource).not.toContain(
|
||||
'inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
||||
'inline-flex h-8 w-8 min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
||||
);
|
||||
}
|
||||
expect(chatMessagesSource).toContain('@/components/shared/Button');
|
||||
|
||||
@@ -183,10 +183,10 @@ export const getActionIconButtonClass = (options: ActionIconButtonClassOptions =
|
||||
export const DRAWER_HEADER_ACTION_GROUP_CLASS = 'flex shrink-0 items-center gap-1.5';
|
||||
|
||||
export const DRAWER_HEADER_ACTION_BUTTON_CLASS =
|
||||
'inline-flex h-8 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 disabled:cursor-wait disabled:opacity-60';
|
||||
'inline-flex h-8 min-h-11 min-w-11 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 disabled:cursor-wait disabled:opacity-60 sm:min-h-0 sm:min-w-0';
|
||||
|
||||
export const DRAWER_HEADER_ICON_BUTTON_CLASS =
|
||||
'inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500';
|
||||
'inline-flex h-8 w-8 min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 sm:min-h-0 sm:min-w-0';
|
||||
|
||||
export const getDrawerHeaderActionGroupClass = (className?: string): string =>
|
||||
[DRAWER_HEADER_ACTION_GROUP_CLASS, className].filter(Boolean).join(' ');
|
||||
|
||||
@@ -162,7 +162,7 @@ export const DockerImagesTable: Component<
|
||||
kind="numeric-value"
|
||||
sort={sort}
|
||||
sortKey="size"
|
||||
class="md:w-[12%]"
|
||||
class="hidden sm:table-cell md:w-[12%]"
|
||||
>
|
||||
Size
|
||||
</PlatformSortableTableHead>
|
||||
@@ -225,7 +225,7 @@ export const DockerImagesTable: Component<
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{dockerByteValue(resource.docker?.sizeBytes)}
|
||||
</TableCell>
|
||||
|
||||
@@ -187,12 +187,13 @@ export const DockerServicesTable: Component<{
|
||||
header={
|
||||
<>
|
||||
{/*
|
||||
Desktop widths: Service and Image take the lion's share
|
||||
Wide desktop widths: Service and Image take the lion's share
|
||||
because their content is long (registry refs, fully
|
||||
qualified service names). Mode / Desired / Running trim
|
||||
to short text and 1-2 digit counts. Update, Ports, and
|
||||
Host get middle slices for rollout state, port lists, and
|
||||
hostnames. Mobile widths are unchanged.
|
||||
hostnames. Phones keep the decision-making replica pair;
|
||||
the basic container tier restores mode, update, and host.
|
||||
*/}
|
||||
<PlatformSortableTableHead
|
||||
kind="name"
|
||||
@@ -218,14 +219,19 @@ export const DockerServicesTable: Component<{
|
||||
>
|
||||
Image
|
||||
</PlatformSortableTableHead>
|
||||
<PlatformSortableTableHead kind="text" sort={sort} sortKey="mode" class="md:w-[8%]">
|
||||
<PlatformSortableTableHead
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="mode"
|
||||
class="hidden sm:table-cell md:w-[8%]"
|
||||
>
|
||||
Mode
|
||||
</PlatformSortableTableHead>
|
||||
<PlatformSortableTableHead
|
||||
kind="numeric-value"
|
||||
sort={sort}
|
||||
sortKey="desired"
|
||||
class="hidden md:table-cell md:w-[8%]"
|
||||
class="md:w-[8%]"
|
||||
>
|
||||
Desired
|
||||
</PlatformSortableTableHead>
|
||||
@@ -241,7 +247,7 @@ export const DockerServicesTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="update"
|
||||
class="hidden md:table-cell md:w-[12%]"
|
||||
class="hidden sm:table-cell md:w-[12%]"
|
||||
>
|
||||
Update
|
||||
</PlatformSortableTableHead>
|
||||
@@ -256,7 +262,7 @@ export const DockerServicesTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="host"
|
||||
class="hidden md:table-cell md:w-[10%]"
|
||||
class="hidden sm:table-cell md:w-[10%]"
|
||||
>
|
||||
Host
|
||||
</PlatformSortableTableHead>
|
||||
@@ -303,12 +309,12 @@ export const DockerServicesTable: Component<{
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} text-base-content`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{mode()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
>
|
||||
<PlatformTableNumberValue value={service.docker?.desiredTasks ?? 0} />
|
||||
</TableCell>
|
||||
@@ -318,7 +324,7 @@ export const DockerServicesTable: Component<{
|
||||
<PlatformTableNumberValue value={service.docker?.runningTasks ?? 0} />
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
<span class="truncate inline-block max-w-[10rem]" title={update().title}>
|
||||
{update().label}
|
||||
@@ -332,7 +338,7 @@ export const DockerServicesTable: Component<{
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{host()}
|
||||
</TableCell>
|
||||
|
||||
@@ -166,7 +166,7 @@ export const DockerSwarmNodesTable: Component<DockerNativeTableProps> = (props)
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="reachability"
|
||||
class="hidden md:table-cell md:w-[14%]"
|
||||
class="md:w-[14%]"
|
||||
>
|
||||
Reachability
|
||||
</PlatformSortableTableHead>
|
||||
@@ -190,7 +190,7 @@ export const DockerSwarmNodesTable: Component<DockerNativeTableProps> = (props)
|
||||
kind="numeric-value"
|
||||
sort={sort}
|
||||
sortKey="memory"
|
||||
class="md:w-[10%]"
|
||||
class="hidden sm:table-cell md:w-[10%]"
|
||||
>
|
||||
<PlatformResponsiveTableLabel compact="Mem" full="Memory" />
|
||||
</PlatformSortableTableHead>
|
||||
@@ -198,7 +198,7 @@ export const DockerSwarmNodesTable: Component<DockerNativeTableProps> = (props)
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="address"
|
||||
class="hidden md:table-cell md:w-[10%]"
|
||||
class="hidden sm:table-cell md:w-[10%]"
|
||||
>
|
||||
Address
|
||||
</PlatformSortableTableHead>
|
||||
@@ -233,7 +233,7 @@ export const DockerSwarmNodesTable: Component<DockerNativeTableProps> = (props)
|
||||
{dockerTextValue(resource.docker?.availability)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} text-base-content`}
|
||||
>
|
||||
{managerReachability()}
|
||||
</TableCell>
|
||||
@@ -250,12 +250,12 @@ export const DockerSwarmNodesTable: Component<DockerNativeTableProps> = (props)
|
||||
{dockerCpuValue(resource.docker?.nanoCpus)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{dockerByteValue(resource.docker?.memoryBytes)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{dockerTextValue(
|
||||
resource.docker?.address || resource.docker?.managerAddress,
|
||||
|
||||
@@ -133,7 +133,7 @@ export const DockerTasksTable: Component<DockerNativeTableProps> = (props) => {
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="service"
|
||||
class="hidden md:table-cell md:w-[18%]"
|
||||
class="hidden sm:table-cell md:w-[18%]"
|
||||
>
|
||||
Service
|
||||
</PlatformSortableTableHead>
|
||||
@@ -165,7 +165,7 @@ export const DockerTasksTable: Component<DockerNativeTableProps> = (props) => {
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="node"
|
||||
class="hidden md:table-cell md:w-[16%]"
|
||||
class="hidden sm:table-cell md:w-[16%]"
|
||||
>
|
||||
Node
|
||||
</PlatformSortableTableHead>
|
||||
@@ -189,7 +189,7 @@ export const DockerTasksTable: Component<DockerNativeTableProps> = (props) => {
|
||||
indicator={mapDockerTaskStatus(resource)}
|
||||
/>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{dockerTextValue(resource.docker?.serviceName)}
|
||||
</TableCell>
|
||||
@@ -218,7 +218,7 @@ export const DockerTasksTable: Component<DockerNativeTableProps> = (props) => {
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{dockerTextValue(resource.docker?.nodeName || resource.docker?.nodeId)}
|
||||
</TableCell>
|
||||
|
||||
@@ -315,7 +315,7 @@ export const KubernetesClustersTable: Component<{
|
||||
<Show when={props.onSelectCluster} fallback={name()}>
|
||||
<button
|
||||
type="button"
|
||||
class={`truncate rounded-sm text-left font-semibold underline-offset-2 hover:text-blue-600 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 dark:hover:text-blue-400 ${
|
||||
class={`-my-3 inline-flex min-h-11 items-center truncate rounded-sm py-3 text-left font-semibold underline-offset-2 hover:text-blue-600 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 sm:my-0 sm:min-h-0 sm:py-0 dark:hover:text-blue-400 ${
|
||||
props.selectedClusterId === clusterScopeId()
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-base-content'
|
||||
|
||||
@@ -274,7 +274,7 @@ export const KubernetesConfigTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="scope"
|
||||
class="hidden md:table-cell md:w-[16%]"
|
||||
class="hidden sm:table-cell md:w-[16%]"
|
||||
>
|
||||
Scope
|
||||
</PlatformSortableTableHead>
|
||||
@@ -356,7 +356,7 @@ export const KubernetesConfigTable: Component<{
|
||||
{kind()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
<span class="inline-block max-w-[13rem] truncate" title={scope()}>
|
||||
{scope()}
|
||||
|
||||
@@ -167,7 +167,8 @@ export const KubernetesDeploymentsTable: Component<{
|
||||
the biggest shares because their content can be long.
|
||||
The integer-count columns (Desired / Updated / Ready /
|
||||
Available) trim to what their headers plus 1-2 digit
|
||||
values need. Mobile widths are unchanged.
|
||||
values need. The basic tier restores namespace context
|
||||
without crowding the phone tier.
|
||||
*/}
|
||||
<PlatformSortableTableHead
|
||||
kind="name"
|
||||
@@ -181,7 +182,7 @@ export const KubernetesDeploymentsTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="namespace"
|
||||
class="hidden md:table-cell md:w-[20%]"
|
||||
class="hidden sm:table-cell md:w-[20%]"
|
||||
>
|
||||
Namespace
|
||||
</PlatformSortableTableHead>
|
||||
@@ -279,7 +280,7 @@ export const KubernetesDeploymentsTable: Component<{
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{ns()}
|
||||
</TableCell>
|
||||
|
||||
@@ -218,7 +218,7 @@ export const KubernetesNetworkingTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="scope"
|
||||
class="hidden md:table-cell md:w-[14%]"
|
||||
class="hidden sm:table-cell md:w-[14%]"
|
||||
>
|
||||
Scope
|
||||
</PlatformSortableTableHead>
|
||||
@@ -305,7 +305,7 @@ export const KubernetesNetworkingTable: Component<{
|
||||
{networkKind(resource)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
<span class="inline-block max-w-[12rem] truncate" title={scope()}>
|
||||
{scope()}
|
||||
|
||||
@@ -210,7 +210,7 @@ export const KubernetesNodesTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="cluster"
|
||||
class="hidden md:table-cell md:w-[10%]"
|
||||
class="hidden sm:table-cell md:w-[10%]"
|
||||
>
|
||||
Cluster
|
||||
</PlatformSortableTableHead>
|
||||
@@ -218,7 +218,7 @@ export const KubernetesNodesTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="roles"
|
||||
class="hidden md:table-cell md:w-[10%]"
|
||||
class="hidden sm:table-cell md:w-[10%]"
|
||||
>
|
||||
Roles
|
||||
</PlatformSortableTableHead>
|
||||
@@ -266,7 +266,7 @@ export const KubernetesNodesTable: Component<{
|
||||
kind="numeric-value"
|
||||
sort={sort}
|
||||
sortKey="capacity"
|
||||
class="md:w-[14%]"
|
||||
class="hidden sm:table-cell md:w-[14%]"
|
||||
>
|
||||
Capacity
|
||||
</PlatformSortableTableHead>
|
||||
@@ -366,12 +366,12 @@ export const KubernetesNodesTable: Component<{
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{cluster()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{formatRoles(meta()?.roles)}
|
||||
</TableCell>
|
||||
@@ -421,7 +421,7 @@ export const KubernetesNodesTable: Component<{
|
||||
{formatPlatformTableUptimeValue(node.uptime)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} hidden text-base-content tabular-nums sm:table-cell`}
|
||||
>
|
||||
<span class="md:hidden">{compactCapacityLabel()}</span>
|
||||
<span class="hidden md:inline">{capacityLabel()}</span>
|
||||
|
||||
@@ -212,7 +212,7 @@ export const KubernetesPodsTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="scope"
|
||||
class="hidden md:table-cell md:w-[13%]"
|
||||
class="hidden sm:table-cell md:w-[13%]"
|
||||
>
|
||||
Scope
|
||||
</PlatformSortableTableHead>
|
||||
@@ -220,7 +220,7 @@ export const KubernetesPodsTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="node"
|
||||
class="hidden md:table-cell md:w-[13%]"
|
||||
class="hidden sm:table-cell md:w-[13%]"
|
||||
>
|
||||
Node
|
||||
</PlatformSortableTableHead>
|
||||
@@ -322,14 +322,14 @@ export const KubernetesPodsTable: Component<{
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
<span class="block max-w-full truncate" title={scope()}>
|
||||
{scope()}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
<span class="block max-w-full truncate" title={node()}>
|
||||
{node()}
|
||||
|
||||
@@ -246,7 +246,7 @@ export const KubernetesPolicyTable: Component<{
|
||||
Kind
|
||||
</TableHead>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[15%]`}
|
||||
class={`${getPlatformTableHeadClassForKind('text')} hidden sm:table-cell md:w-[15%]`}
|
||||
>
|
||||
Scope
|
||||
</TableHead>
|
||||
@@ -318,7 +318,7 @@ export const KubernetesPolicyTable: Component<{
|
||||
{policyKind(resource)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
<span class="inline-block max-w-[13rem] truncate" title={scope()}>
|
||||
{scope()}
|
||||
|
||||
@@ -168,7 +168,7 @@ export const KubernetesServicesTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="scope"
|
||||
class="hidden md:table-cell md:w-[15%]"
|
||||
class="hidden sm:table-cell md:w-[15%]"
|
||||
>
|
||||
Scope
|
||||
</PlatformSortableTableHead>
|
||||
@@ -262,7 +262,7 @@ export const KubernetesServicesTable: Component<{
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
<span class="inline-block max-w-[12rem] truncate" title={scope()}>
|
||||
{scope()}
|
||||
|
||||
@@ -288,7 +288,7 @@ export const KubernetesStorageTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="scope"
|
||||
class="hidden md:table-cell md:w-[13%]"
|
||||
class="hidden sm:table-cell md:w-[13%]"
|
||||
>
|
||||
Scope
|
||||
</PlatformSortableTableHead>
|
||||
@@ -304,7 +304,7 @@ export const KubernetesStorageTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="class"
|
||||
class="hidden md:table-cell md:w-[10%]"
|
||||
class="hidden sm:table-cell md:w-[10%]"
|
||||
>
|
||||
Class
|
||||
</PlatformSortableTableHead>
|
||||
@@ -382,7 +382,7 @@ export const KubernetesStorageTable: Component<{
|
||||
{kind()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
<span class="inline-block max-w-[12rem] truncate" title={scope()}>
|
||||
{scope()}
|
||||
@@ -394,7 +394,7 @@ export const KubernetesStorageTable: Component<{
|
||||
{bindingOrPhase(resource)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`}
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content sm:table-cell`}
|
||||
>
|
||||
{storageClass(resource)}
|
||||
</TableCell>
|
||||
|
||||
+82
@@ -282,6 +282,88 @@ describe('platform overview layout guardrails', () => {
|
||||
expect(dockerHostsTableSource).toContain('md:w-[10%]');
|
||||
});
|
||||
|
||||
it('prioritizes Docker phone decisions and restores context at the basic table tier', () => {
|
||||
expect(dockerImagesTableSource).toMatch(
|
||||
/sortKey="size"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[12%\]"/,
|
||||
);
|
||||
expect(dockerServicesTableSource).toMatch(
|
||||
/sortKey="mode"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[8%\]"/,
|
||||
);
|
||||
expect(dockerServicesTableSource).toMatch(/sortKey="desired"[\s\S]{0,120}?class="md:w-\[8%\]"/);
|
||||
expect(dockerServicesTableSource).toMatch(
|
||||
/sortKey="update"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[12%\]"/,
|
||||
);
|
||||
expect(dockerTasksTableSource).toMatch(
|
||||
/sortKey="service"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[18%\]"/,
|
||||
);
|
||||
expect(dockerTasksTableSource).toMatch(
|
||||
/sortKey="node"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[16%\]"/,
|
||||
);
|
||||
expect(dockerSwarmNodesTableSource).toMatch(
|
||||
/sortKey="reachability"[\s\S]{0,120}?class="md:w-\[14%\]"/,
|
||||
);
|
||||
expect(dockerSwarmNodesTableSource).toMatch(
|
||||
/sortKey="memory"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[10%\]"/,
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps Kubernetes phone identity readable and restores scope at the basic table tier', () => {
|
||||
expect(kubernetesClustersTableSource).toContain(
|
||||
'-my-3 inline-flex min-h-11 items-center truncate',
|
||||
);
|
||||
expect(kubernetesNodesTableSource).toMatch(
|
||||
/sortKey="roles"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[10%\]"/,
|
||||
);
|
||||
expect(kubernetesNodesTableSource).toMatch(
|
||||
/sortKey="capacity"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[14%\]"/,
|
||||
);
|
||||
expect(kubernetesDeploymentsTableSource).toMatch(
|
||||
/sortKey="namespace"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[20%\]"/,
|
||||
);
|
||||
expect(kubernetesPodsTableSource).toMatch(
|
||||
/sortKey="scope"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[13%\]"/,
|
||||
);
|
||||
expect(kubernetesPodsTableSource).toMatch(
|
||||
/sortKey="node"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[13%\]"/,
|
||||
);
|
||||
expect(kubernetesServicesTableSource).toMatch(
|
||||
/sortKey="scope"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[15%\]"/,
|
||||
);
|
||||
expect(kubernetesNetworkingTableSource).toMatch(
|
||||
/sortKey="scope"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[14%\]"/,
|
||||
);
|
||||
expect(kubernetesStorageTableSource).toMatch(
|
||||
/sortKey="class"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[10%\]"/,
|
||||
);
|
||||
expect(kubernetesConfigTableSource).toMatch(
|
||||
/sortKey="scope"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[16%\]"/,
|
||||
);
|
||||
expect(kubernetesPolicyTableSource).toContain(
|
||||
"getPlatformTableHeadClassForKind('text')} hidden sm:table-cell md:w-[15%]",
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps remaining platform phone identity and actions usable', () => {
|
||||
expect(truenasStorageTopologyTableSource).toMatch(
|
||||
/sortKey="kind"[\s\S]{0,120}?class="hidden sm:table-cell md:w-\[10%\]"/,
|
||||
);
|
||||
expect(truenasStorageTopologyTableSource).toContain("return 'pl-6 sm:pl-11'");
|
||||
expect(truenasStorageTopologyTableSource).toMatch(
|
||||
/sortKey="resource"[\s\S]{0,120}?class="w-\[47%\] md:w-\[32%\]"/,
|
||||
);
|
||||
expect(vsphereNetworksTableSource).toMatch(
|
||||
/sortKey="network"[\s\S]{0,120}?class="w-\[80%\] sm:w-\[45%\] md:w-\[24%\]"/,
|
||||
);
|
||||
expect(vsphereNetworksTableSource).toMatch(
|
||||
/sortKey="type"[\s\S]{0,120}?class="hidden sm:table-cell sm:w-\[35%\] md:w-\[13%\]"/,
|
||||
);
|
||||
expect(agentsMachinesTableSource).toContain('class="min-h-11 min-w-11 sm:min-h-0 sm:min-w-0"');
|
||||
expect(agentsMachinesTableSource).toContain(
|
||||
'-my-3 inline-flex min-h-11 max-w-full items-center',
|
||||
);
|
||||
expect(agentsMachinesTableSource).toContain('flex min-h-11 w-full items-center gap-2 rounded');
|
||||
});
|
||||
|
||||
it('keeps provider overview pages in the parent-table plus child-inventory stack', () => {
|
||||
for (const source of overviewSurfaceSources) {
|
||||
expect(source).toMatch(/<div[^>]*class="[^"]*\bspace-y-4\b[^"]*"/);
|
||||
|
||||
@@ -758,6 +758,7 @@ const AgentMachineActionsCell: Component<{
|
||||
label={`Machine actions for ${props.name}`}
|
||||
tone="muted"
|
||||
size="sm"
|
||||
class="min-h-11 min-w-11 sm:min-h-0 sm:min-w-0"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={props.menuOpen ? 'true' : 'false'}
|
||||
onClick={(event) => {
|
||||
@@ -790,7 +791,7 @@ const AgentMachineActionsCell: Component<{
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs font-medium text-red-600 transition-colors hover:bg-red-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500/60 disabled:cursor-not-allowed disabled:opacity-50 dark:text-red-400 dark:hover:bg-red-950"
|
||||
class="flex min-h-11 w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs font-medium text-red-600 transition-colors hover:bg-red-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500/60 disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-0 dark:text-red-400 dark:hover:bg-red-950"
|
||||
aria-label={props.confirmingRemoval ? confirmLabel() : removeLabel()}
|
||||
disabled={props.removing || !props.agentId}
|
||||
onClick={(event) => {
|
||||
@@ -1137,7 +1138,7 @@ const AgentMachineSortableHead: Component<{
|
||||
{(sortKey) => (
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex max-w-full items-center gap-1 truncate hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
class="-my-3 inline-flex min-h-11 max-w-full items-center gap-1 truncate py-3 hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60 sm:my-0 sm:min-h-0 sm:py-0"
|
||||
onClick={() => props.onSort(sortKey())}
|
||||
aria-label={`Sort by ${props.column.label}`}
|
||||
>
|
||||
|
||||
@@ -242,9 +242,9 @@ const sortTrueNASStorageTopologyRows = (
|
||||
|
||||
export const getTrueNASStorageTopologyIndentClass = (depth: number): string => {
|
||||
if (depth <= 0) return '';
|
||||
if (depth === 1) return 'pl-5 sm:pl-7';
|
||||
if (depth === 2) return 'pl-9 sm:pl-11';
|
||||
return 'pl-12 sm:pl-16';
|
||||
if (depth === 1) return 'pl-3 sm:pl-7';
|
||||
if (depth === 2) return 'pl-6 sm:pl-11';
|
||||
return 'pl-8 sm:pl-16';
|
||||
};
|
||||
|
||||
const ResourceCell: Component<{ row: TrueNASStorageTopologyRow; detailToggle?: JSX.Element }> = (
|
||||
@@ -342,7 +342,7 @@ export const TrueNASStorageTopologyTable: Component<{
|
||||
kind="name"
|
||||
sort={sort}
|
||||
sortKey="resource"
|
||||
class="md:w-[32%]"
|
||||
class="w-[47%] md:w-[32%]"
|
||||
>
|
||||
Resource
|
||||
</PlatformSortableTableHead>
|
||||
@@ -350,7 +350,7 @@ export const TrueNASStorageTopologyTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="kind"
|
||||
class="md:w-[10%]"
|
||||
class="hidden sm:table-cell md:w-[10%]"
|
||||
>
|
||||
Kind
|
||||
</PlatformSortableTableHead>
|
||||
@@ -358,7 +358,7 @@ export const TrueNASStorageTopologyTable: Component<{
|
||||
kind="metric-bar"
|
||||
sort={sort}
|
||||
sortKey="usage"
|
||||
class="md:w-[28%]"
|
||||
class="w-[28%] md:w-[28%]"
|
||||
>
|
||||
<PlatformResponsiveTableLabel compact="Usage" full="Usage / Size" />
|
||||
</PlatformSortableTableHead>
|
||||
@@ -382,7 +382,7 @@ export const TrueNASStorageTopologyTable: Component<{
|
||||
kind="badge"
|
||||
sort={sort}
|
||||
sortKey="health"
|
||||
class="md:w-[14%]"
|
||||
class="w-[25%] md:w-[14%]"
|
||||
>
|
||||
Health
|
||||
</PlatformSortableTableHead>
|
||||
@@ -422,7 +422,9 @@ export const TrueNASStorageTopologyTable: Component<{
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell class={getPlatformTableCellClassForKind('text')}>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden sm:table-cell`}
|
||||
>
|
||||
<span class="text-base-content">{kindLabel(row.kind)}</span>
|
||||
</TableCell>
|
||||
<TableCell class={getPlatformTableCellClassForKind('metric-bar')}>
|
||||
|
||||
@@ -71,23 +71,23 @@ describe('TrueNASStorageTopologyTable', () => {
|
||||
expect(
|
||||
mediaRow
|
||||
?.querySelector('[data-truenas-storage-indent-depth="1"]')
|
||||
?.classList.contains('pl-5'),
|
||||
?.classList.contains('pl-3'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
photosRow
|
||||
?.querySelector('[data-truenas-storage-indent-depth="2"]')
|
||||
?.classList.contains('pl-9'),
|
||||
?.classList.contains('pl-6'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
rawRow?.querySelector('[data-truenas-storage-indent-depth="3"]')?.classList.contains('pl-12'),
|
||||
rawRow?.querySelector('[data-truenas-storage-indent-depth="3"]')?.classList.contains('pl-8'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('caps deep indentation at the table-safe depth class', () => {
|
||||
expect(getTrueNASStorageTopologyIndentClass(0)).toBe('');
|
||||
expect(getTrueNASStorageTopologyIndentClass(1)).toBe('pl-5 sm:pl-7');
|
||||
expect(getTrueNASStorageTopologyIndentClass(2)).toBe('pl-9 sm:pl-11');
|
||||
expect(getTrueNASStorageTopologyIndentClass(3)).toBe('pl-12 sm:pl-16');
|
||||
expect(getTrueNASStorageTopologyIndentClass(8)).toBe('pl-12 sm:pl-16');
|
||||
expect(getTrueNASStorageTopologyIndentClass(1)).toBe('pl-3 sm:pl-7');
|
||||
expect(getTrueNASStorageTopologyIndentClass(2)).toBe('pl-6 sm:pl-11');
|
||||
expect(getTrueNASStorageTopologyIndentClass(3)).toBe('pl-8 sm:pl-16');
|
||||
expect(getTrueNASStorageTopologyIndentClass(8)).toBe('pl-8 sm:pl-16');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -173,7 +173,7 @@ export const VsphereNetworksTable: Component<{
|
||||
kind="name"
|
||||
sort={sort}
|
||||
sortKey="network"
|
||||
class="w-[45%] md:w-[24%]"
|
||||
class="w-[80%] sm:w-[45%] md:w-[24%]"
|
||||
>
|
||||
Network
|
||||
</PlatformSortableTableHead>
|
||||
@@ -181,7 +181,7 @@ export const VsphereNetworksTable: Component<{
|
||||
kind="text"
|
||||
sort={sort}
|
||||
sortKey="type"
|
||||
class="w-[35%] md:w-[13%]"
|
||||
class="hidden sm:table-cell sm:w-[35%] md:w-[13%]"
|
||||
>
|
||||
Type
|
||||
</PlatformSortableTableHead>
|
||||
@@ -268,7 +268,9 @@ export const VsphereNetworksTable: Component<{
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class={getPlatformTableCellClassForKind('text')}>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} hidden sm:table-cell`}
|
||||
>
|
||||
<span class="font-mono text-[11px] text-base-content">
|
||||
{networkType(network)}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user