Merge pull request #4283 from TDT-AG/pr/20200717-luci-app-dockerman

luci-app-dockerman: refactoring and update coding style
This commit is contained in:
Florian Eckert 2020-07-30 08:07:08 +02:00 committed by GitHub
commit 8f54db8cc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 4344 additions and 3664 deletions

View file

@ -0,0 +1,7 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" 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>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,9 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/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>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/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>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

View file

@ -2,39 +2,46 @@
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()
local e = entry({"admin", "docker"}, firstchild(), "Docker", 40)
e.dependent = false
e.acl_depends = { "luci-app-dockerman" }
entry({"admin", "docker", "overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true
local remote = luci.model.uci.cursor():get("dockerman", "local", "remote_endpoint")
if remote == nil then
local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
if socket and not nixio.fs.access(socket) then return end
elseif remote == "true" then
local host = luci.model.uci.cursor():get("dockerman", "local", "remote_host")
local port = luci.model.uci.cursor():get("dockerman", "local", "remote_port")
if not host or not port then return end
local remote = luci.model.uci.cursor():get_bool("dockerd", "globals", "remote_endpoint")
if remote then
local host = luci.model.uci.cursor():get("dockerd", "globals", "remote_host")
local port = luci.model.uci.cursor():get("dockerd", "globals", "remote_port")
if not host or not port then
return
end
else
local socket = luci.model.uci.cursor():get("dockerd", "globals", "socket_path")
if socket and not nixio.fs.access(socket) then
return
end
end
if (require "luci.model.docker").new():_ping().code ~= 200 then
return
end
if (require "luci.model.docker").new():_ping().code ~= 200 then return end
entry({"admin", "docker", "containers"}, form("dockerman/containers"), _("Containers"),1).leaf=true
entry({"admin", "docker", "images"}, form("dockerman/images"), _("Images"),2).leaf=true
entry({"admin", "docker", "networks"}, form("dockerman/networks"), _("Networks"),3).leaf=true
entry({"admin", "docker", "volumes"}, form("dockerman/volumes"), _("Volumes"),4).leaf=true
entry({"admin", "docker", "events"}, call("action_events"), _("Events"),5)
entry({"admin", "docker", "newcontainer"}, form("dockerman/newcontainer")).leaf=true
entry({"admin", "docker", "newnetwork"}, form("dockerman/newnetwork")).leaf=true
entry({"admin", "docker", "container"}, form("dockerman/container")).leaf=true
entry({"admin", "docker", "container_stats"}, call("action_get_container_stats")).leaf=true
entry({"admin", "docker", "container_get_archive"}, call("download_archive")).leaf=true
entry({"admin", "docker", "container_put_archive"}, call("upload_archive")).leaf=true
@ -49,10 +56,12 @@ end
function action_events()
local logs = ""
local dk = docker.new()
local query ={}
local dk = docker.new()
query["until"] = os.time()
local events = dk:events({query = query})
if events.code == 200 then
for _, v in ipairs(events.body) do
if v and v.Type == "container" then
@ -64,40 +73,42 @@ function action_events()
end
end
end
luci.template.render("dockerman/logs", {self={syslog = logs, title="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 type(d) ~= "table" then
return
end
local cpu_count = tonumber(d["cpu_stats"]["online_cpus"])
local cpu_percent = 0.0
local cpu_delta = tonumber(d["cpu_stats"]["cpu_usage"]["total_usage"]) - tonumber(d["precpu_stats"]["cpu_usage"]["total_usage"])
local 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"
if type(d) ~= "table" then
return
end
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
if type(d) ~="table" then
return
end
local data = {}
if type(d["networks"]) == "table" then
for e, v in pairs(d["networks"]) do
@ -107,6 +118,7 @@ local get_rx_tx = function(d)
}
end
end
return data
end
@ -165,6 +177,7 @@ function action_confirm()
msg = "finish"
data = "finish"
end
luci.http.status(code, msg)
luci.http.prepare_content("application/json")
luci.http.write_json({info = data})
@ -193,7 +206,12 @@ function download_archive()
end
end
local res = dk.containers:get_archive({id = id, query = {path = path}}, cb)
local res = dk.containers:get_archive({
id = id,
query = {
path = path
}
}, cb)
end
function upload_archive(container_id)
@ -209,7 +227,14 @@ function upload_archive(container_id)
end)
end
local res = dk.containers:put_archive({id = container_id, query = {path = path}, body = rec_send})
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")
@ -238,9 +263,16 @@ function save_images(container_id)
luci.ltn12.pump.all(chunk, luci.http.write)
end
end
docker:write_status("Images: saving" .. " " .. container_id .. "...")
local res = dk.images:get({id = container_id, query = {names = names}}, cb)
local res = dk.images:get({
id = container_id,
query = {
names = names
}
}, cb)
docker:clear_status()
local msg = res and res.body and res.body.message or nil
luci.http.status(res.code, msg)
luci.http.prepare_content("application/json")
@ -262,7 +294,6 @@ function load_images()
docker:write_status("Images: loading...")
local res = dk.images:load({body = rec_send})
-- res.body = {"stream":"Loaded image ID: sha256:1399d3d81f80d68832e85ed6ba5f94436ca17966539ba715f661bd36f3caf08f\n"}
local msg = res and res.body and ( res.body.message or res.body.stream or res.body.error ) or nil
if res.code == 200 and msg and msg:match("Loaded image ID") then
docker:clear_status()
@ -271,6 +302,7 @@ function load_images()
docker:append_status("code:" .. res.code.." ".. msg)
luci.http.status(300, msg)
end
luci.http.prepare_content("application/json")
luci.http.write_json({message = msg})
end
@ -280,6 +312,7 @@ function import_images()
local itag = luci.http.formvalue("tag")
local dk = docker.new()
local ltn12 = require "luci.ltn12"
local rec_send = function(sinkout)
luci.http.setfilehandler(function (meta, chunk, eof)
if chunk then
@ -287,23 +320,32 @@ function import_images()
end
end)
end
docker:write_status("Images: importing".. " ".. itag .."...\n")
local repo = itag and itag:match("^([^:]+)")
local tag = itag and itag:match("^[^:]-:([^:]+)")
local res = dk.images:create({query = {fromSrc = src or "-", repo = repo or nil, tag = tag or nil }, body = not src and rec_send or nil}, docker.import_image_show_status_cb)
local res = dk.images:create({
query = {
fromSrc = src or "-",
repo = repo or nil,
tag = tag or nil
},
body = not src and rec_send or nil
}, docker.import_image_show_status_cb)
local msg = res and res.body and ( res.body.message )or nil
if not msg and #res.body == 0 then
-- res.body = {"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
msg = res.body.status or res.body.error
elseif not msg and #res.body >= 1 then
-- res.body = [...{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}]
msg = res.body[#res.body].status or res.body[#res.body].error
end
if res.code == 200 and msg and msg:match("sha256:") then
docker:clear_status()
else
docker:append_status("code:" .. res.code.." ".. msg)
end
luci.http.status(res.code, msg)
luci.http.prepare_content("application/json")
luci.http.write_json({message = msg})
@ -316,11 +358,15 @@ function get_image_tags(image_id)
luci.http.write_json({message = "no image id"})
return
end
local dk = docker.new()
local res = dk.images:inspect({id = image_id})
local res = dk.images:inspect({
id = image_id
})
local msg = res and res.body and res.body.message or nil
luci.http.status(res.code, msg)
luci.http.prepare_content("application/json")
if res.code == 200 then
local tags = res.body.RepoTags
luci.http.write_json({tags = tags})
@ -333,19 +379,28 @@ end
function tag_image(image_id)
local src = luci.http.formvalue("tag")
local image_id = image_id or luci.http.formvalue("id")
if type(src) ~= "string" or not image_id then
luci.http.status(400, "no image id or tag")
luci.http.prepare_content("application/json")
luci.http.write_json({message = "no image id or tag"})
return
end
local repo = src:match("^([^:]+)")
local tag = src:match("^[^:]-:([^:]+)")
local dk = docker.new()
local res = dk.images:tag({id = image_id, query={repo=repo, tag=tag}})
local res = dk.images:tag({
id = image_id,
query={
repo=repo,
tag=tag
}
})
local msg = res and res.body and res.body.message or nil
luci.http.status(res.code, msg)
luci.http.prepare_content("application/json")
if res.code == 201 then
local tags = res.body.RepoTags
luci.http.write_json({tags = tags})
@ -357,14 +412,17 @@ end
function untag_image(tag)
local tag = tag or luci.http.formvalue("tag")
if not tag then
luci.http.status(400, "no tag name")
luci.http.prepare_content("application/json")
luci.http.write_json({message = "no tag name"})
return
end
local dk = docker.new()
local res = dk.images:inspect({name = tag})
if res.code == 200 then
local tags = res.body.RepoTags
if #tags > 1 then

View file

@ -4,50 +4,73 @@ Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local docker = require "luci.model.docker"
local dk = docker.new()
container_id = arg[1]
local action = arg[2] or "info"
local images, networks, container_info
if not container_id then return end
local res = dk.containers:inspect({id = container_id})
if res.code < 300 then container_info = res.body else return end
local m, s, o
local images, networks, container_info, res
if not container_id then
return
end
res = dk.containers:inspect({id = container_id})
if res.code < 300 then
container_info = res.body
else
return
end
res = dk.networks:list()
if res.code < 300 then networks = res.body else return end
if res.code < 300 then
networks = res.body
else
return
end
local get_ports = function(d)
local data
if d.HostConfig and d.HostConfig.PortBindings then
for inter, out in pairs(d.HostConfig.PortBindings) do
data = (data and (data .. "<br>") or "") .. out[1]["HostPort"] .. ":" .. inter
end
end
return data
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
@ -70,79 +93,95 @@ local get_mounts = function(d)
data = (data and (data .. "<br>") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "")
end
end
return data
end
local get_device = function(d)
local data
if d.HostConfig and d.HostConfig.Devices then
for _,v in ipairs(d.HostConfig.Devices) do
data = (data and (data .. "<br>") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "")
end
end
return data
end
local get_links = function(d)
local data
if d.HostConfig and d.HostConfig.Links then
for _,v in ipairs(d.HostConfig.Links) do
data = (data and (data .. "<br>") or "") .. v
end
end
return data
end
local get_tmpfs = function(d)
local data
if d.HostConfig and d.HostConfig.Tmpfs then
for k, v in pairs(d.HostConfig.Tmpfs) do
data = (data and (data .. "<br>") or "") .. k .. (v~="" and ":" or "")..v
end
end
return data
end
local get_dns = function(d)
local data
if d.HostConfig and d.HostConfig.Dns then
for _, v in ipairs(d.HostConfig.Dns) do
data = (data and (data .. "<br>") or "") .. v
end
end
return data
end
local get_sysctl = function(d)
local data
if d.HostConfig and d.HostConfig.Sysctls then
for k, v in pairs(d.HostConfig.Sysctls) do
data = (data and (data .. "<br>") or "") .. k..":"..v
end
end
return data
end
local get_networks = function(d)
local data={}
if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then
for k,v in pairs(d.NetworkSettings.Networks) do
data[k] = v.IPAddress or ""
end
end
return data
end
local start_stop_remove = function(m, cmd)
local res
docker:clear_status()
docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...")
local res
if cmd ~= "upgrade" then
res = dk.containers[cmd](dk, {id = container_id})
else
res = dk.containers_upgrade(dk, {id = container_id})
end
if res and res.code >= 300 then
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
@ -158,103 +197,175 @@ end
m=SimpleForm("docker", container_info.Name:sub(2), translate("Docker Container") )
m.redirect = luci.dispatcher.build_url("admin/docker/containers")
-- m:append(Template("dockerman/container"))
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
s = m:section(SimpleSection)
s.template = "dockerman/apply_widget"
s.err=docker:read_status()
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if s.err then
docker:clear_status()
end
action_section = m:section(Table,{{}})
action_section.notitle=true
action_section.rowcolors=false
action_section.template = "cbi/nullsection"
s = m:section(Table,{{}})
s.notitle=true
s.rowcolors=false
s.template = "cbi/nullsection"
btnstart=action_section:option(Button, "_start")
btnstart.template = "dockerman/cbi/inlinebutton"
btnstart.inputtitle=translate("Start")
btnstart.inputstyle = "apply"
btnstart.forcewrite = true
btnrestart=action_section:option(Button, "_restart")
btnrestart.template = "dockerman/cbi/inlinebutton"
btnrestart.inputtitle=translate("Restart")
btnrestart.inputstyle = "reload"
btnrestart.forcewrite = true
btnstop=action_section:option(Button, "_stop")
btnstop.template = "dockerman/cbi/inlinebutton"
btnstop.inputtitle=translate("Stop")
btnstop.inputstyle = "reset"
btnstop.forcewrite = true
btnkill=action_section:option(Button, "_kill")
btnkill.template = "dockerman/cbi/inlinebutton"
btnkill.inputtitle=translate("Kill")
btnkill.inputstyle = "reset"
btnkill.forcewrite = true
btnupgrade=action_section:option(Button, "_upgrade")
btnupgrade.template = "dockerman/cbi/inlinebutton"
btnupgrade.inputtitle=translate("Upgrade")
btnupgrade.inputstyle = "reload"
btnstop.forcewrite = true
btnduplicate=action_section:option(Button, "_duplicate")
btnduplicate.template = "dockerman/cbi/inlinebutton"
btnduplicate.inputtitle=translate("Duplicate/Edit")
btnduplicate.inputstyle = "add"
btnstop.forcewrite = true
btnremove=action_section:option(Button, "_remove")
btnremove.template = "dockerman/cbi/inlinebutton"
btnremove.inputtitle=translate("Remove")
btnremove.inputstyle = "remove"
btnremove.forcewrite = true
btnstart.write = function(self, section)
o = s:option(Button, "_start")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Start")
o.inputstyle = "apply"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"start")
end
btnrestart.write = function(self, section)
o = s:option(Button, "_restart")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Restart")
o.inputstyle = "reload"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"restart")
end
btnupgrade.write = function(self, section)
start_stop_remove(m,"upgrade")
end
btnremove.write = function(self, section)
start_stop_remove(m,"remove")
end
btnstop.write = function(self, section)
o = s:option(Button, "_stop")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Stop")
o.inputstyle = "reset"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"stop")
end
btnkill.write = function(self, section)
o = s:option(Button, "_kill")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Kill")
o.inputstyle = "reset"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"kill")
end
btnduplicate.write = function(self, section)
o = s:option(Button, "_upgrade")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Upgrade")
o.inputstyle = "reload"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"upgrade")
end
o = s:option(Button, "_duplicate")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Duplicate/Edit")
o.inputstyle = "add"
o.forcewrite = true
o.write = function(self, section)
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id))
end
tab_section = m:section(SimpleSection)
tab_section.template = "dockerman/container"
o = s:option(Button, "_remove")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Remove")
o.inputstyle = "remove"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"remove")
end
s = m:section(SimpleSection)
s.template = "dockerman/container"
if action == "info" then
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 "-"},
["01name"] = {
_key = translate("Name"),
_value = container_info.Name:sub(2) or "-",
_button=translate("Update")
},
["02id"] = {
_key = translate("ID"),
_value = container_info.Id or "-"
},
["03image"] = {
_key = translate("Image"),
_value = container_info.Config.Image .. "<br>" .. container_info.Image
},
["04status"] = {
_key = translate("Status"),
_value = container_info.State and container_info.State.Status or "-"
},
["05created"] = {
_key = translate("Created"),
_value = container_info.Created or "-"
},
}
table_info["06start"] = container_info.State.Status == "running" and {_key = translate("Start Time"), _value = container_info.State and container_info.State.StartedAt or "-"} or {_key = translate("Finish Time"), _value = container_info.State and container_info.State.FinishedAt or "-"}
table_info["07healthy"] = {_key = translate("Healthy"), _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"}
table_info["08restart"] = {_key = translate("Restart Policy"), _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", _button=translate("Update")}
table_info["081user"] = {_key = translate("User"), _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"}
table_info["09mount"] = {_key = translate("Mount/Volume"), _value = get_mounts(container_info) or "-"}
table_info["10cmd"] = {_key = translate("Command"), _value = get_command(container_info) or "-"}
table_info["11env"] = {_key = translate("Env"), _value = get_env(container_info) or "-"}
table_info["12ports"] = {_key = translate("Ports"), _value = get_ports(container_info) or "-"}
table_info["13links"] = {_key = translate("Links"), _value = get_links(container_info) or "-"}
table_info["14device"] = {_key = translate("Device"), _value = get_device(container_info) or "-"}
table_info["15tmpfs"] = {_key = translate("Tmpfs"), _value = get_tmpfs(container_info) or "-"}
table_info["16dns"] = {_key = translate("DNS"), _value = get_dns(container_info) or "-"}
table_info["17sysctl"] = {_key = translate("Sysctl"), _value = get_sysctl(container_info) or "-"}
if container_info.State.Status == "running" then
table_info["06start"] = {
_key = translate("Start Time"),
_value = container_info.State and container_info.State.StartedAt or "-"
}
else
table_info["06start"] = {
_key = translate("Finish Time"),
_value = container_info.State and container_info.State.FinishedAt or "-"
}
end
table_info["07healthy"] = {
_key = translate("Healthy"),
_value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"
}
table_info["08restart"] = {
_key = translate("Restart Policy"),
_value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-",
_button=translate("Update")
}
table_info["081user"] = {
_key = translate("User"),
_value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"
}
table_info["09mount"] = {
_key = translate("Mount/Volume"),
_value = get_mounts(container_info) or "-"
}
table_info["10cmd"] = {
_key = translate("Command"),
_value = get_command(container_info) or "-"
}
table_info["11env"] = {
_key = translate("Env"),
_value = get_env(container_info) or "-"
}
table_info["12ports"] = {
_key = translate("Ports"),
_value = get_ports(container_info) or "-"
}
table_info["13links"] = {
_key = translate("Links"),
_value = get_links(container_info) or "-"
}
table_info["14device"] = {
_key = translate("Device"),
_value = get_device(container_info) or "-"
}
table_info["15tmpfs"] = {
_key = translate("Tmpfs"),
_value = get_tmpfs(container_info) or "-"
}
table_info["16dns"] = {
_key = translate("DNS"),
_value = get_dns(container_info) or "-"
}
table_info["17sysctl"] = {
_key = translate("Sysctl"),
_value = get_sysctl(container_info) or "-"
}
info_networks = get_networks(container_info)
list_networks = {}
for _, v in ipairs (networks) do
@ -270,24 +381,31 @@ if action == "info" then
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")
_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")}
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)
s = m:section(Table,table_info)
s.nodescr=true
s.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)
o = s:option(DummyValue, "_key", translate("Info"))
o.width = "20%"
o = s:option(ListValue, "_value")
o.render = function(self, section, scope)
if table_info[section]._key == translate("Name") then
self:reset_values()
self.template = "cbi/value"
@ -325,23 +443,23 @@ if action == "info" then
DummyValue.render(self, section, scope)
end
end
dv_value.forcewrite = true -- for write function using simpleform
dv_value.write = function(self, section, value)
o.forcewrite = true
o.write = function(self, section, value)
table_info[section]._value=value
end
dv_value.validate = function(self, value)
o.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)
o = s:option(Value, "_opts")
o.forcewrite = true
o.write = function(self, section, value)
table_info[section]._opts=value
end
dv_opts.validate = function(self, value)
o.validate = function(self, value)
return value
end
dv_opts.render = function(self, section, scope)
o.render = function(self, section, scope)
if table_info[section]._key==translate("Connect Network") then
self.template = "cbi/value"
self.keylist = {}
@ -357,11 +475,12 @@ if action == "info" then
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)
o = s:option(Button, "_button")
o.forcewrite = true
o.render = function(self, section, scope)
if table_info[section]._button and table_info[section]._value ~= nil then
btn_update.inputtitle=table_info[section]._button
self.inputtitle=table_info[section]._button
self.template = "cbi/button"
self.inputstyle = "edit"
Button.render(self, section, scope)
@ -371,26 +490,50 @@ if action == "info" then
DummyValue.render(self, section, scope)
end
end
btn_update.write = function(self, section, value)
o.write = function(self, section, value)
local res
docker:clear_status()
if section == "01name" then
docker:append_status("Containers: rename " .. container_id .. "...")
local new_name = table_info[section]._value
res = dk.containers:rename({id = container_id, query = {name=new_name}})
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}}})
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("(.-) | .+")
local _,_,leave_network
_, _, leave_network = table_info[section]._value:find("(.-) | .+")
leave_network = leave_network or table_info[section]._value
docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...")
res = dk.networks:disconnect({name = leave_network, body = {Container = container_id}})
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
if connect_network ~= "none"
and connect_network ~= "bridge"
and connect_network ~= "host" then
network_opiton = table_info[section]._opts ~= "" and {
IPAMConfig={
IPv4Address=table_info[section]._opts
@ -398,8 +541,15 @@ if action == "info" then
} 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}})
res = dk.networks:connect({
name = connect_network,
body = {
Container = container_id,
EndpointConfig= network_opiton
}
})
end
if res and res.code > 300 then
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
else
@ -407,32 +557,38 @@ if action == "info" then
end
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info"))
end
-- info end
elseif action == "resources" then
local resources_section= m:section(SimpleSection)
d = resources_section:option( Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
d.placeholder = "1.5"
d.rmempty = true
d.datatype="ufloat"
d.default = container_info.HostConfig.NanoCpus / (10^9)
s = m:section(SimpleSection)
o = s:option( Value, "cpus",
translate("CPUs"),
translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
o.placeholder = "1.5"
o.rmempty = true
o.datatype="ufloat"
o.default = container_info.HostConfig.NanoCpus / (10^9)
d = resources_section:option(Value, "cpushares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
d.placeholder = "1024"
d.rmempty = true
d.datatype="uinteger"
d.default = container_info.HostConfig.CpuShares
o = s:option(Value, "cpushares",
translate("CPU Shares Weight"),
translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
o.placeholder = "1024"
o.rmempty = true
o.datatype="uinteger"
o.default = container_info.HostConfig.CpuShares
d = resources_section:option(Value, "memory", translate("Memory"), translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."))
d.placeholder = "128m"
d.rmempty = true
d.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0
o = s:option(Value, "memory",
translate("Memory"),
translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."))
o.placeholder = "128m"
o.rmempty = true
o.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0
d = resources_section:option(Value, "blkioweight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
d.placeholder = "500"
d.rmempty = true
d.datatype="uinteger"
d.default = container_info.HostConfig.BlkioWeight
o = s:option(Value, "blkioweight",
translate("Block IO Weight"),
translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
o.placeholder = "500"
o.rmempty = true
o.datatype="uinteger"
o.default = container_info.HostConfig.BlkioWeight
m.handle = function(self, state, data)
if state == FORM_VALID then
@ -452,12 +608,14 @@ elseif action == "resources" then
end
end
end
request_body = {
BlkioWeight = tonumber(data.blkioweight),
NanoCPUs = tonumber(data.cpus)*10^9,
Memory = tonumber(memory),
CpuShares = tonumber(data.cpushares)
}
docker:write_status("Containers: update " .. container_id .. "...")
local res = dk.containers:update({id = container_id, body = request_body})
if res and res.code >= 300 then
@ -468,35 +626,39 @@ elseif action == "resources" then
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources"))
end
end
elseif action == "file" then
local filesection= m:section(SimpleSection)
s = m:section(SimpleSection)
s.template = "dockerman/container_file"
s.container = container_id
m.submit = false
m.reset = false
filesection.template = "dockerman/container_file"
filesection.container = container_id
elseif action == "inspect" then
local inspectsection= m:section(SimpleSection)
inspectsection.syslog = luci.jsonc.stringify(container_info, true)
inspectsection.title = translate("Container Inspect")
inspectsection.template = "dockerman/logs"
s = m:section(SimpleSection)
s.syslog = luci.jsonc.stringify(container_info, true)
s.title = translate("Container Inspect")
s.template = "dockerman/logs"
m.submit = false
m.reset = false
elseif action == "logs" then
local logsection= m:section(SimpleSection)
local logs = ""
local query ={
stdout = 1,
stderr = 1,
tail = 1000
}
local logs = dk.containers:logs({id = container_id, query = query})
s = m:section(SimpleSection)
logs = dk.containers:logs({id = container_id, query = query})
if logs.code == 200 then
logsection.syslog=logs.body
s.syslog=logs.body
else
logsection.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body
s.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body
end
logsection.title=translate("Container Logs")
logsection.template = "dockerman/logs"
s.title=translate("Container Logs")
s.template = "dockerman/logs"
m.submit = false
m.reset = false
elseif action == "console" then
@ -504,60 +666,80 @@ elseif action == "console" then
m.reset = false
local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil
local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil
if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then
local consolesection= m:section(SimpleSection)
local cmd = "/bin/sh"
local uid
local vcommand = consolesection:option(Value, "command", translate("Command"))
vcommand:value("/bin/sh", "/bin/sh")
vcommand:value("/bin/ash", "/bin/ash")
vcommand:value("/bin/bash", "/bin/bash")
vcommand.default = "/bin/sh"
vcommand.forcewrite = true
vcommand.write = function(self, section, value)
s = m:section(SimpleSection)
o = s:option(Value, "command", translate("Command"))
o:value("/bin/sh", "/bin/sh")
o:value("/bin/ash", "/bin/ash")
o:value("/bin/bash", "/bin/bash")
o.default = "/bin/sh"
o.forcewrite = true
o.write = function(self, section, value)
cmd = value
end
local vuid = consolesection:option(Value, "uid", translate("UID"))
vuid.forcewrite = true
vuid.write = function(self, section, value)
o = s:option(Value, "uid", translate("UID"))
o.forcewrite = true
o.write = function(self, section, value)
uid = value
end
local btn_connect = consolesection:option(Button, "connect")
btn_connect.render = function(self, section, scope)
o = s:option(Button, "connect")
o.render = function(self, section, scope)
self.inputstyle = "add"
self.title = " "
self.inputtitle = translate("Connect")
Button.render(self, section, scope)
end
btn_connect.write = function(self, section)
o.write = function(self, section)
local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil
local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil
if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$") then return end
if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$")then
return
end
local kill_ttyd = 'netstat -lnpt | grep ":7682[ \t].*ttyd$" | awk \'{print $NF}\' | awk -F\'/\' \'{print "kill -9 " $1}\' | sh > /dev/null'
luci.util.exec(kill_ttyd)
local hosts
local uci = (require "luci.model.uci").cursor()
local remote = uci:get("dockerman", "local", "remote_endpoint")
local socket_path = (remote == "false" or not remote) and uci:get("dockerman", "local", "socket_path") or nil
local host = (remote == "true") and uci:get("dockerman", "local", "remote_host") or nil
local port = (remote == "true") and uci:get("dockerman", "local", "remote_port") or nil
local remote = uci:get_bool("dockerd", "globals", "remote_endpoint")
local host = nil
local port = nil
local socket = nil
if remote then
host = uci:get("dockerd", "globals", "remote_host") or nil
port = uci:get("dockerd", "globals", "remote_port") or nil
else
socket = uci:get("dockerd", "globals", "socket_path") or nil
end
if remote and host and port then
hosts = host .. ':'.. port
elseif socket_path then
hosts = "unix://" .. socket_path
hosts = socket_path
else
return
end
local start_cmd = cmd_ttyd .. ' -d 2 --once -p 7682 '.. cmd_docker .. ' -H "'.. hosts ..'" exec -it ' .. (uid and uid ~= "" and (" -u ".. uid .. ' ') or "").. container_id .. ' ' .. cmd .. ' &'
os.execute(start_cmd)
local console = consolesection:option(DummyValue, "console")
console.container_id = container_id
console.template = "dockerman/container_console"
o = s:option(DummyValue, "console")
o.container_id = container_id
o.template = "dockerman/container_console"
end
end
elseif action == "stats" then
local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}})
local container_top
if response.code == 200 then
container_top=response.body
else
@ -568,19 +750,30 @@ elseif action == "stats" then
end
if type(container_top) == "table" then
s = m:section(SimpleSection)
s.container_id = container_id
s.template = "dockerman/container_stats"
table_stats = {
cpu={
key=translate("CPU Useage"),
value='-'
},
memory={
key=translate("Memory Useage"),
value='-'
}
}
container_top = response.body
stat_section = m:section(SimpleSection)
stat_section.container_id = container_id
stat_section.template = "dockerman/container_stats"
table_stats = {cpu={key=translate("CPU Useage"),value='-'},memory={key=translate("Memory Useage"),value='-'}}
stat_section = m:section(Table, table_stats, translate("Stats"))
stat_section:option(DummyValue, "key", translate("Stats")).width="33%"
stat_section:option(DummyValue, "value")
s = m:section(Table, table_stats, translate("Stats"))
s:option(DummyValue, "key", translate("Stats")).width="33%"
s:option(DummyValue, "value")
top_section = m:section(Table, container_top.Processes, translate("TOP"))
for i, v in ipairs(container_top.Titles) do
top_section:option(DummyValue, i, translate(v))
end
end
m.submit = false
m.reset = false
end

View file

@ -3,49 +3,69 @@ 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
local m, s, o
local images, networks, containers, res
local dk = docker.new()
res = dk.images:list()
if res.code <300 then
images = res.body
else
return
end
res = dk.networks:list()
if res.code <300 then networks = res.body else return end
res = dk.containers:list({query = {all=true}})
if res.code <300 then containers = res.body else return end
if res.code <300 then
networks = res.body
else
return
end
res = dk.containers:list({
query = {
all=true
}
})
if res.code <300 then
containers = res.body
else
return
end
local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode
function get_containers()
local data = {}
if type(containers) ~= "table" then return nil end
if type(containers) ~= "table" then
return nil
end
for i, v in ipairs(containers) do
local index = v.Created .. v.Id
data[index]={}
data[index]["_selected"] = 0
data[index]["_id"] = v.Id:sub(1,12)
data[index]["name"] = v.Names[1]:sub(2)
data[index]["_name"] = '<a href='..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. v.Names[1]:sub(2).."</a>"
data[index]["_status"] = v.Status
if v.Status:find("^Up") then
data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>"
else
data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>"
end
if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then
for networkname, netconfig in pairs(v.NetworkSettings.Networks) do
data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "")
end
end
-- networkmode = v.HostConfig.NetworkMode ~= "default" and v.HostConfig.NetworkMode or "bridge"
-- data[index]["_network"] = v.NetworkSettings.Networks[networkmode].IPAddress or nil
-- local _, _, image = v.Image:find("^sha256:(.+)")
-- if image ~= nil then
-- image=image:sub(1,12)
-- end
if v.Ports and next(v.Ports) ~= nil then
data[index]["_ports"] = nil
for _,v2 in ipairs(v.Ports) do
@ -55,6 +75,7 @@ function get_containers()
.. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "</a>" or "")
end
end
for ii,iv in ipairs(images) do
if iv.Id == v.ImageID then
data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>")
@ -64,66 +85,72 @@ function get_containers()
data[index]["_image_id"] = v.ImageID:sub(8,20)
data[index]["_command"] = v.Command
end
return data
end
local c_lists = get_containers()
-- list Containers
-- m = Map("docker", translate("Docker"))
local container_list = get_containers()
m = SimpleForm("docker", translate("Docker"))
m.submit=false
m.reset=false
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
c_table = m:section(Table, c_lists, translate("Containers"))
c_table.nodescr=true
-- v.template = "cbi/tblsection"
-- v.sortable = true
container_selecter = c_table:option(Flag, "_selected","")
container_selecter.disabled = 0
container_selecter.enabled = 1
container_selecter.default = 0
container_id = c_table:option(DummyValue, "_id", translate("ID"))
container_id.width="10%"
container_name = c_table:option(DummyValue, "_name", translate("Container Name"))
container_name.rawhtml = true
container_status = c_table:option(DummyValue, "_status", translate("Status"))
container_status.width="15%"
container_status.rawhtml=true
container_ip = c_table:option(DummyValue, "_network", translate("Network"))
container_ip.width="15%"
container_ports = c_table:option(DummyValue, "_ports", translate("Ports"))
container_ports.width="10%"
container_ports.rawhtml = true
container_image = c_table:option(DummyValue, "_image", translate("Image"))
container_image.width="10%"
container_command = c_table:option(DummyValue, "_command", translate("Command"))
container_command.width="20%"
container_selecter.write=function(self, section, value)
c_lists[section]._selected = value
s = m:section(SimpleSection)
s.template = "dockerman/apply_widget"
s.err=docker:read_status()
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if s.err then
docker:clear_status()
end
s = m:section(Table, container_list, translate("Containers"))
s.nodescr=true
o = s:option(Flag, "_selected","")
o.disabled = 0
o.enabled = 1
o.default = 0
o.write=function(self, section, value)
container_list[section]._selected = value
end
o = s:option(DummyValue, "_id", translate("ID"))
o.width="10%"
o = s:option(DummyValue, "_name", translate("Container Name"))
o.rawhtml = true
o = s:option(DummyValue, "_status", translate("Status"))
o.width="15%"
o.rawhtml=true
o = s:option(DummyValue, "_network", translate("Network"))
o.width="15%"
o = s:option(DummyValue, "_ports", translate("Ports"))
o.width="10%"
o.rawhtml = true
o = s:option(DummyValue, "_image", translate("Image"))
o.width="10%"
o = s:option(DummyValue, "_command", translate("Command"))
o.width="20%"
local start_stop_remove = function(m,cmd)
local c_selected = {}
-- 遍历table中sectionid
local c_table_sids = c_table:cfgsections()
for _, c_table_sid in ipairs(c_table_sids) do
-- 得到选中项的名字
if c_lists[c_table_sid]._selected == 1 then
c_selected[#c_selected+1] = c_lists[c_table_sid].name --container_name:cfgvalue(c_table_sid)
local container_selected = {}
for k in pairs(container_list) do
if container_list[k]._selected == 1 then
container_selected[#container_selected + 1] = container_list[k].name
end
end
if #c_selected >0 then
docker:clear_status()
if #container_selected > 0 then
local success = true
for _,cont in ipairs(c_selected) do
docker:clear_status()
for _, cont in ipairs(container_selected) do
docker:append_status("Containers: " .. cmd .. " " .. cont .. "...")
local res = dk.containers[cmd](dk, {id = cont})
if res and res.code >= 300 then
@ -133,63 +160,72 @@ local start_stop_remove = function(m,cmd)
docker:append_status("done\n")
end
end
if success then docker:clear_status() end
if success then
docker:clear_status()
end
luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
end
end
action_section = m:section(Table,{{}})
action_section.notitle=true
action_section.rowcolors=false
action_section.template="cbi/nullsection"
s = m:section(Table,{{}})
s.notitle=true
s.rowcolors=false
s.template="cbi/nullsection"
btnnew=action_section:option(Button, "_new")
btnnew.inputtitle= translate("New")
btnnew.template = "dockerman/cbi/inlinebutton"
btnnew.inputstyle = "add"
btnnew.forcewrite = true
btnstart=action_section:option(Button, "_start")
btnstart.template = "dockerman/cbi/inlinebutton"
btnstart.inputtitle=translate("Start")
btnstart.inputstyle = "apply"
btnstart.forcewrite = true
btnrestart=action_section:option(Button, "_restart")
btnrestart.template = "dockerman/cbi/inlinebutton"
btnrestart.inputtitle=translate("Restart")
btnrestart.inputstyle = "reload"
btnrestart.forcewrite = true
btnstop=action_section:option(Button, "_stop")
btnstop.template = "dockerman/cbi/inlinebutton"
btnstop.inputtitle=translate("Stop")
btnstop.inputstyle = "reset"
btnstop.forcewrite = true
btnkill=action_section:option(Button, "_kill")
btnkill.template = "dockerman/cbi/inlinebutton"
btnkill.inputtitle=translate("Kill")
btnkill.inputstyle = "reset"
btnkill.forcewrite = true
btnremove=action_section:option(Button, "_remove")
btnremove.template = "dockerman/cbi/inlinebutton"
btnremove.inputtitle=translate("Remove")
btnremove.inputstyle = "remove"
btnremove.forcewrite = true
btnnew.write = function(self, section)
o = s:option(Button, "_new")
o.inputtitle= translate("New")
o.template = "dockerman/cbi/inlinebutton"
o.inputstyle = "add"
o.forcewrite = true
o.write = function(self, section)
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
end
btnstart.write = function(self, section)
o = s:option(Button, "_start")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Start")
o.inputstyle = "apply"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"start")
end
btnrestart.write = function(self, section)
o = s:option(Button, "_restart")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Restart")
o.inputstyle = "reload"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"restart")
end
btnremove.write = function(self, section)
start_stop_remove(m,"remove")
end
btnstop.write = function(self, section)
o = s:option(Button, "_stop")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Stop")
o.inputstyle = "reset"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"stop")
end
btnkill.write = function(self, section)
o = s:option(Button, "_kill")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Kill")
o.inputstyle = "reset"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"kill")
end
o = s:option(Button, "_remove")
o.template = "dockerman/cbi/inlinebutton"
o.inputtitle=translate("Remove")
o.inputstyle = "remove"
o.forcewrite = true
o.write = function(self, section)
start_stop_remove(m,"remove")
end
return m

View file

@ -3,82 +3,107 @@ LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local uci = luci.model.uci.cursor()
local docker = require "luci.model.docker"
local dk = docker.new()
local containers, images
local res = dk.images:list()
if res.code <300 then images = res.body else return end
res = dk.containers:list({query = {all=true}})
if res.code <300 then containers = res.body else return end
local containers, images, res
local m, s, o
res = dk.images:list()
if res.code < 300 then
images = res.body
else
return
end
res = dk.containers:list({
query = {
all=true
}
})
if res.code < 300 then
containers = res.body
else
return
end
function get_images()
local data = {}
for i, v in ipairs(images) do
local index = v.Created .. v.Id
data[index]={}
data[index]["_selected"] = 0
data[index]["id"] = v.Id:sub(8)
data[index]["_id"] = '<a href="javascript:new_tag(\''..v.Id:sub(8,20)..'\')" class="dockerman-link" title="'..translate("New tag")..'">' .. v.Id:sub(8,20) .. '</a>'
if v.RepoTags and next(v.RepoTags)~=nil then
for i, v1 in ipairs(v.RepoTags) do
data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "<br>" )or "") .. ((v1:match("<none>") or (#v.RepoTags == 1)) and v1 or ('<a href="javascript:un_tag(\''..v1..'\')" class="dockerman_link" title="'..translate("Remove tag")..'" >' .. v1 .. '</a>'))
if not data[index]["tag"] then
data[index]["tag"] = v1--:match("<none>") and nil or v1
data[index]["tag"] = v1
end
end
else
data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+")
data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>"
end
data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","&lt;none&gt;")
-- data[index]["_tags"] = '<a href="javascript:handle_tag(\''..data[index]["_id"]..'\')">' .. data[index]["_tags"] .. '</a>'
for ci,cv in ipairs(containers) do
if v.Id == cv.ImageID then
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
'<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2).."</a>"
end
end
data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created)
end
return data
end
local image_list = get_images()
-- m = Map("docker", translate("Docker"))
m = SimpleForm("docker", translate("Docker"))
m.submit=false
m.reset=false
local pull_value={_image_tag_name="", _registry="index.docker.io"}
local pull_section = m:section(SimpleSection, translate("Pull Image"))
pull_section.template="cbi/nullsection"
local tag_name = pull_section:option(Value, "_image_tag_name")
tag_name.template = "dockerman/cbi/inlinevalue"
tag_name.placeholder="lisaac/luci:latest"
local action_pull = pull_section:option(Button, "_pull")
action_pull.inputtitle= translate("Pull")
action_pull.template = "dockerman/cbi/inlinebutton"
action_pull.inputstyle = "add"
tag_name.write = function(self, section, value)
local pull_value={
_image_tag_name="",
_registry="index.docker.io"
}
s = m:section(SimpleSection, translate("Pull Image"))
s.template="cbi/nullsection"
o = s:option(Value, "_image_tag_name")
o.template = "dockerman/cbi/inlinevalue"
o.placeholder="lisaac/luci:latest"
o.write = function(self, section, value)
local hastag = value:find(":")
if not hastag then
value = value .. ":latest"
end
pull_value["_image_tag_name"] = value
end
action_pull.write = function(self, section)
o = s:option(Button, "_pull")
o.inputtitle= translate("Pull")
o.template = "dockerman/cbi/inlinebutton"
o.inputstyle = "add"
o.write = function(self, section)
local tag = pull_value["_image_tag_name"]
local json_stringify = luci.jsonc and luci.jsonc.stringify
if tag and tag ~= "" then
docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n")
-- local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server})) , header={["X-Registry-Auth"] = x_auth}
local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb)
-- {"errorDetail": {"message": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }, "error": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }
if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then
docker:clear_status()
else
@ -87,48 +112,63 @@ action_pull.write = function(self, section)
else
docker:append_status("code: 400 please input the name of image name!")
end
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
end
local import_section = m:section(SimpleSection, translate("Import Images"))
local im = import_section:option(DummyValue, "_image_import")
im.template = "dockerman/images_import"
s = m:section(SimpleSection, translate("Import Images"))
local image_table = m:section(Table, image_list, translate("Images"))
o = s:option(DummyValue, "_image_import")
o.template = "dockerman/images_import"
local image_selecter = image_table:option(Flag, "_selected","")
image_selecter.disabled = 0
image_selecter.enabled = 1
image_selecter.default = 0
s = m:section(Table, image_list, translate("Images"))
local image_id = image_table:option(DummyValue, "_id", translate("ID"))
image_id.rawhtml = true
image_table:option(DummyValue, "_tags", translate("RepoTags")).rawhtml = true
image_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
image_table:option(DummyValue, "_size", translate("Size"))
image_table:option(DummyValue, "_created", translate("Created"))
image_selecter.write = function(self, section, value)
o = s:option(Flag, "_selected","")
o.disabled = 0
o.enabled = 1
o.default = 0
o.write = function(self, section, value)
image_list[section]._selected = value
end
o = s:option(DummyValue, "_tags", translate("RepoTags"))
o.rawhtml = true
o = s:option(DummyValue, "_containers", translate("Containers"))
o.rawhtml = true
o = s:option(DummyValue, "_size", translate("Size"))
o = s:option(DummyValue, "_created", translate("Created"))
o = s:option(DummyValue, "_id", translate("ID"))
o.rawhtml = true
local remove_action = function(force)
local image_selected = {}
-- 遍历table中sectionid
local image_table_sids = image_table:cfgsections()
for _, image_table_sid in ipairs(image_table_sids) do
-- 得到选中项的名字
if image_list[image_table_sid]._selected == 1 then
image_selected[#image_selected+1] = (image_list[image_table_sid]["_tags"]:match("<br>") or image_list[image_table_sid]["_tags"]:match("&lt;none&gt;")) and image_list[image_table_sid].id or image_list[image_table_sid].tag
for k in pairs(image_list) do
if image_list[k]._selected == 1 then
image_selected[#image_selected+1] = (image_list[k]["_tags"]:match("<br>") or image_list[k]["_tags"]:match("&lt;none&gt;")) and image_list[k].id or image_list[k].tag
end
end
if next(image_selected) ~= nil then
local success = true
docker:clear_status()
for _, img in ipairs(image_selected) do
docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
local query
if force then query = {force = true} end
local msg = dk.images:remove({id = img, query = query})
docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
if force then
query = {force = true}
end
local msg = dk.images:remove({
id = img,
query = query
})
if msg.code ~= 200 then
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
success = false
@ -136,59 +176,67 @@ local remove_action = function(force)
docker:append_status("done\n")
end
end
if success then docker:clear_status() end
if success then
docker:clear_status()
end
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
end
end
local docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err = docker:read_status()
docker_status.err = docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
s = m:section(SimpleSection)
s.template = "dockerman/apply_widget"
s.err = docker:read_status()
s.err = s.err and s.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if s.err then
docker:clear_status()
end
local action = m:section(Table,{{}})
action.notitle=true
action.rowcolors=false
action.template="cbi/nullsection"
s = m:section(Table,{{}})
s.notitle=true
s.rowcolors=false
s.template="cbi/nullsection"
local btnremove = action:option(Button, "remove")
btnremove.inputtitle= translate("Remove")
btnremove.template = "dockerman/cbi/inlinebutton"
btnremove.inputstyle = "remove"
btnremove.forcewrite = true
btnremove.write = function(self, section)
o = s:option(Button, "remove")
o.inputtitle= translate("Remove")
o.template = "dockerman/cbi/inlinebutton"
o.inputstyle = "remove"
o.forcewrite = true
o.write = function(self, section)
remove_action()
end
local btnforceremove = action:option(Button, "forceremove")
btnforceremove.inputtitle= translate("Force Remove")
btnforceremove.template = "dockerman/cbi/inlinebutton"
btnforceremove.inputstyle = "remove"
btnforceremove.forcewrite = true
btnforceremove.write = function(self, section)
o = s:option(Button, "forceremove")
o.inputtitle= translate("Force Remove")
o.template = "dockerman/cbi/inlinebutton"
o.inputstyle = "remove"
o.forcewrite = true
o.write = function(self, section)
remove_action(true)
end
local btnsave = action:option(Button, "save")
btnsave.inputtitle= translate("Save")
btnsave.template = "dockerman/cbi/inlinebutton"
btnsave.inputstyle = "edit"
btnsave.forcewrite = true
btnsave.write = function (self, section)
o = s:option(Button, "save")
o.inputtitle= translate("Save")
o.template = "dockerman/cbi/inlinebutton"
o.inputstyle = "edit"
o.forcewrite = true
o.write = function (self, section)
local image_selected = {}
local image_table_sids = image_table:cfgsections()
for _, image_table_sid in ipairs(image_table_sids) do
if image_list[image_table_sid]._selected == 1 then
image_selected[#image_selected+1] = image_list[image_table_sid].id --image_id:cfgvalue(image_table_sid)
for k in pairs(image_list) do
if image_list[k]._selected == 1 then
image_selected[#image_selected + 1] = image_list[k].id
end
end
if next(image_selected) ~= nil then
local names
local names, first
for _, img in ipairs(image_selected) do
names = names and (names .. "&names=".. img) or img
end
local first
local cb = function(res, chunk)
if res.code == 200 then
if not first then
@ -205,8 +253,10 @@ btnsave.write = function (self, section)
luci.ltn12.pump.all(chunk, luci.http.write)
end
end
docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...")
local msg = dk.images:get({query = {names = names}}, cb)
if msg.code ~= 200 then
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
success = false
@ -216,8 +266,9 @@ btnsave.write = function (self, section)
end
end
local btnload = action:option(Button, "load")
btnload.inputtitle= translate("Load")
btnload.template = "dockerman/images_load"
btnload.inputstyle = "add"
o = s:option(Button, "load")
o.inputtitle= translate("Load")
o.template = "dockerman/images_load"
o.inputstyle = "add"
return m

View file

@ -3,113 +3,134 @@ LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local uci = luci.model.uci.cursor()
local docker = require "luci.model.docker"
local dk = docker.new()
local networks
local res = dk.networks:list()
if res.code < 300 then networks = res.body else return end
local m, s, o
local networks, dk, res
dk = docker.new()
res = dk.networks:list()
if res.code < 300 then
networks = res.body
else
return
end
local get_networks = function ()
local data = {}
if type(networks) ~= "table" then return nil end
if type(networks) ~= "table" then
return nil
end
for i, v in ipairs(networks) do
local index = v.Created .. v.Id
data[index]={}
data[index]["_selected"] = 0
data[index]["_id"] = v.Id:sub(1,12)
data[index]["_name"] = v.Name
data[index]["_driver"] = v.Driver
if v.Driver == "bridge" then
data[index]["_interface"] = v.Options["com.docker.network.bridge.name"]
elseif v.Driver == "macvlan" then
data[index]["_interface"] = v.Options.parent
end
data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil
end
return data
end
local network_list = get_networks()
-- m = Map("docker", translate("Docker"))
m = SimpleForm("docker", translate("Docker"))
m.submit=false
m.reset=false
network_table = m:section(Table, network_list, translate("Networks"))
network_table.nodescr=true
s = m:section(Table, network_list, translate("Networks"))
s.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)
o = s:option(Flag, "_selected","")
o.template = "dockerman/cbi/xfvalue"
o.disabled = 0
o.enabled = 1
o.default = 0
o.render = function(self, section, scope)
self.disable = 0
if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then
self.disable = 1
end
Flag.render(self, section, scope)
end
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)
o.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=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
o = s:option(DummyValue, "_id", translate("ID"))
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)
o = s:option(DummyValue, "_name", translate("Network Name"))
o = s:option(DummyValue, "_driver", translate("Driver"))
o = s:option(DummyValue, "_interface", translate("Parent Interface"))
o = s:option(DummyValue, "_subnet", translate("Subnet"))
o = s:option(DummyValue, "_gateway", translate("Gateway"))
s = m:section(SimpleSection)
s.template = "dockerman/apply_widget"
s.err = docker:read_status()
s.err = s.err and s.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if s.err then
docker:clear_status()
end
s = m:section(Table,{{}})
s.notitle=true
s.rowcolors=false
s.template="cbi/nullsection"
o = s:option(Button, "_new")
o.inputtitle= translate("New")
o.template = "dockerman/cbi/inlinebutton"
o.notitle=true
o.inputstyle = "add"
o.forcewrite = true
o.write = function(self, section)
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
end
btnremove = action:option(Button, "_remove")
btnremove.inputtitle= translate("Remove")
btnremove.template = "dockerman/cbi/inlinebutton"
btnremove.inputstyle = "remove"
btnremove.forcewrite = true
btnremove.write = function(self, section)
o = s:option(Button, "_remove")
o.inputtitle= translate("Remove")
o.template = "dockerman/cbi/inlinebutton"
o.inputstyle = "remove"
o.forcewrite = true
o.write = function(self, section)
local network_selected = {}
local network_name_selected = {}
local network_driver_selected = {}
-- 遍历table中sectionid
local network_table_sids = network_table:cfgsections()
for _, network_table_sid in ipairs(network_table_sids) do
-- 得到选中项的名字
if network_list[network_table_sid]._selected == 1 then
network_selected[#network_selected+1] = network_list[network_table_sid]._id --network_name:cfgvalue(network_table_sid)
network_name_selected[#network_name_selected+1] = network_list[network_table_sid]._name
network_driver_selected[#network_driver_selected+1] = network_list[network_table_sid]._driver
for k in pairs(network_list) do
if network_list[k]._selected == 1 then
network_selected[#network_selected + 1] = network_list[k]._id
network_name_selected[#network_name_selected + 1] = network_list[k]._name
network_driver_selected[#network_driver_selected + 1] = network_list[k]._driver
end
end
if next(network_selected) ~= nil then
local success = true
docker:clear_status()
for ii, net in ipairs(network_selected) do
docker:append_status("Networks: " .. "remove" .. " " .. net .. "...")
local res = dk.networks["remove"](dk, {id = net})
if res and res.code >= 300 then
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
success = false
@ -120,6 +141,7 @@ btnremove.write = function(self, section)
end
end
end
if success then
docker:clear_status()
end

View file

@ -3,39 +3,237 @@ LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local uci = luci.model.uci.cursor()
local docker = require "luci.model.docker"
local m, s, o
local dk = docker.new()
local cmd_line = table.concat(arg, '/')
local create_body = {}
local images = dk.images:list().body
local networks = dk.networks:list().body
local containers = dk.containers:list({query = {all=true}}).body
local containers = dk.containers:list({
query = {
all=true
}
}).body
local is_quot_complete = function(str)
local num = 0, w
require "math"
if not str then return true end
if not str then
return true
end
local num = 0, w
for w in str:gmatch("\"") do
num = num + 1
end
if math.fmod(num, 2) ~= 0 then return false end
if math.fmod(num, 2) ~= 0 then
return false
end
num = 0
for w in str:gmatch("\'") do
num = num + 1
end
if math.fmod(num, 2) ~= 0 then return false end
if math.fmod(num, 2) ~= 0 then
return false
end
return true
end
function contains(list, x)
for _, v in pairs(list) do
if v == x then
return true
end
end
return false
end
local resolve_cli = function(cmd_line)
local config = {advance = 1}
local key_no_val = '|t|d|i|tty|rm|read_only|interactive|init|help|detach|privileged|P|publish_all|'
local key_with_val = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|blkio_weight|cgroup_parent|cidfile|cpu_period|cpu_quota|cpu_rt_period|cpu_rt_runtime|c|cpu_shares|cpus|cpuset_cpus|cpuset_mems|detach_keys|disable_content_trust|domainname|entrypoint|gpus|health_cmd|health_interval|health_retries|health_start_period|health_timeout|h|hostname|ip|ip6|ipc|isolation|kernel_memory|log_driver|mac_address|m|memory|memory_reservation|memory_swap|memory_swappiness|mount|name|network|no_healthcheck|oom_kill_disable|oom_score_adj|pid|pids_limit|restart|runtime|shm_size|sig_proxy|stop_signal|stop_timeout|ulimit|u|user|userns|uts|volume_driver|w|workdir|'
local key_abb = {net='network',a='attach',c='cpu-shares',d='detach',e='env',h='hostname',i='interactive',l='label',m='memory',p='publish',P='publish_all',t='tty',u='user',v='volume',w='workdir'}
local key_with_list = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|'
local config = {
advance = 1
}
local key_no_val = {
't',
'd',
'i',
'tty',
'rm',
'read_only',
'interactive',
'init',
'help',
'detach',
'privileged',
'P',
'publish_all',
}
local key_with_val = {
'sysctl',
'add_host',
'a',
'attach',
'blkio_weight_device',
'cap_add',
'cap_drop',
'device',
'device_cgroup_rule',
'device_read_bps',
'device_read_iops',
'device_write_bps',
'device_write_iops',
'dns',
'dns_option',
'dns_search',
'e',
'env',
'env_file',
'expose',
'group_add',
'l',
'label',
'label_file',
'link',
'link_local_ip',
'log_driver',
'log_opt',
'network_alias',
'p',
'publish',
'security_opt',
'storage_opt',
'tmpfs',
'v',
'volume',
'volumes_from',
'blkio_weight',
'cgroup_parent',
'cidfile',
'cpu_period',
'cpu_quota',
'cpu_rt_period',
'cpu_rt_runtime',
'c',
'cpu_shares',
'cpus',
'cpuset_cpus',
'cpuset_mems',
'detach_keys',
'disable_content_trust',
'domainname',
'entrypoint',
'gpus',
'health_cmd',
'health_interval',
'health_retries',
'health_start_period',
'health_timeout',
'h',
'hostname',
'ip',
'ip6',
'ipc',
'isolation',
'kernel_memory',
'log_driver',
'mac_address',
'm',
'memory',
'memory_reservation',
'memory_swap',
'memory_swappiness',
'mount',
'name',
'network',
'no_healthcheck',
'oom_kill_disable',
'oom_score_adj',
'pid',
'pids_limit',
'restart',
'runtime',
'shm_size',
'sig_proxy',
'stop_signal',
'stop_timeout',
'ulimit',
'u',
'user',
'userns',
'uts',
'volume_driver',
'w',
'workdir'
}
local key_abb = {
net='network',
a='attach',
c='cpu-shares',
d='detach',
e='env',
h='hostname',
i='interactive',
l='label',
m='memory',
p='publish',
P='publish_all',
t='tty',
u='user',
v='volume',
w='workdir'
}
local key_with_list = {
'sysctl',
'add_host',
'a',
'attach',
'blkio_weight_device',
'cap_add',
'cap_drop',
'device',
'device_cgroup_rule',
'device_read_bps',
'device_read_iops',
'device_write_bps',
'device_write_iops',
'dns',
'dns_optiondns_search',
'e',
'env',
'env_file',
'expose',
'group_add',
'l',
'label',
'label_file',
'link',
'link_local_ip',
'log_driver',
'log_opt',
'network_alias',
'p',
'publish',
'security_opt',
'storage_opt',
'tmpfs',
'v',
'volume',
'volumes_from',
}
local key = nil
local _key = nil
local val = nil
@ -72,7 +270,9 @@ local resolve_cli = function(cmd_line)
config[key_abb["P"]] = true
key:gsub("P", "")
end
if key == "" then key = nil end
if key == "" then
key = nil
end
end
end
end
@ -80,11 +280,11 @@ local resolve_cli = function(cmd_line)
if key then
key = key:gsub("-","_")
key = key_abb[key] or key
if key_no_val:match("|"..key.."|") then
if contains(key_no_val, key) then
config[key] = true
val = nil
key = nil
elseif key_with_val:match("|"..key.."|") then
elseif contains(key_with_val, key) then
-- if key == "cap_add" then config.privileged = true end
else
key = nil
@ -104,18 +304,19 @@ local resolve_cli = function(cmd_line)
local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or ""
local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or ""
local ro = w:match(",readonly") and "ro" or nil
if source and target then
if _type ~= "tmpfs" then
-- bind or volume
local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil
val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or ""))
else
-- tmpfs
local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil
local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil
key = "tmpfs"
val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "")
if not config[key] then config[key] = {} end
if not config[key] then
config[key] = {}
end
table.insert( config[key], val )
key = nil
val = nil
@ -129,15 +330,16 @@ local resolve_cli = function(cmd_line)
end
if (key or _key) and val then
key = _key or key
if key_with_list:match("|"..key.."|") then
if not config[key] then config[key] = {} end
if contains(key_with_list, key) then
if not config[key] then
config[key] = {}
end
if _key then
config[key][#config[key]] = config[key][#config[key]] .. " " .. w
else
table.insert( config[key], val )
end
if is_quot_complete(config[key][#config[key]]) then
-- clear quotation marks
config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "")
_key = nil
else
@ -146,7 +348,6 @@ local resolve_cli = function(cmd_line)
else
config[key] = (config[key] and (config[key] .. " ") or "") .. val
if is_quot_complete(config[key]) then
-- clear quotation marks
config[key] = config[key]:gsub("[\"\']", "")
_key = nil
else
@ -157,16 +358,22 @@ local resolve_cli = function(cmd_line)
val = nil
end
end
return config
end
-- reslvo default config
local default_config = {}
if cmd_line and cmd_line:match("^DOCKERCLI.+") then
default_config = resolve_cli(cmd_line)
elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
local container_id = cmd_line:match("^duplicate/(.+)")
create_body = dk:containers_duplicate_config({id = container_id}) or {}
if not create_body.HostConfig then create_body.HostConfig = {} end
if 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
@ -175,8 +382,6 @@ elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
default_config.interactive = create_body.OpenStdin and true or false
default_config.privileged = create_body.HostConfig.Privileged and true or false
default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil
-- default_config.network = create_body.HostConfig.NetworkMode == "default" and "bridge" or create_body.HostConfig.NetworkMode
-- if container has leave original network, and add new network, .HostConfig.NetworkMode is INcorrect, so using first child of .NetworkingConfig.EndpointsConfig
default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil
default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil
default_config.link = create_body.HostConfig.Links
@ -221,6 +426,7 @@ elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") )
end
end
if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then
default_config.tmpfs = {}
for k, v in pairs(create_body.HostConfig.Tmpfs) do
@ -230,71 +436,69 @@ elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
end
end
local m = SimpleForm("docker", translate("Docker"))
m = SimpleForm("docker", translate("Docker"))
m.redirect = luci.dispatcher.build_url("admin", "docker", "containers")
-- m.reset = false
-- m.submit = false
-- new Container
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
s = m:section(SimpleSection)
s.template = "dockerman/apply_widget"
s.err=docker:read_status()
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if s.err then
docker:clear_status()
end
local s = m:section(SimpleSection, translate("New Container"))
s = m:section(SimpleSection, translate("New Container"))
s.addremove = true
s.anonymous = true
local d = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
d.rawhtml = true
d.template = "dockerman/newcontainer_resolve"
o = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
o.rawhtml = true
o.template = "dockerman/newcontainer_resolve"
d = s:option(Value, "name", translate("Container Name"))
d.rmempty = true
d.default = default_config.name or nil
o = s:option(Value, "name", translate("Container Name"))
o.rmempty = true
o.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
o = s:option(Flag, "interactive", translate("Interactive (-i)"))
o.rmempty = true
o.disabled = 0
o.enabled = 1
o.default = default_config.interactive and 1 or 0
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
o = s:option(Flag, "tty", translate("TTY (-t)"))
o.rmempty = true
o.disabled = 0
o.enabled = 1
o.default = default_config.tty and 1 or 0
d = s:option(Value, "image", translate("Docker Image"))
d.rmempty = true
d.default = default_config.image or nil
o = s:option(Value, "image", translate("Docker Image"))
o.rmempty = true
o.default = default_config.image or nil
for _, v in ipairs (images) do
if v.RepoTags then
d:value(v.RepoTags[1], v.RepoTags[1])
o:value(v.RepoTags[1], v.RepoTags[1])
end
end
d = s:option(Flag, "_force_pull", translate("Always pull image first"))
d.rmempty = true
d.disabled = 0
d.enabled = 1
d.default = 0
o = s:option(Flag, "_force_pull", translate("Always pull image first"))
o.rmempty = true
o.disabled = 0
o.enabled = 1
o.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
o = s:option(Flag, "privileged", translate("Privileged"))
o.rmempty = true
o.disabled = 0
o.enabled = 1
o.default = default_config.privileged and 1 or 0
d = s:option(ListValue, "restart", translate("Restart Policy"))
d.rmempty = true
d:value("no", "No")
d:value("unless-stopped", "Unless stopped")
d:value("always", "Always")
d:value("on-failure", "On failure")
d.default = default_config.restart or "unless-stopped"
o = s:option(ListValue, "restart", translate("Restart Policy"))
o.rmempty = true
o:value("no", "No")
o:value("unless-stopped", "Unless stopped")
o:value("always", "Always")
o:value("on-failure", "On failure")
o.default = default_config.restart or "unless-stopped"
local d_network = s:option(ListValue, "network", translate("Networks"))
d_network.rmempty = true
@ -305,116 +509,146 @@ d_ip.datatype="ip4addr"
d_ip:depends("network", "nil")
d_ip.default = default_config.ip or nil
d = s:option(DynamicList, "link", translate("Links with other containers"))
d.placeholder = "container_name:alias"
d.rmempty = true
d:depends("network", "bridge")
d.default = default_config.link or nil
o = s:option(DynamicList, "link", translate("Links with other containers"))
o.placeholder = "container_name:alias"
o.rmempty = true
o:depends("network", "bridge")
o.default = default_config.link or nil
d = s:option(DynamicList, "dns", translate("Set custom DNS servers"))
d.placeholder = "8.8.8.8"
d.rmempty = true
d.default = default_config.dns or nil
o = s:option(DynamicList, "dns", translate("Set custom DNS servers"))
o.placeholder = "8.8.8.8"
o.rmempty = true
o.default = default_config.dns or nil
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
o = s:option(Value, "user",
translate("User(-u)"),
translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])"))
o.placeholder = "1000:1000"
o.rmempty = true
o.default = default_config.user or nil
d = s:option(DynamicList, "env", translate("Environmental Variable(-e)"), translate("Set environment variables to inside the container"))
d.placeholder = "TZ=Asia/Shanghai"
d.rmempty = true
d.default = default_config.env or nil
o = s:option(DynamicList, "env",
translate("Environmental Variable(-e)"),
translate("Set environment variables to inside the container"))
o.placeholder = "TZ=Asia/Shanghai"
o.rmempty = true
o.default = default_config.env or nil
d = s:option(DynamicList, "volume", translate("Bind Mount(-v)"), translate("Bind mount a volume"))
d.placeholder = "/media:/media:slave"
d.rmempty = true
d.default = default_config.volume or nil
o = s:option(DynamicList, "volume",
translate("Bind Mount(-v)"),
translate("Bind mount a volume"))
o.placeholder = "/media:/media:slave"
o.rmempty = true
o.default = default_config.volume or nil
local d_publish = s:option(DynamicList, "publish", translate("Exposed Ports(-p)"), translate("Publish container's port(s) to the host"))
local d_publish = s:option(DynamicList, "publish",
translate("Exposed Ports(-p)"),
translate("Publish container's port(s) to the host"))
d_publish.placeholder = "2200:22/tcp"
d_publish.rmempty = true
d_publish.default = default_config.publish or nil
d = s:option(Value, "command", translate("Run command"))
d.placeholder = "/bin/sh init.sh"
d.rmempty = true
d.default = default_config.command or nil
o = s:option(Value, "command", translate("Run command"))
o.placeholder = "/bin/sh init.sh"
o.rmempty = true
o.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
o = s:option(Flag, "advance", translate("Advance"))
o.rmempty = true
o.disabled = 0
o.enabled = 1
o.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)
o = s:option(Value, "hostname",
translate("Host Name"),
translate("The hostname to use for the container"))
o.rmempty = true
o.default = default_config.hostname or nil
o:depends("advance", 1)
d = s:option(Flag, "publish_all", translate("Exposed All Ports(-P)"), translate("Allocates an ephemeral host port for all of a container's exposed ports"))
d.rmempty = true
d.disabled = 0
d.enabled = 1
d.default = default_config.publish_all and 1 or 0
d:depends("advance", 1)
o = s:option(Flag, "publish_all",
translate("Exposed All Ports(-P)"),
translate("Allocates an ephemeral host port for all of a container's exposed ports"))
o.rmempty = true
o.disabled = 0
o.enabled = 1
o.default = default_config.publish_all and 1 or 0
o:depends("advance", 1)
d = s:option(DynamicList, "device", translate("Device(--device)"), translate("Add host device to the container"))
d.placeholder = "/dev/sda:/dev/xvdc:rwm"
d.rmempty = true
d:depends("advance", 1)
d.default = default_config.device or nil
o = s:option(DynamicList, "device",
translate("Device(--device)"),
translate("Add host device to the container"))
o.placeholder = "/dev/sda:/dev/xvdc:rwm"
o.rmempty = true
o:depends("advance", 1)
o.default = default_config.device or nil
d = s:option(DynamicList, "tmpfs", translate("Tmpfs(--tmpfs)"), translate("Mount tmpfs directory"))
d.placeholder = "/run:rw,noexec,nosuid,size=65536k"
d.rmempty = true
d:depends("advance", 1)
d.default = default_config.tmpfs or nil
o = s:option(DynamicList, "tmpfs",
translate("Tmpfs(--tmpfs)"),
translate("Mount tmpfs directory"))
o.placeholder = "/run:rw,noexec,nosuid,size=65536k"
o.rmempty = true
o:depends("advance", 1)
o.default = default_config.tmpfs or nil
d = s:option(DynamicList, "sysctl", translate("Sysctl(--sysctl)"), translate("Sysctls (kernel parameters) options"))
d.placeholder = "net.ipv4.ip_forward=1"
d.rmempty = true
d:depends("advance", 1)
d.default = default_config.sysctl or nil
o = s:option(DynamicList, "sysctl",
translate("Sysctl(--sysctl)"),
translate("Sysctls (kernel parameters) options"))
o.placeholder = "net.ipv4.ip_forward=1"
o.rmempty = true
o:depends("advance", 1)
o.default = default_config.sysctl or nil
d = s:option(DynamicList, "cap_add", translate("CAP-ADD(--cap-add)"), translate("A list of kernel capabilities to add to the container"))
d.placeholder = "NET_ADMIN"
d.rmempty = true
d:depends("advance", 1)
d.default = default_config.cap_add or nil
o = s:option(DynamicList, "cap_add",
translate("CAP-ADD(--cap-add)"),
translate("A list of kernel capabilities to add to the container"))
o.placeholder = "NET_ADMIN"
o.rmempty = true
o:depends("advance", 1)
o.default = default_config.cap_add or nil
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
o = s:option(Value, "cpus",
translate("CPUs"),
translate("Number of CPUs. Number is a fractional number. 0.000 means no limit"))
o.placeholder = "1.5"
o.rmempty = true
o:depends("advance", 1)
o.datatype="ufloat"
o.default = default_config.cpus or nil
d = s:option(Value, "cpu_shares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024"))
d.placeholder = "1024"
d.rmempty = true
d:depends("advance", 1)
d.datatype="uinteger"
d.default = default_config.cpu_shares or nil
o = s:option(Value, "cpu_shares",
translate("CPU Shares Weight"),
translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024"))
o.placeholder = "1024"
o.rmempty = true
o:depends("advance", 1)
o.datatype="uinteger"
o.default = default_config.cpu_shares or nil
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
o = s:option(Value, "memory",
translate("Memory"),
translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M"))
o.placeholder = "128m"
o.rmempty = true
o:depends("advance", 1)
o.default = default_config.memory or nil
d = s:option(Value, "blkio_weight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000"))
d.placeholder = "500"
d.rmempty = true
d:depends("advance", 1)
d.datatype="uinteger"
d.default = default_config.blkio_weight or nil
o = s:option(Value, "blkio_weight",
translate("Block IO Weight"),
translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000"))
o.placeholder = "500"
o.rmempty = true
o:depends("advance", 1)
o.datatype="uinteger"
o.default = default_config.blkio_weight or nil
d = s:option(DynamicList, "log_opt", translate("Log driver options"), translate("The logging configuration for this container"))
d.placeholder = "max-size=1m"
d.rmempty = true
d:depends("advance", 1)
d.default = default_config.log_opt or nil
o = s:option(DynamicList, "log_opt",
translate("Log driver options"),
translate("The logging configuration for this container"))
o.placeholder = "max-size=1m"
o.rmempty = true
o:depends("advance", 1)
o.default = default_config.log_opt or nil
for _, v in ipairs (networks) do
if v.Name then
@ -435,7 +669,10 @@ for _, v in ipairs (networks) do
end
m.handle = function(self, state, data)
if state ~= FORM_VALID then return end
if state ~= FORM_VALID then
return
end
local tmp
local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S"))
local hostname = data.hostname
@ -444,15 +681,18 @@ m.handle = function(self, state, data)
local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false
local image = data.image
local user = data.user
if image and not image:match(".-:.+") then
image = image .. ":latest"
end
local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false
local restart = data.restart
local env = data.env
local dns = data.dns
local cap_add = data.cap_add
local sysctl = {}
tmp = data.sysctl
if type(tmp) == "table" then
for i, v in ipairs(tmp) do
@ -462,6 +702,7 @@ m.handle = function(self, state, data)
end
end
end
local log_opt = {}
tmp = data.log_opt
if type(tmp) == "table" then
@ -472,6 +713,7 @@ m.handle = function(self, state, data)
end
end
end
local network = data.network
local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
local volume = data.volume
@ -482,6 +724,7 @@ m.handle = function(self, state, data)
local portbindings = {}
local exposedports = {}
local tmpfs = {}
tmp = data.tmpfs
if type(tmp) == "table" then
@ -516,6 +759,7 @@ m.handle = function(self, state, data)
t['CgroupPermissions'] = "rwm"
end
end
if next(t) ~= nil then
table.insert( device, t )
end
@ -542,6 +786,7 @@ m.handle = function(self, state, data)
command[#command+1] = v
end
end
if memory ~= 0 then
_,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
if n then
@ -577,14 +822,15 @@ m.handle = function(self, state, data)
create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9
create_body.HostConfig.BlkioWeight = tonumber(blkio_weight)
create_body.HostConfig.PublishAllPorts = publish_all
if create_body.HostConfig.NetworkMode ~= network then
-- network mode changed, need to clear duplicate config
create_body.NetworkingConfig = nil
end
create_body.HostConfig.NetworkMode = network
if ip then
if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then
-- ip + duplicate config
for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do
if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then
v.IPAMConfig.IPv4Address = ip
@ -594,13 +840,12 @@ m.handle = function(self, state, data)
break
end
else
-- ip + no duplicate config
create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } }
end
elseif not create_body.NetworkingConfig then
-- no ip + no duplicate config
create_body.NetworkingConfig = nil
end
create_body["HostConfig"]["Tmpfs"] = tmpfs
create_body["HostConfig"]["Devices"] = device
create_body["HostConfig"]["Sysctls"] = sysctl
@ -610,6 +855,7 @@ m.handle = function(self, state, data)
if network == "bridge" then
create_body["HostConfig"]["Links"] = link
end
local pull_image = function(image)
local json_stringify = luci.jsonc and luci.jsonc.stringify
docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n")
@ -622,8 +868,10 @@ m.handle = function(self, state, data)
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
end
end
docker:clear_status()
local exist_image = false
if image then
for _, v in ipairs (images) do
if v.RepoTags and v.RepoTags[1] == image then
@ -639,6 +887,7 @@ m.handle = function(self, state, data)
end
create_body = docker.clear_empty_tables(create_body)
docker:append_status("Container: " .. "create" .. " " .. name .. "...")
local res = dk.containers:create({name = name, body = create_body})
if res and res.code == 201 then

View file

@ -3,121 +3,127 @@ LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local docker = require "luci.model.docker"
local m, s, o
local dk = docker.new()
m = SimpleForm("docker", translate("Docker"))
m.redirect = luci.dispatcher.build_url("admin", "docker", "networks")
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
s = m:section(SimpleSection)
s.template = "dockerman/apply_widget"
s.err=docker:read_status()
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if s.err then
docker:clear_status()
end
s = m:section(SimpleSection, translate("New Network"))
s.addremove = true
s.anonymous = true
d = s:option(Value, "name", translate("Network Name"))
d.rmempty = true
o = s:option(Value, "name", translate("Network Name"))
o.rmempty = true
d = s:option(ListValue, "dirver", translate("Driver"))
d.rmempty = true
d:value("bridge", "bridge")
d:value("macvlan", "macvlan")
d:value("ipvlan", "ipvlan")
d:value("overlay", "overlay")
o = s:option(ListValue, "dirver", translate("Driver"))
o.rmempty = true
o:value("bridge", "bridge")
o:value("macvlan", "macvlan")
o:value("ipvlan", "ipvlan")
o:value("overlay", "overlay")
d = s:option(Value, "parent", translate("Parent Interface"))
d.rmempty = true
d:depends("dirver", "macvlan")
o = s:option(Value, "parent", translate("Parent Interface"))
o.rmempty = true
o:depends("dirver", "macvlan")
local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
for _, v in ipairs(interfaces) do
d:value(v, v)
o:value(v, v)
end
d.default="br-lan"
d.placeholder="br-lan"
o.default="br-lan"
o.placeholder="br-lan"
d = s:option(Value, "macvlan_mode", translate("Macvlan Mode"))
d.rmempty = true
d:depends("dirver", "macvlan")
d.default="bridge"
d:value("bridge", "bridge")
d:value("private", "private")
d:value("vepa", "vepa")
d:value("passthru", "passthru")
o = s:option(Value, "macvlan_mode", translate("Macvlan Mode"))
o.rmempty = true
o:depends("dirver", "macvlan")
o.default="bridge"
o:value("bridge", "bridge")
o:value("private", "private")
o:value("vepa", "vepa")
o:value("passthru", "passthru")
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")
o = s:option(Value, "ipvlan_mode", translate("Ipvlan Mode"))
o.rmempty = true
o:depends("dirver", "ipvlan")
o.default="l3"
o:value("l2", "l2")
o:value("l3", "l3")
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")
o = s:option(Flag, "ingress",
translate("Ingress"),
translate("Ingress network is the network which provides the routing-mesh in swarm mode"))
o.rmempty = true
o.disabled = 0
o.enabled = 1
o.default = 0
o:depends("dirver", "overlay")
d = s:option(DynamicList, "options", translate("Options"))
d.rmempty = true
d.placeholder="com.docker.network.driver.mtu=1500"
o = s:option(DynamicList, "options", translate("Options"))
o.rmempty = true
o.placeholder="com.docker.network.driver.mtu=1500"
d = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network"))
d.rmempty = true
d:depends("dirver", "overlay")
d.disabled = 0
d.enabled = 1
d.default = 0
o = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network"))
o.rmempty = true
o:depends("dirver", "overlay")
o.disabled = 0
o.enabled = 1
o.default = 0
if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then
d = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt"))
d:depends("dirver", "macvlan")
d.disabled = 0
d.enabled = 1
d.default = 1
o = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt"))
o:depends("dirver", "macvlan")
o.disabled = 0
o.enabled = 1
o.default = 1
end
d = s:option(Value, "subnet", translate("Subnet"))
d.rmempty = true
d.placeholder="10.1.0.0/16"
d.datatype="ip4addr"
o = s:option(Value, "subnet", translate("Subnet"))
o.rmempty = true
o.placeholder="10.1.0.0/16"
o.datatype="ip4addr"
d = s:option(Value, "gateway", translate("Gateway"))
d.rmempty = true
d.placeholder="10.1.1.1"
d.datatype="ip4addr"
o = s:option(Value, "gateway", translate("Gateway"))
o.rmempty = true
o.placeholder="10.1.1.1"
o.datatype="ip4addr"
d = s:option(Value, "ip_range", translate("IP range"))
d.rmempty = true
d.placeholder="10.1.1.0/24"
d.datatype="ip4addr"
o = s:option(Value, "ip_range", translate("IP range"))
o.rmempty = true
o.placeholder="10.1.1.0/24"
o.datatype="ip4addr"
d = s:option(DynamicList, "aux_address", translate("Exclude IPs"))
d.rmempty = true
d.placeholder="my-route=10.1.1.1"
o = s:option(DynamicList, "aux_address", translate("Exclude IPs"))
o.rmempty = true
o.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
o = s:option(Flag, "ipv6", translate("Enable IPv6"))
o.rmempty = true
o.disabled = 0
o.enabled = 1
o.default = 0
d = s:option(Value, "subnet6", translate("IPv6 Subnet"))
d.rmempty = true
d.placeholder="fe80::/10"
d.datatype="ip6addr"
d:depends("ipv6", 1)
o = s:option(Value, "subnet6", translate("IPv6 Subnet"))
o.rmempty = true
o.placeholder="fe80::/10"
o.datatype="ip6addr"
o:depends("ipv6", 1)
d = s:option(Value, "gateway6", translate("IPv6 Gateway"))
d.rmempty = true
d.placeholder="fe80::1"
d.datatype="ip6addr"
d:depends("ipv6", 1)
o = s:option(Value, "gateway6", translate("IPv6 Gateway"))
o.rmempty = true
o.placeholder="fe80::1"
o.datatype="ip6addr"
o:depends("ipv6", 1)
m.handle = function(self, state, data)
if state == FORM_VALID then
@ -167,6 +173,7 @@ m.handle = function(self, state, data)
}
}
end
if driver == "macvlan" then
create_body["Options"] = {
macvlan_mode = data.macvlan_mode,
@ -200,15 +207,33 @@ m.handle = function(self, state, data)
create_body = docker.clear_empty_tables(create_body)
docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
local res = dk.networks:create({body = create_body})
local res = dk.networks:create({
body = create_body
})
if res and res.code == 201 then
docker:write_status("Network: " .. "create macvlan interface...")
res = dk.networks:inspect({ name = create_body.Name })
if driver == "macvlan" and data.op_macvlan ~= 0 and res.code == 200
and res.body and res.body.IPAM and res.body.IPAM.Config and res.body.IPAM.Config[1]
and res.body.IPAM.Config[1].Gateway and res.body.IPAM.Config[1].Subnet then
docker.create_macvlan_interface(data.name, data.parent, res.body.IPAM.Config[1].Gateway, res.body.IPAM.Config[1].Subnet)
res = dk.networks:inspect({
name = create_body.Name
})
if driver == "macvlan" and
data.op_macvlan ~= 0 and
res.code == 200 and
res.body and
res.body.IPAM and
res.body.IPAM.Config and
res.body.IPAM.Config[1] and
res.body.IPAM.Config[1].Gateway and
res.body.IPAM.Config[1].Subnet then
docker.create_macvlan_interface(data.name,
data.parent,
res.body.IPAM.Config[1].Gateway,
res.body.IPAM.Config[1].Subnet)
end
docker:clear_status()
luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
else

View file

@ -3,9 +3,9 @@ LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local docker = require "luci.model.docker"
local uci = require "luci.model.uci"
local m, s, o
function byte_format(byte)
local suff = {"B", "KB", "MB", "GB", "TB"}
@ -18,11 +18,14 @@ function byte_format(byte)
end
end
local map_dockerman = Map("dockerman", translate("Docker"), translate("DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:") .. " ".. [[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] ..translate("Github") .. [[</a>]])
m = Map("dockerd", translate("Docker"),
translate("DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:") ..
" " ..
[[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] ..
translate("Github") ..
[[</a>]])
local docker_info_table = {}
-- docker_info_table['0OperatingSystem'] = {_key=translate("Operating System"),_value='-'}
-- docker_info_table['1Architecture'] = {_key=translate("Architecture"),_value='-'}
-- docker_info_table['2KernelVersion'] = {_key=translate("Kernel Version"),_value='-'}
docker_info_table['3ServerVersion'] = {_key=translate("Docker Version"),_value='-'}
docker_info_table['4ApiVersion'] = {_key=translate("Api Version"),_value='-'}
docker_info_table['5NCPU'] = {_key=translate("CPUs"),_value='-'}
@ -31,29 +34,29 @@ docker_info_table['7DockerRootDir'] = {_key=translate("Docker Root Dir"),_value=
docker_info_table['8IndexServerAddress'] = {_key=translate("Index Server Address"),_value='-'}
docker_info_table['9RegistryMirrors'] = {_key=translate("Registry Mirrors"),_value='-'}
local s = map_dockerman:section(Table, docker_info_table)
s = m:section(Table, docker_info_table)
s:option(DummyValue, "_key", translate("Info"))
s:option(DummyValue, "_value")
s = map_dockerman:section(SimpleSection)
s = m:section(SimpleSection)
s.template = "dockerman/overview"
s.containers_running = '-'
s.images_used = '-'
s.containers_total = '-'
s.images_total = '-'
s.networks_total = '-'
s.volumes_total = '-'
local containers_list
-- local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
if (require "luci.model.docker").new():_ping().code == 200 then
if docker.new():_ping().code == 200 then
local dk = docker.new()
containers_list = dk.containers:list({query = {all=true}}).body
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)
@ -63,6 +66,7 @@ if (require "luci.model.docker").new():_ping().code == 200 then
local size = statvfs and (statvfs.bavail * statvfs.bsize) or 0
docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(byte_format(size)) .. " " .. translate("Available") .. ")"
end
docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress
for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do
docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v)
@ -77,6 +81,7 @@ if (require "luci.model.docker").new():_ping().code == 200 then
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)
@ -84,71 +89,67 @@ if (require "luci.model.docker").new():_ping().code == 200 then
s.networks_total = tostring(#networks_list)
s.volumes_total = tostring(#volumes_list)
end
s.template = "dockerman/overview"
local section_dockerman = map_dockerman:section(NamedSection, "local", "section", translate("Setting"))
section_dockerman:tab("daemon", translate("Docker Daemon"))
section_dockerman:tab("ac", translate("Access Control"))
section_dockerman:tab("dockerman", translate("DockerMan"))
s = m:section(NamedSection, "globals", "section", translate("Setting"))
local socket_path = section_dockerman:taboption("dockerman", Value, "socket_path", translate("Docker Socket Path"))
socket_path.default = "/var/run/docker.sock"
socket_path.placeholder = "/var/run/docker.sock"
socket_path.rmempty = false
o = s:option(Flag, "remote_endpoint",
translate("Remote Endpoint"),
translate("Connect to remote endpoint"))
o.rmempty = false
local remote_endpoint = section_dockerman:taboption("dockerman", Flag, "remote_endpoint", translate("Remote Endpoint"), translate("Dockerman connect to remote endpoint"))
remote_endpoint.rmempty = false
remote_endpoint.enabled = "true"
remote_endpoint.disabled = "false"
o = s:option(Value, "socket_path",
translate("Docker Socket Path"))
o.default = "unix://var/run/docker.sock"
o.placeholder = "unix://var/run/docker.sock"
o:depends("remote_endpoint", 1)
local remote_host = section_dockerman:taboption("dockerman", Value, "remote_host", translate("Remote Host"))
remote_host.placeholder = "10.1.1.2"
-- remote_host:depends("remote_endpoint", "true")
o = s:option(Value, "remote_host",
translate("Remote Host"))
o.placeholder = "10.1.1.2"
o:depends("remote_endpoint", 1)
local remote_port = section_dockerman:taboption("dockerman", Value, "remote_port", translate("Remote Port"))
remote_port.placeholder = "2375"
remote_port.default = "2375"
-- remote_port:depends("remote_endpoint", "true")
-- local status_path = section_dockerman:taboption("dockerman", Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file"))
-- local debug = section_dockerman:taboption("dockerman", Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path"))
-- debug.enabled="true"
-- debug.disabled="false"
-- local debug_path = section_dockerman:taboption("dockerman", Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile"))
o = s:option(Value, "remote_port",
translate("Remote Port"))
o.placeholder = "2375"
o.default = "2375"
o:depends("remote_endpoint", 1)
if nixio.fs.access("/usr/bin/dockerd") then
local allowed_interface = section_dockerman:taboption("ac", DynamicList, "ac_allowed_interface", translate("Allowed access interfaces"), translate("Which interface(s) can access containers under the bridge network, fill-in Interface Name"))
local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
for i, v in ipairs(interfaces) do
allowed_interface:value(v, v)
end
local allowed_container = section_dockerman:taboption("ac", DynamicList, "ac_allowed_container", translate("Containers allowed to be accessed"), translate("Which container(s) under bridge network can be accessed, even from interfaces that are not allowed, fill-in Container Id or Name"))
-- allowed_container.placeholder = "container name_or_id"
if containers_list then
for i, v in ipairs(containers_list) do
if v.State == "running" and v.NetworkSettings and v.NetworkSettings.Networks and v.NetworkSettings.Networks.bridge and v.NetworkSettings.Networks.bridge.IPAddress then
allowed_container:value(v.Id:sub(1,12), v.Names[1]:sub(2) .. " | " .. v.NetworkSettings.Networks.bridge.IPAddress)
end
end
o = s:option(Value, "data_root",
translate("Docker Root Dir"))
o.placeholder = "/opt/docker/"
o:depends("remote_endpoint", 0)
o = s:option(Value, "bip",
translate("Default bridge"),
translate("Configure the default bridge network"))
o.placeholder = "172.17.0.1/16"
o.default = "172.17.0.1/16"
o.datatype = "ipaddr"
o:depends("remote_endpoint", 0)
o = s:option(DynamicList, "registry_mirrors",
translate("Registry Mirrors"))
o:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com")
o:depends("remote_endpoint", 0)
o = s:option(ListValue, "log_level",
translate("Log Level"),
translate('Set the logging level'))
o:value("debug", "debug")
o:value("info", "info")
o:value("warn", "warn")
o:value("error", "error")
o:value("fatal", "fatal")
o:depends("remote_endpoint", 0)
o = s:option(DynamicList, "hosts",
translate("Client connection"),
translate('Specifies where the Docker daemon will listen for client connections'))
o:value("unix://var/run/docker.sock", "unix://var/run/docker.sock")
o:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375")
o.rmempty = true
o:depends("remote_endpoint", 0)
end
local dockerd_enable = section_dockerman:taboption("daemon", Flag, "daemon_ea", translate("Enable"))
dockerd_enable.enabled = "true"
dockerd_enable.rmempty = true
local data_root = section_dockerman:taboption("daemon", Value, "daemon_data_root", translate("Docker Root Dir"))
data_root.placeholder = "/opt/docker/"
local registry_mirrors = section_dockerman:taboption("daemon", DynamicList, "daemon_registry_mirrors", translate("Registry Mirrors"))
registry_mirrors:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com")
local log_level = section_dockerman:taboption("daemon", ListValue, "daemon_log_level", translate("Log Level"), translate('Set the logging level'))
log_level:value("debug", "debug")
log_level:value("info", "info")
log_level:value("warn", "warn")
log_level:value("error", "error")
log_level:value("fatal", "fatal")
local hosts = section_dockerman:taboption("daemon", DynamicList, "daemon_hosts", translate("Server Host"), translate('Daemon unix socket (unix:///var/run/docker.sock) or TCP Remote Hosts (tcp://0.0.0.0:2375), default: unix:///var/run/docker.sock'))
hosts:value("unix:///var/run/docker.sock", "unix:///var/run/docker.sock")
hosts:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375")
hosts.rmempty = true
end
return map_dockerman
return m

View file

@ -3,28 +3,26 @@ LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local uci = luci.model.uci.cursor()
local docker = require "luci.model.docker"
local dk = docker.new()
local containers, volumes
local res = dk.volumes:list()
if res.code <300 then volumes = res.body.Volumes else return end
res = dk.containers:list({query = {all=true}})
if res.code <300 then containers = res.body else return end
local m, s, o
local res, containers, volumes
function get_volumes()
local data = {}
for i, v in ipairs(volumes) do
-- local index = v.CreatedAt .. v.Name
local index = v.Name
data[index]={}
data[index]["_selected"] = 0
data[index]["_nameraw"] = v.Name
data[index]["_name"] = v.Name:sub(1,12)
for ci,cv in ipairs(containers) do
if cv.Mounts and type(cv.Mounts) ~= "table" then break end
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 "")..
@ -34,6 +32,7 @@ function get_volumes()
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) .. "..."
@ -43,59 +42,82 @@ function get_volumes()
end
data[index]["_created"] = v.CreatedAt
end
return data
end
res = dk.volumes:list()
if res.code <300 then
volumes = res.body.Volumes
else
return
end
res = dk.containers:list({
query = {
all=true
}
})
if res.code <300 then
containers = res.body
else
return
end
local volume_list = get_volumes()
-- m = Map("docker", translate("Docker"))
m = SimpleForm("docker", translate("Docker"))
m.submit=false
m.reset=false
s = m:section(Table, volume_list, translate("Volumes"))
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)
o = s:option(Flag, "_selected","")
o.disabled = 0
o.enabled = 1
o.default = 0
o.write = function(self, section, value)
volume_list[section]._selected = value
end
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
o = s:option(DummyValue, "_name", translate("Name"))
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)
o = s:option(DummyValue, "_driver", translate("Driver"))
o = s:option(DummyValue, "_containers", translate("Containers"))
o.rawhtml = true
o = s:option(DummyValue, "_mountpoint", translate("Mount Point"))
o = s:option(DummyValue, "_created", translate("Created"))
s = m:section(SimpleSection)
s.template = "dockerman/apply_widget"
s.err=docker:read_status()
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if s.err then
docker:clear_status()
end
s = m:section(Table,{{}})
s.notitle=true
s.rowcolors=false
s.template="cbi/nullsection"
o = s:option(Button, "remove")
o.inputtitle= translate("Remove")
o.template = "dockerman/cbi/inlinebutton"
o.inputstyle = "remove"
o.forcewrite = true
o.write = function(self, section)
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
for k in pairs(volume_list) do
if volume_list[k]._selected == 1 then
volume_selected[#volume_selected+1] = k
end
end
if next(volume_selected) ~= nil then
local success = true
docker:clear_status()
@ -109,8 +131,12 @@ btnremove.write = function(self, section)
docker:append_status("done\n")
end
end
if success then docker:clear_status() end
if success then
docker:clear_status()
end
luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes"))
end
end
return m

View file

@ -3,39 +3,60 @@ LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local docker = require "luci.docker"
local fs = require "nixio.fs"
local uci = (require "luci.model.uci").cursor()
local _docker = {}
_docker.options = {}
--pull image and return iamge id
local update_image = function(self, image_name)
local json_stringify = luci.jsonc and luci.jsonc.stringify
_docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n")
local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb)
if res and res.code == 200 and (#res.body > 0 and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image_name)) then
_docker:append_status("done\n")
else
res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)
end
new_image_id = self.images:inspect({name = image_name}).body.Id
return new_image_id, res
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
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
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
@ -49,12 +70,19 @@ local table_subtract = function(t1, t2)
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
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
@ -64,15 +92,9 @@ local map_subtract = function(t1, t2)
break
end
end
if not found then
res[k1] = v1
-- if v1 and type(v1) == "table" then
-- if next(v1) == nil then
-- res[k1] = { k = 'v' }
-- else
-- res[k1] = v1
-- end
-- end
end
end
@ -81,6 +103,7 @@ end
_docker.clear_empty_tables = function ( t )
local k, v
if next(t) == nil then
t = nil
else
@ -90,23 +113,39 @@ _docker.clear_empty_tables = function ( t )
end
end
end
return t
end
-- return create_body, extra_network
local get_config = function(container_config, image_config)
local config = container_config.Config
local old_host_config = container_config.HostConfig
local old_network_setting = container_config.NetworkSettings.Networks or {}
if config.WorkingDir == image_config.WorkingDir then config.WorkingDir = "" end
if config.User == image_config.User then config.User = "" end
if table_equal(config.Cmd, image_config.Cmd) then config.Cmd = nil end
if table_equal(config.Entrypoint, image_config.Entrypoint) then config.Entrypoint = nil end
if table_equal(config.ExposedPorts, image_config.ExposedPorts) then config.ExposedPorts = nil end
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
@ -114,10 +153,10 @@ local get_config = function(container_config, image_config)
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
@ -127,12 +166,8 @@ local get_config = function(container_config, image_config)
multi_network = true
end
-- handle hostconfig
local host_config = old_host_config
-- if host_config.PortBindings and next(host_config.PortBindings) == nil then host_config.PortBindings = nil end
-- host_config.LogConfig = nil
host_config.Mounts = {}
-- for volumes
for i, v in ipairs(container_config.Mounts) do
if v.Type == "volume" then
table.insert(host_config.Mounts, {
@ -145,30 +180,37 @@ local get_config = function(container_config, image_config)
end
end
-- merge configs
local create_body = config
create_body["HostConfig"] = host_config
create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
create_body = _docker.clear_empty_tables(create_body) or {}
extra_network = _docker.clear_empty_tables(extra_network) or {}
return create_body, extra_network
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
if not image_name:match(".-:.+") then
image_name = image_name .. ":latest"
end
local old_image_id = container_info.body.Image
local container_name = container_info.body.Name:sub(2)
local image_id, res = update_image(self, image_name)
if res and res.code ~= 200 then return res end
if res and res.code ~= 200 then
return res
end
if image_id == old_image_id then
return {code = 305, body = {message = "Already up to date"}}
end
@ -189,7 +231,6 @@ local upgrade = function(self, request)
return res
end
-- handle config
local image_config = self.images:inspect({id = old_image_id}).body.Config
local create_body, extra_network = get_config(container_info.body, image_config)
@ -197,68 +238,98 @@ local upgrade = function(self, request)
_docker:append_status("Container: Create" .. " " .. container_name .. "...")
create_body = _docker.clear_empty_tables(create_body)
res = self.containers:create({name = container_name, body = create_body})
if res and res.code > 300 then return res end
if res and res.code > 300 then
return res
end
_docker:append_status("done\n")
-- extra networks need to network connect action
for k, v in pairs(extra_network) do
_docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}})
if res.code > 300 then return res end
if res.code > 300 then
return res
end
_docker:append_status("done\n")
end
_docker:clear_status()
return res
end
local duplicate_config = function (self, request)
local container_info = self.containers:inspect({id = request.id})
if container_info.code > 300 and type(container_info.body) == "table" then return nil end
if container_info.code > 300 and type(container_info.body) == "table" then
return nil
end
local old_image_id = container_info.body.Image
local image_config = self.images:inspect({id = old_image_id}).body.Config
return get_config(container_info.body, image_config)
end
_docker.new = function(option)
local option = option or {}
local remote = uci:get("dockerman", "local", "remote_endpoint")
options = {
host = (remote == "true") and (option.host or uci:get("dockerman", "local", "remote_host")) or nil,
port = (remote == "true") and (option.port or uci:get("dockerman", "local", "remote_port")) or nil,
debug = option.debug or uci:get("dockerman", "local", "debug") == 'true' and true or false,
debug_path = option.debug_path or uci:get("dockerman", "local", "debug_path")
_docker.new = function()
local host = nil
local port = nil
local socket_path = nil
local debug_path = nil
local remote = uci:get_bool("dockerd", "globals", "remote_endpoint")
if remote then
host = uci:get("dockerd", "globals", "remote_host") or nil
port = uci:get("dockerd", "globals", "remote_port") or nil
else
socket_path = uci:get("dockerd", "globals", "socket_path") or "/var/run/docker.sock"
end
local debug = uci:get_bool("dockerd", "globals", "debug")
if debug then
debug_path = uci:get("dockerd", "globals", "debug_path") or "/tmp/.docker_debug"
end
local status_path = uci:get("dockerd", "globals", "status_path") or "/tmp/.docker_status"
_docker.options = {
host = host,
port = port,
socket_path = socket_path,
debug = debug,
debug_path = debug_path,
status_path = status_path
}
options.socket_path = (remote ~= "true" or not options.host or not options.port) and (option.socket_path or uci:get("dockerman", "local", "socket_path") or "/var/run/docker.sock") or nil
local _new = docker.new(options)
_new.options.status_path = uci:get("dockerman", "local", "status_path")
local _new = docker.new(_docker.options)
_new.containers_upgrade = upgrade
_new.containers_duplicate_config = duplicate_config
return _new
end
_docker.options={}
_docker.options.status_path = uci:get("dockerman", "local", "status_path")
_docker.append_status=function(self,val)
if not val then return end
if not val then
return
end
local file_docker_action_status=io.open(self.options.status_path, "a+")
file_docker_action_status:write(val)
file_docker_action_status:close()
end
_docker.write_status=function(self,val)
if not val then return end
if not val then
return
end
local file_docker_action_status=io.open(self.options.status_path, "w+")
file_docker_action_status:write(val)
file_docker_action_status:close()
end
_docker.read_status=function(self)
return nixio.fs.readfile(self.options.status_path)
return fs.readfile(self.options.status_path)
end
_docker.clear_status=function(self)
nixio.fs.remove(self.options.status_path)
fs.remove(self.options.status_path)
end
local status_cb = function(res, source, handler)
@ -291,7 +362,9 @@ _docker.pull_image_show_status_cb = function(res, source)
local buf = _docker:read_status()
local num = 0
local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n"
if step.id then buf, num = buf:gsub("\t"..step.id .. ": .-\n", str) end
if step.id then
buf, num = buf:gsub("\t"..step.id .. ": .-\n", str)
end
if num == 0 then
buf = buf .. str
end
@ -311,31 +384,32 @@ _docker.import_image_show_status_cb = function(res, source)
local buf = _docker:read_status()
local num = 0
local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n"
if step.status then buf, num = buf:gsub("\t"..step.status .. " .-\n", str) end
if step.status then
buf, num = buf:gsub("\t"..step.status .. " .-\n", str)
end
if num == 0 then
buf = buf .. str
end
_docker:write_status(buf)
end
end)
end
)
end
-- _docker.print_status_cb = function(res, source)
-- return status_cb(res, source, function(step)
-- luci.util.perror(step)
-- end
-- )
-- end
_docker.create_macvlan_interface = function(name, device, gateway, subnet)
if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end
if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end
if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then
return
end
if uci:get("dockerd", "globals", "remote_endpoint") == "true" then
return
end
local ip = require "luci.ip"
local if_name = "docker_"..name
local dev_name = "macvlan_"..name
local net_mask = tostring(ip.new(subnet):mask())
local lan_interfaces
-- add macvlan device
uci:delete("network", dev_name)
uci:set("network", dev_name, "device")
@ -343,6 +417,7 @@ _docker.create_macvlan_interface = function(name, device, gateway, subnet)
uci:set("network", dev_name, "ifname", device)
uci:set("network", dev_name, "type", "macvlan")
uci:set("network", dev_name, "mode", "bridge")
-- add macvlan interface
uci:delete("network", if_name)
uci:set("network", if_name, "interface")
@ -364,14 +439,22 @@ _docker.create_macvlan_interface = function(name, device, gateway, subnet)
uci:set("firewall", s[".name"], "network", interfaces)
end
end)
uci:commit("firewall")
uci:commit("network")
os.execute("ifup " .. if_name)
end
_docker.remove_macvlan_interface = function(name)
if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end
if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end
if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then
return
end
if uci:get("dockerd", "globals", "remote_endpoint") == "true" then
return
end
local if_name = "docker_"..name
local dev_name = "macvlan_"..name
uci:foreach("firewall", "zone", function(s)
@ -387,10 +470,12 @@ _docker.remove_macvlan_interface = function(name)
uci:set("firewall", s[".name"], "network", interfaces)
end
end)
uci:commit("firewall")
uci:delete("network", dev_name)
uci:delete("network", if_name)
uci:commit("network")
uci:commit("firewall")
os.execute("ip link del " .. if_name)
end

View file

@ -41,6 +41,7 @@
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)%>,
@ -73,11 +74,11 @@ function docker_status_message(type, content) {
}
else {
document.body.classList.remove('apply-overlay-active');
if (was_xhr_poll_running)
XHR.run();
}
}
var loading_msg="Loading.."
function uci_confirm_docker() {
var tt;
@ -85,7 +86,8 @@ function uci_confirm_docker() {
var call = function(r, resjson, duration) {
if (r && r.status === 200 ) {
var indicator = document.querySelector('.uci_change_indicator');
if (indicator) indicator.style.display = 'none';
if (indicator)
indicator.style.display = 'none';
docker_status_message('notice', '<%:Docker actions done.%>');
document.body.classList.remove('apply-overlay-active');
window.clearTimeout(tt);
@ -102,13 +104,15 @@ function uci_confirm_docker() {
var tick = function() {
var now = Date.now();
docker_status_message('notice',
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> <span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">' +
loading_msg + '</span>');
docker_status_message(
'notice',
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> <span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">' + loading_msg + '</span>'
);
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));
@ -125,9 +129,12 @@ function fnSubmitForm(el){
<% if self.err then -%>
docker_status_message('warning', '<span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">'+`<%=self.err%>`+'</span>');
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
document.getElementById('docker_apply_overlay').addEventListener(
"click",
(e)=>{
docker_status_message()
})
}
)
<%- end %>
window.onload= function (){

View file

@ -20,7 +20,8 @@
document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/docker/container/"+container_id+'/'+item
if (action === item) {
document.getElementById("cbi-tab-container_" + item).className="cbi-tab"
} else {
}
else {
document.getElementById("cbi-tab-container_" + item).className="cbi-tab-disabled"
}
})

View file

@ -1,4 +1,3 @@
<div id="upload-container" class="cbi-value cbi-value-last">
<label class="cbi-value-title" for="archive"><%:Upload%></label>
<div class="cbi-value-field">
@ -15,6 +14,7 @@
<input type="button"" class="btn 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) {
@ -22,9 +22,12 @@
let uploadPath = document.getElementById('path').value
if (!uploadArchive.value || !uploadPath) {
docker_status_message('warning', "<%:Please input the PATH and select the file !%>")
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
document.getElementById('docker_apply_overlay').addEventListener(
"click",
(e)=>{
docker_status_message()
})
}
)
return
}
let fileName = uploadArchive.files[0].name
@ -42,20 +45,27 @@
else {
docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText)
}
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
document.getElementById('docker_apply_overlay').addEventListener(
"click",
(e)=>{
docker_status_message()
})
}
)
}
xhr.send(formData)
}
let btnDownload = document.getElementById('download')
btnDownload.onclick = function (e) {
let downloadPath = document.getElementById('path').value
if (!downloadPath) {
docker_status_message('warning', "<%:Please input the PATH !%>")
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
document.getElementById('docker_apply_overlay').addEventListener(
"click",
(e)=>{
docker_status_message()
})
}
)
return
}
window.open('<%=luci.dispatcher.build_url("admin/docker/container_get_archive")%>?id=<%=self.container%>&path=' + encodeURIComponent(downloadPath))

