Until now, the same column (CPU/Memory/Disk) was aligned differently
depending on which table it appeared in: Proxmox top tables defaulted
to left, Docker/TrueNAS/vSphere/Kubernetes top tables used right, the
shared workloads bottom table used center, and integer-count columns
(VMs/CTs/Containers/Datastores/Desired/etc.) were a mix of right and
center. The user's read: "complete hobbledy codge."
Codify the rule once and for all. The new
frontend-modern/src/features/platformPage/columnAlignment.ts defines
five canonical column kinds with a documented align mapping and the
rationale for each:
- 'name' left primary identifier
- 'text' left readable strings
- 'metric-bar' center bar fills cell, label rides at fill edge
- 'numeric-value' right units/digits line up for column scanning
- 'badge' center pill/icon-only cells
sharedPlatformPage exposes getPlatformTableHeadClassForKind() and
getPlatformTableCellClassForKind() wrappers; every platform top table
plus the Docker services table now consume these instead of passing
literal align strings. The Proxmox host column model carries a kind
field instead of a raw align so the same source of truth drives both
the model file and the JSX.
KubernetesClustersTable.tsx is intentionally not migrated in this
commit: another agent has it staged in their working tree. The same
migration applies once that lands; left for a follow-up.
The WorkloadTableHeader (shared bottom guest-list table) is also
unmigrated here. It center-aligns everything today, and changing it to
canonical means flipping Uptime / Net I/O / Disk I/O from center to
right across every guest list on every platform page — bigger visual
blast radius, so it gets its own scoped commit next.
A canonical-platform-audit rule and a CLAUDE.md/AGENTS.md note will
follow in a third commit, so any future agent that writes a
non-canonical align fails the pre-push hook rather than discovers the
drift visually weeks later.
Kubernetes, vSphere, TrueNAS, and Proxmox all rendered the same
single-axis "+ Filter" combobox the Docker overview just lost, leaving
the page surfaces visually inconsistent. The unified workload search
(workloadSearchCandidates in workloadSelectors.ts:52-70) already covers
node, namespace, contextLabel, clusterName, platformType and
containerRuntime — i.e. every axis the dropdown carried on these pages
— and each surface's search placeholder advertises that coverage
("Search pods by name, namespace, image, cluster, or node",
"Search VMs and LXCs by name, VMID, node, or status", etc.). Suppress
hostFilter and namespaceFilter on all four surfaces so the workload
toolbar is consistently chip-only: Status, Runtime (where applicable),
Group by, Metric display.
Same equal-width problem on /kubernetes/nodes: the Nodes table (9
columns) and the Deployments table (7 columns) both ran under
table-fixed with no per-column desktop widths, so longer values like
runtime "containerd://1.7.20" and capacity "6 cores / 51.0 GB"
truncated, while the four integer-count Deployments columns (Desired /
Updated / Ready / Available) each held 183px for a single-digit value.
Set explicit md:w-[...] on every TableHead so the longer-content
columns (Node, Runtime, Capacity, Deployment, Namespace) get the room
they need, the CPU and Memory bars get a balanced share each, and the
short text and integer-count columns trim to fit their headers and
values. Mobile widths are unchanged.
Leaving KubernetesClustersTable.tsx alone for now: another agent has
it mid-edit. Same fix pattern can be applied to it once their work
lands.
After promoting Runtime to a chip toggle, the "+ Filter" combobox
carried only the Node scope. The Docker workload search box already
matches against host identity candidates (docker.hostname,
agent.hostname, identity.hostname, canonicalIdentity.hostname and
aliases — see filterDockerHosts), so narrowing by host name is
already a search keystroke away. Same-named hosts can be disambiguated
by typing the host's id suffix. Dropping hostFilter from the Docker
page surface only, since other workload surfaces (notably Proxmox
where many nodes are not always visible in a top table) have different
ergonomics and should be evaluated separately.
Same equal-width problem as the platform overview tops: this table sat
on table-fixed with no per-column desktop widths, so all 7 columns
rendered at the same ~183px. Image references like
"registry.example.com/backend-deluge:2025-..." got truncated while the
1-digit Desired and Running columns held the same allocation.
Set explicit md:w-[...] on every TableHead. Service and Image take the
biggest shares (22% and 25%) for their long content, Mode / Desired /
Running trim to short text and integers, and Ports / Host get a middle
slice. Image now renders its full registry path at 1440px instead of
truncating. Mobile widths are unchanged.
The promoted Runtime chip group rendered with a verbose "All runtimes"
label and lowercase, dot-less docker/podman pills, breaking the visual
rhythm of the Status / Group by / Metric display chips it now sits
alongside. Map the runtime options to the same style as Status: concise
"All" for the empty value, capitalized "Docker" / "Podman" labels, and
leading dots in the canonical runtime badge colors (sky for Docker,
violet for Podman) so the chip palette echoes the runtime badges
rendered in the table rows.
The Runtime axis was hidden inside the "+ Filter" combobox under a
"Properties" optgroup with at most two real choices (docker, podman),
sitting next to Status / Group by / Metric display which are already
chip toggles. Render it inline alongside Status so the binary axis is
one click instead of two, and hide the filter entirely when fewer than
two distinct runtimes are present so single-runtime deployments are not
shown a degenerate All/<one-runtime> control. The leftover "+ Filter"
select now carries only the Node scope.
Same equal-width issue as the other platform overview top tables: 9
columns at ~147px each under table-fixed meant FQDN-style host names
(esxi-01.lab.local) and longer cluster names ("Production Cluster") sat
on the same width as the Datastores and VMs integer-count columns, and
the CPU / Memory bars couldn't show their inline "% (used / total)"
labels.
Set explicit md:w-[...] on every TableHead so the Host column gets 18%
(comfortable for FQDNs), CPU and Memory share 13% each as parallel bar
metrics, Cluster / Datacenter / Power / vCenter take what their text
content needs, and Datastores / VMs trim to fit their headers plus
1-2 digit counts. Memory now shows the full size info ("71% (57.2 GB/
80.0 GB)") at 1440px instead of just the percentage. Mobile widths are
unchanged.
Same equal-width issue as the Docker and Proxmox top tables: table-fixed
across 11 columns with no per-column desktop widths gave every column the
same ~120px slice. That squeezed the Version field (where
"TrueNAS-SCALE-24.10.2" is a normal 21-char value) and clipped the size
labels on the CPU / Memory / Storage bars, while the four integer-count
columns (Pools / Datasets / Disks / Apps) sat on 120px each holding
single-digit values.
Set explicit md:w-[...] on every TableHead so the three bar columns get
balanced shares (CPU 11%, Memory 12%, Storage 13%), Version and System
each get 14% for their longer mono-spaced and hostname content, and the
four count columns trim to what their headers need (Pools/Disks/Apps
~5-6%, Datasets 7% for the longer header). Memory and Storage bars now
show their full "% (used / total)" labels at 1440px instead of hiding
the size portion. Mobile widths are unchanged.
The Docker overview top table was running on table-fixed with no
per-column desktop widths, so all 10 columns rendered at the same ~132px.
That gave CPU / Memory / Disk bars the same allocation as one-digit
badge columns like "Containers" or short-text columns like "System",
and the bars couldn't show their inline "% (size / size)" labels at all.
Set explicit md:w-[...] on every header so the three bar-metric columns
share an equal generous slice (14% each), the Host name column gets
headroom for full hostnames, and the short-content columns (Version,
System, Uptime, Temp, Containers, Swarm role) trim to what their labels
and values actually need. Mobile widths are unchanged.
Disk shared half the weight of CPU and memory while rendering the same
kind of usage bar, so the disk meter was squeezed and the over-allocated
version/uptime/temp/cluster columns wasted space the node column needed
for longer cluster-style names. Equalize CPU/memory/disk to the workloads
table convention (140px each), give node headroom, and trim the
short-content columns to what their labels and values actually need
across desktop, tablet, and mobile layout modes.
(Recommits the change intended for bd18c0f5e, whose message accidentally
landed against an unrelated Kubernetes diff due to a parallel-agent
commit race; that earlier commit was left intact rather than rewritten.)
Disk shared half the weight of CPU and memory while rendering the same
kind of usage bar, so the disk meter was squeezed and the over-allocated
version/uptime/temp/cluster columns wasted space the node column needed
for longer cluster-style names. Equalize CPU/memory/disk to the workloads
table convention (140px each), give node headroom, and trim the
short-content columns to what their labels and values actually need
across desktop, tablet, and mobile layout modes.
The workload-filter search at the top of each platform overview page now
also narrows the Hosts / Nodes / Clusters tables above the workloads
table, mirroring how Docker filters its hosts table from the same input.
Typing "pve1" on the Proxmox overview now reduces the Nodes table to
matching rows; typing a cluster or namespace fragment on Kubernetes
narrows clusters/nodes/deployments. Status / view / runtime filters
remain scoped to the workloads table (they don't apply to nodes).
Adds a shared `resourceMatchesSearch` helper in src/utils that walks the
common Resource identity fields (name, displayName, hostnames, cluster
and namespace names, tags, canonical aliases).
Move the WorkloadsFilter to the top of the Proxmox, Kubernetes, TrueNAS,
and vSphere overview surfaces so every workload page renders the filter
bar above its tables, matching the Docker pattern. The mid-page filter
placement was inconsistent across pages and pushed the search input
below the fold on busy hosts/nodes/clusters tables.
Each page lifts useWorkloadsState to its overview component, renders
WorkloadsFilter directly, and passes the state into WorkloadsSurface
(removing showFilterToolbar and the filter* configuration props from
the surface). Filter scope is unchanged — it still governs the embedded
workloads table only.
PlatformGovernanceState narrowed to 'supported' | 'presentation-only'
once vSphere left the admitted set, so the badge-label helper's
'admitted' branch and the dropped governanceState='admitted' summary
call are unreachable. Hardcode currentAdmissionPath to [] and drop the
unreachable 'admitted'/'first-lab-ready' branches; the function still
accepts the wider type so it stays valid if a future platform is
re-admitted.
Flip vmware-vsphere from governance_state=admitted, readiness_stage=
first-lab-ready to supported on both axes. The phase-1 floor (vCenter
client, canonical agent/vm/storage projection, alerts integration,
Assistant read paths, mock fixtures, page surface, automated proof for
the read-only boundary) has been in place; the only remaining gate was
a live vCenter run, and we are taking the platform claim on the strength
of the implementation rather than blocking on that proof.
Add vmware-vsphere to default_infrastructure_source_order, to the
First-class platforms list and Current Support Matrix in
PLATFORM_SUPPORT_MODEL, and to the Pulse primary navigation
(automatically via SUPPORTED_PLATFORM_IDS in the regenerated frontend
manifest). Rename and invert the Go contract test that asserted vSphere
stays admitted, and let the admitted-platform helpers tolerate an empty
set now that nothing currently lives there. Drop the "in first-lab-
ready readiness" line from the vSphere empty state.
PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT used: this commit only flips two
JSON values for one platform; no subsystem contract schema or surface
changed.
Drop the duplicate Disks and Apps tabs that just remounted tables
already rendered in the Overview stack. Rename the default tab from
"Systems" to "Overview" so the section label matches the stacked
content (systems + pools + disks + apps). The Workloads filter inside
Overview owns app search/grouping, and the Storage tab continues to
provide the full StorageSurface (pools + disks toggle, with toolbar)
for richer storage exploration.
Also dress the TrueNAS systems table CPU/Memory columns with the
canonical bar primitives (ResponsiveMetricCell + StackedMemoryBar) so
the Overview stack reads as one consistent surface alongside the
Docker / Proxmox / vSphere / Kubernetes host tables.
Drop the duplicate Nodes / Pods / Deployments tabs that just remounted
the same tables already rendered in the Overview stack. Rename the
default tab from "Clusters" to "Overview" so the section label matches
what the tab actually shows (clusters + nodes + pods + deployments).
The Workloads filter inside Overview owns pod search/grouping; the
standalone tabs added zero navigation value.
Also dress the Kubernetes nodes table CPU/Memory columns with the
canonical bar primitives (ResponsiveMetricCell + StackedMemoryBar) so
the Overview stack reads as one consistent surface alongside the
Docker / Proxmox / vSphere host tables.
Drop the duplicate Virtual Machines tab that just remounted the embedded
Workloads table from Overview. Rename the default tab to Overview and
swap the Storage tab from the bespoke VsphereDatastoresTable to the
canonical StorageSurface (forcedSourceFilter=vmware-vsphere,
forcedView=pools), matching how Proxmox handles its Storage tab and
avoiding the Pools/Physical-disks toggle that previously stranded
operators on an empty-disks state.
Also dress the vSphere hosts table CPU/Memory columns with the canonical
bar primitives (ResponsiveMetricCell + StackedMemoryBar) so the Overview
stack reads as one consistent surface alongside the Docker and Proxmox
host tables.
Switching to List view used to light up the Reset/Clear all button
even though no chip rendered for it, conflating a view-option
preference with active filter state. Grouped/List is now treated like
the other view options (bars/trends, charts toggle, columns): no
contribution to Clear all visibility, no reset by Clear all.
Match the canonical LXC badge that appears throughout the workloads
table and other Pulse surfaces: just "LXC", no trailing VMID. The
VMID stays available via the badge tooltip and the host drawer for
operators who need it, but the at-a-glance scan signal is the
hostname column, not a duplicated identifier in the System pill.
The LXC Docker host badge was hand-coded as amber, but amber is the
canonical color for Pods (workloadTypePresentation.ts). The
canonical LXC color is green, matching the system-container
workload badge that appears throughout the rest of the UI. Import
the workload presentation class rather than hardcoding the green
tokens so the badge tracks the single source of truth.
The Proxmox-LXC Docker hosts row was blank (no CPU / MEM-used /
Disk) and the display name appended a noisy "(LXC <vmid>)" suffix,
because the inventory script only collects Docker daemon metadata,
not host resource usage. Pulse already polls the same data for the
underlying LXC via the PVE cluster/resources endpoint, so the
Docker host row should mirror what the rest of Pulse already knows.
Before applying each LXC inventory report, enrich the Host info
with CPU / Memory / Disk / Uptime taken from the source LXC's
existing models.Container. Drop the "(LXC <vmid>)" suffix from
the synthesized display name. On the frontend, detect the
proxmox-lxc-docker: hostSourceId prefix and emit a styled
"LXC <vmid>" badge into the System column instead of the
filtered-out runtime-only "docker" badge.
The "Hosts" tab already rendered the hosts table, the containers
WorkloadsSurface, and the Swarm services table together, so the
separate Containers and Swarm services sub-tabs were strict subset
views of overview. Other platform pages (Proxmox, TrueNAS,
Kubernetes) use sub-tabs for genuinely different data per tab; the
Docker shape was the odd one out.
Drop PlatformSectionTabs from DockerPageSurface and render the
unified hosts + containers + services stack unconditionally. Remove
the now-dead DOCKER_TAB_SPECS / DockerPageTabId / DockerTabSpec /
buildVisibleDockerTabSpecs exports from dockerPageModel. The
DockerPage no longer interprets the URL segment, so legacy
/docker/overview, /docker/containers, /docker/services bookmarks
continue to resolve via the existing /docker/* wildcard route in
App.tsx.
The disconnected branch of ConnectionStatusBadge used the connected
state's min-w-6 h-6 group-hover:px-3 sizing, designed for a circle dot
that expands on hover. But showLabelByDefault() is true for any
non-healthy tone, so the label was rendered without horizontal padding
and spilled against the pill's right edge. Switch to py-1 px-2 to match
the warning and reconnecting branches, which already pad for an
always-visible label.
Extracts the per-disk Storage card introduced for the Docker host drawer
into a shared `DrawerDiskListCard` under `components/Workloads/` and
threads an optional `disks` prop through `NodeDrawer` →
`NodeDrawerOverview`. The Docker drawer now imports the shared component
instead of carrying its own copy, and `NodeDrawerOverview` renders the
shared card whenever the caller supplies two or more disks (falling back
to the existing aggregate Storage rows otherwise).
`ProxmoxNodesTable` is left untouched in this commit because the file is
currently dirty under another agent's lane; wiring the `disks` prop on
its `<NodeDrawer>` call site is a separate one-line follow-up once that
lane settles.
The previous Storage card surfaced only the aggregated host.disk values,
which on multi-drive hosts (Unraid in particular) is just the rootfs and
misses the data drives entirely. Drawer drill-down should be more
informative than the row's hover tooltip, not less.
When the host's agent reports multiple disks, the Storage card now spans
two columns and lists each disk with mountpoint, percent, used/total, and
a threshold-coloured bar that matches the StackedDiskBar tooltip palette.
Single-disk hosts keep the existing aggregate Usage/Total/Free rows.