Skip to content

feat(console): add Cluster Usage admin page#18

Merged
Andrey Kolkov (androndo) merged 20 commits into
mainfrom
feat/cluster-usage-page
May 28, 2026
Merged

feat(console): add Cluster Usage admin page#18
Andrey Kolkov (androndo) merged 20 commits into
mainfrom
feat/cluster-usage-page

Conversation

@lexfrei
Copy link
Copy Markdown
Contributor

@lexfrei Aleksei Sviridkin (lexfrei) commented May 26, 2026

What this PR does

Adds a new admin page under Console → Administration → Cluster Usage that surfaces cluster-scoped resource consumption — CPU, Memory, ephemeral-storage, Pods and any extended resource discovered in node.status.capacity (GPUs and other accelerators). The page renders an aggregate panel on top with a node-summary header and one card per resource, plus a per-node table below with sortable headers, a name/role filter, and one dynamic column per full extended-resource key found in the cluster.

There is no vendor allow-list anywhere in the code: a new accelerator surfaces in the UI the moment a node carrying it joins the cluster.

Fixes #15.

Visibility

The sidebar entry is gated by a SelfSubjectAccessReview on nodes list. Tenant users who lack the permission never see the menu item, so they never click it and hit a 403. Loading and error states resolve as "not allowed" so the entry never flickers in then out. On direct URL navigation a 403 message with a back-to-console link is rendered instead of a browser 403.

Companion PR

cozystack/cozystack#2743 adds the required ClusterRole and ClusterRoleBinding to the dashboard chart, granting cozystack-cluster-admin cluster-wide read on nodes, pods, and metrics.k8s.io/nodes. Without that PR merged, the sidebar gate fails closed and the entry stays invisible — the desired fail-safe for clusters that have not adopted the new RBAC.

Recommended merge order: cozystack/cozystack#2743 first (RBAC change is additive and safe to ship without UI consumer), then this PR.

Design highlights

