feat(dis-console-ui): fleet console web UI over the dis-console API - #3868
Conversation
aeaa10e to
9d68610
Compare
|
Warning Review limit reached
Next review available in: 20 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (93)
📝 WalkthroughWalkthroughChangesThe PR adds the DIS Console UI as a Bun/Vite React application. It includes mock and HTTP fleet APIs, resource and syncroot views, release and workload displays, a Bun production server, Docker and Kubernetes deployment, release configuration, and CI workflows. DIS Console UI
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 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 |
9d68610 to
918afc9
Compare
There was a problem hiding this comment.
Actionable comments posted: 18
🧹 Nitpick comments (23)
services/dis-console-ui/src/components/DisResourcesView.tsx (1)
9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRead the Azure portal tenant from runtime configuration.
import.meta.env.VITE_AZURE_PORTAL_TENANTis inlined at build time. The same container image then carries one fixed tenant, so Portal deep links break when the image is deployed against another tenant. The runtime/config.jsmechanism already carries environment-specific values; add the tenant there and fall back to the build-time value.As per coding guidelines: "live API configuration is selected through runtime
/config.jsandDIS_CONSOLE_API".🤖 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 `@services/dis-console-ui/src/components/DisResourcesView.tsx` at line 9, Update the tenant configuration used by DisResourcesView so it first reads the Azure portal tenant from the runtime /config.js configuration, then falls back to import.meta.env.VITE_AZURE_PORTAL_TENANT when runtime configuration is absent. Add the tenant to the existing runtime configuration contract and preserve the current undefined fallback behavior.Source: Coding guidelines
services/dis-console-ui/src/components/ReleasesBrowser.tsx (1)
27-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the duplicated
firstSeenLabelformatter intosrc/lib. Both files define an identical pure date formatter. The shared root cause is domain formatting logic living in components, so the two copies can diverge and neither is unit tested.
services/dis-console-ui/src/components/ReleasesBrowser.tsx#L27-L33: delete the localfirstSeenLabeland import the shared helper fromsrc/lib.services/dis-console-ui/src/components/ReleaseDialog.tsx#L22-L28: delete the localfirstSeenLabeland import the same shared helper.Add the helper to a
src/libmodule with unit tests covering the missing and invalid ISO cases.As per coding guidelines: "Keep domain logic in
src/libpure and React-free, and protect the data transforms with unit tests".🤖 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 `@services/dis-console-ui/src/components/ReleasesBrowser.tsx` around lines 27 - 33, The duplicated firstSeenLabel formatter must be centralized in a pure, React-free src/lib helper and covered by unit tests for missing and invalid ISO values. In services/dis-console-ui/src/components/ReleasesBrowser.tsx lines 27-33, delete the local firstSeenLabel and import the shared helper; make the same change in services/dis-console-ui/src/components/ReleaseDialog.tsx lines 22-28. Add the shared formatter to an appropriate src/lib module while preserving its existing output behavior.Source: Coding guidelines
services/dis-console-ui/src/components/ClustersTable.tsx (1)
5-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove
relativeintosrc/liband cover it with unit tests.
relativeis pure formatting logic with several branches and no test coverage. Asrc/libmodule keeps it React-free and testable, and lets other views reuse it.As per coding guidelines: "Keep domain logic in
src/libpure and React-free, and protect the data transforms with unit tests".🤖 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 `@services/dis-console-ui/src/components/ClustersTable.tsx` around lines 5 - 16, Move the pure relative-time formatter from the ClustersTable component into a React-free module under src/lib, then import and use it from ClustersTable instead of the local relative function. Add unit tests covering empty or invalid dates, seconds, minutes, hours, and days, including the relevant boundary transitions.Source: Coding guidelines
services/dis-console-ui/src/styles.css (1)
623-649: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMerge the duplicate
.map-noderule.
.map-nodeis declared at Line 623 and again at Line 647 forposition: relative. Move that declaration into the first rule.🤖 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 `@services/dis-console-ui/src/styles.css` around lines 623 - 649, Merge the duplicate .map-node rules by moving position: relative into the initial .map-node declaration, then remove the later standalone .map-node block while preserving all existing styles.services/dis-console-ui/src/components/SyncrootMap.tsx (1)
148-163: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winCapture the pointer during panning.
onPointerMoveandonPointerUponly fire while the pointer stays over the SVG. If the user drags past the map edge and releases there, the SVG never receivespointerupanddrag.currentkeepsmoved: trueuntil the nextpointerdown. Pointer capture keeps the gesture attached to the element.♻️ Proposed refactor
onPointerDown={(e) => { + e.currentTarget.setPointerCapture(e.pointerId); drag.current = { x: e.clientX, y: e.clientY, vb: view, moved: false }; }}🤖 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 `@services/dis-console-ui/src/components/SyncrootMap.tsx` around lines 148 - 163, Update the panning handlers in SyncrootMap so the SVG captures the active pointer during onPointerDown and releases capture when the gesture ends in onPointerUp. Use the event’s pointerId and preserve the existing drag.current reset, ensuring pointer movement and release continue to reach the SVG after the pointer leaves its bounds.services/dis-console-ui/src/components/SyncrootsView.tsx (1)
58-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider runtime configuration for the Grafana and Azure portal values.
import.meta.env.VITE_*values are inlined at build time. The same container image then cannot point at a different Grafana per environment. The project already loads runtime configuration from/config.js. Reading these two values from that runtime configuration keeps one image usable in every environment.As per path instructions for
services/dis-console-ui/public/config.js: "live API configuration is selected through runtime/config.jsandDIS_CONSOLE_API".🤖 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 `@services/dis-console-ui/src/components/SyncrootsView.tsx` around lines 58 - 59, Replace the build-time import.meta.env values used for GRAFANA and PORTAL_TENANT with the corresponding runtime configuration values loaded from /config.js, following the existing runtime configuration pattern and naming used for DIS_CONSOLE_API. Preserve the current trailing-slash removal for GRAFANA and undefined fallback behavior for the portal tenant.Source: Path instructions
services/dis-console-ui/src/components/LeftNav.tsx (1)
85-93: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueBuild LeftNav hrefs with
routeHash.These anchors are page navigation, and every
Sectionid is a valid bare-section route. Usehref={routeHash({ view: id })}so the nav stays synchronized if the route hash format changes.♻️ Proposed refactor
+import { routeHash } from '../lib/route';<a className="leftnav__item" aria-current={active === id ? 'page' : undefined} title={collapsed ? label : hint} - href={`#/${id}`} + href={routeHash({ view: id })} >🤖 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 `@services/dis-console-ui/src/components/LeftNav.tsx` around lines 85 - 93, Update the navigation anchor in LeftNav to build its href with the existing routeHash helper, passing the section id as the view value, instead of interpolating a hash manually. Preserve the current label, active-state, and other anchor behavior.Source: Coding guidelines
services/dis-console-ui/src/lib/artifacts.ts (2)
43-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse the status mapping instead of duplicating it.
artifactStatusrepeats the mapping instatusOf(src/lib/flux.tsLines 130-143). Two copies can drift when a new Ready value or status appears.statusOfonly readsreadyandsuspended, so widen its parameter and delegate.♻️ Suggested change
In
src/lib/flux.ts:-export function statusOf(r: Resource | undefined): DeployStatus { +export function statusOf(r: Pick<Resource, 'ready' | 'suspended'> | undefined): DeployStatus {In
src/lib/artifacts.ts:-export function artifactStatus(a: Pick<Artifact, 'ready' | 'suspended'>): DeployStatus { - if (a.suspended) return 'suspended'; - switch (a.ready) { - case 'True': - return 'healthy'; - case 'False': - return 'failed'; - case 'Unknown': - return 'reconciling'; - default: - return 'unknown'; - } -} +export function artifactStatus(a: Pick<Artifact, 'ready' | 'suspended'>): DeployStatus { + return statusOf(a); +}Add
statusOfto the existing./fluximport.🤖 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 `@services/dis-console-ui/src/lib/artifacts.ts` around lines 43 - 55, Update statusOf in flux.ts to accept an object containing only ready and suspended, preserving its existing status mapping, then replace the duplicated logic in artifactStatus with delegation to statusOf and add the statusOf import from ./flux.
175-205: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRepeated full rescans in the
appliedByclosure.deployedBySyncrootrecomputes its result with a fixed-point loop that rescans every resource of the cluster in each round, and callers invoke it once per cell or per environment. The shared root cause is the missing owner index.
services/dis-console-ui/src/lib/artifacts.ts#L175-L205: build aMapfrom theappliedBykey to resources once, then walk it as a queue seeded with the root Kustomizations; keep roots excluded and let only Kustomizations extend the frontier.services/dis-console-ui/src/lib/workloads.ts#L176-L190: after the indexing fix, no change is required here; if profiling still shows cost, compute the closure once per cluster and reuse it across environments.🤖 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 `@services/dis-console-ui/src/lib/artifacts.ts` around lines 175 - 205, Update deployedBySyncroot in services/dis-console-ui/src/lib/artifacts.ts (lines 175-205) to build a Map from each appliedBy namespace/name key to its resources once, then traverse matching resources with a queue seeded by root Kustomizations; exclude the roots from the result and only let discovered Kustomizations extend the owner frontier. No direct change is required in services/dis-console-ui/src/lib/workloads.ts (lines 176-190); the indexing fix addresses the issue there.services/dis-console-ui/src/lib/mapLayout.ts (1)
41-56: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReturn pixels from the cycle guard.
placereturns a y coordinate in pixels, but the cycle guard returnsnextRow, a row index. The branch is unreachable today becausekidsfilters out seen ids. If that filter changes, the mixed unit produces wrong parent centering.♻️ Suggested change
- if (seen.has(id)) return nextRow; // cycle guard — should not happen + if (seen.has(id)) return pos.get(id)?.y ?? nextRow * rowH; // cycle guard — should not happen🤖 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 `@services/dis-console-ui/src/lib/mapLayout.ts` around lines 41 - 56, Update the cycle-guard branch in place so it returns a y-coordinate in pixels, consistent with the normal placement paths, rather than the row index nextRow. Preserve the existing seen-node guard and parent-centering behavior.services/dis-console-ui/src/lib/flux.test.ts (1)
43-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for
isApp.
isAppencodes the rule that every HelmRelease is an app and a Kustomization is an app only when it is not an azapi root.buildMatrixdepends on it for row selection. A regression here changes the whole matrix silently.♻️ Suggested test
+describe('isApp', () => { + it('treats every HelmRelease as an app and excludes azapi roots', () => { + expect(isApp(res({ kind: 'HelmRelease', appliedBy: undefined }))).toBe(true); + expect(isApp(res({ kind: 'Kustomization', appliedBy: { name: 'root', namespace: 'flux-system' } }))).toBe(true); + expect(isApp(res({ kind: 'Kustomization', appliedBy: undefined }))).toBe(false); + }); +});Import
isAppfrom./fluxas well.Based on learnings: "Keep domain logic in
src/libpure and React-free, and protect the data transforms with unit tests" and "Treat every HelmRelease as an app, and treat a Kustomization as an app only when it is not an azapi root".🤖 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 `@services/dis-console-ui/src/lib/flux.test.ts` around lines 43 - 57, Add unit coverage for isApp in the flux tests, importing it from ./flux alongside the existing symbols. Verify every HelmRelease is classified as an app, while Kustomizations are classified as apps only when they are not azapi roots; include both positive and negative cases to protect buildMatrix row selection.Source: Coding guidelines
services/dis-console-ui/src/lib/tableSort.ts (1)
26-38: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueConsider numeric collation for name-like columns.
localeComparewithout options sortsdb10beforedb2. Resource names in the fleet often end in digits. Numeric collation gives the order users expect.♻️ Suggested change
+const cmp = (a: string, b: string) => a.localeCompare(b, undefined, { numeric: true }); + export function sortRows<T extends SortableRow>(rows: T[], state: SortState): T[] { const sign = state.dir === 'asc' ? 1 : -1; return [...rows].sort((a, b) => { const c = state.col === 'status' ? STATUS_SEVERITY[a.status] - STATUS_SEVERITY[b.status] - : a[state.col].localeCompare(b[state.col]); + : cmp(a[state.col], b[state.col]); if (c !== 0) return sign * c; // Stable tie-break so equal values keep a deterministic order. return ( - a.kind.localeCompare(b.kind) || - a.namespace.localeCompare(b.namespace) || - a.name.localeCompare(b.name) + cmp(a.kind, b.kind) || cmp(a.namespace, b.namespace) || cmp(a.name, b.name) ); }); }🤖 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 `@services/dis-console-ui/src/lib/tableSort.ts` around lines 26 - 38, Update the string comparisons in the table sorting comparator to use numeric collation for name-like columns, including the primary column comparison and the deterministic tie-break fields in the sort callback. Preserve status severity sorting and the existing sort direction behavior while ensuring values such as db2 sort before db10.services/dis-console-ui/src/lib/disResources.test.ts (1)
18-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for the orphan-child path.
buildDisResourcespromotes a child to the top level when its parent is not in the data. No test covers that branch. Add a case with aDatabasewhoseparentis missing.As per coding guidelines: "Keep domain logic in
src/libpure and React-free, and protect the data transforms with unit tests."🤖 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 `@services/dis-console-ui/src/lib/disResources.test.ts` around lines 18 - 47, Extend the buildDisResources test suite with an orphan-child case: include a Database resource whose parent is absent from the input data, then assert it is promoted to the top-level nodes for the selected cluster. Keep the existing grouping, filtering, and scoping tests unchanged.Source: Coding guidelines
services/dis-console-ui/src/lib/artifacts.test.ts (1)
153-178: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the
syncrootSummariestest into its own describe block.This test exercises
syncrootSummaries, notdeployedBySyncroot. The current placement makes the test report misleading.🤖 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 `@services/dis-console-ui/src/lib/artifacts.test.ts` around lines 153 - 178, Move the test titled “summarizes a syncroot like a project: namespaces, envs, worst status” into a dedicated describe block for syncrootSummaries, separate from the deployedBySyncroot tests. Keep the test setup and assertions unchanged.services/dis-console-ui/src/lib/disResources.ts (1)
84-91: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSort orphan children with the other nodes.
Orphan children are pushed after the
sort()call, so they appear in input order at the end of the group. Rows then look unordered when a parent is missing from the data. Build the orphan nodes first, then sort the full list.♻️ Proposed refactor
- const nodes: DisNode[] = parents + const parentNodes: DisNode[] = parents .map((p) => ({ resource: p, children: children .filter((c) => c.parent?.kind === p.kind && c.parent?.name === p.name) .sort((a, b) => a.name.localeCompare(b.name)), - })) - .sort( - (a, b) => - a.resource.kind.localeCompare(b.resource.kind) || - a.resource.name.localeCompare(b.resource.name), - ); + })); - const claimed = new Set(nodes.flatMap((n) => n.children.map((c) => `${c.kind}/${c.name}`))); - for (const c of children) { - if (!claimed.has(`${c.kind}/${c.name}`)) nodes.push({ resource: c, children: [] }); - } + const claimed = new Set( + parentNodes.flatMap((n) => n.children.map((c) => `${c.kind}/${c.name}`)), + ); + const orphans: DisNode[] = children + .filter((c) => !claimed.has(`${c.kind}/${c.name}`)) + .map((c) => ({ resource: c, children: [] })); + const nodes = [...parentNodes, ...orphans].sort( + (a, b) => + a.resource.kind.localeCompare(b.resource.kind) || + a.resource.name.localeCompare(b.resource.name), + );🤖 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 `@services/dis-console-ui/src/lib/disResources.ts` around lines 84 - 91, Update the grouping logic around the nodes sort and orphan handling: create and append all orphan child nodes before sorting the complete nodes collection. Ensure the final nodes list, including entries added by the claimed check, is sorted using the existing ordering before pushing the group.services/dis-console-ui/src/hooks/useAppHistories.ts (1)
29-62: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winKey
useAppHistorieson the row key and cell identifiers.
useAppHistoriesalready runs from rows produced by the memoized matrix and fromappRows, both of which are rebuilt for each relevant change. Sincerowis a freshMatrixRoweach time,[row]makes React treat it as a different dependency for the samerow.key; key the effect onrow.keyplus theenv|cluster|kind|namespace|nametuples of the cell resources instead.🤖 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 `@services/dis-console-ui/src/hooks/useAppHistories.ts` around lines 29 - 62, Update the useAppHistories useEffect dependency array to use row.key and stable cell resource identifiers in env|cluster|kind|namespace|name form instead of the row object. Derive those identifiers from the resource-bearing cells while preserving the existing fetch and cancellation behavior..github/workflows/dis-console-ui-release.yml (1)
22-25: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winNo change needed for pull-request permissions here.
This job does not run the image push paths on
pull_request: the reusable workflow only pushes whengithub.refisrefs/tags/...orrefs/heads/main. Removeid-token: writefrom this workflow unless OIDC issuance is required for the PR run.🤖 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 @.github/workflows/dis-console-ui-release.yml around lines 22 - 25, Remove the unnecessary id-token: write permission from the workflow’s permissions block, leaving contents: read and packages: write unchanged; retain it only if this job explicitly requires OIDC issuance during pull_request runs.services/dis-console-ui/vite.config.ts (1)
13-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider widening the test glob to
.tsx.The pattern matches only
.tsfiles. A future.test.tsxfile is skipped silently, with no failure to signal it.♻️ Proposed change
test: { environment: 'node', - include: ['src/**/*.test.ts'], + include: ['src/**/*.test.{ts,tsx}'], },🤖 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 `@services/dis-console-ui/vite.config.ts` around lines 13 - 16, Update the test include glob in the Vite test configuration to match both .test.ts and .test.tsx files, ensuring TypeScript React tests are discovered alongside existing TypeScript tests.services/dis-console-ui/src/api/http.ts (2)
39-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse
queryStringfor the summary query.
getSummarybuilds its query manually whilequeryStringalready handles encoding and the empty case.♻️ Proposed change
async getSummary(cluster?: string): Promise<Summary> { - const q = cluster ? `?cluster=${encodeURIComponent(cluster)}` : ''; - return getJSON<Summary>(`/api/summary${q}`); + return getJSON<Summary>(`/api/summary${queryString({ cluster })}`); }🤖 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 `@services/dis-console-ui/src/api/http.ts` around lines 39 - 42, Update getSummary to build its optional cluster query through the existing queryString helper instead of manually concatenating and encoding the query. Preserve the /api/summary endpoint and ensure the empty-cluster case still produces no query string.
16-22: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winBound every fleet request with a timeout.
fetchhas no abort signal. If the backend accepts the connection but never responds, the promise never settles. The consuming hooks leaveloadingtrue, so the console shows the skeleton indefinitely with no error path.Add
AbortSignal.timeoutand map the abort to a clear error message.♻️ Proposed change
+const TIMEOUT_MS = 30_000; + async function getJSON<T>(path: string): Promise<T> { - const res = await fetch(`${BASE}${path}`, { headers: { Accept: 'application/json' } }); + let res: Response; + try { + res = await fetch(`${BASE}${path}`, { + headers: { Accept: 'application/json' }, + signal: AbortSignal.timeout(TIMEOUT_MS), + }); + } catch (e) { + if (e instanceof Error && e.name === 'TimeoutError') { + throw new Error(`GET ${path} timed out after ${TIMEOUT_MS} ms`); + } + throw e; + } if (!res.ok) { throw new Error(`GET ${path} failed: ${res.status} ${res.statusText}`); } return (await res.json()) as T; }🤖 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 `@services/dis-console-ui/src/api/http.ts` around lines 16 - 22, Update getJSON to pass an AbortSignal.timeout to fetch so every fleet request has a finite deadline. Catch timeout-induced aborts and throw a clear request-timeout error while preserving the existing HTTP status error handling and successful JSON response path.services/dis-console-ui/server/index.ts (1)
62-65: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winSend no-cache headers with the SPA document.
The fallback serves
index.htmlwithout cache directives. Assets under/assets/are immutable for one year. If a browser or proxy caches the old document, it requests asset filenames that no longer exist after a deployment.Set
Cache-Control: no-cacheon the document response, and on the/index.htmlasset path at Line 59.♻️ Proposed change
return new Response(Bun.file(`${distDir}/index.html`), { - headers: { 'Content-Type': 'text/html' }, + headers: { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache' }, });🤖 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 `@services/dis-console-ui/server/index.ts` around lines 62 - 65, Update the SPA fallback response and the `/index.html` asset response to include the header `Cache-Control: no-cache`, while preserving their existing content type and response behavior.services/dis-console-ui/src/api/mock.fixtures.ts (1)
729-729: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueGuard the padding length.
'0'.repeat(40 - state.digest.length)throwsRangeErrorif a digest longer than 40 characters is added. All current digests are 12 characters, so the fixture works today.♻️ Proposed change
- originRevision: `main/${state.digest}${'0'.repeat(40 - state.digest.length)}`, + originRevision: `main/${state.digest.padEnd(40, '0').slice(0, 40)}`,🤖 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 `@services/dis-console-ui/src/api/mock.fixtures.ts` at line 729, Guard the padding calculation in the fixture’s originRevision construction so String.repeat never receives a negative count when state.digest exceeds 40 characters. Preserve the existing 40-character revision format for shorter digests and avoid changing the digest content.services/dis-console-ui/src/api/mock.ts (1)
50-59: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winFixture objects escape the mock client without deep cloning. Three read paths return references into the module-level
RESOURCESfixture. A consumer that mutates a nested field changes the fixture for every later call, which the live HTTP client never does.getClustersandgetArtifactsalready usestructuredClone; apply the same isolation to the remaining paths.
services/dis-console-ui/src/api/mock.ts#L50-L59: replace.map((r) => ({ ...r }))with.map((r) => structuredClone(r)).services/dis-console-ui/src/api/mock.ts#L61-L77: clonefoundbefore spreading, for example{ ...structuredClone(found), raw: rawFor(found), history: historyFor(found) }.services/dis-console-ui/src/api/mock.ts#L88-L92: clone theentriesarray returned byinventoryFor, because each entry embeds aResourcereference taken directly fromRESOURCES.🤖 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 `@services/dis-console-ui/src/api/mock.ts` around lines 50 - 59, Deep-clone fixture data in all three mock read paths so consumers cannot mutate module-level RESOURCES state: in services/dis-console-ui/src/api/mock.ts lines 50-59, replace the shallow Resource mapping in getResources with structuredClone; in lines 61-77, clone found before constructing the returned object while preserving rawFor and historyFor; and in lines 88-92, clone the entries returned by inventoryFor because they contain Resource references.
🤖 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 `@services/dis-console-ui/Makefile`:
- Around line 55-70: Update the install command in both dev-podman and dev-live
to use Bun’s frozen-lockfile mode. Preserve the existing container mounts,
environment variables, and dev-server commands while ensuring dependency
installation fails if package.json and the lockfile disagree.
- Around line 132-140: Update the kind-create recipe’s $(KIND) create cluster
invocation to pass $(KIND_KUBECONFIG) as the creation kubeconfig when that
variable is non-empty, while preserving the default ambient kubeconfig behavior
when it is empty; leave the existing cluster detection and export kubeconfig
flow unchanged.
In `@services/dis-console-ui/server/index.ts`:
- Around line 34-50: Update the API proxy flow in the request handler to set
init.signal to a 30-second AbortSignal.timeout, await the upstream fetch, and
wrap it in try/catch. Map abort timeouts to a 504 response and other upstream
fetch failures to a 502 response, while preserving the existing successful
response and unconfigured-backend behavior.
In `@services/dis-console-ui/src/api/mock.fixtures.ts`:
- Around line 619-621: Update the schemaVersion returned by buildClusters to
match the v5 schema represented by the fixture images data, so views gating
image or drift display receive a consistent schema version.
In `@services/dis-console-ui/src/components/HomeView.tsx`:
- Around line 20-49: Update HomeView’s useArtifacts destructuring and syncroots
panel rendering to handle the hook’s error state before the empty summaries
state. When the artifacts fetch fails, render the hook error using the
Designsystemet Paragraph component; only show “No syncroot artifacts reported.”
when loading has finished successfully with no artifacts.
In `@services/dis-console-ui/src/components/MockBanner.tsx`:
- Around line 7-8: Update the banner text in MockBanner to replace
VITE_API_BASE_URL with the supported DIS_CONSOLE_API runtime configuration,
while retaining VITE_USE_MOCK=false and accurately indicating that live API
settings come from runtime /config.js.
In `@services/dis-console-ui/src/components/SyncrootMap.tsx`:
- Around line 253-281: Restructure the node rendering around the `inner`,
`clickable`, and `onDetails` logic so details is not nested inside the node
`<button>`. Render the node as a container with sibling native `<button>`
elements for expand/collapse and details, preserving their respective handlers
and keyboard behavior; remove the `role="button"` span and add/use a
`.map-node__main` layout rule so the primary control fills the node while
`.map-node` remains the container.
In `@services/dis-console-ui/src/components/SyncrootsView.tsx`:
- Around line 184-200: Replace the environment-cell button in the syncroots view
with a RouteLink/routeHash-generated anchor targeting the same syncroot route
and key for environment e. Preserve the cell contents, styling, and aria-label
(adding RouteLink aria-label pass-through support if needed), while removing the
onClick-based navigate call so standard link interactions remain available.
- Around line 706-716: Update the aria-label construction in the orderedEnvs
mapping to use the same 'absent' fallback as the StageChip state when
rel.chips[e] is undefined, while preserving the existing environment label and
digest text.
In `@services/dis-console-ui/src/components/WorkloadsTable.tsx`:
- Around line 63-77: Move the status-to-color and tag-variant mapping from the
workload cell renderer into the `imageCellStyle` helper in
`src/lib/statusColor.ts`, accepting the status and drift flag and preserving the
existing precedence and variants. Update the relevant rendering logic in
`WorkloadsTable` to call this helper and use its returned `color` and `variant`
values.
In `@services/dis-console-ui/src/hooks/useDsDialog.ts`:
- Around line 16-38: Update the dialog synchronization effect and close
listeners in useDsDialog to follow the Designsystemet contract: remove the
el.close() path and the close event listener, and rely solely on the toggle
handler when newState is 'closed' to invoke onCloseRef.current(). Preserve
opening behavior and cleanup for the remaining toggle listener.
In `@services/dis-console-ui/src/hooks/useResourceDetail.ts`:
- Around line 21-25: Reset loading state in both lazy-fetch hooks when their
required ref is absent: in
services/dis-console-ui/src/hooks/useResourceDetail.ts lines 21-25, update the
absent-ref branch to call setLoading(false) alongside setResource(null) and
setError(null); in services/dis-console-ui/src/hooks/useInventory.ts lines
20-24, make the equivalent change alongside setInventory(null) and
setError(null).
In `@services/dis-console-ui/src/hooks/useSourceLink.ts`:
- Around line 16-45: Reset loading to false at the start of the useEffect
callback before the early returns and before starting any fetch. Keep setting it
to true only when source resolution requires the asynchronous api.getResource
request, while preserving the existing cancellation cleanup and finally
handling.
In `@services/dis-console-ui/src/lib/matrix.ts`:
- Around line 134-141: Update the status computation loop that reads each cell’s
resource and children to also include every entry in cell.conflict when
calculating the worst status and anyFailed. Ensure failed conflicting resources
affect the cell status and anyFailed exactly like the primary resource, while
preserving the existing healthy-resource behavior.
In `@services/dis-console-ui/src/lib/releases.ts`:
- Around line 63-64: Update the chip assignment in the applied branch to map
artifactStatus(cell.artifact) values of suspended to the suspended chip, while
retaining failed as failed and mapping other statuses to current.
In `@services/dis-console-ui/src/lib/route.ts`:
- Around line 22-26: Update parseRoute’s segment decoding in the parts
construction to catch URIError from decodeURIComponent and retain the original
segment when decoding fails, so malformed hashes such as `#/`% or truncated
percent sequences still produce a route without throwing during useHashRoute
rendering. Add a route.test.ts case covering parseRoute('`#/`%') and verify the
raw segment is preserved.
In `@services/dis-console-ui/src/styles.css`:
- Line 4: Update the font-family declaration in the stylesheet to remove the
redundant quotes around both Inter values while preserving the existing fallback
order and CSS variable fallback.
- Around line 921-931: Update the .sr-only visually hidden style to replace the
deprecated clip property with an equivalent clip-path declaration, preserving
the existing accessibility and layout behavior.
---
Nitpick comments:
In @.github/workflows/dis-console-ui-release.yml:
- Around line 22-25: Remove the unnecessary id-token: write permission from the
workflow’s permissions block, leaving contents: read and packages: write
unchanged; retain it only if this job explicitly requires OIDC issuance during
pull_request runs.
In `@services/dis-console-ui/server/index.ts`:
- Around line 62-65: Update the SPA fallback response and the `/index.html`
asset response to include the header `Cache-Control: no-cache`, while preserving
their existing content type and response behavior.
In `@services/dis-console-ui/src/api/http.ts`:
- Around line 39-42: Update getSummary to build its optional cluster query
through the existing queryString helper instead of manually concatenating and
encoding the query. Preserve the /api/summary endpoint and ensure the
empty-cluster case still produces no query string.
- Around line 16-22: Update getJSON to pass an AbortSignal.timeout to fetch so
every fleet request has a finite deadline. Catch timeout-induced aborts and
throw a clear request-timeout error while preserving the existing HTTP status
error handling and successful JSON response path.
In `@services/dis-console-ui/src/api/mock.fixtures.ts`:
- Line 729: Guard the padding calculation in the fixture’s originRevision
construction so String.repeat never receives a negative count when state.digest
exceeds 40 characters. Preserve the existing 40-character revision format for
shorter digests and avoid changing the digest content.
In `@services/dis-console-ui/src/api/mock.ts`:
- Around line 50-59: Deep-clone fixture data in all three mock read paths so
consumers cannot mutate module-level RESOURCES state: in
services/dis-console-ui/src/api/mock.ts lines 50-59, replace the shallow
Resource mapping in getResources with structuredClone; in lines 61-77, clone
found before constructing the returned object while preserving rawFor and
historyFor; and in lines 88-92, clone the entries returned by inventoryFor
because they contain Resource references.
In `@services/dis-console-ui/src/components/ClustersTable.tsx`:
- Around line 5-16: Move the pure relative-time formatter from the ClustersTable
component into a React-free module under src/lib, then import and use it from
ClustersTable instead of the local relative function. Add unit tests covering
empty or invalid dates, seconds, minutes, hours, and days, including the
relevant boundary transitions.
In `@services/dis-console-ui/src/components/DisResourcesView.tsx`:
- Line 9: Update the tenant configuration used by DisResourcesView so it first
reads the Azure portal tenant from the runtime /config.js configuration, then
falls back to import.meta.env.VITE_AZURE_PORTAL_TENANT when runtime
configuration is absent. Add the tenant to the existing runtime configuration
contract and preserve the current undefined fallback behavior.
In `@services/dis-console-ui/src/components/LeftNav.tsx`:
- Around line 85-93: Update the navigation anchor in LeftNav to build its href
with the existing routeHash helper, passing the section id as the view value,
instead of interpolating a hash manually. Preserve the current label,
active-state, and other anchor behavior.
In `@services/dis-console-ui/src/components/ReleasesBrowser.tsx`:
- Around line 27-33: The duplicated firstSeenLabel formatter must be centralized
in a pure, React-free src/lib helper and covered by unit tests for missing and
invalid ISO values. In
services/dis-console-ui/src/components/ReleasesBrowser.tsx lines 27-33, delete
the local firstSeenLabel and import the shared helper; make the same change in
services/dis-console-ui/src/components/ReleaseDialog.tsx lines 22-28. Add the
shared formatter to an appropriate src/lib module while preserving its existing
output behavior.
In `@services/dis-console-ui/src/components/SyncrootMap.tsx`:
- Around line 148-163: Update the panning handlers in SyncrootMap so the SVG
captures the active pointer during onPointerDown and releases capture when the
gesture ends in onPointerUp. Use the event’s pointerId and preserve the existing
drag.current reset, ensuring pointer movement and release continue to reach the
SVG after the pointer leaves its bounds.
In `@services/dis-console-ui/src/components/SyncrootsView.tsx`:
- Around line 58-59: Replace the build-time import.meta.env values used for
GRAFANA and PORTAL_TENANT with the corresponding runtime configuration values
loaded from /config.js, following the existing runtime configuration pattern and
naming used for DIS_CONSOLE_API. Preserve the current trailing-slash removal for
GRAFANA and undefined fallback behavior for the portal tenant.
In `@services/dis-console-ui/src/hooks/useAppHistories.ts`:
- Around line 29-62: Update the useAppHistories useEffect dependency array to
use row.key and stable cell resource identifiers in
env|cluster|kind|namespace|name form instead of the row object. Derive those
identifiers from the resource-bearing cells while preserving the existing fetch
and cancellation behavior.
In `@services/dis-console-ui/src/lib/artifacts.test.ts`:
- Around line 153-178: Move the test titled “summarizes a syncroot like a
project: namespaces, envs, worst status” into a dedicated describe block for
syncrootSummaries, separate from the deployedBySyncroot tests. Keep the test
setup and assertions unchanged.
In `@services/dis-console-ui/src/lib/artifacts.ts`:
- Around line 43-55: Update statusOf in flux.ts to accept an object containing
only ready and suspended, preserving its existing status mapping, then replace
the duplicated logic in artifactStatus with delegation to statusOf and add the
statusOf import from ./flux.
- Around line 175-205: Update deployedBySyncroot in
services/dis-console-ui/src/lib/artifacts.ts (lines 175-205) to build a Map from
each appliedBy namespace/name key to its resources once, then traverse matching
resources with a queue seeded by root Kustomizations; exclude the roots from the
result and only let discovered Kustomizations extend the owner frontier. No
direct change is required in services/dis-console-ui/src/lib/workloads.ts (lines
176-190); the indexing fix addresses the issue there.
In `@services/dis-console-ui/src/lib/disResources.test.ts`:
- Around line 18-47: Extend the buildDisResources test suite with an
orphan-child case: include a Database resource whose parent is absent from the
input data, then assert it is promoted to the top-level nodes for the selected
cluster. Keep the existing grouping, filtering, and scoping tests unchanged.
In `@services/dis-console-ui/src/lib/disResources.ts`:
- Around line 84-91: Update the grouping logic around the nodes sort and orphan
handling: create and append all orphan child nodes before sorting the complete
nodes collection. Ensure the final nodes list, including entries added by the
claimed check, is sorted using the existing ordering before pushing the group.
In `@services/dis-console-ui/src/lib/flux.test.ts`:
- Around line 43-57: Add unit coverage for isApp in the flux tests, importing it
from ./flux alongside the existing symbols. Verify every HelmRelease is
classified as an app, while Kustomizations are classified as apps only when they
are not azapi roots; include both positive and negative cases to protect
buildMatrix row selection.
In `@services/dis-console-ui/src/lib/mapLayout.ts`:
- Around line 41-56: Update the cycle-guard branch in place so it returns a
y-coordinate in pixels, consistent with the normal placement paths, rather than
the row index nextRow. Preserve the existing seen-node guard and
parent-centering behavior.
In `@services/dis-console-ui/src/lib/tableSort.ts`:
- Around line 26-38: Update the string comparisons in the table sorting
comparator to use numeric collation for name-like columns, including the primary
column comparison and the deterministic tie-break fields in the sort callback.
Preserve status severity sorting and the existing sort direction behavior while
ensuring values such as db2 sort before db10.
In `@services/dis-console-ui/src/styles.css`:
- Around line 623-649: Merge the duplicate .map-node rules by moving position:
relative into the initial .map-node declaration, then remove the later
standalone .map-node block while preserving all existing styles.
In `@services/dis-console-ui/vite.config.ts`:
- Around line 13-16: Update the test include glob in the Vite test configuration
to match both .test.ts and .test.tsx files, ensuring TypeScript React tests are
discovered alongside existing TypeScript tests.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 6b34e7d9-3cc8-49df-98e3-edc2f6c656e9
⛔ Files ignored due to path filters (3)
services/dis-console-ui/bun.lockis excluded by!**/*.lockservices/dis-console-ui/src/assets/dis-logo-color.pngis excluded by!**/*.pngservices/dis-console-ui/src/assets/dis-logo-white.pngis excluded by!**/*.png
📒 Files selected for processing (93)
.github/workflows/dis-console-ui-lint-test.yml.github/workflows/dis-console-ui-release.yml.release-please-manifest.jsondeploy/admin/base/dis-console-ui-deployment.yamldeploy/admin/base/kustomization.yamlrelease-please-config.jsonrenovate.jsonservices/dis-console-ui/.dockerignoreservices/dis-console-ui/.env.exampleservices/dis-console-ui/.gitignoreservices/dis-console-ui/.trivyignoreservices/dis-console-ui/AGENTS.mdservices/dis-console-ui/CHANGELOG.mdservices/dis-console-ui/CLAUDE.mdservices/dis-console-ui/Dockerfileservices/dis-console-ui/Makefileservices/dis-console-ui/README.mdservices/dis-console-ui/config/kind/deployment.yamlservices/dis-console-ui/eslint.config.jsservices/dis-console-ui/index.htmlservices/dis-console-ui/package.jsonservices/dis-console-ui/public/config.jsservices/dis-console-ui/server/index.tsservices/dis-console-ui/src/App.tsxservices/dis-console-ui/src/api/client.tsservices/dis-console-ui/src/api/http.tsservices/dis-console-ui/src/api/index.tsservices/dis-console-ui/src/api/mock.fixtures.tsservices/dis-console-ui/src/api/mock.tsservices/dis-console-ui/src/api/types.tsservices/dis-console-ui/src/components/ArtifactDialog.tsxservices/dis-console-ui/src/components/ClustersTable.tsxservices/dis-console-ui/src/components/DeploymentMatrix.tsxservices/dis-console-ui/src/components/DetailDialog.tsxservices/dis-console-ui/src/components/DisResourcesView.tsxservices/dis-console-ui/src/components/DisScope.tsxservices/dis-console-ui/src/components/HomeView.tsxservices/dis-console-ui/src/components/KustomizationPage.tsxservices/dis-console-ui/src/components/LeftNav.tsxservices/dis-console-ui/src/components/MatrixSkeleton.tsxservices/dis-console-ui/src/components/MockBanner.tsxservices/dis-console-ui/src/components/ReleaseDialog.tsxservices/dis-console-ui/src/components/ReleasesBrowser.tsxservices/dis-console-ui/src/components/RouteLink.tsxservices/dis-console-ui/src/components/SortableTh.tsxservices/dis-console-ui/src/components/StageChip.tsxservices/dis-console-ui/src/components/StaleBanner.tsxservices/dis-console-ui/src/components/StatusCell.tsxservices/dis-console-ui/src/components/StatusTag.tsxservices/dis-console-ui/src/components/SyncrootMap.tsxservices/dis-console-ui/src/components/SyncrootsView.tsxservices/dis-console-ui/src/components/WorkloadsTable.tsxservices/dis-console-ui/src/hooks/useAppHistories.tsservices/dis-console-ui/src/hooks/useArtifacts.tsservices/dis-console-ui/src/hooks/useDialogBackClose.tsservices/dis-console-ui/src/hooks/useDsDialog.tsservices/dis-console-ui/src/hooks/useFleet.tsservices/dis-console-ui/src/hooks/useHashRoute.tsservices/dis-console-ui/src/hooks/useInventory.tsservices/dis-console-ui/src/hooks/useResourceDetail.tsservices/dis-console-ui/src/hooks/useSourceLink.tsservices/dis-console-ui/src/hooks/useSyncrootMap.tsservices/dis-console-ui/src/lib/appReleases.test.tsservices/dis-console-ui/src/lib/appReleases.tsservices/dis-console-ui/src/lib/artifacts.test.tsservices/dis-console-ui/src/lib/artifacts.tsservices/dis-console-ui/src/lib/azure.test.tsservices/dis-console-ui/src/lib/azure.tsservices/dis-console-ui/src/lib/disResources.test.tsservices/dis-console-ui/src/lib/disResources.tsservices/dis-console-ui/src/lib/flux.test.tsservices/dis-console-ui/src/lib/flux.tsservices/dis-console-ui/src/lib/mapLayout.test.tsservices/dis-console-ui/src/lib/mapLayout.tsservices/dis-console-ui/src/lib/matrix.test.tsservices/dis-console-ui/src/lib/matrix.tsservices/dis-console-ui/src/lib/releases.test.tsservices/dis-console-ui/src/lib/releases.tsservices/dis-console-ui/src/lib/route.test.tsservices/dis-console-ui/src/lib/route.tsservices/dis-console-ui/src/lib/sourceLink.test.tsservices/dis-console-ui/src/lib/sourceLink.tsservices/dis-console-ui/src/lib/statusColor.tsservices/dis-console-ui/src/lib/tableSort.test.tsservices/dis-console-ui/src/lib/tableSort.tsservices/dis-console-ui/src/lib/workloads.test.tsservices/dis-console-ui/src/lib/workloads.tsservices/dis-console-ui/src/main.tsxservices/dis-console-ui/src/styles.cssservices/dis-console-ui/src/vite-env.d.tsservices/dis-console-ui/tsconfig.jsonservices/dis-console-ui/tsconfig.server.jsonservices/dis-console-ui/vite.config.ts
- Run the image as USER 1000:1000 so runAsNonRoot can verify the user (a named user fails admission with CreateContainerConfigError) - Time out the BFF /api proxy after 30s and map upstream failures to 504/502 instead of crashing the request - Honor KIND_KUBECONFIG= (ambient kubeconfig) in kind-create; install with --frozen-lockfile in the dev container targets - Reset loading state on early returns in useSourceLink/useResourceDetail; rely on the dialog toggle event alone in useDsDialog - Survive malformed hash routes (decodeURIComponent), fold conflict resources into cell status, show suspended stage chips - Render the artifacts fetch error on Home instead of an empty state - Use a real anchor for syncroot env cells, guard an aria-label against missing chip state, move the image-tag styling into statusColor.ts, and make the map details affordance a sibling button (nested interactive elements are invalid) - Update the mock banner text and mock schemaVersion (5)
Adds dis-console-ui: a read-only web console over the dis-console fleet API. React 19 + Vite + TypeScript + Designsystemet, built and served with Bun (a small BFF serves the static build and proxies /api same-origin).
What it shows
Model
An app is every HelmRelease, every Kustomization that is not an azapi root, and every workload applied directly by a root (release identity = primary container image tag). HelmReleases and workloads applied by an app fold into its row.
Modes
Mock data bundled for standalone demos; the mode is a runtime decision — the BFF serves /config.js from its environment, live whenever DIS_CONSOLE_API is set. One image serves all modes.
CI + deploy
dis-console-ui-lint-test.yml)dis-console-ui-v*tagsdeploy/admin: Deployment + ClusterIP Service on admin-test (no ingress; reach it viakubectl -n product-dis port-forward svc/dis-console-ui 8080:80). Image rideslatestuntil the first release exists; a follow-up pins the version.Rollout
Merge → release-please cuts dis-console-ui + the image builds → dispatch the admin syncroot publish → port-forward.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests
Documentation