luci-app-dockerman: fixes
authorPaul Donald <redacted>
Thu, 14 May 2026 11:56:22 +0000 (14:56 +0300)
committerPaul Donald <redacted>
Thu, 14 May 2026 13:59:09 +0000 (16:59 +0300)
- fill network selection when clicking container net "connect"
- provide container ID for net dis/connect
- correct net link in network overview
- proper split of arguments in CMD
- harden API endpoint access to ACL
- allow stop/kill in restarting state
- show IP(v4) from built-in networks

Signed-off-by: Serhii Ivanov <redacted>
Signed-off-by: Paul Donald <redacted>
applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js
applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js
applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js
applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js
applications/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json

index 1c5604a64329298961e3c60c3c0f8e6668742173..88c14b0d5e1c46d610d0cea97139990b720f8ad8 100644 (file)
@@ -306,7 +306,7 @@ return dm2.dv.extend({
                                Name: name,
                                NetworkID: netid,
                                DNSNames: net?.DNSNames || '',
-                               IPv4Address: net?.IPAMConfig?.IPv4Address || '',
+                               IPv4Address: net?.IPAMConfig?.IPv4Address || net?.IPAddress || '',
                                IPv6Address: net?.IPAMConfig?.IPv6Address || '',
                        });
                }
