feat(console): add Cluster Usage admin page#18
Conversation
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>
|
Warning Review limit reached
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughThis 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. ChangesCluster Usage Admin Page
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.
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>
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (15)
apps/console/src/routes/sidebar-sections.test.tsx (2)
106-113: ⚡ Quick winReplace
setTimeoutflush withwaitForand a meaningful assertion.Same issue as the previous test—using
setTimeoutcreates 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 winReplace
setTimeoutflush withwaitForand a meaningful assertion.Using
setTimeout(r, 0)to flush the async queue creates a timing dependency and is fragile. Testing Library'swaitForwith 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 winPrefer Testing Library queries over direct DOM access.
Using
document.querySelectorAllbypasses 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 winThe “in order” test does not actually verify order
arrayContainingonly 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 winExtended-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 winUse
@/lib/k8s-quantity.tsalias 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 winUse 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 winReplace 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 winUse
@/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 winSwitch 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 winUse
@/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 winUse 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 (
@/forapps/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 winAlign 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 (
@/forapps/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 winReplace relative local imports with
@/aliasesPlease 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 (
@/forapps/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 winUse
@/aliases instead of relative importsPlease 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 (
@/forapps/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
📒 Files selected for processing (38)
apps/console/src/__tests__/k8s-client/provider.test.tsxapps/console/src/__tests__/k8s-client/useApiGroupAvailable.test.tsxapps/console/src/__tests__/k8s-client/useSelfSubjectAccessReview.test.tsxapps/console/src/components/QuotaDisplay.tsxapps/console/src/components/cluster-usage/ClusterUsageAggregates.test.tsxapps/console/src/components/cluster-usage/ClusterUsageAggregates.tsxapps/console/src/components/cluster-usage/ClusterUsageTable.test.tsxapps/console/src/components/cluster-usage/ClusterUsageTable.tsxapps/console/src/components/cluster-usage/ResourceCard.test.tsxapps/console/src/components/cluster-usage/ResourceCard.tsxapps/console/src/hooks/useClusterUsageData.test.tsxapps/console/src/hooks/useClusterUsageData.tsxapps/console/src/lib/cluster-usage/aggregate.test.tsapps/console/src/lib/cluster-usage/aggregate.tsapps/console/src/lib/cluster-usage/extended-resources.test.tsapps/console/src/lib/cluster-usage/extended-resources.tsapps/console/src/lib/cluster-usage/per-node.test.tsapps/console/src/lib/cluster-usage/per-node.tsapps/console/src/lib/cluster-usage/types.tsapps/console/src/lib/k8s-quantity.test.tsapps/console/src/lib/k8s-quantity.tsapps/console/src/routes/ClusterUsagePage.test.tsxapps/console/src/routes/ClusterUsagePage.tsxapps/console/src/routes/ConsolePage.routing.test.tsxapps/console/src/routes/ConsolePage.tsxapps/console/src/routes/sidebar-sections.test.tsxapps/console/src/routes/sidebar-sections.tsxapps/console/src/test-utils/fixtures/node-metrics.tsapps/console/src/test-utils/fixtures/nodes.tsapps/console/src/test-utils/fixtures/pods.tsapps/console/src/test-utils/mock-k8s-client.tsapps/console/src/test-utils/render.test.tsxapps/console/src/test-utils/render.tsxpackages/k8s-client/src/client.tspackages/k8s-client/src/index.tspackages/k8s-client/src/provider.tsxpackages/k8s-client/src/useApiGroupAvailable.tspackages/k8s-client/src/useSelfSubjectAccessReview.ts
| import { ClusterUsageAggregates } from "./ClusterUsageAggregates.tsx" | ||
| import type { AggregateResources } from "../../lib/cluster-usage/types.ts" | ||
| import type { NodeSummary } from "../../hooks/useClusterUsageData.tsx" |
There was a problem hiding this comment.
🛠️ 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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.
| import { ResourceCard } from "./ResourceCard.tsx" | ||
| import type { AggregateResources } from "../../lib/cluster-usage/types.ts" | ||
| import type { NodeSummary } from "../../hooks/useClusterUsageData.tsx" |
There was a problem hiding this comment.
🛠️ 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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.
| import { humanizeBytes, humanizeCpu } from "../../lib/k8s-quantity.ts" | ||
| import type { NodeRow, ResourceTotals } from "../../lib/cluster-usage/types.ts" |
There was a problem hiding this comment.
🛠️ 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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.
| import { humanizeBytes, humanizeCpu } from "../../lib/k8s-quantity.ts" | ||
| import type { ResourceTotals } from "../../lib/cluster-usage/types.ts" |
There was a problem hiding this comment.
🛠️ 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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.
| import { useClusterUsageData } from "../hooks/useClusterUsageData.tsx" | ||
| import { ClusterUsageAggregates } from "../components/cluster-usage/ClusterUsageAggregates.tsx" | ||
| import { ClusterUsageTable } from "../components/cluster-usage/ClusterUsageTable.tsx" |
There was a problem hiding this comment.
🛠️ 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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" |
There was a problem hiding this comment.
🛠️ 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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>
|
Also addressed the nitpicks: the two |
What this PR does
Adds a new admin page under
Console → Administration → Cluster Usagethat surfaces cluster-scoped resource consumption — CPU, Memory, ephemeral-storage, Pods and any extended resource discovered innode.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
SelfSubjectAccessReviewonnodes 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
ClusterRoleandClusterRoleBindingto the dashboard chart, grantingcozystack-cluster-admincluster-wide read onnodes,pods, andmetrics.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:
node.status.capacityandallocatablefor 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./apisdiscovery formetrics.k8s.io. If present, NodeMetrics is polled on a 30-second cadence (matching the default--metric-resolutionof 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.SelfSubjectAccessReviewgates each section so a partially-equipped cluster does not render error states (pods-list denied → Requested cells render an em dash with aRequires cluster-wide pod read accesstooltip; metrics 403 → Used overlay simply disappears; nodes 403 → the spec'd permission-denied block).New primitives added to
@cozystack/k8s-clientfor reuse outside this page:K8sProvideraccepts an optionalclientprop for test injection.K8sClient.getApiGroups()plususeApiGroupAvailable(groupName)— single cached/apislookup 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)
Tests
207 tests cover: K8sProvider injection,
useApiGroupAvailablecaching 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