View file

@ -2,6 +2,7 @@
let last_bw_tx
let last_bw_rx
let interval = 3
function progressbar(v, m, pc, np, f) {
m = m || 100

View file

@ -13,7 +13,8 @@
if (valISrc.value == "") {
document.getElementById("file_import").click()
return
} else {
}
else {
let formData = new FormData()
formData.append('src', valISrc.value)
formData.append('tag', valITag.value)
@ -26,6 +27,7 @@
xhr.send(formData)
}
}
let fileimport = document.getElementById('file_import')
fileimport.onchange = function (e) {
let fileimport = document.getElementById('file_import')
@ -52,37 +54,51 @@
let new_tag = prompt("<%:New tag%>\n<%:Image%>" + "ID: " + image_id + "\n<%:Please input new tag%>:", "")
if (new_tag) {
(new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_tag')%>",
{ id: image_id, tag: new_tag },
{
id: image_id,
tag: new_tag
},
function (r) {
if (r.status == 201) {
location.reload()
}
else {
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
document.getElementById('docker_apply_overlay').addEventListener(
"click",
(e)=>{
docker_status_message()
})
}
})
)
}
}
)
}
}
let un_tag = function (tag) {
if (tag.match("<none>")) return
if (tag.match("<none>"))
return
if (confirm("<%:Remove tag%>: " + tag + " ?")) {
(new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_untag')%>",
{ tag: tag },
{
tag: tag
},
function (r) {
if (r.status == 200) {
location.reload()
}
else {
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
document.getElementById('docker_apply_overlay').addEventListener(
"click",
(e)=>{
docker_status_message()
})
}
})
)
}
}
)
}
}
</script>

