mirror of
https://github.com/coolsnowwolf/lede.git
synced 2025-04-16 04:13:31 +00:00
revert luci-app-dockerman
This commit is contained in:
parent
a1807936af
commit
61b22a78b8
24
package/lean/luci-app-dockerman/Makefile
Normal file → Executable file
24
package/lean/luci-app-dockerman/Makefile
Normal file → Executable file
@ -1,18 +1,18 @@
|
||||
#
|
||||
# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=Docker Manager interface for LuCI
|
||||
LUCI_DEPENDS:=+luci-lib-docker +docker-ce +e2fsprogs +fdisk
|
||||
PKG_NAME:=luci-app-dockerman
|
||||
PKG_VERSION:=v0.3.0
|
||||
PKG_RELEASE:=leanmod-3
|
||||
PKG_MAINTAINER:=lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
PKG_LICENSE:=AGPL-3.0
|
||||
LUCI_TITLE:=Luci for Docker-CE
|
||||
LUCI_DEPENDS:=+docker-ce +e2fsprogs +fdisk
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_VERSION:=1
|
||||
PKG_RELEASE:=9
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
define Package/$(PKG_NAME)/postinst
|
||||
#!/bin/sh
|
||||
rm -rf /tmp/luci-indexcache /tmp/luci-modulecache
|
||||
endef
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
|
17
package/lean/luci-app-dockerman/luasrc/controller/docker.lua
Normal file
17
package/lean/luci-app-dockerman/luasrc/controller/docker.lua
Normal file
@ -0,0 +1,17 @@
|
||||
module("luci.controller.docker", package.seeall)
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/dockerd") then
|
||||
return
|
||||
end
|
||||
|
||||
entry({"admin", "services", "docker"}, cbi("docker"), _("Docker CE Container"), 199).dependent = true
|
||||
entry({"admin","services","docker","status"},call("act_status")).leaf=true
|
||||
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
|
@ -1,201 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
require "luci.util"
|
||||
local docker = require "luci.model.docker"
|
||||
local uci = require "luci.model.uci"
|
||||
|
||||
module("luci.controller.dockerman",package.seeall)
|
||||
|
||||
function index()
|
||||
|
||||
entry({"admin", "services","docker"}, firstchild(), "Docker", 40).dependent = false
|
||||
entry({"admin","services","docker","overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true
|
||||
|
||||
local socket = luci.model.uci.cursor():get("dockerman", "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","services","docker","containers"},form("dockerman/containers"),_("Containers"),1).leaf=true
|
||||
entry({"admin","services","docker","images"},form("dockerman/images"),_("Images"),2).leaf=true
|
||||
entry({"admin","services","docker","networks"},form("dockerman/networks"),_("Networks"),3).leaf=true
|
||||
entry({"admin","services","docker","volumes"},form("dockerman/volumes"),_("Volumes"),4).leaf=true
|
||||
entry({"admin","services","docker","events"},call("action_events"),_("Events"),5)
|
||||
entry({"admin","services","docker","newcontainer"},form("dockerman/newcontainer")).leaf=true
|
||||
entry({"admin","services","docker","newnetwork"},form("dockerman/newnetwork")).leaf=true
|
||||
entry({"admin","services","docker","container"},form("dockerman/container")).leaf=true
|
||||
entry({"admin","services","docker","container_stats"},call("action_get_container_stats")).leaf=true
|
||||
entry({"admin","services","docker","container_get_archive"},call("download_archive")).leaf=true
|
||||
entry({"admin","services","docker","container_put_archive"},call("upload_archive")).leaf=true
|
||||
entry({"admin","services","docker","confirm"},call("action_confirm")).leaf=true
|
||||
|
||||
end
|
||||
|
||||
|
||||
function action_events()
|
||||
local logs = ""
|
||||
local dk = docker.new()
|
||||
local query ={}
|
||||
query["until"] = os.time()
|
||||
local events = dk:events({query = query})
|
||||
for _, v in ipairs(events.body) do
|
||||
if v.Type == "container" then
|
||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. (v.Action or "null") .. " Container ID:".. (v.Actor.ID or "null") .. " Container Name:" .. (v.Actor.Attributes.name or "null")
|
||||
elseif v.Type == "network" then
|
||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Container ID:"..( v.Actor.Attributes.container or "null" ) .. " Network Name:" .. (v.Actor.Attributes.name or "null") .. " Network type:".. v.Actor.Attributes.type or ""
|
||||
elseif v.Type == "image" then
|
||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Image:".. (v.Actor.ID or "null").. " Image Name:" .. (v.Actor.Attributes.name or "null")
|
||||
end
|
||||
end
|
||||
luci.template.render("dockerman/logs", {self={syslog = logs, title="Docker Events"}})
|
||||
end
|
||||
|
||||
local calculate_cpu_percent = function(d)
|
||||
if type(d) ~= "table" then return end
|
||||
cpu_count = tonumber(d["cpu_stats"]["online_cpus"])
|
||||
cpu_percent = 0.0
|
||||
cpu_delta = tonumber(d["cpu_stats"]["cpu_usage"]["total_usage"]) - tonumber(d["precpu_stats"]["cpu_usage"]["total_usage"])
|
||||
system_delta = tonumber(d["cpu_stats"]["system_cpu_usage"]) - tonumber(d["precpu_stats"]["system_cpu_usage"])
|
||||
if system_delta > 0.0 then
|
||||
cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count)
|
||||
end
|
||||
-- return cpu_percent .. "%"
|
||||
return cpu_percent
|
||||
end
|
||||
|
||||
local get_memory = function(d)
|
||||
if type(d) ~= "table" then return end
|
||||
-- local limit = string.format("%.2f", tonumber(d["memory_stats"]["limit"]) / 1024 / 1024)
|
||||
-- local usage = string.format("%.2f", (tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"])) / 1024 / 1024)
|
||||
-- return usage .. "MB / " .. limit.. "MB"
|
||||
local limit =tonumber(d["memory_stats"]["limit"])
|
||||
local usage = tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"])
|
||||
return usage, limit
|
||||
end
|
||||
|
||||
local get_rx_tx = function(d)
|
||||
if type(d) ~="table" then return end
|
||||
-- local data
|
||||
-- if type(d["networks"]) == "table" then
|
||||
-- for e, v in pairs(d["networks"]) do
|
||||
-- data = (data and (data .. "<br>") or "") .. e .. " Total Tx:" .. string.format("%.2f",(tonumber(v.tx_bytes)/1024/1024)) .. "MB Total Rx: ".. string.format("%.2f",(tonumber(v.rx_bytes)/1024/1024)) .. "MB"
|
||||
-- end
|
||||
-- end
|
||||
local data = {}
|
||||
if type(d["networks"]) == "table" then
|
||||
for e, v in pairs(d["networks"]) do
|
||||
data[e] = {
|
||||
bw_tx = tonumber(v.tx_bytes),
|
||||
bw_rx = tonumber(v.rx_bytes)
|
||||
}
|
||||
end
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
function action_get_container_stats(container_id)
|
||||
if container_id then
|
||||
local dk = docker.new()
|
||||
local response = dk.containers:inspect({id = container_id})
|
||||
if response.code == 200 and response.body.State.Running then
|
||||
response = dk.containers:stats({id = container_id, query = {stream = false}})
|
||||
if response.code == 200 then
|
||||
local container_stats = response.body
|
||||
local cpu_percent = calculate_cpu_percent(container_stats)
|
||||
local mem_useage, mem_limit = get_memory(container_stats)
|
||||
local bw_rxtx = get_rx_tx(container_stats)
|
||||
luci.http.status(response.code, response.body.message)
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
cpu_percent = cpu_percent,
|
||||
memory = {
|
||||
mem_useage = mem_useage,
|
||||
mem_limit = mem_limit
|
||||
},
|
||||
bw_rxtx = bw_rxtx
|
||||
})
|
||||
else
|
||||
luci.http.status(response.code, response.body.message)
|
||||
luci.http.prepare_content("text/plain")
|
||||
luci.http.write(response.body.message)
|
||||
end
|
||||
else
|
||||
if response.code == 200 then
|
||||
luci.http.status(500, "container "..container_id.." not running")
|
||||
luci.http.prepare_content("text/plain")
|
||||
luci.http.write("Container "..container_id.." not running")
|
||||
else
|
||||
luci.http.status(response.code, response.body.message)
|
||||
luci.http.prepare_content("text/plain")
|
||||
luci.http.write(response.body.message)
|
||||
end
|
||||
end
|
||||
else
|
||||
luci.http.status(404, "No container name or id")
|
||||
luci.http.prepare_content("text/plain")
|
||||
luci.http.write("No container name or id")
|
||||
end
|
||||
end
|
||||
|
||||
function action_confirm()
|
||||
local status_path=luci.model.uci.cursor():get("dockerman", "local", "status_path")
|
||||
local data = nixio.fs.readfile(status_path)
|
||||
if data then
|
||||
code = 202
|
||||
msg = data
|
||||
else
|
||||
code = 200
|
||||
msg = "finish"
|
||||
data = "finish"
|
||||
end
|
||||
-- luci.util.perror(data)
|
||||
luci.http.status(code, msg)
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({info = data})
|
||||
end
|
||||
|
||||
function download_archive()
|
||||
local id = luci.http.formvalue("id")
|
||||
local path = luci.http.formvalue("path")
|
||||
local dk = docker.new()
|
||||
local first
|
||||
|
||||
local cb = function(res, chunk)
|
||||
if res.code == 200 then
|
||||
if not first then
|
||||
first = true
|
||||
luci.http.header('Content-Disposition', 'inline; filename="archive.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
|
||||
|
||||
local res = dk.containers:get_archive({id = id, query = {path = path}}, cb)
|
||||
end
|
||||
|
||||
function upload_archive(container_id)
|
||||
local path = luci.http.formvalue("upload-path")
|
||||
local dk = docker.new()
|
||||
local ltn12 = require "luci.ltn12"
|
||||
|
||||
rec_send = function(sinkout)
|
||||
luci.http.setfilehandler(function (meta, chunk, eof)
|
||||
if chunk then
|
||||
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local res = dk.containers:put_archive({id = container_id, query = {path = path}, body = rec_send})
|
||||
local msg = res and res.body and res.body.message or nil
|
||||
luci.http.status(res.code, msg)
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({message = msg})
|
||||
end
|
23
package/lean/luci-app-dockerman/luasrc/model/cbi/docker.lua
Normal file
23
package/lean/luci-app-dockerman/luasrc/model/cbi/docker.lua
Normal file
@ -0,0 +1,23 @@
|
||||
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 />"
|
||||
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>")
|
||||
|
||||
return m
|
@ -1,488 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
container_id = arg[1]
|
||||
local action = arg[2] or "info"
|
||||
|
||||
local images, networks, containers_info
|
||||
if not container_id then return end
|
||||
local res = dk.containers:inspect({id = container_id})
|
||||
if res.code < 300 then container_info = res.body else return end
|
||||
res = dk.networks:list()
|
||||
if res.code < 300 then networks = res.body else return end
|
||||
|
||||
local get_ports = function(d)
|
||||
local data
|
||||
if d.HostConfig and d.HostConfig.PortBindings then
|
||||
for inter, out in pairs(d.HostConfig.PortBindings) do
|
||||
data = (data and (data .. "<br>") or "") .. out[1]["HostPort"] .. ":" .. inter
|
||||
end
|
||||
end
|
||||
return data
|
||||
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_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)
|
||||
docker:clear_status()
|
||||
docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...")
|
||||
local res
|
||||
if cmd ~= "upgrade" then
|
||||
res = dk.containers[cmd](dk, {id = container_id})
|
||||
else
|
||||
res = dk.containers_upgrade(dk, {id = container_id})
|
||||
end
|
||||
if res and res.code >= 300 then
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/container/"..container_id))
|
||||
else
|
||||
docker:clear_status()
|
||||
if cmd ~= "remove" and cmd ~= "upgrade" then
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/container/"..container_id))
|
||||
else
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/containers"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
m=SimpleForm("docker", container_info.Name:sub(2), translate("Docker Container") )
|
||||
m.template = "dockerman/cbi/xsimpleform"
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/docker/containers")
|
||||
-- m:append(Template("dockerman/container"))
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
-- luci.util.perror(docker_status.err)
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
|
||||
action_section = m:section(Table,{{}})
|
||||
action_section.notitle=true
|
||||
action_section.rowcolors=false
|
||||
action_section.template = "cbi/nullsection"
|
||||
|
||||
btnstart=action_section:option(Button, "_start")
|
||||
btnstart.template = "dockerman/cbi/inlinebutton"
|
||||
btnstart.inputtitle=translate("Start")
|
||||
btnstart.inputstyle = "apply"
|
||||
btnstart.forcewrite = true
|
||||
btnrestart=action_section:option(Button, "_restart")
|
||||
btnrestart.template = "dockerman/cbi/inlinebutton"
|
||||
btnrestart.inputtitle=translate("Restart")
|
||||
btnrestart.inputstyle = "reload"
|
||||
btnrestart.forcewrite = true
|
||||
btnstop=action_section:option(Button, "_stop")
|
||||
btnstop.template = "dockerman/cbi/inlinebutton"
|
||||
btnstop.inputtitle=translate("Stop")
|
||||
btnstop.inputstyle = "reset"
|
||||
btnstop.forcewrite = true
|
||||
btnupgrade=action_section:option(Button, "_upgrade")
|
||||
btnupgrade.template = "dockerman/cbi/inlinebutton"
|
||||
btnupgrade.inputtitle=translate("Upgrade")
|
||||
btnupgrade.inputstyle = "reload"
|
||||
btnstop.forcewrite = true
|
||||
btnduplicate=action_section:option(Button, "_duplicate")
|
||||
btnduplicate.template = "dockerman/cbi/inlinebutton"
|
||||
btnduplicate.inputtitle=translate("Duplicate")
|
||||
btnduplicate.inputstyle = "add"
|
||||
btnstop.forcewrite = true
|
||||
btnremove=action_section:option(Button, "_remove")
|
||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
||||
btnremove.inputtitle=translate("Remove")
|
||||
btnremove.inputstyle = "remove"
|
||||
btnremove.forcewrite = true
|
||||
|
||||
btnstart.write = function(self, section)
|
||||
start_stop_remove(m,"start")
|
||||
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/services/docker/newcontainer/duplicate/"..container_id))
|
||||
end
|
||||
|
||||
tab_section = m:section(SimpleSection)
|
||||
tab_section.template = "dockerman/container"
|
||||
|
||||
if action == "info" then
|
||||
m.submit = false
|
||||
m.reset = false
|
||||
table_info = {
|
||||
["01name"] = {_key = translate("Name"), _value = container_info.Name:sub(2) or "-", _button=translate("Update")},
|
||||
["02id"] = {_key = translate("ID"), _value = container_info.Id or "-"},
|
||||
["03image"] = {_key = translate("Image"), _value = container_info.Config.Image .. "<br>" .. container_info.Image},
|
||||
["04status"] = {_key = translate("Status"), _value = container_info.State and container_info.State.Status or "-"},
|
||||
["05created"] = {_key = translate("Created"), _value = container_info.Created or "-"},
|
||||
}
|
||||
table_info["06start"] = container_info.State.Status == "running" and {_key = translate("Start Time"), _value = container_info.State and container_info.State.StartedAt or "-"} or {_key = translate("Finish Time"), _value = container_info.State and container_info.State.FinishedAt or "-"}
|
||||
table_info["07healthy"] = {_key = translate("Healthy"), _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"}
|
||||
table_info["08restart"] = {_key = translate("Restart Policy"), _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", _button=translate("Update")}
|
||||
table_info["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 "-"}
|
||||
table_info["13links"] = {_key = translate("Links"), _value = get_links(container_info) or "-"}
|
||||
info_networks = get_networks(container_info)
|
||||
list_networks = {}
|
||||
for _, v in ipairs (networks) do
|
||||
if v.Name then
|
||||
local parent = v.Options and v.Options.parent or nil
|
||||
local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
|
||||
ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil
|
||||
local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "")
|
||||
list_networks[v.Name] = network_name
|
||||
end
|
||||
end
|
||||
|
||||
if type(info_networks)== "table" then
|
||||
for k,v in pairs(info_networks) do
|
||||
table_info["14network"..k] = {
|
||||
_key = translate("Network"), _value = k.. (v~="" and (" | ".. v) or ""), _button=translate("Disconnect")
|
||||
}
|
||||
list_networks[k]=nil
|
||||
end
|
||||
end
|
||||
|
||||
table_info["15connect"] = {_key = translate("Connect Network"), _value = list_networks ,_opts = "", _button=translate("Connect")}
|
||||
|
||||
|
||||
d_info = m:section(Table,table_info)
|
||||
d_info.nodescr=true
|
||||
d_info.formvalue=function(self, section)
|
||||
return table_info
|
||||
end
|
||||
dv_key = d_info:option(DummyValue, "_key", translate("Info"))
|
||||
dv_key.width = "20%"
|
||||
dv_value = d_info:option(ListValue, "_value")
|
||||
dv_value.render = function(self, section, scope)
|
||||
if table_info[section]._key == translate("Name") then
|
||||
self:reset_values()
|
||||
self.template = "cbi/value"
|
||||
self.size = 30
|
||||
self.keylist = {}
|
||||
self.vallist = {}
|
||||
self.default=table_info[section]._value
|
||||
Value.render(self, section, scope)
|
||||
elseif table_info[section]._key == translate("Restart Policy") then
|
||||
self.template = "cbi/lvalue"
|
||||
self:reset_values()
|
||||
self.size = nil
|
||||
self:value("no", "No")
|
||||
self:value("unless-stopped", "Unless stopped")
|
||||
self:value("always", "Always")
|
||||
self:value("on-failure", "On failure")
|
||||
self.default=table_info[section]._value
|
||||
ListValue.render(self, section, scope)
|
||||
elseif table_info[section]._key == translate("Connect Network") then
|
||||
self.template = "cbi/lvalue"
|
||||
self:reset_values()
|
||||
self.size = nil
|
||||
for k,v in pairs(list_networks) do
|
||||
self:value(k,v)
|
||||
end
|
||||
self.default=table_info[section]._value
|
||||
ListValue.render(self, section, scope)
|
||||
else
|
||||
self:reset_values()
|
||||
self.rawhtml=true
|
||||
self.template = "cbi/dvalue"
|
||||
self.default=table_info[section]._value
|
||||
DummyValue.render(self, section, scope)
|
||||
end
|
||||
end
|
||||
dv_value.forcewrite = true -- for write function using simpleform
|
||||
dv_value.write = function(self, section, value)
|
||||
table_info[section]._value=value
|
||||
end
|
||||
dv_value.validate = function(self, value)
|
||||
return value
|
||||
end
|
||||
dv_opts = d_info:option(Value, "_opts")
|
||||
dv_opts.forcewrite = true -- for write function using simpleform
|
||||
dv_opts.write = function(self, section, value)
|
||||
|
||||
table_info[section]._opts=value
|
||||
end
|
||||
dv_opts.validate = function(self, value)
|
||||
return value
|
||||
end
|
||||
dv_opts.render = function(self, section, scope)
|
||||
if table_info[section]._key==translate("Connect Network") then
|
||||
self.template = "cbi/value"
|
||||
self.keylist = {}
|
||||
self.vallist = {}
|
||||
self.placeholder = "10.1.1.254"
|
||||
self.datatype = "ip4addr"
|
||||
self.default=table_info[section]._opts
|
||||
Value.render(self, section, scope)
|
||||
else
|
||||
self.rawhtml=true
|
||||
self.template = "cbi/dvalue"
|
||||
self.default=table_info[section]._opts
|
||||
DummyValue.render(self, section, scope)
|
||||
end
|
||||
end
|
||||
btn_update = d_info:option(Button, "_button")
|
||||
btn_update.forcewrite = true
|
||||
btn_update.render = function(self, section, scope)
|
||||
if table_info[section]._button and table_info[section]._value ~= nil then
|
||||
btn_update.inputtitle=table_info[section]._button
|
||||
self.template = "cbi/button"
|
||||
Button.render(self, section, scope)
|
||||
else
|
||||
self.template = "dockerman/cbi/dummyvalue"
|
||||
self.default=""
|
||||
DummyValue.render(self, section, scope)
|
||||
end
|
||||
end
|
||||
btn_update.write = function(self, section, value)
|
||||
-- luci.util.perror(section)
|
||||
local res
|
||||
docker:clear_status()
|
||||
if section == "01name" then
|
||||
docker:append_status("Containers: rename " .. container_id .. "...")
|
||||
local new_name = table_info[section]._value
|
||||
res = dk.containers:rename({id = container_id, query = {name=new_name}})
|
||||
elseif section == "08restart" then
|
||||
docker:append_status("Containers: update " .. container_id .. "...")
|
||||
local new_restart = table_info[section]._value
|
||||
res = dk.containers:update({id = container_id, body = {RestartPolicy = {Name = new_restart}}})
|
||||
elseif table_info[section]._key == translate("Network") then
|
||||
local _,_,leave_network = table_info[section]._value:find("(.-) | .+")
|
||||
leave_network = leave_network or table_info[section]._value
|
||||
docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...")
|
||||
res = dk.networks:disconnect({name = leave_network, body = {Container = container_id}})
|
||||
elseif section == "15connect" then
|
||||
local connect_network = table_info[section]._value
|
||||
local network_opiton
|
||||
if connect_network ~= "none" and connect_network ~= "bridge" and connect_network ~= "host" then
|
||||
-- luci.util.perror(table_info[section]._opts)
|
||||
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("fail 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/services/docker/container/"..container_id.."/info"))
|
||||
end
|
||||
|
||||
-- info end
|
||||
elseif action == "edit" then
|
||||
local editsection= m:section(SimpleSection)
|
||||
d = editsection:option( Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
|
||||
d.placeholder = "1.5"
|
||||
d.rmempty = true
|
||||
d.datatype="ufloat"
|
||||
d.default = container_info.HostConfig.NanoCpus / (10^9)
|
||||
|
||||
d = editsection:option(Value, "cpushares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
|
||||
d.placeholder = "1024"
|
||||
d.rmempty = true
|
||||
d.datatype="uinteger"
|
||||
d.default = container_info.HostConfig.CpuShares
|
||||
|
||||
d = editsection:option(Value, "memory", translate("Memory"), translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."))
|
||||
d.placeholder = "128m"
|
||||
d.rmempty = true
|
||||
d.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0
|
||||
|
||||
d = editsection:option(Value, "blkioweight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
|
||||
d.placeholder = "500"
|
||||
d.rmempty = true
|
||||
d.datatype="uinteger"
|
||||
d.default = container_info.HostConfig.BlkioWeight
|
||||
|
||||
m.handle = function(self, state, data)
|
||||
if state == FORM_VALID then
|
||||
local memory = data.memory
|
||||
if memory and memory ~= 0 then
|
||||
_,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
|
||||
if n then
|
||||
unit = unit and unit:sub(1,1):upper() or "B"
|
||||
if unit == "M" then
|
||||
memory = tonumber(n) * 1024 * 1024
|
||||
elseif unit == "G" then
|
||||
memory = tonumber(n) * 1024 * 1024 * 1024
|
||||
elseif unit == "K" then
|
||||
memory = tonumber(n) * 1024
|
||||
else
|
||||
memory = tonumber(n)
|
||||
end
|
||||
end
|
||||
end
|
||||
request_body = {
|
||||
BlkioWeight = tonumber(data.blkioweight),
|
||||
NanoCPUs = tonumber(data.cpus)*10^9,
|
||||
Memory = tonumber(memory),
|
||||
CpuShares = tonumber(data.cpushares)
|
||||
}
|
||||
docker:clear_status()
|
||||
docker:append_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("fail 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/services/docker/container/"..container_id.."/edit"))
|
||||
end
|
||||
end
|
||||
elseif action == "file" then
|
||||
local filesection= m:section(SimpleSection)
|
||||
m.submit = false
|
||||
m.reset = false
|
||||
filesection.template = "dockerman/container_file"
|
||||
filesection.container = container_id
|
||||
elseif action == "logs" then
|
||||
local logsection= m:section(SimpleSection)
|
||||
local logs = ""
|
||||
local query ={
|
||||
stdout = 1,
|
||||
stderr = 1,
|
||||
tail = 1000
|
||||
}
|
||||
local logs = dk.containers:logs({id = container_id, query = query})
|
||||
if logs.code == 200 then
|
||||
logsection.syslog=logs.body
|
||||
else
|
||||
logsection.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body
|
||||
end
|
||||
logsection.title=translate("Container Logs")
|
||||
logsection.template = "dockerman/logs"
|
||||
m.submit = false
|
||||
m.reset = false
|
||||
elseif action == "stats" then
|
||||
local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}})
|
||||
local container_top
|
||||
if response.code == 200 then
|
||||
container_top=response.body
|
||||
else
|
||||
response = dk.containers:top({id = container_id})
|
||||
if response.code == 200 then
|
||||
container_top=response.body
|
||||
end
|
||||
end
|
||||
|
||||
if type(container_top) == "table" then
|
||||
container_top=response.body
|
||||
stat_section = m:section(SimpleSection)
|
||||
stat_section.container_id = container_id
|
||||
stat_section.template = "dockerman/stats"
|
||||
table_stats = {cpu={key=translate("CPU Useage"),value='-'},memory={key=translate("Memory Useage"),value='-'}}
|
||||
stat_section = m:section(Table, table_stats, translate("Stats"))
|
||||
stat_section:option(DummyValue, "key", translate("Stats")).width="33%"
|
||||
|
||||
stat_section:option(DummyValue, "value")
|
||||
top_section= m:section(Table, container_top.Processes, translate("TOP"))
|
||||
for i, v in ipairs(container_top.Titles) do
|
||||
top_section:option(DummyValue, i, translate(v))
|
||||
end
|
||||
end
|
||||
m.submit = false
|
||||
m.reset = false
|
||||
end
|
||||
|
||||
|
||||
return m
|
@ -1,197 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
local http = require "luci.http"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
|
||||
local images, networks, containers
|
||||
local res = dk.images:list()
|
||||
if res.code <300 then images = res.body else return end
|
||||
res = dk.networks:list()
|
||||
if res.code <300 then networks = res.body else return end
|
||||
res = dk.containers:list({query = {all=true}})
|
||||
if res.code <300 then containers = res.body else return end
|
||||
|
||||
local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode
|
||||
|
||||
function get_containers()
|
||||
local data = {}
|
||||
if type(containers) ~= "table" then return nil end
|
||||
for i, v in ipairs(containers) do
|
||||
local index = v.Created .. v.Id
|
||||
data[index]={}
|
||||
data[index]["_selected"] = 0
|
||||
data[index]["_id"] = v.Id:sub(1,12)
|
||||
data[index]["_name"] = v.Names[1]:sub(2)
|
||||
data[index]["_status"] = v.Status
|
||||
if v.Status:find("^Up") then
|
||||
data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>"
|
||||
else
|
||||
data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>"
|
||||
end
|
||||
if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then
|
||||
for networkname, netconfig in pairs(v.NetworkSettings.Networks) do
|
||||
data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "")
|
||||
end
|
||||
end
|
||||
-- networkmode = v.HostConfig.NetworkMode ~= "default" and v.HostConfig.NetworkMode or "bridge"
|
||||
-- data[index]["_network"] = v.NetworkSettings.Networks[networkmode].IPAddress or nil
|
||||
-- local _, _, image = v.Image:find("^sha256:(.+)")
|
||||
-- if image ~= nil then
|
||||
-- image=image:sub(1,12)
|
||||
-- end
|
||||
|
||||
if v.Ports then
|
||||
data[index]["_ports"] = nil
|
||||
for _,v2 in ipairs(v.Ports) do
|
||||
data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "") .. (v2.PublicPort and (v2.PublicPort .. ":") or "") .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "")
|
||||
end
|
||||
end
|
||||
for ii,iv in ipairs(images) do
|
||||
if iv.Id == v.ImageID then
|
||||
data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":none")
|
||||
end
|
||||
end
|
||||
|
||||
data[index]["_image_id"] = v.ImageID:sub(8,20)
|
||||
data[index]["_command"] = v.Command
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
local c_lists = get_containers()
|
||||
-- list Containers
|
||||
-- m = Map("docker", translate("Docker"))
|
||||
m = SimpleForm("docker", translate("Docker"))
|
||||
m.template = "dockerman/cbi/xsimpleform"
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
-- luci.util.perror(docker_status.err)
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
c_table = m:section(Table, c_lists, translate("Containers"))
|
||||
c_table.nodescr=true
|
||||
-- v.template = "cbi/tblsection"
|
||||
-- v.sortable = true
|
||||
container_selecter = c_table:option(Flag, "_selected","")
|
||||
container_selecter.disabled = 0
|
||||
container_selecter.enabled = 1
|
||||
container_selecter.default = 0
|
||||
|
||||
container_id = c_table:option(DummyValue, "_id", translate("ID"))
|
||||
container_id.width="10%"
|
||||
container_name = c_table:option(DummyValue, "_name", translate("Container Name"))
|
||||
container_name.width="20%"
|
||||
container_name.template = "dockerman/cbi/dummyvalue"
|
||||
container_name.href = function (self, section)
|
||||
return luci.dispatcher.build_url("admin/services/docker/container/" .. urlencode(container_id:cfgvalue(section)))
|
||||
end
|
||||
container_status = c_table:option(DummyValue, "_status", translate("Status"))
|
||||
container_status.width="15%"
|
||||
container_status.rawhtml=true
|
||||
container_ip = c_table:option(DummyValue, "_network", translate("Network"))
|
||||
container_ip.width="15%"
|
||||
container_ports = c_table:option(DummyValue, "_ports", translate("Ports"))
|
||||
container_ports.width="10%"
|
||||
container_image = c_table:option(DummyValue, "_image", translate("Image"))
|
||||
container_image.template = "dockerman/cbi/dummyvalue"
|
||||
container_image.width="10%"
|
||||
-- container_image.href = function (self, section)
|
||||
-- return luci.dispatcher.build_url("admin/services/docker/image/" .. urlencode(c_lists[section]._image_id))
|
||||
-- end
|
||||
container_command = c_table:option(DummyValue, "_command", translate("Command"))
|
||||
container_command.width="20%"
|
||||
|
||||
container_selecter.write=function(self, section, value)
|
||||
c_lists[section]._selected = value
|
||||
end
|
||||
|
||||
local start_stop_remove = function(m,cmd)
|
||||
-- luci.template.render("admin_uci/apply", {
|
||||
-- changes = next(changes) and changes,
|
||||
-- configs = reload
|
||||
-- })
|
||||
|
||||
local c_selected = {}
|
||||
-- 遍历table中sectionid
|
||||
local c_table_sids = c_table:cfgsections()
|
||||
for _, c_table_sid in ipairs(c_table_sids) do
|
||||
-- 得到选中项的名字
|
||||
if c_lists[c_table_sid]._selected == 1 then
|
||||
c_selected[#c_selected+1] = container_name:cfgvalue(c_table_sid)
|
||||
end
|
||||
end
|
||||
if #c_selected >0 then
|
||||
docker:clear_status()
|
||||
local success = true
|
||||
for _,cont in ipairs(c_selected) do
|
||||
docker:append_status("Containers: " .. cmd .. " " .. cont .. "...")
|
||||
local res = dk.containers[cmd](dk, {id = cont})
|
||||
if res and res.code >= 300 then
|
||||
success = false
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
else
|
||||
docker:append_status("done<br>")
|
||||
end
|
||||
end
|
||||
if success then docker:clear_status() end
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/containers"))
|
||||
end
|
||||
end
|
||||
|
||||
action_section = m:section(Table,{{}})
|
||||
action_section.notitle=true
|
||||
action_section.rowcolors=false
|
||||
action_section.template="cbi/nullsection"
|
||||
|
||||
btnnew=action_section:option(Button, "_new")
|
||||
btnnew.inputtitle= translate("New")
|
||||
btnnew.template = "dockerman/cbi/inlinebutton"
|
||||
btnnew.inputstyle = "add"
|
||||
btnnew.forcewrite = true
|
||||
btnstart=action_section:option(Button, "_start")
|
||||
btnstart.template = "dockerman/cbi/inlinebutton"
|
||||
btnstart.inputtitle=translate("Start")
|
||||
btnstart.inputstyle = "apply"
|
||||
btnstart.forcewrite = true
|
||||
btnrestart=action_section:option(Button, "_restart")
|
||||
btnrestart.template = "dockerman/cbi/inlinebutton"
|
||||
btnrestart.inputtitle=translate("Restart")
|
||||
btnrestart.inputstyle = "reload"
|
||||
btnrestart.forcewrite = true
|
||||
btnstop=action_section:option(Button, "_stop")
|
||||
btnstop.template = "dockerman/cbi/inlinebutton"
|
||||
btnstop.inputtitle=translate("Stop")
|
||||
btnstop.inputstyle = "reset"
|
||||
btnstop.forcewrite = true
|
||||
btnremove=action_section:option(Button, "_remove")
|
||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
||||
btnremove.inputtitle=translate("Remove")
|
||||
btnremove.inputstyle = "remove"
|
||||
btnremove.forcewrite = true
|
||||
btnnew.write = function(self, section)
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newcontainer"))
|
||||
end
|
||||
btnstart.write = function(self, section)
|
||||
start_stop_remove(m,"start")
|
||||
end
|
||||
btnrestart.write = function(self, section)
|
||||
start_stop_remove(m,"restart")
|
||||
end
|
||||
btnremove.write = function(self, section)
|
||||
start_stop_remove(m,"remove")
|
||||
end
|
||||
btnstop.write = function(self, section)
|
||||
start_stop_remove(m,"stop")
|
||||
end
|
||||
|
||||
return m
|
@ -1,176 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
|
||||
local containers, images
|
||||
local res = dk.images:list()
|
||||
if res.code <300 then images = res.body else return end
|
||||
res = dk.containers:list({query = {all=true}})
|
||||
if res.code <300 then containers = res.body else return end
|
||||
|
||||
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,20)
|
||||
if v.RepoTags then
|
||||
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 "")..
|
||||
"<a href=/cgi-bin/luci/admin/services/docker/container/"..cv.Id.." >".. cv.Names[1]:sub(2).."</a>"
|
||||
end
|
||||
end
|
||||
data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
|
||||
data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created)
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
local image_list = get_images()
|
||||
|
||||
-- m = Map("docker", translate("Docker"))
|
||||
m = SimpleForm("docker", translate("Docker"))
|
||||
m.template = "dockerman/cbi/xsimpleform"
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
|
||||
local pull_value={{_image_tag_name="", _registry="index.docker.io"}}
|
||||
local pull_section = m:section(Table,pull_value, translate("Pull Image"))
|
||||
pull_section.template="cbi/nullsection"
|
||||
local tag_name = pull_section:option(Value, "_image_tag_name")
|
||||
tag_name.template = "dockerman/cbi/inlinevalue"
|
||||
tag_name.placeholder="hello-world:latest"
|
||||
local registry = pull_section:option(Value, "_registry")
|
||||
registry.template = "dockerman/cbi/inlinevalue"
|
||||
registry:value("index.docker.io", "Docker Hub")
|
||||
registry:value("hub-mirror.c.163.com", "163 Mirror")
|
||||
registry:value("mirror.ccs.tencentyun.com", "Tencent Mirror")
|
||||
registry:value("docker.mirrors.ustc.edu.cn", "USTC Mirror")
|
||||
local action_pull = pull_section:option(Button, "_pull")
|
||||
action_pull.inputtitle= translate("Pull")
|
||||
action_pull.template = "dockerman/cbi/inlinebutton"
|
||||
action_pull.inputstyle = "add"
|
||||
tag_name.write = function(self, section,value)
|
||||
local hastag = value:find(":")
|
||||
if not hastag then
|
||||
value = value .. ":latest"
|
||||
end
|
||||
pull_value[section]["_image_tag_name"] = value
|
||||
end
|
||||
registry.write = function(self, section,value)
|
||||
pull_value[section]["_registry"] = value
|
||||
end
|
||||
action_pull.write = function(self, section)
|
||||
local tag = pull_value[section]["_image_tag_name"]
|
||||
local server = pull_value[section]["_registry"]
|
||||
--去掉协议前缀和后缀
|
||||
local _,_,tmp = server:find(".-://([%.%w%-%_]+)")
|
||||
if not tmp then
|
||||
_,_,server = server:find("([%.%w%-%_]+)")
|
||||
end
|
||||
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
||||
if tag and tag ~= "" then
|
||||
docker:clear_status()
|
||||
docker:append_status("Images: " .. "pulling" .. " " .. tag .. "...")
|
||||
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
|
||||
local res = dk.images:create({query = {fromImage=tag}, header={["X-Registry-Auth"] = x_auth}})
|
||||
if res and res.code >=300 then
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
else
|
||||
docker:append_status("done<br>")
|
||||
end
|
||||
else
|
||||
docker:append_status("fail code: 400 please input the name of image name!")
|
||||
end
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/images"))
|
||||
end
|
||||
|
||||
image_table = m:section(Table, image_list, translate("Images"))
|
||||
|
||||
image_selecter = image_table:option(Flag, "_selected","")
|
||||
image_selecter.disabled = 0
|
||||
image_selecter.enabled = 1
|
||||
image_selecter.default = 0
|
||||
|
||||
image_id = image_table:option(DummyValue, "_id", translate("ID"))
|
||||
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
|
||||
|
||||
local remove_action = function(force)
|
||||
local image_selected = {}
|
||||
-- 遍历table中sectionid
|
||||
local image_table_sids = image_table:cfgsections()
|
||||
for _, image_table_sid in ipairs(image_table_sids) do
|
||||
-- 得到选中项的名字
|
||||
if image_list[image_table_sid]._selected == 1 then
|
||||
image_selected[#image_selected+1] = image_id:cfgvalue(image_table_sid)
|
||||
end
|
||||
end
|
||||
if next(image_selected) ~= nil then
|
||||
local success = true
|
||||
docker:clear_status()
|
||||
for _,img in ipairs(image_selected) do
|
||||
docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
|
||||
local query
|
||||
if force then query = {force = true} end
|
||||
local msg = dk.images:remove({id = img, query = query})
|
||||
if msg.code ~= 200 then
|
||||
docker:append_status("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/services/docker/images"))
|
||||
end
|
||||
end
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
action = m:section(Table,{{}})
|
||||
action.notitle=true
|
||||
action.rowcolors=false
|
||||
action.template="cbi/nullsection"
|
||||
|
||||
btnremove = action:option(Button, "remove")
|
||||
btnremove.inputtitle= translate("Remove")
|
||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
||||
btnremove.inputstyle = "remove"
|
||||
btnremove.forcewrite = true
|
||||
btnremove.write = function(self, section)
|
||||
remove_action()
|
||||
end
|
||||
|
||||
btnforceremove = action:option(Button, "forceremove")
|
||||
btnforceremove.inputtitle= translate("Force Remove")
|
||||
btnforceremove.template = "dockerman/cbi/inlinebutton"
|
||||
btnforceremove.inputstyle = "remove"
|
||||
btnforceremove.forcewrite = true
|
||||
btnforceremove.write = function(self, section)
|
||||
remove_action(true)
|
||||
end
|
||||
return m
|
@ -1,123 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
local networks
|
||||
local res = dk.networks:list()
|
||||
if res.code < 300 then networks = res.body else return end
|
||||
|
||||
local 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 = get_networks()
|
||||
-- m = Map("docker", translate("Docker"))
|
||||
m = SimpleForm("docker", translate("Docker"))
|
||||
m.template = "dockerman/cbi/xsimpleform"
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
|
||||
network_table = m:section(Table, network_list, translate("Networks"))
|
||||
network_table.nodescr=true
|
||||
|
||||
network_selecter = network_table:option(Flag, "_selected","")
|
||||
network_selecter.template = "dockerman/cbi/xfvalue"
|
||||
network_id = network_table:option(DummyValue, "_id", translate("ID"))
|
||||
network_selecter.disabled = 0
|
||||
network_selecter.enabled = 1
|
||||
network_selecter.default = 0
|
||||
network_selecter.render = function(self, section, scope)
|
||||
self.disable = 0
|
||||
if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then
|
||||
self.disable = 1
|
||||
end
|
||||
Flag.render(self, section, scope)
|
||||
end
|
||||
|
||||
network_name = network_table:option(DummyValue, "_name", translate("Network Name"))
|
||||
network_driver = network_table:option(DummyValue, "_driver", translate("Driver"))
|
||||
network_interface = network_table:option(DummyValue, "_interface", translate("Parent Interface"))
|
||||
network_subnet = network_table:option(DummyValue, "_subnet", translate("Subnet"))
|
||||
network_gateway = network_table:option(DummyValue, "_gateway", translate("Gateway"))
|
||||
|
||||
network_selecter.write = function(self, section, value)
|
||||
network_list[section]._selected = value
|
||||
end
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
action = m:section(Table,{{}})
|
||||
action.notitle=true
|
||||
action.rowcolors=false
|
||||
action.template="cbi/nullsection"
|
||||
btnnew=action:option(Button, "_new")
|
||||
btnnew.inputtitle= translate("New")
|
||||
btnnew.template = "dockerman/cbi/inlinebutton"
|
||||
btnnew.notitle=true
|
||||
btnnew.inputstyle = "add"
|
||||
btnnew.forcewrite = true
|
||||
btnnew.write = function(self, section)
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newnetwork"))
|
||||
end
|
||||
btnremove = action:option(Button, "_remove")
|
||||
btnremove.inputtitle= translate("Remove")
|
||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
||||
btnremove.inputstyle = "remove"
|
||||
btnremove.forcewrite = true
|
||||
btnremove.write = function(self, section)
|
||||
local network_selected = {}
|
||||
-- 遍历table中sectionid
|
||||
local network_table_sids = network_table:cfgsections()
|
||||
for _, network_table_sid in ipairs(network_table_sids) do
|
||||
-- 得到选中项的名字
|
||||
if network_list[network_table_sid]._selected == 1 then
|
||||
network_selected[#network_selected+1] = network_name:cfgvalue(network_table_sid)
|
||||
end
|
||||
end
|
||||
if next(network_selected) ~= nil then
|
||||
local success = true
|
||||
docker:clear_status()
|
||||
for _,net in ipairs(network_selected) do
|
||||
docker:append_status("Networks: " .. "remove" .. " " .. net .. "...")
|
||||
local res = dk.networks["remove"](dk, {id = net})
|
||||
if res and res.code >= 300 then
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.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/services/docker/networks"))
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
@ -1,579 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
require "math"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
local cmd_line = table.concat(arg, '/')
|
||||
local create_body = {}
|
||||
|
||||
local images = dk.images:list().body
|
||||
local networks = dk.networks:list().body
|
||||
local containers = dk.containers:list({query = {all=true}}).body
|
||||
|
||||
local is_quot_complete = function(str)
|
||||
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
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- reslvo default config
|
||||
local default_config = { }
|
||||
if cmd_line and cmd_line:match("^docker.+") then
|
||||
local key = nil, _key
|
||||
--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
|
||||
elseif _key then
|
||||
-- there is a value that unpair quotation marks:
|
||||
-- "i was a ok man"
|
||||
-- now we only get: "i
|
||||
if _key == "mount" or _key == "link" or _key == "env" or _key == "dns" or _key == "port" or _key == "device" or _key == "tmpfs" then
|
||||
default_config[_key][#default_config[_key]] = default_config[_key][#default_config[_key]] .. " " .. w
|
||||
if is_quot_complete(default_config[_key][#default_config[_key]]) then
|
||||
-- clear quotation marks
|
||||
default_config[_key][#default_config[_key]] = default_config[_key][#default_config[_key]]:gsub("[\"\']", "")
|
||||
_key = nil
|
||||
end
|
||||
else
|
||||
default_config[_key] = default_config[_key] .. " ".. w
|
||||
if is_quot_complete(default_config[_key]) then
|
||||
-- clear quotation marks
|
||||
default_config[_key] = default_config[_key]:gsub("[\"\']", "")
|
||||
_key = nil
|
||||
end
|
||||
end
|
||||
-- start with '-'
|
||||
elseif w:match("^%-+.+") and cursor <= 1 then
|
||||
--key=value
|
||||
local val
|
||||
key, val = w:match("^%-+(.-)=(.+)")
|
||||
-- -dit
|
||||
if not key then key = w:match("^%-+(.+)") end
|
||||
|
||||
if not key then
|
||||
key = w:match("^%-(.+)")
|
||||
if key:match("i") or key:match("t") or key:match("d") then
|
||||
if key:match("i") then default_config["interactive"] = true end
|
||||
if key:match("t") then default_config["tty"] = true end
|
||||
-- clear key
|
||||
key = nil
|
||||
end
|
||||
end
|
||||
|
||||
if key == "v" or key == "volume" then
|
||||
key = "mount"
|
||||
elseif key == "p" or key == "publish" then
|
||||
key = "port"
|
||||
elseif key == "e" then
|
||||
key = "env"
|
||||
elseif key == "dns" then
|
||||
key = "dns"
|
||||
elseif key == "net" then
|
||||
key = "network"
|
||||
elseif key == "h" or key == "hostname" then
|
||||
key = "hostname"
|
||||
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"] = true
|
||||
key = nil
|
||||
elseif key == "cap-add" then
|
||||
default_config["privileged"] = true
|
||||
end
|
||||
--key=value
|
||||
if val then
|
||||
if key == "mount" or key == "link" or key == "env" or key == "dns" 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], val )
|
||||
-- clear quotation marks
|
||||
default_config[key][#default_config[key]] = default_config[key][#default_config[key]]:gsub("[\"\']", "")
|
||||
else
|
||||
default_config[key] = val
|
||||
-- clear quotation marks
|
||||
default_config[key] = default_config[key]:gsub("[\"\']", "")
|
||||
end
|
||||
-- if there are " or ' in val and separate by space, we need keep the _key to link with next w
|
||||
if is_quot_complete(val) then
|
||||
_key = nil
|
||||
else
|
||||
_key = key
|
||||
end
|
||||
-- clear key
|
||||
key = nil
|
||||
end
|
||||
cursor = 1
|
||||
-- value
|
||||
elseif key and type(key) == "string" and cursor == 1 then
|
||||
if key == "mount" or key == "link" or key == "env" or key == "dns" 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 )
|
||||
-- clear quotation marks
|
||||
default_config[key][#default_config[key]] = default_config[key][#default_config[key]]:gsub("[\"\']", "")
|
||||
else
|
||||
default_config[key] = w
|
||||
-- clear quotation marks
|
||||
default_config[key] = default_config[key]:gsub("[\"\']", "")
|
||||
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
|
||||
-- if there are " or ' in val and separate by space, we need keep the _key to link with next w
|
||||
if is_quot_complete(w) then
|
||||
_key = nil
|
||||
else
|
||||
_key = key
|
||||
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({id = 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.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 = next(create_body.NetworkingConfig.EndpointsConfig)
|
||||
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.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.template = "dockerman/cbi/xsimpleform"
|
||||
m.redirect = luci.dispatcher.build_url("admin", "services","docker", "containers")
|
||||
-- m.reset = false
|
||||
-- m.submit = false
|
||||
-- new Container
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
local s = m:section(SimpleSection, translate("New Container"))
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
|
||||
local d = s:option(DummyValue,"cmd_line", translate("Resolv CLI"))
|
||||
d.rawhtml = true
|
||||
d.template = "dockerman/resolv_container"
|
||||
|
||||
d = s:option(Value, "name", translate("Container Name"))
|
||||
d.rmempty = true
|
||||
d.default = default_config.name or nil
|
||||
|
||||
d = s:option(Flag, "interactive", translate("Interactive (-i)"))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = default_config.interactive and 1 or 0
|
||||
|
||||
d = s:option(Flag, "tty", translate("TTY (-t)"))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = default_config.tty and 1 or 0
|
||||
|
||||
d = s:option(Value, "image", translate("Docker Image"))
|
||||
d.rmempty = true
|
||||
d.default = default_config.image or nil
|
||||
for _, v in ipairs (images) do
|
||||
if v.RepoTags then
|
||||
d:value(v.RepoTags[1], v.RepoTags[1])
|
||||
end
|
||||
end
|
||||
|
||||
d = s:option(Flag, "_force_pull", translate("Always pull image first"))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = 0
|
||||
|
||||
d = s:option(Flag, "privileged", translate("Privileged"))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = default_config.privileged and 1 or 0
|
||||
|
||||
d = s:option(ListValue, "restart", translate("Restart Policy"))
|
||||
d.rmempty = true
|
||||
|
||||
d:value("no", "No")
|
||||
d:value("unless-stopped", "Unless stopped")
|
||||
d:value("always", "Always")
|
||||
d:value("on-failure", "On failure")
|
||||
d.default = default_config.restart or "unless-stopped"
|
||||
|
||||
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(DynamicList, "link", translate("Links with other containers"))
|
||||
d.template = "dockerman/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, "dns", translate("Set custom DNS servers"))
|
||||
d.template = "dockerman/cbi/xdynlist"
|
||||
d.placeholder = "8.8.8.8"
|
||||
d.rmempty = true
|
||||
d.default = default_config.dns or nil
|
||||
|
||||
d = s:option(Value, "user", translate("User(-u)"), translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])"))
|
||||
d.placeholder = "1000:1000"
|
||||
d.rmempty = true
|
||||
d.default = default_config.user or nil
|
||||
|
||||
d = s:option(DynamicList, "env", translate("Environmental Variable(-e)"), translate("Set environment variables to inside the container"))
|
||||
d.template = "dockerman/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(-v)"), translate("Bind mount a volume"))
|
||||
d.template = "dockerman/cbi/xdynlist"
|
||||
d.placeholder = "/media:/media:slave"
|
||||
d.rmempty = true
|
||||
d.default = default_config.mount or nil
|
||||
|
||||
local d_ports = s:option(DynamicList, "port", translate("Exposed Ports(-p)"), translate("Publish container's port(s) to the host"))
|
||||
d_ports.template = "dockerman/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, "hostname", translate("Host Name"), translate("The hostname to use for the container"))
|
||||
d.rmempty = true
|
||||
d.default = default_config.hostname or nil
|
||||
d:depends("advance", 1)
|
||||
|
||||
d = s:option(DynamicList, "device", translate("Device(--device)"), translate("Add host device to the container"))
|
||||
d.template = "dockerman/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(--tmpfs)"), translate("Mount tmpfs directory"))
|
||||
d.template = "dockerman/cbi/xdynlist"
|
||||
d.placeholder = "/run:rw,noexec,nosuid,size=65536k"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.default = default_config.tmpfs or nil
|
||||
|
||||
d = s:option(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
|
||||
|
||||
|
||||
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 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 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 network = data.network
|
||||
local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
|
||||
local mount = data.mount
|
||||
local memory = data.memory or 0
|
||||
local cpushares = data.cpushares or 0
|
||||
local cpus = data.cpus or 0
|
||||
local blkioweight = data.blkioweight or 500
|
||||
|
||||
local portbindings = {}
|
||||
local exposedports = {}
|
||||
local tmpfs = {}
|
||||
tmp = data.tmpfs
|
||||
if type(tmp) == "table" then
|
||||
for i, v in ipairs(tmp)do
|
||||
local _,_, k,v1 = v:find("(.-):(.+)")
|
||||
if k and v1 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"
|
||||
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+)")
|
||||
if p == nil then
|
||||
v2=v2..'/tcp'
|
||||
end
|
||||
portbindings[v2] = {{HostPort=v1}}
|
||||
exposedports[v2] = {HostPort=v1}
|
||||
end
|
||||
end
|
||||
|
||||
local link = data.link
|
||||
tmp = data.command
|
||||
local command = {}
|
||||
if tmp ~= nil then
|
||||
for v in string.gmatch(tmp, "[^%s]+") do
|
||||
command[#command+1] = v
|
||||
end
|
||||
end
|
||||
if memory ~= 0 then
|
||||
_,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
|
||||
if n then
|
||||
unit = unit and unit:sub(1,1):upper() or "B"
|
||||
if unit == "M" then
|
||||
memory = tonumber(n) * 1024 * 1024
|
||||
elseif unit == "G" then
|
||||
memory = tonumber(n) * 1024 * 1024 * 1024
|
||||
elseif unit == "K" then
|
||||
memory = tonumber(n) * 1024
|
||||
else
|
||||
memory = tonumber(n)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
create_body.Hostname = network ~= "host" and (hostname or name) or nil
|
||||
create_body.Tty = tty and true or false
|
||||
create_body.OpenStdin = interactive and true or false
|
||||
create_body.User = user
|
||||
create_body.Cmd = (#command ~= 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.Dns = dns
|
||||
create_body.HostConfig.Binds = (#mount ~= 0) and mount or nil
|
||||
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 create_body.HostConfig.NetworkMode ~= network then
|
||||
-- network mode changed, need to clear duplicate config
|
||||
create_body.NetworkingConfig = nil
|
||||
end
|
||||
create_body.HostConfig.NetworkMode = network
|
||||
if ip then
|
||||
if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then
|
||||
-- ip + duplicate config
|
||||
for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do
|
||||
if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then
|
||||
v.IPAMConfig.IPv4Address = ip
|
||||
else
|
||||
create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } }
|
||||
end
|
||||
break
|
||||
end
|
||||
else
|
||||
-- ip + no duplicate config
|
||||
create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } }
|
||||
end
|
||||
elseif not create_body.NetworkingConfig then
|
||||
-- no ip + no duplicate config
|
||||
create_body.NetworkingConfig = nil
|
||||
end
|
||||
|
||||
create_body["HostConfig"]["Tmpfs"] = (next(tmpfs) ~= nil) and tmpfs or nil
|
||||
create_body["HostConfig"]["Devices"] = (next(device) ~= nil) and device or nil
|
||||
|
||||
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.jsonc and luci.jsonc.stringify
|
||||
docker:append_status("Images: " .. "pulling" .. " " .. image .. "...")
|
||||
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
|
||||
local res = dk.images:create({query = {fromImage=image}, header={["X-Registry-Auth"]=x_auth}})
|
||||
if res and res.code == 200 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/services/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
|
||||
|
||||
docker:append_status("Container: " .. "create" .. " " .. name .. "...")
|
||||
local res = dk.containers:create({name = name, body = create_body})
|
||||
if res and res.code == 201 then
|
||||
docker:clear_status()
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/containers"))
|
||||
else
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newcontainer"))
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
@ -1,205 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
|
||||
m = SimpleForm("docker", translate("Docker"))
|
||||
m.template = "dockerman/cbi/xsimpleform"
|
||||
m.redirect = luci.dispatcher.build_url("admin", "services","docker", "networks")
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
s = m:section(SimpleSection, translate("New Network"))
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
|
||||
d = s:option(Value, "name", translate("Network Name"))
|
||||
d.rmempty = true
|
||||
|
||||
d = s:option(ListValue, "dirver", translate("Driver"))
|
||||
d.rmempty = true
|
||||
d:value("bridge", "bridge")
|
||||
d:value("macvlan", "macvlan")
|
||||
d:value("ipvlan", "ipvlan")
|
||||
d:value("overlay", "overlay")
|
||||
|
||||
d = s:option(Value, "parent", translate("Parent Interface"))
|
||||
d.rmempty = true
|
||||
d:depends("dirver", "macvlan")
|
||||
d.placeholder="eth0"
|
||||
|
||||
d = s:option(Value, "macvlan_mode", translate("Macvlan Mode"))
|
||||
d.rmempty = true
|
||||
d:depends("dirver", "macvlan")
|
||||
d.default="bridge"
|
||||
d:value("bridge", "bridge")
|
||||
d:value("private", "private")
|
||||
d:value("vepa", "vepa")
|
||||
d:value("passthru", "passthru")
|
||||
|
||||
d = s:option(Value, "ipvlan_mode", translate("Ipvlan Mode"))
|
||||
d.rmempty = true
|
||||
d:depends("dirver", "ipvlan")
|
||||
d.default="l3"
|
||||
d:value("l2", "l2")
|
||||
d:value("l3", "l3")
|
||||
|
||||
d = s:option(Flag, "ingress", translate("Ingress"), translate("Ingress network is the network which provides the routing-mesh in swarm mode."))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = 0
|
||||
d:depends("dirver", "overlay")
|
||||
|
||||
d = s:option(DynamicList, "options", translate("Options"))
|
||||
d.template = "dockerman/cbi/xdynlist"
|
||||
d.rmempty = true
|
||||
d.placeholder="com.docker.network.driver.mtu=1500"
|
||||
|
||||
d = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network"))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = 0
|
||||
|
||||
d = s:option(Value, "subnet", translate("Subnet"))
|
||||
d.rmempty = true
|
||||
d.placeholder="10.1.0.0/16"
|
||||
d.datatype="ip4addr"
|
||||
|
||||
d = s:option(Value, "gateway", translate("Gateway"))
|
||||
d.rmempty = true
|
||||
d.placeholder="10.1.1.1"
|
||||
d.datatype="ip4addr"
|
||||
|
||||
d = s:option(Value, "ip_range", translate("IP range"))
|
||||
d.rmempty = true
|
||||
d.placeholder="10.1.1.0/24"
|
||||
d.datatype="ip4addr"
|
||||
|
||||
d = s:option(DynamicList, "aux_address", translate("Exclude IPs"))
|
||||
d.template = "dockerman/cbi/xdynlist"
|
||||
d.rmempty = true
|
||||
d.placeholder="my-route=10.1.1.1"
|
||||
|
||||
d = s:option(Flag, "ipv6", translate("Enable IPv6"))
|
||||
d.rmempty = true
|
||||
d.disabled = 0
|
||||
d.enabled = 1
|
||||
d.default = 0
|
||||
|
||||
d = s:option(Value, "subnet6", translate("IPv6 Subnet"))
|
||||
d.rmempty = true
|
||||
d.placeholder="fe80::/10"
|
||||
d.datatype="ip6addr"
|
||||
d:depends("ipv6", 1)
|
||||
|
||||
d = s:option(Value, "gateway6", translate("IPv6 Gateway"))
|
||||
d.rmempty = true
|
||||
d.placeholder="fe80::1"
|
||||
d.datatype="ip6addr"
|
||||
d:depends("ipv6", 1)
|
||||
|
||||
m.handle = function(self, state, data)
|
||||
if state == FORM_VALID then
|
||||
local name = data.name
|
||||
local driver = data.dirver
|
||||
|
||||
local internal = data.internal == 1 and true or false
|
||||
|
||||
local subnet = data.subnet
|
||||
local gateway = data.gateway
|
||||
local ip_range = data.ip_range
|
||||
|
||||
local aux_address = {}
|
||||
local tmp = data.aux_address or {}
|
||||
for i,v in ipairs(tmp) do
|
||||
_,_,k1,v1 = v:find("(.-)=(.+)")
|
||||
aux_address[k1] = v1
|
||||
end
|
||||
|
||||
local options = {}
|
||||
tmp = data.options or {}
|
||||
for i,v in ipairs(tmp) do
|
||||
_,_,k1,v1 = v:find("(.-)=(.+)")
|
||||
options[k1] = v1
|
||||
end
|
||||
|
||||
local ipv6 = data.ipv6 == 1 and true or false
|
||||
|
||||
local create_body={
|
||||
Name = name,
|
||||
Driver = driver,
|
||||
EnableIPv6 = ipv6,
|
||||
IPAM = {
|
||||
Driver= "default"
|
||||
},
|
||||
Internal = internal
|
||||
}
|
||||
|
||||
if subnet or gateway or ip_range then
|
||||
create_body["IPAM"]["Config"] = {
|
||||
{
|
||||
Subnet = subnet,
|
||||
Gateway = gateway,
|
||||
IPRange = ip_range,
|
||||
-- AuxAddress = aux_address
|
||||
-- AuxiliaryAddresses = aux_address
|
||||
}
|
||||
}
|
||||
end
|
||||
if next(aux_address)~=nil then
|
||||
create_body["IPAM"]["Config"]["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
|
||||
|
||||
docker:append_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
|
||||
local res = dk.networks:create({body = create_body})
|
||||
if res and res.code == 201 then
|
||||
docker:clear_status()
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/networks"))
|
||||
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/services/docker/newnetwork"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
@ -1,94 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
local docker = require "luci.model.docker"
|
||||
local uci = require "luci.model.uci"
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
local m = Map("dockerman", 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='-'}
|
||||
|
||||
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("dockerman", "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({query = {all=true}}).body
|
||||
local images_list = dk.images:list().body
|
||||
local vol = dk.volumes:list()
|
||||
local volumes_list = vol and vol.body and vol.body.Volumes or {}
|
||||
local networks_list = dk.networks:list().body or {}
|
||||
local docker_info = dk:info()
|
||||
-- docker_info_table['0OperatingSystem']._value = docker_info.body.OperatingSystem
|
||||
-- docker_info_table['1Architecture']._value = docker_info.body.Architecture
|
||||
-- docker_info_table['2KernelVersion']._value = docker_info.body.KernelVersion
|
||||
docker_info_table['3ServerVersion']._value = docker_info.body.ServerVersion
|
||||
docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"]
|
||||
docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU)
|
||||
docker_info_table['6MemTotal']._value = byte_format(docker_info.body.MemTotal)
|
||||
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(#images_list)
|
||||
s.networks_total = tostring(#networks_list)
|
||||
s.volumes_total = tostring(#volumes_list)
|
||||
end
|
||||
s.template = "dockerman/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"))
|
||||
wan_mode = s:option(Flag, "wan_mode", translate("Enable WAN access Dokcer"), translate("Enable WAN access docker mapped ports (need reload Docker-ce service)"))
|
||||
wan_mode.enabled="true"
|
||||
wan_mode.disabled="false"
|
||||
debug = s:option(Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path"))
|
||||
debug.enabled="true"
|
||||
debug.disabled="false"
|
||||
debug_path = s:option(Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile"))
|
||||
|
||||
return m
|
@ -1,116 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
|
||||
local containers, volumes
|
||||
local res = dk.volumes:list()
|
||||
if res.code <300 then volumes = res.body.Volumes else return end
|
||||
res = dk.containers:list({query = {all=true}})
|
||||
if res.code <300 then containers = res.body else return end
|
||||
|
||||
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/services/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.template = "dockerman/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 = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
action = m:section(Table,{{}})
|
||||
action.notitle=true
|
||||
action.rowcolors=false
|
||||
action.template="cbi/nullsection"
|
||||
btnremove = action:option(Button, "remove")
|
||||
btnremove.inputtitle= translate("Remove")
|
||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
||||
btnremove.inputstyle = "remove"
|
||||
btnremove.forcewrite = true
|
||||
btnremove.write = function(self, section)
|
||||
local volume_selected = {}
|
||||
-- 遍历table中sectionid
|
||||
local volume_table_sids = volume_table:cfgsections()
|
||||
for _, volume_table_sid in ipairs(volume_table_sids) do
|
||||
-- 得到选中项的名字
|
||||
if volume_list[volume_table_sid]._selected == 1 then
|
||||
-- volume_selected[#volume_selected+1] = volume_id:cfgvalue(volume_table_sid)
|
||||
volume_selected[#volume_selected+1] = volume_table_sid
|
||||
end
|
||||
end
|
||||
if next(volume_selected) ~= nil then
|
||||
local success = true
|
||||
docker:clear_status()
|
||||
for _,vol in ipairs(volume_selected) do
|
||||
docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...")
|
||||
local msg = dk.volumes["remove"](dk, {id = vol})
|
||||
if msg.code ~= 204 then
|
||||
docker:append_status("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/services/docker/volumes"))
|
||||
end
|
||||
end
|
||||
return m
|
@ -1,229 +0,0 @@
|
||||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
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.jsonc and 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({query = {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({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
|
||||
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
|
||||
if table_equal(config.ExposedPorts, image_config.ExposedPorts) then config.ExposedPorts = nil end
|
||||
config.Env = table_subtract(config.Env, image_config.Env)
|
||||
config.Labels = table_subtract(config.Labels, image_config.Labels)
|
||||
config.Volumes = map_subtract(config.Volumes, image_config.Volumes)
|
||||
-- subtract ports exposed in image from container
|
||||
if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then
|
||||
config.ExposedPorts = {}
|
||||
for p, v in pairs(old_host_config.PortBindings) do
|
||||
config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort }
|
||||
end
|
||||
end
|
||||
|
||||
-- handle network config, we need only one network, extras need to network connect action
|
||||
local network_setting = {}
|
||||
local multi_network = false
|
||||
local extra_network = {}
|
||||
for k, v in pairs(old_network_setting) do
|
||||
if multi_network then
|
||||
extra_network[k] = v
|
||||
else
|
||||
network_setting[k] = v
|
||||
end
|
||||
multi_network = true
|
||||
end
|
||||
|
||||
-- handle hostconfig
|
||||
local host_config = old_host_config
|
||||
if host_config.PortBindings and next(host_config.PortBindings) == nil then host_config.PortBindings = nil end
|
||||
host_config.LogConfig = nil
|
||||
|
||||
-- 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, request)
|
||||
_docker:clear_status()
|
||||
-- get image name, image id, container name, configuration information
|
||||
local container_info = self.containers:inspect({id = request.id})
|
||||
if container_info.code > 300 and type(container_info.body) == "table" then
|
||||
return container_info
|
||||
end
|
||||
local image_name = container_info.body.Config.Image
|
||||
if not image_name:match(".-:.+") then image_name = image_name .. ":latest" end
|
||||
local old_image_id = container_info.body.Image
|
||||
local container_name = container_info.body.Name:sub(2)
|
||||
local 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({name = 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({name = container_name, query = { 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({id = 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({name = container_name, body = 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({id = k, body = {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, 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 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({id = 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("dockerman", "local", "socket_path"),
|
||||
debug = option.debug or uci:get("dockerman", "local", "debug") == 'true' and true or false,
|
||||
debug_path = option.debug_path or uci:get("dockerman", "local", "debug_path")
|
||||
}
|
||||
local _new = docker.new(options)
|
||||
_new.options.status_path = uci:get("dockerman", "local", "status_path")
|
||||
_new.containers_upgrade = upgrade
|
||||
_new.containers_duplicate_config = duplicate_config
|
||||
return _new
|
||||
end
|
||||
_docker.options={}
|
||||
_docker.options.status_path = uci:get("dockerman", "local", "status_path")
|
||||
|
||||
_docker.append_status=function(self,val)
|
||||
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.clear_status=function(self)
|
||||
nixio.fs.remove(self.options.status_path)
|
||||
end
|
||||
|
||||
return _docker
|
@ -0,0 +1,22 @@
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(3, '<%=url([[admin]], [[services]], [[docker]], [[status]])%>', null,
|
||||
function(x, data) {
|
||||
var tb = document.getElementById('docker_status');
|
||||
if (data && tb) {
|
||||
if (data.running) {
|
||||
var links = '<em><b><font color=green>Docker CE <%:RUNNING%></font></b></em>';
|
||||
tb.innerHTML = links;
|
||||
} else {
|
||||
tb.innerHTML = '<em><b><font color=red>Docker CE <%:NOT RUNNING%></font></b></em>';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
|
||||
<fieldset class="cbi-section">
|
||||
<p id="docker_status">
|
||||
<em><%:Collecting data...%></em>
|
||||
</p>
|
||||
</fieldset>
|
@ -1,139 +0,0 @@
|
||||
<style type="text/css">
|
||||
#docker_apply_overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: none;
|
||||
z-index: 20000;
|
||||
}
|
||||
|
||||
#docker_apply_overlay .alert-message {
|
||||
position: relative;
|
||||
top: 10%;
|
||||
width: 60%;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
min-height: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#docker_apply_overlay .alert-message > h4,
|
||||
#docker_apply_overlay .alert-message > p,
|
||||
#docker_apply_overlay .alert-message > div {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
#docker_apply_overlay .alert-message > img {
|
||||
margin-right: 1em;
|
||||
flex-basis: 32px;
|
||||
}
|
||||
|
||||
body.apply-overlay-active {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
body.apply-overlay-active #docker_apply_overlay {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var xhr = new XHR(),
|
||||
uci_apply_rollback = <%=math.max(luci.config and luci.config.apply and luci.config.apply.rollback or 30, 30)%>,
|
||||
uci_apply_holdoff = <%=math.max(luci.config and luci.config.apply and luci.config.apply.holdoff or 4, 1)%>,
|
||||
uci_apply_timeout = <%=math.max(luci.config and luci.config.apply and luci.config.apply.timeout or 5, 1)%>,
|
||||
uci_apply_display = <%=math.max(luci.config and luci.config.apply and luci.config.apply.display or 1.5, 1)%>,
|
||||
was_xhr_poll_running = false;
|
||||
|
||||
function docker_status_message(type, content) {
|
||||
document.getElementById('docker_apply_overlay') || document.body.insertAdjacentHTML("beforeend",'<div id="docker_apply_overlay"><div class="alert-message"></div></div>')
|
||||
var overlay = document.getElementById('docker_apply_overlay')
|
||||
message = overlay.querySelector('.alert-message');
|
||||
|
||||
if (message && type) {
|
||||
if (!message.classList.contains(type)) {
|
||||
message.classList.remove('notice');
|
||||
message.classList.remove('warning');
|
||||
message.classList.add(type);
|
||||
}
|
||||
|
||||
if (content)
|
||||
message.innerHTML = content;
|
||||
|
||||
document.body.classList.add('apply-overlay-active');
|
||||
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
||||
if (!was_xhr_poll_running) {
|
||||
was_xhr_poll_running = XHR.running();
|
||||
XHR.halt();
|
||||
}
|
||||
}
|
||||
else {
|
||||
document.body.classList.remove('apply-overlay-active');
|
||||
|
||||
if (was_xhr_poll_running)
|
||||
XHR.run();
|
||||
}
|
||||
}
|
||||
var loading_msg="Loadding.."
|
||||
function uci_confirm_docker() {
|
||||
var tt;
|
||||
docker_status_message('notice');
|
||||
var call = function(r, resjson, duration) {
|
||||
if (r && r.status === 200 ) {
|
||||
var indicator = document.querySelector('.uci_change_indicator');
|
||||
if (indicator) indicator.style.display = 'none';
|
||||
docker_status_message('notice', '<%:Docker actions done.%>');
|
||||
window.clearTimeout(tt);
|
||||
return;
|
||||
}
|
||||
loading_msg = resjson?resjson.info:loading_msg
|
||||
// var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0);
|
||||
var delay =1000
|
||||
window.setTimeout(function() {
|
||||
xhr.get('<%=url("admin/services/docker/confirm")%>', null, call, uci_apply_timeout * 1000);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
var tick = function() {
|
||||
var now = Date.now();
|
||||
|
||||
docker_status_message('notice',
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> ' +
|
||||
loading_msg);
|
||||
|
||||
tt = window.setTimeout(tick, 200);
|
||||
ts = now;
|
||||
};
|
||||
tick();
|
||||
/* wait a few seconds for the settings to become effective */
|
||||
window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 , 1));
|
||||
}
|
||||
// document.getElementsByTagName("form")[0].addEventListener("submit", (e)=>{
|
||||
// uci_confirm_docker()
|
||||
// })
|
||||
|
||||
function fnSubmitForm(el){
|
||||
if (el.id != "cbid.table.1._new") {
|
||||
uci_confirm_docker()
|
||||
}
|
||||
}
|
||||
|
||||
<% if self.err then -%>
|
||||
docker_status_message('warning', `<%=self.err%>`);
|
||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
||||
docker_status_message()
|
||||
})
|
||||
<%- end %>
|
||||
|
||||
window.onload= function (){
|
||||
var buttons = document.querySelectorAll('input[type="submit"]');
|
||||
[].slice.call(buttons).forEach(function (el) {
|
||||
el.onclick = fnSubmitForm.bind(this, el);
|
||||
});
|
||||
}
|
||||
|
||||
//]]></script>
|
@ -1,13 +0,0 @@
|
||||
<%+cbi/valueheader%>
|
||||
<% if self.href then %><a href="<%=self:href(section)%>"><% end -%>
|
||||
<%
|
||||
local val = self:cfgvalue(section) or self.default or ""
|
||||
if not self.rawhtml then
|
||||
write(pcdata(val))
|
||||
else
|
||||
write(val)
|
||||
end
|
||||
%>
|
||||
<%- if self.href then %></a><%end%>
|
||||
<input type="hidden" id="<%=cbid%>" value="<%=pcdata(self:cfgvalue(section) or self.default or "")%>" />
|
||||
<%+cbi/valuefooter%>
|
@ -1,7 +0,0 @@
|
||||
<div style="display: inline-block;">
|
||||
<% if self:cfgvalue(section) ~= false then %>
|
||||
<input class="cbi-button cbi-button-<%=self.inputstyle or "button" %>" type="submit"" <% if self.disable then %>disabled <% end %><%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> />
|
||||
<% else %>
|
||||
-
|
||||
<% end %>
|
||||
</div>
|
@ -1,33 +0,0 @@
|
||||
<div style="display: inline-block;">
|
||||
<%- if self.title then -%>
|
||||
<label class="cbi-value-title"<%= attr("for", cbid) %>>
|
||||
<%- if self.titleref then -%><a title="<%=self.titledesc or translate('Go to relevant configuration page')%>" class="cbi-title-ref" href="<%=self.titleref%>"><%- end -%>
|
||||
<%-=self.title-%>
|
||||
<%- if self.titleref then -%></a><%- end -%>
|
||||
</label>
|
||||
<%- end -%>
|
||||
<%- if self.password then -%>
|
||||
<input type="password" style="position:absolute; left:-100000px" aria-hidden="true"<%=
|
||||
attr("name", "password." .. cbid)
|
||||
%> />
|
||||
<%- end -%>
|
||||
<input data-update="change"<%=
|
||||
attr("id", cbid) ..
|
||||
attr("name", cbid) ..
|
||||
attr("type", self.password and "password" or "text") ..
|
||||
attr("class", self.password and "cbi-input-password" or "cbi-input-text") ..
|
||||
attr("value", self:cfgvalue(section) or self.default) ..
|
||||
ifattr(self.password, "autocomplete", "new-password") ..
|
||||
ifattr(self.size, "size") ..
|
||||
ifattr(self.placeholder, "placeholder") ..
|
||||
ifattr(self.readonly, "readonly") ..
|
||||
ifattr(self.maxlength, "maxlength") ..
|
||||
ifattr(self.datatype, "data-type", self.datatype) ..
|
||||
ifattr(self.datatype, "data-optional", self.optional or self.rmempty) ..
|
||||
ifattr(self.combobox_manual, "data-manual", self.combobox_manual) ..
|
||||
ifattr(#self.keylist > 0, "data-choices", { self.keylist, self.vallist })
|
||||
%> />
|
||||
<%- if self.password then -%>
|
||||
<div class="cbi-button cbi-button-neutral" title="<%:Reveal/hide password%>" onclick="var e = this.previousElementSibling; e.type = (e.type === 'password') ? 'text' : 'password'">∗</div>
|
||||
<% end %>
|
||||
</div>
|
@ -1,27 +0,0 @@
|
||||
<%+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%>
|
@ -1,10 +0,0 @@
|
||||
<%+cbi/valueheader%>
|
||||
<input type="hidden" value="1"<%=
|
||||
attr("name", "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option)
|
||||
%> />
|
||||
<input class="cbi-input-checkbox" data-update="click change" type="checkbox" <% if self.disable == 1 then %>disabled <% end %><%=
|
||||
attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) ..
|
||||
ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked")
|
||||
%> />
|
||||
<label<%= attr("for", cbid)%>></label>
|
||||
<%+cbi/valuefooter%>
|
@ -1,89 +0,0 @@
|
||||
<%
|
||||
if not self.embedded then
|
||||
%><form method="post" enctype="multipart/form-data" action="<%=REQUEST_URI%>"<%=
|
||||
attr("data-strings", luci.util.serialize_json({
|
||||
label = {
|
||||
choose = translate('-- Please choose --'),
|
||||
custom = translate('-- custom --'),
|
||||
},
|
||||
path = {
|
||||
resource = resource,
|
||||
browser = url("admin/filebrowser")
|
||||
}
|
||||
}))
|
||||
%>>
|
||||
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
|
||||
<input type="hidden" name="token" value="<%=token%>" />
|
||||
<input type="hidden" name="cbi.submit" value="1" /><%
|
||||
end
|
||||
|
||||
%><div class="cbi-map" id="cbi-<%=self.config%>"><%
|
||||
|
||||
if self.title and #self.title > 0 then
|
||||
%><h2 name="content"><%=self.title%></h2><%
|
||||
end
|
||||
|
||||
if self.description and #self.description > 0 then
|
||||
%><div class="cbi-map-descr"><%=self.description%></div><%
|
||||
end
|
||||
|
||||
self:render_children()
|
||||
|
||||
%></div><%
|
||||
|
||||
if self.message then
|
||||
%><div class="alert-message notice"><%=self.message%></div><%
|
||||
end
|
||||
|
||||
if self.errmessage then
|
||||
%><div class="alert-message warning"><%=self.errmessage%></div><%
|
||||
end
|
||||
|
||||
if not self.embedded then
|
||||
if type(self.hidden) == "table" then
|
||||
local k, v
|
||||
for k, v in pairs(self.hidden) do
|
||||
%><input type="hidden" id="<%=k%>" name="<%=k%>" value="<%=pcdata(v)%>" /><%
|
||||
end
|
||||
end
|
||||
|
||||
local display_back = (self.redirect)
|
||||
local display_cancel = (self.cancel ~= false and self.on_cancel)
|
||||
local display_skip = (self.flow and self.flow.skip)
|
||||
local display_submit = (self.submit ~= false)
|
||||
local display_reset = (self.reset ~= false)
|
||||
|
||||
if display_back or display_cancel or display_skip or display_submit or display_reset then
|
||||
%><div class="cbi-page-actions"><%
|
||||
|
||||
if display_back then
|
||||
%><input class="cbi-button cbi-button-link" type="button" value="<%:Back to Overview%>" onclick="location.href='<%=pcdata(self.redirect)%>'" /> <%
|
||||
end
|
||||
|
||||
if display_cancel then
|
||||
local label = pcdata(self.cancel or translate("Cancel"))
|
||||
%><input class="cbi-button cbi-button-link" type="button" value="<%=label%>" onclick="cbi_submit(this, 'cbi.cancel')" /> <%
|
||||
end
|
||||
|
||||
if display_skip then
|
||||
%><input class="cbi-button cbi-button-neutral" type="button" value="<%:Skip%>" onclick="cbi_submit(this, 'cbi.skip')" /> <%
|
||||
end
|
||||
|
||||
if display_submit then
|
||||
local label = pcdata(self.submit or translate("Submit"))
|
||||
%><input class="cbi-button cbi-button-save" type="submit" value="<%=label%>" /> <%
|
||||
end
|
||||
|
||||
if display_reset then
|
||||
local label = pcdata(self.reset or translate("Reset"))
|
||||
%><input class="cbi-button cbi-button-reset" type="reset" value="<%=label%>" /> <%
|
||||
end
|
||||
|
||||
%></div><%
|
||||
end
|
||||
|
||||
%></form><%
|
||||
end
|
||||
%>
|
||||
|
||||
<script type="text/javascript">cbi_init();</script>
|
@ -1,25 +0,0 @@
|
||||
<br>
|
||||
<ul class="cbi-tabmenu">
|
||||
<li id="cbi-tab-container_info"><a id="a-cbi-tab-container_info" href=""><%:Info%></a></li>
|
||||
<li id="cbi-tab-container_edit"><a id="a-cbi-tab-container_edit" href=""><%:Edit%></a></li>
|
||||
<li id="cbi-tab-container_stats"><a id="a-cbi-tab-container_stats" href=""><%:Stats%></a></li>
|
||||
<li id="cbi-tab-container_file"><a id="a-cbi-tab-container_file" href=""><%:File%></a></li>
|
||||
<li id="cbi-tab-container_logs"><a id="a-cbi-tab-container_logs" href=""><%:Logs%></a></li>
|
||||
</ul>
|
||||
|
||||
<script type="text/javascript">
|
||||
let re = /\/admin\/services\/docker\/container\//
|
||||
let p = window.location.href
|
||||
let path = p.split(re)
|
||||
let container_id = path[1].split('/')[0] || path[1]
|
||||
let action = path[1].split('/')[1] || "info"
|
||||
let actions=["info","edit","stats","file","logs"]
|
||||
actions.forEach(function(item) {
|
||||
document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/services/docker/container/"+container_id+'/'+item
|
||||
if (action === item) {
|
||||
document.getElementById("cbi-tab-container_" + item).className="cbi-tab"
|
||||
} else {
|
||||
document.getElementById("cbi-tab-container_" + item).className="cbi-tab-disable"
|
||||
}
|
||||
})
|
||||
</script>
|
@ -1,54 +0,0 @@
|
||||
|
||||
<div id="upload-container" class="cbi-value cbi-value-last">
|
||||
<label class="cbi-value-title" for="archive"><%:Upload%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input type="file" name="upload_archive" accept="application/x-tar" id="upload_archive" />
|
||||
</div>
|
||||
<br>
|
||||
<label class="cbi-value-title" for="path"><%:Path%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input type="text" class="cbi-input-text" name="path" placeholder="/home/myfiles" id="path" />
|
||||
</div>
|
||||
<br>
|
||||
<div class="cbi-value-field">
|
||||
<input type="button"" class="cbi-button cbi-button-action important" id="upload" name="upload" value="<%:Upload%>" />
|
||||
<input type="button"" class="cbi-button cbi-button-action important" id="download" name="download" value="<%:Download%>" />
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
let btnUpload = document.getElementById('upload')
|
||||
btnUpload.onclick = function (e) {
|
||||
let uploadArchive = document.getElementById('upload_archive')
|
||||
let uploadPath = document.getElementById('path').value
|
||||
if (!uploadArchive.value || !uploadPath) {
|
||||
alert("<%:Please input the PATH and select the file !%>")
|
||||
return
|
||||
}
|
||||
let fileName = uploadArchive.files[0].name
|
||||
let formData = new FormData()
|
||||
formData.append('upload-filename', fileName)
|
||||
formData.append('upload-path', uploadPath)
|
||||
formData.append('upload-archive', uploadArchive.files[0])
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("POST", "/cgi-bin/luci/admin/services/docker/container_put_archive/<%=self.container%>", true)
|
||||
xhr.onload = function() {
|
||||
if (xhr.status == 200) {
|
||||
uploadArchive.value = ''
|
||||
alert("<%:Upload Success%> !")
|
||||
}
|
||||
else {
|
||||
alert("<%:Upload Error%>:" + xhr.statusText)
|
||||
}
|
||||
}
|
||||
xhr.send(formData)
|
||||
}
|
||||
let btnDownload = document.getElementById('download')
|
||||
btnDownload.onclick = function (e) {
|
||||
let downloadPath = document.getElementById('path').value
|
||||
if (!downloadPath) {
|
||||
alert("<%:Please input the PATH !%>")
|
||||
return
|
||||
}
|
||||
window.open("/cgi-bin/luci/admin/services/docker/container_get_archive/?id=<%=self.container%>&path=" + encodeURIComponent(downloadPath))
|
||||
}
|
||||
</script>
|
@ -1,10 +0,0 @@
|
||||
<% if self.title == translate("Docker Events") then %>
|
||||
<%+header%>
|
||||
<% end %>
|
||||
<h2><a id="content" name="content"><%=self.title%></a></h2>
|
||||
<div id="content_syslog">
|
||||
<textarea readonly="readonly" wrap="off" rows="<%=self.syslog:cmatch("\n")+2%>" id="syslog"><%=self.syslog:pcdata()%></textarea>
|
||||
</div>
|
||||
<% if self.title == translate("Docker Events") then %>
|
||||
<%+footer%>
|
||||
<% end %>
|
@ -1,281 +0,0 @@
|
||||
<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;
|
||||
min-width: 4rem;
|
||||
max-width: 4rem;
|
||||
min-height: 4rem;
|
||||
max-height: 4rem;
|
||||
}
|
||||
|
||||
.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">
|
||||
<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/services/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 -4 42 50" 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/services/docker/images"><%- end -%>
|
||||
<span style="font-size: 2rem; color: #2dce89;"><%=self.images_used%></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" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 48.723 48.723" xml:space="preserve">
|
||||
<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" />
|
||||
</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/services/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 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/services/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>
|
@ -1,94 +0,0 @@
|
||||
<style type="text/css">
|
||||
#dialog_reslov {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: none;
|
||||
z-index: 20000;
|
||||
}
|
||||
|
||||
#dialog_reslov .dialog_box {
|
||||
position: relative;
|
||||
background: rgba(255, 255, 255);
|
||||
top: 10%;
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height:auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#dialog_reslov .dialog_line {
|
||||
margin-top: .5em;
|
||||
margin-bottom: .5em;
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
#dialog_reslov .dialog_box>h4,
|
||||
#dialog_reslov .dialog_box>p,
|
||||
#dialog_reslov .dialog_box>div {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
#dialog_reslov .dialog_box>img {
|
||||
margin-right: 1em;
|
||||
flex-basis: 32px;
|
||||
}
|
||||
|
||||
body.dialog-reslov-active {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
body.dialog-reslov-active #dialog_reslov {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function close_reslov_dialog() {
|
||||
document.body.classList.remove('dialog-reslov-active')
|
||||
document.documentElement.style.overflowY = 'scroll'
|
||||
}
|
||||
|
||||
function reslov_container() {
|
||||
let s = document.getElementById('cmd-line-status')
|
||||
if (!s) return
|
||||
let cmd_line = document.getElementById("dialog_reslov_text").value;
|
||||
if (cmd_line == null || cmd_line == "") {
|
||||
return
|
||||
}
|
||||
if (!cmd_line.match(/^docker\s+(run|create)/)) {
|
||||
s.innerHTML = "<font color='red'><%:Command line Error%></font>"
|
||||
return
|
||||
}
|
||||
let reg_space = /\s+/g
|
||||
let reg_muti_line= /\\\s*\n/g
|
||||
// reg_rem =/(?<!\\)`#.+(?<!\\)`/g // the command has `# `
|
||||
let reg_rem =/`#.+`/g// the command has `# `
|
||||
cmd_line = cmd_line.replace(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ")
|
||||
console.log(cmd_line)
|
||||
window.location.href = "/cgi-bin/luci/admin/services/docker/newcontainer/" + encodeURI(cmd_line)
|
||||
}
|
||||
|
||||
function clear_text(){
|
||||
let s = document.getElementById('cmd-line-status')
|
||||
s.innerHTML = ""
|
||||
}
|
||||
|
||||
function show_reslov_dialog() {
|
||||
document.getElementById('dialog_reslov') || document.body.insertAdjacentHTML("beforeend", '<div id="dialog_reslov"><div class="dialog_box"><div class="dialog_line"></div><div class="dialog_line"><span><%:Plese input <docker create/run> command line:%></span><br><span id="cmd-line-status"></span></div><div class="dialog_line"><textarea class="cbi-input-textarea" id="dialog_reslov_text" style="width: 100%; height:100%;" rows="15" onkeyup="clear_text()"></textarea></div><div class="dialog_line" style="text-align: right;"><input type="button" class="cbi-button cbi-button-apply" type="submit" value="<%:Submit%>" onclick="reslov_container()" /> <input type="button" class="cbi-button cbi-button-reset" type="reset" value="<%:Cancel%>" onclick="close_reslov_dialog()" /></div><div class="dialog_line"></div></div></div>')
|
||||
document.body.classList.add('dialog-reslov-active')
|
||||
let s = document.getElementById('cmd-line-status')
|
||||
s.innerHTML = ""
|
||||
document.documentElement.style.overflowY = 'hidden'
|
||||
}
|
||||
</script>
|
||||
<%+cbi/valueheader%>
|
||||
<input type="button" class="cbi-button cbi-button-apply" value="<%:Command line%>" onclick="show_reslov_dialog()" />
|
||||
|
||||
<%+cbi/valuefooter%>
|
@ -1,60 +0,0 @@
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function progressbar(v, m, pc, np) {
|
||||
m = m || 100
|
||||
|
||||
return String.format(
|
||||
'<div style="width:100%%; max-width:500px; position:relative; border:1px solid #999999">' +
|
||||
'<div style="background-color:#CCCCCC; width:%d%%; height:15px">' +
|
||||
'<div style="position:absolute; left:0; top:0; text-align:center; width:100%%; color:#000000">' +
|
||||
'<small>%s / %s ' + (np == true ? "" : '(%d%%)') + '</small>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>', pc, v, m, pc
|
||||
);
|
||||
}
|
||||
|
||||
function niceBytes(bytes, decimals) {
|
||||
if (bytes == 0) return '0 Bytes';
|
||||
var k = 1000,
|
||||
dm = decimals + 1 || 3,
|
||||
sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
|
||||
i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
XHR.poll(5, '/cgi-bin/luci/admin/services/docker/container_stats/<%=self.container_id%>', { status: 1 },
|
||||
function (x, info) {
|
||||
var e;
|
||||
|
||||
if (e = document.getElementById('cbi-table-cpu-value'))
|
||||
e.innerHTML = progressbar(
|
||||
(info.cpu_percent), 100, (info.cpu_percent ? info.cpu_percent : 0));
|
||||
if (e = document.getElementById('cbi-table-memory-value'))
|
||||
e.innerHTML = progressbar(
|
||||
niceBytes(info.memory.mem_useage),
|
||||
niceBytes(info.memory.mem_limit),
|
||||
((100 / (info.memory.mem_limit ? info.memory.mem_limit : 100)) * (info.memory.mem_useage ? info.memory.mem_useage : 0))
|
||||
);
|
||||
|
||||
let num = 1
|
||||
for (var key in info.bw_rxtx) {
|
||||
if (!document.getElementById("cbi-table-network_" + key + "-value")) {
|
||||
let tab = document.getElementById("cbi-table-cpu").parentNode
|
||||
num++
|
||||
let div = document.getElementById('cbi-table-cpu').cloneNode(true);
|
||||
div.id = "cbi-table-network_" + key;
|
||||
div.children[0].innerHTML = "<%:Network TX/RX%>: " + key
|
||||
div.children[1].id = "cbi-table-network_" + key + "-value"
|
||||
tab.appendChild(div)
|
||||
}
|
||||
e = document.getElementById("cbi-table-network_" + key + "-value")
|
||||
e.innerHTML = progressbar(
|
||||
niceBytes(info.bw_rxtx[key].bw_tx),
|
||||
niceBytes(info.bw_rxtx[key].bw_rx),
|
||||
((100 / (info.bw_rxtx[key].bw_rx ? info.bw_rxtx[key].bw_rx : 100)) * (info.bw_rxtx[key].bw_tx ? info.bw_rxtx[key].bw_tx : 0)) > 100 ? 100 : Math.floor((100 / (info.bw_rxtx[key].bw_rx ? info.bw_rxtx[key].bw_rx : 100)) * (info.bw_rxtx[key].bw_tx ? info.bw_rxtx[key].bw_tx : 0)),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
//]]></script>
|
39
package/lean/luci-app-dockerman/po/zh-cn/docker.po
Normal file
39
package/lean/luci-app-dockerman/po/zh-cn/docker.po
Normal file
@ -0,0 +1,39 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Luci ARP Bind\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-06-23 20:16+0800\n"
|
||||
"PO-Revision-Date: 2015-06-23 20:17+0800\n"
|
||||
"Last-Translator: coolsnowwolf <coolsnowwolf@gmail.com>\n"
|
||||
"Language-Team: PandoraBox Team\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.8.1\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
msgid "Docker CE Container"
|
||||
msgstr "Docker CE 容器"
|
||||
|
||||
msgid "Open Portainer Docker Admin"
|
||||
msgstr "打开 Portainer Docker 管理页面"
|
||||
|
||||
msgid "Docker is a set of platform-as-a-service (PaaS) products that use OS-level virtualization to deliver software in packages called containers."
|
||||
msgstr "Docker是一组平台即服务(platform-as-a-service,PaaS)产品,它使用操作系统级容器虚拟化来交付软件包。"
|
||||
|
||||
msgid "Enable WAN access Dokcer"
|
||||
msgstr "允许 WAN 访问 Dokcer"
|
||||
|
||||
msgid "Enable WAN access docker mapped ports"
|
||||
msgstr "允许 WAN 访问 Dokcer 映射后的端口(易受攻击!)。<br /><br />推荐禁用该选项后,用系统防火墙选择性映射 172.17.0.X:XX 端口到 WAN"
|
||||
|
||||
msgid "Docker Readme First"
|
||||
msgstr "Docker 初始化无脑配置教程"
|
||||
|
||||
msgid "Download DockerReadme.pdf"
|
||||
msgstr "下载 Docker 初始化无脑配置教程"
|
||||
|
||||
msgid "Please download DockerReadme.pdf to read when first-running"
|
||||
msgstr "初次在OpenWrt中运行Docker必读(只需执行一次流程)"
|
@ -1,359 +0,0 @@
|
||||
msgid "Containers"
|
||||
msgstr "容器"
|
||||
|
||||
msgid "Images"
|
||||
msgstr "镜像"
|
||||
|
||||
msgid "Networks"
|
||||
msgstr "网络"
|
||||
|
||||
msgid "Volumes"
|
||||
msgstr "存储卷"
|
||||
|
||||
msgid "Events"
|
||||
msgstr "事件"
|
||||
|
||||
msgid "Docker Contaienr"
|
||||
msgstr "Docker 容器"
|
||||
|
||||
msgid "Start"
|
||||
msgstr "启动"
|
||||
|
||||
msgid "Restart"
|
||||
msgstr "重启"
|
||||
|
||||
msgid "Stop"
|
||||
msgstr "停止"
|
||||
|
||||
msgid "Upgrade"
|
||||
msgstr "升级容器"
|
||||
|
||||
msgid "Duplicate"
|
||||
msgstr "复制容器"
|
||||
|
||||
msgid "Remove"
|
||||
msgstr "移除"
|
||||
|
||||
msgid "Name"
|
||||
msgstr "名称"
|
||||
|
||||
msgid "Image"
|
||||
msgstr "镜像"
|
||||
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
msgid "Created"
|
||||
msgstr "创建时间"
|
||||
|
||||
msgid "Start Time"
|
||||
msgstr "启动时间"
|
||||
|
||||
msgid "Healthy"
|
||||
msgstr "健康"
|
||||
|
||||
msgid "Restart Policy"
|
||||
msgstr "重启策略"
|
||||
|
||||
msgid "Update"
|
||||
msgstr "更新"
|
||||
|
||||
msgid "Device(--device)"
|
||||
msgstr "设备(--device)"
|
||||
|
||||
msgid "Mount/Volume"
|
||||
msgstr "挂载/存储卷"
|
||||
|
||||
msgid "Command"
|
||||
msgstr "启动命令"
|
||||
|
||||
msgid "Setting"
|
||||
msgstr "设置"
|
||||
|
||||
msgid "Driver"
|
||||
msgstr "驱动"
|
||||
|
||||
msgid "Env"
|
||||
msgstr "环境变量"
|
||||
|
||||
msgid "Ports"
|
||||
msgstr "端口"
|
||||
|
||||
msgid "Links"
|
||||
msgstr "链接"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "断开"
|
||||
|
||||
msgid "Connect Network"
|
||||
msgstr "连接网络"
|
||||
|
||||
msgid "Connect"
|
||||
msgstr "连接"
|
||||
|
||||
msgid "Info"
|
||||
msgstr "信息"
|
||||
|
||||
msgid "CPUs"
|
||||
msgstr "CPU数量"
|
||||
|
||||
msgid "Number of CPUs. Number is a fractional number. 0.000 means no limit."
|
||||
msgstr "CPU数量,数字是小数,0.000表示没有限制。"
|
||||
|
||||
msgid "CPU Shares Weight"
|
||||
msgstr "CPU份额权重"
|
||||
|
||||
msgid "CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."
|
||||
msgstr "CPU份额相对权重,如果设置为0,则系统将忽略该值,并使用默认值1024。"
|
||||
|
||||
msgid "Memory"
|
||||
msgstr "内存"
|
||||
|
||||
msgid "Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."
|
||||
msgstr "内存限制 (格式: <容量>[<单位>]). 数字是一个正整数。单位可以是b,k,m或g之一。最小为4M。"
|
||||
|
||||
msgid "Block IO Weight"
|
||||
msgstr "IO 权重"
|
||||
|
||||
msgid "Block IO weight (relative weight) accepts a weight value between 10 and 1000."
|
||||
msgstr "IO 权重 (相对权重) 接受10到1000之间的权重值。"
|
||||
|
||||
msgid "Container Logs"
|
||||
msgstr "容器日志"
|
||||
|
||||
msgid "RepoTags"
|
||||
msgstr "标签"
|
||||
|
||||
msgid "Size"
|
||||
msgstr "大小"
|
||||
|
||||
msgid "Force Remove"
|
||||
msgstr "强制移除"
|
||||
|
||||
msgid "Subnet"
|
||||
msgstr "子网络"
|
||||
|
||||
msgid "Gateway"
|
||||
msgstr "网关"
|
||||
|
||||
msgid "New"
|
||||
msgstr "新建"
|
||||
|
||||
msgid "Resolv CLI"
|
||||
msgstr "解析命令行"
|
||||
|
||||
msgid "Docker Image"
|
||||
msgstr "Docker 镜像"
|
||||
|
||||
msgid "User(-u)"
|
||||
msgstr "用户(-u)"
|
||||
|
||||
msgid "New Container"
|
||||
msgstr "新容器"
|
||||
|
||||
msgid "Container Name"
|
||||
msgstr "容器名称"
|
||||
|
||||
msgid "Interactive (-i)"
|
||||
msgstr "互动式 (-i)"
|
||||
|
||||
msgid "Always pull image first"
|
||||
msgstr "始终先拉取镜像"
|
||||
|
||||
msgid "Privileged"
|
||||
msgstr "特权模式(--privileged)"
|
||||
|
||||
msgid "IPv4 Address"
|
||||
msgstr "IPv4 地址"
|
||||
|
||||
msgid "Links with other containers"
|
||||
msgstr "与其他容器的链接(--link)"
|
||||
|
||||
msgid "Environmental Variable(-e)"
|
||||
msgstr "环境变量(-e)"
|
||||
|
||||
msgid "Bind Mount(-v)"
|
||||
msgstr "挂载(-v)"
|
||||
|
||||
msgid "Exposed Ports(-p)"
|
||||
msgstr "暴露端口(-p)"
|
||||
|
||||
msgid "Run command"
|
||||
msgstr "运行命令"
|
||||
|
||||
msgid "Advance"
|
||||
msgstr "高级"
|
||||
|
||||
msgid "Mount tmpfs directory"
|
||||
msgstr "挂载tmpfs到容器内部目录"
|
||||
|
||||
msgid "New Network"
|
||||
msgstr "新网络"
|
||||
|
||||
msgid "Network Name"
|
||||
msgstr "网络名称"
|
||||
|
||||
msgid "Parent Interface"
|
||||
msgstr "父接口"
|
||||
|
||||
msgid "Macvlan Mode"
|
||||
msgstr "Macvlan模式"
|
||||
|
||||
msgid "Ipvlan Mode"
|
||||
msgstr "Ipvlan模式"
|
||||
|
||||
msgid "Ingress network is the network which provides the routing-mesh in swarm mode."
|
||||
msgstr "Ingress网络是在群集模式下提供路由网的网络。"
|
||||
|
||||
msgid "Options"
|
||||
msgstr "选项"
|
||||
|
||||
msgid "Restrict external access to the network"
|
||||
msgstr "限制外部访问网络"
|
||||
|
||||
msgid "IP range"
|
||||
msgstr "IP范围"
|
||||
|
||||
msgid "Exclude IPs"
|
||||
msgstr "排除IP"
|
||||
|
||||
msgid "Enable IPv6"
|
||||
msgstr "启用IPv6"
|
||||
|
||||
msgid "IPv6 Subnet"
|
||||
msgstr "IPv6子网"
|
||||
|
||||
msgid "IPv6 Gateway"
|
||||
msgstr "IPv6网关"
|
||||
|
||||
msgid "Docker Version"
|
||||
msgstr "Docker版本"
|
||||
|
||||
msgid "Api Version"
|
||||
msgstr "API版本"
|
||||
|
||||
msgid "Total Memory"
|
||||
msgstr "总内存"
|
||||
|
||||
msgid "Docker Root Dir"
|
||||
msgstr "Docker根目录"
|
||||
|
||||
msgid "Index Server Address"
|
||||
msgstr "默认服务器地址"
|
||||
|
||||
msgid "Socket Path"
|
||||
msgstr "Socket路径"
|
||||
|
||||
msgid "Action Status Tempfile Path"
|
||||
msgstr "Docker 动作状态的临时文件路径"
|
||||
|
||||
msgid "Where you want to save the docker status file"
|
||||
msgstr "保存docker status文件的位置"
|
||||
|
||||
msgid "Enable WAN access Dokcer"
|
||||
msgstr "允许 WAN 访问 Dokcer"
|
||||
|
||||
msgid "Enable WAN access docker mapped ports (need reload Docker-ce service)"
|
||||
msgstr "允许 WAN 访问 Dokcer 映射后的端口(易受攻击!)。<br /><br />如已更改此选项,需要点击应用并保存后重启docker服务。<br />推荐禁用该选项后,用系统防火墙选择性映射 172.17.0.X:XX 端口到 WAN"
|
||||
|
||||
msgid "Enable Debug"
|
||||
msgstr "启用调试"
|
||||
|
||||
msgid "For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path"
|
||||
msgstr "用于调试,在调试临时文件路径中显示 luci-app-dockerman 的所有 Docker API 操作"
|
||||
|
||||
msgid "Debug Tempfile Path"
|
||||
msgstr "调试临时文件路径"
|
||||
|
||||
msgid "Where you want to save the debug tempfile"
|
||||
msgstr "保存调试临时文件的位置"
|
||||
|
||||
msgid "Edit"
|
||||
msgstr "编辑"
|
||||
|
||||
msgid "Stats"
|
||||
msgstr "状态"
|
||||
|
||||
msgid "Logs"
|
||||
msgstr "日志"
|
||||
|
||||
msgid "Network TX/RX"
|
||||
msgstr "网络发送/接收"
|
||||
|
||||
msgid "CPU Useage"
|
||||
msgstr "CPU用量"
|
||||
|
||||
msgid "Memory Useage"
|
||||
msgstr "内存用量"
|
||||
|
||||
msgid "Docker Container"
|
||||
msgstr "Docker 容器"
|
||||
|
||||
msgid "Overview"
|
||||
msgstr "概况"
|
||||
|
||||
msgid "Pull Image"
|
||||
msgstr "拉取镜像"
|
||||
|
||||
msgid "Pull"
|
||||
msgstr "拉取"
|
||||
|
||||
msgid "Command line"
|
||||
msgstr "输入命令行"
|
||||
|
||||
msgid "Plese input <docker create/run> command line:"
|
||||
msgstr "请输入 docker run/create ... 命令行:"
|
||||
|
||||
msgid "Network Name"
|
||||
msgstr "网络名"
|
||||
|
||||
msgid "Set custom DNS servers"
|
||||
msgstr "自定义 DNS 服务器"
|
||||
|
||||
msgid "The user that commands are run as inside the container.(format: name|uid[:group|gid])"
|
||||
msgstr "容器内部执行命令的用户(组), 格式: UID:GID"
|
||||
|
||||
msgid "Set environment variables to inside the container"
|
||||
msgstr "容器内部环境变量"
|
||||
|
||||
msgid "Bind mount a volume"
|
||||
msgstr "绑定挂载"
|
||||
|
||||
msgid "Publish container's port(s) to the host"
|
||||
msgstr "将容器的端口发布到宿主"
|
||||
|
||||
msgid "Add host device to the container"
|
||||
msgstr "添加宿主设备到容器内部"
|
||||
|
||||
msgid "Device"
|
||||
msgstr "设备"
|
||||
|
||||
msgid "Finish Time"
|
||||
msgstr "结束时间"
|
||||
|
||||
msgid "Command line Error"
|
||||
msgstr "命令行错误"
|
||||
|
||||
msgid "Host Name"
|
||||
msgstr "主机名称"
|
||||
|
||||
msgid "The hostname to use for the container"
|
||||
msgstr "容器使用的主机名"
|
||||
|
||||
msgid "File"
|
||||
msgstr "文件"
|
||||
|
||||
msgid "Upload"
|
||||
msgstr "上传"
|
||||
|
||||
msgid "Download"
|
||||
msgstr "下载"
|
||||
|
||||
msgid "Path"
|
||||
msgstr "路径"
|
||||
|
||||
msgid "Upload Error"
|
||||
msgstr "上传错误"
|
||||
|
||||
msgid "Upload Success"
|
||||
msgstr "上传成功"
|
4
package/lean/luci-app-dockerman/root/etc/config/dockerd
Normal file
4
package/lean/luci-app-dockerman/root/etc/config/dockerd
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
config docker
|
||||
option wan_mode '0'
|
||||
|
@ -1,6 +0,0 @@
|
||||
config section 'local'
|
||||
option socket_path '/var/run/docker.sock'
|
||||
option status_path '/tmp/.docker_action_status'
|
||||
option wan_mode 'false'
|
||||
option debug_path '/tmp/.docker_debug'
|
||||
option debug 'false'
|
@ -23,3 +23,4 @@ w
|
||||
fi
|
||||
|
||||
echo "y" | mkfs.ext4 /dev/sda$partid
|
||||
|
||||
|
3
package/lean/luci-app-dockerman/root/etc/docker-web
Executable file
3
package/lean/luci-app-dockerman/root/etc/docker-web
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
docker run -d --restart=always --name="portainer" -p 9999:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer
|
@ -5,9 +5,9 @@ START=25
|
||||
|
||||
start_service() {
|
||||
local nofile=$(cat /proc/sys/fs/nr_open)
|
||||
local wanmode=$(uci get dockerman.local.wan_mode)
|
||||
|
||||
if [ $wanmode = "true" ] ;then
|
||||
local wanmode=$(uci get dockerd.@docker[0].wan_mode)
|
||||
|
||||
if [ $wanmode = "1" ] ;then
|
||||
dockerwan=" "
|
||||
else
|
||||
dockerwan="--iptables=false"
|
||||
@ -18,5 +18,5 @@ start_service() {
|
||||
procd_set_param command /usr/bin/dockerd $dockerwan
|
||||
procd_set_param limits nofile="${nofile} ${nofile}"
|
||||
procd_close_instance
|
||||
|
||||
|
||||
}
|
||||
|
11
package/lean/luci-app-dockerman/root/etc/uci-defaults/docker
Executable file
11
package/lean/luci-app-dockerman/root/etc/uci-defaults/docker
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@dockerd[-1]
|
||||
add ucitrack dockerd
|
||||
set ucitrack.@dockerd[-1].init=dockerd
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
BIN
package/lean/luci-app-dockerman/root/www/DockerReadme.pdf
Normal file
BIN
package/lean/luci-app-dockerman/root/www/DockerReadme.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user