From 84e0a0bf14b04fb9311d109168c0be4ad79322fa Mon Sep 17 00:00:00 2001 From: jarek Date: Sun, 19 Apr 2026 09:30:42 +0200 Subject: [PATCH] collapse consecutive port mappings into ranges in container list (#821) --- src/lib/utils/port-format.ts | 61 ++++++++++++++++++++++++++++++ src/routes/containers/+page.svelte | 28 ++------------ 2 files changed, 64 insertions(+), 25 deletions(-) create mode 100644 src/lib/utils/port-format.ts diff --git a/src/lib/utils/port-format.ts b/src/lib/utils/port-format.ts new file mode 100644 index 0000000..1836c38 --- /dev/null +++ b/src/lib/utils/port-format.ts @@ -0,0 +1,61 @@ +export interface PortMapping { + publicPort: number; + privatePort: number; + display: string; + isRange?: boolean; +} + +interface PortInfo { + PublicPort: number; + PrivatePort: number; +} + +/** + * Format Docker port mappings, collapsing consecutive ranges. + * e.g. 8080:8080, 8081:8081, 8082:8082 → 8080-8082:8080-8082 + */ +export function formatPorts(ports: PortInfo[] | undefined | null): PortMapping[] { + if (!ports || ports.length === 0) return []; + const seen = new Set(); + const individual = ports + .filter(p => p.PublicPort) + .map(p => ({ + publicPort: p.PublicPort, + privatePort: p.PrivatePort, + display: `${p.PublicPort}:${p.PrivatePort}` + })) + .filter(p => { + const key = p.display; + if (seen.has(key)) return false; + seen.add(key); + return true; + }) + .sort((a, b) => a.publicPort - b.publicPort); + + // Collapse consecutive port ranges + if (individual.length <= 1) return individual; + + const result: PortMapping[] = []; + let rangeStart = individual[0]; + let rangeEnd = individual[0]; + + for (let i = 1; i < individual.length; i++) { + const curr = individual[i]; + const offset = curr.publicPort - rangeStart.publicPort; + const expectedPrivate = rangeStart.privatePort + offset; + if (curr.publicPort === rangeEnd.publicPort + 1 && curr.privatePort === expectedPrivate) { + rangeEnd = curr; + } else { + result.push(rangeStart.publicPort === rangeEnd.publicPort + ? rangeStart + : { publicPort: rangeStart.publicPort, privatePort: rangeStart.privatePort, display: `${rangeStart.publicPort}-${rangeEnd.publicPort}:${rangeStart.privatePort}-${rangeEnd.privatePort}`, isRange: true }); + rangeStart = curr; + rangeEnd = curr; + } + } + result.push(rangeStart.publicPort === rangeEnd.publicPort + ? rangeStart + : { publicPort: rangeStart.publicPort, privatePort: rangeStart.privatePort, display: `${rangeStart.publicPort}-${rangeEnd.publicPort}:${rangeStart.privatePort}-${rangeEnd.privatePort}`, isRange: true }); + + return result; +} diff --git a/src/routes/containers/+page.svelte b/src/routes/containers/+page.svelte index be2d08a..6fa084a 100644 --- a/src/routes/containers/+page.svelte +++ b/src/routes/containers/+page.svelte @@ -1149,29 +1149,7 @@ } } - interface PortMapping { - publicPort: number; - privatePort: number; - display: string; - } - - function formatPorts(ports: ContainerInfo['ports']): PortMapping[] { - if (!ports || ports.length === 0) return []; - const seen = new Set(); - return ports - .filter(p => p.PublicPort) - .map(p => ({ - publicPort: p.PublicPort, - privatePort: p.PrivatePort, - display: `${p.PublicPort}:${p.PrivatePort}` - })) - .filter(p => { - const key = p.display; - if (seen.has(key)) return false; - seen.add(key); - return true; - }); - } + import { formatPorts, type PortMapping } from '$lib/utils/port-format'; function extractHostFromUrl(urlString: string): string | null { if (!urlString) return null; @@ -1823,7 +1801,7 @@ {@const memoryTooltip = stats.memoryCache > 0 ? `${formatBytes(stats.memoryUsage)} / ${formatBytes(stats.memoryLimit)} (Total: ${formatBytes(stats.memoryRaw)} | Cache: ${formatBytes(stats.memoryCache)})` : `${formatBytes(stats.memoryUsage)} / ${formatBytes(stats.memoryLimit)}`} - {formatBytes(stats.memoryUsage)} + {formatBytes(stats.memoryUsage)}/{formatBytes(stats.memoryLimit, 0)} {:else if container.state === 'running'} ... {:else} @@ -1883,7 +1861,7 @@ {@const remainingCount = ports.length - 1}
{#each displayPorts as port} - {@const url = currentEnvDetails ? getPortUrl(port.publicPort) : null} + {@const url = !port.isRange && currentEnvDetails ? getPortUrl(port.publicPort) : null} {#if url}