mirror of
https://github.com/openwrt/luci.git
synced 2026-05-31 10:31:55 +08:00
luci-app-dockerman: links for port bindings
- handling of hostIP in ports - retain during container clone - make link clickable - provide router host if ip is general - provide container host if specific Signed-off-by: Serhii Ivanov <icegood1980@gmail.com>
This commit is contained in:
+6
-2
@@ -250,8 +250,12 @@ return dm2.dv.extend({
|
||||
if (!portBindings || typeof portBindings !== 'object') return [];
|
||||
const ports = [];
|
||||
for (const [containerPort, bindings] of Object.entries(portBindings)) {
|
||||
if (Array.isArray(bindings) && bindings.length > 0 && bindings[0]?.HostPort) {
|
||||
ports.push(`${bindings[0].HostPort}:${containerPort}`);
|
||||
if (Array.isArray(bindings)) {
|
||||
for (const b of bindings) {
|
||||
if (!b?.HostPort) continue;
|
||||
const ip = (b.HostIp && b.HostIp !== '0.0.0.0' && b.HostIp !== '::') ? b.HostIp + ':' : '';
|
||||
ports.push(`${ip}${b.HostPort}:${containerPort}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ports;
|
||||
|
||||
+32
-5
@@ -138,9 +138,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 && b.HostIp !== '0.0.0.0' && b.HostIp !== '::') ? b.HostIp : '';
|
||||
ports.push((ip ? ip + ':' : '') + b.HostPort + ':' + containerPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ports;
|
||||
@@ -816,8 +819,32 @@ 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] }]];
|
||||
// hostIp:hostPort:cPort/proto (e.g. 192.168.1.100:8080:80/tcp)
|
||||
const m = p.match(/^([^:]+):(\d+):(\d+)\/(tcp|udp)$/);
|
||||
if (m) {
|
||||
const hostIp = m[1];
|
||||
const hostPort = m[2];
|
||||
const cPort = m[3];
|
||||
const proto = m[4];
|
||||
return [`${cPort}/${proto}`, [{ HostIp: hostIp, HostPort: hostPort }]];
|
||||
}
|
||||
// [ipv6]:hostPort:cPort/proto (e.g. [::1]:8080:80/tcp)
|
||||
const m6 = p.match(/^\[([^\]]+)\]:(\d+):(\d+)\/(tcp|udp)$/);
|
||||
if (m6) {
|
||||
const hostIp = m6[1];
|
||||
const hostPort = m6[2];
|
||||
const cPort = m6[3];
|
||||
const proto = m6[4];
|
||||
return [`${cPort}/${proto}`, [{ HostIp: hostIp, HostPort: hostPort }]];
|
||||
}
|
||||
// hostPort:cPort/proto (e.g. 8080:80/tcp)
|
||||
const m2 = p.match(/^(\d+):(\d+)\/(tcp|udp)$/);
|
||||
if (m2) {
|
||||
const hostPort = m2[1];
|
||||
const cPort = m2[2];
|
||||
const proto = m2[3];
|
||||
return [`${cPort}/${proto}`, [{ HostPort: hostPort }]];
|
||||
}
|
||||
return null;
|
||||
}).filter(Boolean)
|
||||
) : undefined,
|
||||
|
||||
+51
-10
@@ -309,6 +309,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,
|
||||
@@ -343,16 +393,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),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user