View file

@ -8,6 +8,7 @@
document.getElementById("file_load").click()
e.preventDefault()
}
let fileLoad = document.getElementById('file_load')
fileLoad.onchange = function(e){
let fileLoad = document.getElementById('file_load')

View file

@ -49,6 +49,7 @@
display: block;
}
</style>
<script type="text/javascript">
function close_reslov_dialog() {
document.body.classList.remove('dialog-reslov-active')
@ -57,16 +58,21 @@
function reslov_container() {
let s = document.getElementById('cmd-line-status')
if (!s) return
if (!s)
return
let cmd_line = document.getElementById("dialog_reslov_text").value;
if (cmd_line == null || cmd_line == "") {
return
}
cmd_line = cmd_line.replace(/(^\s*)/g,"")
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 `# `
@ -90,6 +96,7 @@
}
</script>
<%+cbi/valueheader%>
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Command line%>" onclick="show_reslov_dialog()" />
<%+cbi/valuefooter%>

View file

@ -126,11 +126,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
<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>
<img src="<%=resource%>/dockerman/containers.svg" />
</div>
</div>
<div class="pure-u-3-5">
@ -148,13 +144,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
<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>
<img src="<%=resource%>/dockerman/images.svg" />
</div>
</div>
<div class="pure-u-3-5">
@ -172,21 +162,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
<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>
<img src="<%=resource%>/dockerman/networks.svg" />
</div>
</div>
<div class="pure-u-3-5">
@ -204,66 +180,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
<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>
<img src="<%=resource%>/dockerman/volumes.svg" />
</div>
</div>
<div class="pure-u-3-5">

View file

@ -1,10 +0,0 @@
config section 'local'
option socket_path '/var/run/docker.sock'
option status_path '/tmp/.docker_action_status'
option debug 'false'
option debug_path '/tmp/.docker_debug'
option remote_endpoint 'false'
option daemon_ea 'true'
option daemon_data_root '/opt/docker'
option daemon_log_level 'warn'
list ac_allowed_interface 'br-lan'

View file

@ -1,47 +0,0 @@
#!/bin/sh /etc/rc.common
START=99
DOCKERD_CONF="/etc/docker/daemon.json"
init_dockerman_chain(){
iptables -N DOCKER-MAN >/dev/null 2>&1
iptables -F DOCKER-MAN >/dev/null 2>&1
iptables -D DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1
iptables -I DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1
}
add_allowed_interface(){
iptables -A DOCKER-MAN -i $1 -o docker0 -j RETURN
}
add_allowed_ip(){
iptables -A DOCKER-MAN -d $1 -o docker0 -j RETURN
}
handle_allowed_interface(){
#config_list_foreach "local" allowed_ip add_allowed_ip
config_list_foreach "local" ac_allowed_interface add_allowed_interface
iptables -A DOCKER-MAN -m conntrack --ctstate ESTABLISHED,RELATED -o docker0 -j RETURN >/dev/null 2>&1
iptables -A DOCKER-MAN -m conntrack --ctstate NEW,INVALID -o docker0 -j DROP >/dev/null 2>&1
iptables -A DOCKER-MAN -j RETURN >/dev/null 2>&1
}
start(){
[ ! -x "/etc/init.d/dockerd" ] && return 0
config_load dockerman
config_get daemon_ea "local" daemon_ea
init_dockerman_chain
if [ -n "$daemon_ea" ]; then
handle_allowed_interface
lua /usr/share/dockerman/dockerd-config.lua "$DOCKERD_CONF" && /etc/init.d/dockerd restart && sleep 5 || {
# 1 running, 0 stopped
STATE=$([ -n "$(ps |grep /usr/bin/dockerd | grep -v grep)" ] && echo 1 || echo 0)
[ "$STATE" == "0" ] && /etc/init.d/dockerd start && sleep 5
}
lua /usr/share/dockerman/dockerd-ac.lua
else
/etc/init.d/dockerd stop
fi
}

View file

@ -1,15 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
set uhttpd.main.script_timeout="360"
commit uhttpd
delete ucitrack.@dockerman[-1]
add ucitrack dockerman
set ucitrack.@dockerman[-1].exec='/etc/init.d/dockerman start'
commit ucitrack
EOF
[ -x "$(which dockerd)" ] && chmod +x /etc/init.d/dockerman && /etc/init.d/dockerd disable && /etc/init.d/dockerman enable >/dev/null 2>&1
sed -i 's/self:cfgvalue(section) or {}/self:cfgvalue(section) or self.default or {}/' /usr/lib/lua/luci/view/cbi/dynlist.htm
/etc/init.d/uhttpd restart >/dev/null 2>&1
rm -fr /tmp/luci-indexcache /tmp/luci-modulecache >/dev/null 2>&1
exit 0

View file

@ -1,20 +0,0 @@
require "luci.util"
docker = require "luci.docker"
uci = (require "luci.model.uci").cursor()
dk = docker.new({socket_path = "/var/run/docker.sock"})
if dk:_ping().code ~= 200 then return end
containers_list = dk.containers:list({query = {all=true}}).body
allowed_container = uci:get("dockerman", "local", "ac_allowed_container")
if not allowed_container or next(allowed_container)==nil then return end
allowed_ip = {}
for i, v in ipairs(containers_list) do
for ii, vv in ipairs(allowed_container) do
if v.Id:sub(1,12) == vv and v.NetworkSettings and v.NetworkSettings.Networks and v.NetworkSettings.Networks.bridge and v.NetworkSettings.Networks.bridge.IPAddress then
print(v.NetworkSettings.Networks.bridge.IPAddress)
luci.util.exec("iptables -I DOCKER-MAN -d "..v.NetworkSettings.Networks.bridge.IPAddress.." -o docker0 -j RETURN")
table.remove(allowed_container, ii)
end
end
end

View file

@ -1,52 +0,0 @@
require "luci.util"
fs = require "nixio.fs"
uci = (require "luci.model.uci").cursor()
raw_file_dir = arg[1]
raw_json_str = fs.readfile(raw_file_dir) or "[]"
raw_json = luci.jsonc.parse(raw_json_str) or {}
new_json = {}
new_json["data-root"] = uci:get("dockerman", "local", "daemon_data_root")
new_json["hosts"] = uci:get("dockerman", "local", "daemon_hosts") or {}
new_json["registry-mirrors"] = uci:get("dockerman", "local", "daemon_registry_mirrors") or {}
new_json["log-level"] = uci:get("dockerman", "local", "daemon_log_level")
function comp(raw, new)
for k, v in pairs(new) do
if type(v) == "table" and raw[k] then
if #v == #raw[k] then
comp(raw[k], v)
else
changed = true
raw[k] = v
end
elseif raw[k] ~= v then
changed = true
raw[k] = v
end
end
for k, v in ipairs(new) do
if type(v) == "table" and raw[k] then
if #v == #raw[k] then
comp(raw[k], v)
else
changed = true
raw[k] = v
end
elseif raw[k] ~= v then
changed = true
raw[k] = v
end
end
end
comp(raw_json, new_json)
if changed then
if next(raw_json["registry-mirrors"]) == nil then raw_json["registry-mirrors"] = nil end
if next(raw_json["hosts"]) == nil then raw_json["hosts"] = nil end
fs.writefile(raw_file_dir, luci.jsonc.stringify(raw_json, true):gsub("\\", ""))
os.exit(0)
else
os.exit(1)
end

View file

@ -2,10 +2,10 @@
"luci-app-dockerman": {
"description": "Grant UCI access for luci-app-dockerman",
"read": {
"uci": [ "dockerman" ]
"uci": [ "dockerd" ]
},
"write": {
"uci": [ "dockerman" ]
"uci": [ "dockerd" ]
}
}
}