Three discovery layers, runtime only:

  1. node.status.capacity and allocatable for every node — always available. Standard resources (cpu, memory, ephemeral-storage, pods, hugepages-*) are filtered out; the remaining keys define both the aggregate cards and the dynamic table columns. No code change is required to support a new accelerator vendor.
  2. /apis discovery for metrics.k8s.io. If present, NodeMetrics is polled on a 30-second cadence (matching the default --metric-resolution of metrics-server) and a Used overlay is drawn on the CPU and Memory cards plus per-node cells. If absent, the Used overlay is omitted with no warning banner.
  3. Per-source SelfSubjectAccessReview gates each section so a partially-equipped cluster does not render error states (pods-list denied → Requested cells render an em dash with a Requires cluster-wide pod read access tooltip; metrics 403 → Used overlay simply disappears; nodes 403 → the spec'd permission-denied block).

New primitives added to @cozystack/k8s-client for reuse outside this page:

  • K8sProvider accepts an optional client prop for test injection.
  • K8sClient.getApiGroups() plus useApiGroupAvailable(groupName) — single cached /apis lookup shared by all consumers.
  • useSelfSubjectAccessReview(spec) — single POST cached by spec, staleTime: Infinity, refetchOnWindowFocus: false. The same hook can later gate other cluster-scoped sidebar entries (Tenants, External IPs, Modules currently all render for everyone and 403 on click).

Out of scope (first iteration)

  • True utilisation for accelerators (DCGM SM utilisation, HAMi fractional usage, Prometheus-backed metrics). The page detects the relevant CRD groups via discovery and is structured to add these as additional per-source-gated sections in a follow-up.
  • Per-tenant resource breakdown (this page is cluster-scoped only).
  • Historical time-series / charts.
  • A node-detail page reached by clicking a node name.
  • Pagination of the per-node table.

Tests

207 tests cover: K8sProvider injection, useApiGroupAvailable caching semantics, useSelfSubjectAccessReview (allowed / denied / cached / error), pure derivation utilities (capacity/allocatable/requested/used aggregation and per-node breakdown across mixed extended-resource fixtures, unscheduled pods, NotReady nodes, allocatable=0 guard, empty role suffix, hugepages filtering), composite hook behaviour (loading / happy path / metrics absent / metrics 403 / nodes error), the aggregate panel (node-summary header, four standard cards, dynamic extended cards alphabetical, Used presence/absence, pods-unavailable tooltip), the per-node table (default sort, click-to-flip, name+role filter, pressure chip, taint chip, NotReady em-dashes, pods-unavailable tooltip, dynamic columns), the page (loading / empty / nodes error / 403 / metrics absent / pods-unavailable propagation / nodeSummary header), the sidebar gate (SSAR allowed / denied / loading / error), and routing.

Pre-existing 79 ESLint errors on main are untouched by this PR; the new code adds zero lint hits.

Manual verification

Browser checks against a dev cluster — sidebar visibility for cluster-admin vs tenant users (separate Keycloak sessions), aggregates on a cluster with and without metrics-server, dynamic column appearance by patching a synthetic extended resource onto a node, and the watch / poll cadences in devtools — are scheduled as a follow-up comment with screenshots before merge.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a new "Cluster Usage" admin page displaying cluster-wide and per-node resource usage metrics (CPU, memory, storage, pods) with support for extended resources (e.g., GPUs).
    • Integrated optional metrics collection from the metrics API with graceful fallback when unavailable.
    • Added RBAC-based visibility control for the Cluster Usage menu entry.

Review Change Stack

K8sProvider now accepts an optional 'client' prop. When supplied, it
replaces the K8sClient that would otherwise be constructed from 'config';
when omitted, the existing 'new K8sClient(config)' path is preserved
so production callers stay unchanged.

This is the lever tests need to substitute a mock without touching
network or globals. Without it, the only way to isolate components from
real fetch behaviour is to monkey-patch K8sClient.prototype, which
couples every test file to the implementation and leaks across files
when restoreMocks is forgotten.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Adds the minimum testing surface needed to exercise components that
consume K8s hooks: renderWithK8sProvider wraps the tree in a QueryClient
with retries off, the K8sProvider with an injected mock, and a
MemoryRouter; createMockK8sClient builds a K8sClient subclass whose
list/get/watch methods serve in-memory overrides keyed by api group,
version, plural, and namespace. A compile-time type assertion at the
foot of the mock file pins the production K8sClient interface in place
so future additions to that class force a corresponding mock stub.

Fixture data covers the three node shapes the cluster-usage page must
render — control-plane without extended resources, plain worker, GPU
worker with one nvidia.com/gpu — plus a matching pod set including one
unscheduled pod so the aggregator can be verified to skip it.

The smoke test pins the contract: a child component that calls
useK8sList resolves through the injected mock without touching network,
the mock receives the expected list call, and the queryClient is
exposed to callers that need to clear or inspect the cache.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
parseQuantity, humanizeBytes and humanizeCpu used to live as private
helpers in QuotaDisplay.tsx. The cluster-usage admin page needs the
same functions to render node capacity, requested and used numbers,
so they move to a dedicated module under src/lib/ and pick up
table-driven test coverage along the way.

Behaviour is preserved verbatim — the tests pin the pre-existing
corner case where a bare 'm' suffix returns NaN, so future cleanups
have an explicit place to file an issue before changing it.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
K8sClient.getApiGroups() issues GET /apis and returns the registered
APIGroupList. The useApiGroupAvailable hook layers on top of it,
caching the discovery result with staleTime: Infinity so all consumers
on a page share a single network round trip.

The cluster-usage admin page needs this to runtime-detect the presence
of metrics.k8s.io — if the API group is not registered, the page must
omit the 'Used' overlay rather than fall back to error reporting.
Errors from the discovery call are intentionally absorbed and surfaced
as available=false: callers are asking a discovery question, and 'we
cannot find out' is functionally identical to 'not registered' for
gating logic that drives whether a UI section renders.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
POSTs a SelfSubjectAccessReview against authorization.k8s.io/v1 and
returns whether the current user is allowed to perform the requested
action. The result is cached per spec for the lifetime of the
QueryClient (staleTime: Infinity, refetchOnWindowFocus disabled) so
two consumers asking the same question share a single network round
trip; different specs cache independently.

This is the lever the new Administration sidebar uses to gate the
Cluster Usage entry on a nodes-list permission check, hiding the
entry from users who would only see a 403 if they clicked it.

Errors are absorbed and surfaced as allowed=false: a caller asking
'can I see this UI section' is functionally indifferent to 'denied'
versus 'cannot determine'; in both cases the section stays hidden.
A revoked grant surfaces on the next page load — a price worth
paying to keep the discovery query off the focus / reconnect path.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Pure functions, no React: getExtendedResourceKeys discovers the set of
extended-resource keys present in node.status.capacity across the
cluster (filtering out the four standard scheduler resources and every
hugepages-* variant), getExtendedResourcePrefixes groups them by their
vendor prefix for aggregate-card rendering, aggregateNodeResources sums
capacity/allocatable/requested/used cluster-wide for every standard and
discovered extended resource, and derivePerNodeRows builds the same
totals one node at a time alongside roles, conditions, taints and age.

A node-by-node split is required for the per-node table the
cluster-usage page renders below the aggregate panel; computing it from
the same primitives keeps the table totals reconcilable with the
aggregate cards.

The extended-resource pathway has no vendor allow-list — keys are
rendered exactly as the cluster exposes them, so a previously unseen
accelerator (acme.io/fpga, anything) surfaces in the UI the moment a
node carrying it joins.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Wires together nodes (watch), cluster-wide pods (watch), API group
discovery for metrics.k8s.io and — only when that group is registered —
a 30-second poll of NodeMetrics. metrics.k8s.io is not watchable, so a
refetch interval is the only option; the rest of the page works fine
without it.

A 403 on the metrics endpoint is absorbed as 'no usage data' rather
than promoted to a page-level error: the Used overlay simply
disappears, the rest of the panel still renders. Nodes-list or
pods-list errors are surfaced as the hook's error so the page can
render an explicit failure state.

The 30-second cadence is exported as a named constant so tests can
assert on it without duplicating the magic number.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Top panel of the Cluster Usage admin page. ResourceCard renders one
aggregate-resource summary with Capacity, Allocatable, an always-on
Requested progress bar, and an optional Used progress bar that appears
only when totals.used is defined — which mirrors the case where
metrics.k8s.io is discovered for cpu and memory.

ClusterUsageAggregates lays out four fixed cards (CPU, Memory, Storage,
Pods) followed by one card per extended-resource key found in node
capacity, sorted alphabetically with the full key as the title. The
extended section disappears entirely when no extended resources are
present — no empty 'No GPUs found' placeholder, mirroring the
discovery-first contract for the whole page.

Progress bar colour rises with allocation: ≤70% neutral, 70-90%
amber, >90% red. Allocatable=0 (very early node lifecycle) renders em
dashes for every number and omits the bar rather than dividing by zero.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Bottom panel of the Cluster Usage admin page. Fixed columns for Name,
Status, Roles, CPU, and Memory, followed by one column per full
extended-resource key found in the cluster, then Age. Headers for
extended-resource columns use monospace and the resource key verbatim
so vendor-specific names like 'nvidia.com/gpu' read as identifiers
rather than English words.

CPU and Memory cells render two lines: used/allocatable (when
metrics-server is discovered) and requested/allocatable (always when
allocatable is non-zero). NotReady nodes render em dashes for these
cells because the capacity numbers stop being authoritative; the rest
of the row stays visible so the row remains a useful pointer for the
operator.

Status, Roles and taints each carry their own visual treatment so the
table doubles as a quick triage view — pressure conditions render as
amber chips, taints surface a 'tainted N' label, and SchedulingDisabled
nodes call themselves out distinctly from NotReady.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…table

Single cluster-scoped page reads from useClusterUsageData and renders
the aggregate panel on top, the per-node table below. The locked
loading / empty / error matrix is honoured: nodes-loading shows a
page-level spinner, nodes-error shows an explicit failure block,
empty cluster shows 'No nodes found', anything else renders both
panels.

The page is reachable by URL even when the sidebar gate hides the
entry; in that case the underlying useK8sList call returns 403 and
the error block carries the message. A fancier page-level permission
gate is explicitly out of scope for the first iteration.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The new entry sits at the top of Administration. It only appears when
a SelfSubjectAccessReview confirms the current user can list nodes —
tenant users never see the menu item, so they never click it and hit
a 403. Loading and error states resolve as 'not allowed' so the entry
never flickers in then out for users who can't see it.

The companion route in ConsolePage mounts ClusterUsagePage at
/cluster-usage. Users who bookmark the URL directly land on an error
block rendered by the page itself when the underlying API call is
denied — a fancier page-level gate is intentionally deferred.

A consequence worth calling out: this is the first sidebar entry in
the console gated by a permission check. The pattern (single SSAR call,
isLoading-false-and-allowed-true predicate) can be reused for other
cluster-scoped entries later — Tenants, External IPs, Modules all
currently render for everyone and 403 on click.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Round out the page against the issue's full acceptance criteria.

Aggregates panel:
- New header line shows total node count plus a Ready / NotReady /
  SchedulingDisabled split. Singular 'node' vs plural is honoured for
  one-node clusters.
- Requested numbers everywhere collapse to an em dash with a
  'Requires cluster-wide pod read access' tooltip when the pods watch
  has failed; the rest of the panel still renders so the operator
  retains capacity and Used data.

Per-node table:
- All column headers are clickable to toggle sort direction. Default
  is Name ascending. Dynamic extended-resource columns are sortable
  by requested percentage.
- New filter input above the table matches against node name and
  roles, case-insensitive.
- CPU and Memory Requested cells switch to the same em-dash tooltip
  when pods are unavailable. Extended-resource cells switch their
  Requested half similarly while keeping the allocatable / capacity
  figures, which remain authoritative from node.status.

ClusterUsagePage:
- A 403 on the nodes-list call renders the spec'd 'You do not have
  permission' message with a back-to-console link, rather than the
  generic failure text. Other status codes keep the existing block.
- The page now passes nodeSummary and podsUnavailable through to the
  aggregate panel and the table.

Other clean-ups required by branch review:
- The mock K8sClient factory no longer carries the misleading
  '_typeDriftCheck' tautology and the JSDoc no longer claims a drift
  guarantee that the code never delivered. Watch stub flips to
  mockReturnValue, which removes the six unused-parameter lint errors.
- useClusterUsageData wraps its derived nodes/pods/metrics arrays in
  useMemo so the downstream aggregate/per-node useMemo dependencies
  no longer flip identity on every render (silences the
  react-hooks/exhaustive-deps warning).
- Unused getExtendedResourcePrefixes helper removed; the only
  remaining caller was its own test. isExtendedResourceKey is now the
  single source of truth used by both extended-resources.ts and
  per-node.ts.
- rolesFromLabels skips empty role suffixes
  ('node-role.kubernetes.io/' with no name) so the UI does not render
  blank pills.
- useClusterUsageData test stub is now keyed by (apiGroup, plural);
  metrics-server's NodeMetrics is plural=nodes too, so the previous
  plural-only key never matched and the metrics path was effectively
  untested.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Collapse the per-file STANDARD_KEYS Set into a single export from
types.ts (STANDARD_RESOURCE_KEY_SET) and have aggregate.ts and
per-node.ts use it directly. The previous local copies and the ad-hoc
'(STANDARD_RESOURCE_KEYS as readonly string[]).includes(...)' cast in
aggregate.ts both went away.

Spell out the scaling trade-off of the cluster-wide pods watch in the
JSDoc on useClusterUsageData so a future reader does not second-guess
the design choice or treat the cost as accidental: every pod stays hot
in memory because Requested totals need every pod regardless of
namespace; the field-selector projection / server-side aggregation
follow-up is named explicitly.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

@lexfrei, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 50 minutes and 57 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f34aabc-d049-48be-b12b-cbd7d2e5843e

📥 Commits

Reviewing files that changed from the base of the PR and between da6961c and a4dc332.

📒 Files selected for processing (9)
  • apps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsx
  • apps/console/src/components/cluster-usage/ClusterUsageTable.test.tsx
  • apps/console/src/components/cluster-usage/ClusterUsageTable.tsx
  • apps/console/src/lib/cluster-usage/aggregate.test.ts
  • apps/console/src/lib/cluster-usage/aggregate.ts
  • apps/console/src/lib/k8s-quantity.test.ts
  • apps/console/src/lib/k8s-quantity.ts
  • apps/console/src/routes/ClusterUsagePage.test.tsx
  • apps/console/src/routes/sidebar-sections.test.tsx
📝 Walkthrough

Walkthrough

This PR adds a complete "Cluster Usage" admin page to the Kubernetes dashboard with discovery-driven extended resources, per-node resource tracking, permission-based visibility, and comprehensive test coverage. It enhances the K8s client with API group discovery and SSAR hooks, provides core data aggregation and per-node calculations, builds UI components for resource display, and integrates the page with RBAC-based sidebar gating.

Changes

Cluster Usage Admin Page

Layer / File(s) Summary
K8s Client API Enhancements for Discovery and SSAR
packages/k8s-client/src/client.ts, packages/k8s-client/src/useApiGroupAvailable.ts, packages/k8s-client/src/useSelfSubjectAccessReview.ts, packages/k8s-client/src/provider.tsx, packages/k8s-client/src/index.ts, apps/console/src/__tests__/k8s-client/*
K8sClient gains getApiGroups() for discovering metrics.k8s.io presence. New hooks useApiGroupAvailable and useSelfSubjectAccessReview provide cached queries for metrics discovery and permission checking. K8sProvider accepts optional injected K8sClient for testing. Tests cover injected clients, config-based construction, discovery caching, SSAR payload fidelity, and permission gating behavior.
Type Definitions and Kubernetes Quantity Parsing
apps/console/src/lib/cluster-usage/types.ts, apps/console/src/lib/k8s-quantity.ts, apps/console/src/lib/k8s-quantity.test.ts, apps/console/src/components/QuotaDisplay.tsx
New types define minimal Kubernetes Node/Pod/metrics shapes, resource accounting (capacity/allocatable/requested/used), and extended resource classification via isExtendedResourceKey(). Quantity utilities parse Kubernetes resource strings (milli, binary, decimal suffixes) and format CPU/byte values for display. QuotaDisplay refactored to import shared utilities.
Cluster-Wide Resource Aggregation
apps/console/src/lib/cluster-usage/aggregate.ts, apps/console/src/lib/cluster-usage/aggregate.test.ts
aggregateNodeResources() sums node capacity/allocatable, scopes pod requests to scheduled nodes, and conditionally aggregates CPU/memory usage from metrics. Tests verify proper handling of extended resources, pod filtering, and undefined metrics.
Extended Resource Key Extraction
apps/console/src/lib/cluster-usage/extended-resources.ts, apps/console/src/lib/cluster-usage/extended-resources.test.ts
getExtendedResourceKeys() extracts non-standard resource types from node capacity, filters standard and hugepages resources, deduplicates, and sorts for stable discovery-driven columns.
Per-Node Row Calculation with Metrics
apps/console/src/lib/cluster-usage/per-node.ts, apps/console/src/lib/cluster-usage/per-node.test.ts
derivePerNodeRows() computes per-node rows with resource totals (scoped by pod scheduling), conditions/readiness/pressure, roles, taints, age, and optional metrics-based usage. Tests cover extended resources, metric unit conversion, and missing-metrics degradation.
Cluster Usage Data Hook
apps/console/src/hooks/useClusterUsageData.tsx, apps/console/src/hooks/useClusterUsageData.test.tsx
Hook orchestrates list/watch for nodes/pods and conditional polling for NodeMetrics (only when metrics API discovered). Composes aggregates, per-node rows, and node-count summary. Handles partial failures: nodes failure blocks page, metrics/pods failures degrade to UI flags and undefined values.
ResourceCard Progress Display
apps/console/src/components/cluster-usage/ResourceCard.tsx, apps/console/src/components/cluster-usage/ResourceCard.test.tsx
Component renders resource cards with capacity/allocatable, conditional Used line, Requested progress bar with clamping to 100%, and tooltip support for unavailable-requested state. Tests cover progress bar thresholds, zero-allocatable em-dashing, and over-commit clamping.
Cluster-Wide Aggregates Panel
apps/console/src/components/cluster-usage/ClusterUsageAggregates.tsx, apps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsx
Renders cluster summary header (node counts with pluralization), standard resource cards (CPU/Memory/Storage/Pods) in fixed grid, and conditional extended-resources section sorted alphabetically. Tests verify header text, card order, extended-resource filtering, and podsUnavailable propagation.
Per-Node Resource Table
apps/console/src/components/cluster-usage/ClusterUsageTable.tsx, apps/console/src/components/cluster-usage/ClusterUsageTable.test.tsx
Sortable, filterable table of per-node resources with dynamic columns for extended resources. Handles NotReady em-dashing, pressure conditions as chips, roles with em-dash fallback, and conditional requested-unavailable tooltip. Tests cover sorting, filtering (name/roles), and per-node extended-resource inclusion.
ClusterUsagePage Route Component and Integration
apps/console/src/routes/ClusterUsagePage.tsx, apps/console/src/routes/ClusterUsagePage.test.tsx, apps/console/src/routes/ConsolePage.tsx, apps/console/src/routes/ConsolePage.routing.test.tsx, apps/console/src/routes/sidebar-sections.tsx, apps/console/src/routes/sidebar-sections.test.tsx
ClusterUsagePage assembles aggregates and table components with loading/permission/error/empty/data state rendering. Route registered at /cluster-usage. Sidebar entry ("Cluster Usage" icon) gated by SSAR check for nodes list. Tests verify route registration, state rendering, permission gating, and metrics absence handling.
Test Utilities and Fixtures
apps/console/src/test-utils/mock-k8s-client.ts, apps/console/src/test-utils/render.tsx, apps/console/src/test-utils/render.test.tsx, apps/console/src/test-utils/fixtures/nodes.ts, apps/console/src/test-utils/fixtures/pods.ts, apps/console/src/test-utils/fixtures/node-metrics.ts
createMockK8sClient() factory stubs K8sClient methods with override-based responses. renderWithK8sProvider() helper renders K8s-dependent components with injected client, QueryClient, and MemoryRouter. Fixtures provide realistic node/pod/metrics data for tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • kitsunoff

Poem

🐰 A dashboard of usage blooms with extended grace,
Discovery paints resources across the cluster's face,
Per-node wisdom aggregates with measured care,
Permission gates keep secrets, metrics fill the air! 🎯📊

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(console): add Cluster Usage admin page' accurately describes the main change—introducing a new admin page for viewing cluster resource usage.
Linked Issues check ✅ Passed The pull request implements all coding requirements from issue #15: discovery-driven extended resources, per-node and aggregates displays, SSAR-based sidebar gating, metrics polling, error handling for missing APIs/permissions, and required k8s-client primitives.
Out of Scope Changes check ✅ Passed All code changes align with issue #15 scope: new Cluster Usage page, discovery utilities, test infrastructure, and k8s-client enhancements. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cluster-usage-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/XXL This PR changes 1000+ lines, ignoring generated files label May 26, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new "Cluster Usage" administration page that displays cluster-scoped capacity, allocation, and usage across all nodes, including discovered extended resources. It adds several new components, hooks, and tests, such as ClusterUsageAggregates, ClusterUsageTable, ResourceCard, and the useClusterUsageData hook. Additionally, it introduces utility hooks in the @cozystack/k8s-client package, including useApiGroupAvailable and useSelfSubjectAccessReview for permission gating. The review feedback suggests improving error handling in useClusterUsageData.tsx by importing and using K8sApiError directly for type-safe status code extraction instead of using unsafe type assertions. It also recommends removing a redundant .slice() call in ClusterUsageTable.tsx before sorting, as .filter() already returns a new array instance.

Comment thread apps/console/src/hooks/useClusterUsageData.tsx
Comment thread apps/console/src/hooks/useClusterUsageData.tsx Outdated
Comment thread apps/console/src/components/cluster-usage/ClusterUsageTable.tsx Outdated
Use 'instanceof K8sApiError' to read status from the nodes-list error
instead of the previous 'as unknown as { status?: unknown }' bridge —
the runtime check is type-safe and the cast is gone. K8sApiError is
the only error shape the client throws on HTTP failures, and the
exported class is already part of the @cozystack/k8s-client public
surface.

Drop the .slice() before .sort() in the per-node table: Array.filter()
already returns a fresh array, so .slice() was a no-op safeguard. The
chained form (.filter().sort()) is what the rest of the codebase
uses when the input is known to be filter-produced.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@github-actions github-actions Bot added area/console Issues or PRs related to apps/console — routes, detail pages, marketplace, command palette kind/feature Categorizes issue or PR as related to a new feature labels May 27, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (15)
apps/console/src/routes/sidebar-sections.test.tsx (2)

106-113: ⚡ Quick win

Replace setTimeout flush with waitFor and a meaningful assertion.

Same issue as the previous test—using setTimeout creates a timing dependency.

♻️ Suggested waitFor pattern
     const { result } = renderHook(() => useConsoleSidebarSections(), {
       wrapper: makeWrapper(client),
     })
-    await new Promise((r) => setTimeout(r, 10))
-    expect(findItem(result.current, "Cluster Usage")).toBeUndefined()
+    await waitFor(() => {
+      expect(findItem(result.current, "Cluster Usage")).toBeUndefined()
+    })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/routes/sidebar-sections.test.tsx` around lines 106 - 113,
Test uses an unstable setTimeout flush; replace the artificial delay with React
Testing Library's waitFor and assert a concrete condition: after rendering with
makeClient({ ssar: new K8sApiError(500, "boom") }) and wrapper from makeWrapper,
remove the new Promise/setTimeout and instead call await waitFor(() =>
expect(findItem(result.current, "Cluster Usage")).toBeUndefined()) so it waits
for state updates from useConsoleSidebarSections to settle; keep renderHook(...)
and references to useConsoleSidebarSections, makeClient, makeWrapper, and
findItem.

88-95: ⚡ Quick win

Replace setTimeout flush with waitFor and a meaningful assertion.

Using setTimeout(r, 0) to flush the async queue creates a timing dependency and is fragile. Testing Library's waitFor with a proper assertion is more robust.

♻️ Suggested waitFor pattern
-    // Wait for the SSAR query to settle so the absence is real.
     await waitFor(() => {
       const adminSection = result.current.find((s) => s.title === "Administration")
       expect(adminSection).toBeDefined()
+      // Ensure SSAR query has settled
+      expect(findItem(result.current, "Cluster Usage")).toBeUndefined()
     })
-    // Need an explicit settle window for the SSAR query.
-    await new Promise((r) => setTimeout(r, 0))
-    expect(findItem(result.current, "Cluster Usage")).toBeUndefined()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/routes/sidebar-sections.test.tsx` around lines 88 - 95,
Remove the fragile setTimeout flush and instead await a waitFor that asserts the
absence of the menu item; specifically, replace the new Promise(setTimeout...)
block with an await waitFor(...) that checks findItem(result.current, "Cluster
Usage") is undefined, using the existing findItem helper and result.current so
the test reliably waits for the SSAR query to settle.
apps/console/src/routes/ClusterUsagePage.test.tsx (1)

146-149: ⚡ Quick win

Prefer Testing Library queries over direct DOM access.

Using document.querySelectorAll bypasses Testing Library's accessibility-focused query methods and makes tests more brittle to DOM structure changes.

♻️ Suggested Testing Library query
-    const tooltipNodes = document.querySelectorAll(
-      '[title="Requires cluster-wide pod read access"]',
-    )
-    expect(tooltipNodes.length).toBeGreaterThan(0)
+    expect(
+      screen.getAllByTitle("Requires cluster-wide pod read access").length,
+    ).toBeGreaterThan(0)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/routes/ClusterUsagePage.test.tsx` around lines 146 - 149,
Replace the direct DOM access using document.querySelectorAll in
ClusterUsagePage.test.tsx (the tooltipNodes variable) with Testing Library
queries: import/use screen from `@testing-library/react` and call
screen.getAllByTitle("Requires cluster-wide pod read access") (or another
appropriate accessible query such as getAllByText/getAllByRole if the tooltip
content is rendered accessibly) and assert on that result instead of using
document.querySelectorAll to make the test more robust and aligned with Testing
Library practices.
apps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsx (1)

47-53: ⚡ Quick win

The “in order” test does not actually verify order

arrayContaining only checks presence. This can pass even when card order regresses.

Suggested test adjustment
-    expect(labels).toEqual(
-      expect.arrayContaining(["CPU", "Memory", "Storage", "Pods"]),
-    )
+    expect(labels).toEqual(["CPU", "Memory", "Storage", "Pods"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsx`
around lines 47 - 53, The test "renders the four standard cards in order CPU,
Memory, Storage, Pods" currently uses expect.arrayContaining which only checks
presence; update the assertion to verify exact order by comparing the labels
array produced from headings (headings.map(h => h.textContent)) against the
exact ordered array ["CPU","Memory","Storage","Pods"] (e.g. use
toEqual/toStrictEqual) and ensure any whitespace/case is normalized if needed so
the order assertion fails when card order regresses; replace the
expect.arrayContaining(...) call with the strict equality assertion on labels.
apps/console/src/components/cluster-usage/ClusterUsageTable.test.tsx (1)

97-100: ⚡ Quick win

Extended-column order test doesn’t assert order

Current checks only verify presence. If order matters, assert sequence directly.

Suggested test adjustment
-    expect(headers).toContain("nvidia.com/gpu")
-    expect(headers).toContain("amd.com/gpu")
+    const gpuHeaders = headers.filter((h) =>
+      h === "nvidia.com/gpu" || h === "amd.com/gpu",
+    )
+    expect(gpuHeaders).toEqual(["nvidia.com/gpu", "amd.com/gpu"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/cluster-usage/ClusterUsageTable.test.tsx` around
lines 97 - 100, The test currently only checks presence of GPU header labels
using the headers array in ClusterUsageTable.test.tsx; update it to assert
ordering by comparing positions rather than just containment—use the existing
headers variable and either assert headers.indexOf("nvidia.com/gpu") <
headers.indexOf("amd.com/gpu") or assert a slice/equality (e.g.,
expect(headers).toContainEqual([...]) replaced with an explicit sequence check
such as expect(headers).toEqual(expect.arrayContaining([...])) combined with
order assertion) so the test fails if the column order changes.
apps/console/src/components/QuotaDisplay.tsx (1)

4-4: ⚡ Quick win

Use @/lib/k8s-quantity.ts alias import.

This should use the app alias rather than a relative path.

As per coding guidelines, Use path aliases (@/for apps/console/src/,@cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/QuotaDisplay.tsx` at line 4, Replace the relative
import in QuotaDisplay.tsx with the app alias import: change the import that
currently pulls parseQuantity, humanizeBytes, and humanizeCpu from
"../lib/k8s-quantity.ts" to use "`@/lib/k8s-quantity.ts`" so the module is
resolved via the apps/console/src alias; ensure the imported symbols
(parseQuantity, humanizeBytes, humanizeCpu) remain unchanged.
apps/console/src/lib/cluster-usage/extended-resources.test.ts (1)

2-3: ⚡ Quick win

Use app path aliases in this test module.

Convert the relative imports to @/ aliases.

As per coding guidelines, Use path aliases (@/for apps/console/src/,@cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/cluster-usage/extended-resources.test.ts` around lines 2
- 3, Replace the relative imports at the top of the test with the project path
aliases: change the import of getExtendedResourceKeys from
"./extended-resources.ts" to the app alias starting with "`@/`..." that maps to
apps/console/src, and change the Node type import from "./types.ts" to the
corresponding "`@/`..." alias; ensure you use the exact symbol names
getExtendedResourceKeys and Node so the test still references the same exports
but via the alias imports per the coding guideline.
apps/console/src/lib/cluster-usage/aggregate.ts (1)

1-3: ⚡ Quick win

Replace relative imports with @/ aliases.

Use app-level aliases for these internal imports.

As per coding guidelines, Use path aliases (@/for apps/console/src/,@cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

Also applies to: 11-11

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/cluster-usage/aggregate.ts` around lines 1 - 3, Replace
the relative imports in this file with app-level path aliases: change imports
like "../k8s-quantity.ts" and "./extended-resources.ts" (and the nearby type
imports) to use the "`@/`..." alias for files under apps/console/src; if any
imports point to workspace packages, use the "`@cozystack/`{k8s-client,ui,types}"
aliases instead. Update the import statements that reference parseQuantity,
getExtendedResourceKeys, and the nearby type imports to the appropriate alias
paths so they resolve via the project path-mapping conventions.
apps/console/src/lib/k8s-quantity.test.ts (1)

2-2: ⚡ Quick win

Use @/ alias instead of relative import in tests.

Please switch this import to the configured app alias for consistency and easier refactors.

As per coding guidelines, Use path aliases (@/for apps/console/src/,@cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/k8s-quantity.test.ts` at line 2, Replace the relative
test import with the app alias: change the import that currently brings in
parseQuantity, humanizeBytes, and humanizeCpu from "./k8s-quantity.ts" to use
the configured "`@/`..." alias for the console app (so tests import parseQuantity,
humanizeBytes, humanizeCpu via the app alias path), ensuring the three exported
symbols (parseQuantity, humanizeBytes, humanizeCpu) are resolved through the
"`@/`..." alias to comply with the workspace path-alias guideline.
apps/console/src/lib/cluster-usage/extended-resources.ts (1)

1-2: ⚡ Quick win

Switch to @/ alias imports.

Use app aliases instead of relative paths here.

As per coding guidelines, Use path aliases (@/for apps/console/src/,@cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/cluster-usage/extended-resources.ts` around lines 1 - 2,
Update the two relative imports to use the app alias root instead of
"./types.ts": import isExtendedResourceKey and the Node type from
"`@/lib/cluster-usage/types`" (so replace the current imports referencing
"./types.ts" with alias imports for the same symbols isExtendedResourceKey and
Node). Ensure the runtime import (isExtendedResourceKey) and the type-only
import (Node) both reference "`@/lib/cluster-usage/types`" to match the coding
guidelines.
apps/console/src/lib/cluster-usage/aggregate.test.ts (1)

2-3: ⚡ Quick win

Use @/ aliases for local test imports.

Please convert these relative imports to app aliases.

As per coding guidelines, Use path aliases (@/for apps/console/src/,@cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/cluster-usage/aggregate.test.ts` around lines 2 - 3,
Replace the relative test imports with app path aliases: change the import of
aggregateNodeResources from "./aggregate.ts" to "`@/lib/cluster-usage/aggregate`"
and the types import of Node, Pod, NodeMetrics from "./types.ts" to
"`@/lib/cluster-usage/types`" so the test uses the `@/` alias for apps/console/src;
keep the same exported symbols (aggregateNodeResources, Node, Pod, NodeMetrics)
when updating the import statements.
apps/console/src/hooks/useClusterUsageData.test.tsx (1)

12-15: ⚡ Quick win

Use alias imports in this test file

Please convert these relative imports to @/ aliases for consistency.

Suggested diff
-import { useClusterUsageData } from "./useClusterUsageData.tsx"
-import { nodesListFixture } from "../test-utils/fixtures/nodes.ts"
-import { podsListFixture } from "../test-utils/fixtures/pods.ts"
-import { nodeMetricsListFixture } from "../test-utils/fixtures/node-metrics.ts"
+import { useClusterUsageData } from "`@/hooks/useClusterUsageData.tsx`"
+import { nodesListFixture } from "`@/test-utils/fixtures/nodes.ts`"
+import { podsListFixture } from "`@/test-utils/fixtures/pods.ts`"
+import { nodeMetricsListFixture } from "`@/test-utils/fixtures/node-metrics.ts`"

As per coding guidelines, "Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/...".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/hooks/useClusterUsageData.test.tsx` around lines 12 - 15,
Update the test imports to use path aliases instead of relative paths: replace
the import of useClusterUsageData with "`@/hooks/useClusterUsageData`" and the
fixtures with "`@/test-utils/fixtures/nodes`", "`@/test-utils/fixtures/pods`", and
"`@/test-utils/fixtures/node-metrics`" so the symbols useClusterUsageData,
nodesListFixture, podsListFixture, and nodeMetricsListFixture are imported via
the `@/` alias for consistency with project guidelines.
apps/console/src/lib/cluster-usage/per-node.test.ts (1)

2-3: ⚡ Quick win

Align test imports with alias convention

Use @/ aliases here as well to keep imports consistent across the app.

Suggested diff
-import { derivePerNodeRows } from "./per-node.ts"
-import type { Node, Pod, NodeMetrics } from "./types.ts"
+import { derivePerNodeRows } from "`@/lib/cluster-usage/per-node.ts`"
+import type { Node, Pod, NodeMetrics } from "`@/lib/cluster-usage/types.ts`"

As per coding guidelines, "Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/...".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/cluster-usage/per-node.test.ts` around lines 2 - 3,
Update the imports in per-node.test.ts to use the project path alias style
instead of relative paths: replace the relative import of derivePerNodeRows with
an alias import from "`@/lib/cluster-usage/per-node.ts`" and replace the type
imports for Node, Pod, NodeMetrics to use "`@/lib/cluster-usage/types.ts`" (or the
appropriate "`@/`..." alias that maps to apps/console/src/) so the test uses the
same `@/` alias convention as the rest of the app.
apps/console/src/hooks/useClusterUsageData.tsx (1)

8-16: ⚡ Quick win

Replace relative local imports with @/ aliases

Please update these imports to the project alias format.

Suggested diff
-import { aggregateNodeResources } from "../lib/cluster-usage/aggregate.ts"
-import { derivePerNodeRows } from "../lib/cluster-usage/per-node.ts"
+import { aggregateNodeResources } from "`@/lib/cluster-usage/aggregate.ts`"
+import { derivePerNodeRows } from "`@/lib/cluster-usage/per-node.ts`"
 import type {
   AggregateResources,
   Node,
   NodeMetrics,
   NodeRow,
   Pod,
-} from "../lib/cluster-usage/types.ts"
+} from "`@/lib/cluster-usage/types.ts`"

As per coding guidelines, "Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/...".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/hooks/useClusterUsageData.tsx` around lines 8 - 16, Update
the relative local imports in useClusterUsageData.tsx to use the project alias
format (start with "`@/`") instead of relative paths: replace import of
aggregateNodeResources from "../lib/cluster-usage/aggregate.ts" and
derivePerNodeRows from "../lib/cluster-usage/per-node.ts" and the type imports
(AggregateResources, Node, NodeMetrics, NodeRow, Pod) from
"../lib/cluster-usage/types.ts" with equivalent "`@/`..." alias paths rooted at
apps/console/src so the module resolver uses the project's path-alias
convention.
apps/console/src/lib/cluster-usage/per-node.ts (1)

1-15: ⚡ Quick win

Use @/ aliases instead of relative imports

Please switch these imports to path aliases for consistency with project rules.

Suggested diff
-import { parseQuantity } from "../k8s-quantity.ts"
-import { formatAge } from "../status.ts"
+import { parseQuantity } from "`@/lib/k8s-quantity.ts`"
+import { formatAge } from "`@/lib/status.ts`"
 import {
   STANDARD_RESOURCE_KEYS,
   STANDARD_RESOURCE_KEY_SET,
   isExtendedResourceKey,
-} from "./types.ts"
+} from "`@/lib/cluster-usage/types.ts`"
 import type {
   Node,
   NodeMetrics,
   NodeRow,
   Pod,
   ResourceTotals,
   StandardResourceKey,
-} from "./types.ts"
+} from "`@/lib/cluster-usage/types.ts`"

As per coding guidelines, "Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/...".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/cluster-usage/per-node.ts` around lines 1 - 15, Replace
the relative imports at the top of this file with project path aliases: change
the import of parseQuantity to "`@/lib/k8s-quantity.ts`" (or the correct module
under apps/console/src), formatAge to "`@/lib/status.ts`", and the types and
constants (STANDARD_RESOURCE_KEYS, STANDARD_RESOURCE_KEY_SET,
isExtendedResourceKey and the type imports Node, NodeMetrics, NodeRow, Pod,
ResourceTotals, StandardResourceKey) to their corresponding
"`@/lib/cluster-usage/types.ts`" or appropriate "`@/`..." paths so the file uses the
"`@/`..." alias convention instead of relative paths; keep the imported symbol
names (parseQuantity, formatAge, STANDARD_RESOURCE_KEYS,
STANDARD_RESOURCE_KEY_SET, isExtendedResourceKey, Node, NodeMetrics, NodeRow,
Pod, ResourceTotals, StandardResourceKey) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsx`:
- Around line 3-5: Replace the relative import paths with project path aliases:
import ClusterUsageAggregates from
"`@/components/cluster-usage/ClusterUsageAggregates`" and use
"`@/lib/cluster-usage/types`" for AggregateResources and
"`@/hooks/useClusterUsageData`" for NodeSummary so the test uses the `@/` root
alias instead of "../../" relative paths; update the three import statements
(ClusterUsageAggregates, AggregateResources, NodeSummary) accordingly.

In `@apps/console/src/components/cluster-usage/ClusterUsageAggregates.tsx`:
- Around line 1-3: The file imports use relative paths; update the three imports
to use the project aliases: replace the local "./ResourceCard.tsx" import for
ResourceCard with the "`@/components/cluster-usage/ResourceCard`" alias, replace
"../../lib/cluster-usage/types.ts" for AggregateResources with
"`@/lib/cluster-usage/types`", and replace "../../hooks/useClusterUsageData.tsx"
for NodeSummary with "`@/hooks/useClusterUsageData`"; keep the same exported names
(ResourceCard, AggregateResources, NodeSummary) and file extensions as needed so
the imports resolve using the configured '`@/`...' root alias.

In `@apps/console/src/components/cluster-usage/ClusterUsageTable.test.tsx`:
- Around line 4-5: The test imports use relative paths; update the two import
statements that reference ClusterUsageTable and NodeRow to use the project path
alias root (start with '`@/`') instead of relative paths so they resolve via the
alias config; locate the imports for ClusterUsageTable and the NodeRow type in
ClusterUsageTable.test.tsx and replace their "../../..." style module specifiers
with equivalent "`@/`..." alias specifiers consistent with the repo's alias
mapping.

In `@apps/console/src/components/cluster-usage/ClusterUsageTable.tsx`:
- Around line 3-4: The imports use relative paths; switch them to project path
aliases: replace "../../lib/k8s-quantity.ts" with "`@/lib/k8s-quantity`" (or
"`@/lib/k8s-quantity.ts`" per convention) for the humanizeBytes and humanizeCpu
imports, and replace "../../lib/cluster-usage/types.ts" with
"`@/lib/cluster-usage/types`" for the NodeRow and ResourceTotals types; update the
import statements that reference humanizeBytes, humanizeCpu, NodeRow, and
ResourceTotals to use these "`@/`..." aliases so they follow the repo convention.
- Around line 91-108: extended-resource cells render capacity numbers even for
NotReady rows because callers only pass podsUnavailable; update all places that
call extendedCell (e.g., the current call near the shown function and the other
occurrences around the 308-311 region) to pass podsUnavailable || !row.ready so
the extendedCell sees podsUnavailable=true whenever the node/row is NotReady,
causing it to render em dashes instead of capacity-derived numbers.

In `@apps/console/src/components/cluster-usage/ResourceCard.test.tsx`:
- Line 3: Replace the relative import of the ResourceCard component in
ResourceCard.test.tsx with the project path-alias form; locate the import
referencing ResourceCard and change it from the local relative path to the
alias-style import (use the "`@/`..." alias targeting the components/cluster-usage
ResourceCard module) so the test uses the workspace path alias convention for
ResourceCard.

In `@apps/console/src/components/cluster-usage/ResourceCard.tsx`:
- Around line 1-2: The imports in ResourceCard.tsx use relative paths; replace
them with the console alias so they follow repo conventions: change the import
of humanizeBytes and humanizeCpu from "../../lib/k8s-quantity.ts" to use
"`@/lib/k8s-quantity`" and change the ResourceTotals type import from
"../../lib/cluster-usage/types.ts" to "`@/lib/cluster-usage/types`"; update any
file extensions if your build expects extension-less imports and keep the same
exported symbol names (humanizeBytes, humanizeCpu, ResourceTotals) so the rest
of ResourceCard.tsx continues to work.

In `@apps/console/src/lib/cluster-usage/aggregate.ts`:
- Around line 59-73: The aggregation loop is including terminal pods
(Succeeded/Failed) which no longer consume requested resources; inside the pods
loop (where you read pod.spec?.nodeName and iterate containers) add a check for
pod.status?.phase and skip when phase === 'Succeeded' or phase === 'Failed' so
those pods are not counted in standard[...] .requested or
extended[...].requested (the code around pods, nodeName, standard, extended,
parseQuantity should be updated accordingly).

In `@apps/console/src/lib/k8s-quantity.ts`:
- Line 9: The milli parsing in parseQuantity should guard against parseFloat
producing NaN (e.g., when s === "m") so it doesn't contaminate totals; update
the branch in parseQuantity that handles strings ending with "m" (the line with
if (s.endsWith("m")) return parseFloat(s) / 1000) to first parse the numeric
part, check isNaN(result), and return 0 (or a defined safe default) when parsing
fails, otherwise return the parsed value divided by 1000.

In `@apps/console/src/routes/ClusterUsagePage.tsx`:
- Around line 3-5: Replace the three relative route-local imports in
ClusterUsagePage.tsx with path-alias imports rooted at apps/console/src using
the '`@/`...' alias: update the import of useClusterUsageData to
'`@/hooks/useClusterUsageData.tsx`' and the imports of ClusterUsageAggregates and
ClusterUsageTable to '`@/components/cluster-usage/ClusterUsageAggregates.tsx`' and
'`@/components/cluster-usage/ClusterUsageTable.tsx`' respectively so they follow
the project aliasing convention.

In `@apps/console/src/routes/ConsolePage.tsx`:
- Line 7: Replace the relative import of ClusterUsagePage in ConsolePage.tsx
with the project alias import; locate the import statement that currently reads
import { ClusterUsagePage } from "./ClusterUsagePage.tsx" and change it to use
the workspace alias (e.g. import { ClusterUsagePage } from
"`@/routes/ClusterUsagePage`") so it follows the coding guideline to use "`@/`..."
for apps/console/src imports.

---

Nitpick comments:
In `@apps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsx`:
- Around line 47-53: The test "renders the four standard cards in order CPU,
Memory, Storage, Pods" currently uses expect.arrayContaining which only checks
presence; update the assertion to verify exact order by comparing the labels
array produced from headings (headings.map(h => h.textContent)) against the
exact ordered array ["CPU","Memory","Storage","Pods"] (e.g. use
toEqual/toStrictEqual) and ensure any whitespace/case is normalized if needed so
the order assertion fails when card order regresses; replace the
expect.arrayContaining(...) call with the strict equality assertion on labels.

In `@apps/console/src/components/cluster-usage/ClusterUsageTable.test.tsx`:
- Around line 97-100: The test currently only checks presence of GPU header
labels using the headers array in ClusterUsageTable.test.tsx; update it to
assert ordering by comparing positions rather than just containment—use the
existing headers variable and either assert headers.indexOf("nvidia.com/gpu") <
headers.indexOf("amd.com/gpu") or assert a slice/equality (e.g.,
expect(headers).toContainEqual([...]) replaced with an explicit sequence check
such as expect(headers).toEqual(expect.arrayContaining([...])) combined with
order assertion) so the test fails if the column order changes.

In `@apps/console/src/components/QuotaDisplay.tsx`:
- Line 4: Replace the relative import in QuotaDisplay.tsx with the app alias
import: change the import that currently pulls parseQuantity, humanizeBytes, and
humanizeCpu from "../lib/k8s-quantity.ts" to use "`@/lib/k8s-quantity.ts`" so the
module is resolved via the apps/console/src alias; ensure the imported symbols
(parseQuantity, humanizeBytes, humanizeCpu) remain unchanged.

In `@apps/console/src/hooks/useClusterUsageData.test.tsx`:
- Around line 12-15: Update the test imports to use path aliases instead of
relative paths: replace the import of useClusterUsageData with
"`@/hooks/useClusterUsageData`" and the fixtures with
"`@/test-utils/fixtures/nodes`", "`@/test-utils/fixtures/pods`", and
"`@/test-utils/fixtures/node-metrics`" so the symbols useClusterUsageData,
nodesListFixture, podsListFixture, and nodeMetricsListFixture are imported via
the `@/` alias for consistency with project guidelines.

In `@apps/console/src/hooks/useClusterUsageData.tsx`:
- Around line 8-16: Update the relative local imports in useClusterUsageData.tsx
to use the project alias format (start with "`@/`") instead of relative paths:
replace import of aggregateNodeResources from
"../lib/cluster-usage/aggregate.ts" and derivePerNodeRows from
"../lib/cluster-usage/per-node.ts" and the type imports (AggregateResources,
Node, NodeMetrics, NodeRow, Pod) from "../lib/cluster-usage/types.ts" with
equivalent "`@/`..." alias paths rooted at apps/console/src so the module resolver
uses the project's path-alias convention.

In `@apps/console/src/lib/cluster-usage/aggregate.test.ts`:
- Around line 2-3: Replace the relative test imports with app path aliases:
change the import of aggregateNodeResources from "./aggregate.ts" to
"`@/lib/cluster-usage/aggregate`" and the types import of Node, Pod, NodeMetrics
from "./types.ts" to "`@/lib/cluster-usage/types`" so the test uses the `@/` alias
for apps/console/src; keep the same exported symbols (aggregateNodeResources,
Node, Pod, NodeMetrics) when updating the import statements.

In `@apps/console/src/lib/cluster-usage/aggregate.ts`:
- Around line 1-3: Replace the relative imports in this file with app-level path
aliases: change imports like "../k8s-quantity.ts" and "./extended-resources.ts"
(and the nearby type imports) to use the "`@/`..." alias for files under
apps/console/src; if any imports point to workspace packages, use the
"`@cozystack/`{k8s-client,ui,types}" aliases instead. Update the import statements
that reference parseQuantity, getExtendedResourceKeys, and the nearby type
imports to the appropriate alias paths so they resolve via the project
path-mapping conventions.

In `@apps/console/src/lib/cluster-usage/extended-resources.test.ts`:
- Around line 2-3: Replace the relative imports at the top of the test with the
project path aliases: change the import of getExtendedResourceKeys from
"./extended-resources.ts" to the app alias starting with "`@/`..." that maps to
apps/console/src, and change the Node type import from "./types.ts" to the
corresponding "`@/`..." alias; ensure you use the exact symbol names
getExtendedResourceKeys and Node so the test still references the same exports
but via the alias imports per the coding guideline.

In `@apps/console/src/lib/cluster-usage/extended-resources.ts`:
- Around line 1-2: Update the two relative imports to use the app alias root
instead of "./types.ts": import isExtendedResourceKey and the Node type from
"`@/lib/cluster-usage/types`" (so replace the current imports referencing
"./types.ts" with alias imports for the same symbols isExtendedResourceKey and
Node). Ensure the runtime import (isExtendedResourceKey) and the type-only
import (Node) both reference "`@/lib/cluster-usage/types`" to match the coding
guidelines.

In `@apps/console/src/lib/cluster-usage/per-node.test.ts`:
- Around line 2-3: Update the imports in per-node.test.ts to use the project
path alias style instead of relative paths: replace the relative import of
derivePerNodeRows with an alias import from "`@/lib/cluster-usage/per-node.ts`"
and replace the type imports for Node, Pod, NodeMetrics to use
"`@/lib/cluster-usage/types.ts`" (or the appropriate "`@/`..." alias that maps to
apps/console/src/) so the test uses the same `@/` alias convention as the rest
of the app.

In `@apps/console/src/lib/cluster-usage/per-node.ts`:
- Around line 1-15: Replace the relative imports at the top of this file with
project path aliases: change the import of parseQuantity to
"`@/lib/k8s-quantity.ts`" (or the correct module under apps/console/src),
formatAge to "`@/lib/status.ts`", and the types and constants
(STANDARD_RESOURCE_KEYS, STANDARD_RESOURCE_KEY_SET, isExtendedResourceKey and
the type imports Node, NodeMetrics, NodeRow, Pod, ResourceTotals,
StandardResourceKey) to their corresponding "`@/lib/cluster-usage/types.ts`" or
appropriate "`@/`..." paths so the file uses the "`@/`..." alias convention instead
of relative paths; keep the imported symbol names (parseQuantity, formatAge,
STANDARD_RESOURCE_KEYS, STANDARD_RESOURCE_KEY_SET, isExtendedResourceKey, Node,
NodeMetrics, NodeRow, Pod, ResourceTotals, StandardResourceKey) unchanged.

In `@apps/console/src/lib/k8s-quantity.test.ts`:
- Line 2: Replace the relative test import with the app alias: change the import
that currently brings in parseQuantity, humanizeBytes, and humanizeCpu from
"./k8s-quantity.ts" to use the configured "`@/`..." alias for the console app (so
tests import parseQuantity, humanizeBytes, humanizeCpu via the app alias path),
ensuring the three exported symbols (parseQuantity, humanizeBytes, humanizeCpu)
are resolved through the "`@/`..." alias to comply with the workspace path-alias
guideline.

In `@apps/console/src/routes/ClusterUsagePage.test.tsx`:
- Around line 146-149: Replace the direct DOM access using
document.querySelectorAll in ClusterUsagePage.test.tsx (the tooltipNodes
variable) with Testing Library queries: import/use screen from
`@testing-library/react` and call screen.getAllByTitle("Requires cluster-wide pod
read access") (or another appropriate accessible query such as
getAllByText/getAllByRole if the tooltip content is rendered accessibly) and
assert on that result instead of using document.querySelectorAll to make the
test more robust and aligned with Testing Library practices.

In `@apps/console/src/routes/sidebar-sections.test.tsx`:
- Around line 106-113: Test uses an unstable setTimeout flush; replace the
artificial delay with React Testing Library's waitFor and assert a concrete
condition: after rendering with makeClient({ ssar: new K8sApiError(500, "boom")
}) and wrapper from makeWrapper, remove the new Promise/setTimeout and instead
call await waitFor(() => expect(findItem(result.current, "Cluster
Usage")).toBeUndefined()) so it waits for state updates from
useConsoleSidebarSections to settle; keep renderHook(...) and references to
useConsoleSidebarSections, makeClient, makeWrapper, and findItem.
- Around line 88-95: Remove the fragile setTimeout flush and instead await a
waitFor that asserts the absence of the menu item; specifically, replace the new
Promise(setTimeout...) block with an await waitFor(...) that checks
findItem(result.current, "Cluster Usage") is undefined, using the existing
findItem helper and result.current so the test reliably waits for the SSAR query
to settle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e0099d5-ec12-4371-bfdf-57a48f755ac8

📥 Commits

Reviewing files that changed from the base of the PR and between 92ff040 and da6961c.

📒 Files selected for processing (38)
  • apps/console/src/__tests__/k8s-client/provider.test.tsx
  • apps/console/src/__tests__/k8s-client/useApiGroupAvailable.test.tsx
  • apps/console/src/__tests__/k8s-client/useSelfSubjectAccessReview.test.tsx
  • apps/console/src/components/QuotaDisplay.tsx
  • apps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsx
  • apps/console/src/components/cluster-usage/ClusterUsageAggregates.tsx
  • apps/console/src/components/cluster-usage/ClusterUsageTable.test.tsx
  • apps/console/src/components/cluster-usage/ClusterUsageTable.tsx
  • apps/console/src/components/cluster-usage/ResourceCard.test.tsx
  • apps/console/src/components/cluster-usage/ResourceCard.tsx
  • apps/console/src/hooks/useClusterUsageData.test.tsx
  • apps/console/src/hooks/useClusterUsageData.tsx
  • apps/console/src/lib/cluster-usage/aggregate.test.ts
  • apps/console/src/lib/cluster-usage/aggregate.ts
  • apps/console/src/lib/cluster-usage/extended-resources.test.ts
  • apps/console/src/lib/cluster-usage/extended-resources.ts
  • apps/console/src/lib/cluster-usage/per-node.test.ts
  • apps/console/src/lib/cluster-usage/per-node.ts
  • apps/console/src/lib/cluster-usage/types.ts
  • apps/console/src/lib/k8s-quantity.test.ts
  • apps/console/src/lib/k8s-quantity.ts
  • apps/console/src/routes/ClusterUsagePage.test.tsx
  • apps/console/src/routes/ClusterUsagePage.tsx
  • apps/console/src/routes/ConsolePage.routing.test.tsx
  • apps/console/src/routes/ConsolePage.tsx
  • apps/console/src/routes/sidebar-sections.test.tsx
  • apps/console/src/routes/sidebar-sections.tsx
  • apps/console/src/test-utils/fixtures/node-metrics.ts
  • apps/console/src/test-utils/fixtures/nodes.ts
  • apps/console/src/test-utils/fixtures/pods.ts
  • apps/console/src/test-utils/mock-k8s-client.ts
  • apps/console/src/test-utils/render.test.tsx
  • apps/console/src/test-utils/render.tsx
  • packages/k8s-client/src/client.ts
  • packages/k8s-client/src/index.ts
  • packages/k8s-client/src/provider.tsx
  • packages/k8s-client/src/useApiGroupAvailable.ts
  • packages/k8s-client/src/useSelfSubjectAccessReview.ts

Comment on lines +3 to +5
import { ClusterUsageAggregates } from "./ClusterUsageAggregates.tsx"
import type { AggregateResources } from "../../lib/cluster-usage/types.ts"
import type { NodeSummary } from "../../hooks/useClusterUsageData.tsx"
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use aliases in test imports

Use @/ aliases for these imports instead of relative paths.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsx`
around lines 3 - 5, Replace the relative import paths with project path aliases:
import ClusterUsageAggregates from
"`@/components/cluster-usage/ClusterUsageAggregates`" and use
"`@/lib/cluster-usage/types`" for AggregateResources and
"`@/hooks/useClusterUsageData`" for NodeSummary so the test uses the `@/` root
alias instead of "../../" relative paths; update the three import statements
(ClusterUsageAggregates, AggregateResources, NodeSummary) accordingly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the relative import here for consistency: every file under apps/console/src currently uses relative intra-app imports and none use the @/ alias yet. The repo guideline only forbids reaching into ../../packages/... for workspace deps, which this PR already avoids via the @cozystack/* entry points. Switching only the files in this PR would split the convention, so I'd rather migrate to @/ repo-wide in a dedicated change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lexfrei, that's a fair and well-reasoned point. Keeping the convention consistent across the existing codebase until a dedicated repo-wide migration makes total sense — a piecemeal switch would just create two styles in parallel. I'll defer to your plan.

Would you like me to open a follow-up GitHub issue to track the repo-wide migration to @/ aliases in apps/console/src?


✏️ Learnings added
Learnt from: lexfrei
Repo: cozystack/cozystack-ui PR: 18
File: apps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsx:3-5
Timestamp: 2026-05-27T18:35:02.514Z
Learning: In the `apps/console/src` directory of the `cozystack/cozystack-ui` repository, all intra-app imports currently use relative paths (e.g. `./`, `../`) rather than the `@/` path alias. The `@/` alias migration is planned as a dedicated repo-wide change; do not flag relative imports within `apps/console/src` as violations until that migration is complete. The only import guideline currently enforced is that cross-workspace deps must use `cozystack/*` entry points instead of `../../packages/...` relative reaches.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +1 to +3
import { ResourceCard } from "./ResourceCard.tsx"
import type { AggregateResources } from "../../lib/cluster-usage/types.ts"
import type { NodeSummary } from "../../hooks/useClusterUsageData.tsx"
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Normalize imports to configured aliases

Please replace these new relative imports with @/ aliases.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/cluster-usage/ClusterUsageAggregates.tsx` around
lines 1 - 3, The file imports use relative paths; update the three imports to
use the project aliases: replace the local "./ResourceCard.tsx" import for
ResourceCard with the "`@/components/cluster-usage/ResourceCard`" alias, replace
"../../lib/cluster-usage/types.ts" for AggregateResources with
"`@/lib/cluster-usage/types`", and replace "../../hooks/useClusterUsageData.tsx"
for NodeSummary with "`@/hooks/useClusterUsageData`"; keep the same exported names
(ResourceCard, AggregateResources, NodeSummary) and file extensions as needed so
the imports resolve using the configured '`@/`...' root alias.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the relative import here for consistency: every file under apps/console/src currently uses relative intra-app imports and none use the @/ alias yet. The repo guideline only forbids reaching into ../../packages/... for workspace deps, which this PR already avoids via the @cozystack/* entry points. Switching only the files in this PR would split the convention, so I'd rather migrate to @/ repo-wide in a dedicated change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lexfrei, that makes sense — a partial migration would create inconsistency within the codebase. I'll note the current convention for future reviews.

Would you like me to open a follow-up GitHub issue to track the repo-wide migration to @/ aliases in apps/console/src?

(∩`-´)⊃━☆゚.*・。


✏️ Learnings added
Learnt from: lexfrei
Repo: cozystack/cozystack-ui PR: 18
File: apps/console/src/components/cluster-usage/ClusterUsageAggregates.tsx:1-3
Timestamp: 2026-05-27T18:35:04.142Z
Learning: In `apps/console/src`, all intra-app imports currently use relative paths (e.g., `./Foo`, `../../lib/bar`). The `@/` alias (mapping to `apps/console/src/`) is configured but not yet adopted anywhere in the codebase. The coding guideline restriction on relative imports applies only to reaching into `../../packages/...` for workspace dependencies — those must use `cozystack/*` package entry points. A repo-wide migration to `@/` aliases is planned as a separate dedicated change and should not be done piecemeal on individual PRs.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +3 to +4
import { humanizeBytes, humanizeCpu } from "../../lib/k8s-quantity.ts"
import type { NodeRow, ResourceTotals } from "../../lib/cluster-usage/types.ts"
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use @/ aliases for local imports

Replace these relative imports with alias-based imports to match repo conventions.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/cluster-usage/ClusterUsageTable.tsx` around lines
3 - 4, The imports use relative paths; switch them to project path aliases:
replace "../../lib/k8s-quantity.ts" with "`@/lib/k8s-quantity`" (or
"`@/lib/k8s-quantity.ts`" per convention) for the humanizeBytes and humanizeCpu
imports, and replace "../../lib/cluster-usage/types.ts" with
"`@/lib/cluster-usage/types`" for the NodeRow and ResourceTotals types; update the
import statements that reference humanizeBytes, humanizeCpu, NodeRow, and
ResourceTotals to use these "`@/`..." aliases so they follow the repo convention.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the relative import here for consistency: every file under apps/console/src currently uses relative intra-app imports and none use the @/ alias yet. The repo guideline only forbids reaching into ../../packages/... for workspace deps, which this PR already avoids via the @cozystack/* entry points. Switching only the files in this PR would split the convention, so I'd rather migrate to @/ repo-wide in a dedicated change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lexfrei, that's a fair and well-reasoned call. Consistent conventions within the codebase outweigh a partial migration, and this PR already correctly avoids the cross-package relative path anti-pattern. I'll record this for future reviews.

(。•ᴗ•。)🐰


✏️ Learnings added
Learnt from: lexfrei
Repo: cozystack/cozystack-ui PR: 18
File: apps/console/src/components/cluster-usage/ClusterUsageTable.tsx:3-4
Timestamp: 2026-05-27T18:35:09.145Z
Learning: In `apps/console/src` (cozystack-ui), all intra-app imports currently use relative paths (e.g., `../../lib/k8s-quantity.ts`). The `@/` alias for `apps/console/src/` is not yet adopted anywhere in the codebase. The repo guideline against `../../packages/...` applies only to cross-workspace imports reaching into workspace packages directly; intra-app relative imports are acceptable and are the current convention. A repo-wide `@/` alias migration is planned as a separate, dedicated change. Do not flag relative intra-app imports in `apps/console/src` as violations of the path-alias guideline.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread apps/console/src/components/cluster-usage/ClusterUsageTable.tsx Outdated
Comment on lines +1 to +2
import { humanizeBytes, humanizeCpu } from "../../lib/k8s-quantity.ts"
import type { ResourceTotals } from "../../lib/cluster-usage/types.ts"
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use @/ aliases for console-local imports

These new relative imports should use the configured path alias to match repo import conventions.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/cluster-usage/ResourceCard.tsx` around lines 1 -
2, The imports in ResourceCard.tsx use relative paths; replace them with the
console alias so they follow repo conventions: change the import of
humanizeBytes and humanizeCpu from "../../lib/k8s-quantity.ts" to use
"`@/lib/k8s-quantity`" and change the ResourceTotals type import from
"../../lib/cluster-usage/types.ts" to "`@/lib/cluster-usage/types`"; update any
file extensions if your build expects extension-less imports and keep the same
exported symbol names (humanizeBytes, humanizeCpu, ResourceTotals) so the rest
of ResourceCard.tsx continues to work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the relative import here for consistency: every file under apps/console/src currently uses relative intra-app imports and none use the @/ alias yet. The repo guideline only forbids reaching into ../../packages/... for workspace deps, which this PR already avoids via the @cozystack/* entry points. Switching only the files in this PR would split the convention, so I'd rather migrate to @/ repo-wide in a dedicated change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lexfrei, that's a reasonable call — a partial migration mid-PR would introduce inconsistency rather than remove it. I'll update the knowledge base to reflect the current convention so this isn't flagged again in the future.


✏️ Learnings added
Learnt from: lexfrei
Repo: cozystack/cozystack-ui PR: 18
File: apps/console/src/components/cluster-usage/ResourceCard.tsx:1-2
Timestamp: 2026-05-27T18:35:10.689Z
Learning: In the `apps/console` app (cozystack/cozystack-ui), intra-app imports under `apps/console/src/` use relative paths (e.g., `../../lib/k8s-quantity`). The `@/` alias for `apps/console/src/` is not yet adopted anywhere in the codebase. Do NOT flag relative intra-app imports as a style issue. The only import guideline that currently applies is: avoid `../../packages/...` paths for workspace dependencies — those must use `cozystack/*` package entry points instead. A repo-wide migration to `@/` aliases is deferred to a dedicated future change.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread apps/console/src/lib/cluster-usage/aggregate.ts
Comment thread apps/console/src/lib/k8s-quantity.ts Outdated
Comment on lines +3 to +5
import { useClusterUsageData } from "../hooks/useClusterUsageData.tsx"
import { ClusterUsageAggregates } from "../components/cluster-usage/ClusterUsageAggregates.tsx"
import { ClusterUsageTable } from "../components/cluster-usage/ClusterUsageTable.tsx"
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use path aliases for route-local imports

These new relative imports should be migrated to @/ aliases.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/routes/ClusterUsagePage.tsx` around lines 3 - 5, Replace the
three relative route-local imports in ClusterUsagePage.tsx with path-alias
imports rooted at apps/console/src using the '`@/`...' alias: update the import of
useClusterUsageData to '`@/hooks/useClusterUsageData.tsx`' and the imports of
ClusterUsageAggregates and ClusterUsageTable to
'`@/components/cluster-usage/ClusterUsageAggregates.tsx`' and
'`@/components/cluster-usage/ClusterUsageTable.tsx`' respectively so they follow
the project aliasing convention.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the relative import here for consistency: every file under apps/console/src currently uses relative intra-app imports and none use the @/ alias yet. The repo guideline only forbids reaching into ../../packages/... for workspace deps, which this PR already avoids via the @cozystack/* entry points. Switching only the files in this PR would split the convention, so I'd rather migrate to @/ repo-wide in a dedicated change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lexfrei, that's a fair point — partial adoption would fragment the convention more than it helps. A repo-wide migration as a dedicated change makes sense. I'll note this for future reviews.


✏️ Learnings added
Learnt from: lexfrei
Repo: cozystack/cozystack-ui PR: 18
File: apps/console/src/routes/ClusterUsagePage.tsx:3-5
Timestamp: 2026-05-27T18:35:11.669Z
Learning: In the `cozystack/cozystack-ui` repository, all files under `apps/console/src` currently use relative intra-app imports. No file uses the `@/` alias yet. The coding guideline forbidding `../../packages/...` paths only applies to workspace dependency imports (which should use `cozystack/*` entry points). Do not flag relative intra-app imports (e.g., `../hooks/...`, `../components/...`) in `apps/console/src` as guideline violations — a repo-wide migration to `@/` aliases is planned as a separate dedicated change.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

import { ModulesPage } from "./ModulesPage.tsx"
import { ExternalIpsPage } from "./ExternalIpsPage.tsx"
import { InfoRedirect } from "./InfoRedirect.tsx"
import { ClusterUsagePage } from "./ClusterUsagePage.tsx"
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use alias import for the new route component

Please import ClusterUsagePage via @/routes/... instead of a relative path.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Use path aliases (@/ for apps/console/src/, @cozystack/{k8s-client,ui,types} for workspace deps) instead of relative imports like ../../packages/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/routes/ConsolePage.tsx` at line 7, Replace the relative
import of ClusterUsagePage in ConsolePage.tsx with the project alias import;
locate the import statement that currently reads import { ClusterUsagePage }
from "./ClusterUsagePage.tsx" and change it to use the workspace alias (e.g.
import { ClusterUsagePage } from "`@/routes/ClusterUsagePage`") so it follows the
coding guideline to use "`@/`..." for apps/console/src imports.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the relative import here for consistency: every file under apps/console/src currently uses relative intra-app imports and none use the @/ alias yet. The repo guideline only forbids reaching into ../../packages/... for workspace deps, which this PR already avoids via the @cozystack/* entry points. Switching only the files in this PR would split the convention, so I'd rather migrate to @/ repo-wide in a dedicated change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lexfrei, that's a fair point — a piecemeal switch would create two competing styles in the same directory. I'll note this for future reviews.

Would you like me to open a follow-up issue to track the repo-wide @/ alias migration?


✏️ Learnings added
Learnt from: lexfrei
Repo: cozystack/cozystack-ui PR: 18
File: apps/console/src/routes/ConsolePage.tsx:7-7
Timestamp: 2026-05-27T18:35:15.880Z
Learning: In `apps/console/src` (cozystack-ui), all intra-app imports currently use relative paths (e.g., `./ClusterUsagePage.tsx`). The `@/` alias is not yet adopted anywhere in that directory. The coding guideline requiring `@/` applies to future migrations; the only strictly enforced rule today is to avoid reaching into `../../packages/...` for workspace dependencies (those must use `cozystack/*` entry points). Do not flag relative imports within `apps/console/src` as violations until a dedicated repo-wide alias migration is completed.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

A bare suffix such as "m" parsed to parseFloat("m")/1000 = NaN, which
then propagated into aggregated cluster totals and the percentages
derived from them. Parse the numeric part once and fall back to 0 when
it is not finite, for every suffix branch rather than just milli.

Address review feedback on apps/console/src/lib/k8s-quantity.ts.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
Succeeded/Failed pods linger in API list responses but no longer hold
schedulable requests. Counting their container requests inflated the
cluster requested totals; skip pods whose status.phase is Succeeded or
Failed.

Address review feedback on apps/console/src/lib/cluster-usage/aggregate.ts.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
CPU and memory cells already render an em dash when a node is NotReady,
but the extended-resource cells only checked podsUnavailable and kept
showing capacity-derived numbers. Pass row.ready into extendedCell so
NotReady nodes render an em dash for extended resources too.

Address review feedback on apps/console/src/components/cluster-usage/ClusterUsageTable.tsx.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
The standard-card test used arrayContaining and the extended-column
test used toContain, so neither actually verified the rendered order.
Pin the exact card order (CPU, Memory, Storage, Pods) and assert the
extended columns follow extendedKeys order via their header indices.

Address review feedback on the cluster-usage component tests.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
…e tests

The deny and error cases flushed with an arbitrary setTimeout before
asserting the gated entry was absent, which is timing-dependent and can
pass while the SSAR query is still in flight. Wait on the SSAR request
having fired plus the entry being absent so the assertion is settled and
meaningful.

Address review feedback on apps/console/src/routes/sidebar-sections.test.tsx.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
Replace document.querySelectorAll lookups for the pods-unavailable
tooltip with screen.getAllByTitle so the assertions go through Testing
Library's accessible queries and are less brittle to DOM structure.

Address review feedback on the cluster-usage page and aggregates tests.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei
Copy link
Copy Markdown
Contributor Author

Also addressed the nitpicks: the two setTimeout flushes in the sidebar gate tests now wait on a settled condition (b06c208), the standard-card and extended-column order assertions are now exact (f90314b), and the pods-unavailable tooltip lookups use getAllByTitle instead of document.querySelectorAll (a4dc332). I'm leaving the relative-import nitpicks as-is for the reason noted on the inline threads.

@androndo Andrey Kolkov (androndo) merged commit 4570d44 into main May 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/console Issues or PRs related to apps/console — routes, detail pages, marketplace, command palette kind/feature Categorizes issue or PR as related to a new feature size/XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Cluster Usage admin page with discovery-driven resource display

2 participants