From 02a5dc95cf7ecf12a4646a42a9859c2532e208b9 Mon Sep 17 00:00:00 2001 From: kiddin9 <48883331+kiddin9@users.noreply.github.com> Date: Fri, 20 Mar 2026 23:03:15 +0800 Subject: [PATCH] Update dockerman.patch --- .github/diy/patches/dockerman.patch | 154 ++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/.github/diy/patches/dockerman.patch b/.github/diy/patches/dockerman.patch index a6e49fb5..833b6bee 100644 --- a/.github/diy/patches/dockerman.patch +++ b/.github/diy/patches/dockerman.patch @@ -20,3 +20,157 @@ o = s.taboption('globals', form.ListValue, 'log_level', _('Log Level'), + +--- a/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js ++++ b/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js +@@ -73,7 +73,8 @@ return dm2.dv.extend({ + const hostConfig = c.HostConfig || {}; + const resolvedImage = resolveImageId(c.Image) || resolveImageId(c.Config?.Image) || c.Image || c.Config?.Image || ''; + const builtInNetworks = new Set(['none', 'bridge', 'host']); +- const [netnames, nets] = Object.entries(c.NetworkSettings?.Networks || {}); ++ // Object.entries returns [[name, obj], ...] — pick the first network ++ const [[firstName, firstNet] = []] = Object.entries(c.NetworkSettings?.Networks || {}); + + containerData.container = { + name: c.Name?.substring(1) || '', +@@ -82,20 +83,18 @@ return dm2.dv.extend({ + image: resolvedImage, + privileged: hostConfig.Privileged ? 1 : 0, + restart_policy: hostConfig.RestartPolicy?.Name || 'unless-stopped', +- network: (() => { +- return (netnames && (netnames.length > 0)) ? netnames[0] : ''; +- })(), ++ network: firstName || '', + ipv4: (() => { +- if (builtInNetworks.has(netnames[0])) return ''; +- return (nets && (nets.length > 0)) ? nets[0]?.IPAddress || '' : ''; ++ if (!firstName || builtInNetworks.has(firstName)) return ''; ++ return firstNet?.IPAddress || ''; + })(), + ipv6: (() => { +- if (builtInNetworks.has(netnames[0])) return ''; +- return (nets && (nets.length > 0)) ? nets[0]?.GlobalIPv6Address || '' : ''; ++ if (!firstName || builtInNetworks.has(firstName)) return ''; ++ return firstNet?.GlobalIPv6Address || ''; + })(), + ipv6_lla: (() => { +- if (builtInNetworks.has(netnames[0])) return ''; +- return (nets && (nets.length > 0)) ? nets[0]?.LinkLocalIPv6Address || '' : ''; ++ if (!firstName || builtInNetworks.has(firstName)) return ''; ++ return firstNet?.LinkLocalIPv6Address || ''; + })(), + link: hostConfig.Links || [], + dns: hostConfig.Dns || [], +@@ -135,9 +134,12 @@ return dm2.dv.extend({ + publish: (() => { + const ports = []; + for (const [containerPort, bindings] of Object.entries(hostConfig.PortBindings || {})) { +- if (Array.isArray(bindings) && bindings.length > 0 && bindings[0]?.HostPort) { +- const hostPort = bindings[0].HostPort; +- ports.push(hostPort + ':' + containerPort); ++ if (Array.isArray(bindings) && bindings.length > 0) { ++ for (const b of bindings) { ++ if (!b?.HostPort) continue; ++ const ip = b.HostIp || ''; ++ ports.push((ip ? ip + ':' : '') + b.HostPort + ':' + containerPort); ++ } + } + } + return ports; +@@ -801,9 +803,15 @@ return dm2.dv.extend({ + (Array.isArray(publish) ? publish : [publish]) + .filter(p => p && typeof p === 'string' && p.trim().length > 0) + .map(p => { +- const m = p.match(/^(\d+):(\d+)\/(tcp|udp)$/); +- if (m) return [`${m[2]}/${m[3]}`, [{ HostPort: m[1] }]]; +- return null; ++ // formats: hostPort:cPort/proto or hostIp:hostPort:cPort/proto ++ const m = p.match(/^(?:([^:]+):(\d+)|(\d+)):(\d+)\/(tcp|udp)$/); ++ if (!m) return null; ++ const hostIp = m[1] || ''; ++ const hostPort = m[2] || m[3]; ++ const cPort = m[4]; ++ const proto = m[5]; ++ const binding = hostIp ? { HostIp: hostIp, HostPort: hostPort } : { HostPort: hostPort }; ++ return [`${cPort}/${proto}`, [binding]]; + }).filter(Boolean) + ) : undefined, + Mounts: undefined, + +--- a/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js ++++ b/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js +@@ -308,6 +308,56 @@ return dm2.dv.extend({ + }, E('div', btns)); + }, + ++ buildPortLinks(ports) { ++ // cont.Ports[] from GET /containers/json — flat array. ++ // Published ports have {IP, PrivatePort, PublicPort, Type}. ++ // Exposed-only ports omit IP and PublicPort: {PrivatePort, Type}. ++ if (!Array.isArray(ports) || ports.length === 0) return ''; ++ ++ const LOCAL_IPS = new Set(['0.0.0.0', '::']); ++ ++ // Sort: published (has PublicPort) before exposed-only, then by PrivatePort ++ const sorted = [...ports].sort((a, b) => { ++ const aHasPub = a.PublicPort ? 1 : 0; ++ const bHasPub = b.PublicPort ? 1 : 0; ++ if (aHasPub !== bHasPub) return bHasPub - aHasPub; ++ return (a.PrivatePort || 0) - (b.PrivatePort || 0); ++ }); ++ ++ const lines = sorted.map(p => { ++ const ip = p.IP || ''; ++ const pub = p.PublicPort || ''; ++ const priv = p.PrivatePort || ''; ++ const type = p.Type || ''; ++ ++ const isIPv6 = ip.includes(':'); ++ const isLocal = LOCAL_IPS.has(ip); ++ const displayIp = isIPv6 ? `[${ip}]` : ip; ++ ++ let label; ++ if (pub && ip) label = `${displayIp}:${pub}->${priv}/${type}`; ++ else if (pub) label = `${pub}->${priv}/${type}`; ++ else label = `${priv}/${type}`; ++ ++ // Clickable link for published TCP ports only ++ if (type === 'tcp' && pub) { ++ const host = isLocal ? window.location.hostname : displayIp; ++ return E('div', {}, [ ++ E('a', { ++ href: `http://${host}:${pub}`, ++ target: '_blank', ++ rel: 'noopener noreferrer', ++ title: _('Open in browser'), ++ }, [label]), ++ ]); ++ } ++ ++ return E('div', {}, [label]); ++ }); ++ ++ return E('div', {}, lines); ++ }, ++ + handleSave: null, + handleSaveApply: null, + handleReset: null, +@@ -342,16 +392,7 @@ return dm2.dv.extend({ + _shortId: (cont?.Id || '').substring(0, 12), + Networks: this.parseNetworkLinksForContainer(network_list, cont?.NetworkSettings?.Networks || {}, true), + Created: this.buildTimeString(cont?.Created) || '', +- Ports: (Array.isArray(cont.Ports) && cont.Ports.length > 0) +- ? cont.Ports.map(p => { +- // const ip = p.IP || ''; +- const pub = p.PublicPort || ''; +- const priv = p.PrivatePort || ''; +- const type = p.Type || ''; +- return `${pub ? pub + ':' : ''}${priv}/${type}`; +- // return `${ip ? ip + ':' : ''}${pub} -> ${priv} (${type})`; +- }).join('
') +- : '', ++ Ports: this.buildPortLinks(cont.Ports), + }); + } +