View file

@ -2,6 +2,7 @@
LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-lib-docker>
]]--
require "nixio.util"
require "luci.util"
local jsonc = require "luci.jsonc"
@ -15,44 +16,55 @@ local json_parse = jsonc.parse
local chunksource = function(sock, buffer)
buffer = buffer or ""
return function()
local output
local _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
if not count then
local newblock, code = sock:recv(1024)
if not newblock then return nil, code end
if not newblock then
return nil, code
end
buffer = buffer .. newblock
_, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
end
count = tonumber(count, 16)
if not count then
return nil, -1, "invalid encoding"
elseif count == 0 then -- finial
return nil
elseif count <= #buffer - endp then
--data >= count
elseif count <= #buffer - endp then -- data >= count
output = buffer:sub(endp + 1, endp + count)
if count == #buffer - endp then -- [data]
buffer = buffer:sub(endp + count + 1)
count, code = sock:recvall(2) --read \r\n
if not count then return nil, code end
if not count then
return nil, code
end
elseif count + 1 == #buffer - endp then -- [data]\r
buffer = buffer:sub(endp + count + 2)
count, code = sock:recvall(1) --read \n
if not count then return nil, code end
if not count then
return nil, code
end
else -- [data]\r\n[count]\r\n[data]...
buffer = buffer:sub(endp + count + 3) -- cut buffer
end
return output
else
-- data < count
else -- data < count
output = buffer:sub(endp + 1, endp + count)
buffer = buffer:sub(endp + count + 1)
local remain, code = sock:recvall(count - #output) --need read remaining
if not remain then return nil, code end
if not remain then
return nil, code
end
output = output .. remain
count, code = sock:recvall(2) --read \r\n
if not count then return nil, code end
if not count then
return nil, code
end
return output
end
end
@ -74,21 +86,23 @@ local docker_stream_filter = function(buffer)
return ""
end
local stream_type = ((string.byte(buffer, 1) == 1) and "stdout") or ((string.byte(buffer, 1) == 2) and "stderr") or ((string.byte(buffer, 1) == 0) and "stdin") or "stream_err"
local valid_length =
tonumber(string.byte(buffer, 5)) * 256 * 256 * 256 + tonumber(string.byte(buffer, 6)) * 256 * 256 + tonumber(string.byte(buffer, 7)) * 256 + tonumber(string.byte(buffer, 8))
local valid_length = tonumber(string.byte(buffer, 5)) * 256 * 256 * 256 + tonumber(string.byte(buffer, 6)) * 256 * 256 + tonumber(string.byte(buffer, 7)) * 256 + tonumber(string.byte(buffer, 8))
if valid_length > #buffer + 8 then
return ""
end
return stream_type .. ": " .. string.sub(buffer, 9, valid_length + 8)
-- return string.sub(buffer, 9, valid_length + 8)
end
local open_socket = function(req_options)
local socket
if type(req_options) ~= "table" then return socket end
if type(req_options) ~= "table" then
return socket
end
if req_options.socket_path then
socket = nixio.socket("unix", "stream")
if socket:connect(req_options.socket_path) ~= true then return nil end
if socket:connect(req_options.socket_path) ~= true then
return nil
end
elseif req_options.host and req_options.port then
socket = nixio.connect(req_options.host, req_options.port)
end
@ -99,11 +113,17 @@ local open_socket = function(req_options)
end
end
local send_http_socket = function(docker_socket, req_header, req_body, callback)
local send_http_socket = function(options, docker_socket, req_header, req_body, callback)
if docker_socket:send(req_header) == 0 then
return {
headers={code=498,message="bad path", protocol="HTTP/1.1"},
body={message="can\'t send data to socket"}
headers={
code=498,
message="bad path",
protocol="HTTP/1.1"
},
body={
message="can\'t send data to socket"
}
}
end
@ -116,10 +136,14 @@ local send_http_socket = function(docker_socket, req_header, req_body, callback)
elseif req_body and type(req_body) == "table" then
-- json
docker_socket:send(json_stringify(req_body))
if options.debug then io.popen("echo '".. json_stringify(req_body) .. "' >> " .. options.debug_path) end
if options.debug then
io.popen("echo '".. json_stringify(req_body) .. "' >> " .. options.debug_path)
end
elseif req_body then
docker_socket:send(req_body)
if options.debug then io.popen("echo '".. req_body .. "' >> " .. options.debug_path) end
if options.debug then
io.popen("echo '".. req_body .. "' >> " .. options.debug_path)
end
end
local linesrc = docker_socket:linesource()
@ -130,22 +154,37 @@ local send_http_socket = function(docker_socket, req_header, req_body, callback)
if not line then
docker_socket:close()
return {
headers = {code=499, message="bad socket path", protocol="HTTP/1.1"},
body = {message="no data receive from socket"}
headers = {
code=499,
message="bad socket path",
protocol="HTTP/1.1"
},
body = {
message="no data receive from socket"
}
}
end
local response = {code = 0, headers = {}, body = {}}
local response = {
code = 0,
headers = {},
body = {}
}
local p, code, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
response.protocol = p
response.code = tonumber(code)
response.message = msg
line = linesrc()
while line and line ~= "" do
local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
if key and key ~= "Status" then
if type(response.headers[key]) == "string" then
response.headers[key] = {response.headers[key], val}
response.headers[key] = {
response.headers[key],
val
}
elseif type(response.headers[key]) == "table" then
response.headers[key][#response.headers[key] + 1] = val
else
@ -154,9 +193,11 @@ local send_http_socket = function(docker_socket, req_header, req_body, callback)
end
line = linesrc()
end
-- handle response body
local body_buffer = linesrc(true)
response.body = {}
if type(callback) ~= "function" then
if response.headers["Transfer-Encoding"] == "chunked" then
local source = chunksource(docker_socket, body_buffer)
@ -196,6 +237,7 @@ local gen_header = function(options, http_method, api_group, api_action, name_or
end
end
end
path = (api_group and ("/" .. api_group) or "") .. (name_or_id and ("/" .. name_or_id) or "") .. (api_action and ("/" .. api_action) or "") .. (query or "")
header = (http_method or "GET") .. " " .. path .. " " .. options.protocol .. "\r\n"
header = header .. "Host: " .. options.host .. "\r\n"
@ -221,32 +263,51 @@ local gen_header = function(options, http_method, api_group, api_action, name_or
elseif request and request.body and type(request.body) == "string" then
header = header .. "Content-Length: " .. #request.body .. "\r\n"
end
header = header .. "\r\n"
if options.debug then io.popen("echo '".. header .. "' >> " .. options.debug_path) end
if options.debug then
io.popen("echo '".. header .. "' >> " .. options.debug_path)
end
return header
end
local call_docker = function(options, http_method, api_group, api_action, name_or_id, request, callback)
local req_options = setmetatable({}, {__index = options})
local req_options = setmetatable({}, {
__index = options
})
local req_header = gen_header(req_options,
http_method,
api_group,
api_action,
name_or_id,
request)
local req_header = gen_header(req_options, http_method, api_group, api_action, name_or_id, request)
local req_body = request and request.body or nil
local docker_socket = open_socket(req_options)
if docker_socket then
return send_http_socket(docker_socket, req_header, req_body, callback)
return send_http_socket(options, docker_socket, req_header, req_body, callback)
else
return {
headers = {code=497, message="bad socket path or host", protocol="HTTP/1.1"},
body = {message="can\'t connect to socket"}
headers = {
code=497,
message="bad socket path or host",
protocol="HTTP/1.1"
},
body = {
message="can\'t connect to socket"
}
}
end
end
local gen_api = function(_table, http_method, api_group, api_action)
local _api_action
if api_action == "get_archive" or api_action == "put_archive" then
_api_action = "archive"
api_action = "archive"
elseif api_action == "df" then
_api_action = "system/df"
elseif api_action ~= "list" and api_action ~= "inspect" and api_action ~= "remove" then
@ -257,6 +318,7 @@ local gen_api = function(_table, http_method, api_group, api_action)
local fp = function(self, request, callback)
local name_or_id = request and (request.name or request.id or request.name_or_id) or nil
if api_action == "list" then
if (name_or_id ~= "" and name_or_id ~= nil) then
if api_group == "images" then
@ -277,7 +339,13 @@ local gen_api = function(_table, http_method, api_group, api_action)
end
elseif api_action == "logs" then
local body_buffer = ""
local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
local response = call_docker(self.options,
http_method,
api_group,
_api_action,
name_or_id,
request,
callback)
if response.code >= 200 and response.code < 300 then
for i, v in ipairs(response.body) do
body_buffer = body_buffer .. docker_stream_filter(response.body[i])
@ -286,7 +354,9 @@ local gen_api = function(_table, http_method, api_group, api_action)
end
return response
end
local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
if response.headers and response.headers["Content-Type"] == "application/json" then
if #response.body == 1 then
response.body = json_parse(response.body[1])
@ -308,7 +378,13 @@ local gen_api = function(_table, http_method, api_group, api_action)
end
end
local _docker = {containers = {}, exec = {}, images = {}, networks = {}, volumes = {}}
local _docker = {
containers = {},
exec = {},
images = {},
networks = {},
volumes = {}
}
gen_api(_docker, "GET", "containers", "list")
gen_api(_docker, "POST", "containers", "create")
@ -370,6 +446,7 @@ gen_api(_docker, "GET", nil, "df")
function _docker.new(options)
local docker = {}
local _options = options or {}
docker.options = {
socket_path = _options.socket_path or nil,
host = _options.socket_path and "localhost" or _options.host,
@ -384,6 +461,7 @@ function _docker.new(options)
debug = _options.debug or false,
debug_path = _options.debug and _options.debug_path or nil
}
setmetatable(
docker,
{
@ -396,6 +474,7 @@ function _docker.new(options)
end
}
)
setmetatable(
docker.containers,
{
@ -406,6 +485,7 @@ function _docker.new(options)
end
}
)
setmetatable(
docker.networks,
{
@ -416,6 +496,7 @@ function _docker.new(options)
end
}
)
setmetatable(
docker.images,
{
@ -426,6 +507,7 @@ function _docker.new(options)
end
}
)
setmetatable(
docker.volumes,
{
@ -436,6 +518,7 @@ function _docker.new(options)
end
}
)
setmetatable(
docker.exec,
{
@ -446,6 +529,7 @@ function _docker.new(options)
end
}
)
return docker
end