op-packages/.github/diy/patches/dockerman.patch
2026-03-30 13:19:46 +08:00

464 lines
13 KiB
Diff

--- a/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json
+++ b/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json
@@ -1,7 +1,7 @@
{
- "admin/services/dockerman": {
- "title": "Dockerman JS",
- "order": "60",
+ "admin/dockerman": {
+ "title": "Docker",
+ "order": 40,
"action": {
"type": "firstchild"
},
@@ -15,7 +15,7 @@
}
},
- "admin/services/dockerman/overview": {
+ "admin/dockerman/overview": {
"title": "Overview",
"order": 1,
"action": {
@@ -24,7 +24,7 @@
}
},
- "admin/services/dockerman/configuration": {
+ "admin/dockerman/configuration": {
"title": "Configuration",
"order": 2,
"action": {
@@ -33,14 +33,14 @@
}
},
- "admin/services/dockerman/container/archive/*": {
+ "admin/dockerman/container/archive/*": {
"action": {
"type": "alias",
- "path": "admin/services/dockerman/containers"
+ "path": "admin/dockerman/containers"
}
},
- "admin/services/dockerman/container/archive/put/*": {
+ "admin/dockerman/container/archive/put/*": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -52,7 +52,7 @@
}
},
- "admin/services/dockerman/container/archive/get/*": {
+ "admin/dockerman/container/archive/get/*": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -64,7 +64,7 @@
}
},
- "admin/services/dockerman/container/export/*": {
+ "admin/dockerman/container/export/*": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -76,7 +76,7 @@
}
},
- "admin/services/dockerman/container/*": {
+ "admin/dockerman/container/*": {
"title_hide": "Container",
"action": {
"type": "view",
@@ -84,7 +84,7 @@
}
},
- "admin/services/dockerman/containers": {
+ "admin/dockerman/containers": {
"title": "Containers",
"order": 3,
"action": {
@@ -93,7 +93,7 @@
}
},
- "admin/services/dockerman/containers/prune": {
+ "admin/dockerman/containers/prune": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -106,14 +106,14 @@
}
},
- "admin/services/dockerman/container": {
+ "admin/dockerman/container": {
"action": {
"type": "alias",
- "path": "admin/services/dockerman/containers"
+ "path": "admin/dockerman/containers"
}
},
- "admin/services/dockerman/container_new": {
+ "admin/dockerman/container_new": {
"title_hide": "Container",
"action": {
"type": "view",
@@ -121,7 +121,7 @@
}
},
- "admin/services/dockerman/images/build": {
+ "admin/dockerman/images/build": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -134,7 +134,7 @@
}
},
- "admin/services/dockerman/images/build/prune": {
+ "admin/dockerman/images/build/prune": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -147,7 +147,7 @@
}
},
- "admin/services/dockerman/images/get/*": {
+ "admin/dockerman/images/get/*": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -159,7 +159,7 @@
}
},
- "admin/services/dockerman/images/load": {
+ "admin/dockerman/images/load": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -172,7 +172,7 @@
}
},
- "admin/services/dockerman/images/prune": {
+ "admin/dockerman/images/prune": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -185,7 +185,7 @@
}
},
- "admin/services/dockerman/images": {
+ "admin/dockerman/images": {
"title": "Images",
"order": 4,
"action": {
@@ -194,7 +194,7 @@
}
},
- "admin/services/dockerman/images/create": {
+ "admin/dockerman/images/create": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -207,7 +207,7 @@
}
},
- "admin/services/dockerman/images/push/*": {
+ "admin/dockerman/images/push/*": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -220,14 +220,14 @@
}
},
- "admin/services/dockerman/image": {
+ "admin/dockerman/image": {
"action": {
"type": "alias",
- "path": "admin/services/dockerman/images"
+ "path": "admin/dockerman/images"
}
},
- "admin/services/dockerman/network/*": {
+ "admin/dockerman/network/*": {
"title_hide": "Network",
"action": {
"type": "view",
@@ -235,7 +235,7 @@
}
},
- "admin/services/dockerman/networks": {
+ "admin/dockerman/networks": {
"title": "Networks",
"order": 5,
"action": {
@@ -244,7 +244,7 @@
}
},
- "admin/services/dockerman/networks/prune": {
+ "admin/dockerman/networks/prune": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -257,7 +257,7 @@
}
},
- "admin/services/dockerman/network_new": {
+ "admin/dockerman/network_new": {
"title_hide": "Network",
"action": {
"type": "view",
@@ -265,14 +265,14 @@
}
},
- "admin/services/dockerman/network": {
+ "admin/dockerman/network": {
"action": {
"type": "alias",
- "path": "admin/services/dockerman/networks"
+ "path": "admin/dockerman/networks"
}
},
- "admin/services/dockerman/volumes": {
+ "admin/dockerman/volumes": {
"title": "Volumes",
"order": 6,
"action": {
@@ -281,7 +281,7 @@
}
},
- "admin/services/dockerman/volumes/prune": {
+ "admin/dockerman/volumes/prune": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -294,7 +294,7 @@
}
},
- "admin/services/dockerman/docker/events": {
+ "admin/dockerman/docker/events": {
"action": {
"type": "function",
"module": "luci.controller.docker",
@@ -307,7 +307,7 @@
}
},
- "admin/services/dockerman/events": {
+ "admin/dockerman/events": {
"title": "Events",
"order": 7,
"action": {
--- a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js
+++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js
@@ -437,7 +437,7 @@ const dv = view.extend({
outputText: '', // Initialize output text
get dockerman_url() {
- return L.url('admin/services/dockerman');
+ return L.url('admin/dockerman');
},
parseHeaders(headers, array) {
--- /dev/null
+++ b/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+uci -q batch <<-EOF >/dev/null
+ set uhttpd.main.script_timeout="360"
+ commit uhttpd
+EOF
+
+/etc/init.d/dockerd restart
--- a/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js
+++ b/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js
@@ -66,6 +66,7 @@ return L.view.extend({
o.value('https://docker.io');
o.value('https://ghcr.io');
o.value('https://hub-mirror.c.163.com');
+ o.value('https://ustc-edu-cn.mirror.aliyuncs.com');
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('<br/>')
- : '',
+ Ports: this.buildPortLinks(cont.Ports),
});
}