@@ -315,6 +315,7 @@ return dm2.dv.extend({
        },
 
        render([this_container, images, networks, cpus_mem, ps_top, stats_data, changes_data]) {
+               this.networks = networks;
                const view = this;
                const containerName = this_container.Name?.substring(1) || this_container.Id || '';
                const containerIdShort = (this_container.Id || '').substring(0, 12);
@@ -359,7 +360,7 @@ return dm2.dv.extend({
                }
 
                // Stop button
-               if (containerStatus === 'running' || containerStatus === 'paused') {
+               if (containerStatus === 'running' || containerStatus === 'paused' || containerStatus === 'restarting') {
                        const stopBtn = E('button', {
                                'class': 'cbi-button cbi-button-reset',
                                'click': (ev) => this.executeAction(ev, 'stop', this_container.Id)
@@ -368,7 +369,7 @@ return dm2.dv.extend({
                }
 
                // Kill button
-               if (containerStatus === 'running') {
+               if (containerStatus === 'running' || containerStatus === 'restarting') {
                        const killBtn = E('button', {
                                'class': 'cbi-button',
                                'style': 'background-color: #dc3545;',
@@ -1849,7 +1850,7 @@ return dm2.dv.extend({
                                dm2.network_disconnect,
                                {
                                        id: networkID,
-                                       body: { Container: view.containerId, Force: false }
+                                       body: { Container: this_container.Id, Force: false }
                                },
                                _('Disconnect network'),
                                {
@@ -1927,7 +1928,7 @@ return dm2.dv.extend({
 
                                                        ui.hideModal();
 
-                                                       const body = { Container: view.containerId };
+                                                       const body = { Container: this_container.Id };
                                                        body.EndpointConfig = { IPAMConfig: { IPv4Address: ip4Address } }; //, IPv6Address: ip6Address || null
 
                                                        view.executeDockerAction(
index d624ec0e5062f50b04b6379ae349bb1f231c891c..b919004766edc37b4793986b6c87113dd4bde7da 100644 (file)
@@ -142,7 +142,12 @@ return dm2.dv.extend({
                                        }
                                        return ports;
                                })(),
-                               command: c.Config?.Cmd ? c.Config?.Cmd.join(' ') : '',
+                               command: c.Config?.Cmd ? c.Config?.Cmd.map(arg => {
+                                       if (arg.includes(' ') || arg.includes('"') || arg.includes("'")) {
+                                               return '"' + arg.replace(/"/g, '\\"') + '"';
+                                       }
+                                       return arg;
+                               }).join(' ') : '',
                                hostname: c.Config?.Hostname || '',
                                publish_all: hostConfig.PublishAllPorts ? 1 : 0,
                                device: (hostConfig.Devices || []).map(d => d.PathOnHost + ':' + d.PathInContainer + (d.CgroupPermissions ? ':' + d.CgroupPermissions : '')),
@@ -772,7 +777,7 @@ return dm2.dv.extend({
                                        Tty: toBool(get('tty')),
                                        OpenStdin: toBool(get('interactive')),
                                        Env: get('env'),
-                                       Cmd: command ? command.split(' ') : null,
+                                       Cmd: command ? (command.match(/(?:[^\s"]+|"[^"]*")+/g) || []).map(arg => arg.replace(/^"|"$/g, '')) : null,
                                        Image: get('image'),
                                        HostConfig: {
                                                CpuShares: toInt(get('cpu_shares')),
index e118ac4f528d5fd9bfeddbec176a64f4ef0e8630..ec0499bb847e1b3824ffd95d456b3645ada7dc17 100644 (file)
@@ -177,6 +177,7 @@ return dm2.dv.extend({
        buildContainerActions(cont, idx) {
                const view = this;
                const isRunning = cont?.State === 'running';
+               const isRestarting = cont?.State === 'restarting';
                const isPaused = cont?.State === 'paused';
                const btns = [
                        E('button', {
@@ -252,7 +253,7 @@ return dm2.dv.extend({
                                        dm2.Types['container'].sub['stop'].i18n,
                                        {showOutput: true, showSuccess: false}
                                ),
-                               'disabled' : !(isRunning || isPaused) ? true : null
+                               'disabled' : !(isRunning || isPaused || isRestarting) ? true : null
                        }, [dm2.Types['container'].sub['stop'].e]),
 
                        E('button', {
@@ -264,7 +265,7 @@ return dm2.dv.extend({
                                        dm2.Types['container'].sub['kill'].i18n,
                                        {showOutput: true, showSuccess: false}
                                ),
-                               'disabled' : !(isRunning || isPaused) ? true : null
+                               'disabled' : !(isRunning || isPaused || isRestarting) ? true : null
                        }, [dm2.Types['container'].sub['kill'].e]),
 
                        E('button', {
index b8638edfedcf16c82efb188a6578c70b4e947ed7..62a16722d1b79753797fa5d030aba85c5cef6a07 100644 (file)
@@ -208,7 +208,7 @@ return dm2.dv.extend({
                        const n = net.Name;
                        const _shortId = (net.Id || '').substring(0, 12);
                        const shortLink = E('a', {
-                               'href': `${view.dockerman_url}/network/${net.Id}`,
+                               'href': `${this.dockerman_url}/network/${net.Id}`,
                                'style': 'font-family: monospace;',
                                'title': _('Click to view this network'),
                        }, [_shortId]);
index 0cc3995d152639af212913399422fff9f8558a70..9fa0e7e10557bf003a1432a89d7a244398025af5 100644 (file)
@@ -21,6 +21,9 @@
                "action": {
                        "type": "view",
                        "path": "dockerman/overview"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
@@ -30,6 +33,9 @@
                "action": {
                        "type": "view",
                        "path": "dockerman/configuration"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
@@ -37,6 +43,9 @@
                "action": {
                        "type": "alias",
                        "path": "admin/services/dockerman/containers"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
@@ -49,6 +58,9 @@
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
@@ -61,6 +73,9 @@
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
@@ -73,6 +88,9 @@
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
@@ -81,6 +99,9 @@
                "action": {
                        "type": "view",
                        "path": "dockerman/container"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "view",
                        "path": "dockerman/containers"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "alias",
                        "path": "admin/services/dockerman/containers"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "view",
                        "path": "dockerman/container_new"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "alias",
                        "path": "admin/services/dockerman/images"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "view",
                        "path": "dockerman/network"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "view",
                        "path": "dockerman/networks"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "view",
                        "path": "dockerman/network_new"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "alias",
                        "path": "admin/services/dockerman/networks"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "view",
                        "path": "dockerman/volumes"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "auth": {
                        "methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
                        "login": true
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        },
 
                "action": {
                        "type": "view",
                        "path": "dockerman/events"
+               },
+               "depends": {
+                       "acl": [ "luci-app-dockerman" ]
                }
        }
 }
\ No newline at end of file
git clone https://git.99rst.org/PROJECT