Conversation
|
@coderabbitait review |
|
@kmendell I'll review the changes in this PR for you. ✅ Actions performedReview triggered.
|
📝 WalkthroughWalkthroughImplements a new PortBadges Svelte component and integrates it into container list and detail pages, adds settings loading to provide a baseServerUrl, updates i18n keys for ports, and removes the old compose ports count badge. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Pre-merge checks❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. 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.
Actionable comments posted: 3
🧹 Nitpick comments (8)
frontend/src/routes/containers/+page.svelte (1)
27-30: Deduplicate baseServerUrl derivation via a helper.This precedence chain is duplicated in multiple files. Centralize it (e.g.,
getBaseServerUrl(settings)in a small util) to avoid drift.Example helper:
// $lib/utils/url.util.ts export function getBaseServerUrl(s: any): string { return s?.serverBaseUrl ?? s?.baseServerUrl ?? s?.baseUrl ?? ''; }Usage here:
-const baseServerUrl = $derived( - (data.settings as any)?.serverBaseUrl ?? (data.settings as any)?.baseServerUrl ?? (data.settings as any)?.baseUrl ?? '' -); +import { getBaseServerUrl } from '$lib/utils/url.util'; +const baseServerUrl = $derived(getBaseServerUrl(data.settings as any));frontend/src/routes/containers/container-table.svelte (1)
26-38: Make baseServerUrl optional with a safe default.Avoid forcing all callers to pass it. Defaulting to
''keeps behavior (falls back to window origin in PortBadges).- requestOptions = $bindable(), - baseServerUrl = $bindable() + requestOptions = $bindable(), + baseServerUrl = $bindable('') }: { containers: Paginated<ContainerSummaryDto>; selectedIds: string[]; requestOptions: SearchPaginationSortRequest; - baseServerUrl: string; + baseServerUrl?: string; } = $props();frontend/src/routes/containers/[containerId]/+page.svelte (2)
269-271: Reuse a shared helper for baseServerUrl.Mirror the containers list page suggestion to avoid duplicated precedence logic.
+import { getBaseServerUrl } from '$lib/utils/url.util'; -const baseServerUrl = $derived( - (data?.settings as any)?.serverBaseUrl ?? (data?.settings as any)?.baseServerUrl ?? (data?.settings as any)?.baseUrl ?? '' -); +const baseServerUrl = $derived(getBaseServerUrl(data?.settings as any));
368-431: Also surface ports in the Overview card to match the “at-a-glance” objective.Add a Ports row under Overview so users don’t need to jump to Config.
<div class="space-y-4"> @@ <div class="flex items-center gap-3"> <div class="rounded bg-amber-50 p-2 dark:bg-amber-950/20"> <TerminalIcon class="size-4 text-amber-600" /> </div> <div class="min-w-0 flex-1"> <div class="text-muted-foreground text-sm">{m.containers_command()}</div> <div class="truncate font-medium" title={container.config?.cmd?.join(' ')}> {container.config?.cmd?.join(' ') || m.common_na()} </div> </div> </div> + <div class="flex items-center gap-3"> + <div class="rounded bg-sky-50 p-2 dark:bg-sky-950/20"> + <NetworkIcon class="size-4 text-sky-600" /> + </div> + <div class="min-w-0 flex-1"> + <div class="text-muted-foreground text-sm">{m.ports()}</div> + <div class="font-medium"> + <PortBadges ports={container.ports ?? []} {baseServerUrl} /> + </div> + </div> + </div> </div>frontend/src/lib/components/port-badges.svelte (4)
10-16: Normalize protocol to ‘tcp’ by default and avoidanyleakage.Default unknown protocol to
tcp(Docker’s default) for consistent display/dedupe; narrowprototostring.type NormalizedPort = { hostPort: string; containerPort: string; - proto?: string; + proto: string; ip?: string | null; }; @@ -function getProto(p: PortDto): string | undefined { - return (p as any).type ?? (p as any).protocol ?? undefined; -} +function getProto(p: PortDto): string { + const v = (p as any).type ?? (p as any).protocol ?? 'tcp'; + return String(v).toLowerCase(); +} @@ return { hostPort, containerPort: getPrivatePort(p), - proto: getProto(p), + proto: getProto(p), ip: (p as any).ip ?? null };Also applies to: 25-27, 29-38
40-53: Fix sort for non-numeric ports and add protocol tiebreaker.Current
Number(...)can yield NaN, producing unstable ordering.-function uniquePublished(list: PortDto[]): NormalizedPort[] { +function uniquePublished(list: PortDto[]): NormalizedPort[] { const map = new Map<string, NormalizedPort>(); for (const p of list) { const n = normalize(p); if (!n) continue; - const key = `${n.hostPort}:${n.containerPort}/${n.proto ?? ''}`; + const key = `${n.hostPort}:${n.containerPort}/${n.proto}`; if (!map.has(key)) map.set(key, n); } - return Array.from(map.values()).sort((a, b) => { - const hp = Number(a.hostPort) - Number(b.hostPort); - if (hp !== 0) return hp; - return Number(a.containerPort) - Number(b.containerPort); - }); + const isNumeric = (s: string) => /^\d+$/.test(s); + return Array.from(map.values()).sort((a, b) => { + const hp = isNumeric(a.hostPort) && isNumeric(b.hostPort) + ? Number(a.hostPort) - Number(b.hostPort) + : a.hostPort.localeCompare(b.hostPort, undefined, { numeric: true }); + if (hp !== 0) return hp; + const cp = isNumeric(a.containerPort) && isNumeric(b.containerPort) + ? Number(a.containerPort) - Number(b.containerPort) + : a.containerPort.localeCompare(b.containerPort, undefined, { numeric: true }); + if (cp !== 0) return cp; + return a.proto.localeCompare(b.proto); + }); }
55-64: Preserve origin scheme but guard against base with paths.Optional: strip path/query from
baseServerUrlto avoid unexpected/foopaths in links.-const url = new URL(base.startsWith('http') ? base : `http://${base}`); +const url = new URL(base.startsWith('http') ? base : `http://${base}`); +url.pathname = '/'; +url.search = ''; +url.hash = '';
69-87: Don’t hyperlink non-TCP ports; visually indicate disabled.Clicking UDP/SCTP yields meaningless HTTP requests. Keep badge, disable link.
- <a - class="ring-offset-background focus-visible:ring-ring bg-background/70 inline-flex items-center gap-1 rounded-lg border border-sky-700/20 px-2 py-1 text-[11px] shadow-sm transition-colors transition-shadow hover:border-sky-700/40 hover:bg-sky-500/10 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2" - href={toHref(p.hostPort)} - target="_blank" - rel="noopener noreferrer" - title={`${p.ip ?? '0.0.0.0'}:${p.hostPort} → ${p.containerPort}${p.proto ? `/${p.proto}` : ''}`} - > + <a + class="ring-offset-background focus-visible:ring-ring bg-background/70 inline-flex items-center gap-1 rounded-lg border border-sky-700/20 px-2 py-1 text-[11px] shadow-sm transition-colors transition-shadow hover:border-sky-700/40 hover:bg-sky-500/10 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 {p.proto !== 'tcp' ? 'pointer-events-none opacity-60' : ''}" + href={p.proto === 'tcp' ? toHref(p.hostPort) : '#'} + aria-disabled={p.proto !== 'tcp'} + target="_blank" + rel="noopener noreferrer" + title={`${p.ip ?? '0.0.0.0'}:${p.hostPort} → ${p.containerPort}${p.proto ? `/${p.proto}` : ''}`} + >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
frontend/messages/en.json(1 hunks)frontend/src/lib/components/port-badges.svelte(1 hunks)frontend/src/routes/compose/[projectId]/+page.svelte(0 hunks)frontend/src/routes/containers/+page.svelte(2 hunks)frontend/src/routes/containers/+page.ts(1 hunks)frontend/src/routes/containers/[containerId]/+page.svelte(3 hunks)frontend/src/routes/containers/[containerId]/+page.ts(1 hunks)frontend/src/routes/containers/container-table.svelte(2 hunks)
💤 Files with no reviewable changes (1)
- frontend/src/routes/compose/[projectId]/+page.svelte
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-09T21:42:07.222Z
Learnt from: CR
PR: ofkm/arcane#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-09T21:42:07.222Z
Learning: Applies to frontend/src/routes/**/+page.svelte : Implement page components using +page.svelte within routes
Applied to files:
frontend/src/routes/containers/[containerId]/+page.sveltefrontend/src/routes/containers/+page.svelte
🔇 Additional comments (8)
frontend/src/routes/containers/+page.svelte (1)
112-112: Propagate baseServerUrl to the table — LGTM.frontend/src/routes/containers/container-table.svelte (2)
134-136: New “Ports” column — LGTM.Good placement and non-sortable as expected.
139-142: Ports cell integration — LGTM.Delegating to PortBadges keeps the table lean.
frontend/src/routes/containers/[containerId]/+page.ts (1)
20-23: Return shape extension — LGTM.Downstream code now reading
data.settingsis consistent with this.frontend/src/routes/containers/[containerId]/+page.svelte (2)
29-29: Import PortBadges — LGTM.
713-713: Config section PortBadges — LGTM.frontend/src/routes/containers/+page.ts (1)
7-9: Pagination/sort defaults — LGTM.frontend/src/lib/components/port-badges.svelte (1)
66-67: Derived store usage — LGTM.Simple and efficient computation of published ports.
Fixes: #379
Fixes: #382
Summary by CodeRabbit
New Features
Refactor
Localization