mirror of
https://github.com/Ysurac/openmptcprouter-feeds.git
synced 2025-03-09 15:40:03 +00:00
fix
This commit is contained in:
parent
6b7f0b4dba
commit
fd8b7384d5
104 changed files with 13356 additions and 31 deletions
152
luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua
Normal file
152
luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua
Normal file
|
@ -0,0 +1,152 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2021 Florian Eckert <fe@dev.tdt.de>
|
||||
Copyright 2021 lisaac <lisaac.cn@gmail.com>
|
||||
]]--
|
||||
|
||||
local uci = (require "luci.model.uci").cursor()
|
||||
|
||||
local m, s, o
|
||||
|
||||
m = Map("dockerd",
|
||||
translate("Docker - Configuration"),
|
||||
translate("DockerMan is a simple docker manager client for LuCI"))
|
||||
|
||||
if nixio.fs.access("/usr/bin/dockerd") and not m.uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
|
||||
s = m:section(NamedSection, "globals", "section", translate("Docker Daemon settings"))
|
||||
|
||||
o = s:option(Flag, "auto_start", translate("Auto start"))
|
||||
o.rmempty = false
|
||||
o.write = function(self, section, value)
|
||||
if value == "1" then
|
||||
luci.util.exec("/etc/init.d/dockerd enable")
|
||||
else
|
||||
luci.util.exec("/etc/init.d/dockerd disable")
|
||||
end
|
||||
m.uci:set("dockerd", "globals", "auto_start", value)
|
||||
end
|
||||
|
||||
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.datatype = "ipaddr"
|
||||
o:depends("remote_endpoint", 0)
|
||||
|
||||
o = s:option(DynamicList, "registry_mirrors",
|
||||
translate("Registry Mirrors"),
|
||||
translate("It replaces the daemon registry mirrors with a new set of registry mirrors"))
|
||||
o:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com")
|
||||
o:depends("remote_endpoint", 0)
|
||||
o.forcewrite = true
|
||||
|
||||
o = s:option(ListValue, "log_level",
|
||||
translate("Log Level"),
|
||||
translate('Set the logging level'))
|
||||
o:value("debug", translate("Debug"))
|
||||
o:value("", translate("Info")) -- This is the default debug level from the deamon is optin is not set
|
||||
o:value("warn", translate("Warning"))
|
||||
o:value("error", translate("Error"))
|
||||
o:value("fatal", translate("Fatal"))
|
||||
o.rmempty = true
|
||||
o:depends("remote_endpoint", 0)
|
||||
|
||||
o = s:option(DynamicList, "hosts",
|
||||
translate("Client connection"),
|
||||
translate('Specifies where the Docker daemon will listen for client connections (default: unix:///var/run/docker.sock)'))
|
||||
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
|
||||
|
||||
s = m:section(NamedSection, "dockerman", "section", translate("DockerMan settings"))
|
||||
s:tab("ac", translate("Access Control"))
|
||||
s:tab("dockerman", translate("DockerMan"))
|
||||
|
||||
o = s:taboption("dockerman", Flag, "remote_endpoint",
|
||||
translate("Remote Endpoint"),
|
||||
translate("Connect to remote docker endpoint"))
|
||||
o.rmempty = false
|
||||
o.validate = function(self, value, sid)
|
||||
local res = luci.http.formvaluetable("cbid.dockerd")
|
||||
if res["dockerman.remote_endpoint"] == "1" then
|
||||
if res["dockerman.remote_port"] and res["dockerman.remote_port"] ~= "" and res["dockerman.remote_host"] and res["dockerman.remote_host"] ~= "" then
|
||||
return 1
|
||||
else
|
||||
return nil, translate("Please input the PORT or HOST IP of remote docker instance!")
|
||||
end
|
||||
else
|
||||
if not res["dockerman.socket_path"] then
|
||||
return nil, translate("Please input the SOCKET PATH of docker daemon!")
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
o = s:taboption("dockerman", Value, "socket_path",
|
||||
translate("Docker Socket Path"))
|
||||
o.default = "/var/run/docker.sock"
|
||||
o.placeholder = "/var/run/docker.sock"
|
||||
o:depends("remote_endpoint", 0)
|
||||
|
||||
o = s:taboption("dockerman", Value, "remote_host",
|
||||
translate("Remote Host"),
|
||||
translate("Host or IP Address for the connection to a remote docker instance"))
|
||||
o.datatype = "host"
|
||||
o.placeholder = "10.1.1.2"
|
||||
o:depends("remote_endpoint", 1)
|
||||
|
||||
o = s:taboption("dockerman", Value, "remote_port",
|
||||
translate("Remote Port"))
|
||||
o.placeholder = "2375"
|
||||
o.datatype = "port"
|
||||
o:depends("remote_endpoint", 1)
|
||||
|
||||
-- o = s:taboption("dockerman", Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file"))
|
||||
-- o = s:taboption("dockerman", Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path"))
|
||||
-- o.enabled="true"
|
||||
-- o.disabled="false"
|
||||
-- o = s:taboption("dockerman", Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile"))
|
||||
|
||||
if nixio.fs.access("/usr/bin/dockerd") and not m.uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
|
||||
o = s: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
|
||||
o:value(v, v)
|
||||
end
|
||||
o = s:taboption("ac", DynamicList, "ac_allowed_ports", translate("Ports allowed to be accessed"), translate("Which Port(s) can be accessed, it's not restricted by the Allowed Access interfaces configuration. Use this configuration with caution!"))
|
||||
o.placeholder = "8080/tcp"
|
||||
local docker = require "luci.model.docker"
|
||||
local containers, res, lost_state
|
||||
local dk = docker.new()
|
||||
if dk:_ping().code ~= 200 then
|
||||
lost_state = true
|
||||
else
|
||||
lost_state = false
|
||||
res = dk.containers:list()
|
||||
if res and res.code and res.code < 300 then
|
||||
containers = res.body
|
||||
end
|
||||
end
|
||||
|
||||
-- allowed_container.placeholder = "container name_or_id"
|
||||
if containers then
|
||||
for i, v in ipairs(containers) do
|
||||
if v.State == "running" and v.Ports then
|
||||
for _, port in ipairs(v.Ports) do
|
||||
if port.PublicPort and port.IP and not string.find(port.IP,":") then
|
||||
o:value(port.PublicPort.."/"..port.Type, v.Names[1]:sub(2) .. " | " .. port.PublicPort .. " | " .. port.Type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
810
luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua
Normal file
810
luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua
Normal file
|
@ -0,0 +1,810 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
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 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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
local start_stop_remove = function(m, cmd)
|
||||
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",
|
||||
translatef("Docker - Container (%s)", container_info.Name:sub(2)),
|
||||
translate("On this page, the selected container can be managed."))
|
||||
m.redirect = luci.dispatcher.build_url("admin/docker/containers")
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
o = s:option(Button, "_export")
|
||||
o.template = "dockerman/cbi/inlinebutton"
|
||||
o.inputtitle=translate("Export")
|
||||
o.inputstyle = "apply"
|
||||
o.forcewrite = true
|
||||
o.write = function(self, section)
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container_export/"..container_id))
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
s = m:section(SimpleSection)
|
||||
s.template = "dockerman/container"
|
||||
|
||||
if action == "info" then
|
||||
res = dk.networks:list()
|
||||
if res.code < 300 then
|
||||
networks = res.body
|
||||
else
|
||||
return
|
||||
end
|
||||
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 and 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
|
||||
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
|
||||
m.submit = false
|
||||
m.reset = false
|
||||
s= m:section(SimpleSection)
|
||||
s.template = "dockerman/container_file_manager"
|
||||
s.container = container_id
|
||||
m.redirect = nil
|
||||
elseif action == "inspect" then
|
||||
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 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("command -v docker"):match("^.+docker") or nil
|
||||
local cmd_ttyd = luci.util.exec("command -v 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("command -v docker"):match("^.+docker") or nil
|
||||
local cmd_ttyd = luci.util.exec("command -v 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 uci = (require "luci.model.uci").cursor()
|
||||
|
||||
local ttyd_ssl = uci:get("ttyd", "@ttyd[0]", "ssl")
|
||||
local ttyd_ssl_key = uci:get("ttyd", "@ttyd[0]", "ssl_key")
|
||||
local ttyd_ssl_cert = uci:get("ttyd", "@ttyd[0]", "ssl_cert")
|
||||
|
||||
if ttyd_ssl == "1" and ttyd_ssl_cert and ttyd_ssl_key then
|
||||
cmd_ttyd = string.format('%s -S -C %s -K %s', cmd_ttyd, ttyd_ssl_cert, ttyd_ssl_key)
|
||||
end
|
||||
|
||||
local pid = luci.util.trim(luci.util.exec("netstat -lnpt | grep :7682 | grep ttyd | tr -s ' ' | cut -d ' ' -f7 | cut -d'/' -f1"))
|
||||
if pid and pid ~= "" then
|
||||
luci.util.exec("kill -9 " .. pid)
|
||||
end
|
||||
|
||||
local hosts
|
||||
local remote = uci:get_bool("dockerd", "dockerman", "remote_endpoint") or false
|
||||
local host = nil
|
||||
local port = nil
|
||||
local socket = nil
|
||||
|
||||
if remote then
|
||||
host = uci:get("dockerd", "dockerman", "remote_host") or nil
|
||||
port = uci:get("dockerd", "dockerman", "remote_port") or nil
|
||||
else
|
||||
socket = uci:get("dockerd", "dockerman", "socket_path") or "/var/run/docker.sock"
|
||||
end
|
||||
|
||||
if remote and host and port then
|
||||
hosts = "tcp://" .. host .. ':'.. port
|
||||
elseif socket then
|
||||
hosts = "unix://" .. socket
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
if uid and uid ~= "" then
|
||||
uid = "-u " .. uid
|
||||
else
|
||||
uid = ""
|
||||
end
|
||||
|
||||
local start_cmd = string.format('%s -d 2 --once -p 7682 %s -H "%s" exec -it %s %s %s&', cmd_ttyd, cmd_docker, hosts, uid, 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
|
||||
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
|
284
luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua
Normal file
284
luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua
Normal file
|
@ -0,0 +1,284 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
local http = require "luci.http"
|
||||
local docker = require "luci.model.docker"
|
||||
|
||||
local m, s, o
|
||||
local images, networks, containers, res, lost_state
|
||||
local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode
|
||||
local dk = docker.new()
|
||||
|
||||
if dk:_ping().code ~= 200 then
|
||||
lost_state = true
|
||||
else
|
||||
res = dk.images:list()
|
||||
if res and res.code and res.code < 300 then
|
||||
images = res.body
|
||||
end
|
||||
|
||||
res = dk.networks:list()
|
||||
if res and res.code and res.code < 300 then
|
||||
networks = res.body
|
||||
end
|
||||
|
||||
res = dk.containers:list({
|
||||
query = {
|
||||
all = true
|
||||
}
|
||||
})
|
||||
if res and res.code and res.code < 300 then
|
||||
containers = res.body
|
||||
end
|
||||
end
|
||||
|
||||
function get_containers()
|
||||
local data = {}
|
||||
if type(containers) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
|
||||
for i, v in ipairs(containers) do
|
||||
local index = (10^12 - v.Created) .. "_id_" .. 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]["_status"] = v.Status
|
||||
|
||||
if v.Status:find("^Up") then
|
||||
data[index]["_name"] = "<font color='green'>"..v.Names[1]:sub(2).."</font>"
|
||||
data[index]["_status"] = "<a href='"..luci.dispatcher.build_url("admin/docker/container/"..v.Id).."/stats'><font color='green'>".. data[index]["_status"] .. "</font>" .. "<br><font color='#9f9f9f' class='container_cpu_status'></font><br><font color='#9f9f9f' class='container_mem_status'></font><br><font color='#9f9f9f' class='container_network_status'></font></a>"
|
||||
else
|
||||
data[index]["_name"] = "<font color='red'>"..v.Names[1]:sub(2).."</font>"
|
||||
data[index]["_status"] = '<font class="container_not_running" 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
|
||||
local ip = require "luci.ip"
|
||||
for _,v2 in ipairs(v.Ports) do
|
||||
-- display ipv4 only
|
||||
if ip.new(v2.IP or "0.0.0.0"):is4() then
|
||||
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
|
||||
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]["_id_name"] = '<a href='..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. data[index]["_name"] .. "<br><font color='#9f9f9f'>ID: " .. data[index]["_id"]
|
||||
.. "</font></a><br>Image: " .. (data[index]["_image"] or "<none>")
|
||||
.. "<br><font color='#9f9f9f' class='container_size_".. v.Id .."'></font>"
|
||||
|
||||
if type(v.Mounts) == "table" and next(v.Mounts) then
|
||||
for _, v2 in pairs(v.Mounts) do
|
||||
if v2.Type ~= "volume" then
|
||||
local v_sorce_d, v_dest_d
|
||||
local v_sorce = ""
|
||||
local v_dest = ""
|
||||
for v_sorce_d in v2["Source"]:gmatch('[^/]+') do
|
||||
if v_sorce_d and #v_sorce_d > 12 then
|
||||
v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,8) .. ".."
|
||||
else
|
||||
v_sorce = v_sorce .."/".. v_sorce_d
|
||||
end
|
||||
end
|
||||
for v_dest_d in v2["Destination"]:gmatch('[^/]+') do
|
||||
if v_dest_d and #v_dest_d > 12 then
|
||||
v_dest = v_dest .. "/" .. v_dest_d:sub(1,8) .. ".."
|
||||
else
|
||||
v_dest = v_dest .."/".. v_dest_d
|
||||
end
|
||||
end
|
||||
data[index]["_mounts"] = (data[index]["_mounts"] and (data[index]["_mounts"] .. "<br>") or "") .. '<span title="'.. v2.Source.. "→" .. v2.Destination .. '" ><a href="'..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..'/file?path='..v2["Destination"]..'">' .. v_sorce .. "→" .. v_dest..'</a></span>'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
data[index]["_image_id"] = v.ImageID:sub(8,20)
|
||||
data[index]["_command"] = v.Command
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
local container_list = not lost_state and get_containers() or {}
|
||||
|
||||
m = SimpleForm("docker",
|
||||
translate("Docker - Containers"),
|
||||
translate("This page displays all containers that have been created on the connected docker host."))
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
m:append(Template("dockerman/containers_running_stats"))
|
||||
|
||||
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
|
||||
s.config="containers"
|
||||
|
||||
o = s:option(Flag, "_selected","")
|
||||
o.disabled = 0
|
||||
o.enabled = 1
|
||||
o.default = 0
|
||||
o.width = "1%"
|
||||
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, "_id_name", translate("Container Info"))
|
||||
o.rawhtml = true
|
||||
o.width="15%"
|
||||
|
||||
o = s:option(DummyValue, "_status", translate("Status"))
|
||||
o.width="15%"
|
||||
o.rawhtml=true
|
||||
|
||||
o = s:option(DummyValue, "_network", translate("Network"))
|
||||
o.width="10%"
|
||||
|
||||
o = s:option(DummyValue, "_ports", translate("Ports"))
|
||||
o.width="5%"
|
||||
o.rawhtml = true
|
||||
o = s:option(DummyValue, "_mounts", translate("Mounts"))
|
||||
o.width="25%"
|
||||
o.rawhtml = true
|
||||
|
||||
-- o = s:option(DummyValue, "_image", translate("Image"))
|
||||
-- o.width="8%"
|
||||
|
||||
o = s:option(DummyValue, "_command", translate("Command"))
|
||||
o.width="15%"
|
||||
|
||||
local start_stop_remove = function(m, cmd)
|
||||
local container_selected = {}
|
||||
-- 遍历table中sectionid
|
||||
for k in pairs(container_list) do
|
||||
-- 得到选中项的名字
|
||||
if container_list[k]._selected == 1 then
|
||||
container_selected[#container_selected + 1] = container_list[k]["_id"]
|
||||
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 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
|
||||
|
||||
s = m:section(Table,{{}})
|
||||
s.notitle=true
|
||||
s.rowcolors=false
|
||||
s.template="cbi/nullsection"
|
||||
|
||||
o = s:option(Button, "_new")
|
||||
o.inputtitle = translate("Add")
|
||||
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
|
||||
o.disable = lost_state
|
||||
|
||||
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
|
||||
o.disable = lost_state
|
||||
|
||||
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
|
||||
o.disable = lost_state
|
||||
|
||||
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
|
||||
o.disable = lost_state
|
||||
|
||||
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
|
||||
o.disable = lost_state
|
||||
|
||||
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
|
||||
o.disable = lost_state
|
||||
|
||||
return m
|
284
luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua
Normal file
284
luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua
Normal file
|
@ -0,0 +1,284 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
|
||||
local containers, images, res, lost_state
|
||||
local m, s, o
|
||||
|
||||
if dk:_ping().code ~= 200 then
|
||||
lost_state = true
|
||||
else
|
||||
res = dk.images:list()
|
||||
if res and res.code and res.code < 300 then
|
||||
images = res.body
|
||||
end
|
||||
|
||||
res = dk.containers:list({ query = { all = true } })
|
||||
if res and res.code and res.code < 300 then
|
||||
containers = res.body
|
||||
end
|
||||
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
|
||||
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 = not lost_state and get_images() or {}
|
||||
|
||||
m = SimpleForm("docker",
|
||||
translate("Docker - Images"),
|
||||
translate("On this page all images are displayed that are available on the system and with which a container can be created."))
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
|
||||
local pull_value={
|
||||
_image_tag_name="",
|
||||
_registry="index.docker.io"
|
||||
}
|
||||
|
||||
s = m:section(SimpleSection,
|
||||
translate("Pull Image"),
|
||||
translate("By entering a valid image name with the corresponding version, the docker image can be downloaded from the configured registry."))
|
||||
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
|
||||
|
||||
o = s:option(Button, "_pull")
|
||||
o.inputtitle= translate("Pull")
|
||||
o.template = "dockerman/cbi/inlinebutton"
|
||||
o.inputstyle = "add"
|
||||
o.disable = lost_state
|
||||
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 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
|
||||
|
||||
s = m:section(SimpleSection,
|
||||
translate("Import Image"),
|
||||
translate("When pressing the Import button, both a local image can be loaded onto the system and a valid image tar can be downloaded from remote."))
|
||||
|
||||
o = s:option(DummyValue, "_image_import")
|
||||
o.template = "dockerman/images_import"
|
||||
o.disable = lost_state
|
||||
|
||||
s = m:section(Table, image_list, translate("Images overview"))
|
||||
|
||||
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, "_id", translate("ID"))
|
||||
o.rawhtml = true
|
||||
|
||||
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"))
|
||||
|
||||
local remove_action = function(force)
|
||||
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 and 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
|
||||
|
||||
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, "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.disable = lost_state
|
||||
|
||||
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
|
||||
o.disable = lost_state
|
||||
|
||||
o = s:option(Button, "save")
|
||||
o.inputtitle= translate("Save")
|
||||
o.template = "dockerman/cbi/inlinebutton"
|
||||
o.inputstyle = "edit"
|
||||
o.disable = lost_state
|
||||
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, show_name
|
||||
|
||||
for _, img in ipairs(image_selected) do
|
||||
names = names and (names .. "&names=".. img) or img
|
||||
end
|
||||
if #image_selected > 1 then
|
||||
show_name = "images"
|
||||
else
|
||||
show_name = image_selected[1]
|
||||
end
|
||||
local cb = function(res, chunk)
|
||||
if res and res.code and res.code == 200 then
|
||||
if not first then
|
||||
first = true
|
||||
luci.http.header('Content-Disposition', 'inline; filename="'.. show_name .. '.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 and msg.code and msg.code ~= 200 then
|
||||
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
||||
else
|
||||
docker:clear_status()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(Button, "load")
|
||||
o.inputtitle= translate("Load")
|
||||
o.template = "dockerman/images_load"
|
||||
o.inputstyle = "add"
|
||||
o.disable = lost_state
|
||||
|
||||
return m
|
159
luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua
Normal file
159
luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua
Normal file
|
@ -0,0 +1,159 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
local docker = require "luci.model.docker"
|
||||
|
||||
local m, s, o
|
||||
local networks, dk, res, lost_state
|
||||
|
||||
dk = docker.new()
|
||||
|
||||
if dk:_ping().code ~= 200 then
|
||||
lost_state = true
|
||||
else
|
||||
res = dk.networks:list()
|
||||
if res and res.code and res.code < 300 then
|
||||
networks = res.body
|
||||
end
|
||||
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
|
||||
end
|
||||
|
||||
local network_list = not lost_state and get_networks() or {}
|
||||
|
||||
m = SimpleForm("docker",
|
||||
translate("Docker - Networks"),
|
||||
translate("This page displays all docker networks that have been created on the connected docker host."))
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
|
||||
s = m:section(Table, network_list, translate("Networks overview"))
|
||||
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
|
||||
|
||||
o = s:option(DummyValue, "_id", translate("ID"))
|
||||
|
||||
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
|
||||
|
||||
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.disable = lost_state
|
||||
o.write = function(self, section)
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
|
||||
end
|
||||
|
||||
o = s:option(Button, "_remove")
|
||||
o.inputtitle= translate("Remove")
|
||||
o.template = "dockerman/cbi/inlinebutton"
|
||||
o.inputstyle = "remove"
|
||||
o.forcewrite = true
|
||||
o.disable = lost_state
|
||||
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 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
|
923
luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua
Normal file
923
luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua
Normal file
|
@ -0,0 +1,923 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
local docker = require "luci.model.docker"
|
||||
|
||||
local m, s, o
|
||||
|
||||
local dk = docker.new()
|
||||
|
||||
local cmd_line = table.concat(arg, '/')
|
||||
local images, networks
|
||||
local create_body = {}
|
||||
|
||||
if dk:_ping().code ~= 200 then
|
||||
lost_state = true
|
||||
images = {}
|
||||
networks = {}
|
||||
else
|
||||
images = dk.images:list().body
|
||||
networks = dk.networks:list().body
|
||||
end
|
||||
|
||||
local is_quot_complete = function(str)
|
||||
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',
|
||||
'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_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
|
||||
|
||||
local default_config = {}
|
||||
|
||||
if cmd_line and cmd_line:match("^DOCKERCLI.+") then
|
||||
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 then
|
||||
if 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
|
||||
default_config.log_driver = create_body.HostConfig.LogConfig.Type or nil
|
||||
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
|
||||
for x, y in ipairs(v) do
|
||||
table.insert( default_config.publish, y.HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
|
||||
end
|
||||
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
|
||||
|
||||
m = SimpleForm("docker", translate("Docker - Containers"))
|
||||
m.redirect = luci.dispatcher.build_url("admin", "docker", "containers")
|
||||
if lost_state then
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
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("Create new docker container"))
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
|
||||
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
|
||||
o:value(v.RepoTags[1], v.RepoTags[1])
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
d_network.default = default_config.network or "bridge"
|
||||
|
||||
local d_ip = s:option(Value, "ip", translate("IPv4 Address"))
|
||||
d_ip.datatype="ip4addr"
|
||||
d_ip:depends("network", "nil")
|
||||
d_ip.default = default_config.ip or nil
|
||||
|
||||
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
|
||||
|
||||
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(Value, "log_driver",
|
||||
translate("Logging driver"),
|
||||
translate("The logging driver for the container"))
|
||||
o.placeholder = "json-file"
|
||||
o.rmempty = true
|
||||
o:depends("advance", 1)
|
||||
o.default = default_config.log_driver 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
|
||||
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 = {}
|
||||
local log_driver = data.log_driver
|
||||
|
||||
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 nil
|
||||
local cpu_shares = data.cpu_shares or nil
|
||||
local cpus = data.cpus or nil
|
||||
local blkio_weight = data.blkio_weight or nil
|
||||
|
||||
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 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
|
||||
|
||||
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 = memory and tonumber(memory)
|
||||
create_body.HostConfig.CpuShares = cpu_shares and tonumber(cpu_shares)
|
||||
create_body.HostConfig.NanoCPUs = cpus and tonumber(cpus) * 10 ^ 9
|
||||
create_body.HostConfig.BlkioWeight = blkio_weight and 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"] = {
|
||||
Config = log_opt,
|
||||
Type = log_driver
|
||||
}
|
||||
|
||||
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 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 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
|
258
luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua
Normal file
258
luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua
Normal file
|
@ -0,0 +1,258 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
local docker = require "luci.model.docker"
|
||||
|
||||
local m, s, o
|
||||
|
||||
local dk = docker.new()
|
||||
if dk:_ping().code ~= 200 then
|
||||
lost_state = true
|
||||
end
|
||||
|
||||
m = SimpleForm("docker", translate("Docker - Network"))
|
||||
m.redirect = luci.dispatcher.build_url("admin", "docker", "networks")
|
||||
if lost_state then
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
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("Create new docker network"))
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(Value, "name",
|
||||
translate("Network Name"),
|
||||
translate("Name of the network that can be selected during container creation"))
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(ListValue, "driver", translate("Driver"))
|
||||
o.rmempty = true
|
||||
o:value("bridge", translate("Bridge device"))
|
||||
o:value("macvlan", translate("MAC VLAN"))
|
||||
o:value("ipvlan", translate("IP VLAN"))
|
||||
o:value("overlay", translate("Overlay network"))
|
||||
|
||||
o = s:option(Value, "parent", translate("Base device"))
|
||||
o.rmempty = true
|
||||
o:depends("driver", "macvlan")
|
||||
local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
|
||||
for _, v in ipairs(interfaces) do
|
||||
o:value(v, v)
|
||||
end
|
||||
o.default="br-lan"
|
||||
o.placeholder="br-lan"
|
||||
|
||||
o = s:option(ListValue, "macvlan_mode", translate("Mode"))
|
||||
o.rmempty = true
|
||||
o:depends("driver", "macvlan")
|
||||
o.default="bridge"
|
||||
o:value("bridge", translate("Bridge (Support direct communication between MAC VLANs)"))
|
||||
o:value("private", translate("Private (Prevent communication between MAC VLANs)"))
|
||||
o:value("vepa", translate("VEPA (Virtual Ethernet Port Aggregator)"))
|
||||
o:value("passthru", translate("Pass-through (Mirror physical device to single MAC VLAN)"))
|
||||
|
||||
o = s:option(ListValue, "ipvlan_mode", translate("Ipvlan Mode"))
|
||||
o.rmempty = true
|
||||
o:depends("driver", "ipvlan")
|
||||
o.default="l3"
|
||||
o:value("l2", translate("L2 bridge"))
|
||||
o:value("l3", translate("L3 bridge"))
|
||||
|
||||
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("driver", "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("driver", "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("driver", "macvlan")
|
||||
o.disabled = 0
|
||||
o.enabled = 1
|
||||
o.default = 1
|
||||
end
|
||||
|
||||
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.driver
|
||||
|
||||
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 and
|
||||
res.code 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
|
151
luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua
Normal file
151
luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua
Normal file
|
@ -0,0 +1,151 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
local docker = require "luci.model.docker"
|
||||
local uci = (require "luci.model.uci").cursor()
|
||||
|
||||
local m, s, o, lost_state
|
||||
local dk = docker.new()
|
||||
|
||||
if dk:_ping().code ~= 200 then
|
||||
lost_state = true
|
||||
end
|
||||
|
||||
m = SimpleForm("dockerd",
|
||||
translate("Docker - Overview"),
|
||||
translate("An overview with the relevant data is displayed here with which the LuCI docker client is connected.")
|
||||
..
|
||||
" " ..
|
||||
[[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] ..
|
||||
translate("Github") ..
|
||||
[[</a>]])
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
|
||||
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='-'}
|
||||
docker_info_table['6MemTotal'] = {_key=translate("Total Memory"),_value='-'}
|
||||
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='-'}
|
||||
|
||||
if nixio.fs.access("/usr/bin/dockerd") and not uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
|
||||
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 = lost_state and translate("Start") or translate("Stop")
|
||||
o.inputstyle = lost_state and "add" or "remove"
|
||||
o.forcewrite = true
|
||||
o.write = function(self, section)
|
||||
docker:clear_status()
|
||||
|
||||
if lost_state then
|
||||
docker:append_status("Docker daemon: starting")
|
||||
luci.util.exec("/etc/init.d/dockerd start")
|
||||
luci.util.exec("sleep 5")
|
||||
luci.util.exec("/etc/init.d/dockerman start")
|
||||
|
||||
else
|
||||
docker:append_status("Docker daemon: stopping")
|
||||
luci.util.exec("/etc/init.d/dockerd stop")
|
||||
end
|
||||
docker:clear_status()
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/overview"))
|
||||
end
|
||||
|
||||
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)
|
||||
docker:clear_status()
|
||||
docker:append_status("Docker daemon: restarting")
|
||||
luci.util.exec("/etc/init.d/dockerd restart")
|
||||
luci.util.exec("sleep 5")
|
||||
luci.util.exec("/etc/init.d/dockerman start")
|
||||
docker:clear_status()
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/overview"))
|
||||
end
|
||||
end
|
||||
|
||||
s = m:section(Table, docker_info_table)
|
||||
s:option(DummyValue, "_key", translate("Info"))
|
||||
s:option(DummyValue, "_value")
|
||||
|
||||
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 socket = luci.model.uci.cursor():get("dockerd", "dockerman", "socket_path")
|
||||
if not lost_state then
|
||||
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()
|
||||
|
||||
-- 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 = docker.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(docker.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)
|
||||
else
|
||||
docker_info_table['3ServerVersion']._value = translate("Can NOT connect to docker daemon, please check!!")
|
||||
end
|
||||
|
||||
return m
|
142
luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua
Normal file
142
luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua
Normal file
|
@ -0,0 +1,142 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
|
||||
local m, s, o
|
||||
|
||||
local res, containers, volumes, lost_state
|
||||
|
||||
function get_volumes()
|
||||
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
|
||||
data[index]["_size"] = "<font class='volume_size_" .. v.Name .. "'>-</font>"
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
if dk:_ping().code ~= 200 then
|
||||
lost_state = true
|
||||
else
|
||||
res = dk.volumes:list()
|
||||
if res and res.code and res.code <300 then
|
||||
volumes = res.body.Volumes
|
||||
end
|
||||
|
||||
res = dk.containers:list({
|
||||
query = {
|
||||
all=true
|
||||
}
|
||||
})
|
||||
if res and res.code and res.code <300 then
|
||||
containers = res.body
|
||||
end
|
||||
end
|
||||
|
||||
local volume_list = not lost_state and get_volumes() or {}
|
||||
|
||||
m = SimpleForm("docker", translate("Docker - Volumes"))
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
m:append(Template("dockerman/volume_size"))
|
||||
|
||||
s = m:section(Table, volume_list, translate("Volumes overview"))
|
||||
|
||||
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"))
|
||||
o = s:option(DummyValue, "_containers", translate("Containers"))
|
||||
o.rawhtml = true
|
||||
o = s:option(DummyValue, "_mountpoint", translate("Mount Point"))
|
||||
o = s:option(DummyValue, "_size", translate("Size"))
|
||||
o.rawhtml = true
|
||||
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
|
||||
|
||||
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.disable = lost_state
|
||||
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 and msg.code and 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
|
507
luci-app-dockerman/luasrc/model/docker.lua
Normal file
507
luci-app-dockerman/luasrc/model/docker.lua
Normal file
|
@ -0,0 +1,507 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
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 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
|
||||
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
|
||||
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
|
||||
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)
|
||||
if t[k] and next(t[k]) == nil then
|
||||
t[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
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 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
|
||||
|
||||
local t = os.date("%Y%m%d%H%M%S")
|
||||
_docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old_".. t .. "...")
|
||||
res = self.containers:rename({name = container_name, query = { name = container_name .. "_old_" ..t }})
|
||||
if res and res.code 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 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 and res.code and res.code > 300 then
|
||||
return res
|
||||
end
|
||||
_docker:append_status("done\n")
|
||||
end
|
||||
|
||||
_docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "_old_".. t .. "...")
|
||||
res = self.containers:stop({name = container_name .. "_old_" ..t })
|
||||
if res and res.code and res.code < 305 then
|
||||
_docker:append_status("done\n")
|
||||
else
|
||||
return res
|
||||
end
|
||||
|
||||
_docker:append_status("Container: " .. "Start" .. " " .. container_name .. "...")
|
||||
res = self.containers:start({name = container_name})
|
||||
if res and res.code and res.code < 305 then
|
||||
_docker:append_status("done\n")
|
||||
else
|
||||
return res
|
||||
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)
|
||||
end
|
||||
|
||||
_docker.new = function()
|
||||
local host = nil
|
||||
local port = nil
|
||||
local socket_path = nil
|
||||
local debug_path = nil
|
||||
|
||||
if uci:get_bool("dockerd", "dockerman", "remote_endpoint") then
|
||||
host = uci:get("dockerd", "dockerman", "remote_host") or nil
|
||||
port = uci:get("dockerd", "dockerman", "remote_port") or nil
|
||||
else
|
||||
socket_path = uci:get("dockerd", "dockerman", "socket_path") or "/var/run/docker.sock"
|
||||
end
|
||||
|
||||
local debug = uci:get_bool("dockerd", "dockerman", "debug")
|
||||
if debug then
|
||||
debug_path = uci:get("dockerd", "dockerman", "debug_path") or "/tmp/.docker_debug"
|
||||
end
|
||||
|
||||
local status_path = uci:get("dockerd", "dockerman", "status_path") or "/tmp/.docker_action_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.status_path = uci:get("dockerd", "dockerman", "status_path") or "/tmp/.docker_action_status"
|
||||
|
||||
_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()
|
||||
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()
|
||||
end
|
||||
|
||||
_docker.read_status=function(self)
|
||||
return fs.readfile(self.options.status_path)
|
||||
end
|
||||
|
||||
_docker.clear_status=function(self)
|
||||
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
|
||||
end
|
||||
|
||||
--{"status":"Pulling from library\/debian","id":"latest"}
|
||||
--{"status":"Pulling fs layer","progressDetail":[],"id":"50e431f79093"}
|
||||
--{"status":"Downloading","progressDetail":{"total":50381971,"current":2029978},"id":"50e431f79093","progress":"[==> ] 2.03MB\/50.38MB"}
|
||||
--{"status":"Download complete","progressDetail":[],"id":"50e431f79093"}
|
||||
--{"status":"Extracting","progressDetail":{"total":50381971,"current":17301504},"id":"50e431f79093","progress":"[=================> ] 17.3MB\/50.38MB"}
|
||||
--{"status":"Pull complete","progressDetail":[],"id":"50e431f79093"}
|
||||
--{"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)
|
||||
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)
|
||||
end
|
||||
|
||||
_docker.create_macvlan_interface = function(name, device, gateway, subnet)
|
||||
if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then
|
||||
return
|
||||
end
|
||||
|
||||
if uci:get_bool("dockerd", "dockerman", "remote_endpoint") 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 fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then
|
||||
return
|
||||
end
|
||||
|
||||
if uci:get_bool("dockerd", "dockerman", "remote_endpoint") 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
|
||||
|
||||
_docker.byte_format = function (byte)
|
||||
if not byte then return 'NaN' 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
|
||||
|
||||
return _docker
|
Loading…
Add table
Add a link
Reference in a new issue