luci-app-dockerman: sync upsteam source
This commit is contained in:
parent
1d40d1ef59
commit
5e3bae59a5
@ -1,7 +1,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-dockerman
|
||||
PKG_VERSION:=v0.1.0
|
||||
PKG_VERSION:=v0.1.4
|
||||
PKG_RELEASE:=beta
|
||||
PKG_MAINTAINER:=lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
@ -14,31 +14,24 @@ local uci = require "luci.model.uci"
|
||||
module("luci.controller.dockerman",package.seeall)
|
||||
|
||||
function index()
|
||||
|
||||
|
||||
entry({"admin", "docker"}, firstchild(), "Docker", 40).dependent = false
|
||||
entry({"admin","docker","overview"},cbi("docker/overview"),_("Overview"),0).leaf=true
|
||||
|
||||
socket = luci.model.uci.cursor().get("docker","local", "socket_path")
|
||||
if not nixio.fs.access(socket) then
|
||||
return
|
||||
end
|
||||
if (require "luci.model.docker").new():version().code ~= 200 then return end
|
||||
|
||||
local socket = luci.model.uci.cursor():get("docker", "local", "socket_path")
|
||||
if not nixio.fs.access(socket) then return end
|
||||
if (require "luci.model.docker").new():_ping().code ~= 200 then return end
|
||||
entry({"admin","docker","containers"},form("docker/containers"),_("Containers"),1).leaf=true
|
||||
entry({"admin","docker","networks"},form("docker/networks"),_("Networks"),3).leaf=true
|
||||
entry({"admin","docker","images"},form("docker/images"),_("Images"),2).leaf=true
|
||||
entry({"admin","docker","events"},call("action_events"),_("Events"),4)
|
||||
entry({"admin","docker","networks"},form("docker/networks"),_("Networks"),3).leaf=true
|
||||
entry({"admin","docker","volumes"},form("docker/volumes"),_("Volumes"),4).leaf=true
|
||||
entry({"admin","docker","events"},call("action_events"),_("Events"),5)
|
||||
entry({"admin","docker","newcontainer"},form("docker/newcontainer")).leaf=true
|
||||
entry({"admin","docker","newnetwork"},form("docker/newnetwork")).leaf=true
|
||||
entry({"admin","docker","container"},form("docker/container")).leaf=true
|
||||
entry({"admin","docker","container_stats"},call("action_get_container_stats")).leaf=true
|
||||
entry({"admin","docker","confirm"},call("action_confirm")).leaf=true
|
||||
|
||||
-- for openwrt docker-ce by lean
|
||||
if nixio.fs.access("/etc/config/dockerd") then
|
||||
entry({"admin","services","docker"},alias("admin", "docker", "overview"))
|
||||
entry({"admin","services","docker","status"},call("act_status")).leaf=true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -148,7 +141,7 @@ function action_get_container_stats(container_id)
|
||||
end
|
||||
|
||||
function action_confirm()
|
||||
local status_path=luci.model.uci.cursor().get("docker","local", "status_path")
|
||||
local status_path=luci.model.uci.cursor():get("docker", "local", "status_path")
|
||||
local data = nixio.fs.readfile(status_path)
|
||||
if data then
|
||||
code = 202
|
||||
@ -163,10 +156,3 @@ function action_confirm()
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({info = data})
|
||||
end
|
||||
|
||||
function act_status()
|
||||
local e={}
|
||||
e.running=luci.sys.call("pgrep /usr/bin/dockerd >/dev/null")==0
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
@ -24,8 +24,8 @@ if res.code < 300 then networks = res.body else return end
|
||||
|
||||
local get_ports = function(d)
|
||||
local data
|
||||
if d.NetworkSettings and d.NetworkSettings.Ports then
|
||||
for inter, out in pairs(d.NetworkSettings.Ports) do
|
||||
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
|
||||
@ -62,6 +62,15 @@ local get_mounts = function(d)
|
||||
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
|
||||
@ -84,19 +93,25 @@ local get_networks = function(d)
|
||||
end
|
||||
|
||||
|
||||
local start_stop_remove = function(m,cmd)
|
||||
local start_stop_remove = function(m, cmd)
|
||||
docker:clear_status()
|
||||
docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...")
|
||||
local res = dk.containers[cmd](dk, container_id)
|
||||
local res
|
||||
if cmd ~= "upgrade" then
|
||||
res = dk.containers[cmd](dk, container_id)
|
||||
else
|
||||
res = dk.containers_upgrade(dk, container_id)
|
||||
end
|
||||
if res and res.code >= 300 then
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
else
|
||||
docker:clear_status()
|
||||
end
|
||||
if cmd ~= "remove" then
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
|
||||
else
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
|
||||
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
|
||||
|
||||
@ -131,6 +146,16 @@ btnstop.template="cbi/inlinebutton"
|
||||
btnstop.inputtitle=translate("Stop")
|
||||
btnstop.inputstyle = "reset"
|
||||
btnstop.forcewrite = true
|
||||
btnupgrade=action_section:option(Button, "_upgrade")
|
||||
btnupgrade.template="cbi/inlinebutton"
|
||||
btnupgrade.inputtitle=translate("Upgrade")
|
||||
btnupgrade.inputstyle = "reload"
|
||||
btnstop.forcewrite = true
|
||||
btnduplicate=action_section:option(Button, "_duplicate")
|
||||
btnduplicate.template="cbi/inlinebutton"
|
||||
btnduplicate.inputtitle=translate("Duplicate")
|
||||
btnduplicate.inputstyle = "add"
|
||||
btnstop.forcewrite = true
|
||||
btnremove=action_section:option(Button, "_remove")
|
||||
btnremove.template="cbi/inlinebutton"
|
||||
btnremove.inputtitle=translate("Remove")
|
||||
@ -143,12 +168,18 @@ end
|
||||
btnrestart.write = function(self, section)
|
||||
start_stop_remove(m,"restart")
|
||||
end
|
||||
btnupgrade.write = function(self, section)
|
||||
start_stop_remove(m,"upgrade")
|
||||
end
|
||||
btnremove.write = function(self, section)
|
||||
start_stop_remove(m,"remove")
|
||||
end
|
||||
btnstop.write = function(self, section)
|
||||
start_stop_remove(m,"stop")
|
||||
end
|
||||
btnduplicate.write = function(self, section)
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id))
|
||||
end
|
||||
|
||||
tab_section = m:section(SimpleSection)
|
||||
tab_section.template="docker/container"
|
||||
@ -166,7 +197,9 @@ if action == "info" then
|
||||
table_info["06start"] = container_info.State.Status == "running" and {_key = translate("Start Time"), _value = container_info.State and container_info.State.StartedAt or "-"} or {_key = translate("Finish Time"), _value = container_info.State and container_info.State.FinishedAt or "-"}
|
||||
table_info["07healthy"] = {_key = translate("Healthy"), _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"}
|
||||
table_info["08restart"] = {_key = translate("Restart Policy"), _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", _button=translate("Update")}
|
||||
table_info["09device"] = {_key = translate("Device"), _value = get_device(container_info) 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 "-"}
|
||||
|
||||
@ -27,14 +27,17 @@ function get_images()
|
||||
data[index]["_selected"] = 0
|
||||
data[index]["_id"] = v.Id:sub(8,20)
|
||||
if v.RepoTags then
|
||||
data[index]["_tags"] = v.RepoTags[1]
|
||||
for i, v1 in ipairs(v.RepoTags) do
|
||||
data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "<br\>" )or "") .. v1
|
||||
end
|
||||
else
|
||||
_,_, data[index]["_tags"] = v.RepoDigests[1]:find("^(.-)@.+")
|
||||
data[index]["_tags"]=data[index]["_tags"]..":none"
|
||||
end
|
||||
for ci,cv in ipairs(containers) do
|
||||
if v.Id == cv.ImageID then
|
||||
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..cv.Names[1]:sub(2)
|
||||
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
|
||||
"<a href=/cgi-bin/luci/admin/docker/container/"..cv.Id.." >".. cv.Names[1]:sub(2).."</a>"
|
||||
end
|
||||
end
|
||||
data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
|
||||
@ -105,29 +108,15 @@ image_selecter.enabled = 1
|
||||
image_selecter.default = 0
|
||||
|
||||
image_id = image_table:option(DummyValue, "_id", translate("ID"))
|
||||
image_table:option(DummyValue, "_tags", translate("RepoTags"))
|
||||
image_table:option(DummyValue, "_containers", translate("Containers"))
|
||||
image_table:option(DummyValue, "_tags", translate("RepoTags")).rawhtml = true
|
||||
image_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
|
||||
image_table:option(DummyValue, "_size", translate("Size"))
|
||||
image_table:option(DummyValue, "_created", translate("Created"))
|
||||
image_selecter.write = function(self, section, value)
|
||||
image_list[section]._selected = value
|
||||
end
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template="docker/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
if docker_status then docker:clear_status() end
|
||||
|
||||
action = m:section(Table,{{}})
|
||||
action.notitle=true
|
||||
action.rowcolors=false
|
||||
action.template="cbi/nullsection"
|
||||
btnremove = action:option(Button, "remove")
|
||||
btnremove.inputtitle= translate("Remove")
|
||||
btnremove.template="cbi/inlinebutton"
|
||||
btnremove.inputstyle = "remove"
|
||||
btnremove.forcewrite = true
|
||||
btnremove.write = function(self, section)
|
||||
local remove_action = function(force)
|
||||
local image_selected = {}
|
||||
-- 遍历table中sectionid
|
||||
local image_table_sids = image_table:cfgsections()
|
||||
@ -142,7 +131,11 @@ btnremove.write = function(self, section)
|
||||
docker:clear_status()
|
||||
for _,img in ipairs(image_selected) do
|
||||
docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
|
||||
local msg = dk.images["remove"](dk, img)
|
||||
local query_body ={}
|
||||
if force then
|
||||
query_body.force = true
|
||||
end
|
||||
local msg = dk.images["remove"](dk, img, query_body)
|
||||
if msg.code ~= 200 then
|
||||
docker:append_status("fail code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "<br>")
|
||||
success = false
|
||||
@ -154,4 +147,32 @@ btnremove.write = function(self, section)
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
|
||||
end
|
||||
end
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template="docker/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
if docker_status then docker:clear_status() end
|
||||
|
||||
action = m:section(Table,{{}})
|
||||
action.notitle=true
|
||||
action.rowcolors=false
|
||||
action.template="cbi/nullsection"
|
||||
|
||||
btnremove = action:option(Button, "remove")
|
||||
btnremove.inputtitle= translate("Remove")
|
||||
btnremove.template="cbi/inlinebutton"
|
||||
btnremove.inputstyle = "remove"
|
||||
btnremove.forcewrite = true
|
||||
btnremove.write = function(self, section)
|
||||
remove_action()
|
||||
end
|
||||
|
||||
btnforceremove = action:option(Button, "forceremove")
|
||||
btnforceremove.inputtitle= translate("Force Remove")
|
||||
btnforceremove.template="cbi/inlinebutton"
|
||||
btnforceremove.inputstyle = "remove"
|
||||
btnforceremove.forcewrite = true
|
||||
btnforceremove.write = function(self, section)
|
||||
remove_action(true)
|
||||
end
|
||||
return m
|
||||
@ -12,13 +12,122 @@ require "luci.util"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
local container_name = arg[1] or ""
|
||||
local image_name = arg[2] or ""
|
||||
local cmd_line = table.concat(arg, '/')
|
||||
local create_body = {}
|
||||
|
||||
local images = dk.images:list().body
|
||||
local networks = dk.networks:list().body
|
||||
local containers = dk.containers:list(nil, {all=true}).body
|
||||
|
||||
-- reslvo default config
|
||||
local default_config = { }
|
||||
if cmd_line and cmd_line:match("^docker.+") then
|
||||
local key = nil
|
||||
--cursor = 0: docker run
|
||||
--cursor = 1: resloving para
|
||||
--cursor = 2: resloving image
|
||||
--cursor > 2: resloving command
|
||||
local cursor = 0
|
||||
for w in cmd_line:gmatch("[^%s]+") do
|
||||
-- skip '\'
|
||||
if w == '\\' then
|
||||
-- start with '-'
|
||||
elseif w:match("^%-+(.+)") and cursor <= 1 then
|
||||
local v = w:match("^%-+.-=(.+)")
|
||||
if v then
|
||||
key = w:match("^%-+(.-)=.+"):lower()
|
||||
else
|
||||
key = w:match("^%-+(.+)"):lower()
|
||||
end
|
||||
if key == "v" or key == "volume" then
|
||||
key = "mount"
|
||||
elseif key == "p" then
|
||||
key = "port"
|
||||
elseif key == "e" then
|
||||
key = "env"
|
||||
elseif key == "net" then
|
||||
key = "network"
|
||||
elseif key == "cpu-shares" then
|
||||
key = "cpushares"
|
||||
elseif key == "m" then
|
||||
key = "memory"
|
||||
elseif key == "blkio-weight" then
|
||||
key = "blkioweight"
|
||||
elseif key == "privileged" then
|
||||
default_config["privileged"] = 1
|
||||
end
|
||||
if v then
|
||||
if key == "mount" or key == "link" or key == "env" or key == "port" or key == "device" or key == "tmpfs" then
|
||||
if not default_config[key] then default_config[key] = {} end
|
||||
table.insert( default_config[key], v )
|
||||
else
|
||||
default_config[key] = v
|
||||
end
|
||||
end
|
||||
-- value
|
||||
elseif key and type(key) == "string" then
|
||||
if key == "mount" or key == "link" or key == "env" or key == "port" or key == "device" or key == "tmpfs" then
|
||||
if not default_config[key] then default_config[key] = {} end
|
||||
table.insert( default_config[key], w )
|
||||
else
|
||||
default_config[key] = w
|
||||
end
|
||||
|
||||
if key == "cpus" or key == "cpushare" or key == "memory" or key == "blkioweight" or key == "device" or key == "tmpfs" then
|
||||
default_config["advance"] = 1
|
||||
end
|
||||
key = nil
|
||||
cursor = 1
|
||||
--image and command
|
||||
elseif cursor >= 1 and key == nil then
|
||||
if cursor == 1 then
|
||||
default_config["image"] = w
|
||||
elseif cursor > 1 then
|
||||
default_config["command"] = (default_config["command"] and (default_config["command"] .. " " )or "") .. w
|
||||
end
|
||||
cursor = cursor + 1
|
||||
end
|
||||
end
|
||||
elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
|
||||
local container_id = cmd_line:match("^duplicate/(.+)")
|
||||
create_body = dk:containers_duplicate_config(container_id)
|
||||
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.privileged = create_body.HostConfig.Privileged and 1 or 0
|
||||
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
|
||||
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.mount = create_body.HostConfig.Binds
|
||||
|
||||
if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then
|
||||
default_config.port = {}
|
||||
for k, v in pairs(create_body.HostConfig.PortBindings) do
|
||||
table.insert( default_config.port, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
|
||||
end
|
||||
end
|
||||
|
||||
default_config.user = create_body.User or nil
|
||||
default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil
|
||||
default_config.advance = 1
|
||||
default_config.cpus = create_body.HostConfig.NanoCPUs
|
||||
default_config.cpushares = create_body.HostConfig.CpuShares
|
||||
default_config.memory = create_body.HostConfig.Memory
|
||||
default_config.blkioweight = 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
|
||||
|
||||
default_config.tmpfs = create_body.HostConfig.Tmpfs
|
||||
end
|
||||
end
|
||||
|
||||
local m = SimpleForm("docker", translate("Docker"))
|
||||
m.tempalte = "cbi/xsimpleform"
|
||||
@ -36,20 +145,34 @@ local s = m:section(SimpleSection, translate("New Container"))
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
|
||||
local d = s:option(Value, "name", translate("Container Name"))
|
||||
local d = s:option(DummyValue,"cmd_line","Resolv CLI")
|
||||
d.rawhtml = true
|
||||
d.template = "docker/resolv_container"
|
||||
|
||||
d = s:option(Value, "name", translate("Container Name"))
|
||||
d.rmempty = true
|
||||
d.default = container_name
|
||||
|
||||
d = s:option(Flag, "_force_pull", translate("Always pull image first"))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = 0
|
||||
|
||||
d.default = default_config.name or nil
|
||||
d = s:option(Value, "image", translate("Docker Image"))
|
||||
d.rmempty = true
|
||||
d.default = image_name
|
||||
|
||||
d.default = default_config.image or nil
|
||||
for _, v in ipairs (images) do
|
||||
if v.RepoTags then
|
||||
d:value(v.RepoTags[1], v.RepoTags[1])
|
||||
end
|
||||
end
|
||||
|
||||
d = s:option(Flag, "privileged", translate("Privileged"))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = default_config.privileged or 0
|
||||
|
||||
d = s:option(ListValue, "restart", translate("Restart policy"))
|
||||
d.rmempty = true
|
||||
@ -58,88 +181,123 @@ d:value("no", "No")
|
||||
d:value("unless-stopped", "Unless stopped")
|
||||
d:value("always", "Always")
|
||||
d:value("on-failure", "On failure")
|
||||
d.default = "unless-stopped"
|
||||
d.default = default_config.restart or "unless-stopped"
|
||||
|
||||
d = s:option(ListValue, "network", translate("Networks"))
|
||||
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
|
||||
|
||||
d = s:option(Value, "user", translate("User"))
|
||||
d.placeholder = "1000:1000"
|
||||
d.rmempty = true
|
||||
d.default = "bridge"
|
||||
d.default = default_config.user or nil
|
||||
|
||||
local dip = s:option(Value, "ip", translate("IPv4 Address"))
|
||||
dip.datatype="ip4addr"
|
||||
dip:depends("network", "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:value(v.Name, network_name)
|
||||
|
||||
if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then
|
||||
dip:depends("network", v.Name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
d = s:option(DynamicList, "links", translate("Links with other containers"))
|
||||
d = s:option(DynamicList, "link", translate("Links with other containers"))
|
||||
d.template = "cbi/xdynlist"
|
||||
d.placeholder = "container_name:alias"
|
||||
d.rmempty = true
|
||||
d:depends("network", "bridge")
|
||||
d.default = default_config.link or nil
|
||||
|
||||
d = s:option(DynamicList, "env", translate("Environmental Variable"))
|
||||
d.template = "cbi/xdynlist"
|
||||
d.placeholder = "TZ=Asia/Shanghai"
|
||||
d.rmempty = true
|
||||
d.default = default_config.env or nil
|
||||
|
||||
d = s:option(DynamicList, "mount", translate("Bind Mount"))
|
||||
d.template = "cbi/xdynlist"
|
||||
d.placeholder = "/media:/media:slave"
|
||||
d.rmempty = true
|
||||
d.default = default_config.mount or nil
|
||||
|
||||
d = s:option(DynamicList, "port", translate("Exposed Ports"))
|
||||
d.placeholder = "2200:22/tcp"
|
||||
d.rmempty = true
|
||||
local d_ports = s:option(DynamicList, "port", translate("Exposed Ports"))
|
||||
d_ports.template = "cbi/xdynlist"
|
||||
d_ports.placeholder = "2200:22/tcp"
|
||||
d_ports.rmempty = true
|
||||
d_ports.default = default_config.port or nil
|
||||
|
||||
d = s:option(Value, "command", translate("Run command"))
|
||||
d.placeholder = "/bin/sh init.sh"
|
||||
d.rmempty = true
|
||||
d.default = default_config.command or nil
|
||||
|
||||
d = s:option(Flag, "advance", translate("Advance"))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = default_config.advance or 0
|
||||
|
||||
d = s:option(Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
|
||||
d.placeholder = "1.5"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.datatype="ufloat"
|
||||
d.default = default_config.cpus or nil
|
||||
|
||||
d = s:option(Value, "cpushares", translate("CPU Shares Weight"), translate("CPU shares (relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
|
||||
d.placeholder = "1024"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.datatype="uinteger"
|
||||
d.default = default_config.cpushares or nil
|
||||
|
||||
d = s:option(Value, "memory", translate("Memory"), translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."))
|
||||
d.placeholder = "128m"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.default = default_config.memory or nil
|
||||
|
||||
d = s:option(Value, "blkioweight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
|
||||
d.placeholder = "500"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.datatype="uinteger"
|
||||
d.default = default_config.blkioweight or nil
|
||||
|
||||
d = s:option(DynamicList, "device", translate("Device"))
|
||||
d.template = "cbi/xdynlist"
|
||||
d.placeholder = "/dev/sda:/dev/xvdc:rwm"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.default = default_config.device or nil
|
||||
|
||||
d = s:option(DynamicList, "tmpfs", translate("Tmpfs"), translate("Mount tmpfs filesystems"))
|
||||
d.template = "cbi/xdynlist"
|
||||
d.placeholder = "/run:rw,noexec,nosuid,size=65536k"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.default = default_config.tmpfs 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_ports:depends("network", v.Name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
m.handle = function(self, state, data)
|
||||
if state == FORM_VALID then
|
||||
local tmp
|
||||
local name = data.name
|
||||
local image = data.image
|
||||
local user = data.user
|
||||
if not image:match(".-:.+") then
|
||||
image = image .. ":latest"
|
||||
end
|
||||
@ -167,7 +325,30 @@ m.handle = function(self, state, data)
|
||||
end
|
||||
end
|
||||
|
||||
tmp = data.port
|
||||
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 nil
|
||||
else
|
||||
local _,_, h, c = v:find("(.-):(.+)")
|
||||
if h and c then
|
||||
t['PathOnHost'] = h
|
||||
t['PathInContainer'] = c
|
||||
end
|
||||
end
|
||||
if next(t) ~= nil then
|
||||
table.insert( device, t )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
tmp = data.port or {}
|
||||
for i, v in ipairs(tmp) do
|
||||
for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do
|
||||
local _,_,p= v2:find("^%d+/(%w+)")
|
||||
@ -179,11 +360,11 @@ m.handle = function(self, state, data)
|
||||
end
|
||||
end
|
||||
|
||||
local links = data.links
|
||||
local link = data.link
|
||||
tmp = data.command
|
||||
local command = {}
|
||||
if tmp ~= nil then
|
||||
for v in string.gmatch(tmp, "[^%s]+") do
|
||||
for v in string.gmatch(tmp, "[^%s]+") do
|
||||
command[#command+1] = v
|
||||
end
|
||||
end
|
||||
@ -203,45 +384,62 @@ m.handle = function(self, state, data)
|
||||
end
|
||||
end
|
||||
|
||||
local create_body={
|
||||
Hostname = name,
|
||||
Domainname = "",
|
||||
Cmd = (#command ~= 0) and command or nil,
|
||||
Env = env,
|
||||
Image = image,
|
||||
Volumes = nil,
|
||||
ExposedPorts = (next(exposedports) ~= nil) and exposedports or nil,
|
||||
HostConfig = {
|
||||
Binds = (#mount ~= 0) and mount or nil,
|
||||
NetworkMode = network,
|
||||
RestartPolicy ={
|
||||
Name = restart,
|
||||
MaximumRetryCount = 0
|
||||
},
|
||||
Privileged = privileged and true or false,
|
||||
PortBindings = (next(portbindings) ~= nil) and portbindings or nil,
|
||||
Memory = memory,
|
||||
CpuShares = tonumber(cpushares),
|
||||
NanoCPUs = tonumber(cpus) * 10 ^ 9,
|
||||
BlkioWeight = tonumber(blkioweight)
|
||||
},
|
||||
NetworkingConfig = ip and {
|
||||
EndpointsConfig = {
|
||||
[network] = {
|
||||
IPAMConfig = {
|
||||
IPv4Address = ip
|
||||
}
|
||||
}
|
||||
}
|
||||
} or nil
|
||||
}
|
||||
create_body.Hostname = name
|
||||
create_body.User = user
|
||||
create_body.Cmd = (#command ~= 0) and command or nil
|
||||
create_body.Env = env
|
||||
create_body.Image = image
|
||||
create_body.ExposedPorts = (next(exposedports) ~= nil) and exposedports or nil
|
||||
create_body.HostConfig = create_body.HostConfig or {}
|
||||
create_body.HostConfig.Binds = (#mount ~= 0) and mount or nil
|
||||
create_body.HostConfig.NetworkMode = network
|
||||
create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 }
|
||||
create_body.HostConfig.Privileged = privileged and true or false
|
||||
create_body.HostConfig.PortBindings = (next(portbindings) ~= nil) and portbindings or nil
|
||||
create_body.HostConfig.Memory = tonumber(memory)
|
||||
create_body.HostConfig.CpuShares = tonumber(cpushares)
|
||||
create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9
|
||||
create_body.HostConfig.BlkioWeight = tonumber(blkioweight)
|
||||
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
|
||||
|
||||
if next(tmpfs) ~= nil then
|
||||
create_body["HostConfig"]["Tmpfs"] = tmpfs
|
||||
end
|
||||
if network == "bridge" and next(links) ~= nil then
|
||||
create_body["HostConfig"]["Links"] = links
|
||||
if next(device) ~= nil then
|
||||
create_body["HostConfig"]["Devices"] = device
|
||||
end
|
||||
|
||||
if network == "bridge" and next(link) ~= nil then
|
||||
create_body["HostConfig"]["Links"] = link
|
||||
end
|
||||
local pull_image = function(image)
|
||||
local server = "index.docker.io"
|
||||
local json_stringify = luci.json and luci.json.encode or luci.jsonc.stringify
|
||||
docker:append_status("Images: " .. "pulling" .. " " .. image .. "...")
|
||||
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
|
||||
local res = dk.images:create(nil, {fromImage=image,_header={["X-Registry-Auth"]=x_auth}})
|
||||
if res and res.code < 300 then
|
||||
docker:append_status("done<br>")
|
||||
else
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
|
||||
end
|
||||
end
|
||||
docker:clear_status()
|
||||
local exist_image = false
|
||||
if image then
|
||||
@ -252,17 +450,9 @@ m.handle = function(self, state, data)
|
||||
end
|
||||
end
|
||||
if not exist_image then
|
||||
local server = "index.docker.io"
|
||||
local json_stringify = luci.json and luci.json.encode or luci.jsonc.stringify
|
||||
docker:append_status("Images: " .. "pulling" .. " " .. image .. "...")
|
||||
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
|
||||
local res = dk.images:create(nil, {fromImage=image,_header={["X-Registry-Auth"]=x_auth}})
|
||||
if res and res.code < 300 then
|
||||
docker:append_status("done<br>")
|
||||
else
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
|
||||
end
|
||||
pull_image(image)
|
||||
elseif data._force_pull == 1 then
|
||||
pull_image(image)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -65,6 +65,7 @@ d.default = 0
|
||||
d:depends("dirver", "overlay")
|
||||
|
||||
d = s:option(DynamicList, "options", translate("Options"))
|
||||
d.template = "cbi/xdynlist"
|
||||
d.rmempty = true
|
||||
d.placeholder="com.docker.network.driver.mtu=1500"
|
||||
|
||||
@ -90,6 +91,7 @@ d.placeholder="10.1.1.0/24"
|
||||
d.datatype="ip4addr"
|
||||
|
||||
d = s:option(DynamicList, "aux_address", translate("Exclude IPs"))
|
||||
d.template = "cbi/xdynlist"
|
||||
d.rmempty = true
|
||||
d.placeholder="my-route=10.1.1.1"
|
||||
|
||||
|
||||
@ -7,33 +7,83 @@ You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
$Id$
|
||||
]]--
|
||||
if nixio.fs.access("/etc/config/dockerd") then
|
||||
local running = (luci.sys.call("pidof portainer >/dev/null") == 0)
|
||||
local button = ""
|
||||
|
||||
if running then
|
||||
button = " <br /><br /><input type=\"button\" value=\" " .. translate("Open Portainer Docker Admin") .. " \" onclick=\"window.open('http://'+window.location.hostname+':" .. 9999 .. "')\"/><br />"
|
||||
require "luci.util"
|
||||
local docker = require "luci.model.docker"
|
||||
local uci = require "luci.model.uci"
|
||||
|
||||
function byte_format(byte)
|
||||
local suff = {"B", "KB", "MB", "GB", "TB"}
|
||||
for i=1, 5 do
|
||||
if byte > 1024 and i < 5 then
|
||||
byte = byte / 1024
|
||||
else
|
||||
return string.format("%.2f %s", byte, suff[i])
|
||||
end
|
||||
end
|
||||
|
||||
m = Map("dockerd", "Docker CE", translate("Docker is a set of platform-as-a-service (PaaS) products that use OS-level virtualization to deliver software in packages called containers.") .. button)
|
||||
|
||||
|
||||
m:section(SimpleSection).template = "docker/docker_status"
|
||||
|
||||
s = m:section(TypedSection, "docker")
|
||||
s.anonymous = true
|
||||
|
||||
wan_mode = s:option(Flag, "wan_mode", translate("Enable WAN access Dokcer"), translate("Enable WAN access docker mapped ports"))
|
||||
wan_mode.default = 0
|
||||
wan_mode.rmempty = false
|
||||
|
||||
o=s:option(DummyValue,"readme",translate(" "))
|
||||
o.description=translate("<a href=\"../../../../DockerReadme.pdf\" target=\"_blank\" />"..translate("Download DockerReadme.pdf").."</a>")
|
||||
end
|
||||
|
||||
local m = Map("docker", translate("Docker"))
|
||||
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='-'}
|
||||
|
||||
m2 = Map("docker", translate("Docker"))
|
||||
s = m2:section(NamedSection, "local", "section", translate("Setting"))
|
||||
s = m:section(Table, docker_info_table)
|
||||
s:option(DummyValue, "_key", translate("Info"))
|
||||
s:option(DummyValue, "_value")
|
||||
|
||||
s = m:section(SimpleSection)
|
||||
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("docker", "local", "socket_path")
|
||||
if nixio.fs.access(socket) and (require "luci.model.docker").new():_ping().code == 200 then
|
||||
local dk = docker.new()
|
||||
-- local containers_list = dk.containers:list(nil, {all=true}).body
|
||||
-- local images_list = dk.images:list().body
|
||||
local volumes_list = dk.volumes:list().body.Volumes
|
||||
local networks_list = dk.networks:list().body
|
||||
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 = tostring(byte_format(docker_info.body.MemTotal))
|
||||
docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir
|
||||
docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress
|
||||
|
||||
-- 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(docker_info.body.Images)
|
||||
s.networks_total = tostring(#networks_list)
|
||||
s.volumes_total = tostring(#volumes_list)
|
||||
end
|
||||
s.template = "docker/overview"
|
||||
|
||||
|
||||
s = m:section(NamedSection, "local", "section", translate("Setting"))
|
||||
|
||||
socket_path = s:option(Value, "socket_path", translate("Socket Path"))
|
||||
status_path = s:option(Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file"))
|
||||
@ -41,8 +91,5 @@ debug = s:option(Flag, "debug", translate("Enable Debug"), translate("For debug,
|
||||
debug.enabled="true"
|
||||
debug.disabled="false"
|
||||
debug_path = s:option(Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile"))
|
||||
if m then
|
||||
return m, m2
|
||||
else
|
||||
return m2
|
||||
end
|
||||
|
||||
return m
|
||||
|
||||
@ -0,0 +1,122 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <lisaac.cn@gmail.com>
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
|
||||
local containers, volumes
|
||||
local res = dk.volumes:list()
|
||||
if res.code <300 then volumes = res.body.Volumes else return end
|
||||
res = dk.containers:list(nil, {all=true})
|
||||
if res.code <300 then containers = res.body else return end
|
||||
|
||||
function get_volumes()
|
||||
local data = {}
|
||||
for i, v in ipairs(volumes) do
|
||||
-- local index = v.CreatedAt .. v.Name
|
||||
local index = v.Name
|
||||
data[index]={}
|
||||
data[index]["_selected"] = 0
|
||||
data[index]["_nameraw"] = v.Name
|
||||
data[index]["_name"] = v.Name:sub(1,12)
|
||||
for ci,cv in ipairs(containers) do
|
||||
if cv.Mounts and type(cv.Mounts) ~= "table" then break end
|
||||
for vi, vv in ipairs(cv.Mounts) do
|
||||
if v.Name == vv.Name then
|
||||
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
|
||||
"<a href=/cgi-bin/luci/admin/docker/container/"..cv.Id.." >".. cv.Names[1]:sub(2).."</a>"
|
||||
end
|
||||
end
|
||||
end
|
||||
data[index]["_driver"] = v.Driver
|
||||
data[index]["_mountpoint"] = nil
|
||||
for v1 in v.Mountpoint:gmatch('[^/]+') do
|
||||
if v1 == index then
|
||||
data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..."
|
||||
else
|
||||
data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1
|
||||
end
|
||||
end
|
||||
data[index]["_created"] = v.CreatedAt
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
local volume_list = get_volumes()
|
||||
|
||||
-- m = Map("docker", translate("Docker"))
|
||||
m = SimpleForm("docker", translate("Docker"))
|
||||
m.tempalte = "cbi/xsimpleform"
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
|
||||
|
||||
volume_table = m:section(Table, volume_list, translate("Volumes"))
|
||||
|
||||
volume_selecter = volume_table:option(Flag, "_selected","")
|
||||
volume_selecter.disabled = 0
|
||||
volume_selecter.enabled = 1
|
||||
volume_selecter.default = 0
|
||||
|
||||
volume_id = volume_table:option(DummyValue, "_name", translate("Name"))
|
||||
volume_table:option(DummyValue, "_driver", translate("Driver"))
|
||||
volume_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
|
||||
volume_table:option(DummyValue, "_mountpoint", translate("Mount Point"))
|
||||
volume_table:option(DummyValue, "_created", translate("Created"))
|
||||
volume_selecter.write = function(self, section, value)
|
||||
volume_list[section]._selected = value
|
||||
end
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template="docker/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
if docker_status then docker:clear_status() end
|
||||
|
||||
action = m:section(Table,{{}})
|
||||
action.notitle=true
|
||||
action.rowcolors=false
|
||||
action.template="cbi/nullsection"
|
||||
btnremove = action:option(Button, "remove")
|
||||
btnremove.inputtitle= translate("Remove")
|
||||
btnremove.template="cbi/inlinebutton"
|
||||
btnremove.inputstyle = "remove"
|
||||
btnremove.forcewrite = true
|
||||
btnremove.write = function(self, section)
|
||||
local volume_selected = {}
|
||||
-- 遍历table中sectionid
|
||||
local volume_table_sids = volume_table:cfgsections()
|
||||
for _, volume_table_sid in ipairs(volume_table_sids) do
|
||||
-- 得到选中项的名字
|
||||
if volume_list[volume_table_sid]._selected == 1 then
|
||||
-- volume_selected[#volume_selected+1] = volume_id:cfgvalue(volume_table_sid)
|
||||
luci.util.perror(volume_table_sid)
|
||||
volume_selected[#volume_selected+1] = volume_table_sid
|
||||
end
|
||||
end
|
||||
if next(volume_selected) ~= nil then
|
||||
local success = true
|
||||
docker:clear_status()
|
||||
for _,vol in ipairs(volume_selected) do
|
||||
docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...")
|
||||
local msg = dk.volumes["remove"](dk, vol)
|
||||
if msg.code ~= 204 then
|
||||
docker:append_status("fail code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "<br>")
|
||||
success = false
|
||||
else
|
||||
docker:append_status("done<br>")
|
||||
end
|
||||
end
|
||||
if success then docker:clear_status() end
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes"))
|
||||
end
|
||||
end
|
||||
return m
|
||||
@ -7,28 +7,221 @@ You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
local docker = require "luci.docker"
|
||||
local uci = (require "luci.model.uci").cursor()
|
||||
|
||||
local _docker = {}
|
||||
|
||||
--pull image and return iamge id
|
||||
local update_image = function(self, image_name)
|
||||
local server = "index.docker.io"
|
||||
|
||||
local json_stringify = luci.json and luci.json.encode or luci.jsonc.stringify
|
||||
_docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...")
|
||||
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
|
||||
local res = self.images:create(nil, {fromImage=image_name, _header={["X-Registry-Auth"]=x_auth}})
|
||||
if res and res.code < 300 then
|
||||
_docker:append_status("done<br>")
|
||||
else
|
||||
_docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
end
|
||||
new_image_id = self.images:inspect(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
|
||||
if v1 and type(v1) == "table" then
|
||||
if next(v1) == nil then
|
||||
res[k1] = { k = 'v' }
|
||||
else
|
||||
res[k1] = v1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return next(res) ~= nil and res or nil
|
||||
end
|
||||
|
||||
-- return create_body, extra_network
|
||||
local get_config = function(old_config, old_host_config, old_network_setting, image_config)
|
||||
local config = old_config
|
||||
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
|
||||
|
||||
config.Env = table_subtract(config.Env, image_config.Env)
|
||||
config.Labels = table_subtract(config.Labels, image_config.Labels)
|
||||
config.Volumes = map_subtract(config.Volumes, image_config.Volumes)
|
||||
-- subtract ports exposed in image from container
|
||||
if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then
|
||||
for p, v in pairs(old_host_config.PortBindings) do
|
||||
config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort }
|
||||
end
|
||||
end
|
||||
|
||||
-- handle network config, we need only one network, extras need to network connect action
|
||||
local network_setting = {}
|
||||
local multi_network = false
|
||||
local extra_network = {}
|
||||
for k, v in pairs(old_network_setting) do
|
||||
if multi_network then
|
||||
extra_network[k] = v
|
||||
else
|
||||
network_setting[k] = v
|
||||
end
|
||||
multi_network = true
|
||||
end
|
||||
|
||||
-- handle hostconfig
|
||||
local host_config = old_host_config
|
||||
if host_config.PortBindings and next(host_config.PortBindings) == nil then host_config.PortBindings = nil end
|
||||
host_config.LogConfig = nil
|
||||
|
||||
-- merge configs
|
||||
local create_body = config
|
||||
create_body["HostConfig"] = host_config
|
||||
create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
|
||||
|
||||
return create_body, extra_network
|
||||
end
|
||||
|
||||
local upgrade = function(self, container_id)
|
||||
_docker:clear_status()
|
||||
-- get image name, image id, container name, configuration information
|
||||
local container_info = self.containers:inspect(container_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 old_config = container_info.body.Config
|
||||
local old_host_config = container_info.body.HostConfig
|
||||
local old_network_setting = container_info.body.NetworkSettings.Networks or {}
|
||||
|
||||
local image_id, res = update_image(self, image_name)
|
||||
if res and res.code > 300 then return res end
|
||||
if image_id == old_image_id then
|
||||
return {code = 305, body = {message = "Already up to date"}}
|
||||
end
|
||||
|
||||
_docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...")
|
||||
res = self.containers:stop(container_name)
|
||||
if res and res.code < 305 then
|
||||
_docker:append_status("done<br>")
|
||||
else
|
||||
return res
|
||||
end
|
||||
|
||||
_docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...")
|
||||
res = self.containers:rename(container_name,{ name = container_name .. "_old" })
|
||||
if res and res.code < 300 then
|
||||
_docker:append_status("done<br>")
|
||||
else
|
||||
return res
|
||||
end
|
||||
|
||||
-- handle config
|
||||
local image_config = self.images:inspect(old_image_id).body.Config
|
||||
local create_body, extra_network = get_config(old_config, old_host_config, old_network_setting, image_config)
|
||||
|
||||
-- create new container
|
||||
_docker:append_status("Container: Create" .. " " .. container_name .. "...")
|
||||
res = self.containers:create(container_name, nil, create_body)
|
||||
if res and res.code > 300 then return res end
|
||||
_docker:append_status("done<br>")
|
||||
|
||||
-- extra networks need to network connect action
|
||||
for k, v in pairs(extra_network) do
|
||||
if v.IPAMConfig and next(v.IPAMConfig) == nil then v.IPAMConfig =nil end
|
||||
if v.DriverOpts and next(v.DriverOpts) == nil then v.DriverOpts =nil end
|
||||
if v.Aliases and next(v.Aliases) == nil then v.Aliases =nil end
|
||||
|
||||
_docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
|
||||
res = self.networks:connect(k, nil, {Container = container_name, EndpointConfig = v})
|
||||
if res.code > 300 then return res end
|
||||
|
||||
_docker:append_status("done<br>")
|
||||
end
|
||||
_docker:clear_status()
|
||||
return res
|
||||
end
|
||||
|
||||
local duplicate_config = function (self, container_id)
|
||||
local container_info = self.containers:inspect(container_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 old_config = container_info.body.Config
|
||||
local old_host_config = container_info.body.HostConfig
|
||||
local old_network_setting = container_info.body.NetworkSettings.Networks or {}
|
||||
local image_config = self.images:inspect(old_image_id).body.Config
|
||||
return get_config(old_config, old_host_config, old_network_setting, image_config)
|
||||
end
|
||||
|
||||
_docker.new = function(option)
|
||||
local option = option or {}
|
||||
options = {
|
||||
socket_path = option.socket_path or uci.get("docker","local", "socket_path"),
|
||||
debug = option.debug or uci.get("docker","local", "debug") == 'true' and true or false,
|
||||
debug_path = option.debug_path or uci.get("docker","local", "debug_path")
|
||||
socket_path = option.socket_path or uci:get("docker", "local", "socket_path"),
|
||||
debug = option.debug or uci:get("docker", "local", "debug") == 'true' and true or false,
|
||||
debug_path = option.debug_path or uci:get("docker", "local", "debug_path")
|
||||
}
|
||||
local _new = docker.new(options)
|
||||
_new.options.status_path = uci.get("docker","local", "status_path")
|
||||
_new.options.status_path = uci:get("docker", "local", "status_path")
|
||||
_new.containers_upgrade = upgrade
|
||||
_new.containers_duplicate_config = duplicate_config
|
||||
return _new
|
||||
end
|
||||
_docker.options={}
|
||||
_docker.options.status_path = uci.get("docker","local", "status_path")
|
||||
_docker.options.status_path = uci:get("docker", "local", "status_path")
|
||||
|
||||
_docker.append_status=function(self,val)
|
||||
local file_docker_action_status=io.open(self.options.status_path,"a+")
|
||||
local file_docker_action_status=io.open(self.options.status_path, "a+")
|
||||
file_docker_action_status:write(val)
|
||||
file_docker_action_status:close()
|
||||
end
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
<%+cbi/valueheader%>
|
||||
<div<%=
|
||||
attr("data-prefix", cbid) ..
|
||||
attr("data-browser-path", self.default_path) ..
|
||||
attr("data-dynlist", luci.util.serialize_json({
|
||||
self.keylist, self.vallist,
|
||||
self.datatype, self.optional or self.rmempty
|
||||
})) ..
|
||||
|
||||
ifattr(self.size, "data-size", self.size) ..
|
||||
ifattr(self.placeholder, "data-placeholder", self.placeholder)
|
||||
%>>
|
||||
<%
|
||||
local vals = self:cfgvalue(section) or self.default or {}
|
||||
for i=1, #vals + 1 do
|
||||
local val = vals[i]
|
||||
if (val and #val > 0) or (i == 1) then
|
||||
%>
|
||||
<input class="cbi-input-text" value="<%=pcdata(val)%>" data-update="change" type="text"<%=
|
||||
attr("id", cbid .. "." .. i) ..
|
||||
attr("name", cbid) ..
|
||||
ifattr(self.size, "size") ..
|
||||
ifattr(i == 1 and self.placeholder, "placeholder", self.placeholder)
|
||||
%> /><br />
|
||||
<% end end %>
|
||||
</div>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -0,0 +1,287 @@
|
||||
<style>
|
||||
/*!
|
||||
Pure v1.0.1
|
||||
Copyright 2013 Yahoo!
|
||||
Licensed under the BSD License.
|
||||
https://github.com/pure-css/pure/blob/master/LICENSE.md
|
||||
*/
|
||||
.pure-g {
|
||||
letter-spacing: -.31em;
|
||||
text-rendering: optimizespeed;
|
||||
font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-flow: row wrap;
|
||||
-ms-flex-flow: row wrap;
|
||||
flex-flow: row wrap;
|
||||
-webkit-align-content: flex-start;
|
||||
-ms-flex-line-pack: start;
|
||||
align-content: flex-start
|
||||
}
|
||||
|
||||
.pure-u {
|
||||
display: inline-block;
|
||||
zoom: 1;
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
vertical-align: top;
|
||||
text-rendering: auto
|
||||
}
|
||||
|
||||
.pure-g [class*=pure-u] {
|
||||
font-family: sans-serif
|
||||
}
|
||||
|
||||
.pure-u-1-4,
|
||||
.pure-u-2-5,
|
||||
.pure-u-3-5 {
|
||||
display: inline-block;
|
||||
zoom: 1;
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
vertical-align: top;
|
||||
text-rendering: auto
|
||||
}
|
||||
|
||||
.pure-u-1-4 {
|
||||
width: 25%
|
||||
}
|
||||
|
||||
.pure-u-2-5 {
|
||||
width: 40%
|
||||
}
|
||||
|
||||
.pure-u-3-5 {
|
||||
width: 60%
|
||||
}
|
||||
|
||||
.status {
|
||||
margin: 1rem -0.5rem 1rem -0.5rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
margin: 0.5rem 0.5rem;
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
line-height: 1;
|
||||
font-family: inherit;
|
||||
min-width: inherit;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
border: 1px solid rgba(0, 0, 0, .05);
|
||||
border-radius: .375rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15);
|
||||
}
|
||||
|
||||
.img-con {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.block h4 {
|
||||
font-size: .8125rem;
|
||||
font-weight: 600;
|
||||
margin: 1rem;
|
||||
color: #8898aa !important;
|
||||
line-height: 1.8em;
|
||||
}
|
||||
|
||||
.cbi-section-table-cell {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.pure-u-1-4 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.cbi-button-add {
|
||||
position: fixed;
|
||||
padding: 0.3rem 0.5rem;
|
||||
z-index: 1000;
|
||||
width: 50px !important;
|
||||
height: 50px;
|
||||
bottom: 90px;
|
||||
right: 5px;
|
||||
font-size: 16px;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
background-color: #fb6340 !important;
|
||||
border-color: #fb6340 !important;
|
||||
box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .75);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="pure-g status">
|
||||
<div class="pure-u-1-4">
|
||||
<div class="block pure-g">
|
||||
<div class="pure-u-2-5">
|
||||
<div class="img-con">
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Docker icon</title>
|
||||
<path
|
||||
d="M4.82 17.275c-.684 0-1.304-.56-1.304-1.24s.56-1.243 1.305-1.243c.748 0 1.31.56 1.31 1.242s-.622 1.24-1.305 1.24zm16.012-6.763c-.135-.992-.75-1.8-1.56-2.42l-.315-.25-.254.31c-.494.56-.69 1.553-.63 2.295.06.562.24 1.12.554 1.554-.254.13-.568.25-.81.377-.57.187-1.124.25-1.68.25H.097l-.06.37c-.12 1.182.06 2.42.562 3.54l.244.435v.06c1.5 2.483 4.17 3.6 7.078 3.6 5.594 0 10.182-2.42 12.357-7.633 1.425.062 2.864-.31 3.54-1.676l.18-.31-.3-.187c-.81-.494-1.92-.56-2.85-.31l-.018.002zm-8.008-.992h-2.428v2.42h2.43V9.518l-.002.003zm0-3.043h-2.428v2.42h2.43V6.48l-.002-.003zm0-3.104h-2.428v2.42h2.43v-2.42h-.002zm2.97 6.147H13.38v2.42h2.42V9.518l-.007.003zm-8.998 0H4.383v2.42h2.422V9.518l-.01.003zm3.03 0h-2.4v2.42H9.84V9.518l-.015.003zm-6.03 0H1.4v2.42h2.428V9.518l-.03.003zm6.03-3.043h-2.4v2.42H9.84V6.48l-.015-.003zm-3.045 0H4.387v2.42H6.8V6.48l-.016-.003z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-3-5">
|
||||
<h4 style="text-align: right; font-size: 1rem"><%:Containers%></h4>
|
||||
<h4 style="text-align: right;">
|
||||
<%- if self.containers_total ~= "-" then -%><a href="/cgi-bin/luci/admin/docker/containers"><%- end -%>
|
||||
<span style="font-size: 2rem; color: #2dce89;"><%=self.containers_running%></span>
|
||||
<span style="font-size: 1rem; color: #8898aa !important;">/<%=self.containers_total%></span>
|
||||
<%- if self.containers_total ~= "-" then -%></a><%- end -%>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1-4">
|
||||
<div class="block pure-g">
|
||||
<div class="pure-u-2-5">
|
||||
<div class="img-con">
|
||||
<svg id="icon-hub" viewBox="0 0 42 38" stroke-width="2" fill-rule="nonzero" width="100%" height="100%">
|
||||
<path
|
||||
d="M37.176371,36.2324812 C37.1920117,36.8041095 36.7372743,37.270685 36.1684891,37.270685 L3.74335204,37.2703476 C3.17827583,37.2703476 2.72400056,36.8091818 2.72400056,36.2397767 L2.72400056,19.6131383 C1.4312007,18.4881431 0.662551336,16.8884326 0.662551336,15.1618249 L0.664207893,14.69503 C0.63774183,14.4532127 0.650524255,14.2942438 0.711604827,14.1238231 L5.10793246,1.20935468 C5.24853286,0.797020623 5.63848594,0.511627907 6.06681069,0.511627907 L34.0728364,0.511627907 C34.5091607,0.511627907 34.889927,0.793578201 35.0316653,1.20921034 L39.4428567,14.1234095 C39.4871296,14.273204 39.5020782,14.4249444 39.4884726,14.5493649 L39.4884726,15.1505835 C39.4884726,16.9959517 38.6190601,18.6883031 37.1764746,19.7563084 L37.176371,36.2324812 Z M35.1376208,35.209311 L35.1376208,20.7057152 C34.7023924,20.8097593 34.271333,20.8633641 33.8336069,20.8633641 C32.0046019,20.8633641 30.3013756,19.9547008 29.2437221,18.4771538 C28.1860473,19.954695 26.4828515,20.8633641 24.6538444,20.8633641 C22.824803,20.8633641 21.1216155,19.9547157 20.0639591,18.4771544 C19.0062842,19.9546953 17.3030887,20.8633641 15.4740818,20.8633641 C13.6450404,20.8633641 11.9418529,19.9547157 10.8841965,18.4771544 C9.82652161,19.9546953 8.12332608,20.8633641 6.29431919,20.8633641 C5.76735555,20.8633641 5.24095778,20.7883418 4.73973398,20.644674 L4.73973398,35.209311 L35.1376208,35.209311 Z M30.2720226,15.6557626 C30.5154632,17.4501192 32.0503909,18.8018554 33.845083,18.8018554 C35.7286794,18.8018554 37.285413,17.3395134 37.4474599,15.4751932 L30.2280765,15.4751932 C30.2470638,15.532987 30.2617919,15.5932958 30.2720226,15.6557626 Z M21.0484306,15.4751932 C21.0674179,15.532987 21.0821459,15.5932958 21.0923767,15.6557626 C21.3358173,17.4501192 22.8707449,18.8018554 24.665437,18.8018554 C26.4601001,18.8018554 27.9950169,17.4501481 28.2378191,15.6611556 C28.2451225,15.5981318 28.2590045,15.5358056 28.2787375,15.4751932 L21.0484306,15.4751932 Z M11.9238102,15.6557626 C12.1672508,17.4501192 13.7021785,18.8018554 15.4968705,18.8018554 C17.2915336,18.8018554 18.8264505,17.4501481 19.0692526,15.6611556 C19.0765561,15.5981318 19.0904381,15.5358056 19.110171,15.4751932 L11.8798641,15.4751932 C11.8988514,15.532987 11.9135795,15.5932958 11.9238102,15.6557626 Z M6.31682805,18.8018317 C8.11149114,18.8018317 9.64640798,17.4501244 9.88921012,15.6611319 C9.89651357,15.5981081 9.91039559,15.5357819 9.93012856,15.4751696 L2.70318796,15.4751696 C2.86612006,17.3346852 4.42809696,18.8018317 6.31682805,18.8018317 Z M3.09670082,13.4139924 L37.04257,13.4139924 L33.3489482,2.57204736 L6.80119239,2.57204736 L3.09670082,13.4139924 Z"
|
||||
id="Fill-1"></path>
|
||||
<rect id="Rectangle-3" x="14" y="26" width="6" height="10"></rect>
|
||||
<path d="M20,26 L20,36 L26,36 L26,26 L20,26 Z" id="Rectangle-3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-3-5">
|
||||
<h4 style="text-align: right; font-size: 1rem"><%:Images%></h4>
|
||||
<h4 style="text-align: right;">
|
||||
<%- if self.images_total ~= "-" then -%><a href="/cgi-bin/luci/admin/docker/images"><%- end -%>
|
||||
<span style="font-size: 2rem; color: #2dce89;"><%=self.images_total%></span>
|
||||
<!-- <span style="font-size: 1rem; color: #8898aa !important;">/<%=self.images_total%></span> -->
|
||||
<%- if self.images_total ~= "-" then -%></a><%- end -%>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1-4">
|
||||
<div class="block pure-g">
|
||||
<div class="pure-u-2-5">
|
||||
<div class="img-con">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px" y="0px" width="48.723px" height="48.723px" viewBox="0 0 48.723 48.723"
|
||||
style="enable-background:new 0 0 48.723 48.723;" xml:space="preserve">
|
||||
<g>
|
||||
<g id="_x33_8_89_">
|
||||
<g>
|
||||
<path d="M7.452,24.152h3.435v5.701h0.633c0.001,0,0.001,0,0.002,0h0.636v-5.701h3.51v-1.059h17.124v1.104h3.178v5.656h0.619
|
||||
c0,0,0,0,0.002,0h0.619v-5.656h3.736v-0.856c0-0.012,0.006-0.021,0.006-0.032c0-0.072,0-0.143,0-0.215h5.721v-1.316h-5.721
|
||||
c0-0.054,0-0.108,0-0.164c0-0.011-0.006-0.021-0.006-0.032v-0.832h-8.154v1.028h-7.911v-2.652h-0.689c-0.001,0-0.001,0-0.002,0
|
||||
h-0.678v2.652h-7.846v-1.104H7.452v1.104H1.114v1.316h6.338V24.152z" />
|
||||
<path
|
||||
d="M21.484,16.849h5.204v-2.611h7.133V1.555H14.588v12.683h6.896V16.849z M16.537,12.288V3.505h15.335v8.783H16.537z" />
|
||||
<rect x="18.682" y="16.898" width="10.809" height="0.537" />
|
||||
<path
|
||||
d="M0,43.971h6.896v2.611H12.1v-2.611h7.134V31.287H0V43.971z M1.95,33.236h15.334v8.785H1.95V33.236z" />
|
||||
<rect x="4.095" y="46.631" width="10.808" height="0.537" />
|
||||
<path
|
||||
d="M29.491,30.994v12.684h6.895v2.611h5.205v-2.611h7.133V30.994H29.491z M46.774,41.729H31.44v-8.783h15.334V41.729z" />
|
||||
<rect x="33.584" y="46.338" width="10.809" height="0.537" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-3-5">
|
||||
<h4 style="text-align: right; font-size: 1rem"><%:Networks%></h4>
|
||||
<h4 style="text-align: right;">
|
||||
<%- if self.networks_total ~= "-" then -%><a href="/cgi-bin/luci/admin/docker/networks"><%- end -%>
|
||||
<span style="font-size: 2rem; color: #2dce89;"><%=self.networks_total%></span>
|
||||
<!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
|
||||
<%- if self.networks_total ~= "-" then -%></a><%- end -%>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1-4">
|
||||
<div class="block pure-g">
|
||||
<div class="pure-u-2-5">
|
||||
<div class="img-con">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px" y="0px" viewBox="0 0 55 55" style="enable-background:new 0 0 55 55;" xml:space="preserve">
|
||||
<path
|
||||
d="M52.354,8.51C51.196,4.22,42.577,0,27.5,0C12.423,0,3.803,4.22,2.646,8.51C2.562,8.657,2.5,8.818,2.5,9v0.5V21v0.5V22v11
|
||||
v0.5V34v12c0,0.162,0.043,0.315,0.117,0.451C3.798,51.346,14.364,55,27.5,55c13.106,0,23.655-3.639,24.875-8.516
|
||||
C52.455,46.341,52.5,46.176,52.5,46V34v-0.5V33V22v-0.5V21V9.5V9C52.5,8.818,52.438,8.657,52.354,8.51z M50.421,33.985
|
||||
c-0.028,0.121-0.067,0.241-0.116,0.363c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369
|
||||
c-0.066,0.093-0.141,0.185-0.219,0.277c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236
|
||||
c-0.164,0.143-0.335,0.285-0.526,0.426c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463
|
||||
c-0.068,0.041-0.141,0.08-0.212,0.12c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061
|
||||
c-0.375,0.177-0.767,0.351-1.186,0.519c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17
|
||||
c-0.017,0.002-0.034,0.004-0.051,0.007c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032
|
||||
c-0.605,0.063-1.217,0.121-1.847,0.167c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076
|
||||
C29.137,40.984,28.327,41,27.5,41s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076
|
||||
c-0.293-0.017-0.595-0.028-0.883-0.049c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032
|
||||
c-0.693-0.076-1.368-0.163-2.026-0.259c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17
|
||||
c-0.012-0.004-0.024-0.009-0.036-0.014c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061
|
||||
c-0.336-0.162-0.647-0.328-0.945-0.497c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463
|
||||
c-0.086-0.06-0.175-0.119-0.257-0.18c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236
|
||||
c-0.134-0.13-0.252-0.26-0.363-0.392c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369
|
||||
c-0.054-0.099-0.102-0.198-0.143-0.297c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,33.823,4.5,33.661,4.5,33.5
|
||||
c0-0.113,0.013-0.226,0.031-0.338c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.028,0.026,0.063,0.051,0.092,0.077
|
||||
c0.218,0.192,0.44,0.383,0.69,0.567C9.049,28.786,16.582,31,27.5,31c10.872,0,18.386-2.196,22.169-5.028
|
||||
c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001v7.424c-0.042,0.143-0.056,0.294-0.031,0.445c0.019,0.112,0.031,0.225,0.031,0.338
|
||||
C50.5,33.661,50.459,33.823,50.421,33.985z M50.5,13.293v7.424c-0.042,0.143-0.056,0.294-0.031,0.445
|
||||
c0.019,0.112,0.031,0.225,0.031,0.338c0,0.161-0.041,0.323-0.079,0.485c-0.028,0.121-0.067,0.241-0.116,0.363
|
||||
c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369c-0.066,0.093-0.141,0.185-0.219,0.277
|
||||
c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236c-0.164,0.143-0.335,0.285-0.526,0.426
|
||||
c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463c-0.068,0.041-0.141,0.08-0.212,0.12
|
||||
c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061c-0.375,0.177-0.767,0.351-1.186,0.519
|
||||
c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17c-0.017,0.002-0.034,0.004-0.051,0.007
|
||||
c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032c-0.605,0.063-1.217,0.121-1.847,0.167
|
||||
c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076C29.137,28.984,28.327,29,27.5,29
|
||||
s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076c-0.293-0.017-0.595-0.028-0.883-0.049
|
||||
c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032c-0.693-0.076-1.368-0.163-2.026-0.259
|
||||
c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17c-0.012-0.004-0.024-0.009-0.036-0.014
|
||||
c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061c-0.336-0.162-0.647-0.328-0.945-0.497
|
||||
c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463c-0.086-0.06-0.175-0.119-0.257-0.18
|
||||
c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236c-0.134-0.13-0.252-0.26-0.363-0.392
|
||||
c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369c-0.054-0.099-0.102-0.198-0.143-0.297
|
||||
c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,21.823,4.5,21.661,4.5,21.5c0-0.113,0.013-0.226,0.031-0.338
|
||||
c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.12,0.109,0.257,0.216,0.387,0.324c0.072,0.06,0.139,0.12,0.215,0.18
|
||||
c0.3,0.236,0.624,0.469,0.975,0.696c0.073,0.047,0.155,0.093,0.231,0.14c0.294,0.183,0.605,0.362,0.932,0.538
|
||||
c0.121,0.065,0.242,0.129,0.367,0.193c0.365,0.186,0.748,0.367,1.151,0.542c0.066,0.029,0.126,0.059,0.193,0.087
|
||||
c0.469,0.199,0.967,0.389,1.485,0.573c0.143,0.051,0.293,0.099,0.44,0.149c0.412,0.139,0.838,0.272,1.279,0.401
|
||||
c0.159,0.046,0.315,0.094,0.478,0.138c0.585,0.162,1.189,0.316,1.823,0.458c0.087,0.02,0.181,0.036,0.269,0.055
|
||||
c0.559,0.122,1.139,0.235,1.735,0.341c0.202,0.036,0.407,0.07,0.613,0.104c0.567,0.093,1.151,0.178,1.75,0.256
|
||||
c0.154,0.02,0.301,0.043,0.457,0.062c0.744,0.09,1.514,0.167,2.305,0.233c0.195,0.016,0.398,0.028,0.596,0.042
|
||||
c0.633,0.046,1.28,0.084,1.942,0.114c0.241,0.011,0.481,0.022,0.727,0.031C25.712,18.979,26.59,19,27.5,19s1.788-0.021,2.65-0.05
|
||||
c0.245-0.009,0.485-0.02,0.727-0.031c0.662-0.03,1.309-0.068,1.942-0.114c0.198-0.015,0.4-0.026,0.596-0.042
|
||||
c0.791-0.065,1.561-0.143,2.305-0.233c0.156-0.019,0.303-0.042,0.457-0.062c0.599-0.078,1.182-0.163,1.75-0.256
|
||||
c0.206-0.034,0.411-0.068,0.613-0.104c0.596-0.106,1.176-0.219,1.735-0.341c0.088-0.019,0.182-0.036,0.269-0.055
|
||||
c0.634-0.142,1.238-0.297,1.823-0.458c0.163-0.045,0.319-0.092,0.478-0.138c0.441-0.129,0.867-0.262,1.279-0.401
|
||||
c0.147-0.05,0.297-0.098,0.44-0.149c0.518-0.184,1.017-0.374,1.485-0.573c0.067-0.028,0.127-0.058,0.193-0.087
|
||||
c0.403-0.176,0.786-0.356,1.151-0.542c0.125-0.064,0.247-0.128,0.367-0.193c0.327-0.175,0.638-0.354,0.932-0.538
|
||||
c0.076-0.047,0.158-0.093,0.231-0.14c0.351-0.227,0.675-0.459,0.975-0.696c0.075-0.06,0.142-0.12,0.215-0.18
|
||||
C50.243,13.509,50.38,13.402,50.5,13.293z M27.5,2c13.555,0,23,3.952,23,7.5s-9.445,7.5-23,7.5s-23-3.952-23-7.5S13.945,2,27.5,2z
|
||||
M50.5,45.703c-0.014,0.044-0.024,0.089-0.032,0.135C49.901,49.297,40.536,53,27.5,53S5.099,49.297,4.532,45.838
|
||||
c-0.008-0.045-0.019-0.089-0.032-0.131v-8.414c0.028,0.026,0.063,0.051,0.092,0.077c0.218,0.192,0.44,0.383,0.69,0.567
|
||||
C9.049,40.786,16.582,43,27.5,43c10.872,0,18.386-2.196,22.169-5.028c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001V45.703z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-3-5">
|
||||
<h4 style="text-align: right; font-size: 1rem"><%:Volumes%></h4>
|
||||
<h4 style="text-align: right;">
|
||||
<%- if self.volumes_total ~= "-" then -%><a href="/cgi-bin/luci/admin/docker/volumes"><%- end -%>
|
||||
<span style="font-size: 2rem; color: #2dce89;"><%=self.volumes_total%></span>
|
||||
<!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
|
||||
<%- if self.volumes_total ~= "-" then -%></a><%- end -%>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,19 @@
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">
|
||||
function resolv_container(op) {
|
||||
let s = document.getElementById(op + '-status');
|
||||
if (!s) return;
|
||||
let cmd_line = prompt("Plese input <docker create> command line:", "");
|
||||
if (cmd_line == null || cmd_line == "") {
|
||||
s.innerHTML = "<font color='red'>Canceled</font>";
|
||||
return;
|
||||
}
|
||||
let re = /\/admin\/docker\/newcontainer\//
|
||||
let p = window.location.href
|
||||
let path = p.split(re)
|
||||
window.location = path[0] + "/admin/docker/newcontainer/" + encodeURI(cmd_line)
|
||||
}
|
||||
</script>
|
||||
<input type="button" class="cbi-button cbi-button-apply" value="Command line" onclick="resolv_container('<%=self.option%>')" />
|
||||
<span id="<%=self.option%>-status"></span>
|
||||
<%+cbi/valuefooter%>
|
||||
Loading…
Reference in New Issue
Block a user