mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Correct stale Storage embed and History range contract prose (#2718)
Three contract passages described toolbar controls that no longer exist. **storage-recovery.md (StorageSurface embed).** The contract said platform pages opt into the Storage toolbar through a `showFilterToolbar` prop on `StorageProps`, keep `tableOnly` to hide a storage summary section, and get a chart-collapse control. It also mentioned `RecoverySurface`, `forcedPlatformFilter`, and an embedded `StorageSurface` at `/truenas/storage`. On main: - `Storage.tsx` always renders `StoragePageControls`; only kiosk mode hides it. - There is no summary section and no chart-collapse control. - The Proxmox Storage tab is the only embed. It passes `forcedSourceFilter`, `resourceSource`, `suppressNodeFilter`, `filterAriaLabel` and search copy. - `/truenas/storage` renders `TrueNASStorageTopologyTable`. The rewrite describes what `Storage.tsx`, `StoragePageControls.tsx` and `useStoragePageFilters.ts` actually do: - The forced source is read as the route source and never written back to the URL. - The Source chip is auto-suppressed and does not surface the clear action. - `suppressNodeFilter` drops the Node chip and resets a selected node to all nodes. **performance-and-scalability.md and frontend-primitives.md (Workloads History range).** Both said the sparkline range shows only while Trends is active and carries a `Trend range` label. `WorkloadsFilter` actually shows it in Trends, and in Bars at desktop widths while Row hover is `History`. Its label is `History`. **Test.** A new rendered test in `Storage.test.tsx` pins the embed behaviour the rewrite claims. Five negative controls each fail it: - dropping the forced-source suppression - dropping the node reset - unlocking the route source - gating the toolbar off - keeping the Node filter The `History` label was already pinned by `WorkloadsFilter.test.tsx` and `WorkloadsSurface.performance.contract.test.tsx`. Another stale test title, "table-only embedding", is renamed. Sibling stale passages are left for a follow-up: `embedded tableOnly` in frontend-primitives.md and cloud-paid.md, the WorkloadsSurface `showFilterToolbar` text in performance-and-scalability.md, and the TrueNAS StorageSurface line in unified-resources.md.
This commit is contained in:
@@ -733,8 +733,11 @@ choices compose the shared `ViewOptionsDisclosure` instead of occupying the
|
||||
primary filter rail: layout, metric style, chart visibility, memory basis, and
|
||||
columns remain discoverable behind one `View` trigger. The history range stays
|
||||
inline in both metric modes because bars now expose an intent-driven row
|
||||
history lens and Trends keeps the same charts persistent. That inline range
|
||||
must carry a visible contextual label.
|
||||
history lens and Trends keeps the same charts persistent. In Bars the range
|
||||
renders only at desktop widths while Row hover is set to `History`, the mode
|
||||
that drives that lens; `Details` hover and the phone (`isMobile`) layout hide
|
||||
it. The inline range carries the visible `History` label that
|
||||
`WorkloadsFilter` passes to `MetricHistoryRangeSegmentedControl`.
|
||||
Controls inside the View disclosure must expand in place rather than opening
|
||||
nested absolute panels that can clip or create competing overlay stacks. The
|
||||
Proxmox page owns and persists the `Guest` / `Host` memory basis;
|
||||
|
||||
@@ -2801,10 +2801,13 @@ counts wrap independently so a narrow toolbar cannot strand View on a line by
|
||||
itself or force page-level horizontal overflow. The compact Add filter control
|
||||
joins that cluster only in the default state where no active menu chip,
|
||||
contextual Clear action, or leading action needs its own filter row; active
|
||||
scope editing keeps Add filter beside the chips. The sparkline range remains
|
||||
inline only while trends are active because it is the frequently changed
|
||||
analytical window, and its compact control must include a visible `Trend range`
|
||||
label so the values are not an orphaned set of durations. Columns must expand
|
||||
scope editing keeps Add filter beside the chips. The sparkline range is a
|
||||
trailing inline control, not a View option, because it is the frequently
|
||||
changed analytical window. `WorkloadsFilter` renders it whenever the metric
|
||||
mode is Trends, and in Bars only at desktop widths while Row hover is `History`
|
||||
(the default); Bars with `Details` hover, or Bars below the 768px `isMobile`
|
||||
breakpoint, hides it. Its compact control carries the visible `History` label
|
||||
so the values are not an orphaned set of durations. Columns must expand
|
||||
as a full-width responsive checkbox row inside the View disclosure instead of
|
||||
opening a nested absolute panel or a tall single-column desktop list. On
|
||||
Proxmox, the non-default Host basis must remain visible in the workload memory
|
||||
|
||||
@@ -2436,11 +2436,13 @@ recovery scope, or a storage/recovery-owned secret source.
|
||||
Storage and recovery UI must keep sourcing those signals from their
|
||||
existing canonical page models instead of polling the connections
|
||||
ledger for per-datastore or per-backup truth.
|
||||
Platform-first top-level pages may embed `StorageSurface` and
|
||||
`RecoverySurface` with `embedded tableOnly` and forced source or
|
||||
platform filters (e.g. `forcedSourceFilter`, `forcedPlatformFilter`)
|
||||
so platform-scoped storage and recovery rows render through the same
|
||||
canonical surfaces rather than a forked per-platform table.
|
||||
Platform-first top-level pages may embed `StorageSurface`
|
||||
(`frontend-modern/src/components/Storage/Storage.tsx`) with a forced
|
||||
source filter (`forcedSourceFilter`) so platform-scoped storage rows
|
||||
render through the same canonical surface rather than a forked
|
||||
per-platform table. The surface has no `embedded` or `tableOnly` mode
|
||||
and no `forcedPlatformFilter`, and no `RecoverySurface` component
|
||||
exists to embed alongside it.
|
||||
`frontend-modern/src/App.tsx` may carry the platform-page route
|
||||
registrations that mount those embedded canonical surfaces, but the
|
||||
routes themselves must derive their paths from the canonical builders
|
||||
@@ -2450,22 +2452,30 @@ recovery scope, or a storage/recovery-owned secret source.
|
||||
canonical surface that actually populates. The canonical TrueNAS
|
||||
adapter already emits the top-level TrueNAS system as a unified
|
||||
`agent` row tagged with the `truenas` platform, so the platform
|
||||
page defaults to `/truenas/overview` (the Systems sub-tab) and the
|
||||
embedded `StorageSurface` lives at `/truenas/storage`. The Source
|
||||
filter chip in `StoragePageControls` is also suppressed when a
|
||||
platform page locks source scope through `forcedSourceFilter` (via
|
||||
`suppressSourceFilter`, auto-applied whenever `forcedSourceFilter`
|
||||
is set), so the user never sees the platform's name pinned as a
|
||||
removable filter chip inside the embedded surface.
|
||||
Platform pages that embed `StorageSurface` reuse the canonical
|
||||
`StoragePageControls` toolbar through the `showFilterToolbar` prop on
|
||||
`StorageProps`. The page keeps `tableOnly` to hide the storage summary
|
||||
section but opts in to the shared search, status, grouping, sort,
|
||||
node, view, and chart-collapse controls so platform operators get
|
||||
dense-table storage controls on every embedded storage tab without
|
||||
forking the toolbar. The source scope flows through
|
||||
`forcedSourceFilter` as a typed page input; the source filter remains
|
||||
available in the toolbar only when not forced. The seven-state Storage
|
||||
page defaults to `/truenas/overview`, whose Overview sub-tab lists
|
||||
those systems. `/truenas/storage` renders the TrueNAS-owned
|
||||
`TrueNASStorageTopologyTable`, not `StorageSurface`; the Proxmox
|
||||
Storage tab in `frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx`
|
||||
is the only page that embeds `StorageSurface` today.
|
||||
An embedding page gets the canonical `StoragePageControls` toolbar
|
||||
without opting in: `Storage.tsx` always renders it (only kiosk mode
|
||||
hides it), there is no `showFilterToolbar` prop, and there is no storage
|
||||
summary section to hide. The Proxmox embed passes `forcedSourceFilter`,
|
||||
its own `resourceSource`, `suppressNodeFilter`, `filterAriaLabel`, and
|
||||
Proxmox search placeholder and empty-history copy. It passes no
|
||||
`forcedView`, so the Storage / Physical Disks view tabs stay.
|
||||
`forcedSourceFilter` is a typed page input that the route state reads
|
||||
as the source value and never writes back to the URL, and `Storage.tsx`
|
||||
sets `suppressSourceFilter` whenever it is present. `StoragePageControls`
|
||||
then drops the Source chip, so the user never sees the platform's name
|
||||
pinned as a removable filter chip, and the locked source does not count
|
||||
as an active filter that surfaces the toolbar's clear action.
|
||||
`suppressNodeFilter` drops the Node chip so search carries host and
|
||||
node scoping, and `Storage.tsx` resets any selected node to all nodes
|
||||
while it is set. The embedded toolbar otherwise keeps search, the
|
||||
Status filter on the Storage view (Role and Group filters on Physical
|
||||
Disks once there is more than one choice), and the View popover's
|
||||
grouping and sort; it has no chart-collapse control. The seven-state Storage
|
||||
status catalog stays in the shared Add filter menu instead of occupying a
|
||||
permanent segmented rail; a non-default status surfaces through the
|
||||
canonical FilterBar chip. Grouping and sort remain durable presentation
|
||||
|
||||
@@ -481,6 +481,65 @@ describe('Storage', () => {
|
||||
expect(screen.queryByText('Local-LVM-PVE2')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('gives a platform embed the canonical toolbar with source and node scope locked', async () => {
|
||||
mockLocationSearch = '?node=node-2&source=truenas';
|
||||
hookResources = [
|
||||
buildStorageResource('storage-1', 'Local-LVM-PVE1', 'pve1', {
|
||||
parentId: 'node-1',
|
||||
parentName: 'pve1',
|
||||
includePlatformNode: false,
|
||||
}),
|
||||
buildStorageResource('storage-2', 'Local-LVM-PVE2', 'pve2', {
|
||||
parentId: 'node-2',
|
||||
parentName: 'pve2',
|
||||
includePlatformNode: false,
|
||||
}),
|
||||
buildStorageResource('storage-3', 'Tank-TrueNAS', 'truenas-1', {
|
||||
platformId: 'truenas-main',
|
||||
platformType: 'truenas',
|
||||
}),
|
||||
];
|
||||
|
||||
render(() => (
|
||||
<Storage
|
||||
forcedSourceFilter="proxmox-all"
|
||||
suppressNodeFilter
|
||||
filterAriaLabel="Proxmox storage filters"
|
||||
filterSearchPlaceholder="Search Proxmox storage by pool, datastore, node, or device"
|
||||
/>
|
||||
));
|
||||
|
||||
// No prop opts the embed into the toolbar or keeps it out of the view tabs.
|
||||
expect(screen.getByRole('group', { name: 'Proxmox storage filters' })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByPlaceholderText('Search Proxmox storage by pool, datastore, node, or device'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('tablist', { name: 'Storage view' })).toBeInTheDocument();
|
||||
|
||||
// The forced source outranks the URL source, and the URL node resets to all.
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Local-LVM-PVE1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Local-LVM-PVE2')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByText('Tank-TrueNAS')).not.toBeInTheDocument();
|
||||
expect(navigateSpy.mock.calls.some(([path]) => String(path).includes('source='))).toBe(false);
|
||||
|
||||
// Neither lock surfaces as a chip, an Add filter option, or a clear action.
|
||||
expect(queryStorageChip('Source')).toBeNull();
|
||||
expect(queryStorageChip('Node')).toBeNull();
|
||||
const addFilter = screen.getByRole('combobox', { name: 'Filter' });
|
||||
expect(within(addFilter).getByRole('option', { name: 'Status: Warning' })).toBeInTheDocument();
|
||||
expect(
|
||||
within(addFilter).queryByRole('option', { name: /^(Source|Node):/ }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Clear filters' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /charts/i })).not.toBeInTheDocument();
|
||||
|
||||
const viewOptions = openStorageViewOptions();
|
||||
expect(within(viewOptions).getByLabelText('Group by')).toBeInTheDocument();
|
||||
expect(within(viewOptions).getByLabelText('Sort by')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders per-pool growth from the shared storage summary history contract', async () => {
|
||||
const gib = 1024 * 1024 * 1024;
|
||||
const storageSummarySpy = vi.spyOn(ChartsAPI, 'getStorageSummaryCharts').mockResolvedValue({
|
||||
@@ -1395,7 +1454,7 @@ describe('Storage', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the storage view selector available in table-only embedding', async () => {
|
||||
it('keeps the storage view selector available in a platform-page embed', async () => {
|
||||
mockLocationPath = '/proxmox/storage';
|
||||
hookResources = [
|
||||
buildPhysicalDiskResource('sda', 'node-1', 'pve1'),
|
||||
|
||||
Reference in New Issue
Block a user