Closes the only API-coverage gap from the Docker / Kubernetes IA
maturity review: Roles, ClusterRoles, RoleBindings, and
ClusterRoleBindings now flow from the Kubernetes agent through the
canonical resource registry into the Kubernetes platform-page
Configuration tab.
Agent: pkg/agents/kubernetes/report.go gains four new report struct
types that carry summary counts plus subject-kind sets; individual
subject names and full PolicyRule contents are deliberately omitted
so Pulse stays a "what permissions exist where" surface, not an RBAC
enumeration tool. internal/kubernetesagent/agent.go gains four
collectors that call rbacv1.RoleList/ClusterRoleList/etc. through the
existing runKubernetesCallWithRetry wrapper, matching the
ServiceAccount collector's RBAC-forbidden retry pattern.
Canonical: internal/models mirrors with NormalizeCollections coverage;
convert* funcs in internal/monitoring/kubernetes_agents.go translate
agent report -> model; ResourceTypeK8sRole / K8sClusterRole /
K8sRoleBinding / K8sClusterRoleBinding join the canonical type set;
registry ingest* + adapter resourceFrom* functions emit one Resource
per RBAC object with ruleCount / roleKind / roleName / subjectCount /
subjectKinds / aggregationLabels on the K8s meta; search mapping in
internal/api/resources.go and the privacy allow-list in
internal/api/org_handlers.go pick up the four new type tokens; the
K8s privacy category in unifiedresources/policy_metadata.go classifies
them like the rest of K8s.
Frontend: ResourceType union + ResourceKubernetesMeta carry the new
kinds and RBAC summary fields; KubernetesPageSurface query asks for
them; the page model buckets them into the Configuration group;
KubernetesConfigTable renders Role / ClusterRole rule counts and the
aggregated flag, plus RoleBinding / ClusterRoleBinding role refs and
"N subjects · Kind1, Kind2 +overflow" subject summaries.
Curated demo seeds per-namespace Roles + RoleBindings plus an
aggregated ClusterRole + ClusterRoleBinding for pulse-demo-monitoring
in each cluster so the Configuration tab renders 18 RBAC rows across
the three demo clusters.
Contracts updated for the canonical-shape guard: monitoring,
api-contracts, unified-resources, frontend-primitives,
organization-settings (canonical) plus agent-lifecycle and
storage-recovery (dependent via Extension Points). Verification
proofs extended: kubernetes_registry_test.go, kubernetes_agents_test.go,
agent_inventory_test.go (new TestCollectRBACInventoryReportsSummaryCountsOnly
that pins the subject-name-omission contract), demo_scenarios_test.go,
adapter_coverage_test.go, contract_test.go, org_handlers_test.go,
resourceIdentity.test.ts, reportingResourceTypes.test.ts,
KubernetesConfigTable.test.tsx, and the
subsystem_lookup_test.py line-anchor bumps that the contract edits
shifted (api-contracts 246 -> 253, organization-settings 92 -> 93).
Verified:
- go build ./internal/... ./cmd/... clean
- go test ./internal/unifiedresources/..., ./internal/mock/...,
./internal/kubernetesagent/..., ./internal/api/...,
the K8s subset of ./internal/monitoring/... all clean (three
pre-existing unrelated monitoring failures noted earlier remain
unchanged by this commit)
- npm run type-check, lint:eslint, lint:theme,
lint:canonical-platforms clean
- vitest: 70 K8s frontend tests pass including the new RBAC render
coverage in KubernetesConfigTable.test.tsx
- browser proof on /kubernetes/configuration: 36 config rows
including 18 RBAC rows across three clusters; ClusterRole
"pulse-demo-monitoring" shows "12 rules · Aggregated";
ClusterRoleBinding shows "3 subjects · Group, ServiceAccount +1"
Per-cluster node profiles, kubelet versions, and degraded scenarios
replace the global rotation that made every demo cluster look like a
copy of the same one. Production EU keeps its prod-euw1-k8s-{01..05}
nodes and the NotReady worker on prod-euw1-k8s-03 (preserving the
existing host-posture test contract). Staging EU runs
stage-euw1-k8s-{01..05} and carries the payments-worker
CrashLoopBackOff. Development EU runs dev-euw1-{01..05} and carries
an ImagePullBackOff on cron-nightly-backfill (re-labelled from the
previous "Pending / PodInitializing" rotation so the curated
reconciler doesn't recover it). The unused Edge profile gets distinct
edge-pop-{lax,nrt,fra,iad,sin}-01 names + k3s version for when the
cluster count is bumped above three.
A new TestKubernetesDemoClustersTellDistinctStories test guards the
slice goal: each cluster's nodes use its own prefix, exactly one
cluster carries each degraded scenario, and every cluster has a
unique kubelet version. The monitoring subsystem contract is updated
to reflect the new three-cluster cast (Production EU + Staging EU +
Development EU) plus the per-cluster scenario distribution.
Side effect: with distinct node names per cluster, the K8s page
model's cluster-to-node matching now resolves all five nodes for
each cluster (previously two clusters showed "0 nodes" because every
cluster's nodes shared the same prod-euw1-* names, breaking
buildKubernetesClusterChildCounts' clusterId lookup).
Verified:
- go vet ./internal/mock/..., go test ./internal/mock/... clean
- browser proof on /kubernetes/overview: three clusters render with
distinct versions (v1.30.4 / v1.31.2 / v1.32.0-rc.1) and 5 nodes
each (vs the previous 5/0/0 split)
- /kubernetes/nodes: 15 rows across the three clusters with three
distinct name prefixes; one red NotReady dot on Production EU's
prod-euw1-k8s-03; fourteen green Ready dots elsewhere
dockerPageModel and kubernetesPageModel now expose
buildDockerIncidentRows / buildKubernetesIncidentRows + filter helpers
that walk resource.incidents[] for each row and synthesize a rollup
row when only the rollup-level incident fields (incidentCount,
incidentSeverity, etc.) are populated. Both builders sort
critical -> warning -> info, then by priority, then by resource name.
Mirrors buildVmwareIncidentRows and buildTrueNASIncidentRows.
New DockerAlertsTable and KubernetesAlertsTable render the rows as
a domain-coloured StatusDot list with an expandable per-row detail
grid (host / runtime / swarm cluster for Docker; cluster / namespace
/ node / owner for Kubernetes). DockerPageSurface and
KubernetesPageSurface render the alerts table under the hosts /
clusters table on Overview, wrapped in <Show when=incidents.length>
so a clean estate stays clean. No new tab — the rollup sits beneath
the existing inventory the same way TrueNAS's AlertsTable does.
Verified:
- npm run type-check, lint:eslint, lint:theme, lint:canonical-platforms clean
- vitest: 155 tests pass across docker / kubernetes / platformPage suites,
including 12 new page-model tests pinning incident builder + filter
behaviour and 2 new alerts-table render tests pinning row order
(critical -> warning -> info) and StatusDot variants
- browser proof on /docker/overview and /kubernetes/overview with a
clean estate: alerts table correctly hidden when incidents.length == 0
frontend-modern/src/features/platformPage/sharedPlatformPage.tsx
carried ~90 lines of resource.docker?.* and resource.kubernetes?.*
field knowledge inside matchesPlatformSearch. That coupled the
cross-platform helper to two specific platforms — every new docker.*
or kubernetes.* field anyone wanted searchable had to be edited in a
shared file, and the same fields were duplicated inside
DockerContainersTable's bespoke search-candidates helper.
dockerPageModel now exports dockerResourceSearchHaystack +
filterDockerResources, and kubernetesPageModel exports
kubernetesResourceSearchHaystack + filterKubernetesResources, each
covering the platform's own generic + meta fields and the composite
labels operators are likely to paste (e.g. "0.0.0.0:8080->80/tcp" on
container rows). All 12 Docker tables and all 12 Kubernetes tables now
import the per-platform filter; the bespoke DockerContainersTable
filter helper disappears since the page-model haystack subsumes it.
matchesPlatformSearch in sharedPlatformPage drops every docker.* and
kubernetes.* reference. The shared helper still serves the four
providers that consume it directly without a domain-specific haystack
(TrueNAS systems, Proxmox Mail Gateway, vSphere hosts, Agents), so
generic + pmg.* + vmware.* + tags stay. Existing sharedPlatformPage
test coverage of docker/k8s native metadata moves to the per-platform
page-model tests.
Verified:
- npm run type-check, lint:eslint, lint:theme, lint:canonical-platforms clean
- vitest: 138 tests pass across docker / kubernetes / platformPage suites
- browser proof on /docker/containers searching "traefik" trims 70 -> 6
by docker.image; on /kubernetes/workloads searching "payments" trims
42 -> 9 by kubernetes.namespace, "production-eu" trims to 14 by
kubernetes.clusterName
KubernetesClustersTable used to walk the entire resource scope inline
to count Nodes / Pods / Deployments per cluster, with a comment
apologizing for the cluster-id matching being brittle. That rollup now
lives on the page model as buildKubernetesClusterChildCounts(scope,
clusters), reusable by future Overview surfaces (incident rollups,
"active issues" cards) and pinned by tests on the model side.
The formula is unchanged: each resource looks up its owning cluster by
matching kubernetes.clusterId or kubernetes.clusterName against the
cluster row's own kubernetes meta, then increments the corresponding
counter. Agent rows that report a Kubernetes source still count as
nodes. The table now just memoises a call into the model helper.
No backend changes. Browser proof on /kubernetes/overview shows the
clusters table renders identical Nodes/Pods/Deployments counts as
before the lift.
Container, Swarm service, Swarm task, and Swarm node rows now derive
their StatusDot variant from Docker-specific state instead of the
generic resource.status triad. Dead/OOMKilled containers, unhealthy
running containers, exited-non-zero containers, services with running
< desired replicas, paused service rollbacks, failed/rejected Swarm
tasks, and unreachable Swarm managers all escalate to attention
without depending on what the unified-resource pipeline happened to
set as the row's overall status.
Each affected table also consumes a rank comparator (danger ->
warning -> muted -> success, tie-broken by display name) so attention
rows float to the top regardless of input order, and the corresponding
page-model buckets (containers, services, tasks, nodes) ship
pre-sorted so any consumer that bypasses the table-local sort still
gets the right ordering.
DockerResourceNameCell now accepts an optional indicator so
table-side mappers can drive the dot variant and tooltip without
re-doing the layout. Mapper truth tables and comparator order are
pinned in dockerPageModel.test.ts; the containers native-table test
pins rendered StatusDot variant plus row order on a four-row mixed
input.
Pod, node, deployment, and controller (ReplicaSet / StatefulSet /
DaemonSet / Job / CronJob) rows now derive their StatusDot variant
from kubernetes-specific state instead of the generic resource.status
triad. CrashLoopBackOff, ImagePullBackOff, OOMKilled, NotReady nodes,
and under-replicated controllers escalate to attention without
depending on whatever the unified-resource pipeline happened to set as
the row's overall status.
Each affected table also consumes a rank comparator (danger ->
warning -> muted -> success, tie-broken by display name) so attention
rows float to the top regardless of input order, and the corresponding
page-model buckets (pods, deployments, nodes, workloads) ship
pre-sorted to keep Overview and Workloads consistent.
Mapper truth tables and comparator order are pinned in
kubernetesPageModel.test.ts; KubernetesPodsTable and
KubernetesDeploymentsTable tests pin the rendered StatusDot variant
plus row order on synthetic three-row inputs.
Collect native Kubernetes config, policy, and autoscaling objects.
Project the new resource types through API filters, unified resources, mock fixtures, and Kubernetes tabs.
Keep Secret inventory metadata-only and route k8s-secret policy as restricted local-only.
Without an explicit case for `network`, `canonicalizeFrontendResourceType`
returned `undefined` and `resolveType` fell through to its `'agent'`
default, which is why REST-normalized vSphere network resources briefly
appeared in the vSphere Hosts table on Overview. The WS snapshot then
corrected the type to 'network' and they vanished, producing the
"lots of entries that disappear" flicker.
The TrueNAS Storage table was hand-rolling a thin emerald slab below
the used/total text, which read as cosmetic and didn't match any other
v6 platform table. Swap for ResponsiveMetricCell type="disk", which
renders the canonical MetricBar with the percentage label inside the
fill, severity-aligned coloring, sparkline support, and "used / total"
as the sublabel — the same primitive used by vSphere Datastores,
Proxmox Storage/Nodes, Docker Hosts, Kubernetes Nodes, and the other
TrueNAS tables (Apps, Systems).
Physical-disk rows have no used/total, so they fall back to a plain
"4.00 TB" size label rather than rendering a degenerate bar.
Bring TrueNAS Storage table in line with Kubernetes/Docker/Proxmox
peers on the canonical platform primitives.
- Add coloured leading dots to Healthy/Attention/Offline chips so the
filter toolbar matches PLATFORM_HEALTH_FILTER_OPTIONS.
- Drop the redundant State column. Health was being signalled three
times (row status dot, raw ZFS/SMART text, Health pill); raw state
still surfaces in the resource drawer.
- Drop the Shares column, which printed 0 on nearly every row; share
count remains available in the dataset detail.
- Rename TableCardHeader title to "Storage" to match peer titles
("Nodes", "Services", "Deployments").