diff options
author | Florian Eckert <fe@dev.tdt.de> | 2020-07-30 08:07:08 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-30 08:07:08 +0200 |
commit | 8f54db8cc34c1ef1b8c4a2c691f69e0df13bfde9 (patch) | |
tree | 465039082c7aaba97b3b6f247b1125a9b2ec3e89 /applications/luci-app-dockerman/luasrc/model | |
parent | 815d255276c0b5b9ba13aca15a3994c206adbc77 (diff) | |
parent | c724c20b83ce5b6495d64c2cfce32041a2191c49 (diff) |
Merge pull request #4283 from TDT-AG/pr/20200717-luci-app-dockerman
luci-app-dockerman: refactoring and update coding style
Diffstat (limited to 'applications/luci-app-dockerman/luasrc/model')
9 files changed, 2990 insertions, 2302 deletions
diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua index 7c0c969336..9ea006396a 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua @@ -4,585 +4,778 @@ Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- require "luci.util" + local docker = require "luci.model.docker" local dk = docker.new() + container_id = arg[1] local action = arg[2] or "info" -local images, networks, container_info -if not container_id then return end -local res = dk.containers:inspect({id = container_id}) -if res.code < 300 then container_info = res.body else return end +local m, s, o +local images, networks, container_info, res + +if not container_id then + return +end + +res = dk.containers:inspect({id = container_id}) +if res.code < 300 then + container_info = res.body +else + return +end + res = dk.networks:list() -if res.code < 300 then networks = res.body else return end +if res.code < 300 then + networks = res.body +else + return +end local get_ports = function(d) - local data - if d.HostConfig and d.HostConfig.PortBindings then - for inter, out in pairs(d.HostConfig.PortBindings) do - data = (data and (data .. "<br>") or "") .. out[1]["HostPort"] .. ":" .. inter - end - end - return data + local data + + if d.HostConfig and d.HostConfig.PortBindings then + for inter, out in pairs(d.HostConfig.PortBindings) do + data = (data and (data .. "<br>") or "") .. out[1]["HostPort"] .. ":" .. inter + end + end + + return data end local get_env = function(d) - local data - if d.Config and d.Config.Env then - for _,v in ipairs(d.Config.Env) do - data = (data and (data .. "<br>") or "") .. v - end - end - return data + local data + + if d.Config and d.Config.Env then + for _,v in ipairs(d.Config.Env) do + data = (data and (data .. "<br>") or "") .. v + end + end + + return data end local get_command = function(d) - local data - if d.Config and d.Config.Cmd then - for _,v in ipairs(d.Config.Cmd) do - data = (data and (data .. " ") or "") .. v - end - end - return data + local data + + if d.Config and d.Config.Cmd then + for _,v in ipairs(d.Config.Cmd) do + data = (data and (data .. " ") or "") .. v + end + end + + return data end local get_mounts = function(d) - local data - if d.Mounts then - for _,v in ipairs(d.Mounts) do - local v_sorce_d, v_dest_d - local v_sorce = "" - local v_dest = "" - for v_sorce_d in v["Source"]:gmatch('[^/]+') do - if v_sorce_d and #v_sorce_d > 12 then - v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..." - else - v_sorce = v_sorce .."/".. v_sorce_d - end - end - for v_dest_d in v["Destination"]:gmatch('[^/]+') do - if v_dest_d and #v_dest_d > 12 then - v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..." - else - v_dest = v_dest .."/".. v_dest_d - end - end - data = (data and (data .. "<br>") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "") - end - end - return data + local data + + if d.Mounts then + for _,v in ipairs(d.Mounts) do + local v_sorce_d, v_dest_d + local v_sorce = "" + local v_dest = "" + for v_sorce_d in v["Source"]:gmatch('[^/]+') do + if v_sorce_d and #v_sorce_d > 12 then + v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..." + else + v_sorce = v_sorce .."/".. v_sorce_d + end + end + for v_dest_d in v["Destination"]:gmatch('[^/]+') do + if v_dest_d and #v_dest_d > 12 then + v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..." + else + v_dest = v_dest .."/".. v_dest_d + end + end + data = (data and (data .. "<br>") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "") + end + end + + return data end local get_device = function(d) - local data - if d.HostConfig and d.HostConfig.Devices then - for _,v in ipairs(d.HostConfig.Devices) do - data = (data and (data .. "<br>") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "") - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Devices then + for _,v in ipairs(d.HostConfig.Devices) do + data = (data and (data .. "<br>") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "") + end + end + + return data end local get_links = function(d) - local data - if d.HostConfig and d.HostConfig.Links then - for _,v in ipairs(d.HostConfig.Links) do - data = (data and (data .. "<br>") or "") .. v - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Links then + for _,v in ipairs(d.HostConfig.Links) do + data = (data and (data .. "<br>") or "") .. v + end + end + + return data end local get_tmpfs = function(d) - local data - if d.HostConfig and d.HostConfig.Tmpfs then - for k, v in pairs(d.HostConfig.Tmpfs) do - data = (data and (data .. "<br>") or "") .. k .. (v~="" and ":" or "")..v - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Tmpfs then + for k, v in pairs(d.HostConfig.Tmpfs) do + data = (data and (data .. "<br>") or "") .. k .. (v~="" and ":" or "")..v + end + end + + return data end local get_dns = function(d) - local data - if d.HostConfig and d.HostConfig.Dns then - for _, v in ipairs(d.HostConfig.Dns) do - data = (data and (data .. "<br>") or "") .. v - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Dns then + for _, v in ipairs(d.HostConfig.Dns) do + data = (data and (data .. "<br>") or "") .. v + end + end + + return data end local get_sysctl = function(d) - local data - if d.HostConfig and d.HostConfig.Sysctls then - for k, v in pairs(d.HostConfig.Sysctls) do - data = (data and (data .. "<br>") or "") .. k..":"..v - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Sysctls then + for k, v in pairs(d.HostConfig.Sysctls) do + data = (data and (data .. "<br>") or "") .. k..":"..v + end + end + + return data end local get_networks = function(d) - local data={} - if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then - for k,v in pairs(d.NetworkSettings.Networks) do - data[k] = v.IPAddress or "" - end - end - return data + local data={} + + if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then + for k,v in pairs(d.NetworkSettings.Networks) do + data[k] = v.IPAddress or "" + end + end + + return data end local start_stop_remove = function(m, cmd) - docker:clear_status() - docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...") - local res - if cmd ~= "upgrade" then - res = dk.containers[cmd](dk, {id = container_id}) - else - res = dk.containers_upgrade(dk, {id = container_id}) - end - if res and res.code >= 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) - else - docker:clear_status() - if cmd ~= "remove" and cmd ~= "upgrade" then - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) - else - luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) - end - end + local res + + docker:clear_status() + docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...") + + if cmd ~= "upgrade" then + res = dk.containers[cmd](dk, {id = container_id}) + else + res = dk.containers_upgrade(dk, {id = container_id}) + end + + if res and res.code >= 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) + else + docker:clear_status() + if cmd ~= "remove" and cmd ~= "upgrade" then + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) + else + luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) + end + end end m=SimpleForm("docker", container_info.Name:sub(2), translate("Docker Container") ) m.redirect = luci.dispatcher.build_url("admin/docker/containers") --- m:append(Template("dockerman/container")) -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - - -action_section = m:section(Table,{{}}) -action_section.notitle=true -action_section.rowcolors=false -action_section.template = "cbi/nullsection" - -btnstart=action_section:option(Button, "_start") -btnstart.template = "dockerman/cbi/inlinebutton" -btnstart.inputtitle=translate("Start") -btnstart.inputstyle = "apply" -btnstart.forcewrite = true -btnrestart=action_section:option(Button, "_restart") -btnrestart.template = "dockerman/cbi/inlinebutton" -btnrestart.inputtitle=translate("Restart") -btnrestart.inputstyle = "reload" -btnrestart.forcewrite = true -btnstop=action_section:option(Button, "_stop") -btnstop.template = "dockerman/cbi/inlinebutton" -btnstop.inputtitle=translate("Stop") -btnstop.inputstyle = "reset" -btnstop.forcewrite = true -btnkill=action_section:option(Button, "_kill") -btnkill.template = "dockerman/cbi/inlinebutton" -btnkill.inputtitle=translate("Kill") -btnkill.inputstyle = "reset" -btnkill.forcewrite = true -btnupgrade=action_section:option(Button, "_upgrade") -btnupgrade.template = "dockerman/cbi/inlinebutton" -btnupgrade.inputtitle=translate("Upgrade") -btnupgrade.inputstyle = "reload" -btnstop.forcewrite = true -btnduplicate=action_section:option(Button, "_duplicate") -btnduplicate.template = "dockerman/cbi/inlinebutton" -btnduplicate.inputtitle=translate("Duplicate/Edit") -btnduplicate.inputstyle = "add" -btnstop.forcewrite = true -btnremove=action_section:option(Button, "_remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputtitle=translate("Remove") -btnremove.inputstyle = "remove" -btnremove.forcewrite = true - -btnstart.write = function(self, section) - start_stop_remove(m,"start") + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template = "cbi/nullsection" + +o = s:option(Button, "_start") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Start") +o.inputstyle = "apply" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"start") end -btnrestart.write = function(self, section) - start_stop_remove(m,"restart") + +o = s:option(Button, "_restart") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Restart") +o.inputstyle = "reload" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"restart") end -btnupgrade.write = function(self, section) - start_stop_remove(m,"upgrade") + +o = s:option(Button, "_stop") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Stop") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"stop") end -btnremove.write = function(self, section) - start_stop_remove(m,"remove") + +o = s:option(Button, "_kill") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Kill") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"kill") end -btnstop.write = function(self, section) - start_stop_remove(m,"stop") + +o = s:option(Button, "_upgrade") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Upgrade") +o.inputstyle = "reload" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"upgrade") end -btnkill.write = function(self, section) - start_stop_remove(m,"kill") + +o = s:option(Button, "_duplicate") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Duplicate/Edit") +o.inputstyle = "add" +o.forcewrite = true +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id)) end -btnduplicate.write = function(self, section) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id)) + +o = s:option(Button, "_remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Remove") +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"remove") end -tab_section = m:section(SimpleSection) -tab_section.template = "dockerman/container" - -if action == "info" then - m.submit = false - m.reset = false - table_info = { - ["01name"] = {_key = translate("Name"), _value = container_info.Name:sub(2) or "-", _button=translate("Update")}, - ["02id"] = {_key = translate("ID"), _value = container_info.Id or "-"}, - ["03image"] = {_key = translate("Image"), _value = container_info.Config.Image .. "<br>" .. container_info.Image}, - ["04status"] = {_key = translate("Status"), _value = container_info.State and container_info.State.Status or "-"}, - ["05created"] = {_key = translate("Created"), _value = container_info.Created or "-"}, - } - table_info["06start"] = container_info.State.Status == "running" and {_key = translate("Start Time"), _value = container_info.State and container_info.State.StartedAt or "-"} or {_key = translate("Finish Time"), _value = container_info.State and container_info.State.FinishedAt or "-"} - table_info["07healthy"] = {_key = translate("Healthy"), _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"} - table_info["08restart"] = {_key = translate("Restart Policy"), _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", _button=translate("Update")} - table_info["081user"] = {_key = translate("User"), _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"} - table_info["09mount"] = {_key = translate("Mount/Volume"), _value = get_mounts(container_info) or "-"} - table_info["10cmd"] = {_key = translate("Command"), _value = get_command(container_info) or "-"} - table_info["11env"] = {_key = translate("Env"), _value = get_env(container_info) or "-"} - table_info["12ports"] = {_key = translate("Ports"), _value = get_ports(container_info) or "-"} - table_info["13links"] = {_key = translate("Links"), _value = get_links(container_info) or "-"} - table_info["14device"] = {_key = translate("Device"), _value = get_device(container_info) or "-"} - table_info["15tmpfs"] = {_key = translate("Tmpfs"), _value = get_tmpfs(container_info) or "-"} - table_info["16dns"] = {_key = translate("DNS"), _value = get_dns(container_info) or "-"} - table_info["17sysctl"] = {_key = translate("Sysctl"), _value = get_sysctl(container_info) or "-"} - info_networks = get_networks(container_info) - list_networks = {} - for _, v in ipairs (networks) do - if v.Name then - local parent = v.Options and v.Options.parent or nil - local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil - ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil - local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") - list_networks[v.Name] = network_name - end - end - - if type(info_networks)== "table" then - for k,v in pairs(info_networks) do - table_info["14network"..k] = { - _key = translate("Network"), _value = k.. (v~="" and (" | ".. v) or ""), _button=translate("Disconnect") - } - list_networks[k]=nil - end - end - - table_info["15connect"] = {_key = translate("Connect Network"), _value = list_networks ,_opts = "", _button=translate("Connect")} - - - d_info = m:section(Table,table_info) - d_info.nodescr=true - d_info.formvalue=function(self, section) - return table_info - end - dv_key = d_info:option(DummyValue, "_key", translate("Info")) - dv_key.width = "20%" - dv_value = d_info:option(ListValue, "_value") - dv_value.render = function(self, section, scope) - if table_info[section]._key == translate("Name") then - self:reset_values() - self.template = "cbi/value" - self.size = 30 - self.keylist = {} - self.vallist = {} - self.default=table_info[section]._value - Value.render(self, section, scope) - elseif table_info[section]._key == translate("Restart Policy") then - self.template = "cbi/lvalue" - self:reset_values() - self.size = nil - self:value("no", "No") - self:value("unless-stopped", "Unless stopped") - self:value("always", "Always") - self:value("on-failure", "On failure") - self.default=table_info[section]._value - ListValue.render(self, section, scope) - elseif table_info[section]._key == translate("Connect Network") then - self.template = "cbi/lvalue" - self:reset_values() - self.size = nil - for k,v in pairs(list_networks) do - if k ~= "host" then - self:value(k,v) - end - end - self.default=table_info[section]._value - ListValue.render(self, section, scope) - else - self:reset_values() - self.rawhtml=true - self.template = "cbi/dvalue" - self.default=table_info[section]._value - DummyValue.render(self, section, scope) - end - end - dv_value.forcewrite = true -- for write function using simpleform - dv_value.write = function(self, section, value) - table_info[section]._value=value - end - dv_value.validate = function(self, value) - return value - end - dv_opts = d_info:option(Value, "_opts") - dv_opts.forcewrite = true -- for write function using simpleform - dv_opts.write = function(self, section, value) - - table_info[section]._opts=value - end - dv_opts.validate = function(self, value) - return value - end - dv_opts.render = function(self, section, scope) - if table_info[section]._key==translate("Connect Network") then - self.template = "cbi/value" - self.keylist = {} - self.vallist = {} - self.placeholder = "10.1.1.254" - self.datatype = "ip4addr" - self.default=table_info[section]._opts - Value.render(self, section, scope) - else - self.rawhtml=true - self.template = "cbi/dvalue" - self.default=table_info[section]._opts - DummyValue.render(self, section, scope) - end - end - btn_update = d_info:option(Button, "_button") - btn_update.forcewrite = true - btn_update.render = function(self, section, scope) - if table_info[section]._button and table_info[section]._value ~= nil then - btn_update.inputtitle=table_info[section]._button - self.template = "cbi/button" - self.inputstyle = "edit" - Button.render(self, section, scope) - else - self.template = "cbi/dvalue" - self.default="" - DummyValue.render(self, section, scope) - end - end - btn_update.write = function(self, section, value) - local res - docker:clear_status() - if section == "01name" then - docker:append_status("Containers: rename " .. container_id .. "...") - local new_name = table_info[section]._value - res = dk.containers:rename({id = container_id, query = {name=new_name}}) - elseif section == "08restart" then - docker:append_status("Containers: update " .. container_id .. "...") - local new_restart = table_info[section]._value - res = dk.containers:update({id = container_id, body = {RestartPolicy = {Name = new_restart}}}) - elseif table_info[section]._key == translate("Network") then - local _,_,leave_network = table_info[section]._value:find("(.-) | .+") - leave_network = leave_network or table_info[section]._value - docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...") - res = dk.networks:disconnect({name = leave_network, body = {Container = container_id}}) - elseif section == "15connect" then - local connect_network = table_info[section]._value - local network_opiton - if connect_network ~= "none" and connect_network ~= "bridge" and connect_network ~= "host" then - network_opiton = table_info[section]._opts ~= "" and { - IPAMConfig={ - IPv4Address=table_info[section]._opts - } - } or nil - end - docker:append_status("Network: connect " .. connect_network .. container_id .. "...") - res = dk.networks:connect({name = connect_network, body = {Container = container_id, EndpointConfig= network_opiton}}) - end - if res and res.code > 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - else - docker:clear_status() - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info")) - end - --- info end +s = m:section(SimpleSection) +s.template = "dockerman/container" + +if action == "info" then + m.submit = false + m.reset = false + table_info = { + ["01name"] = { + _key = translate("Name"), + _value = container_info.Name:sub(2) or "-", + _button=translate("Update") + }, + ["02id"] = { + _key = translate("ID"), + _value = container_info.Id or "-" + }, + ["03image"] = { + _key = translate("Image"), + _value = container_info.Config.Image .. "<br>" .. container_info.Image + }, + ["04status"] = { + _key = translate("Status"), + _value = container_info.State and container_info.State.Status or "-" + }, + ["05created"] = { + _key = translate("Created"), + _value = container_info.Created or "-" + }, + } + + if container_info.State.Status == "running" then + table_info["06start"] = { + _key = translate("Start Time"), + _value = container_info.State and container_info.State.StartedAt or "-" + } + else + table_info["06start"] = { + _key = translate("Finish Time"), + _value = container_info.State and container_info.State.FinishedAt or "-" + } + end + + table_info["07healthy"] = { + _key = translate("Healthy"), + _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-" + } + table_info["08restart"] = { + _key = translate("Restart Policy"), + _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", + _button=translate("Update") + } + table_info["081user"] = { + _key = translate("User"), + _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-" + } + table_info["09mount"] = { + _key = translate("Mount/Volume"), + _value = get_mounts(container_info) or "-" + } + table_info["10cmd"] = { + _key = translate("Command"), + _value = get_command(container_info) or "-" + } + table_info["11env"] = { + _key = translate("Env"), + _value = get_env(container_info) or "-" + } + table_info["12ports"] = { + _key = translate("Ports"), + _value = get_ports(container_info) or "-" + } + table_info["13links"] = { + _key = translate("Links"), + _value = get_links(container_info) or "-" + } + table_info["14device"] = { + _key = translate("Device"), + _value = get_device(container_info) or "-" + } + table_info["15tmpfs"] = { + _key = translate("Tmpfs"), + _value = get_tmpfs(container_info) or "-" + } + table_info["16dns"] = { + _key = translate("DNS"), + _value = get_dns(container_info) or "-" + } + table_info["17sysctl"] = { + _key = translate("Sysctl"), + _value = get_sysctl(container_info) or "-" + } + + info_networks = get_networks(container_info) + list_networks = {} + for _, v in ipairs (networks) do + if v.Name then + local parent = v.Options and v.Options.parent or nil + local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil + ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil + local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") + list_networks[v.Name] = network_name + end + end + + if type(info_networks)== "table" then + for k,v in pairs(info_networks) do + table_info["14network"..k] = { + _key = translate("Network"), + value = k.. (v~="" and (" | ".. v) or ""), + _button=translate("Disconnect") + } + list_networks[k]=nil + end + end + + table_info["15connect"] = { + _key = translate("Connect Network"), + _value = list_networks ,_opts = "", + _button=translate("Connect") + } + + s = m:section(Table,table_info) + s.nodescr=true + s.formvalue=function(self, section) + return table_info + end + + o = s:option(DummyValue, "_key", translate("Info")) + o.width = "20%" + + o = s:option(ListValue, "_value") + o.render = function(self, section, scope) + if table_info[section]._key == translate("Name") then + self:reset_values() + self.template = "cbi/value" + self.size = 30 + self.keylist = {} + self.vallist = {} + self.default=table_info[section]._value + Value.render(self, section, scope) + elseif table_info[section]._key == translate("Restart Policy") then + self.template = "cbi/lvalue" + self:reset_values() + self.size = nil + self:value("no", "No") + self:value("unless-stopped", "Unless stopped") + self:value("always", "Always") + self:value("on-failure", "On failure") + self.default=table_info[section]._value + ListValue.render(self, section, scope) + elseif table_info[section]._key == translate("Connect Network") then + self.template = "cbi/lvalue" + self:reset_values() + self.size = nil + for k,v in pairs(list_networks) do + if k ~= "host" then + self:value(k,v) + end + end + self.default=table_info[section]._value + ListValue.render(self, section, scope) + else + self:reset_values() + self.rawhtml=true + self.template = "cbi/dvalue" + self.default=table_info[section]._value + DummyValue.render(self, section, scope) + end + end + o.forcewrite = true + o.write = function(self, section, value) + table_info[section]._value=value + end + o.validate = function(self, value) + return value + end + + o = s:option(Value, "_opts") + o.forcewrite = true + o.write = function(self, section, value) + table_info[section]._opts=value + end + o.validate = function(self, value) + return value + end + o.render = function(self, section, scope) + if table_info[section]._key==translate("Connect Network") then + self.template = "cbi/value" + self.keylist = {} + self.vallist = {} + self.placeholder = "10.1.1.254" + self.datatype = "ip4addr" + self.default=table_info[section]._opts + Value.render(self, section, scope) + else + self.rawhtml=true + self.template = "cbi/dvalue" + self.default=table_info[section]._opts + DummyValue.render(self, section, scope) + end + end + + o = s:option(Button, "_button") + o.forcewrite = true + o.render = function(self, section, scope) + if table_info[section]._button and table_info[section]._value ~= nil then + self.inputtitle=table_info[section]._button + self.template = "cbi/button" + self.inputstyle = "edit" + Button.render(self, section, scope) + else + self.template = "cbi/dvalue" + self.default="" + DummyValue.render(self, section, scope) + end + end + o.write = function(self, section, value) + local res + + docker:clear_status() + + if section == "01name" then + docker:append_status("Containers: rename " .. container_id .. "...") + local new_name = table_info[section]._value + res = dk.containers:rename({ + id = container_id, + query = { + name=new_name + } + }) + elseif section == "08restart" then + docker:append_status("Containers: update " .. container_id .. "...") + local new_restart = table_info[section]._value + res = dk.containers:update({ + id = container_id, + body = { + RestartPolicy = { + Name = new_restart + } + } + }) + elseif table_info[section]._key == translate("Network") then + local _,_,leave_network + + _, _, leave_network = table_info[section]._value:find("(.-) | .+") + leave_network = leave_network or table_info[section]._value + docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...") + res = dk.networks:disconnect({ + name = leave_network, + body = { + Container = container_id + } + }) + elseif section == "15connect" then + local connect_network = table_info[section]._value + local network_opiton + if connect_network ~= "none" + and connect_network ~= "bridge" + and connect_network ~= "host" then + + network_opiton = table_info[section]._opts ~= "" and { + IPAMConfig={ + IPv4Address=table_info[section]._opts + } + } or nil + end + docker:append_status("Network: connect " .. connect_network .. container_id .. "...") + res = dk.networks:connect({ + name = connect_network, + body = { + Container = container_id, + EndpointConfig= network_opiton + } + }) + end + + if res and res.code > 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + else + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info")) + end elseif action == "resources" then - local resources_section= m:section(SimpleSection) - d = resources_section:option( Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit.")) - d.placeholder = "1.5" - d.rmempty = true - d.datatype="ufloat" - d.default = container_info.HostConfig.NanoCpus / (10^9) - - d = resources_section:option(Value, "cpushares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024.")) - d.placeholder = "1024" - d.rmempty = true - d.datatype="uinteger" - d.default = container_info.HostConfig.CpuShares - - d = resources_section:option(Value, "memory", translate("Memory"), translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.")) - d.placeholder = "128m" - d.rmempty = true - d.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0 - - d = resources_section:option(Value, "blkioweight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000.")) - d.placeholder = "500" - d.rmempty = true - d.datatype="uinteger" - d.default = container_info.HostConfig.BlkioWeight - - m.handle = function(self, state, data) - if state == FORM_VALID then - local memory = data.memory - if memory and memory ~= 0 then - _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") - if n then - unit = unit and unit:sub(1,1):upper() or "B" - if unit == "M" then - memory = tonumber(n) * 1024 * 1024 - elseif unit == "G" then - memory = tonumber(n) * 1024 * 1024 * 1024 - elseif unit == "K" then - memory = tonumber(n) * 1024 - else - memory = tonumber(n) - end - end - end - request_body = { - BlkioWeight = tonumber(data.blkioweight), - NanoCPUs = tonumber(data.cpus)*10^9, - Memory = tonumber(memory), - CpuShares = tonumber(data.cpushares) - } - docker:write_status("Containers: update " .. container_id .. "...") - local res = dk.containers:update({id = container_id, body = request_body}) - if res and res.code >= 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - else - docker:clear_status() - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources")) - end - end + s = m:section(SimpleSection) + o = s:option( Value, "cpus", + translate("CPUs"), + translate("Number of CPUs. Number is a fractional number. 0.000 means no limit.")) + o.placeholder = "1.5" + o.rmempty = true + o.datatype="ufloat" + o.default = container_info.HostConfig.NanoCpus / (10^9) + + o = s:option(Value, "cpushares", + translate("CPU Shares Weight"), + translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024.")) + o.placeholder = "1024" + o.rmempty = true + o.datatype="uinteger" + o.default = container_info.HostConfig.CpuShares + + o = s:option(Value, "memory", + translate("Memory"), + translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.")) + o.placeholder = "128m" + o.rmempty = true + o.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0 + + o = s:option(Value, "blkioweight", + translate("Block IO Weight"), + translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000.")) + o.placeholder = "500" + o.rmempty = true + o.datatype="uinteger" + o.default = container_info.HostConfig.BlkioWeight + + m.handle = function(self, state, data) + if state == FORM_VALID then + local memory = data.memory + if memory and memory ~= 0 then + _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") + if n then + unit = unit and unit:sub(1,1):upper() or "B" + if unit == "M" then + memory = tonumber(n) * 1024 * 1024 + elseif unit == "G" then + memory = tonumber(n) * 1024 * 1024 * 1024 + elseif unit == "K" then + memory = tonumber(n) * 1024 + else + memory = tonumber(n) + end + end + end + + request_body = { + BlkioWeight = tonumber(data.blkioweight), + NanoCPUs = tonumber(data.cpus)*10^9, + Memory = tonumber(memory), + CpuShares = tonumber(data.cpushares) + } + + docker:write_status("Containers: update " .. container_id .. "...") + local res = dk.containers:update({id = container_id, body = request_body}) + if res and res.code >= 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + else + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources")) + end + end + elseif action == "file" then - local filesection= m:section(SimpleSection) - m.submit = false - m.reset = false - filesection.template = "dockerman/container_file" - filesection.container = container_id + s = m:section(SimpleSection) + s.template = "dockerman/container_file" + s.container = container_id + m.submit = false + m.reset = false elseif action == "inspect" then - local inspectsection= m:section(SimpleSection) - inspectsection.syslog = luci.jsonc.stringify(container_info, true) - inspectsection.title = translate("Container Inspect") - inspectsection.template = "dockerman/logs" - m.submit = false - m.reset = false + s = m:section(SimpleSection) + s.syslog = luci.jsonc.stringify(container_info, true) + s.title = translate("Container Inspect") + s.template = "dockerman/logs" + m.submit = false + m.reset = false elseif action == "logs" then - local logsection= m:section(SimpleSection) - local logs = "" - local query ={ - stdout = 1, - stderr = 1, - tail = 1000 - } - local logs = dk.containers:logs({id = container_id, query = query}) - if logs.code == 200 then - logsection.syslog=logs.body - else - logsection.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body - end - logsection.title=translate("Container Logs") - logsection.template = "dockerman/logs" - m.submit = false - m.reset = false + local logs = "" + local query ={ + stdout = 1, + stderr = 1, + tail = 1000 + } + + s = m:section(SimpleSection) + + logs = dk.containers:logs({id = container_id, query = query}) + if logs.code == 200 then + s.syslog=logs.body + else + s.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body + end + + s.title=translate("Container Logs") + s.template = "dockerman/logs" + m.submit = false + m.reset = false elseif action == "console" then - m.submit = false - m.reset = false - local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil - local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil - if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then - local consolesection= m:section(SimpleSection) - local cmd = "/bin/sh" - local uid - local vcommand = consolesection:option(Value, "command", translate("Command")) - vcommand:value("/bin/sh", "/bin/sh") - vcommand:value("/bin/ash", "/bin/ash") - vcommand:value("/bin/bash", "/bin/bash") - vcommand.default = "/bin/sh" - vcommand.forcewrite = true - vcommand.write = function(self, section, value) - cmd = value - end - local vuid = consolesection:option(Value, "uid", translate("UID")) - vuid.forcewrite = true - vuid.write = function(self, section, value) - uid = value - end - local btn_connect = consolesection:option(Button, "connect") - btn_connect.render = function(self, section, scope) - self.inputstyle = "add" - self.title = " " - self.inputtitle = translate("Connect") - Button.render(self, section, scope) - end - btn_connect.write = function(self, section) - local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil - local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil - if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$") then return end - local kill_ttyd = 'netstat -lnpt | grep ":7682[ \t].*ttyd$" | awk \'{print $NF}\' | awk -F\'/\' \'{print "kill -9 " $1}\' | sh > /dev/null' - luci.util.exec(kill_ttyd) - local hosts - local uci = (require "luci.model.uci").cursor() - local remote = uci:get("dockerman", "local", "remote_endpoint") - local socket_path = (remote == "false" or not remote) and uci:get("dockerman", "local", "socket_path") or nil - local host = (remote == "true") and uci:get("dockerman", "local", "remote_host") or nil - local port = (remote == "true") and uci:get("dockerman", "local", "remote_port") or nil - if remote and host and port then - hosts = host .. ':'.. port - elseif socket_path then - hosts = "unix://" .. socket_path - else - return - end - local start_cmd = cmd_ttyd .. ' -d 2 --once -p 7682 '.. cmd_docker .. ' -H "'.. hosts ..'" exec -it ' .. (uid and uid ~= "" and (" -u ".. uid .. ' ') or "").. container_id .. ' ' .. cmd .. ' &' - os.execute(start_cmd) - local console = consolesection:option(DummyValue, "console") - console.container_id = container_id - console.template = "dockerman/container_console" - end - end + m.submit = false + m.reset = false + local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil + local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil + + if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then + local cmd = "/bin/sh" + local uid + + s = m:section(SimpleSection) + + o = s:option(Value, "command", translate("Command")) + o:value("/bin/sh", "/bin/sh") + o:value("/bin/ash", "/bin/ash") + o:value("/bin/bash", "/bin/bash") + o.default = "/bin/sh" + o.forcewrite = true + o.write = function(self, section, value) + cmd = value + end + + o = s:option(Value, "uid", translate("UID")) + o.forcewrite = true + o.write = function(self, section, value) + uid = value + end + + o = s:option(Button, "connect") + o.render = function(self, section, scope) + self.inputstyle = "add" + self.title = " " + self.inputtitle = translate("Connect") + Button.render(self, section, scope) + end + o.write = function(self, section) + local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil + local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil + + if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$")then + return + end + + local kill_ttyd = 'netstat -lnpt | grep ":7682[ \t].*ttyd$" | awk \'{print $NF}\' | awk -F\'/\' \'{print "kill -9 " $1}\' | sh > /dev/null' + luci.util.exec(kill_ttyd) + local hosts + local uci = (require "luci.model.uci").cursor() + local remote = uci:get_bool("dockerd", "globals", "remote_endpoint") + local host = nil + local port = nil + local socket = nil + + if remote then + host = uci:get("dockerd", "globals", "remote_host") or nil + port = uci:get("dockerd", "globals", "remote_port") or nil + else + socket = uci:get("dockerd", "globals", "socket_path") or nil + end + + if remote and host and port then + hosts = host .. ':'.. port + elseif socket_path then + hosts = socket_path + else + return + end + + local start_cmd = cmd_ttyd .. ' -d 2 --once -p 7682 '.. cmd_docker .. ' -H "'.. hosts ..'" exec -it ' .. (uid and uid ~= "" and (" -u ".. uid .. ' ') or "").. container_id .. ' ' .. cmd .. ' &' + os.execute(start_cmd) + + o = s:option(DummyValue, "console") + o.container_id = container_id + o.template = "dockerman/container_console" + end + end elseif action == "stats" then - local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}}) - local container_top - if response.code == 200 then - container_top=response.body - else - response = dk.containers:top({id = container_id}) - if response.code == 200 then - container_top=response.body - end - end - - if type(container_top) == "table" then - container_top=response.body - stat_section = m:section(SimpleSection) - stat_section.container_id = container_id - stat_section.template = "dockerman/container_stats" - table_stats = {cpu={key=translate("CPU Useage"),value='-'},memory={key=translate("Memory Useage"),value='-'}} - stat_section = m:section(Table, table_stats, translate("Stats")) - stat_section:option(DummyValue, "key", translate("Stats")).width="33%" - stat_section:option(DummyValue, "value") - top_section= m:section(Table, container_top.Processes, translate("TOP")) - for i, v in ipairs(container_top.Titles) do - top_section:option(DummyValue, i, translate(v)) - end -end -m.submit = false -m.reset = false + local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}}) + local container_top + + if response.code == 200 then + container_top=response.body + else + response = dk.containers:top({id = container_id}) + if response.code == 200 then + container_top=response.body + end + end + + if type(container_top) == "table" then + s = m:section(SimpleSection) + s.container_id = container_id + s.template = "dockerman/container_stats" + table_stats = { + cpu={ + key=translate("CPU Useage"), + value='-' + }, + memory={ + key=translate("Memory Useage"), + value='-' + } + } + + container_top = response.body + s = m:section(Table, table_stats, translate("Stats")) + s:option(DummyValue, "key", translate("Stats")).width="33%" + s:option(DummyValue, "value") + top_section = m:section(Table, container_top.Processes, translate("TOP")) + for i, v in ipairs(container_top.Titles) do + top_section:option(DummyValue, i, translate(v)) + end + end + + m.submit = false + m.reset = false end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua index 2187de4662..cb1d2d943f 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua @@ -3,193 +3,229 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" local http = require "luci.http" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" + +local m, s, o +local images, networks, containers, res + local dk = docker.new() +res = dk.images:list() +if res.code <300 then + images = res.body +else + return +end -local images, networks, containers -local res = dk.images:list() -if res.code <300 then images = res.body else return end res = dk.networks:list() -if res.code <300 then networks = res.body else return end -res = dk.containers:list({query = {all=true}}) -if res.code <300 then containers = res.body else return end +if res.code <300 then + networks = res.body +else + return +end + +res = dk.containers:list({ + query = { + all=true + } +}) +if res.code <300 then + containers = res.body +else + return +end local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode function get_containers() - local data = {} - if type(containers) ~= "table" then return nil end - for i, v in ipairs(containers) do - local index = v.Created .. v.Id - data[index]={} - data[index]["_selected"] = 0 - data[index]["_id"] = v.Id:sub(1,12) - data[index]["name"] = v.Names[1]:sub(2) - data[index]["_name"] = '<a href='..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. v.Names[1]:sub(2).."</a>" - data[index]["_status"] = v.Status - if v.Status:find("^Up") then - data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>" - else - data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>" - end - if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then - for networkname, netconfig in pairs(v.NetworkSettings.Networks) do - data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "") - end - end - -- networkmode = v.HostConfig.NetworkMode ~= "default" and v.HostConfig.NetworkMode or "bridge" - -- data[index]["_network"] = v.NetworkSettings.Networks[networkmode].IPAddress or nil - -- local _, _, image = v.Image:find("^sha256:(.+)") - -- if image ~= nil then - -- image=image:sub(1,12) - -- end - if v.Ports and next(v.Ports) ~= nil then - data[index]["_ports"] = nil - for _,v2 in ipairs(v.Ports) do - data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "") - .. ((v2.PublicPort and v2.Type and v2.Type == "tcp") and ('<a href="javascript:void(0);" onclick="window.open((window.location.origin.match(/^(.+):\\d+$/) && window.location.origin.match(/^(.+):\\d+$/)[1] || window.location.origin) + \':\' + '.. v2.PublicPort ..', \'_blank\');">') or "") - .. (v2.PublicPort and (v2.PublicPort .. ":") or "") .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "") - .. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "</a>" or "") - end - end - for ii,iv in ipairs(images) do - if iv.Id == v.ImageID then - data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>") - end - end - - data[index]["_image_id"] = v.ImageID:sub(8,20) - data[index]["_command"] = v.Command - end - return data + local data = {} + + if type(containers) ~= "table" then + return nil + end + + for i, v in ipairs(containers) do + local index = v.Created .. v.Id + + data[index]={} + data[index]["_selected"] = 0 + data[index]["_id"] = v.Id:sub(1,12) + data[index]["name"] = v.Names[1]:sub(2) + data[index]["_name"] = '<a href='..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. v.Names[1]:sub(2).."</a>" + data[index]["_status"] = v.Status + + if v.Status:find("^Up") then + data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>" + else + data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>" + end + + if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then + for networkname, netconfig in pairs(v.NetworkSettings.Networks) do + data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "") + end + end + + if v.Ports and next(v.Ports) ~= nil then + data[index]["_ports"] = nil + for _,v2 in ipairs(v.Ports) do + data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "") + .. ((v2.PublicPort and v2.Type and v2.Type == "tcp") and ('<a href="javascript:void(0);" onclick="window.open((window.location.origin.match(/^(.+):\\d+$/) && window.location.origin.match(/^(.+):\\d+$/)[1] || window.location.origin) + \':\' + '.. v2.PublicPort ..', \'_blank\');">') or "") + .. (v2.PublicPort and (v2.PublicPort .. ":") or "") .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "") + .. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "</a>" or "") + end + end + + for ii,iv in ipairs(images) do + if iv.Id == v.ImageID then + data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>") + end + end + + data[index]["_image_id"] = v.ImageID:sub(8,20) + data[index]["_command"] = v.Command + end + + return data end -local c_lists = get_containers() --- list Containers --- m = Map("docker", translate("Docker")) +local container_list = get_containers() + m = SimpleForm("docker", translate("Docker")) m.submit=false m.reset=false -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - -c_table = m:section(Table, c_lists, translate("Containers")) -c_table.nodescr=true --- v.template = "cbi/tblsection" --- v.sortable = true -container_selecter = c_table:option(Flag, "_selected","") -container_selecter.disabled = 0 -container_selecter.enabled = 1 -container_selecter.default = 0 - -container_id = c_table:option(DummyValue, "_id", translate("ID")) -container_id.width="10%" -container_name = c_table:option(DummyValue, "_name", translate("Container Name")) -container_name.rawhtml = true -container_status = c_table:option(DummyValue, "_status", translate("Status")) -container_status.width="15%" -container_status.rawhtml=true -container_ip = c_table:option(DummyValue, "_network", translate("Network")) -container_ip.width="15%" -container_ports = c_table:option(DummyValue, "_ports", translate("Ports")) -container_ports.width="10%" -container_ports.rawhtml = true -container_image = c_table:option(DummyValue, "_image", translate("Image")) -container_image.width="10%" -container_command = c_table:option(DummyValue, "_command", translate("Command")) -container_command.width="20%" - -container_selecter.write=function(self, section, value) - c_lists[section]._selected = value +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(Table, container_list, translate("Containers")) +s.nodescr=true + +o = s:option(Flag, "_selected","") +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.write=function(self, section, value) + container_list[section]._selected = value end +o = s:option(DummyValue, "_id", translate("ID")) +o.width="10%" + +o = s:option(DummyValue, "_name", translate("Container Name")) +o.rawhtml = true + +o = s:option(DummyValue, "_status", translate("Status")) +o.width="15%" +o.rawhtml=true + +o = s:option(DummyValue, "_network", translate("Network")) +o.width="15%" + +o = s:option(DummyValue, "_ports", translate("Ports")) +o.width="10%" +o.rawhtml = true + +o = s:option(DummyValue, "_image", translate("Image")) +o.width="10%" + +o = s:option(DummyValue, "_command", translate("Command")) +o.width="20%" + local start_stop_remove = function(m,cmd) - local c_selected = {} - -- 遍历table中sectionid - local c_table_sids = c_table:cfgsections() - for _, c_table_sid in ipairs(c_table_sids) do - -- 得到选中项的名字 - if c_lists[c_table_sid]._selected == 1 then - c_selected[#c_selected+1] = c_lists[c_table_sid].name --container_name:cfgvalue(c_table_sid) - end - end - if #c_selected >0 then - docker:clear_status() - local success = true - for _,cont in ipairs(c_selected) do - docker:append_status("Containers: " .. cmd .. " " .. cont .. "...") - local res = dk.containers[cmd](dk, {id = cont}) - if res and res.code >= 300 then - success = false - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") - else - docker:append_status("done\n") - end - end - if success then docker:clear_status() end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) - end + local container_selected = {} + + for k in pairs(container_list) do + if container_list[k]._selected == 1 then + container_selected[#container_selected + 1] = container_list[k].name + end + end + + if #container_selected > 0 then + local success = true + + docker:clear_status() + for _, cont in ipairs(container_selected) do + docker:append_status("Containers: " .. cmd .. " " .. cont .. "...") + local res = dk.containers[cmd](dk, {id = cont}) + if res and res.code >= 300 then + success = false + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") + else + docker:append_status("done\n") + end + end + + if success then + docker:clear_status() + end + + luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) + end end -action_section = m:section(Table,{{}}) -action_section.notitle=true -action_section.rowcolors=false -action_section.template="cbi/nullsection" - -btnnew=action_section:option(Button, "_new") -btnnew.inputtitle= translate("New") -btnnew.template = "dockerman/cbi/inlinebutton" -btnnew.inputstyle = "add" -btnnew.forcewrite = true -btnstart=action_section:option(Button, "_start") -btnstart.template = "dockerman/cbi/inlinebutton" -btnstart.inputtitle=translate("Start") -btnstart.inputstyle = "apply" -btnstart.forcewrite = true -btnrestart=action_section:option(Button, "_restart") -btnrestart.template = "dockerman/cbi/inlinebutton" -btnrestart.inputtitle=translate("Restart") -btnrestart.inputstyle = "reload" -btnrestart.forcewrite = true -btnstop=action_section:option(Button, "_stop") -btnstop.template = "dockerman/cbi/inlinebutton" -btnstop.inputtitle=translate("Stop") -btnstop.inputstyle = "reset" -btnstop.forcewrite = true -btnkill=action_section:option(Button, "_kill") -btnkill.template = "dockerman/cbi/inlinebutton" -btnkill.inputtitle=translate("Kill") -btnkill.inputstyle = "reset" -btnkill.forcewrite = true -btnremove=action_section:option(Button, "_remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputtitle=translate("Remove") -btnremove.inputstyle = "remove" -btnremove.forcewrite = true -btnnew.write = function(self, section) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "_new") +o.inputtitle= translate("New") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "add" +o.forcewrite = true +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) end -btnstart.write = function(self, section) - start_stop_remove(m,"start") + +o = s:option(Button, "_start") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Start") +o.inputstyle = "apply" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"start") end -btnrestart.write = function(self, section) - start_stop_remove(m,"restart") + +o = s:option(Button, "_restart") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Restart") +o.inputstyle = "reload" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"restart") end -btnremove.write = function(self, section) - start_stop_remove(m,"remove") + +o = s:option(Button, "_stop") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Stop") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"stop") end -btnstop.write = function(self, section) - start_stop_remove(m,"stop") + +o = s:option(Button, "_kill") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Kill") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"kill") end -btnkill.write = function(self, section) - start_stop_remove(m,"kill") + +o = s:option(Button, "_remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Remove") +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"remove") end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua index 29d4a63573..01a7e3f237 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua @@ -3,221 +3,272 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" local dk = docker.new() -local containers, images -local res = dk.images:list() -if res.code <300 then images = res.body else return end -res = dk.containers:list({query = {all=true}}) -if res.code <300 then containers = res.body else return end +local containers, images, res +local m, s, o + +res = dk.images:list() +if res.code < 300 then + images = res.body +else + return +end + +res = dk.containers:list({ + query = { + all=true + } +}) +if res.code < 300 then + containers = res.body +else + return +end function get_images() - local data = {} - for i, v in ipairs(images) do - local index = v.Created .. v.Id - data[index]={} - data[index]["_selected"] = 0 - data[index]["id"] = v.Id:sub(8) - data[index]["_id"] = '<a href="javascript:new_tag(\''..v.Id:sub(8,20)..'\')" class="dockerman-link" title="'..translate("New tag")..'">' .. v.Id:sub(8,20) .. '</a>' - if v.RepoTags and next(v.RepoTags)~=nil then - for i, v1 in ipairs(v.RepoTags) do - data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "<br>" )or "") .. ((v1:match("<none>") or (#v.RepoTags == 1)) and v1 or ('<a href="javascript:un_tag(\''..v1..'\')" class="dockerman_link" title="'..translate("Remove tag")..'" >' .. v1 .. '</a>')) - if not data[index]["tag"] then - data[index]["tag"] = v1--:match("<none>") and nil or v1 - end - end - else - data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+") - data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>" - end - data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","<none>") - -- data[index]["_tags"] = '<a href="javascript:handle_tag(\''..data[index]["_id"]..'\')">' .. data[index]["_tags"] .. '</a>' - for ci,cv in ipairs(containers) do - if v.Id == cv.ImageID then - data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. - '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2).."</a>" - end - end - data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB" - data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created) - end - return data + local data = {} + + for i, v in ipairs(images) do + local index = v.Created .. v.Id + + data[index]={} + data[index]["_selected"] = 0 + data[index]["id"] = v.Id:sub(8) + data[index]["_id"] = '<a href="javascript:new_tag(\''..v.Id:sub(8,20)..'\')" class="dockerman-link" title="'..translate("New tag")..'">' .. v.Id:sub(8,20) .. '</a>' + + if v.RepoTags and next(v.RepoTags)~=nil then + for i, v1 in ipairs(v.RepoTags) do + data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "<br>" )or "") .. ((v1:match("<none>") or (#v.RepoTags == 1)) and v1 or ('<a href="javascript:un_tag(\''..v1..'\')" class="dockerman_link" title="'..translate("Remove tag")..'" >' .. v1 .. '</a>')) + + if not data[index]["tag"] then + data[index]["tag"] = v1 + end + end + else + data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+") + data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>" + end + + data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","<none>") + for ci,cv in ipairs(containers) do + if v.Id == cv.ImageID then + data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. + '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2).."</a>" + end + end + + data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB" + data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created) + end + + return data end local image_list = get_images() --- m = Map("docker", translate("Docker")) m = SimpleForm("docker", translate("Docker")) m.submit=false m.reset=false -local pull_value={_image_tag_name="", _registry="index.docker.io"} -local pull_section = m:section(SimpleSection, translate("Pull Image")) -pull_section.template="cbi/nullsection" -local tag_name = pull_section:option(Value, "_image_tag_name") -tag_name.template = "dockerman/cbi/inlinevalue" -tag_name.placeholder="lisaac/luci:latest" -local action_pull = pull_section:option(Button, "_pull") -action_pull.inputtitle= translate("Pull") -action_pull.template = "dockerman/cbi/inlinebutton" -action_pull.inputstyle = "add" -tag_name.write = function(self, section, value) - local hastag = value:find(":") - if not hastag then - value = value .. ":latest" - end - pull_value["_image_tag_name"] = value +local pull_value={ + _image_tag_name="", + _registry="index.docker.io" +} + +s = m:section(SimpleSection, translate("Pull Image")) +s.template="cbi/nullsection" + +o = s:option(Value, "_image_tag_name") +o.template = "dockerman/cbi/inlinevalue" +o.placeholder="lisaac/luci:latest" +o.write = function(self, section, value) + local hastag = value:find(":") + + if not hastag then + value = value .. ":latest" + end + pull_value["_image_tag_name"] = value end -action_pull.write = function(self, section) - local tag = pull_value["_image_tag_name"] - local json_stringify = luci.jsonc and luci.jsonc.stringify - if tag and tag ~= "" then - docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n") - -- local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server})) , header={["X-Registry-Auth"] = x_auth} - local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb) - -- {"errorDetail": {"message": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }, "error": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" } - if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then - docker:clear_status() - else - docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") - end - else - docker:append_status("code: 400 please input the name of image name!") - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) + +o = s:option(Button, "_pull") +o.inputtitle= translate("Pull") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "add" +o.write = function(self, section) + local tag = pull_value["_image_tag_name"] + local json_stringify = luci.jsonc and luci.jsonc.stringify + + if tag and tag ~= "" then + docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n") + local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb) + + if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then + docker:clear_status() + else + docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") + end + else + docker:append_status("code: 400 please input the name of image name!") + end + + luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) end -local import_section = m:section(SimpleSection, translate("Import Images")) -local im = import_section:option(DummyValue, "_image_import") -im.template = "dockerman/images_import" - -local image_table = m:section(Table, image_list, translate("Images")) - -local image_selecter = image_table:option(Flag, "_selected","") -image_selecter.disabled = 0 -image_selecter.enabled = 1 -image_selecter.default = 0 - -local image_id = image_table:option(DummyValue, "_id", translate("ID")) -image_id.rawhtml = true -image_table:option(DummyValue, "_tags", translate("RepoTags")).rawhtml = true -image_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true -image_table:option(DummyValue, "_size", translate("Size")) -image_table:option(DummyValue, "_created", translate("Created")) -image_selecter.write = function(self, section, value) - image_list[section]._selected = value +s = m:section(SimpleSection, translate("Import Images")) + +o = s:option(DummyValue, "_image_import") +o.template = "dockerman/images_import" + +s = m:section(Table, image_list, translate("Images")) + +o = s:option(Flag, "_selected","") +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.write = function(self, section, value) + image_list[section]._selected = value end +o = s:option(DummyValue, "_tags", translate("RepoTags")) +o.rawhtml = true + +o = s:option(DummyValue, "_containers", translate("Containers")) +o.rawhtml = true + +o = s:option(DummyValue, "_size", translate("Size")) + +o = s:option(DummyValue, "_created", translate("Created")) + +o = s:option(DummyValue, "_id", translate("ID")) +o.rawhtml = true + local remove_action = function(force) - local image_selected = {} - -- 遍历table中sectionid - local image_table_sids = image_table:cfgsections() - for _, image_table_sid in ipairs(image_table_sids) do - -- 得到选中项的名字 - if image_list[image_table_sid]._selected == 1 then - image_selected[#image_selected+1] = (image_list[image_table_sid]["_tags"]:match("<br>") or image_list[image_table_sid]["_tags"]:match("<none>")) and image_list[image_table_sid].id or image_list[image_table_sid].tag - end - end - if next(image_selected) ~= nil then - local success = true - docker:clear_status() - for _,img in ipairs(image_selected) do - docker:append_status("Images: " .. "remove" .. " " .. img .. "...") - local query - if force then query = {force = true} end - local msg = dk.images:remove({id = img, query = query}) - if msg.code ~= 200 then - docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") - success = false - else - docker:append_status("done\n") - end - end - if success then docker:clear_status() end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) - end + local image_selected = {} + + for k in pairs(image_list) do + if image_list[k]._selected == 1 then + image_selected[#image_selected+1] = (image_list[k]["_tags"]:match("<br>") or image_list[k]["_tags"]:match("<none>")) and image_list[k].id or image_list[k].tag + end + end + + if next(image_selected) ~= nil then + local success = true + + docker:clear_status() + for _, img in ipairs(image_selected) do + local query + docker:append_status("Images: " .. "remove" .. " " .. img .. "...") + + if force then + query = {force = true} + end + + local msg = dk.images:remove({ + id = img, + query = query + }) + if msg.code ~= 200 then + docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") + success = false + else + docker:append_status("done\n") + end + end + + if success then + docker:clear_status() + end + + luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) + end end -local docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err = docker:read_status() -docker_status.err = docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - -local action = m:section(Table,{{}}) -action.notitle=true -action.rowcolors=false -action.template="cbi/nullsection" - -local btnremove = action:option(Button, "remove") -btnremove.inputtitle= translate("Remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputstyle = "remove" -btnremove.forcewrite = true -btnremove.write = function(self, section) - remove_action() +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err = docker:read_status() +s.err = s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() end -local btnforceremove = action:option(Button, "forceremove") -btnforceremove.inputtitle= translate("Force Remove") -btnforceremove.template = "dockerman/cbi/inlinebutton" -btnforceremove.inputstyle = "remove" -btnforceremove.forcewrite = true -btnforceremove.write = function(self, section) - remove_action(true) +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "remove") +o.inputtitle= translate("Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + remove_action() +end + +o = s:option(Button, "forceremove") +o.inputtitle= translate("Force Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + remove_action(true) end -local btnsave = action:option(Button, "save") -btnsave.inputtitle= translate("Save") -btnsave.template = "dockerman/cbi/inlinebutton" -btnsave.inputstyle = "edit" -btnsave.forcewrite = true -btnsave.write = function (self, section) - local image_selected = {} - local image_table_sids = image_table:cfgsections() - for _, image_table_sid in ipairs(image_table_sids) do - if image_list[image_table_sid]._selected == 1 then - image_selected[#image_selected+1] = image_list[image_table_sid].id --image_id:cfgvalue(image_table_sid) - end - end - if next(image_selected) ~= nil then - local names - for _,img in ipairs(image_selected) do - names = names and (names .. "&names=".. img) or img - end - local first - local cb = function(res, chunk) - if res.code == 200 then - if not first then - first = true - luci.http.header('Content-Disposition', 'inline; filename="images.tar"') - luci.http.header('Content-Type', 'application\/x-tar') - end - luci.ltn12.pump.all(chunk, luci.http.write) - else - if not first then - first = true - luci.http.prepare_content("text/plain") - end - luci.ltn12.pump.all(chunk, luci.http.write) - end - end - docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...") - local msg = dk.images:get({query = {names = names}}, cb) - if msg.code ~= 200 then - docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") - success = false - else - docker:clear_status() - end - end +o = s:option(Button, "save") +o.inputtitle= translate("Save") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "edit" +o.forcewrite = true +o.write = function (self, section) + local image_selected = {} + + for k in pairs(image_list) do + if image_list[k]._selected == 1 then + image_selected[#image_selected + 1] = image_list[k].id + end + end + + if next(image_selected) ~= nil then + local names, first + + for _, img in ipairs(image_selected) do + names = names and (names .. "&names=".. img) or img + end + + local cb = function(res, chunk) + if res.code == 200 then + if not first then + first = true + luci.http.header('Content-Disposition', 'inline; filename="images.tar"') + luci.http.header('Content-Type', 'application\/x-tar') + end + luci.ltn12.pump.all(chunk, luci.http.write) + else + if not first then + first = true + luci.http.prepare_content("text/plain") + end + luci.ltn12.pump.all(chunk, luci.http.write) + end + end + + docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...") + local msg = dk.images:get({query = {names = names}}, cb) + + if msg.code ~= 200 then + docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") + success = false + else + docker:clear_status() + end + end end -local btnload = action:option(Button, "load") -btnload.inputtitle= translate("Load") -btnload.template = "dockerman/images_load" -btnload.inputstyle = "add" +o = s:option(Button, "load") +o.inputtitle= translate("Load") +o.template = "dockerman/images_load" +o.inputstyle = "add" + return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua index e8e392f6ab..4cea32915d 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua @@ -3,128 +3,150 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" -local dk = docker.new() -local networks -local res = dk.networks:list() -if res.code < 300 then networks = res.body else return end + +local m, s, o +local networks, dk, res + +dk = docker.new() +res = dk.networks:list() +if res.code < 300 then + networks = res.body +else + return +end local get_networks = function () - local data = {} - - if type(networks) ~= "table" then return nil end - for i, v in ipairs(networks) do - local index = v.Created .. v.Id - data[index]={} - data[index]["_selected"] = 0 - data[index]["_id"] = v.Id:sub(1,12) - data[index]["_name"] = v.Name - data[index]["_driver"] = v.Driver - if v.Driver == "bridge" then - data[index]["_interface"] = v.Options["com.docker.network.bridge.name"] - elseif v.Driver == "macvlan" then - data[index]["_interface"] = v.Options.parent - end - data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil - data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil - end - return data + local data = {} + + if type(networks) ~= "table" then + return nil + end + + for i, v in ipairs(networks) do + local index = v.Created .. v.Id + + data[index]={} + data[index]["_selected"] = 0 + data[index]["_id"] = v.Id:sub(1,12) + data[index]["_name"] = v.Name + data[index]["_driver"] = v.Driver + + if v.Driver == "bridge" then + data[index]["_interface"] = v.Options["com.docker.network.bridge.name"] + elseif v.Driver == "macvlan" then + data[index]["_interface"] = v.Options.parent + end + + data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil + data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil + end + + return data end local network_list = get_networks() --- m = Map("docker", translate("Docker")) + m = SimpleForm("docker", translate("Docker")) m.submit=false m.reset=false -network_table = m:section(Table, network_list, translate("Networks")) -network_table.nodescr=true - -network_selecter = network_table:option(Flag, "_selected","") -network_selecter.template = "dockerman/cbi/xfvalue" -network_id = network_table:option(DummyValue, "_id", translate("ID")) -network_selecter.disabled = 0 -network_selecter.enabled = 1 -network_selecter.default = 0 -network_selecter.render = function(self, section, scope) - self.disable = 0 - if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then - self.disable = 1 - end - Flag.render(self, section, scope) +s = m:section(Table, network_list, translate("Networks")) +s.nodescr=true + +o = s:option(Flag, "_selected","") +o.template = "dockerman/cbi/xfvalue" +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.render = function(self, section, scope) + self.disable = 0 + if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then + self.disable = 1 + end + Flag.render(self, section, scope) +end +o.write = function(self, section, value) + network_list[section]._selected = value end -network_name = network_table:option(DummyValue, "_name", translate("Network Name")) -network_driver = network_table:option(DummyValue, "_driver", translate("Driver")) -network_interface = network_table:option(DummyValue, "_interface", translate("Parent Interface")) -network_subnet = network_table:option(DummyValue, "_subnet", translate("Subnet")) -network_gateway = network_table:option(DummyValue, "_gateway", translate("Gateway")) +o = s:option(DummyValue, "_id", translate("ID")) -network_selecter.write = function(self, section, value) - network_list[section]._selected = value +o = s:option(DummyValue, "_name", translate("Network Name")) + +o = s:option(DummyValue, "_driver", translate("Driver")) + +o = s:option(DummyValue, "_interface", translate("Parent Interface")) + +o = s:option(DummyValue, "_subnet", translate("Subnet")) + +o = s:option(DummyValue, "_gateway", translate("Gateway")) + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err = docker:read_status() +s.err = s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() end -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - -action = m:section(Table,{{}}) -action.notitle=true -action.rowcolors=false -action.template="cbi/nullsection" -btnnew=action:option(Button, "_new") -btnnew.inputtitle= translate("New") -btnnew.template = "dockerman/cbi/inlinebutton" -btnnew.notitle=true -btnnew.inputstyle = "add" -btnnew.forcewrite = true -btnnew.write = function(self, section) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "_new") +o.inputtitle= translate("New") +o.template = "dockerman/cbi/inlinebutton" +o.notitle=true +o.inputstyle = "add" +o.forcewrite = true +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) end -btnremove = action:option(Button, "_remove") -btnremove.inputtitle= translate("Remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputstyle = "remove" -btnremove.forcewrite = true -btnremove.write = function(self, section) - local network_selected = {} - local network_name_selected = {} - local network_driver_selected = {} - -- 遍历table中sectionid - local network_table_sids = network_table:cfgsections() - for _, network_table_sid in ipairs(network_table_sids) do - -- 得到选中项的名字 - if network_list[network_table_sid]._selected == 1 then - network_selected[#network_selected+1] = network_list[network_table_sid]._id --network_name:cfgvalue(network_table_sid) - network_name_selected[#network_name_selected+1] = network_list[network_table_sid]._name - network_driver_selected[#network_driver_selected+1] = network_list[network_table_sid]._driver - end - end - if next(network_selected) ~= nil then - local success = true - docker:clear_status() - for ii, net in ipairs(network_selected) do - docker:append_status("Networks: " .. "remove" .. " " .. net .. "...") - local res = dk.networks["remove"](dk, {id = net}) - if res and res.code >= 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") - success = false - else - docker:append_status("done\n") - if network_driver_selected[ii] == "macvlan" then - docker.remove_macvlan_interface(network_name_selected[ii]) - end - end - end - if success then - docker:clear_status() - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) - end + +o = s:option(Button, "_remove") +o.inputtitle= translate("Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + local network_selected = {} + local network_name_selected = {} + local network_driver_selected = {} + + for k in pairs(network_list) do + if network_list[k]._selected == 1 then + network_selected[#network_selected + 1] = network_list[k]._id + network_name_selected[#network_name_selected + 1] = network_list[k]._name + network_driver_selected[#network_driver_selected + 1] = network_list[k]._driver + end + end + + if next(network_selected) ~= nil then + local success = true + docker:clear_status() + + for ii, net in ipairs(network_selected) do + docker:append_status("Networks: " .. "remove" .. " " .. net .. "...") + local res = dk.networks["remove"](dk, {id = net}) + + if res and res.code >= 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") + success = false + else + docker:append_status("done\n") + if network_driver_selected[ii] == "macvlan" then + docker.remove_macvlan_interface(network_name_selected[ii]) + end + end + end + + if success then + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) + end end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua index 324fc6dd70..0ee344370e 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua @@ -3,298 +3,502 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" + +local m, s, o + local dk = docker.new() + local cmd_line = table.concat(arg, '/') local create_body = {} local images = dk.images:list().body local networks = dk.networks:list().body -local containers = dk.containers:list({query = {all=true}}).body +local containers = dk.containers:list({ + query = { + all=true + } +}).body local is_quot_complete = function(str) - require "math" - if not str then return true end - local num = 0, w - for w in str:gmatch("\"") do - num = num + 1 - end - if math.fmod(num, 2) ~= 0 then return false end - num = 0 - for w in str:gmatch("\'") do - num = num + 1 - end - if math.fmod(num, 2) ~= 0 then return false end - return true + local num = 0, w + require "math" + + if not str then + return true + end + + local num = 0, w + for w in str:gmatch("\"") do + num = num + 1 + end + + if math.fmod(num, 2) ~= 0 then + return false + end + + num = 0 + for w in str:gmatch("\'") do + num = num + 1 + end + + if math.fmod(num, 2) ~= 0 then + return false + end + + return true +end + +function contains(list, x) + for _, v in pairs(list) do + if v == x then + return true + end + end + return false end local resolve_cli = function(cmd_line) - local config = {advance = 1} - local key_no_val = '|t|d|i|tty|rm|read_only|interactive|init|help|detach|privileged|P|publish_all|' - local key_with_val = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|blkio_weight|cgroup_parent|cidfile|cpu_period|cpu_quota|cpu_rt_period|cpu_rt_runtime|c|cpu_shares|cpus|cpuset_cpus|cpuset_mems|detach_keys|disable_content_trust|domainname|entrypoint|gpus|health_cmd|health_interval|health_retries|health_start_period|health_timeout|h|hostname|ip|ip6|ipc|isolation|kernel_memory|log_driver|mac_address|m|memory|memory_reservation|memory_swap|memory_swappiness|mount|name|network|no_healthcheck|oom_kill_disable|oom_score_adj|pid|pids_limit|restart|runtime|shm_size|sig_proxy|stop_signal|stop_timeout|ulimit|u|user|userns|uts|volume_driver|w|workdir|' - local key_abb = {net='network',a='attach',c='cpu-shares',d='detach',e='env',h='hostname',i='interactive',l='label',m='memory',p='publish',P='publish_all',t='tty',u='user',v='volume',w='workdir'} - local key_with_list = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|' - local key = nil - local _key = nil - local val = nil - local is_cmd = false - - cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)") - for w in cmd_line:gmatch("[^%s]+") do - if w =='\\' then - elseif not key and not _key and not is_cmd then - --key=val - key, val = w:match("^%-%-([%lP%-]-)=(.+)") - if not key then - --key val - key = w:match("^%-%-([%lP%-]+)") - if not key then - -- -v val - key = w:match("^%-([%lP%-]+)") - if key then - -- for -dit - if key:match("i") or key:match("t") or key:match("d") then - if key:match("i") then - config[key_abb["i"]] = true - key:gsub("i", "") - end - if key:match("t") then - config[key_abb["t"]] = true - key:gsub("t", "") - end - if key:match("d") then - config[key_abb["d"]] = true - key:gsub("d", "") - end - if key:match("P") then - config[key_abb["P"]] = true - key:gsub("P", "") - end - if key == "" then key = nil end - end - end - end - end - if key then - key = key:gsub("-","_") - key = key_abb[key] or key - if key_no_val:match("|"..key.."|") then - config[key] = true - val = nil - key = nil - elseif key_with_val:match("|"..key.."|") then - -- if key == "cap_add" then config.privileged = true end - else - key = nil - val = nil - end - else - config.image = w - key = nil - val = nil - is_cmd = true - end - elseif (key or _key) and not is_cmd then - if key == "mount" then - -- we need resolve mount options here - -- type=bind,source=/source,target=/app - local _type = w:match("^type=([^,]+),") or "bind" - local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or "" - local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or "" - local ro = w:match(",readonly") and "ro" or nil - if source and target then - if _type ~= "tmpfs" then - -- bind or volume - local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil - val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or "")) - else - -- tmpfs - local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil - local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil - key = "tmpfs" - val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "") - if not config[key] then config[key] = {} end - table.insert( config[key], val ) - key = nil - val = nil - end - end - else - val = w - end - elseif is_cmd then - config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w - end - if (key or _key) and val then - key = _key or key - if key_with_list:match("|"..key.."|") then - if not config[key] then config[key] = {} end - if _key then - config[key][#config[key]] = config[key][#config[key]] .. " " .. w - else - table.insert( config[key], val ) - end - if is_quot_complete(config[key][#config[key]]) then - -- clear quotation marks - config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "") - _key = nil - else - _key = key - end - else - config[key] = (config[key] and (config[key] .. " ") or "") .. val - if is_quot_complete(config[key]) then - -- clear quotation marks - config[key] = config[key]:gsub("[\"\']", "") - _key = nil - else - _key = key - end - end - key = nil - val = nil - end - end - return config + local config = { + advance = 1 + } + + local key_no_val = { + 't', + 'd', + 'i', + 'tty', + 'rm', + 'read_only', + 'interactive', + 'init', + 'help', + 'detach', + 'privileged', + 'P', + 'publish_all', + } + + local key_with_val = { + 'sysctl', + 'add_host', + 'a', + 'attach', + 'blkio_weight_device', + 'cap_add', + 'cap_drop', + 'device', + 'device_cgroup_rule', + 'device_read_bps', + 'device_read_iops', + 'device_write_bps', + 'device_write_iops', + 'dns', + 'dns_option', + 'dns_search', + 'e', + 'env', + 'env_file', + 'expose', + 'group_add', + 'l', + 'label', + 'label_file', + 'link', + 'link_local_ip', + 'log_driver', + 'log_opt', + 'network_alias', + 'p', + 'publish', + 'security_opt', + 'storage_opt', + 'tmpfs', + 'v', + 'volume', + 'volumes_from', + 'blkio_weight', + 'cgroup_parent', + 'cidfile', + 'cpu_period', + 'cpu_quota', + 'cpu_rt_period', + 'cpu_rt_runtime', + 'c', + 'cpu_shares', + 'cpus', + 'cpuset_cpus', + 'cpuset_mems', + 'detach_keys', + 'disable_content_trust', + 'domainname', + 'entrypoint', + 'gpus', + 'health_cmd', + 'health_interval', + 'health_retries', + 'health_start_period', + 'health_timeout', + 'h', + 'hostname', + 'ip', + 'ip6', + 'ipc', + 'isolation', + 'kernel_memory', + 'log_driver', + 'mac_address', + 'm', + 'memory', + 'memory_reservation', + 'memory_swap', + 'memory_swappiness', + 'mount', + 'name', + 'network', + 'no_healthcheck', + 'oom_kill_disable', + 'oom_score_adj', + 'pid', + 'pids_limit', + 'restart', + 'runtime', + 'shm_size', + 'sig_proxy', + 'stop_signal', + 'stop_timeout', + 'ulimit', + 'u', + 'user', + 'userns', + 'uts', + 'volume_driver', + 'w', + 'workdir' + } + + local key_abb = { + net='network', + a='attach', + c='cpu-shares', + d='detach', + e='env', + h='hostname', + i='interactive', + l='label', + m='memory', + p='publish', + P='publish_all', + t='tty', + u='user', + v='volume', + w='workdir' + } + + local key_with_list = { + 'sysctl', + 'add_host', + 'a', + 'attach', + 'blkio_weight_device', + 'cap_add', + 'cap_drop', + 'device', + 'device_cgroup_rule', + 'device_read_bps', + 'device_read_iops', + 'device_write_bps', + 'device_write_iops', + 'dns', + 'dns_optiondns_search', + 'e', + 'env', + 'env_file', + 'expose', + 'group_add', + 'l', + 'label', + 'label_file', + 'link', + 'link_local_ip', + 'log_driver', + 'log_opt', + 'network_alias', + 'p', + 'publish', + 'security_opt', + 'storage_opt', + 'tmpfs', + 'v', + 'volume', + 'volumes_from', + } + + local key = nil + local _key = nil + local val = nil + local is_cmd = false + + cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)") + for w in cmd_line:gmatch("[^%s]+") do + if w =='\\' then + elseif not key and not _key and not is_cmd then + --key=val + key, val = w:match("^%-%-([%lP%-]-)=(.+)") + if not key then + --key val + key = w:match("^%-%-([%lP%-]+)") + if not key then + -- -v val + key = w:match("^%-([%lP%-]+)") + if key then + -- for -dit + if key:match("i") or key:match("t") or key:match("d") then + if key:match("i") then + config[key_abb["i"]] = true + key:gsub("i", "") + end + if key:match("t") then + config[key_abb["t"]] = true + key:gsub("t", "") + end + if key:match("d") then + config[key_abb["d"]] = true + key:gsub("d", "") + end + if key:match("P") then + config[key_abb["P"]] = true + key:gsub("P", "") + end + if key == "" then + key = nil + end + end + end + end + end + if key then + key = key:gsub("-","_") + key = key_abb[key] or key + if contains(key_no_val, key) then + config[key] = true + val = nil + key = nil + elseif contains(key_with_val, key) then + -- if key == "cap_add" then config.privileged = true end + else + key = nil + val = nil + end + else + config.image = w + key = nil + val = nil + is_cmd = true + end + elseif (key or _key) and not is_cmd then + if key == "mount" then + -- we need resolve mount options here + -- type=bind,source=/source,target=/app + local _type = w:match("^type=([^,]+),") or "bind" + local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or "" + local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or "" + local ro = w:match(",readonly") and "ro" or nil + + if source and target then + if _type ~= "tmpfs" then + local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil + val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or "")) + else + local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil + local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil + key = "tmpfs" + val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "") + if not config[key] then + config[key] = {} + end + table.insert( config[key], val ) + key = nil + val = nil + end + end + else + val = w + end + elseif is_cmd then + config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w + end + if (key or _key) and val then + key = _key or key + if contains(key_with_list, key) then + if not config[key] then + config[key] = {} + end + if _key then + config[key][#config[key]] = config[key][#config[key]] .. " " .. w + else + table.insert( config[key], val ) + end + if is_quot_complete(config[key][#config[key]]) then + config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "") + _key = nil + else + _key = key + end + else + config[key] = (config[key] and (config[key] .. " ") or "") .. val + if is_quot_complete(config[key]) then + config[key] = config[key]:gsub("[\"\']", "") + _key = nil + else + _key = key + end + end + key = nil + val = nil + end + end + + return config end --- reslvo default config + local default_config = {} + if cmd_line and cmd_line:match("^DOCKERCLI.+") then - default_config = resolve_cli(cmd_line) + default_config = resolve_cli(cmd_line) elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then - local container_id = cmd_line:match("^duplicate/(.+)") - create_body = dk:containers_duplicate_config({id = container_id}) or {} - if not create_body.HostConfig then create_body.HostConfig = {} end - if next(create_body) ~= nil then - default_config.name = nil - default_config.image = create_body.Image - default_config.hostname = create_body.Hostname - default_config.tty = create_body.Tty and true or false - default_config.interactive = create_body.OpenStdin and true or false - default_config.privileged = create_body.HostConfig.Privileged and true or false - default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil - -- default_config.network = create_body.HostConfig.NetworkMode == "default" and "bridge" or create_body.HostConfig.NetworkMode - -- if container has leave original network, and add new network, .HostConfig.NetworkMode is INcorrect, so using first child of .NetworkingConfig.EndpointsConfig - default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil - default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil - default_config.link = create_body.HostConfig.Links - default_config.env = create_body.Env - default_config.dns = create_body.HostConfig.Dns - default_config.volume = create_body.HostConfig.Binds - default_config.cap_add = create_body.HostConfig.CapAdd - default_config.publish_all = create_body.HostConfig.PublishAllPorts - - if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then - default_config.sysctl = {} - for k, v in pairs(create_body.HostConfig.Sysctls) do - table.insert( default_config.sysctl, k.."="..v ) - end - end - - if create_body.HostConfig.LogConfig and create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then - default_config.log_opt = {} - for k, v in pairs(create_body.HostConfig.LogConfig.Config) do - table.insert( default_config.log_opt, k.."="..v ) - end - end - - if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then - default_config.publish = {} - for k, v in pairs(create_body.HostConfig.PortBindings) do - table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") ) - end - end - - default_config.user = create_body.User or nil - default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil - default_config.advance = 1 - default_config.cpus = create_body.HostConfig.NanoCPUs - default_config.cpu_shares = create_body.HostConfig.CpuShares - default_config.memory = create_body.HostConfig.Memory - default_config.blkio_weight = create_body.HostConfig.BlkioWeight - - if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then - default_config.device = {} - for _, v in ipairs(create_body.HostConfig.Devices) do - table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") ) - end - end - if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then - default_config.tmpfs = {} - for k, v in pairs(create_body.HostConfig.Tmpfs) do - table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v ) - end - end - end + local container_id = cmd_line:match("^duplicate/(.+)") + create_body = dk:containers_duplicate_config({id = container_id}) or {} + + if not create_body.HostConfig then + create_body.HostConfig = {} + end + + if next(create_body) ~= nil then + default_config.name = nil + default_config.image = create_body.Image + default_config.hostname = create_body.Hostname + default_config.tty = create_body.Tty and true or false + default_config.interactive = create_body.OpenStdin and true or false + default_config.privileged = create_body.HostConfig.Privileged and true or false + default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil + default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil + default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil + default_config.link = create_body.HostConfig.Links + default_config.env = create_body.Env + default_config.dns = create_body.HostConfig.Dns + default_config.volume = create_body.HostConfig.Binds + default_config.cap_add = create_body.HostConfig.CapAdd + default_config.publish_all = create_body.HostConfig.PublishAllPorts + + if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then + default_config.sysctl = {} + for k, v in pairs(create_body.HostConfig.Sysctls) do + table.insert( default_config.sysctl, k.."="..v ) + end + end + + if create_body.HostConfig.LogConfig and create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then + default_config.log_opt = {} + for k, v in pairs(create_body.HostConfig.LogConfig.Config) do + table.insert( default_config.log_opt, k.."="..v ) + end + end + + if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then + default_config.publish = {} + for k, v in pairs(create_body.HostConfig.PortBindings) do + table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") ) + end + end + + default_config.user = create_body.User or nil + default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil + default_config.advance = 1 + default_config.cpus = create_body.HostConfig.NanoCPUs + default_config.cpu_shares = create_body.HostConfig.CpuShares + default_config.memory = create_body.HostConfig.Memory + default_config.blkio_weight = create_body.HostConfig.BlkioWeight + + if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then + default_config.device = {} + for _, v in ipairs(create_body.HostConfig.Devices) do + table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") ) + end + end + + if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then + default_config.tmpfs = {} + for k, v in pairs(create_body.HostConfig.Tmpfs) do + table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v ) + end + end + end end -local m = SimpleForm("docker", translate("Docker")) +m = SimpleForm("docker", translate("Docker")) m.redirect = luci.dispatcher.build_url("admin", "docker", "containers") --- m.reset = false --- m.submit = false --- new Container -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() +end -local s = m:section(SimpleSection, translate("New Container")) +s = m:section(SimpleSection, translate("New Container")) s.addremove = true s.anonymous = true -local d = s:option(DummyValue,"cmd_line", translate("Resolve CLI")) -d.rawhtml = true -d.template = "dockerman/newcontainer_resolve" - -d = s:option(Value, "name", translate("Container Name")) -d.rmempty = true -d.default = default_config.name or nil - -d = s:option(Flag, "interactive", translate("Interactive (-i)")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.interactive and 1 or 0 - -d = s:option(Flag, "tty", translate("TTY (-t)")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.tty and 1 or 0 - -d = s:option(Value, "image", translate("Docker Image")) -d.rmempty = true -d.default = default_config.image or nil +o = s:option(DummyValue,"cmd_line", translate("Resolve CLI")) +o.rawhtml = true +o.template = "dockerman/newcontainer_resolve" + +o = s:option(Value, "name", translate("Container Name")) +o.rmempty = true +o.default = default_config.name or nil + +o = s:option(Flag, "interactive", translate("Interactive (-i)")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.interactive and 1 or 0 + +o = s:option(Flag, "tty", translate("TTY (-t)")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.tty and 1 or 0 + +o = s:option(Value, "image", translate("Docker Image")) +o.rmempty = true +o.default = default_config.image or nil for _, v in ipairs (images) do - if v.RepoTags then - d:value(v.RepoTags[1], v.RepoTags[1]) - end + if v.RepoTags then + o:value(v.RepoTags[1], v.RepoTags[1]) + end end -d = s:option(Flag, "_force_pull", translate("Always pull image first")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = 0 - -d = s:option(Flag, "privileged", translate("Privileged")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.privileged and 1 or 0 - -d = s:option(ListValue, "restart", translate("Restart Policy")) -d.rmempty = true - -d:value("no", "No") -d:value("unless-stopped", "Unless stopped") -d:value("always", "Always") -d:value("on-failure", "On failure") -d.default = default_config.restart or "unless-stopped" +o = s:option(Flag, "_force_pull", translate("Always pull image first")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = 0 + +o = s:option(Flag, "privileged", translate("Privileged")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.privileged and 1 or 0 + +o = s:option(ListValue, "restart", translate("Restart Policy")) +o.rmempty = true +o:value("no", "No") +o:value("unless-stopped", "Unless stopped") +o:value("always", "Always") +o:value("on-failure", "On failure") +o.default = default_config.restart or "unless-stopped" local d_network = s:option(ListValue, "network", translate("Networks")) d_network.rmempty = true @@ -305,349 +509,394 @@ d_ip.datatype="ip4addr" d_ip:depends("network", "nil") d_ip.default = default_config.ip or nil -d = s:option(DynamicList, "link", translate("Links with other containers")) -d.placeholder = "container_name:alias" -d.rmempty = true -d:depends("network", "bridge") -d.default = default_config.link or nil - -d = s:option(DynamicList, "dns", translate("Set custom DNS servers")) -d.placeholder = "8.8.8.8" -d.rmempty = true -d.default = default_config.dns or nil - -d = s:option(Value, "user", translate("User(-u)"), translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])")) -d.placeholder = "1000:1000" -d.rmempty = true -d.default = default_config.user or nil - -d = s:option(DynamicList, "env", translate("Environmental Variable(-e)"), translate("Set environment variables to inside the container")) -d.placeholder = "TZ=Asia/Shanghai" -d.rmempty = true -d.default = default_config.env or nil - -d = s:option(DynamicList, "volume", translate("Bind Mount(-v)"), translate("Bind mount a volume")) -d.placeholder = "/media:/media:slave" -d.rmempty = true -d.default = default_config.volume or nil - -local d_publish = s:option(DynamicList, "publish", translate("Exposed Ports(-p)"), translate("Publish container's port(s) to the host")) +o = s:option(DynamicList, "link", translate("Links with other containers")) +o.placeholder = "container_name:alias" +o.rmempty = true +o:depends("network", "bridge") +o.default = default_config.link or nil + +o = s:option(DynamicList, "dns", translate("Set custom DNS servers")) +o.placeholder = "8.8.8.8" +o.rmempty = true +o.default = default_config.dns or nil + +o = s:option(Value, "user", + translate("User(-u)"), + translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])")) +o.placeholder = "1000:1000" +o.rmempty = true +o.default = default_config.user or nil + +o = s:option(DynamicList, "env", + translate("Environmental Variable(-e)"), + translate("Set environment variables to inside the container")) +o.placeholder = "TZ=Asia/Shanghai" +o.rmempty = true +o.default = default_config.env or nil + +o = s:option(DynamicList, "volume", + translate("Bind Mount(-v)"), + translate("Bind mount a volume")) +o.placeholder = "/media:/media:slave" +o.rmempty = true +o.default = default_config.volume or nil + +local d_publish = s:option(DynamicList, "publish", + translate("Exposed Ports(-p)"), + translate("Publish container's port(s) to the host")) d_publish.placeholder = "2200:22/tcp" d_publish.rmempty = true d_publish.default = default_config.publish or nil -d = s:option(Value, "command", translate("Run command")) -d.placeholder = "/bin/sh init.sh" -d.rmempty = true -d.default = default_config.command or nil - -d = s:option(Flag, "advance", translate("Advance")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.advance or 0 - -d = s:option(Value, "hostname", translate("Host Name"), translate("The hostname to use for the container")) -d.rmempty = true -d.default = default_config.hostname or nil -d:depends("advance", 1) - -d = s:option(Flag, "publish_all", translate("Exposed All Ports(-P)"), translate("Allocates an ephemeral host port for all of a container's exposed ports")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.publish_all and 1 or 0 -d:depends("advance", 1) - -d = s:option(DynamicList, "device", translate("Device(--device)"), translate("Add host device to the container")) -d.placeholder = "/dev/sda:/dev/xvdc:rwm" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.device or nil - -d = s:option(DynamicList, "tmpfs", translate("Tmpfs(--tmpfs)"), translate("Mount tmpfs directory")) -d.placeholder = "/run:rw,noexec,nosuid,size=65536k" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.tmpfs or nil - -d = s:option(DynamicList, "sysctl", translate("Sysctl(--sysctl)"), translate("Sysctls (kernel parameters) options")) -d.placeholder = "net.ipv4.ip_forward=1" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.sysctl or nil - -d = s:option(DynamicList, "cap_add", translate("CAP-ADD(--cap-add)"), translate("A list of kernel capabilities to add to the container")) -d.placeholder = "NET_ADMIN" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.cap_add or nil - -d = s:option(Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit")) -d.placeholder = "1.5" -d.rmempty = true -d:depends("advance", 1) -d.datatype="ufloat" -d.default = default_config.cpus or nil - -d = s:option(Value, "cpu_shares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024")) -d.placeholder = "1024" -d.rmempty = true -d:depends("advance", 1) -d.datatype="uinteger" -d.default = default_config.cpu_shares or nil - -d = s:option(Value, "memory", translate("Memory"), translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M")) -d.placeholder = "128m" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.memory or nil - -d = s:option(Value, "blkio_weight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000")) -d.placeholder = "500" -d.rmempty = true -d:depends("advance", 1) -d.datatype="uinteger" -d.default = default_config.blkio_weight or nil - -d = s:option(DynamicList, "log_opt", translate("Log driver options"), translate("The logging configuration for this container")) -d.placeholder = "max-size=1m" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.log_opt or nil +o = s:option(Value, "command", translate("Run command")) +o.placeholder = "/bin/sh init.sh" +o.rmempty = true +o.default = default_config.command or nil + +o = s:option(Flag, "advance", translate("Advance")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.advance or 0 + +o = s:option(Value, "hostname", + translate("Host Name"), + translate("The hostname to use for the container")) +o.rmempty = true +o.default = default_config.hostname or nil +o:depends("advance", 1) + +o = s:option(Flag, "publish_all", + translate("Exposed All Ports(-P)"), + translate("Allocates an ephemeral host port for all of a container's exposed ports")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.publish_all and 1 or 0 +o:depends("advance", 1) + +o = s:option(DynamicList, "device", + translate("Device(--device)"), + translate("Add host device to the container")) +o.placeholder = "/dev/sda:/dev/xvdc:rwm" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.device or nil + +o = s:option(DynamicList, "tmpfs", + translate("Tmpfs(--tmpfs)"), + translate("Mount tmpfs directory")) +o.placeholder = "/run:rw,noexec,nosuid,size=65536k" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.tmpfs or nil + +o = s:option(DynamicList, "sysctl", + translate("Sysctl(--sysctl)"), + translate("Sysctls (kernel parameters) options")) +o.placeholder = "net.ipv4.ip_forward=1" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.sysctl or nil + +o = s:option(DynamicList, "cap_add", + translate("CAP-ADD(--cap-add)"), + translate("A list of kernel capabilities to add to the container")) +o.placeholder = "NET_ADMIN" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.cap_add or nil + +o = s:option(Value, "cpus", + translate("CPUs"), + translate("Number of CPUs. Number is a fractional number. 0.000 means no limit")) +o.placeholder = "1.5" +o.rmempty = true +o:depends("advance", 1) +o.datatype="ufloat" +o.default = default_config.cpus or nil + +o = s:option(Value, "cpu_shares", + translate("CPU Shares Weight"), + translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024")) +o.placeholder = "1024" +o.rmempty = true +o:depends("advance", 1) +o.datatype="uinteger" +o.default = default_config.cpu_shares or nil + +o = s:option(Value, "memory", + translate("Memory"), + translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M")) +o.placeholder = "128m" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.memory or nil + +o = s:option(Value, "blkio_weight", + translate("Block IO Weight"), + translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000")) +o.placeholder = "500" +o.rmempty = true +o:depends("advance", 1) +o.datatype="uinteger" +o.default = default_config.blkio_weight or nil + +o = s:option(DynamicList, "log_opt", + translate("Log driver options"), + translate("The logging configuration for this container")) +o.placeholder = "max-size=1m" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.log_opt or nil for _, v in ipairs (networks) do - if v.Name then - local parent = v.Options and v.Options.parent or nil - local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil - ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil - local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") - d_network:value(v.Name, network_name) - - if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then - d_ip:depends("network", v.Name) - end - - if v.Driver == "bridge" then - d_publish:depends("network", v.Name) - end - end + if v.Name then + local parent = v.Options and v.Options.parent or nil + local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil + ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil + local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") + d_network:value(v.Name, network_name) + + if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then + d_ip:depends("network", v.Name) + end + + if v.Driver == "bridge" then + d_publish:depends("network", v.Name) + end + end end m.handle = function(self, state, data) - if state ~= FORM_VALID then return end - local tmp - local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S")) - local hostname = data.hostname - local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false - local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false - local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false - local image = data.image - local user = data.user - if image and not image:match(".-:.+") then - image = image .. ":latest" - end - local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false - local restart = data.restart - local env = data.env - local dns = data.dns - local cap_add = data.cap_add - local sysctl = {} - tmp = data.sysctl - if type(tmp) == "table" then - for i, v in ipairs(tmp) do - local k,v1 = v:match("(.-)=(.+)") - if k and v1 then - sysctl[k]=v1 - end - end - end - local log_opt = {} - tmp = data.log_opt - if type(tmp) == "table" then - for i, v in ipairs(tmp) do - local k,v1 = v:match("(.-)=(.+)") - if k and v1 then - log_opt[k]=v1 - end - end - end - local network = data.network - local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil - local volume = data.volume - local memory = data.memory or 0 - local cpu_shares = data.cpu_shares or 0 - local cpus = data.cpus or 0 - local blkio_weight = data.blkio_weight or 500 - - local portbindings = {} - local exposedports = {} - local tmpfs = {} - tmp = data.tmpfs - if type(tmp) == "table" then - for i, v in ipairs(tmp)do - local k= v:match("([^:]+)") - local v1 = v:match(".-:([^:]+)") or "" - if k then - tmpfs[k]=v1 - end - end - end - - local device = {} - tmp = data.device - if type(tmp) == "table" then - for i, v in ipairs(tmp) do - local t = {} - local _,_, h, c, p = v:find("(.-):(.-):(.+)") - if h and c then - t['PathOnHost'] = h - t['PathInContainer'] = c - t['CgroupPermissions'] = p or "rwm" - else - local _,_, h, c = v:find("(.-):(.+)") - if h and c then - t['PathOnHost'] = h - t['PathInContainer'] = c - t['CgroupPermissions'] = "rwm" - else - t['PathOnHost'] = v - t['PathInContainer'] = v - t['CgroupPermissions'] = "rwm" - end - end - if next(t) ~= nil then - table.insert( device, t ) - end - end - end - - tmp = data.publish or {} - for i, v in ipairs(tmp) do - for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do - local _,_,p= v2:find("^%d+/(%w+)") - if p == nil then - v2=v2..'/tcp' - end - portbindings[v2] = {{HostPort=v1}} - exposedports[v2] = {HostPort=v1} - end - end - - local link = data.link - tmp = data.command - local command = {} - if tmp ~= nil then - for v in string.gmatch(tmp, "[^%s]+") do - command[#command+1] = v - end - end - if memory ~= 0 then - _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") - if n then - unit = unit and unit:sub(1,1):upper() or "B" - if unit == "M" then - memory = tonumber(n) * 1024 * 1024 - elseif unit == "G" then - memory = tonumber(n) * 1024 * 1024 * 1024 - elseif unit == "K" then - memory = tonumber(n) * 1024 - else - memory = tonumber(n) - end - end - end - - create_body.Hostname = network ~= "host" and (hostname or name) or nil - create_body.Tty = tty and true or false - create_body.OpenStdin = interactive and true or false - create_body.User = user - create_body.Cmd = command - create_body.Env = env - create_body.Image = image - create_body.ExposedPorts = exposedports - create_body.HostConfig = create_body.HostConfig or {} - create_body.HostConfig.Dns = dns - create_body.HostConfig.Binds = volume - create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 } - create_body.HostConfig.Privileged = privileged and true or false - create_body.HostConfig.PortBindings = portbindings - create_body.HostConfig.Memory = tonumber(memory) - create_body.HostConfig.CpuShares = tonumber(cpu_shares) - create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9 - create_body.HostConfig.BlkioWeight = tonumber(blkio_weight) - create_body.HostConfig.PublishAllPorts = publish_all - if create_body.HostConfig.NetworkMode ~= network then - -- network mode changed, need to clear duplicate config - create_body.NetworkingConfig = nil - end - create_body.HostConfig.NetworkMode = network - if ip then - if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then - -- ip + duplicate config - for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do - if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then - v.IPAMConfig.IPv4Address = ip - else - create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } - end - break - end - else - -- ip + no duplicate config - create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } } - end - elseif not create_body.NetworkingConfig then - -- no ip + no duplicate config - create_body.NetworkingConfig = nil - end - create_body["HostConfig"]["Tmpfs"] = tmpfs - create_body["HostConfig"]["Devices"] = device - create_body["HostConfig"]["Sysctls"] = sysctl - create_body["HostConfig"]["CapAdd"] = cap_add - create_body["HostConfig"]["LogConfig"] = next(log_opt) ~= nil and { Config = log_opt } or nil - - if network == "bridge" then - create_body["HostConfig"]["Links"] = link - end - local pull_image = function(image) - local json_stringify = luci.jsonc and luci.jsonc.stringify - docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n") - local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb) - if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image or res.body[#res.body].status == "Status: Image is up to date for ".. image)) then - docker:append_status("done\n") - else - res.code = (res.code == 200) and 500 or res.code - docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) - end - end - docker:clear_status() - local exist_image = false - if image then - for _, v in ipairs (images) do - if v.RepoTags and v.RepoTags[1] == image then - exist_image = true - break - end - end - if not exist_image then - pull_image(image) - elseif data._force_pull == 1 then - pull_image(image) - end - end - - create_body = docker.clear_empty_tables(create_body) - docker:append_status("Container: " .. "create" .. " " .. name .. "...") - local res = dk.containers:create({name = name, body = create_body}) - if res and res.code == 201 then - docker:clear_status() - luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) - else - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) - end + if state ~= FORM_VALID then + return + end + + local tmp + local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S")) + local hostname = data.hostname + local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false + local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false + local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false + local image = data.image + local user = data.user + + if image and not image:match(".-:.+") then + image = image .. ":latest" + end + + local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false + local restart = data.restart + local env = data.env + local dns = data.dns + local cap_add = data.cap_add + local sysctl = {} + + tmp = data.sysctl + if type(tmp) == "table" then + for i, v in ipairs(tmp) do + local k,v1 = v:match("(.-)=(.+)") + if k and v1 then + sysctl[k]=v1 + end + end + end + + local log_opt = {} + tmp = data.log_opt + if type(tmp) == "table" then + for i, v in ipairs(tmp) do + local k,v1 = v:match("(.-)=(.+)") + if k and v1 then + log_opt[k]=v1 + end + end + end + + local network = data.network + local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil + local volume = data.volume + local memory = data.memory or 0 + local cpu_shares = data.cpu_shares or 0 + local cpus = data.cpus or 0 + local blkio_weight = data.blkio_weight or 500 + + local portbindings = {} + local exposedports = {} + + local tmpfs = {} + tmp = data.tmpfs + if type(tmp) == "table" then + for i, v in ipairs(tmp)do + local k= v:match("([^:]+)") + local v1 = v:match(".-:([^:]+)") or "" + if k then + tmpfs[k]=v1 + end + end + end + + local device = {} + tmp = data.device + if type(tmp) == "table" then + for i, v in ipairs(tmp) do + local t = {} + local _,_, h, c, p = v:find("(.-):(.-):(.+)") + if h and c then + t['PathOnHost'] = h + t['PathInContainer'] = c + t['CgroupPermissions'] = p or "rwm" + else + local _,_, h, c = v:find("(.-):(.+)") + if h and c then + t['PathOnHost'] = h + t['PathInContainer'] = c + t['CgroupPermissions'] = "rwm" + else + t['PathOnHost'] = v + t['PathInContainer'] = v + t['CgroupPermissions'] = "rwm" + end + end + + if next(t) ~= nil then + table.insert( device, t ) + end + end + end + + tmp = data.publish or {} + for i, v in ipairs(tmp) do + for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do + local _,_,p= v2:find("^%d+/(%w+)") + if p == nil then + v2=v2..'/tcp' + end + portbindings[v2] = {{HostPort=v1}} + exposedports[v2] = {HostPort=v1} + end + end + + local link = data.link + tmp = data.command + local command = {} + if tmp ~= nil then + for v in string.gmatch(tmp, "[^%s]+") do + command[#command+1] = v + end + end + + if memory ~= 0 then + _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") + if n then + unit = unit and unit:sub(1,1):upper() or "B" + if unit == "M" then + memory = tonumber(n) * 1024 * 1024 + elseif unit == "G" then + memory = tonumber(n) * 1024 * 1024 * 1024 + elseif unit == "K" then + memory = tonumber(n) * 1024 + else + memory = tonumber(n) + end + end + end + + create_body.Hostname = network ~= "host" and (hostname or name) or nil + create_body.Tty = tty and true or false + create_body.OpenStdin = interactive and true or false + create_body.User = user + create_body.Cmd = command + create_body.Env = env + create_body.Image = image + create_body.ExposedPorts = exposedports + create_body.HostConfig = create_body.HostConfig or {} + create_body.HostConfig.Dns = dns + create_body.HostConfig.Binds = volume + create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 } + create_body.HostConfig.Privileged = privileged and true or false + create_body.HostConfig.PortBindings = portbindings + create_body.HostConfig.Memory = tonumber(memory) + create_body.HostConfig.CpuShares = tonumber(cpu_shares) + create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9 + create_body.HostConfig.BlkioWeight = tonumber(blkio_weight) + create_body.HostConfig.PublishAllPorts = publish_all + + if create_body.HostConfig.NetworkMode ~= network then + create_body.NetworkingConfig = nil + end + + create_body.HostConfig.NetworkMode = network + + if ip then + if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then + for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do + if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then + v.IPAMConfig.IPv4Address = ip + else + create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } + end + break + end + else + create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } } + end + elseif not create_body.NetworkingConfig then + create_body.NetworkingConfig = nil + end + + create_body["HostConfig"]["Tmpfs"] = tmpfs + create_body["HostConfig"]["Devices"] = device + create_body["HostConfig"]["Sysctls"] = sysctl + create_body["HostConfig"]["CapAdd"] = cap_add + create_body["HostConfig"]["LogConfig"] = next(log_opt) ~= nil and { Config = log_opt } or nil + + if network == "bridge" then + create_body["HostConfig"]["Links"] = link + end + + local pull_image = function(image) + local json_stringify = luci.jsonc and luci.jsonc.stringify + docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n") + local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb) + if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image or res.body[#res.body].status == "Status: Image is up to date for ".. image)) then + docker:append_status("done\n") + else + res.code = (res.code == 200) and 500 or res.code + docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) + end + end + + docker:clear_status() + local exist_image = false + + if image then + for _, v in ipairs (images) do + if v.RepoTags and v.RepoTags[1] == image then + exist_image = true + break + end + end + if not exist_image then + pull_image(image) + elseif data._force_pull == 1 then + pull_image(image) + end + end + + create_body = docker.clear_empty_tables(create_body) + + docker:append_status("Container: " .. "create" .. " " .. name .. "...") + local res = dk.containers:create({name = name, body = create_body}) + if res and res.code == 201 then + docker:clear_status() + luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) + else + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) + end end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua index bdadbaf881..f4cfea5bc5 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua @@ -3,219 +3,244 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" local docker = require "luci.model.docker" + +local m, s, o + local dk = docker.new() m = SimpleForm("docker", translate("Docker")) m.redirect = luci.dispatcher.build_url("admin", "docker", "networks") -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() +end s = m:section(SimpleSection, translate("New Network")) s.addremove = true s.anonymous = true -d = s:option(Value, "name", translate("Network Name")) -d.rmempty = true +o = s:option(Value, "name", translate("Network Name")) +o.rmempty = true -d = s:option(ListValue, "dirver", translate("Driver")) -d.rmempty = true -d:value("bridge", "bridge") -d:value("macvlan", "macvlan") -d:value("ipvlan", "ipvlan") -d:value("overlay", "overlay") +o = s:option(ListValue, "dirver", translate("Driver")) +o.rmempty = true +o:value("bridge", "bridge") +o:value("macvlan", "macvlan") +o:value("ipvlan", "ipvlan") +o:value("overlay", "overlay") -d = s:option(Value, "parent", translate("Parent Interface")) -d.rmempty = true -d:depends("dirver", "macvlan") +o = s:option(Value, "parent", translate("Parent Interface")) +o.rmempty = true +o:depends("dirver", "macvlan") local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {} for _, v in ipairs(interfaces) do - d:value(v, v) + o:value(v, v) end -d.default="br-lan" -d.placeholder="br-lan" - -d = s:option(Value, "macvlan_mode", translate("Macvlan Mode")) -d.rmempty = true -d:depends("dirver", "macvlan") -d.default="bridge" -d:value("bridge", "bridge") -d:value("private", "private") -d:value("vepa", "vepa") -d:value("passthru", "passthru") - -d = s:option(Value, "ipvlan_mode", translate("Ipvlan Mode")) -d.rmempty = true -d:depends("dirver", "ipvlan") -d.default="l3" -d:value("l2", "l2") -d:value("l3", "l3") - -d = s:option(Flag, "ingress", translate("Ingress"), translate("Ingress network is the network which provides the routing-mesh in swarm mode")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = 0 -d:depends("dirver", "overlay") - -d = s:option(DynamicList, "options", translate("Options")) -d.rmempty = true -d.placeholder="com.docker.network.driver.mtu=1500" - -d = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network")) -d.rmempty = true -d:depends("dirver", "overlay") -d.disabled = 0 -d.enabled = 1 -d.default = 0 - -if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then - d = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt")) - d:depends("dirver", "macvlan") - d.disabled = 0 - d.enabled = 1 - d.default = 1 +o.default="br-lan" +o.placeholder="br-lan" + +o = s:option(Value, "macvlan_mode", translate("Macvlan Mode")) +o.rmempty = true +o:depends("dirver", "macvlan") +o.default="bridge" +o:value("bridge", "bridge") +o:value("private", "private") +o:value("vepa", "vepa") +o:value("passthru", "passthru") + +o = s:option(Value, "ipvlan_mode", translate("Ipvlan Mode")) +o.rmempty = true +o:depends("dirver", "ipvlan") +o.default="l3" +o:value("l2", "l2") +o:value("l3", "l3") + +o = s:option(Flag, "ingress", + translate("Ingress"), + translate("Ingress network is the network which provides the routing-mesh in swarm mode")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o:depends("dirver", "overlay") + +o = s:option(DynamicList, "options", translate("Options")) +o.rmempty = true +o.placeholder="com.docker.network.driver.mtu=1500" + +o = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network")) +o.rmempty = true +o:depends("dirver", "overlay") +o.disabled = 0 +o.enabled = 1 +o.default = 0 + +if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then + o = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt")) + o:depends("dirver", "macvlan") + o.disabled = 0 + o.enabled = 1 + o.default = 1 end -d = s:option(Value, "subnet", translate("Subnet")) -d.rmempty = true -d.placeholder="10.1.0.0/16" -d.datatype="ip4addr" - -d = s:option(Value, "gateway", translate("Gateway")) -d.rmempty = true -d.placeholder="10.1.1.1" -d.datatype="ip4addr" - -d = s:option(Value, "ip_range", translate("IP range")) -d.rmempty = true -d.placeholder="10.1.1.0/24" -d.datatype="ip4addr" - -d = s:option(DynamicList, "aux_address", translate("Exclude IPs")) -d.rmempty = true -d.placeholder="my-route=10.1.1.1" - -d = s:option(Flag, "ipv6", translate("Enable IPv6")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = 0 - -d = s:option(Value, "subnet6", translate("IPv6 Subnet")) -d.rmempty = true -d.placeholder="fe80::/10" -d.datatype="ip6addr" -d:depends("ipv6", 1) - -d = s:option(Value, "gateway6", translate("IPv6 Gateway")) -d.rmempty = true -d.placeholder="fe80::1" -d.datatype="ip6addr" -d:depends("ipv6", 1) +o = s:option(Value, "subnet", translate("Subnet")) +o.rmempty = true +o.placeholder="10.1.0.0/16" +o.datatype="ip4addr" + +o = s:option(Value, "gateway", translate("Gateway")) +o.rmempty = true +o.placeholder="10.1.1.1" +o.datatype="ip4addr" + +o = s:option(Value, "ip_range", translate("IP range")) +o.rmempty = true +o.placeholder="10.1.1.0/24" +o.datatype="ip4addr" + +o = s:option(DynamicList, "aux_address", translate("Exclude IPs")) +o.rmempty = true +o.placeholder="my-route=10.1.1.1" + +o = s:option(Flag, "ipv6", translate("Enable IPv6")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = 0 + +o = s:option(Value, "subnet6", translate("IPv6 Subnet")) +o.rmempty = true +o.placeholder="fe80::/10" +o.datatype="ip6addr" +o:depends("ipv6", 1) + +o = s:option(Value, "gateway6", translate("IPv6 Gateway")) +o.rmempty = true +o.placeholder="fe80::1" +o.datatype="ip6addr" +o:depends("ipv6", 1) m.handle = function(self, state, data) - if state == FORM_VALID then - local name = data.name - local driver = data.dirver - - local internal = data.internal == 1 and true or false - - local subnet = data.subnet - local gateway = data.gateway - local ip_range = data.ip_range - - local aux_address = {} - local tmp = data.aux_address or {} - for i,v in ipairs(tmp) do - _,_,k1,v1 = v:find("(.-)=(.+)") - aux_address[k1] = v1 - end - - local options = {} - tmp = data.options or {} - for i,v in ipairs(tmp) do - _,_,k1,v1 = v:find("(.-)=(.+)") - options[k1] = v1 - end - - local ipv6 = data.ipv6 == 1 and true or false - - local create_body={ - Name = name, - Driver = driver, - EnableIPv6 = ipv6, - IPAM = { - Driver= "default" - }, - Internal = internal - } - - if subnet or gateway or ip_range then - create_body["IPAM"]["Config"] = { - { - Subnet = subnet, - Gateway = gateway, - IPRange = ip_range, - AuxAddress = aux_address, - AuxiliaryAddresses = aux_address - } - } - end - if driver == "macvlan" then - create_body["Options"] = { - macvlan_mode = data.macvlan_mode, - parent = data.parent - } - elseif driver == "ipvlan" then - create_body["Options"] = { - ipvlan_mode = data.ipvlan_mode - } - elseif driver == "overlay" then - create_body["Ingress"] = data.ingerss == 1 and true or false - end - - if ipv6 and data.subnet6 and data.subnet6 then - if type(create_body["IPAM"]["Config"]) ~= "table" then - create_body["IPAM"]["Config"] = {} - end - local index = #create_body["IPAM"]["Config"] - create_body["IPAM"]["Config"][index+1] = { - Subnet = data.subnet6, - Gateway = data.gateway6 - } - end - - if next(options) ~= nil then - create_body["Options"] = create_body["Options"] or {} - for k, v in pairs(options) do - create_body["Options"][k] = v - end - end - - create_body = docker.clear_empty_tables(create_body) - docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...") - local res = dk.networks:create({body = create_body}) - if res and res.code == 201 then - docker:write_status("Network: " .. "create macvlan interface...") - res = dk.networks:inspect({ name = create_body.Name }) - if driver == "macvlan" and data.op_macvlan ~= 0 and res.code == 200 - and res.body and res.body.IPAM and res.body.IPAM.Config and res.body.IPAM.Config[1] - and res.body.IPAM.Config[1].Gateway and res.body.IPAM.Config[1].Subnet then - docker.create_macvlan_interface(data.name, data.parent, res.body.IPAM.Config[1].Gateway, res.body.IPAM.Config[1].Subnet) - end - docker:clear_status() - luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) - else - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) - end - end + if state == FORM_VALID then + local name = data.name + local driver = data.dirver + + local internal = data.internal == 1 and true or false + + local subnet = data.subnet + local gateway = data.gateway + local ip_range = data.ip_range + + local aux_address = {} + local tmp = data.aux_address or {} + for i,v in ipairs(tmp) do + _,_,k1,v1 = v:find("(.-)=(.+)") + aux_address[k1] = v1 + end + + local options = {} + tmp = data.options or {} + for i,v in ipairs(tmp) do + _,_,k1,v1 = v:find("(.-)=(.+)") + options[k1] = v1 + end + + local ipv6 = data.ipv6 == 1 and true or false + + local create_body = { + Name = name, + Driver = driver, + EnableIPv6 = ipv6, + IPAM = { + Driver= "default" + }, + Internal = internal + } + + if subnet or gateway or ip_range then + create_body["IPAM"]["Config"] = { + { + Subnet = subnet, + Gateway = gateway, + IPRange = ip_range, + AuxAddress = aux_address, + AuxiliaryAddresses = aux_address + } + } + end + + if driver == "macvlan" then + create_body["Options"] = { + macvlan_mode = data.macvlan_mode, + parent = data.parent + } + elseif driver == "ipvlan" then + create_body["Options"] = { + ipvlan_mode = data.ipvlan_mode + } + elseif driver == "overlay" then + create_body["Ingress"] = data.ingerss == 1 and true or false + end + + if ipv6 and data.subnet6 and data.subnet6 then + if type(create_body["IPAM"]["Config"]) ~= "table" then + create_body["IPAM"]["Config"] = {} + end + local index = #create_body["IPAM"]["Config"] + create_body["IPAM"]["Config"][index+1] = { + Subnet = data.subnet6, + Gateway = data.gateway6 + } + end + + if next(options) ~= nil then + create_body["Options"] = create_body["Options"] or {} + for k, v in pairs(options) do + create_body["Options"][k] = v + end + end + + create_body = docker.clear_empty_tables(create_body) + docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...") + + local res = dk.networks:create({ + body = create_body + }) + + if res and res.code == 201 then + docker:write_status("Network: " .. "create macvlan interface...") + res = dk.networks:inspect({ + name = create_body.Name + }) + + if driver == "macvlan" and + data.op_macvlan ~= 0 and + res.code == 200 and + res.body and + res.body.IPAM and + res.body.IPAM.Config and + res.body.IPAM.Config[1] and + res.body.IPAM.Config[1].Gateway and + res.body.IPAM.Config[1].Subnet then + + docker.create_macvlan_interface(data.name, + data.parent, + res.body.IPAM.Config[1].Gateway, + res.body.IPAM.Config[1].Subnet) + end + + docker:clear_status() + luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) + else + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) + end + end end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua index 5515aacc72..dc20125e29 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua @@ -3,26 +3,29 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" local docker = require "luci.model.docker" -local uci = require "luci.model.uci" + +local m, s, o function byte_format(byte) - local suff = {"B", "KB", "MB", "GB", "TB"} - for i=1, 5 do - if byte > 1024 and i < 5 then - byte = byte / 1024 - else - return string.format("%.2f %s", byte, suff[i]) - end - end + local suff = {"B", "KB", "MB", "GB", "TB"} + for i=1, 5 do + if byte > 1024 and i < 5 then + byte = byte / 1024 + else + return string.format("%.2f %s", byte, suff[i]) + end + end end -local map_dockerman = Map("dockerman", translate("Docker"), translate("DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:") .. " ".. [[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] ..translate("Github") .. [[</a>]]) +m = Map("dockerd", translate("Docker"), + translate("DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:") .. + " " .. + [[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] .. + translate("Github") .. + [[</a>]]) + local docker_info_table = {} --- docker_info_table['0OperatingSystem'] = {_key=translate("Operating System"),_value='-'} --- docker_info_table['1Architecture'] = {_key=translate("Architecture"),_value='-'} --- docker_info_table['2KernelVersion'] = {_key=translate("Kernel Version"),_value='-'} docker_info_table['3ServerVersion'] = {_key=translate("Docker Version"),_value='-'} docker_info_table['4ApiVersion'] = {_key=translate("Api Version"),_value='-'} docker_info_table['5NCPU'] = {_key=translate("CPUs"),_value='-'} @@ -31,124 +34,122 @@ docker_info_table['7DockerRootDir'] = {_key=translate("Docker Root Dir"),_value= docker_info_table['8IndexServerAddress'] = {_key=translate("Index Server Address"),_value='-'} docker_info_table['9RegistryMirrors'] = {_key=translate("Registry Mirrors"),_value='-'} -local s = map_dockerman:section(Table, docker_info_table) +s = m:section(Table, docker_info_table) s:option(DummyValue, "_key", translate("Info")) s:option(DummyValue, "_value") -s = map_dockerman:section(SimpleSection) + +s = m:section(SimpleSection) +s.template = "dockerman/overview" + s.containers_running = '-' s.images_used = '-' s.containers_total = '-' s.images_total = '-' s.networks_total = '-' s.volumes_total = '-' -local containers_list --- local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path") -if (require "luci.model.docker").new():_ping().code == 200 then - local dk = docker.new() - containers_list = dk.containers:list({query = {all=true}}).body - local images_list = dk.images:list().body - local vol = dk.volumes:list() - local volumes_list = vol and vol.body and vol.body.Volumes or {} - local networks_list = dk.networks:list().body or {} - local docker_info = dk:info() - -- docker_info_table['0OperatingSystem']._value = docker_info.body.OperatingSystem - -- docker_info_table['1Architecture']._value = docker_info.body.Architecture - -- docker_info_table['2KernelVersion']._value = docker_info.body.KernelVersion - docker_info_table['3ServerVersion']._value = docker_info.body.ServerVersion - docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"] - docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU) - docker_info_table['6MemTotal']._value = byte_format(docker_info.body.MemTotal) - if docker_info.body.DockerRootDir then - local statvfs = nixio.fs.statvfs(docker_info.body.DockerRootDir) - local size = statvfs and (statvfs.bavail * statvfs.bsize) or 0 - docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(byte_format(size)) .. " " .. translate("Available") .. ")" - end - docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress - for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do - docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v) - end - - s.images_used = 0 - for i, v in ipairs(images_list) do - for ci,cv in ipairs(containers_list) do - if v.Id == cv.ImageID then - s.images_used = s.images_used + 1 - break - end - end - end - s.containers_running = tostring(docker_info.body.ContainersRunning) - s.images_used = tostring(s.images_used) - s.containers_total = tostring(docker_info.body.Containers) - s.images_total = tostring(#images_list) - s.networks_total = tostring(#networks_list) - s.volumes_total = tostring(#volumes_list) -end -s.template = "dockerman/overview" -local section_dockerman = map_dockerman:section(NamedSection, "local", "section", translate("Setting")) -section_dockerman:tab("daemon", translate("Docker Daemon")) -section_dockerman:tab("ac", translate("Access Control")) -section_dockerman:tab("dockerman", translate("DockerMan")) +if docker.new():_ping().code == 200 then + local dk = docker.new() + local containers_list = dk.containers:list({query = {all=true}}).body + local images_list = dk.images:list().body + local vol = dk.volumes:list() + local volumes_list = vol and vol.body and vol.body.Volumes or {} + local networks_list = dk.networks:list().body or {} + local docker_info = dk:info() -local socket_path = section_dockerman:taboption("dockerman", Value, "socket_path", translate("Docker Socket Path")) -socket_path.default = "/var/run/docker.sock" -socket_path.placeholder = "/var/run/docker.sock" -socket_path.rmempty = false + docker_info_table['3ServerVersion']._value = docker_info.body.ServerVersion + docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"] + docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU) + docker_info_table['6MemTotal']._value = byte_format(docker_info.body.MemTotal) + if docker_info.body.DockerRootDir then + local statvfs = nixio.fs.statvfs(docker_info.body.DockerRootDir) + local size = statvfs and (statvfs.bavail * statvfs.bsize) or 0 + docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(byte_format(size)) .. " " .. translate("Available") .. ")" + end -local remote_endpoint = section_dockerman:taboption("dockerman", Flag, "remote_endpoint", translate("Remote Endpoint"), translate("Dockerman connect to remote endpoint")) -remote_endpoint.rmempty = false -remote_endpoint.enabled = "true" -remote_endpoint.disabled = "false" + docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress + for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do + docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v) + end -local remote_host = section_dockerman:taboption("dockerman", Value, "remote_host", translate("Remote Host")) -remote_host.placeholder = "10.1.1.2" --- remote_host:depends("remote_endpoint", "true") + s.images_used = 0 + for i, v in ipairs(images_list) do + for ci,cv in ipairs(containers_list) do + if v.Id == cv.ImageID then + s.images_used = s.images_used + 1 + break + end + end + end + + s.containers_running = tostring(docker_info.body.ContainersRunning) + s.images_used = tostring(s.images_used) + s.containers_total = tostring(docker_info.body.Containers) + s.images_total = tostring(#images_list) + s.networks_total = tostring(#networks_list) + s.volumes_total = tostring(#volumes_list) +end -local remote_port = section_dockerman:taboption("dockerman", Value, "remote_port", translate("Remote Port")) -remote_port.placeholder = "2375" -remote_port.default = "2375" --- remote_port:depends("remote_endpoint", "true") +s = m:section(NamedSection, "globals", "section", translate("Setting")) --- local status_path = section_dockerman:taboption("dockerman", Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file")) --- local debug = section_dockerman:taboption("dockerman", Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path")) --- debug.enabled="true" --- debug.disabled="false" --- local debug_path = section_dockerman:taboption("dockerman", Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile")) +o = s:option(Flag, "remote_endpoint", + translate("Remote Endpoint"), + translate("Connect to remote endpoint")) +o.rmempty = false + +o = s:option(Value, "socket_path", + translate("Docker Socket Path")) +o.default = "unix://var/run/docker.sock" +o.placeholder = "unix://var/run/docker.sock" +o:depends("remote_endpoint", 1) + +o = s:option(Value, "remote_host", + translate("Remote Host")) +o.placeholder = "10.1.1.2" +o:depends("remote_endpoint", 1) + +o = s:option(Value, "remote_port", + translate("Remote Port")) +o.placeholder = "2375" +o.default = "2375" +o:depends("remote_endpoint", 1) if nixio.fs.access("/usr/bin/dockerd") then - local allowed_interface = section_dockerman:taboption("ac", DynamicList, "ac_allowed_interface", translate("Allowed access interfaces"), translate("Which interface(s) can access containers under the bridge network, fill-in Interface Name")) - local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {} - for i, v in ipairs(interfaces) do - allowed_interface:value(v, v) - end - local allowed_container = section_dockerman:taboption("ac", DynamicList, "ac_allowed_container", translate("Containers allowed to be accessed"), translate("Which container(s) under bridge network can be accessed, even from interfaces that are not allowed, fill-in Container Id or Name")) - -- allowed_container.placeholder = "container name_or_id" - if containers_list then - for i, v in ipairs(containers_list) do - if v.State == "running" and v.NetworkSettings and v.NetworkSettings.Networks and v.NetworkSettings.Networks.bridge and v.NetworkSettings.Networks.bridge.IPAddress then - allowed_container:value(v.Id:sub(1,12), v.Names[1]:sub(2) .. " | " .. v.NetworkSettings.Networks.bridge.IPAddress) - end - end - end - - local dockerd_enable = section_dockerman:taboption("daemon", Flag, "daemon_ea", translate("Enable")) - dockerd_enable.enabled = "true" - dockerd_enable.rmempty = true - local data_root = section_dockerman:taboption("daemon", Value, "daemon_data_root", translate("Docker Root Dir")) - data_root.placeholder = "/opt/docker/" - local registry_mirrors = section_dockerman:taboption("daemon", DynamicList, "daemon_registry_mirrors", translate("Registry Mirrors")) - registry_mirrors:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com") - - local log_level = section_dockerman:taboption("daemon", ListValue, "daemon_log_level", translate("Log Level"), translate('Set the logging level')) - log_level:value("debug", "debug") - log_level:value("info", "info") - log_level:value("warn", "warn") - log_level:value("error", "error") - log_level:value("fatal", "fatal") - local hosts = section_dockerman:taboption("daemon", DynamicList, "daemon_hosts", translate("Server Host"), translate('Daemon unix socket (unix:///var/run/docker.sock) or TCP Remote Hosts (tcp://0.0.0.0:2375), default: unix:///var/run/docker.sock')) - hosts:value("unix:///var/run/docker.sock", "unix:///var/run/docker.sock") - hosts:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375") - hosts.rmempty = true + o = s:option(Value, "data_root", + translate("Docker Root Dir")) + o.placeholder = "/opt/docker/" + o:depends("remote_endpoint", 0) + + o = s:option(Value, "bip", + translate("Default bridge"), + translate("Configure the default bridge network")) + o.placeholder = "172.17.0.1/16" + o.default = "172.17.0.1/16" + o.datatype = "ipaddr" + o:depends("remote_endpoint", 0) + + o = s:option(DynamicList, "registry_mirrors", + translate("Registry Mirrors")) + o:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com") + o:depends("remote_endpoint", 0) + + o = s:option(ListValue, "log_level", + translate("Log Level"), + translate('Set the logging level')) + o:value("debug", "debug") + o:value("info", "info") + o:value("warn", "warn") + o:value("error", "error") + o:value("fatal", "fatal") + o:depends("remote_endpoint", 0) + + o = s:option(DynamicList, "hosts", + translate("Client connection"), + translate('Specifies where the Docker daemon will listen for client connections')) + o:value("unix://var/run/docker.sock", "unix://var/run/docker.sock") + o:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375") + o.rmempty = true + o:depends("remote_endpoint", 0) end -return map_dockerman + +return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua index 1685027203..8dae4a020d 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua @@ -3,114 +3,140 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" local dk = docker.new() -local containers, volumes -local res = dk.volumes:list() -if res.code <300 then volumes = res.body.Volumes else return end -res = dk.containers:list({query = {all=true}}) -if res.code <300 then containers = res.body else return end +local m, s, o + +local res, containers, volumes function get_volumes() - local data = {} - for i, v in ipairs(volumes) do - -- local index = v.CreatedAt .. v.Name - local index = v.Name - data[index]={} - data[index]["_selected"] = 0 - data[index]["_nameraw"] = v.Name - data[index]["_name"] = v.Name:sub(1,12) - for ci,cv in ipairs(containers) do - if cv.Mounts and type(cv.Mounts) ~= "table" then break end - for vi, vv in ipairs(cv.Mounts) do - if v.Name == vv.Name then - data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. - '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2)..'</a>' - end - end - end - data[index]["_driver"] = v.Driver - data[index]["_mountpoint"] = nil - for v1 in v.Mountpoint:gmatch('[^/]+') do - if v1 == index then - data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..." - else - data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1 - end - end - data[index]["_created"] = v.CreatedAt - end - return data + local data = {} + for i, v in ipairs(volumes) do + local index = v.Name + data[index]={} + data[index]["_selected"] = 0 + data[index]["_nameraw"] = v.Name + data[index]["_name"] = v.Name:sub(1,12) + + for ci,cv in ipairs(containers) do + if cv.Mounts and type(cv.Mounts) ~= "table" then + break + end + for vi, vv in ipairs(cv.Mounts) do + if v.Name == vv.Name then + data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. + '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2)..'</a>' + end + end + end + data[index]["_driver"] = v.Driver + data[index]["_mountpoint"] = nil + + for v1 in v.Mountpoint:gmatch('[^/]+') do + if v1 == index then + data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..." + else + data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1 + end + end + data[index]["_created"] = v.CreatedAt + end + + return data +end + +res = dk.volumes:list() +if res.code <300 then + volumes = res.body.Volumes +else + return +end + +res = dk.containers:list({ + query = { + all=true + } +}) +if res.code <300 then + containers = res.body +else + return end local volume_list = get_volumes() --- m = Map("docker", translate("Docker")) m = SimpleForm("docker", translate("Docker")) m.submit=false m.reset=false +s = m:section(Table, volume_list, translate("Volumes")) + +o = s:option(Flag, "_selected","") +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.write = function(self, section, value) + volume_list[section]._selected = value +end + +o = s:option(DummyValue, "_name", translate("Name")) + +o = s:option(DummyValue, "_driver", translate("Driver")) -volume_table = m:section(Table, volume_list, translate("Volumes")) +o = s:option(DummyValue, "_containers", translate("Containers")) +o.rawhtml = true -volume_selecter = volume_table:option(Flag, "_selected","") -volume_selecter.disabled = 0 -volume_selecter.enabled = 1 -volume_selecter.default = 0 +o = s:option(DummyValue, "_mountpoint", translate("Mount Point")) -volume_id = volume_table:option(DummyValue, "_name", translate("Name")) -volume_table:option(DummyValue, "_driver", translate("Driver")) -volume_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true -volume_table:option(DummyValue, "_mountpoint", translate("Mount Point")) -volume_table:option(DummyValue, "_created", translate("Created")) -volume_selecter.write = function(self, section, value) - volume_list[section]._selected = value +o = s:option(DummyValue, "_created", translate("Created")) + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() end -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - -action = m:section(Table,{{}}) -action.notitle=true -action.rowcolors=false -action.template="cbi/nullsection" -btnremove = action:option(Button, "remove") -btnremove.inputtitle= translate("Remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputstyle = "remove" -btnremove.forcewrite = true -btnremove.write = function(self, section) - local volume_selected = {} - -- 遍历table中sectionid - local volume_table_sids = volume_table:cfgsections() - for _, volume_table_sid in ipairs(volume_table_sids) do - -- 得到选中项的名字 - if volume_list[volume_table_sid]._selected == 1 then - -- volume_selected[#volume_selected+1] = volume_id:cfgvalue(volume_table_sid) - volume_selected[#volume_selected+1] = volume_table_sid - end - end - if next(volume_selected) ~= nil then - local success = true - docker:clear_status() - for _,vol in ipairs(volume_selected) do - docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...") - local msg = dk.volumes["remove"](dk, {id = vol}) - if msg.code ~= 204 then - docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") - success = false - else - docker:append_status("done\n") - end - end - if success then docker:clear_status() end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes")) - end +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "remove") +o.inputtitle= translate("Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + local volume_selected = {} + + for k in pairs(volume_list) do + if volume_list[k]._selected == 1 then + volume_selected[#volume_selected+1] = k + end + end + + if next(volume_selected) ~= nil then + local success = true + docker:clear_status() + for _,vol in ipairs(volume_selected) do + docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...") + local msg = dk.volumes["remove"](dk, {id = vol}) + if msg.code ~= 204 then + docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") + success = false + else + docker:append_status("done\n") + end + end + + if success then + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes")) + end end + return m diff --git a/applications/luci-app-dockerman/luasrc/model/docker.lua b/applications/luci-app-dockerman/luasrc/model/docker.lua index e62454a8fc..a0c74c0e41 100644 --- a/applications/luci-app-dockerman/luasrc/model/docker.lua +++ b/applications/luci-app-dockerman/luasrc/model/docker.lua @@ -3,276 +3,347 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" local docker = require "luci.docker" +local fs = require "nixio.fs" local uci = (require "luci.model.uci").cursor() local _docker = {} +_docker.options = {} --pull image and return iamge id local update_image = function(self, image_name) - local json_stringify = luci.jsonc and luci.jsonc.stringify - _docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n") - local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb) - if res and res.code == 200 and (#res.body > 0 and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image_name)) then - _docker:append_status("done\n") - else - res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message) - end - new_image_id = self.images:inspect({name = image_name}).body.Id - return new_image_id, res + local json_stringify = luci.jsonc and luci.jsonc.stringify + _docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n") + local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb) + + if res and res.code == 200 and (#res.body > 0 and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image_name)) then + _docker:append_status("done\n") + else + res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message) + end + + new_image_id = self.images:inspect({name = image_name}).body.Id + return new_image_id, res end local table_equal = function(t1, t2) - if not t1 then return true end - if not t2 then return false end - if #t1 ~= #t2 then return false end - for i, v in ipairs(t1) do - if t1[i] ~= t2[i] then return false end - end - return true + if not t1 then + return true + end + + if not t2 then + return false + end + + if #t1 ~= #t2 then + return false + end + + for i, v in ipairs(t1) do + if t1[i] ~= t2[i] then + return false + end + end + + return true end local table_subtract = function(t1, t2) - if not t1 or next(t1) == nil then return nil end - if not t2 or next(t2) == nil then return t1 end - local res = {} - for _, v1 in ipairs(t1) do - local found = false - for _, v2 in ipairs(t2) do - if v1 == v2 then - found= true - break - end - end - if not found then - table.insert(res, v1) - end - end - return next(res) == nil and nil or res + if not t1 or next(t1) == nil then + return nil + end + + if not t2 or next(t2) == nil then + return t1 + end + + local res = {} + for _, v1 in ipairs(t1) do + local found = false + for _, v2 in ipairs(t2) do + if v1 == v2 then + found= true + break + end + end + if not found then + table.insert(res, v1) + end + end + + return next(res) == nil and nil or res end local map_subtract = function(t1, t2) - if not t1 or next(t1) == nil then return nil end - if not t2 or next(t2) == nil then return t1 end - local res = {} - for k1, v1 in pairs(t1) do - local found = false - for k2, v2 in ipairs(t2) do - if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then - found= true - break - end - end - if not found then - res[k1] = v1 - -- if v1 and type(v1) == "table" then - -- if next(v1) == nil then - -- res[k1] = { k = 'v' } - -- else - -- res[k1] = v1 - -- end - -- end - end - end - - return next(res) ~= nil and res or nil + if not t1 or next(t1) == nil then + return nil + end + + if not t2 or next(t2) == nil then + return t1 + end + + local res = {} + for k1, v1 in pairs(t1) do + local found = false + for k2, v2 in ipairs(t2) do + if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then + found= true + break + end + end + + if not found then + res[k1] = v1 + end + end + + return next(res) ~= nil and res or nil end _docker.clear_empty_tables = function ( t ) - local k, v - if next(t) == nil then - t = nil - else - for k, v in pairs(t) do - if type(v) == 'table' then - t[k] = _docker.clear_empty_tables(v) - end - end - end - return t + local k, v + + if next(t) == nil then + t = nil + else + for k, v in pairs(t) do + if type(v) == 'table' then + t[k] = _docker.clear_empty_tables(v) + end + end + end + + return t end --- return create_body, extra_network local get_config = function(container_config, image_config) - local config = container_config.Config - local old_host_config = container_config.HostConfig - local old_network_setting = container_config.NetworkSettings.Networks or {} - if config.WorkingDir == image_config.WorkingDir then config.WorkingDir = "" end - if config.User == image_config.User then config.User = "" end - if table_equal(config.Cmd, image_config.Cmd) then config.Cmd = nil end - if table_equal(config.Entrypoint, image_config.Entrypoint) then config.Entrypoint = nil end - if table_equal(config.ExposedPorts, image_config.ExposedPorts) then config.ExposedPorts = nil end - config.Env = table_subtract(config.Env, image_config.Env) - config.Labels = table_subtract(config.Labels, image_config.Labels) - config.Volumes = map_subtract(config.Volumes, image_config.Volumes) - -- subtract ports exposed in image from container - if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then - config.ExposedPorts = {} - for p, v in pairs(old_host_config.PortBindings) do - config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort } - end - end - - -- handle network config, we need only one network, extras need to network connect action - local network_setting = {} - local multi_network = false - local extra_network = {} - for k, v in pairs(old_network_setting) do - if multi_network then - extra_network[k] = v - else - network_setting[k] = v - end - multi_network = true - end - - -- handle hostconfig - local host_config = old_host_config - -- if host_config.PortBindings and next(host_config.PortBindings) == nil then host_config.PortBindings = nil end - -- host_config.LogConfig = nil - host_config.Mounts = {} - -- for volumes - for i, v in ipairs(container_config.Mounts) do - if v.Type == "volume" then - table.insert(host_config.Mounts, { - Type = v.Type, - Target = v.Destination, - Source = v.Source:match("([^/]+)\/_data"), - BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil, - ReadOnly = not v.RW - }) - end - end - - - -- merge configs - local create_body = config - create_body["HostConfig"] = host_config - create_body["NetworkingConfig"] = {EndpointsConfig = network_setting} - create_body = _docker.clear_empty_tables(create_body) or {} - extra_network = _docker.clear_empty_tables(extra_network) or {} - return create_body, extra_network + local config = container_config.Config + local old_host_config = container_config.HostConfig + local old_network_setting = container_config.NetworkSettings.Networks or {} + + if config.WorkingDir == image_config.WorkingDir then + config.WorkingDir = "" + end + + if config.User == image_config.User then + config.User = "" + end + + if table_equal(config.Cmd, image_config.Cmd) then + config.Cmd = nil + end + + if table_equal(config.Entrypoint, image_config.Entrypoint) then + config.Entrypoint = nil + end + + if table_equal(config.ExposedPorts, image_config.ExposedPorts) then + config.ExposedPorts = nil + end + + config.Env = table_subtract(config.Env, image_config.Env) + config.Labels = table_subtract(config.Labels, image_config.Labels) + config.Volumes = map_subtract(config.Volumes, image_config.Volumes) + + if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then + config.ExposedPorts = {} + for p, v in pairs(old_host_config.PortBindings) do + config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort } + end + end + + local network_setting = {} + local multi_network = false + local extra_network = {} + + for k, v in pairs(old_network_setting) do + if multi_network then + extra_network[k] = v + else + network_setting[k] = v + end + multi_network = true + end + + local host_config = old_host_config + host_config.Mounts = {} + for i, v in ipairs(container_config.Mounts) do + if v.Type == "volume" then + table.insert(host_config.Mounts, { + Type = v.Type, + Target = v.Destination, + Source = v.Source:match("([^/]+)\/_data"), + BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil, + ReadOnly = not v.RW + }) + end + end + + local create_body = config + create_body["HostConfig"] = host_config + create_body["NetworkingConfig"] = {EndpointsConfig = network_setting} + create_body = _docker.clear_empty_tables(create_body) or {} + extra_network = _docker.clear_empty_tables(extra_network) or {} + + return create_body, extra_network end local upgrade = function(self, request) - _docker:clear_status() - -- get image name, image id, container name, configuration information - local container_info = self.containers:inspect({id = request.id}) - if container_info.code > 300 and type(container_info.body) == "table" then - return container_info - end - local image_name = container_info.body.Config.Image - if not image_name:match(".-:.+") then image_name = image_name .. ":latest" end - local old_image_id = container_info.body.Image - local container_name = container_info.body.Name:sub(2) - - local image_id, res = update_image(self, image_name) - if res and res.code ~= 200 then return res end - if image_id == old_image_id then - return {code = 305, body = {message = "Already up to date"}} - end - - _docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...") - res = self.containers:stop({name = container_name}) - if res and res.code < 305 then - _docker:append_status("done\n") - else - return res - end - - _docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...") - res = self.containers:rename({name = container_name, query = { name = container_name .. "_old" }}) - if res and res.code < 300 then - _docker:append_status("done\n") - else - return res - end - - -- handle config - local image_config = self.images:inspect({id = old_image_id}).body.Config - local create_body, extra_network = get_config(container_info.body, image_config) - - -- create new container - _docker:append_status("Container: Create" .. " " .. container_name .. "...") - create_body = _docker.clear_empty_tables(create_body) - res = self.containers:create({name = container_name, body = create_body}) - if res and res.code > 300 then return res end - _docker:append_status("done\n") - - -- extra networks need to network connect action - for k, v in pairs(extra_network) do - _docker:append_status("Networks: Connect" .. " " .. container_name .. "...") - res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}}) - if res.code > 300 then return res end - - _docker:append_status("done\n") - end - _docker:clear_status() - return res + _docker:clear_status() + + local container_info = self.containers:inspect({id = request.id}) + + if container_info.code > 300 and type(container_info.body) == "table" then + return container_info + end + + local image_name = container_info.body.Config.Image + if not image_name:match(".-:.+") then + image_name = image_name .. ":latest" + end + + local old_image_id = container_info.body.Image + local container_name = container_info.body.Name:sub(2) + + local image_id, res = update_image(self, image_name) + if res and res.code ~= 200 then + return res + end + + if image_id == old_image_id then + return {code = 305, body = {message = "Already up to date"}} + end + + _docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...") + res = self.containers:stop({name = container_name}) + if res and res.code < 305 then + _docker:append_status("done\n") + else + return res + end + + _docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...") + res = self.containers:rename({name = container_name, query = { name = container_name .. "_old" }}) + if res and res.code < 300 then + _docker:append_status("done\n") + else + return res + end + + local image_config = self.images:inspect({id = old_image_id}).body.Config + local create_body, extra_network = get_config(container_info.body, image_config) + + -- create new container + _docker:append_status("Container: Create" .. " " .. container_name .. "...") + create_body = _docker.clear_empty_tables(create_body) + res = self.containers:create({name = container_name, body = create_body}) + if res and res.code > 300 then + return res + end + _docker:append_status("done\n") + + -- extra networks need to network connect action + for k, v in pairs(extra_network) do + _docker:append_status("Networks: Connect" .. " " .. container_name .. "...") + res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}}) + if res.code > 300 then + return res + end + _docker:append_status("done\n") + end + + _docker:clear_status() + return res end local duplicate_config = function (self, request) - local container_info = self.containers:inspect({id = request.id}) - if container_info.code > 300 and type(container_info.body) == "table" then return nil end - local old_image_id = container_info.body.Image - local image_config = self.images:inspect({id = old_image_id}).body.Config - return get_config(container_info.body, image_config) + local container_info = self.containers:inspect({id = request.id}) + if container_info.code > 300 and type(container_info.body) == "table" then + return nil + end + + local old_image_id = container_info.body.Image + local image_config = self.images:inspect({id = old_image_id}).body.Config + + return get_config(container_info.body, image_config) end -_docker.new = function(option) - local option = option or {} - local remote = uci:get("dockerman", "local", "remote_endpoint") - options = { - host = (remote == "true") and (option.host or uci:get("dockerman", "local", "remote_host")) or nil, - port = (remote == "true") and (option.port or uci:get("dockerman", "local", "remote_port")) or nil, - debug = option.debug or uci:get("dockerman", "local", "debug") == 'true' and true or false, - debug_path = option.debug_path or uci:get("dockerman", "local", "debug_path") - } - options.socket_path = (remote ~= "true" or not options.host or not options.port) and (option.socket_path or uci:get("dockerman", "local", "socket_path") or "/var/run/docker.sock") or nil - local _new = docker.new(options) - _new.options.status_path = uci:get("dockerman", "local", "status_path") - _new.containers_upgrade = upgrade - _new.containers_duplicate_config = duplicate_config - return _new +_docker.new = function() + local host = nil + local port = nil + local socket_path = nil + local debug_path = nil + + local remote = uci:get_bool("dockerd", "globals", "remote_endpoint") + if remote then + host = uci:get("dockerd", "globals", "remote_host") or nil + port = uci:get("dockerd", "globals", "remote_port") or nil + else + socket_path = uci:get("dockerd", "globals", "socket_path") or "/var/run/docker.sock" + end + + local debug = uci:get_bool("dockerd", "globals", "debug") + if debug then + debug_path = uci:get("dockerd", "globals", "debug_path") or "/tmp/.docker_debug" + end + + local status_path = uci:get("dockerd", "globals", "status_path") or "/tmp/.docker_status" + + _docker.options = { + host = host, + port = port, + socket_path = socket_path, + debug = debug, + debug_path = debug_path, + status_path = status_path + } + + local _new = docker.new(_docker.options) + _new.containers_upgrade = upgrade + _new.containers_duplicate_config = duplicate_config + + return _new end -_docker.options={} -_docker.options.status_path = uci:get("dockerman", "local", "status_path") _docker.append_status=function(self,val) - if not val then return end - local file_docker_action_status=io.open(self.options.status_path, "a+") - file_docker_action_status:write(val) - file_docker_action_status:close() + if not val then + return + end + local file_docker_action_status=io.open(self.options.status_path, "a+") + file_docker_action_status:write(val) + file_docker_action_status:close() end _docker.write_status=function(self,val) - if not val then return end - local file_docker_action_status=io.open(self.options.status_path, "w+") - file_docker_action_status:write(val) - file_docker_action_status:close() + if not val then + return + end + local file_docker_action_status=io.open(self.options.status_path, "w+") + file_docker_action_status:write(val) + file_docker_action_status:close() end _docker.read_status=function(self) - return nixio.fs.readfile(self.options.status_path) + return fs.readfile(self.options.status_path) end _docker.clear_status=function(self) - nixio.fs.remove(self.options.status_path) + fs.remove(self.options.status_path) end local status_cb = function(res, source, handler) - res.body = res.body or {} - while true do - local chunk = source() - if chunk then - --standard output to res.body - table.insert(res.body, chunk) - handler(chunk) - else - return - end - end + res.body = res.body or {} + while true do + local chunk = source() + if chunk then + --standard output to res.body + table.insert(res.body, chunk) + handler(chunk) + else + return + end + end end --{"status":"Pulling from library\/debian","id":"latest"} @@ -284,114 +355,128 @@ end --{"status":"Digest: sha256:a63d0b2ecbd723da612abf0a8bdb594ee78f18f691d7dc652ac305a490c9b71a"} --{"status":"Status: Downloaded newer image for debian:latest"} _docker.pull_image_show_status_cb = function(res, source) - return status_cb(res, source, function(chunk) - local json_parse = luci.jsonc.parse - local step = json_parse(chunk) - if type(step) == "table" then - local buf = _docker:read_status() - local num = 0 - local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" - if step.id then buf, num = buf:gsub("\t"..step.id .. ": .-\n", str) end - if num == 0 then - buf = buf .. str - end - _docker:write_status(buf) - end - end) + return status_cb(res, source, function(chunk) + local json_parse = luci.jsonc.parse + local step = json_parse(chunk) + if type(step) == "table" then + local buf = _docker:read_status() + local num = 0 + local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" + if step.id then + buf, num = buf:gsub("\t"..step.id .. ": .-\n", str) + end + if num == 0 then + buf = buf .. str + end + _docker:write_status(buf) + end + end) end --{"status":"Downloading from https://downloads.openwrt.org/releases/19.07.0/targets/x86/64/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz"} --{"status":"Importing","progressDetail":{"current":1572391,"total":3821714},"progress":"[====================\u003e ] 1.572MB/3.822MB"} --{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"} _docker.import_image_show_status_cb = function(res, source) - return status_cb(res, source, function(chunk) - local json_parse = luci.jsonc.parse - local step = json_parse(chunk) - if type(step) == "table" then - local buf = _docker:read_status() - local num = 0 - local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" - if step.status then buf, num = buf:gsub("\t"..step.status .. " .-\n", str) end - if num == 0 then - buf = buf .. str - end - _docker:write_status(buf) - end - end - ) + return status_cb(res, source, function(chunk) + local json_parse = luci.jsonc.parse + local step = json_parse(chunk) + if type(step) == "table" then + local buf = _docker:read_status() + local num = 0 + local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" + if step.status then + buf, num = buf:gsub("\t"..step.status .. " .-\n", str) + end + if num == 0 then + buf = buf .. str + end + _docker:write_status(buf) + end + end) end --- _docker.print_status_cb = function(res, source) --- return status_cb(res, source, function(step) --- luci.util.perror(step) --- end --- ) --- end - _docker.create_macvlan_interface = function(name, device, gateway, subnet) - if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end - if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end - local ip = require "luci.ip" - local if_name = "docker_"..name - local dev_name = "macvlan_"..name - local net_mask = tostring(ip.new(subnet):mask()) - local lan_interfaces - -- add macvlan device - uci:delete("network", dev_name) - uci:set("network", dev_name, "device") - uci:set("network", dev_name, "name", dev_name) - uci:set("network", dev_name, "ifname", device) - uci:set("network", dev_name, "type", "macvlan") - uci:set("network", dev_name, "mode", "bridge") - -- add macvlan interface - uci:delete("network", if_name) - uci:set("network", if_name, "interface") - uci:set("network", if_name, "proto", "static") - uci:set("network", if_name, "ifname", dev_name) - uci:set("network", if_name, "ipaddr", gateway) - uci:set("network", if_name, "netmask", net_mask) - uci:foreach("firewall", "zone", function(s) - if s.name == "lan" then - local interfaces - if type(s.network) == "table" then - interfaces = table.concat(s.network, " ") - uci:delete("firewall", s[".name"], "network") - else - interfaces = s.network and s.network or "" - end - interfaces = interfaces .. " " .. if_name - interfaces = interfaces:gsub("%s+", " ") - uci:set("firewall", s[".name"], "network", interfaces) - end - end) - uci:commit("firewall") - uci:commit("network") - os.execute("ifup " .. if_name) + if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then + return + end + + if uci:get("dockerd", "globals", "remote_endpoint") == "true" then + return + end + + local ip = require "luci.ip" + local if_name = "docker_"..name + local dev_name = "macvlan_"..name + local net_mask = tostring(ip.new(subnet):mask()) + local lan_interfaces + + -- add macvlan device + uci:delete("network", dev_name) + uci:set("network", dev_name, "device") + uci:set("network", dev_name, "name", dev_name) + uci:set("network", dev_name, "ifname", device) + uci:set("network", dev_name, "type", "macvlan") + uci:set("network", dev_name, "mode", "bridge") + + -- add macvlan interface + uci:delete("network", if_name) + uci:set("network", if_name, "interface") + uci:set("network", if_name, "proto", "static") + uci:set("network", if_name, "ifname", dev_name) + uci:set("network", if_name, "ipaddr", gateway) + uci:set("network", if_name, "netmask", net_mask) + uci:foreach("firewall", "zone", function(s) + if s.name == "lan" then + local interfaces + if type(s.network) == "table" then + interfaces = table.concat(s.network, " ") + uci:delete("firewall", s[".name"], "network") + else + interfaces = s.network and s.network or "" + end + interfaces = interfaces .. " " .. if_name + interfaces = interfaces:gsub("%s+", " ") + uci:set("firewall", s[".name"], "network", interfaces) + end + end) + + uci:commit("firewall") + uci:commit("network") + + os.execute("ifup " .. if_name) end _docker.remove_macvlan_interface = function(name) - if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end - if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end - local if_name = "docker_"..name - local dev_name = "macvlan_"..name - uci:foreach("firewall", "zone", function(s) - if s.name == "lan" then - local interfaces - if type(s.network) == "table" then - interfaces = table.concat(s.network, " ") - else - interfaces = s.network and s.network or "" - end - interfaces = interfaces and interfaces:gsub(if_name, "") - interfaces = interfaces and interfaces:gsub("%s+", " ") - uci:set("firewall", s[".name"], "network", interfaces) - end - end) - uci:commit("firewall") - uci:delete("network", dev_name) - uci:delete("network", if_name) - uci:commit("network") - os.execute("ip link del " .. if_name) + if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then + return + end + + if uci:get("dockerd", "globals", "remote_endpoint") == "true" then + return + end + + local if_name = "docker_"..name + local dev_name = "macvlan_"..name + uci:foreach("firewall", "zone", function(s) + if s.name == "lan" then + local interfaces + if type(s.network) == "table" then + interfaces = table.concat(s.network, " ") + else + interfaces = s.network and s.network or "" + end + interfaces = interfaces and interfaces:gsub(if_name, "") + interfaces = interfaces and interfaces:gsub("%s+", " ") + uci:set("firewall", s[".name"], "network", interfaces) + end + end) + + uci:delete("network", dev_name) + uci:delete("network", if_name) + uci:commit("network") + uci:commit("firewall") + + os.execute("ip link del " .. if_name) end return _docker |