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:
commit
8f54db8cc3
30 changed files with 4344 additions and 3664 deletions
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -2,39 +2,46 @@
|
||||||
LuCI - Lua Configuration Interface
|
LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||||
]]--
|
]]--
|
||||||
require "luci.util"
|
|
||||||
local docker = require "luci.model.docker"
|
local docker = require "luci.model.docker"
|
||||||
-- local uci = require "luci.model.uci"
|
|
||||||
|
|
||||||
module("luci.controller.dockerman",package.seeall)
|
module("luci.controller.dockerman",package.seeall)
|
||||||
|
|
||||||
function index()
|
function index()
|
||||||
|
|
||||||
local e = entry({"admin", "docker"}, firstchild(), "Docker", 40)
|
local e = entry({"admin", "docker"}, firstchild(), "Docker", 40)
|
||||||
e.dependent = false
|
e.dependent = false
|
||||||
e.acl_depends = { "luci-app-dockerman" }
|
e.acl_depends = { "luci-app-dockerman" }
|
||||||
|
|
||||||
entry({"admin", "docker", "overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true
|
entry({"admin", "docker", "overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true
|
||||||
|
|
||||||
local remote = luci.model.uci.cursor():get("dockerman", "local", "remote_endpoint")
|
local remote = luci.model.uci.cursor():get_bool("dockerd", "globals", "remote_endpoint")
|
||||||
if remote == nil then
|
if remote then
|
||||||
local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
|
local host = luci.model.uci.cursor():get("dockerd", "globals", "remote_host")
|
||||||
if socket and not nixio.fs.access(socket) then return end
|
local port = luci.model.uci.cursor():get("dockerd", "globals", "remote_port")
|
||||||
elseif remote == "true" then
|
if not host or not port then
|
||||||
local host = luci.model.uci.cursor():get("dockerman", "local", "remote_host")
|
return
|
||||||
local port = luci.model.uci.cursor():get("dockerman", "local", "remote_port")
|
end
|
||||||
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
|
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", "containers"}, form("dockerman/containers"), _("Containers"),1).leaf=true
|
||||||
entry({"admin", "docker", "images"}, form("dockerman/images"), _("Images"),2).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", "networks"}, form("dockerman/networks"), _("Networks"),3).leaf=true
|
||||||
entry({"admin", "docker", "volumes"}, form("dockerman/volumes"), _("Volumes"),4).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", "events"}, call("action_events"), _("Events"),5)
|
||||||
|
|
||||||
entry({"admin", "docker", "newcontainer"}, form("dockerman/newcontainer")).leaf=true
|
entry({"admin", "docker", "newcontainer"}, form("dockerman/newcontainer")).leaf=true
|
||||||
entry({"admin", "docker", "newnetwork"}, form("dockerman/newnetwork")).leaf=true
|
entry({"admin", "docker", "newnetwork"}, form("dockerman/newnetwork")).leaf=true
|
||||||
entry({"admin", "docker", "container"}, form("dockerman/container")).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_stats"}, call("action_get_container_stats")).leaf=true
|
||||||
entry({"admin", "docker", "container_get_archive"}, call("download_archive")).leaf=true
|
entry({"admin", "docker", "container_get_archive"}, call("download_archive")).leaf=true
|
||||||
entry({"admin", "docker", "container_put_archive"}, call("upload_archive")).leaf=true
|
entry({"admin", "docker", "container_put_archive"}, call("upload_archive")).leaf=true
|
||||||
|
@ -49,10 +56,12 @@ end
|
||||||
|
|
||||||
function action_events()
|
function action_events()
|
||||||
local logs = ""
|
local logs = ""
|
||||||
local dk = docker.new()
|
|
||||||
local query ={}
|
local query ={}
|
||||||
|
|
||||||
|
local dk = docker.new()
|
||||||
query["until"] = os.time()
|
query["until"] = os.time()
|
||||||
local events = dk:events({query = query})
|
local events = dk:events({query = query})
|
||||||
|
|
||||||
if events.code == 200 then
|
if events.code == 200 then
|
||||||
for _, v in ipairs(events.body) do
|
for _, v in ipairs(events.body) do
|
||||||
if v and v.Type == "container" then
|
if v and v.Type == "container" then
|
||||||
|
@ -64,40 +73,42 @@ function action_events()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}})
|
luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}})
|
||||||
end
|
end
|
||||||
|
|
||||||
local calculate_cpu_percent = function(d)
|
local calculate_cpu_percent = function(d)
|
||||||
if type(d) ~= "table" then return end
|
if type(d) ~= "table" then
|
||||||
cpu_count = tonumber(d["cpu_stats"]["online_cpus"])
|
return
|
||||||
cpu_percent = 0.0
|
end
|
||||||
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"])
|
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
|
if system_delta > 0.0 then
|
||||||
cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count)
|
cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count)
|
||||||
end
|
end
|
||||||
-- return cpu_percent .. "%"
|
|
||||||
return cpu_percent
|
return cpu_percent
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_memory = function(d)
|
local get_memory = function(d)
|
||||||
if type(d) ~= "table" then return end
|
if type(d) ~= "table" then
|
||||||
-- local limit = string.format("%.2f", tonumber(d["memory_stats"]["limit"]) / 1024 / 1024)
|
return
|
||||||
-- local usage = string.format("%.2f", (tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"])) / 1024 / 1024)
|
end
|
||||||
-- return usage .. "MB / " .. limit.. "MB"
|
|
||||||
local limit =tonumber(d["memory_stats"]["limit"])
|
local limit =tonumber(d["memory_stats"]["limit"])
|
||||||
local usage = tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"])
|
local usage = tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"])
|
||||||
|
|
||||||
return usage, limit
|
return usage, limit
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_rx_tx = function(d)
|
local get_rx_tx = function(d)
|
||||||
if type(d) ~="table" then return end
|
if type(d) ~="table" then
|
||||||
-- local data
|
return
|
||||||
-- if type(d["networks"]) == "table" then
|
end
|
||||||
-- for e, v in pairs(d["networks"]) do
|
|
||||||
-- data = (data and (data .. "<br>") or "") .. e .. " Total Tx:" .. string.format("%.2f",(tonumber(v.tx_bytes)/1024/1024)) .. "MB Total Rx: ".. string.format("%.2f",(tonumber(v.rx_bytes)/1024/1024)) .. "MB"
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
local data = {}
|
local data = {}
|
||||||
if type(d["networks"]) == "table" then
|
if type(d["networks"]) == "table" then
|
||||||
for e, v in pairs(d["networks"]) do
|
for e, v in pairs(d["networks"]) do
|
||||||
|
@ -107,6 +118,7 @@ local get_rx_tx = function(d)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -165,6 +177,7 @@ function action_confirm()
|
||||||
msg = "finish"
|
msg = "finish"
|
||||||
data = "finish"
|
data = "finish"
|
||||||
end
|
end
|
||||||
|
|
||||||
luci.http.status(code, msg)
|
luci.http.status(code, msg)
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
luci.http.write_json({info = data})
|
luci.http.write_json({info = data})
|
||||||
|
@ -193,7 +206,12 @@ function download_archive()
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
function upload_archive(container_id)
|
function upload_archive(container_id)
|
||||||
|
@ -209,7 +227,14 @@ function upload_archive(container_id)
|
||||||
end)
|
end)
|
||||||
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
|
local msg = res and res.body and res.body.message or nil
|
||||||
luci.http.status(res.code, msg)
|
luci.http.status(res.code, msg)
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
|
@ -238,9 +263,16 @@ function save_images(container_id)
|
||||||
luci.ltn12.pump.all(chunk, luci.http.write)
|
luci.ltn12.pump.all(chunk, luci.http.write)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
docker:write_status("Images: saving" .. " " .. container_id .. "...")
|
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()
|
docker:clear_status()
|
||||||
|
|
||||||
local msg = res and res.body and res.body.message or nil
|
local msg = res and res.body and res.body.message or nil
|
||||||
luci.http.status(res.code, msg)
|
luci.http.status(res.code, msg)
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
|
@ -262,7 +294,6 @@ function load_images()
|
||||||
|
|
||||||
docker:write_status("Images: loading...")
|
docker:write_status("Images: loading...")
|
||||||
local res = dk.images:load({body = rec_send})
|
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
|
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
|
if res.code == 200 and msg and msg:match("Loaded image ID") then
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
|
@ -271,6 +302,7 @@ function load_images()
|
||||||
docker:append_status("code:" .. res.code.." ".. msg)
|
docker:append_status("code:" .. res.code.." ".. msg)
|
||||||
luci.http.status(300, msg)
|
luci.http.status(300, msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
luci.http.write_json({message = msg})
|
luci.http.write_json({message = msg})
|
||||||
end
|
end
|
||||||
|
@ -280,6 +312,7 @@ function import_images()
|
||||||
local itag = luci.http.formvalue("tag")
|
local itag = luci.http.formvalue("tag")
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
local ltn12 = require "luci.ltn12"
|
local ltn12 = require "luci.ltn12"
|
||||||
|
|
||||||
local rec_send = function(sinkout)
|
local rec_send = function(sinkout)
|
||||||
luci.http.setfilehandler(function (meta, chunk, eof)
|
luci.http.setfilehandler(function (meta, chunk, eof)
|
||||||
if chunk then
|
if chunk then
|
||||||
|
@ -287,23 +320,32 @@ function import_images()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
docker:write_status("Images: importing".. " ".. itag .."...\n")
|
docker:write_status("Images: importing".. " ".. itag .."...\n")
|
||||||
local repo = itag and itag:match("^([^:]+)")
|
local repo = itag and itag:match("^([^:]+)")
|
||||||
local tag = 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
|
local msg = res and res.body and ( res.body.message )or nil
|
||||||
if not msg and #res.body == 0 then
|
if not msg and #res.body == 0 then
|
||||||
-- res.body = {"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
|
|
||||||
msg = res.body.status or res.body.error
|
msg = res.body.status or res.body.error
|
||||||
elseif not msg and #res.body >= 1 then
|
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
|
msg = res.body[#res.body].status or res.body[#res.body].error
|
||||||
end
|
end
|
||||||
|
|
||||||
if res.code == 200 and msg and msg:match("sha256:") then
|
if res.code == 200 and msg and msg:match("sha256:") then
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
else
|
else
|
||||||
docker:append_status("code:" .. res.code.." ".. msg)
|
docker:append_status("code:" .. res.code.." ".. msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
luci.http.status(res.code, msg)
|
luci.http.status(res.code, msg)
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
luci.http.write_json({message = msg})
|
luci.http.write_json({message = msg})
|
||||||
|
@ -316,11 +358,15 @@ function get_image_tags(image_id)
|
||||||
luci.http.write_json({message = "no image id"})
|
luci.http.write_json({message = "no image id"})
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local dk = docker.new()
|
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
|
local msg = res and res.body and res.body.message or nil
|
||||||
luci.http.status(res.code, msg)
|
luci.http.status(res.code, msg)
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
|
|
||||||
if res.code == 200 then
|
if res.code == 200 then
|
||||||
local tags = res.body.RepoTags
|
local tags = res.body.RepoTags
|
||||||
luci.http.write_json({tags = tags})
|
luci.http.write_json({tags = tags})
|
||||||
|
@ -333,19 +379,28 @@ end
|
||||||
function tag_image(image_id)
|
function tag_image(image_id)
|
||||||
local src = luci.http.formvalue("tag")
|
local src = luci.http.formvalue("tag")
|
||||||
local image_id = image_id or luci.http.formvalue("id")
|
local image_id = image_id or luci.http.formvalue("id")
|
||||||
|
|
||||||
if type(src) ~= "string" or not image_id then
|
if type(src) ~= "string" or not image_id then
|
||||||
luci.http.status(400, "no image id or tag")
|
luci.http.status(400, "no image id or tag")
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
luci.http.write_json({message = "no image id or tag"})
|
luci.http.write_json({message = "no image id or tag"})
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local repo = src:match("^([^:]+)")
|
local repo = src:match("^([^:]+)")
|
||||||
local tag = src:match("^[^:]-:([^:]+)")
|
local tag = src:match("^[^:]-:([^:]+)")
|
||||||
local dk = docker.new()
|
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
|
local msg = res and res.body and res.body.message or nil
|
||||||
luci.http.status(res.code, msg)
|
luci.http.status(res.code, msg)
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
|
|
||||||
if res.code == 201 then
|
if res.code == 201 then
|
||||||
local tags = res.body.RepoTags
|
local tags = res.body.RepoTags
|
||||||
luci.http.write_json({tags = tags})
|
luci.http.write_json({tags = tags})
|
||||||
|
@ -357,14 +412,17 @@ end
|
||||||
|
|
||||||
function untag_image(tag)
|
function untag_image(tag)
|
||||||
local tag = tag or luci.http.formvalue("tag")
|
local tag = tag or luci.http.formvalue("tag")
|
||||||
|
|
||||||
if not tag then
|
if not tag then
|
||||||
luci.http.status(400, "no tag name")
|
luci.http.status(400, "no tag name")
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
luci.http.write_json({message = "no tag name"})
|
luci.http.write_json({message = "no tag name"})
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
local res = dk.images:inspect({name = tag})
|
local res = dk.images:inspect({name = tag})
|
||||||
|
|
||||||
if res.code == 200 then
|
if res.code == 200 then
|
||||||
local tags = res.body.RepoTags
|
local tags = res.body.RepoTags
|
||||||
if #tags > 1 then
|
if #tags > 1 then
|
||||||
|
|
|
@ -4,50 +4,73 @@ Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
require "luci.util"
|
require "luci.util"
|
||||||
|
|
||||||
local docker = require "luci.model.docker"
|
local docker = require "luci.model.docker"
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
|
|
||||||
container_id = arg[1]
|
container_id = arg[1]
|
||||||
local action = arg[2] or "info"
|
local action = arg[2] or "info"
|
||||||
|
|
||||||
local images, networks, container_info
|
local m, s, o
|
||||||
if not container_id then return end
|
local images, networks, container_info, res
|
||||||
local res = dk.containers:inspect({id = container_id})
|
|
||||||
if res.code < 300 then container_info = res.body else return end
|
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()
|
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 get_ports = function(d)
|
||||||
local data
|
local data
|
||||||
|
|
||||||
if d.HostConfig and d.HostConfig.PortBindings then
|
if d.HostConfig and d.HostConfig.PortBindings then
|
||||||
for inter, out in pairs(d.HostConfig.PortBindings) do
|
for inter, out in pairs(d.HostConfig.PortBindings) do
|
||||||
data = (data and (data .. "<br>") or "") .. out[1]["HostPort"] .. ":" .. inter
|
data = (data and (data .. "<br>") or "") .. out[1]["HostPort"] .. ":" .. inter
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_env = function(d)
|
local get_env = function(d)
|
||||||
local data
|
local data
|
||||||
|
|
||||||
if d.Config and d.Config.Env then
|
if d.Config and d.Config.Env then
|
||||||
for _,v in ipairs(d.Config.Env) do
|
for _,v in ipairs(d.Config.Env) do
|
||||||
data = (data and (data .. "<br>") or "") .. v
|
data = (data and (data .. "<br>") or "") .. v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_command = function(d)
|
local get_command = function(d)
|
||||||
local data
|
local data
|
||||||
|
|
||||||
if d.Config and d.Config.Cmd then
|
if d.Config and d.Config.Cmd then
|
||||||
for _,v in ipairs(d.Config.Cmd) do
|
for _,v in ipairs(d.Config.Cmd) do
|
||||||
data = (data and (data .. " ") or "") .. v
|
data = (data and (data .. " ") or "") .. v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_mounts = function(d)
|
local get_mounts = function(d)
|
||||||
local data
|
local data
|
||||||
|
|
||||||
if d.Mounts then
|
if d.Mounts then
|
||||||
for _,v in ipairs(d.Mounts) do
|
for _,v in ipairs(d.Mounts) do
|
||||||
local v_sorce_d, v_dest_d
|
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 "")
|
data = (data and (data .. "<br>") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_device = function(d)
|
local get_device = function(d)
|
||||||
local data
|
local data
|
||||||
|
|
||||||
if d.HostConfig and d.HostConfig.Devices then
|
if d.HostConfig and d.HostConfig.Devices then
|
||||||
for _,v in ipairs(d.HostConfig.Devices) do
|
for _,v in ipairs(d.HostConfig.Devices) do
|
||||||
data = (data and (data .. "<br>") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "")
|
data = (data and (data .. "<br>") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_links = function(d)
|
local get_links = function(d)
|
||||||
local data
|
local data
|
||||||
|
|
||||||
if d.HostConfig and d.HostConfig.Links then
|
if d.HostConfig and d.HostConfig.Links then
|
||||||
for _,v in ipairs(d.HostConfig.Links) do
|
for _,v in ipairs(d.HostConfig.Links) do
|
||||||
data = (data and (data .. "<br>") or "") .. v
|
data = (data and (data .. "<br>") or "") .. v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_tmpfs = function(d)
|
local get_tmpfs = function(d)
|
||||||
local data
|
local data
|
||||||
|
|
||||||
if d.HostConfig and d.HostConfig.Tmpfs then
|
if d.HostConfig and d.HostConfig.Tmpfs then
|
||||||
for k, v in pairs(d.HostConfig.Tmpfs) do
|
for k, v in pairs(d.HostConfig.Tmpfs) do
|
||||||
data = (data and (data .. "<br>") or "") .. k .. (v~="" and ":" or "")..v
|
data = (data and (data .. "<br>") or "") .. k .. (v~="" and ":" or "")..v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_dns = function(d)
|
local get_dns = function(d)
|
||||||
local data
|
local data
|
||||||
|
|
||||||
if d.HostConfig and d.HostConfig.Dns then
|
if d.HostConfig and d.HostConfig.Dns then
|
||||||
for _, v in ipairs(d.HostConfig.Dns) do
|
for _, v in ipairs(d.HostConfig.Dns) do
|
||||||
data = (data and (data .. "<br>") or "") .. v
|
data = (data and (data .. "<br>") or "") .. v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_sysctl = function(d)
|
local get_sysctl = function(d)
|
||||||
local data
|
local data
|
||||||
|
|
||||||
if d.HostConfig and d.HostConfig.Sysctls then
|
if d.HostConfig and d.HostConfig.Sysctls then
|
||||||
for k, v in pairs(d.HostConfig.Sysctls) do
|
for k, v in pairs(d.HostConfig.Sysctls) do
|
||||||
data = (data and (data .. "<br>") or "") .. k..":"..v
|
data = (data and (data .. "<br>") or "") .. k..":"..v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_networks = function(d)
|
local get_networks = function(d)
|
||||||
local data={}
|
local data={}
|
||||||
|
|
||||||
if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then
|
if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then
|
||||||
for k,v in pairs(d.NetworkSettings.Networks) do
|
for k,v in pairs(d.NetworkSettings.Networks) do
|
||||||
data[k] = v.IPAddress or ""
|
data[k] = v.IPAddress or ""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local start_stop_remove = function(m, cmd)
|
local start_stop_remove = function(m, cmd)
|
||||||
|
local res
|
||||||
|
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...")
|
docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...")
|
||||||
local res
|
|
||||||
if cmd ~= "upgrade" then
|
if cmd ~= "upgrade" then
|
||||||
res = dk.containers[cmd](dk, {id = container_id})
|
res = dk.containers[cmd](dk, {id = container_id})
|
||||||
else
|
else
|
||||||
res = dk.containers_upgrade(dk, {id = container_id})
|
res = dk.containers_upgrade(dk, {id = container_id})
|
||||||
end
|
end
|
||||||
|
|
||||||
if res and res.code >= 300 then
|
if res and res.code >= 300 then
|
||||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
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))
|
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=SimpleForm("docker", container_info.Name:sub(2), translate("Docker Container") )
|
||||||
m.redirect = luci.dispatcher.build_url("admin/docker/containers")
|
m.redirect = luci.dispatcher.build_url("admin/docker/containers")
|
||||||
-- m:append(Template("dockerman/container"))
|
|
||||||
docker_status = m:section(SimpleSection)
|
|
||||||
docker_status.template = "dockerman/apply_widget"
|
|
||||||
docker_status.err=docker:read_status()
|
|
||||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
|
||||||
if docker_status.err then docker:clear_status() end
|
|
||||||
|
|
||||||
|
s = m:section(SimpleSection)
|
||||||
|
s.template = "dockerman/apply_widget"
|
||||||
|
s.err=docker:read_status()
|
||||||
|
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ")
|
||||||
|
if s.err then
|
||||||
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
|
||||||
action_section = m:section(Table,{{}})
|
s = m:section(Table,{{}})
|
||||||
action_section.notitle=true
|
s.notitle=true
|
||||||
action_section.rowcolors=false
|
s.rowcolors=false
|
||||||
action_section.template = "cbi/nullsection"
|
s.template = "cbi/nullsection"
|
||||||
|
|
||||||
btnstart=action_section:option(Button, "_start")
|
o = s:option(Button, "_start")
|
||||||
btnstart.template = "dockerman/cbi/inlinebutton"
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnstart.inputtitle=translate("Start")
|
o.inputtitle=translate("Start")
|
||||||
btnstart.inputstyle = "apply"
|
o.inputstyle = "apply"
|
||||||
btnstart.forcewrite = true
|
o.forcewrite = true
|
||||||
btnrestart=action_section:option(Button, "_restart")
|
o.write = function(self, section)
|
||||||
btnrestart.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnrestart.inputtitle=translate("Restart")
|
|
||||||
btnrestart.inputstyle = "reload"
|
|
||||||
btnrestart.forcewrite = true
|
|
||||||
btnstop=action_section:option(Button, "_stop")
|
|
||||||
btnstop.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnstop.inputtitle=translate("Stop")
|
|
||||||
btnstop.inputstyle = "reset"
|
|
||||||
btnstop.forcewrite = true
|
|
||||||
btnkill=action_section:option(Button, "_kill")
|
|
||||||
btnkill.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnkill.inputtitle=translate("Kill")
|
|
||||||
btnkill.inputstyle = "reset"
|
|
||||||
btnkill.forcewrite = true
|
|
||||||
btnupgrade=action_section:option(Button, "_upgrade")
|
|
||||||
btnupgrade.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnupgrade.inputtitle=translate("Upgrade")
|
|
||||||
btnupgrade.inputstyle = "reload"
|
|
||||||
btnstop.forcewrite = true
|
|
||||||
btnduplicate=action_section:option(Button, "_duplicate")
|
|
||||||
btnduplicate.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnduplicate.inputtitle=translate("Duplicate/Edit")
|
|
||||||
btnduplicate.inputstyle = "add"
|
|
||||||
btnstop.forcewrite = true
|
|
||||||
btnremove=action_section:option(Button, "_remove")
|
|
||||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnremove.inputtitle=translate("Remove")
|
|
||||||
btnremove.inputstyle = "remove"
|
|
||||||
btnremove.forcewrite = true
|
|
||||||
|
|
||||||
btnstart.write = function(self, section)
|
|
||||||
start_stop_remove(m,"start")
|
start_stop_remove(m,"start")
|
||||||
end
|
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")
|
start_stop_remove(m,"restart")
|
||||||
end
|
end
|
||||||
btnupgrade.write = function(self, section)
|
|
||||||
start_stop_remove(m,"upgrade")
|
o = s:option(Button, "_stop")
|
||||||
end
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnremove.write = function(self, section)
|
o.inputtitle=translate("Stop")
|
||||||
start_stop_remove(m,"remove")
|
o.inputstyle = "reset"
|
||||||
end
|
o.forcewrite = true
|
||||||
btnstop.write = function(self, section)
|
o.write = function(self, section)
|
||||||
start_stop_remove(m,"stop")
|
start_stop_remove(m,"stop")
|
||||||
end
|
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")
|
start_stop_remove(m,"kill")
|
||||||
end
|
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))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
tab_section = m:section(SimpleSection)
|
o = s:option(Button, "_remove")
|
||||||
tab_section.template = "dockerman/container"
|
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
|
if action == "info" then
|
||||||
m.submit = false
|
m.submit = false
|
||||||
m.reset = false
|
m.reset = false
|
||||||
table_info = {
|
table_info = {
|
||||||
["01name"] = {_key = translate("Name"), _value = container_info.Name:sub(2) or "-", _button=translate("Update")},
|
["01name"] = {
|
||||||
["02id"] = {_key = translate("ID"), _value = container_info.Id or "-"},
|
_key = translate("Name"),
|
||||||
["03image"] = {_key = translate("Image"), _value = container_info.Config.Image .. "<br>" .. container_info.Image},
|
_value = container_info.Name:sub(2) or "-",
|
||||||
["04status"] = {_key = translate("Status"), _value = container_info.State and container_info.State.Status or "-"},
|
_button=translate("Update")
|
||||||
["05created"] = {_key = translate("Created"), _value = container_info.Created or "-"},
|
},
|
||||||
|
["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 "-"}
|
if container_info.State.Status == "running" then
|
||||||
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["06start"] = {
|
||||||
table_info["081user"] = {_key = translate("User"), _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"}
|
_key = translate("Start Time"),
|
||||||
table_info["09mount"] = {_key = translate("Mount/Volume"), _value = get_mounts(container_info) or "-"}
|
_value = container_info.State and container_info.State.StartedAt 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 "-"}
|
else
|
||||||
table_info["12ports"] = {_key = translate("Ports"), _value = get_ports(container_info) or "-"}
|
table_info["06start"] = {
|
||||||
table_info["13links"] = {_key = translate("Links"), _value = get_links(container_info) or "-"}
|
_key = translate("Finish Time"),
|
||||||
table_info["14device"] = {_key = translate("Device"), _value = get_device(container_info) or "-"}
|
_value = container_info.State and container_info.State.FinishedAt 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 "-"}
|
end
|
||||||
table_info["17sysctl"] = {_key = translate("Sysctl"), _value = get_sysctl(container_info) or "-"}
|
|
||||||
|
table_info["07healthy"] = {
|
||||||
|
_key = translate("Healthy"),
|
||||||
|
_value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"
|
||||||
|
}
|
||||||
|
table_info["08restart"] = {
|
||||||
|
_key = translate("Restart Policy"),
|
||||||
|
_value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-",
|
||||||
|
_button=translate("Update")
|
||||||
|
}
|
||||||
|
table_info["081user"] = {
|
||||||
|
_key = translate("User"),
|
||||||
|
_value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"
|
||||||
|
}
|
||||||
|
table_info["09mount"] = {
|
||||||
|
_key = translate("Mount/Volume"),
|
||||||
|
_value = get_mounts(container_info) or "-"
|
||||||
|
}
|
||||||
|
table_info["10cmd"] = {
|
||||||
|
_key = translate("Command"),
|
||||||
|
_value = get_command(container_info) or "-"
|
||||||
|
}
|
||||||
|
table_info["11env"] = {
|
||||||
|
_key = translate("Env"),
|
||||||
|
_value = get_env(container_info) or "-"
|
||||||
|
}
|
||||||
|
table_info["12ports"] = {
|
||||||
|
_key = translate("Ports"),
|
||||||
|
_value = get_ports(container_info) or "-"
|
||||||
|
}
|
||||||
|
table_info["13links"] = {
|
||||||
|
_key = translate("Links"),
|
||||||
|
_value = get_links(container_info) or "-"
|
||||||
|
}
|
||||||
|
table_info["14device"] = {
|
||||||
|
_key = translate("Device"),
|
||||||
|
_value = get_device(container_info) or "-"
|
||||||
|
}
|
||||||
|
table_info["15tmpfs"] = {
|
||||||
|
_key = translate("Tmpfs"),
|
||||||
|
_value = get_tmpfs(container_info) or "-"
|
||||||
|
}
|
||||||
|
table_info["16dns"] = {
|
||||||
|
_key = translate("DNS"),
|
||||||
|
_value = get_dns(container_info) or "-"
|
||||||
|
}
|
||||||
|
table_info["17sysctl"] = {
|
||||||
|
_key = translate("Sysctl"),
|
||||||
|
_value = get_sysctl(container_info) or "-"
|
||||||
|
}
|
||||||
|
|
||||||
info_networks = get_networks(container_info)
|
info_networks = get_networks(container_info)
|
||||||
list_networks = {}
|
list_networks = {}
|
||||||
for _, v in ipairs (networks) do
|
for _, v in ipairs (networks) do
|
||||||
|
@ -270,24 +381,31 @@ if action == "info" then
|
||||||
if type(info_networks)== "table" then
|
if type(info_networks)== "table" then
|
||||||
for k,v in pairs(info_networks) do
|
for k,v in pairs(info_networks) do
|
||||||
table_info["14network"..k] = {
|
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
|
list_networks[k]=nil
|
||||||
end
|
end
|
||||||
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
s = m:section(Table,table_info)
|
||||||
d_info = m:section(Table,table_info)
|
s.nodescr=true
|
||||||
d_info.nodescr=true
|
s.formvalue=function(self, section)
|
||||||
d_info.formvalue=function(self, section)
|
|
||||||
return table_info
|
return table_info
|
||||||
end
|
end
|
||||||
dv_key = d_info:option(DummyValue, "_key", translate("Info"))
|
|
||||||
dv_key.width = "20%"
|
o = s:option(DummyValue, "_key", translate("Info"))
|
||||||
dv_value = d_info:option(ListValue, "_value")
|
o.width = "20%"
|
||||||
dv_value.render = function(self, section, scope)
|
|
||||||
|
o = s:option(ListValue, "_value")
|
||||||
|
o.render = function(self, section, scope)
|
||||||
if table_info[section]._key == translate("Name") then
|
if table_info[section]._key == translate("Name") then
|
||||||
self:reset_values()
|
self:reset_values()
|
||||||
self.template = "cbi/value"
|
self.template = "cbi/value"
|
||||||
|
@ -325,23 +443,23 @@ if action == "info" then
|
||||||
DummyValue.render(self, section, scope)
|
DummyValue.render(self, section, scope)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
dv_value.forcewrite = true -- for write function using simpleform
|
o.forcewrite = true
|
||||||
dv_value.write = function(self, section, value)
|
o.write = function(self, section, value)
|
||||||
table_info[section]._value=value
|
table_info[section]._value=value
|
||||||
end
|
end
|
||||||
dv_value.validate = function(self, value)
|
o.validate = function(self, value)
|
||||||
return value
|
return value
|
||||||
end
|
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
|
table_info[section]._opts=value
|
||||||
end
|
end
|
||||||
dv_opts.validate = function(self, value)
|
o.validate = function(self, value)
|
||||||
return value
|
return value
|
||||||
end
|
end
|
||||||
dv_opts.render = function(self, section, scope)
|
o.render = function(self, section, scope)
|
||||||
if table_info[section]._key==translate("Connect Network") then
|
if table_info[section]._key==translate("Connect Network") then
|
||||||
self.template = "cbi/value"
|
self.template = "cbi/value"
|
||||||
self.keylist = {}
|
self.keylist = {}
|
||||||
|
@ -357,11 +475,12 @@ if action == "info" then
|
||||||
DummyValue.render(self, section, scope)
|
DummyValue.render(self, section, scope)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
btn_update = d_info:option(Button, "_button")
|
|
||||||
btn_update.forcewrite = true
|
o = s:option(Button, "_button")
|
||||||
btn_update.render = function(self, section, scope)
|
o.forcewrite = true
|
||||||
|
o.render = function(self, section, scope)
|
||||||
if table_info[section]._button and table_info[section]._value ~= nil then
|
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.template = "cbi/button"
|
||||||
self.inputstyle = "edit"
|
self.inputstyle = "edit"
|
||||||
Button.render(self, section, scope)
|
Button.render(self, section, scope)
|
||||||
|
@ -371,26 +490,50 @@ if action == "info" then
|
||||||
DummyValue.render(self, section, scope)
|
DummyValue.render(self, section, scope)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
btn_update.write = function(self, section, value)
|
o.write = function(self, section, value)
|
||||||
local res
|
local res
|
||||||
|
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
|
|
||||||
if section == "01name" then
|
if section == "01name" then
|
||||||
docker:append_status("Containers: rename " .. container_id .. "...")
|
docker:append_status("Containers: rename " .. container_id .. "...")
|
||||||
local new_name = table_info[section]._value
|
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
|
elseif section == "08restart" then
|
||||||
docker:append_status("Containers: update " .. container_id .. "...")
|
docker:append_status("Containers: update " .. container_id .. "...")
|
||||||
local new_restart = table_info[section]._value
|
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
|
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
|
leave_network = leave_network or table_info[section]._value
|
||||||
docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...")
|
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
|
elseif section == "15connect" then
|
||||||
local connect_network = table_info[section]._value
|
local connect_network = table_info[section]._value
|
||||||
local network_opiton
|
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 {
|
network_opiton = table_info[section]._opts ~= "" and {
|
||||||
IPAMConfig={
|
IPAMConfig={
|
||||||
IPv4Address=table_info[section]._opts
|
IPv4Address=table_info[section]._opts
|
||||||
|
@ -398,8 +541,15 @@ if action == "info" then
|
||||||
} or nil
|
} or nil
|
||||||
end
|
end
|
||||||
docker:append_status("Network: connect " .. connect_network .. container_id .. "...")
|
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
|
end
|
||||||
|
|
||||||
if res and res.code > 300 then
|
if res and res.code > 300 then
|
||||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||||
else
|
else
|
||||||
|
@ -407,32 +557,38 @@ if action == "info" then
|
||||||
end
|
end
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info"))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- info end
|
|
||||||
elseif action == "resources" then
|
elseif action == "resources" then
|
||||||
local resources_section= m:section(SimpleSection)
|
s = 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."))
|
o = s:option( Value, "cpus",
|
||||||
d.placeholder = "1.5"
|
translate("CPUs"),
|
||||||
d.rmempty = true
|
translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
|
||||||
d.datatype="ufloat"
|
o.placeholder = "1.5"
|
||||||
d.default = container_info.HostConfig.NanoCpus / (10^9)
|
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."))
|
o = s:option(Value, "cpushares",
|
||||||
d.placeholder = "1024"
|
translate("CPU Shares Weight"),
|
||||||
d.rmempty = true
|
translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
|
||||||
d.datatype="uinteger"
|
o.placeholder = "1024"
|
||||||
d.default = container_info.HostConfig.CpuShares
|
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."))
|
o = s:option(Value, "memory",
|
||||||
d.placeholder = "128m"
|
translate("Memory"),
|
||||||
d.rmempty = true
|
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.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0
|
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."))
|
o = s:option(Value, "blkioweight",
|
||||||
d.placeholder = "500"
|
translate("Block IO Weight"),
|
||||||
d.rmempty = true
|
translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
|
||||||
d.datatype="uinteger"
|
o.placeholder = "500"
|
||||||
d.default = container_info.HostConfig.BlkioWeight
|
o.rmempty = true
|
||||||
|
o.datatype="uinteger"
|
||||||
|
o.default = container_info.HostConfig.BlkioWeight
|
||||||
|
|
||||||
m.handle = function(self, state, data)
|
m.handle = function(self, state, data)
|
||||||
if state == FORM_VALID then
|
if state == FORM_VALID then
|
||||||
|
@ -452,12 +608,14 @@ elseif action == "resources" then
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
request_body = {
|
request_body = {
|
||||||
BlkioWeight = tonumber(data.blkioweight),
|
BlkioWeight = tonumber(data.blkioweight),
|
||||||
NanoCPUs = tonumber(data.cpus)*10^9,
|
NanoCPUs = tonumber(data.cpus)*10^9,
|
||||||
Memory = tonumber(memory),
|
Memory = tonumber(memory),
|
||||||
CpuShares = tonumber(data.cpushares)
|
CpuShares = tonumber(data.cpushares)
|
||||||
}
|
}
|
||||||
|
|
||||||
docker:write_status("Containers: update " .. container_id .. "...")
|
docker:write_status("Containers: update " .. container_id .. "...")
|
||||||
local res = dk.containers:update({id = container_id, body = request_body})
|
local res = dk.containers:update({id = container_id, body = request_body})
|
||||||
if res and res.code >= 300 then
|
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"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif action == "file" then
|
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.submit = false
|
||||||
m.reset = false
|
m.reset = false
|
||||||
filesection.template = "dockerman/container_file"
|
|
||||||
filesection.container = container_id
|
|
||||||
elseif action == "inspect" then
|
elseif action == "inspect" then
|
||||||
local inspectsection= m:section(SimpleSection)
|
s = m:section(SimpleSection)
|
||||||
inspectsection.syslog = luci.jsonc.stringify(container_info, true)
|
s.syslog = luci.jsonc.stringify(container_info, true)
|
||||||
inspectsection.title = translate("Container Inspect")
|
s.title = translate("Container Inspect")
|
||||||
inspectsection.template = "dockerman/logs"
|
s.template = "dockerman/logs"
|
||||||
m.submit = false
|
m.submit = false
|
||||||
m.reset = false
|
m.reset = false
|
||||||
elseif action == "logs" then
|
elseif action == "logs" then
|
||||||
local logsection= m:section(SimpleSection)
|
|
||||||
local logs = ""
|
local logs = ""
|
||||||
local query ={
|
local query ={
|
||||||
stdout = 1,
|
stdout = 1,
|
||||||
stderr = 1,
|
stderr = 1,
|
||||||
tail = 1000
|
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
|
if logs.code == 200 then
|
||||||
logsection.syslog=logs.body
|
s.syslog=logs.body
|
||||||
else
|
else
|
||||||
logsection.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body
|
s.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body
|
||||||
end
|
end
|
||||||
logsection.title=translate("Container Logs")
|
|
||||||
logsection.template = "dockerman/logs"
|
s.title=translate("Container Logs")
|
||||||
|
s.template = "dockerman/logs"
|
||||||
m.submit = false
|
m.submit = false
|
||||||
m.reset = false
|
m.reset = false
|
||||||
elseif action == "console" then
|
elseif action == "console" then
|
||||||
|
@ -504,60 +666,80 @@ elseif action == "console" then
|
||||||
m.reset = false
|
m.reset = false
|
||||||
local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil
|
local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil
|
||||||
local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") 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
|
if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then
|
||||||
local consolesection= m:section(SimpleSection)
|
|
||||||
local cmd = "/bin/sh"
|
local cmd = "/bin/sh"
|
||||||
local uid
|
local uid
|
||||||
local vcommand = consolesection:option(Value, "command", translate("Command"))
|
|
||||||
vcommand:value("/bin/sh", "/bin/sh")
|
s = m:section(SimpleSection)
|
||||||
vcommand:value("/bin/ash", "/bin/ash")
|
|
||||||
vcommand:value("/bin/bash", "/bin/bash")
|
o = s:option(Value, "command", translate("Command"))
|
||||||
vcommand.default = "/bin/sh"
|
o:value("/bin/sh", "/bin/sh")
|
||||||
vcommand.forcewrite = true
|
o:value("/bin/ash", "/bin/ash")
|
||||||
vcommand.write = function(self, section, value)
|
o:value("/bin/bash", "/bin/bash")
|
||||||
|
o.default = "/bin/sh"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section, value)
|
||||||
cmd = value
|
cmd = value
|
||||||
end
|
end
|
||||||
local vuid = consolesection:option(Value, "uid", translate("UID"))
|
|
||||||
vuid.forcewrite = true
|
o = s:option(Value, "uid", translate("UID"))
|
||||||
vuid.write = function(self, section, value)
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section, value)
|
||||||
uid = value
|
uid = value
|
||||||
end
|
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.inputstyle = "add"
|
||||||
self.title = " "
|
self.title = " "
|
||||||
self.inputtitle = translate("Connect")
|
self.inputtitle = translate("Connect")
|
||||||
Button.render(self, section, scope)
|
Button.render(self, section, scope)
|
||||||
end
|
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_docker = luci.util.exec("which docker"):match("^.+docker") or nil
|
||||||
local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") 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'
|
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)
|
luci.util.exec(kill_ttyd)
|
||||||
local hosts
|
local hosts
|
||||||
local uci = (require "luci.model.uci").cursor()
|
local uci = (require "luci.model.uci").cursor()
|
||||||
local remote = uci:get("dockerman", "local", "remote_endpoint")
|
local remote = uci:get_bool("dockerd", "globals", "remote_endpoint")
|
||||||
local socket_path = (remote == "false" or not remote) and uci:get("dockerman", "local", "socket_path") or nil
|
local host = nil
|
||||||
local host = (remote == "true") and uci:get("dockerman", "local", "remote_host") or nil
|
local port = nil
|
||||||
local port = (remote == "true") and uci:get("dockerman", "local", "remote_port") or 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
|
if remote and host and port then
|
||||||
hosts = host .. ':'.. port
|
hosts = host .. ':'.. port
|
||||||
elseif socket_path then
|
elseif socket_path then
|
||||||
hosts = "unix://" .. socket_path
|
hosts = socket_path
|
||||||
else
|
else
|
||||||
return
|
return
|
||||||
end
|
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 .. ' &'
|
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)
|
os.execute(start_cmd)
|
||||||
local console = consolesection:option(DummyValue, "console")
|
|
||||||
console.container_id = container_id
|
o = s:option(DummyValue, "console")
|
||||||
console.template = "dockerman/container_console"
|
o.container_id = container_id
|
||||||
|
o.template = "dockerman/container_console"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif action == "stats" then
|
elseif action == "stats" then
|
||||||
local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}})
|
local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}})
|
||||||
local container_top
|
local container_top
|
||||||
|
|
||||||
if response.code == 200 then
|
if response.code == 200 then
|
||||||
container_top=response.body
|
container_top=response.body
|
||||||
else
|
else
|
||||||
|
@ -568,19 +750,30 @@ elseif action == "stats" then
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(container_top) == "table" then
|
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
|
container_top = response.body
|
||||||
stat_section = m:section(SimpleSection)
|
s = m:section(Table, table_stats, translate("Stats"))
|
||||||
stat_section.container_id = container_id
|
s:option(DummyValue, "key", translate("Stats")).width="33%"
|
||||||
stat_section.template = "dockerman/container_stats"
|
s:option(DummyValue, "value")
|
||||||
table_stats = {cpu={key=translate("CPU Useage"),value='-'},memory={key=translate("Memory Useage"),value='-'}}
|
|
||||||
stat_section = m:section(Table, table_stats, translate("Stats"))
|
|
||||||
stat_section:option(DummyValue, "key", translate("Stats")).width="33%"
|
|
||||||
stat_section:option(DummyValue, "value")
|
|
||||||
top_section = m:section(Table, container_top.Processes, translate("TOP"))
|
top_section = m:section(Table, container_top.Processes, translate("TOP"))
|
||||||
for i, v in ipairs(container_top.Titles) do
|
for i, v in ipairs(container_top.Titles) do
|
||||||
top_section:option(DummyValue, i, translate(v))
|
top_section:option(DummyValue, i, translate(v))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
m.submit = false
|
m.submit = false
|
||||||
m.reset = false
|
m.reset = false
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,49 +3,69 @@ LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
require "luci.util"
|
|
||||||
local http = require "luci.http"
|
local http = require "luci.http"
|
||||||
local uci = luci.model.uci.cursor()
|
|
||||||
local docker = require "luci.model.docker"
|
local docker = require "luci.model.docker"
|
||||||
local dk = docker.new()
|
|
||||||
|
|
||||||
local images, networks, containers
|
local m, s, o
|
||||||
local res = dk.images:list()
|
local images, networks, containers, res
|
||||||
if res.code <300 then images = res.body else return end
|
|
||||||
|
local dk = docker.new()
|
||||||
|
res = dk.images:list()
|
||||||
|
if res.code <300 then
|
||||||
|
images = res.body
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
res = dk.networks:list()
|
res = dk.networks:list()
|
||||||
if res.code <300 then networks = res.body else return end
|
if res.code <300 then
|
||||||
res = dk.containers:list({query = {all=true}})
|
networks = res.body
|
||||||
if res.code <300 then containers = res.body else return end
|
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
|
local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode
|
||||||
|
|
||||||
function get_containers()
|
function get_containers()
|
||||||
local data = {}
|
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
|
for i, v in ipairs(containers) do
|
||||||
local index = v.Created .. v.Id
|
local index = v.Created .. v.Id
|
||||||
|
|
||||||
data[index]={}
|
data[index]={}
|
||||||
data[index]["_selected"] = 0
|
data[index]["_selected"] = 0
|
||||||
data[index]["_id"] = v.Id:sub(1,12)
|
data[index]["_id"] = v.Id:sub(1,12)
|
||||||
data[index]["name"] = v.Names[1]:sub(2)
|
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]["_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
|
data[index]["_status"] = v.Status
|
||||||
|
|
||||||
if v.Status:find("^Up") then
|
if v.Status:find("^Up") then
|
||||||
data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>"
|
data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>"
|
||||||
else
|
else
|
||||||
data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>"
|
data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>"
|
||||||
end
|
end
|
||||||
|
|
||||||
if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then
|
if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then
|
||||||
for networkname, netconfig in pairs(v.NetworkSettings.Networks) do
|
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 "")
|
data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "")
|
||||||
end
|
end
|
||||||
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
|
if v.Ports and next(v.Ports) ~= nil then
|
||||||
data[index]["_ports"] = nil
|
data[index]["_ports"] = nil
|
||||||
for _,v2 in ipairs(v.Ports) do
|
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 "")
|
.. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "</a>" or "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for ii,iv in ipairs(images) do
|
for ii,iv in ipairs(images) do
|
||||||
if iv.Id == v.ImageID then
|
if iv.Id == v.ImageID then
|
||||||
data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>")
|
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]["_image_id"] = v.ImageID:sub(8,20)
|
||||||
data[index]["_command"] = v.Command
|
data[index]["_command"] = v.Command
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local c_lists = get_containers()
|
local container_list = get_containers()
|
||||||
-- list Containers
|
|
||||||
-- m = Map("docker", translate("Docker"))
|
|
||||||
m = SimpleForm("docker", translate("Docker"))
|
m = SimpleForm("docker", translate("Docker"))
|
||||||
m.submit=false
|
m.submit=false
|
||||||
m.reset=false
|
m.reset=false
|
||||||
|
|
||||||
docker_status = m:section(SimpleSection)
|
s = m:section(SimpleSection)
|
||||||
docker_status.template = "dockerman/apply_widget"
|
s.template = "dockerman/apply_widget"
|
||||||
docker_status.err=docker:read_status()
|
s.err=docker:read_status()
|
||||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ")
|
||||||
if docker_status.err then docker:clear_status() end
|
if s.err then
|
||||||
|
docker:clear_status()
|
||||||
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
|
|
||||||
end
|
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 start_stop_remove = function(m,cmd)
|
||||||
local c_selected = {}
|
local container_selected = {}
|
||||||
-- 遍历table中sectionid
|
|
||||||
local c_table_sids = c_table:cfgsections()
|
for k in pairs(container_list) do
|
||||||
for _, c_table_sid in ipairs(c_table_sids) do
|
if container_list[k]._selected == 1 then
|
||||||
-- 得到选中项的名字
|
container_selected[#container_selected + 1] = container_list[k].name
|
||||||
if c_lists[c_table_sid]._selected == 1 then
|
|
||||||
c_selected[#c_selected+1] = c_lists[c_table_sid].name --container_name:cfgvalue(c_table_sid)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #c_selected >0 then
|
|
||||||
docker:clear_status()
|
if #container_selected > 0 then
|
||||||
local success = true
|
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 .. "...")
|
docker:append_status("Containers: " .. cmd .. " " .. cont .. "...")
|
||||||
local res = dk.containers[cmd](dk, {id = cont})
|
local res = dk.containers[cmd](dk, {id = cont})
|
||||||
if res and res.code >= 300 then
|
if res and res.code >= 300 then
|
||||||
|
@ -133,63 +160,72 @@ local start_stop_remove = function(m,cmd)
|
||||||
docker:append_status("done\n")
|
docker:append_status("done\n")
|
||||||
end
|
end
|
||||||
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"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
action_section = m:section(Table,{{}})
|
s = m:section(Table,{{}})
|
||||||
action_section.notitle=true
|
s.notitle=true
|
||||||
action_section.rowcolors=false
|
s.rowcolors=false
|
||||||
action_section.template="cbi/nullsection"
|
s.template="cbi/nullsection"
|
||||||
|
|
||||||
btnnew=action_section:option(Button, "_new")
|
o = s:option(Button, "_new")
|
||||||
btnnew.inputtitle= translate("New")
|
o.inputtitle= translate("New")
|
||||||
btnnew.template = "dockerman/cbi/inlinebutton"
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnnew.inputstyle = "add"
|
o.inputstyle = "add"
|
||||||
btnnew.forcewrite = true
|
o.forcewrite = true
|
||||||
btnstart=action_section:option(Button, "_start")
|
o.write = function(self, section)
|
||||||
btnstart.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnstart.inputtitle=translate("Start")
|
|
||||||
btnstart.inputstyle = "apply"
|
|
||||||
btnstart.forcewrite = true
|
|
||||||
btnrestart=action_section:option(Button, "_restart")
|
|
||||||
btnrestart.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnrestart.inputtitle=translate("Restart")
|
|
||||||
btnrestart.inputstyle = "reload"
|
|
||||||
btnrestart.forcewrite = true
|
|
||||||
btnstop=action_section:option(Button, "_stop")
|
|
||||||
btnstop.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnstop.inputtitle=translate("Stop")
|
|
||||||
btnstop.inputstyle = "reset"
|
|
||||||
btnstop.forcewrite = true
|
|
||||||
btnkill=action_section:option(Button, "_kill")
|
|
||||||
btnkill.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnkill.inputtitle=translate("Kill")
|
|
||||||
btnkill.inputstyle = "reset"
|
|
||||||
btnkill.forcewrite = true
|
|
||||||
btnremove=action_section:option(Button, "_remove")
|
|
||||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnremove.inputtitle=translate("Remove")
|
|
||||||
btnremove.inputstyle = "remove"
|
|
||||||
btnremove.forcewrite = true
|
|
||||||
btnnew.write = function(self, section)
|
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
|
||||||
end
|
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")
|
start_stop_remove(m,"start")
|
||||||
end
|
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")
|
start_stop_remove(m,"restart")
|
||||||
end
|
end
|
||||||
btnremove.write = function(self, section)
|
|
||||||
start_stop_remove(m,"remove")
|
o = s:option(Button, "_stop")
|
||||||
end
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnstop.write = function(self, section)
|
o.inputtitle=translate("Stop")
|
||||||
|
o.inputstyle = "reset"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section)
|
||||||
start_stop_remove(m,"stop")
|
start_stop_remove(m,"stop")
|
||||||
end
|
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")
|
start_stop_remove(m,"kill")
|
||||||
end
|
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
|
return m
|
||||||
|
|
|
@ -3,82 +3,107 @@ LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
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 docker = require "luci.model.docker"
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
|
|
||||||
local containers, images
|
local containers, images, res
|
||||||
local res = dk.images:list()
|
local m, s, o
|
||||||
if res.code <300 then images = res.body else return end
|
|
||||||
res = dk.containers:list({query = {all=true}})
|
res = dk.images:list()
|
||||||
if res.code <300 then containers = res.body else return end
|
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()
|
function get_images()
|
||||||
local data = {}
|
local data = {}
|
||||||
|
|
||||||
for i, v in ipairs(images) do
|
for i, v in ipairs(images) do
|
||||||
local index = v.Created .. v.Id
|
local index = v.Created .. v.Id
|
||||||
|
|
||||||
data[index]={}
|
data[index]={}
|
||||||
data[index]["_selected"] = 0
|
data[index]["_selected"] = 0
|
||||||
data[index]["id"] = v.Id:sub(8)
|
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>'
|
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
|
if v.RepoTags and next(v.RepoTags)~=nil then
|
||||||
for i, v1 in ipairs(v.RepoTags) do
|
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>'))
|
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
|
if not data[index]["tag"] then
|
||||||
data[index]["tag"] = v1--:match("<none>") and nil or v1
|
data[index]["tag"] = v1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+")
|
data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+")
|
||||||
data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>"
|
data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>"
|
||||||
end
|
end
|
||||||
|
|
||||||
data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","<none>")
|
data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","<none>")
|
||||||
-- data[index]["_tags"] = '<a href="javascript:handle_tag(\''..data[index]["_id"]..'\')">' .. data[index]["_tags"] .. '</a>'
|
|
||||||
for ci,cv in ipairs(containers) do
|
for ci,cv in ipairs(containers) do
|
||||||
if v.Id == cv.ImageID then
|
if v.Id == cv.ImageID then
|
||||||
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
|
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>"
|
'<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2).."</a>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
|
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)
|
data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created)
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local image_list = get_images()
|
local image_list = get_images()
|
||||||
|
|
||||||
-- m = Map("docker", translate("Docker"))
|
|
||||||
m = SimpleForm("docker", translate("Docker"))
|
m = SimpleForm("docker", translate("Docker"))
|
||||||
m.submit=false
|
m.submit=false
|
||||||
m.reset=false
|
m.reset=false
|
||||||
|
|
||||||
local pull_value={_image_tag_name="", _registry="index.docker.io"}
|
local pull_value={
|
||||||
local pull_section = m:section(SimpleSection, translate("Pull Image"))
|
_image_tag_name="",
|
||||||
pull_section.template="cbi/nullsection"
|
_registry="index.docker.io"
|
||||||
local tag_name = pull_section:option(Value, "_image_tag_name")
|
}
|
||||||
tag_name.template = "dockerman/cbi/inlinevalue"
|
|
||||||
tag_name.placeholder="lisaac/luci:latest"
|
s = m:section(SimpleSection, translate("Pull Image"))
|
||||||
local action_pull = pull_section:option(Button, "_pull")
|
s.template="cbi/nullsection"
|
||||||
action_pull.inputtitle= translate("Pull")
|
|
||||||
action_pull.template = "dockerman/cbi/inlinebutton"
|
o = s:option(Value, "_image_tag_name")
|
||||||
action_pull.inputstyle = "add"
|
o.template = "dockerman/cbi/inlinevalue"
|
||||||
tag_name.write = function(self, section, value)
|
o.placeholder="lisaac/luci:latest"
|
||||||
|
o.write = function(self, section, value)
|
||||||
local hastag = value:find(":")
|
local hastag = value:find(":")
|
||||||
|
|
||||||
if not hastag then
|
if not hastag then
|
||||||
value = value .. ":latest"
|
value = value .. ":latest"
|
||||||
end
|
end
|
||||||
pull_value["_image_tag_name"] = value
|
pull_value["_image_tag_name"] = value
|
||||||
end
|
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 tag = pull_value["_image_tag_name"]
|
||||||
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
||||||
|
|
||||||
if tag and tag ~= "" then
|
if tag and tag ~= "" then
|
||||||
docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n")
|
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)
|
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
|
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()
|
docker:clear_status()
|
||||||
else
|
else
|
||||||
|
@ -87,48 +112,63 @@ action_pull.write = function(self, section)
|
||||||
else
|
else
|
||||||
docker:append_status("code: 400 please input the name of image name!")
|
docker:append_status("code: 400 please input the name of image name!")
|
||||||
end
|
end
|
||||||
|
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
|
||||||
end
|
end
|
||||||
|
|
||||||
local import_section = m:section(SimpleSection, translate("Import Images"))
|
s = m:section(SimpleSection, translate("Import Images"))
|
||||||
local im = import_section:option(DummyValue, "_image_import")
|
|
||||||
im.template = "dockerman/images_import"
|
|
||||||
|
|
||||||
local image_table = m:section(Table, image_list, translate("Images"))
|
o = s:option(DummyValue, "_image_import")
|
||||||
|
o.template = "dockerman/images_import"
|
||||||
|
|
||||||
local image_selecter = image_table:option(Flag, "_selected","")
|
s = m:section(Table, image_list, translate("Images"))
|
||||||
image_selecter.disabled = 0
|
|
||||||
image_selecter.enabled = 1
|
|
||||||
image_selecter.default = 0
|
|
||||||
|
|
||||||
local image_id = image_table:option(DummyValue, "_id", translate("ID"))
|
o = s:option(Flag, "_selected","")
|
||||||
image_id.rawhtml = true
|
o.disabled = 0
|
||||||
image_table:option(DummyValue, "_tags", translate("RepoTags")).rawhtml = true
|
o.enabled = 1
|
||||||
image_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
|
o.default = 0
|
||||||
image_table:option(DummyValue, "_size", translate("Size"))
|
o.write = function(self, section, value)
|
||||||
image_table:option(DummyValue, "_created", translate("Created"))
|
|
||||||
image_selecter.write = function(self, section, value)
|
|
||||||
image_list[section]._selected = value
|
image_list[section]._selected = value
|
||||||
end
|
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 remove_action = function(force)
|
||||||
local image_selected = {}
|
local image_selected = {}
|
||||||
-- 遍历table中sectionid
|
|
||||||
local image_table_sids = image_table:cfgsections()
|
for k in pairs(image_list) do
|
||||||
for _, image_table_sid in ipairs(image_table_sids) do
|
if image_list[k]._selected == 1 then
|
||||||
-- 得到选中项的名字
|
image_selected[#image_selected+1] = (image_list[k]["_tags"]:match("<br>") or image_list[k]["_tags"]:match("<none>")) and image_list[k].id or image_list[k].tag
|
||||||
if image_list[image_table_sid]._selected == 1 then
|
|
||||||
image_selected[#image_selected+1] = (image_list[image_table_sid]["_tags"]:match("<br>") or image_list[image_table_sid]["_tags"]:match("<none>")) and image_list[image_table_sid].id or image_list[image_table_sid].tag
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if next(image_selected) ~= nil then
|
if next(image_selected) ~= nil then
|
||||||
local success = true
|
local success = true
|
||||||
|
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
for _, img in ipairs(image_selected) do
|
for _, img in ipairs(image_selected) do
|
||||||
docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
|
|
||||||
local query
|
local query
|
||||||
if force then query = {force = true} end
|
docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
|
||||||
local msg = dk.images:remove({id = img, query = query})
|
|
||||||
|
if force then
|
||||||
|
query = {force = true}
|
||||||
|
end
|
||||||
|
|
||||||
|
local msg = dk.images:remove({
|
||||||
|
id = img,
|
||||||
|
query = query
|
||||||
|
})
|
||||||
if msg.code ~= 200 then
|
if msg.code ~= 200 then
|
||||||
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
||||||
success = false
|
success = false
|
||||||
|
@ -136,59 +176,67 @@ local remove_action = function(force)
|
||||||
docker:append_status("done\n")
|
docker:append_status("done\n")
|
||||||
end
|
end
|
||||||
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"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local docker_status = m:section(SimpleSection)
|
s = m:section(SimpleSection)
|
||||||
docker_status.template = "dockerman/apply_widget"
|
s.template = "dockerman/apply_widget"
|
||||||
docker_status.err = docker:read_status()
|
s.err = docker:read_status()
|
||||||
docker_status.err = docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
s.err = s.err and s.err:gsub("\n","<br>"):gsub(" "," ")
|
||||||
if docker_status.err then docker:clear_status() end
|
if s.err then
|
||||||
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
|
||||||
local action = m:section(Table,{{}})
|
s = m:section(Table,{{}})
|
||||||
action.notitle=true
|
s.notitle=true
|
||||||
action.rowcolors=false
|
s.rowcolors=false
|
||||||
action.template="cbi/nullsection"
|
s.template="cbi/nullsection"
|
||||||
|
|
||||||
local btnremove = action:option(Button, "remove")
|
o = s:option(Button, "remove")
|
||||||
btnremove.inputtitle= translate("Remove")
|
o.inputtitle= translate("Remove")
|
||||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnremove.inputstyle = "remove"
|
o.inputstyle = "remove"
|
||||||
btnremove.forcewrite = true
|
o.forcewrite = true
|
||||||
btnremove.write = function(self, section)
|
o.write = function(self, section)
|
||||||
remove_action()
|
remove_action()
|
||||||
end
|
end
|
||||||
|
|
||||||
local btnforceremove = action:option(Button, "forceremove")
|
o = s:option(Button, "forceremove")
|
||||||
btnforceremove.inputtitle= translate("Force Remove")
|
o.inputtitle= translate("Force Remove")
|
||||||
btnforceremove.template = "dockerman/cbi/inlinebutton"
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnforceremove.inputstyle = "remove"
|
o.inputstyle = "remove"
|
||||||
btnforceremove.forcewrite = true
|
o.forcewrite = true
|
||||||
btnforceremove.write = function(self, section)
|
o.write = function(self, section)
|
||||||
remove_action(true)
|
remove_action(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
local btnsave = action:option(Button, "save")
|
o = s:option(Button, "save")
|
||||||
btnsave.inputtitle= translate("Save")
|
o.inputtitle= translate("Save")
|
||||||
btnsave.template = "dockerman/cbi/inlinebutton"
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnsave.inputstyle = "edit"
|
o.inputstyle = "edit"
|
||||||
btnsave.forcewrite = true
|
o.forcewrite = true
|
||||||
btnsave.write = function (self, section)
|
o.write = function (self, section)
|
||||||
local image_selected = {}
|
local image_selected = {}
|
||||||
local image_table_sids = image_table:cfgsections()
|
|
||||||
for _, image_table_sid in ipairs(image_table_sids) do
|
for k in pairs(image_list) do
|
||||||
if image_list[image_table_sid]._selected == 1 then
|
if image_list[k]._selected == 1 then
|
||||||
image_selected[#image_selected+1] = image_list[image_table_sid].id --image_id:cfgvalue(image_table_sid)
|
image_selected[#image_selected + 1] = image_list[k].id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if next(image_selected) ~= nil then
|
if next(image_selected) ~= nil then
|
||||||
local names
|
local names, first
|
||||||
|
|
||||||
for _, img in ipairs(image_selected) do
|
for _, img in ipairs(image_selected) do
|
||||||
names = names and (names .. "&names=".. img) or img
|
names = names and (names .. "&names=".. img) or img
|
||||||
end
|
end
|
||||||
local first
|
|
||||||
local cb = function(res, chunk)
|
local cb = function(res, chunk)
|
||||||
if res.code == 200 then
|
if res.code == 200 then
|
||||||
if not first then
|
if not first then
|
||||||
|
@ -205,8 +253,10 @@ btnsave.write = function (self, section)
|
||||||
luci.ltn12.pump.all(chunk, luci.http.write)
|
luci.ltn12.pump.all(chunk, luci.http.write)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...")
|
docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...")
|
||||||
local msg = dk.images:get({query = {names = names}}, cb)
|
local msg = dk.images:get({query = {names = names}}, cb)
|
||||||
|
|
||||||
if msg.code ~= 200 then
|
if msg.code ~= 200 then
|
||||||
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
||||||
success = false
|
success = false
|
||||||
|
@ -216,8 +266,9 @@ btnsave.write = function (self, section)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local btnload = action:option(Button, "load")
|
o = s:option(Button, "load")
|
||||||
btnload.inputtitle= translate("Load")
|
o.inputtitle= translate("Load")
|
||||||
btnload.template = "dockerman/images_load"
|
o.template = "dockerman/images_load"
|
||||||
btnload.inputstyle = "add"
|
o.inputstyle = "add"
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
|
@ -3,113 +3,134 @@ LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
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 docker = require "luci.model.docker"
|
||||||
local dk = docker.new()
|
|
||||||
local networks
|
local m, s, o
|
||||||
local res = dk.networks:list()
|
local networks, dk, res
|
||||||
if res.code < 300 then networks = res.body else return end
|
|
||||||
|
dk = docker.new()
|
||||||
|
res = dk.networks:list()
|
||||||
|
if res.code < 300 then
|
||||||
|
networks = res.body
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local get_networks = function ()
|
local get_networks = function ()
|
||||||
local data = {}
|
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
|
for i, v in ipairs(networks) do
|
||||||
local index = v.Created .. v.Id
|
local index = v.Created .. v.Id
|
||||||
|
|
||||||
data[index]={}
|
data[index]={}
|
||||||
data[index]["_selected"] = 0
|
data[index]["_selected"] = 0
|
||||||
data[index]["_id"] = v.Id:sub(1,12)
|
data[index]["_id"] = v.Id:sub(1,12)
|
||||||
data[index]["_name"] = v.Name
|
data[index]["_name"] = v.Name
|
||||||
data[index]["_driver"] = v.Driver
|
data[index]["_driver"] = v.Driver
|
||||||
|
|
||||||
if v.Driver == "bridge" then
|
if v.Driver == "bridge" then
|
||||||
data[index]["_interface"] = v.Options["com.docker.network.bridge.name"]
|
data[index]["_interface"] = v.Options["com.docker.network.bridge.name"]
|
||||||
elseif v.Driver == "macvlan" then
|
elseif v.Driver == "macvlan" then
|
||||||
data[index]["_interface"] = v.Options.parent
|
data[index]["_interface"] = v.Options.parent
|
||||||
end
|
end
|
||||||
|
|
||||||
data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
|
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
|
data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local network_list = get_networks()
|
local network_list = get_networks()
|
||||||
-- m = Map("docker", translate("Docker"))
|
|
||||||
m = SimpleForm("docker", translate("Docker"))
|
m = SimpleForm("docker", translate("Docker"))
|
||||||
m.submit=false
|
m.submit=false
|
||||||
m.reset=false
|
m.reset=false
|
||||||
|
|
||||||
network_table = m:section(Table, network_list, translate("Networks"))
|
s = m:section(Table, network_list, translate("Networks"))
|
||||||
network_table.nodescr=true
|
s.nodescr=true
|
||||||
|
|
||||||
network_selecter = network_table:option(Flag, "_selected","")
|
o = s:option(Flag, "_selected","")
|
||||||
network_selecter.template = "dockerman/cbi/xfvalue"
|
o.template = "dockerman/cbi/xfvalue"
|
||||||
network_id = network_table:option(DummyValue, "_id", translate("ID"))
|
o.disabled = 0
|
||||||
network_selecter.disabled = 0
|
o.enabled = 1
|
||||||
network_selecter.enabled = 1
|
o.default = 0
|
||||||
network_selecter.default = 0
|
o.render = function(self, section, scope)
|
||||||
network_selecter.render = function(self, section, scope)
|
|
||||||
self.disable = 0
|
self.disable = 0
|
||||||
if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then
|
if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then
|
||||||
self.disable = 1
|
self.disable = 1
|
||||||
end
|
end
|
||||||
Flag.render(self, section, scope)
|
Flag.render(self, section, scope)
|
||||||
end
|
end
|
||||||
|
o.write = function(self, section, value)
|
||||||
network_name = network_table:option(DummyValue, "_name", translate("Network Name"))
|
|
||||||
network_driver = network_table:option(DummyValue, "_driver", translate("Driver"))
|
|
||||||
network_interface = network_table:option(DummyValue, "_interface", translate("Parent Interface"))
|
|
||||||
network_subnet = network_table:option(DummyValue, "_subnet", translate("Subnet"))
|
|
||||||
network_gateway = network_table:option(DummyValue, "_gateway", translate("Gateway"))
|
|
||||||
|
|
||||||
network_selecter.write = function(self, section, value)
|
|
||||||
network_list[section]._selected = value
|
network_list[section]._selected = value
|
||||||
end
|
end
|
||||||
|
|
||||||
docker_status = m:section(SimpleSection)
|
o = s:option(DummyValue, "_id", translate("ID"))
|
||||||
docker_status.template = "dockerman/apply_widget"
|
|
||||||
docker_status.err=docker:read_status()
|
|
||||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
|
||||||
if docker_status.err then docker:clear_status() end
|
|
||||||
|
|
||||||
action = m:section(Table,{{}})
|
o = s:option(DummyValue, "_name", translate("Network Name"))
|
||||||
action.notitle=true
|
|
||||||
action.rowcolors=false
|
o = s:option(DummyValue, "_driver", translate("Driver"))
|
||||||
action.template="cbi/nullsection"
|
|
||||||
btnnew=action:option(Button, "_new")
|
o = s:option(DummyValue, "_interface", translate("Parent Interface"))
|
||||||
btnnew.inputtitle= translate("New")
|
|
||||||
btnnew.template = "dockerman/cbi/inlinebutton"
|
o = s:option(DummyValue, "_subnet", translate("Subnet"))
|
||||||
btnnew.notitle=true
|
|
||||||
btnnew.inputstyle = "add"
|
o = s:option(DummyValue, "_gateway", translate("Gateway"))
|
||||||
btnnew.forcewrite = true
|
|
||||||
btnnew.write = function(self, section)
|
s = m:section(SimpleSection)
|
||||||
|
s.template = "dockerman/apply_widget"
|
||||||
|
s.err = docker:read_status()
|
||||||
|
s.err = s.err and s.err:gsub("\n","<br>"):gsub(" "," ")
|
||||||
|
if s.err then
|
||||||
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
|
||||||
|
s = m:section(Table,{{}})
|
||||||
|
s.notitle=true
|
||||||
|
s.rowcolors=false
|
||||||
|
s.template="cbi/nullsection"
|
||||||
|
|
||||||
|
o = s:option(Button, "_new")
|
||||||
|
o.inputtitle= translate("New")
|
||||||
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
|
o.notitle=true
|
||||||
|
o.inputstyle = "add"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section)
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
|
||||||
end
|
end
|
||||||
btnremove = action:option(Button, "_remove")
|
|
||||||
btnremove.inputtitle= translate("Remove")
|
o = s:option(Button, "_remove")
|
||||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
o.inputtitle= translate("Remove")
|
||||||
btnremove.inputstyle = "remove"
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnremove.forcewrite = true
|
o.inputstyle = "remove"
|
||||||
btnremove.write = function(self, section)
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section)
|
||||||
local network_selected = {}
|
local network_selected = {}
|
||||||
local network_name_selected = {}
|
local network_name_selected = {}
|
||||||
local network_driver_selected = {}
|
local network_driver_selected = {}
|
||||||
-- 遍历table中sectionid
|
|
||||||
local network_table_sids = network_table:cfgsections()
|
for k in pairs(network_list) do
|
||||||
for _, network_table_sid in ipairs(network_table_sids) do
|
if network_list[k]._selected == 1 then
|
||||||
-- 得到选中项的名字
|
network_selected[#network_selected + 1] = network_list[k]._id
|
||||||
if network_list[network_table_sid]._selected == 1 then
|
network_name_selected[#network_name_selected + 1] = network_list[k]._name
|
||||||
network_selected[#network_selected+1] = network_list[network_table_sid]._id --network_name:cfgvalue(network_table_sid)
|
network_driver_selected[#network_driver_selected + 1] = network_list[k]._driver
|
||||||
network_name_selected[#network_name_selected+1] = network_list[network_table_sid]._name
|
|
||||||
network_driver_selected[#network_driver_selected+1] = network_list[network_table_sid]._driver
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if next(network_selected) ~= nil then
|
if next(network_selected) ~= nil then
|
||||||
local success = true
|
local success = true
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
|
|
||||||
for ii, net in ipairs(network_selected) do
|
for ii, net in ipairs(network_selected) do
|
||||||
docker:append_status("Networks: " .. "remove" .. " " .. net .. "...")
|
docker:append_status("Networks: " .. "remove" .. " " .. net .. "...")
|
||||||
local res = dk.networks["remove"](dk, {id = net})
|
local res = dk.networks["remove"](dk, {id = net})
|
||||||
|
|
||||||
if res and res.code >= 300 then
|
if res and res.code >= 300 then
|
||||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
|
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
|
||||||
success = false
|
success = false
|
||||||
|
@ -120,6 +141,7 @@ btnremove.write = function(self, section)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,39 +3,237 @@ LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
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 docker = require "luci.model.docker"
|
||||||
|
|
||||||
|
local m, s, o
|
||||||
|
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
|
|
||||||
local cmd_line = table.concat(arg, '/')
|
local cmd_line = table.concat(arg, '/')
|
||||||
local create_body = {}
|
local create_body = {}
|
||||||
|
|
||||||
local images = dk.images:list().body
|
local images = dk.images:list().body
|
||||||
local networks = dk.networks: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 is_quot_complete = function(str)
|
||||||
|
local num = 0, w
|
||||||
require "math"
|
require "math"
|
||||||
if not str then return true end
|
|
||||||
|
if not str then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
local num = 0, w
|
local num = 0, w
|
||||||
for w in str:gmatch("\"") do
|
for w in str:gmatch("\"") do
|
||||||
num = num + 1
|
num = num + 1
|
||||||
end
|
end
|
||||||
if math.fmod(num, 2) ~= 0 then return false end
|
|
||||||
|
if math.fmod(num, 2) ~= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
num = 0
|
num = 0
|
||||||
for w in str:gmatch("\'") do
|
for w in str:gmatch("\'") do
|
||||||
num = num + 1
|
num = num + 1
|
||||||
end
|
end
|
||||||
if math.fmod(num, 2) ~= 0 then return false end
|
|
||||||
|
if math.fmod(num, 2) ~= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
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 resolve_cli = function(cmd_line)
|
||||||
local config = {advance = 1}
|
local config = {
|
||||||
local key_no_val = '|t|d|i|tty|rm|read_only|interactive|init|help|detach|privileged|P|publish_all|'
|
advance = 1
|
||||||
local key_with_val = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|blkio_weight|cgroup_parent|cidfile|cpu_period|cpu_quota|cpu_rt_period|cpu_rt_runtime|c|cpu_shares|cpus|cpuset_cpus|cpuset_mems|detach_keys|disable_content_trust|domainname|entrypoint|gpus|health_cmd|health_interval|health_retries|health_start_period|health_timeout|h|hostname|ip|ip6|ipc|isolation|kernel_memory|log_driver|mac_address|m|memory|memory_reservation|memory_swap|memory_swappiness|mount|name|network|no_healthcheck|oom_kill_disable|oom_score_adj|pid|pids_limit|restart|runtime|shm_size|sig_proxy|stop_signal|stop_timeout|ulimit|u|user|userns|uts|volume_driver|w|workdir|'
|
}
|
||||||
local key_abb = {net='network',a='attach',c='cpu-shares',d='detach',e='env',h='hostname',i='interactive',l='label',m='memory',p='publish',P='publish_all',t='tty',u='user',v='volume',w='workdir'}
|
|
||||||
local key_with_list = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|'
|
local key_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 _key = nil
|
local _key = nil
|
||||||
local val = nil
|
local val = nil
|
||||||
|
@ -72,7 +270,9 @@ local resolve_cli = function(cmd_line)
|
||||||
config[key_abb["P"]] = true
|
config[key_abb["P"]] = true
|
||||||
key:gsub("P", "")
|
key:gsub("P", "")
|
||||||
end
|
end
|
||||||
if key == "" then key = nil end
|
if key == "" then
|
||||||
|
key = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -80,11 +280,11 @@ local resolve_cli = function(cmd_line)
|
||||||
if key then
|
if key then
|
||||||
key = key:gsub("-","_")
|
key = key:gsub("-","_")
|
||||||
key = key_abb[key] or key
|
key = key_abb[key] or key
|
||||||
if key_no_val:match("|"..key.."|") then
|
if contains(key_no_val, key) then
|
||||||
config[key] = true
|
config[key] = true
|
||||||
val = nil
|
val = nil
|
||||||
key = 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
|
-- if key == "cap_add" then config.privileged = true end
|
||||||
else
|
else
|
||||||
key = nil
|
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 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 target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or ""
|
||||||
local ro = w:match(",readonly") and "ro" or nil
|
local ro = w:match(",readonly") and "ro" or nil
|
||||||
|
|
||||||
if source and target then
|
if source and target then
|
||||||
if _type ~= "tmpfs" then
|
if _type ~= "tmpfs" then
|
||||||
-- bind or volume
|
|
||||||
local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil
|
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 ""))
|
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
|
else
|
||||||
-- tmpfs
|
|
||||||
local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil
|
local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil
|
||||||
local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil
|
local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil
|
||||||
key = "tmpfs"
|
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 "")
|
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 )
|
table.insert( config[key], val )
|
||||||
key = nil
|
key = nil
|
||||||
val = nil
|
val = nil
|
||||||
|
@ -129,15 +330,16 @@ local resolve_cli = function(cmd_line)
|
||||||
end
|
end
|
||||||
if (key or _key) and val then
|
if (key or _key) and val then
|
||||||
key = _key or key
|
key = _key or key
|
||||||
if key_with_list:match("|"..key.."|") then
|
if contains(key_with_list, key) then
|
||||||
if not config[key] then config[key] = {} end
|
if not config[key] then
|
||||||
|
config[key] = {}
|
||||||
|
end
|
||||||
if _key then
|
if _key then
|
||||||
config[key][#config[key]] = config[key][#config[key]] .. " " .. w
|
config[key][#config[key]] = config[key][#config[key]] .. " " .. w
|
||||||
else
|
else
|
||||||
table.insert( config[key], val )
|
table.insert( config[key], val )
|
||||||
end
|
end
|
||||||
if is_quot_complete(config[key][#config[key]]) then
|
if is_quot_complete(config[key][#config[key]]) then
|
||||||
-- clear quotation marks
|
|
||||||
config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "")
|
config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "")
|
||||||
_key = nil
|
_key = nil
|
||||||
else
|
else
|
||||||
|
@ -146,7 +348,6 @@ local resolve_cli = function(cmd_line)
|
||||||
else
|
else
|
||||||
config[key] = (config[key] and (config[key] .. " ") or "") .. val
|
config[key] = (config[key] and (config[key] .. " ") or "") .. val
|
||||||
if is_quot_complete(config[key]) then
|
if is_quot_complete(config[key]) then
|
||||||
-- clear quotation marks
|
|
||||||
config[key] = config[key]:gsub("[\"\']", "")
|
config[key] = config[key]:gsub("[\"\']", "")
|
||||||
_key = nil
|
_key = nil
|
||||||
else
|
else
|
||||||
|
@ -157,16 +358,22 @@ local resolve_cli = function(cmd_line)
|
||||||
val = nil
|
val = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return config
|
return config
|
||||||
end
|
end
|
||||||
-- reslvo default config
|
|
||||||
local default_config = {}
|
local default_config = {}
|
||||||
|
|
||||||
if cmd_line and cmd_line:match("^DOCKERCLI.+") then
|
if cmd_line and cmd_line:match("^DOCKERCLI.+") then
|
||||||
default_config = resolve_cli(cmd_line)
|
default_config = resolve_cli(cmd_line)
|
||||||
elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
|
elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
|
||||||
local container_id = cmd_line:match("^duplicate/(.+)")
|
local container_id = cmd_line:match("^duplicate/(.+)")
|
||||||
create_body = dk:containers_duplicate_config({id = container_id}) or {}
|
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
|
if next(create_body) ~= nil then
|
||||||
default_config.name = nil
|
default_config.name = nil
|
||||||
default_config.image = create_body.Image
|
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.interactive = create_body.OpenStdin and true or false
|
||||||
default_config.privileged = create_body.HostConfig.Privileged 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.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.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.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil
|
||||||
default_config.link = create_body.HostConfig.Links
|
default_config.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 "") )
|
table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then
|
if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then
|
||||||
default_config.tmpfs = {}
|
default_config.tmpfs = {}
|
||||||
for k, v in pairs(create_body.HostConfig.Tmpfs) do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
local m = SimpleForm("docker", translate("Docker"))
|
m = SimpleForm("docker", translate("Docker"))
|
||||||
m.redirect = luci.dispatcher.build_url("admin", "docker", "containers")
|
m.redirect = luci.dispatcher.build_url("admin", "docker", "containers")
|
||||||
-- m.reset = false
|
|
||||||
-- m.submit = false
|
|
||||||
-- new Container
|
|
||||||
|
|
||||||
docker_status = m:section(SimpleSection)
|
s = m:section(SimpleSection)
|
||||||
docker_status.template = "dockerman/apply_widget"
|
s.template = "dockerman/apply_widget"
|
||||||
docker_status.err=docker:read_status()
|
s.err=docker:read_status()
|
||||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ")
|
||||||
if docker_status.err then docker:clear_status() end
|
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.addremove = true
|
||||||
s.anonymous = true
|
s.anonymous = true
|
||||||
|
|
||||||
local d = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
|
o = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
|
||||||
d.rawhtml = true
|
o.rawhtml = true
|
||||||
d.template = "dockerman/newcontainer_resolve"
|
o.template = "dockerman/newcontainer_resolve"
|
||||||
|
|
||||||
d = s:option(Value, "name", translate("Container Name"))
|
o = s:option(Value, "name", translate("Container Name"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.default = default_config.name or nil
|
o.default = default_config.name or nil
|
||||||
|
|
||||||
d = s:option(Flag, "interactive", translate("Interactive (-i)"))
|
o = s:option(Flag, "interactive", translate("Interactive (-i)"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.disabled = 0
|
o.disabled = 0
|
||||||
d.enabled = 1
|
o.enabled = 1
|
||||||
d.default = default_config.interactive and 1 or 0
|
o.default = default_config.interactive and 1 or 0
|
||||||
|
|
||||||
d = s:option(Flag, "tty", translate("TTY (-t)"))
|
o = s:option(Flag, "tty", translate("TTY (-t)"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.disabled = 0
|
o.disabled = 0
|
||||||
d.enabled = 1
|
o.enabled = 1
|
||||||
d.default = default_config.tty and 1 or 0
|
o.default = default_config.tty and 1 or 0
|
||||||
|
|
||||||
d = s:option(Value, "image", translate("Docker Image"))
|
o = s:option(Value, "image", translate("Docker Image"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.default = default_config.image or nil
|
o.default = default_config.image or nil
|
||||||
for _, v in ipairs (images) do
|
for _, v in ipairs (images) do
|
||||||
if v.RepoTags then
|
if v.RepoTags then
|
||||||
d:value(v.RepoTags[1], v.RepoTags[1])
|
o:value(v.RepoTags[1], v.RepoTags[1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
d = s:option(Flag, "_force_pull", translate("Always pull image first"))
|
o = s:option(Flag, "_force_pull", translate("Always pull image first"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.disabled = 0
|
o.disabled = 0
|
||||||
d.enabled = 1
|
o.enabled = 1
|
||||||
d.default = 0
|
o.default = 0
|
||||||
|
|
||||||
d = s:option(Flag, "privileged", translate("Privileged"))
|
o = s:option(Flag, "privileged", translate("Privileged"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.disabled = 0
|
o.disabled = 0
|
||||||
d.enabled = 1
|
o.enabled = 1
|
||||||
d.default = default_config.privileged and 1 or 0
|
o.default = default_config.privileged and 1 or 0
|
||||||
|
|
||||||
d = s:option(ListValue, "restart", translate("Restart Policy"))
|
o = s:option(ListValue, "restart", translate("Restart Policy"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
|
o:value("no", "No")
|
||||||
d:value("no", "No")
|
o:value("unless-stopped", "Unless stopped")
|
||||||
d:value("unless-stopped", "Unless stopped")
|
o:value("always", "Always")
|
||||||
d:value("always", "Always")
|
o:value("on-failure", "On failure")
|
||||||
d:value("on-failure", "On failure")
|
o.default = default_config.restart or "unless-stopped"
|
||||||
d.default = default_config.restart or "unless-stopped"
|
|
||||||
|
|
||||||
local d_network = s:option(ListValue, "network", translate("Networks"))
|
local d_network = s:option(ListValue, "network", translate("Networks"))
|
||||||
d_network.rmempty = true
|
d_network.rmempty = true
|
||||||
|
@ -305,116 +509,146 @@ d_ip.datatype="ip4addr"
|
||||||
d_ip:depends("network", "nil")
|
d_ip:depends("network", "nil")
|
||||||
d_ip.default = default_config.ip or nil
|
d_ip.default = default_config.ip or nil
|
||||||
|
|
||||||
d = s:option(DynamicList, "link", translate("Links with other containers"))
|
o = s:option(DynamicList, "link", translate("Links with other containers"))
|
||||||
d.placeholder = "container_name:alias"
|
o.placeholder = "container_name:alias"
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d:depends("network", "bridge")
|
o:depends("network", "bridge")
|
||||||
d.default = default_config.link or nil
|
o.default = default_config.link or nil
|
||||||
|
|
||||||
d = s:option(DynamicList, "dns", translate("Set custom DNS servers"))
|
o = s:option(DynamicList, "dns", translate("Set custom DNS servers"))
|
||||||
d.placeholder = "8.8.8.8"
|
o.placeholder = "8.8.8.8"
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.default = default_config.dns or nil
|
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])"))
|
o = s:option(Value, "user",
|
||||||
d.placeholder = "1000:1000"
|
translate("User(-u)"),
|
||||||
d.rmempty = true
|
translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])"))
|
||||||
d.default = default_config.user or nil
|
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"))
|
o = s:option(DynamicList, "env",
|
||||||
d.placeholder = "TZ=Asia/Shanghai"
|
translate("Environmental Variable(-e)"),
|
||||||
d.rmempty = true
|
translate("Set environment variables to inside the container"))
|
||||||
d.default = default_config.env or nil
|
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"))
|
o = s:option(DynamicList, "volume",
|
||||||
d.placeholder = "/media:/media:slave"
|
translate("Bind Mount(-v)"),
|
||||||
d.rmempty = true
|
translate("Bind mount a volume"))
|
||||||
d.default = default_config.volume or nil
|
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.placeholder = "2200:22/tcp"
|
||||||
d_publish.rmempty = true
|
d_publish.rmempty = true
|
||||||
d_publish.default = default_config.publish or nil
|
d_publish.default = default_config.publish or nil
|
||||||
|
|
||||||
d = s:option(Value, "command", translate("Run command"))
|
o = s:option(Value, "command", translate("Run command"))
|
||||||
d.placeholder = "/bin/sh init.sh"
|
o.placeholder = "/bin/sh init.sh"
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.default = default_config.command or nil
|
o.default = default_config.command or nil
|
||||||
|
|
||||||
d = s:option(Flag, "advance", translate("Advance"))
|
o = s:option(Flag, "advance", translate("Advance"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.disabled = 0
|
o.disabled = 0
|
||||||
d.enabled = 1
|
o.enabled = 1
|
||||||
d.default = default_config.advance or 0
|
o.default = default_config.advance or 0
|
||||||
|
|
||||||
d = s:option(Value, "hostname", translate("Host Name"), translate("The hostname to use for the container"))
|
o = s:option(Value, "hostname",
|
||||||
d.rmempty = true
|
translate("Host Name"),
|
||||||
d.default = default_config.hostname or nil
|
translate("The hostname to use for the container"))
|
||||||
d:depends("advance", 1)
|
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"))
|
o = s:option(Flag, "publish_all",
|
||||||
d.rmempty = true
|
translate("Exposed All Ports(-P)"),
|
||||||
d.disabled = 0
|
translate("Allocates an ephemeral host port for all of a container's exposed ports"))
|
||||||
d.enabled = 1
|
o.rmempty = true
|
||||||
d.default = default_config.publish_all and 1 or 0
|
o.disabled = 0
|
||||||
d:depends("advance", 1)
|
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"))
|
o = s:option(DynamicList, "device",
|
||||||
d.placeholder = "/dev/sda:/dev/xvdc:rwm"
|
translate("Device(--device)"),
|
||||||
d.rmempty = true
|
translate("Add host device to the container"))
|
||||||
d:depends("advance", 1)
|
o.placeholder = "/dev/sda:/dev/xvdc:rwm"
|
||||||
d.default = default_config.device or nil
|
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"))
|
o = s:option(DynamicList, "tmpfs",
|
||||||
d.placeholder = "/run:rw,noexec,nosuid,size=65536k"
|
translate("Tmpfs(--tmpfs)"),
|
||||||
d.rmempty = true
|
translate("Mount tmpfs directory"))
|
||||||
d:depends("advance", 1)
|
o.placeholder = "/run:rw,noexec,nosuid,size=65536k"
|
||||||
d.default = default_config.tmpfs or nil
|
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"))
|
o = s:option(DynamicList, "sysctl",
|
||||||
d.placeholder = "net.ipv4.ip_forward=1"
|
translate("Sysctl(--sysctl)"),
|
||||||
d.rmempty = true
|
translate("Sysctls (kernel parameters) options"))
|
||||||
d:depends("advance", 1)
|
o.placeholder = "net.ipv4.ip_forward=1"
|
||||||
d.default = default_config.sysctl or nil
|
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"))
|
o = s:option(DynamicList, "cap_add",
|
||||||
d.placeholder = "NET_ADMIN"
|
translate("CAP-ADD(--cap-add)"),
|
||||||
d.rmempty = true
|
translate("A list of kernel capabilities to add to the container"))
|
||||||
d:depends("advance", 1)
|
o.placeholder = "NET_ADMIN"
|
||||||
d.default = default_config.cap_add or nil
|
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"))
|
o = s:option(Value, "cpus",
|
||||||
d.placeholder = "1.5"
|
translate("CPUs"),
|
||||||
d.rmempty = true
|
translate("Number of CPUs. Number is a fractional number. 0.000 means no limit"))
|
||||||
d:depends("advance", 1)
|
o.placeholder = "1.5"
|
||||||
d.datatype="ufloat"
|
o.rmempty = true
|
||||||
d.default = default_config.cpus or nil
|
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"))
|
o = s:option(Value, "cpu_shares",
|
||||||
d.placeholder = "1024"
|
translate("CPU Shares Weight"),
|
||||||
d.rmempty = true
|
translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024"))
|
||||||
d:depends("advance", 1)
|
o.placeholder = "1024"
|
||||||
d.datatype="uinteger"
|
o.rmempty = true
|
||||||
d.default = default_config.cpu_shares or nil
|
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"))
|
o = s:option(Value, "memory",
|
||||||
d.placeholder = "128m"
|
translate("Memory"),
|
||||||
d.rmempty = true
|
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:depends("advance", 1)
|
o.placeholder = "128m"
|
||||||
d.default = default_config.memory or nil
|
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"))
|
o = s:option(Value, "blkio_weight",
|
||||||
d.placeholder = "500"
|
translate("Block IO Weight"),
|
||||||
d.rmempty = true
|
translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000"))
|
||||||
d:depends("advance", 1)
|
o.placeholder = "500"
|
||||||
d.datatype="uinteger"
|
o.rmempty = true
|
||||||
d.default = default_config.blkio_weight or nil
|
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"))
|
o = s:option(DynamicList, "log_opt",
|
||||||
d.placeholder = "max-size=1m"
|
translate("Log driver options"),
|
||||||
d.rmempty = true
|
translate("The logging configuration for this container"))
|
||||||
d:depends("advance", 1)
|
o.placeholder = "max-size=1m"
|
||||||
d.default = default_config.log_opt or nil
|
o.rmempty = true
|
||||||
|
o:depends("advance", 1)
|
||||||
|
o.default = default_config.log_opt or nil
|
||||||
|
|
||||||
for _, v in ipairs (networks) do
|
for _, v in ipairs (networks) do
|
||||||
if v.Name then
|
if v.Name then
|
||||||
|
@ -435,7 +669,10 @@ for _, v in ipairs (networks) do
|
||||||
end
|
end
|
||||||
|
|
||||||
m.handle = function(self, state, data)
|
m.handle = function(self, state, data)
|
||||||
if state ~= FORM_VALID then return end
|
if state ~= FORM_VALID then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local tmp
|
local tmp
|
||||||
local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S"))
|
local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S"))
|
||||||
local hostname = data.hostname
|
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 interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false
|
||||||
local image = data.image
|
local image = data.image
|
||||||
local user = data.user
|
local user = data.user
|
||||||
|
|
||||||
if image and not image:match(".-:.+") then
|
if image and not image:match(".-:.+") then
|
||||||
image = image .. ":latest"
|
image = image .. ":latest"
|
||||||
end
|
end
|
||||||
|
|
||||||
local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false
|
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 restart = data.restart
|
||||||
local env = data.env
|
local env = data.env
|
||||||
local dns = data.dns
|
local dns = data.dns
|
||||||
local cap_add = data.cap_add
|
local cap_add = data.cap_add
|
||||||
local sysctl = {}
|
local sysctl = {}
|
||||||
|
|
||||||
tmp = data.sysctl
|
tmp = data.sysctl
|
||||||
if type(tmp) == "table" then
|
if type(tmp) == "table" then
|
||||||
for i, v in ipairs(tmp) do
|
for i, v in ipairs(tmp) do
|
||||||
|
@ -462,6 +702,7 @@ m.handle = function(self, state, data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local log_opt = {}
|
local log_opt = {}
|
||||||
tmp = data.log_opt
|
tmp = data.log_opt
|
||||||
if type(tmp) == "table" then
|
if type(tmp) == "table" then
|
||||||
|
@ -472,6 +713,7 @@ m.handle = function(self, state, data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local network = data.network
|
local network = data.network
|
||||||
local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
|
local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
|
||||||
local volume = data.volume
|
local volume = data.volume
|
||||||
|
@ -482,6 +724,7 @@ m.handle = function(self, state, data)
|
||||||
|
|
||||||
local portbindings = {}
|
local portbindings = {}
|
||||||
local exposedports = {}
|
local exposedports = {}
|
||||||
|
|
||||||
local tmpfs = {}
|
local tmpfs = {}
|
||||||
tmp = data.tmpfs
|
tmp = data.tmpfs
|
||||||
if type(tmp) == "table" then
|
if type(tmp) == "table" then
|
||||||
|
@ -516,6 +759,7 @@ m.handle = function(self, state, data)
|
||||||
t['CgroupPermissions'] = "rwm"
|
t['CgroupPermissions'] = "rwm"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if next(t) ~= nil then
|
if next(t) ~= nil then
|
||||||
table.insert( device, t )
|
table.insert( device, t )
|
||||||
end
|
end
|
||||||
|
@ -542,6 +786,7 @@ m.handle = function(self, state, data)
|
||||||
command[#command+1] = v
|
command[#command+1] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if memory ~= 0 then
|
if memory ~= 0 then
|
||||||
_,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
|
_,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
|
||||||
if n then
|
if n then
|
||||||
|
@ -577,14 +822,15 @@ m.handle = function(self, state, data)
|
||||||
create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9
|
create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9
|
||||||
create_body.HostConfig.BlkioWeight = tonumber(blkio_weight)
|
create_body.HostConfig.BlkioWeight = tonumber(blkio_weight)
|
||||||
create_body.HostConfig.PublishAllPorts = publish_all
|
create_body.HostConfig.PublishAllPorts = publish_all
|
||||||
|
|
||||||
if create_body.HostConfig.NetworkMode ~= network then
|
if create_body.HostConfig.NetworkMode ~= network then
|
||||||
-- network mode changed, need to clear duplicate config
|
|
||||||
create_body.NetworkingConfig = nil
|
create_body.NetworkingConfig = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
create_body.HostConfig.NetworkMode = network
|
create_body.HostConfig.NetworkMode = network
|
||||||
|
|
||||||
if ip then
|
if ip then
|
||||||
if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" 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
|
for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do
|
||||||
if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then
|
if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then
|
||||||
v.IPAMConfig.IPv4Address = ip
|
v.IPAMConfig.IPv4Address = ip
|
||||||
|
@ -594,13 +840,12 @@ m.handle = function(self, state, data)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- ip + no duplicate config
|
|
||||||
create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } }
|
create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } }
|
||||||
end
|
end
|
||||||
elseif not create_body.NetworkingConfig then
|
elseif not create_body.NetworkingConfig then
|
||||||
-- no ip + no duplicate config
|
|
||||||
create_body.NetworkingConfig = nil
|
create_body.NetworkingConfig = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
create_body["HostConfig"]["Tmpfs"] = tmpfs
|
create_body["HostConfig"]["Tmpfs"] = tmpfs
|
||||||
create_body["HostConfig"]["Devices"] = device
|
create_body["HostConfig"]["Devices"] = device
|
||||||
create_body["HostConfig"]["Sysctls"] = sysctl
|
create_body["HostConfig"]["Sysctls"] = sysctl
|
||||||
|
@ -610,6 +855,7 @@ m.handle = function(self, state, data)
|
||||||
if network == "bridge" then
|
if network == "bridge" then
|
||||||
create_body["HostConfig"]["Links"] = link
|
create_body["HostConfig"]["Links"] = link
|
||||||
end
|
end
|
||||||
|
|
||||||
local pull_image = function(image)
|
local pull_image = function(image)
|
||||||
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
||||||
docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n")
|
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"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
local exist_image = false
|
local exist_image = false
|
||||||
|
|
||||||
if image then
|
if image then
|
||||||
for _, v in ipairs (images) do
|
for _, v in ipairs (images) do
|
||||||
if v.RepoTags and v.RepoTags[1] == image then
|
if v.RepoTags and v.RepoTags[1] == image then
|
||||||
|
@ -639,6 +887,7 @@ m.handle = function(self, state, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
create_body = docker.clear_empty_tables(create_body)
|
create_body = docker.clear_empty_tables(create_body)
|
||||||
|
|
||||||
docker:append_status("Container: " .. "create" .. " " .. name .. "...")
|
docker:append_status("Container: " .. "create" .. " " .. name .. "...")
|
||||||
local res = dk.containers:create({name = name, body = create_body})
|
local res = dk.containers:create({name = name, body = create_body})
|
||||||
if res and res.code == 201 then
|
if res and res.code == 201 then
|
||||||
|
|
|
@ -3,121 +3,127 @@ LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
require "luci.util"
|
|
||||||
local docker = require "luci.model.docker"
|
local docker = require "luci.model.docker"
|
||||||
|
|
||||||
|
local m, s, o
|
||||||
|
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
|
|
||||||
m = SimpleForm("docker", translate("Docker"))
|
m = SimpleForm("docker", translate("Docker"))
|
||||||
m.redirect = luci.dispatcher.build_url("admin", "docker", "networks")
|
m.redirect = luci.dispatcher.build_url("admin", "docker", "networks")
|
||||||
|
|
||||||
docker_status = m:section(SimpleSection)
|
s = m:section(SimpleSection)
|
||||||
docker_status.template = "dockerman/apply_widget"
|
s.template = "dockerman/apply_widget"
|
||||||
docker_status.err=docker:read_status()
|
s.err=docker:read_status()
|
||||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ")
|
||||||
if docker_status.err then docker:clear_status() end
|
if s.err then
|
||||||
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
|
||||||
s = m:section(SimpleSection, translate("New Network"))
|
s = m:section(SimpleSection, translate("New Network"))
|
||||||
s.addremove = true
|
s.addremove = true
|
||||||
s.anonymous = true
|
s.anonymous = true
|
||||||
|
|
||||||
d = s:option(Value, "name", translate("Network Name"))
|
o = s:option(Value, "name", translate("Network Name"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
|
|
||||||
d = s:option(ListValue, "dirver", translate("Driver"))
|
o = s:option(ListValue, "dirver", translate("Driver"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d:value("bridge", "bridge")
|
o:value("bridge", "bridge")
|
||||||
d:value("macvlan", "macvlan")
|
o:value("macvlan", "macvlan")
|
||||||
d:value("ipvlan", "ipvlan")
|
o:value("ipvlan", "ipvlan")
|
||||||
d:value("overlay", "overlay")
|
o:value("overlay", "overlay")
|
||||||
|
|
||||||
d = s:option(Value, "parent", translate("Parent Interface"))
|
o = s:option(Value, "parent", translate("Parent Interface"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d:depends("dirver", "macvlan")
|
o:depends("dirver", "macvlan")
|
||||||
local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
|
local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
|
||||||
for _, v in ipairs(interfaces) do
|
for _, v in ipairs(interfaces) do
|
||||||
d:value(v, v)
|
o:value(v, v)
|
||||||
end
|
end
|
||||||
d.default="br-lan"
|
o.default="br-lan"
|
||||||
d.placeholder="br-lan"
|
o.placeholder="br-lan"
|
||||||
|
|
||||||
d = s:option(Value, "macvlan_mode", translate("Macvlan Mode"))
|
o = s:option(Value, "macvlan_mode", translate("Macvlan Mode"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d:depends("dirver", "macvlan")
|
o:depends("dirver", "macvlan")
|
||||||
d.default="bridge"
|
o.default="bridge"
|
||||||
d:value("bridge", "bridge")
|
o:value("bridge", "bridge")
|
||||||
d:value("private", "private")
|
o:value("private", "private")
|
||||||
d:value("vepa", "vepa")
|
o:value("vepa", "vepa")
|
||||||
d:value("passthru", "passthru")
|
o:value("passthru", "passthru")
|
||||||
|
|
||||||
d = s:option(Value, "ipvlan_mode", translate("Ipvlan Mode"))
|
o = s:option(Value, "ipvlan_mode", translate("Ipvlan Mode"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d:depends("dirver", "ipvlan")
|
o:depends("dirver", "ipvlan")
|
||||||
d.default="l3"
|
o.default="l3"
|
||||||
d:value("l2", "l2")
|
o:value("l2", "l2")
|
||||||
d:value("l3", "l3")
|
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"))
|
o = s:option(Flag, "ingress",
|
||||||
d.rmempty = true
|
translate("Ingress"),
|
||||||
d.disabled = 0
|
translate("Ingress network is the network which provides the routing-mesh in swarm mode"))
|
||||||
d.enabled = 1
|
o.rmempty = true
|
||||||
d.default = 0
|
o.disabled = 0
|
||||||
d:depends("dirver", "overlay")
|
o.enabled = 1
|
||||||
|
o.default = 0
|
||||||
|
o:depends("dirver", "overlay")
|
||||||
|
|
||||||
d = s:option(DynamicList, "options", translate("Options"))
|
o = s:option(DynamicList, "options", translate("Options"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.placeholder="com.docker.network.driver.mtu=1500"
|
o.placeholder="com.docker.network.driver.mtu=1500"
|
||||||
|
|
||||||
d = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network"))
|
o = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d:depends("dirver", "overlay")
|
o:depends("dirver", "overlay")
|
||||||
d.disabled = 0
|
o.disabled = 0
|
||||||
d.enabled = 1
|
o.enabled = 1
|
||||||
d.default = 0
|
o.default = 0
|
||||||
|
|
||||||
if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then
|
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"))
|
o = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt"))
|
||||||
d:depends("dirver", "macvlan")
|
o:depends("dirver", "macvlan")
|
||||||
d.disabled = 0
|
o.disabled = 0
|
||||||
d.enabled = 1
|
o.enabled = 1
|
||||||
d.default = 1
|
o.default = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
d = s:option(Value, "subnet", translate("Subnet"))
|
o = s:option(Value, "subnet", translate("Subnet"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.placeholder="10.1.0.0/16"
|
o.placeholder="10.1.0.0/16"
|
||||||
d.datatype="ip4addr"
|
o.datatype="ip4addr"
|
||||||
|
|
||||||
d = s:option(Value, "gateway", translate("Gateway"))
|
o = s:option(Value, "gateway", translate("Gateway"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.placeholder="10.1.1.1"
|
o.placeholder="10.1.1.1"
|
||||||
d.datatype="ip4addr"
|
o.datatype="ip4addr"
|
||||||
|
|
||||||
d = s:option(Value, "ip_range", translate("IP range"))
|
o = s:option(Value, "ip_range", translate("IP range"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.placeholder="10.1.1.0/24"
|
o.placeholder="10.1.1.0/24"
|
||||||
d.datatype="ip4addr"
|
o.datatype="ip4addr"
|
||||||
|
|
||||||
d = s:option(DynamicList, "aux_address", translate("Exclude IPs"))
|
o = s:option(DynamicList, "aux_address", translate("Exclude IPs"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.placeholder="my-route=10.1.1.1"
|
o.placeholder="my-route=10.1.1.1"
|
||||||
|
|
||||||
d = s:option(Flag, "ipv6", translate("Enable IPv6"))
|
o = s:option(Flag, "ipv6", translate("Enable IPv6"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.disabled = 0
|
o.disabled = 0
|
||||||
d.enabled = 1
|
o.enabled = 1
|
||||||
d.default = 0
|
o.default = 0
|
||||||
|
|
||||||
d = s:option(Value, "subnet6", translate("IPv6 Subnet"))
|
o = s:option(Value, "subnet6", translate("IPv6 Subnet"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.placeholder="fe80::/10"
|
o.placeholder="fe80::/10"
|
||||||
d.datatype="ip6addr"
|
o.datatype="ip6addr"
|
||||||
d:depends("ipv6", 1)
|
o:depends("ipv6", 1)
|
||||||
|
|
||||||
d = s:option(Value, "gateway6", translate("IPv6 Gateway"))
|
o = s:option(Value, "gateway6", translate("IPv6 Gateway"))
|
||||||
d.rmempty = true
|
o.rmempty = true
|
||||||
d.placeholder="fe80::1"
|
o.placeholder="fe80::1"
|
||||||
d.datatype="ip6addr"
|
o.datatype="ip6addr"
|
||||||
d:depends("ipv6", 1)
|
o:depends("ipv6", 1)
|
||||||
|
|
||||||
m.handle = function(self, state, data)
|
m.handle = function(self, state, data)
|
||||||
if state == FORM_VALID then
|
if state == FORM_VALID then
|
||||||
|
@ -167,6 +173,7 @@ m.handle = function(self, state, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
if driver == "macvlan" then
|
if driver == "macvlan" then
|
||||||
create_body["Options"] = {
|
create_body["Options"] = {
|
||||||
macvlan_mode = data.macvlan_mode,
|
macvlan_mode = data.macvlan_mode,
|
||||||
|
@ -200,15 +207,33 @@ m.handle = function(self, state, data)
|
||||||
|
|
||||||
create_body = docker.clear_empty_tables(create_body)
|
create_body = docker.clear_empty_tables(create_body)
|
||||||
docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
|
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
|
if res and res.code == 201 then
|
||||||
docker:write_status("Network: " .. "create macvlan interface...")
|
docker:write_status("Network: " .. "create macvlan interface...")
|
||||||
res = dk.networks:inspect({ name = create_body.Name })
|
res = dk.networks:inspect({
|
||||||
if driver == "macvlan" and data.op_macvlan ~= 0 and res.code == 200
|
name = create_body.Name
|
||||||
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)
|
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
|
end
|
||||||
|
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
|
||||||
else
|
else
|
||||||
|
|
|
@ -3,9 +3,9 @@ LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
require "luci.util"
|
|
||||||
local docker = require "luci.model.docker"
|
local docker = require "luci.model.docker"
|
||||||
local uci = require "luci.model.uci"
|
|
||||||
|
local m, s, o
|
||||||
|
|
||||||
function byte_format(byte)
|
function byte_format(byte)
|
||||||
local suff = {"B", "KB", "MB", "GB", "TB"}
|
local suff = {"B", "KB", "MB", "GB", "TB"}
|
||||||
|
@ -18,11 +18,14 @@ function byte_format(byte)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local map_dockerman = Map("dockerman", translate("Docker"), translate("DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:") .. " ".. [[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] ..translate("Github") .. [[</a>]])
|
m = Map("dockerd", translate("Docker"),
|
||||||
|
translate("DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:") ..
|
||||||
|
" " ..
|
||||||
|
[[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] ..
|
||||||
|
translate("Github") ..
|
||||||
|
[[</a>]])
|
||||||
|
|
||||||
local docker_info_table = {}
|
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['3ServerVersion'] = {_key=translate("Docker Version"),_value='-'}
|
||||||
docker_info_table['4ApiVersion'] = {_key=translate("Api Version"),_value='-'}
|
docker_info_table['4ApiVersion'] = {_key=translate("Api Version"),_value='-'}
|
||||||
docker_info_table['5NCPU'] = {_key=translate("CPUs"),_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['8IndexServerAddress'] = {_key=translate("Index Server Address"),_value='-'}
|
||||||
docker_info_table['9RegistryMirrors'] = {_key=translate("Registry Mirrors"),_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, "_key", translate("Info"))
|
||||||
s:option(DummyValue, "_value")
|
s:option(DummyValue, "_value")
|
||||||
s = map_dockerman:section(SimpleSection)
|
|
||||||
|
s = m:section(SimpleSection)
|
||||||
|
s.template = "dockerman/overview"
|
||||||
|
|
||||||
s.containers_running = '-'
|
s.containers_running = '-'
|
||||||
s.images_used = '-'
|
s.images_used = '-'
|
||||||
s.containers_total = '-'
|
s.containers_total = '-'
|
||||||
s.images_total = '-'
|
s.images_total = '-'
|
||||||
s.networks_total = '-'
|
s.networks_total = '-'
|
||||||
s.volumes_total = '-'
|
s.volumes_total = '-'
|
||||||
local containers_list
|
|
||||||
-- local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
|
if docker.new():_ping().code == 200 then
|
||||||
if (require "luci.model.docker").new():_ping().code == 200 then
|
|
||||||
local dk = docker.new()
|
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 images_list = dk.images:list().body
|
||||||
local vol = dk.volumes:list()
|
local vol = dk.volumes:list()
|
||||||
local volumes_list = vol and vol.body and vol.body.Volumes or {}
|
local volumes_list = vol and vol.body and vol.body.Volumes or {}
|
||||||
local networks_list = dk.networks:list().body or {}
|
local networks_list = dk.networks:list().body or {}
|
||||||
local docker_info = dk:info()
|
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['3ServerVersion']._value = docker_info.body.ServerVersion
|
||||||
docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"]
|
docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"]
|
||||||
docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU)
|
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
|
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") .. ")"
|
docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(byte_format(size)) .. " " .. translate("Available") .. ")"
|
||||||
end
|
end
|
||||||
|
|
||||||
docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress
|
docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress
|
||||||
for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do
|
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)
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
s.containers_running = tostring(docker_info.body.ContainersRunning)
|
s.containers_running = tostring(docker_info.body.ContainersRunning)
|
||||||
s.images_used = tostring(s.images_used)
|
s.images_used = tostring(s.images_used)
|
||||||
s.containers_total = tostring(docker_info.body.Containers)
|
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.networks_total = tostring(#networks_list)
|
||||||
s.volumes_total = tostring(#volumes_list)
|
s.volumes_total = tostring(#volumes_list)
|
||||||
end
|
end
|
||||||
s.template = "dockerman/overview"
|
|
||||||
|
|
||||||
local section_dockerman = map_dockerman:section(NamedSection, "local", "section", translate("Setting"))
|
s = m:section(NamedSection, "globals", "section", translate("Setting"))
|
||||||
section_dockerman:tab("daemon", translate("Docker Daemon"))
|
|
||||||
section_dockerman:tab("ac", translate("Access Control"))
|
|
||||||
section_dockerman:tab("dockerman", translate("DockerMan"))
|
|
||||||
|
|
||||||
local socket_path = section_dockerman:taboption("dockerman", Value, "socket_path", translate("Docker Socket Path"))
|
o = s:option(Flag, "remote_endpoint",
|
||||||
socket_path.default = "/var/run/docker.sock"
|
translate("Remote Endpoint"),
|
||||||
socket_path.placeholder = "/var/run/docker.sock"
|
translate("Connect to remote endpoint"))
|
||||||
socket_path.rmempty = false
|
o.rmempty = false
|
||||||
|
|
||||||
local remote_endpoint = section_dockerman:taboption("dockerman", Flag, "remote_endpoint", translate("Remote Endpoint"), translate("Dockerman connect to remote endpoint"))
|
o = s:option(Value, "socket_path",
|
||||||
remote_endpoint.rmempty = false
|
translate("Docker Socket Path"))
|
||||||
remote_endpoint.enabled = "true"
|
o.default = "unix://var/run/docker.sock"
|
||||||
remote_endpoint.disabled = "false"
|
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"))
|
o = s:option(Value, "remote_host",
|
||||||
remote_host.placeholder = "10.1.1.2"
|
translate("Remote Host"))
|
||||||
-- remote_host:depends("remote_endpoint", "true")
|
o.placeholder = "10.1.1.2"
|
||||||
|
o:depends("remote_endpoint", 1)
|
||||||
|
|
||||||
local remote_port = section_dockerman:taboption("dockerman", Value, "remote_port", translate("Remote Port"))
|
o = s:option(Value, "remote_port",
|
||||||
remote_port.placeholder = "2375"
|
translate("Remote Port"))
|
||||||
remote_port.default = "2375"
|
o.placeholder = "2375"
|
||||||
-- remote_port:depends("remote_endpoint", "true")
|
o.default = "2375"
|
||||||
|
o:depends("remote_endpoint", 1)
|
||||||
-- 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"))
|
|
||||||
|
|
||||||
if nixio.fs.access("/usr/bin/dockerd") then
|
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"))
|
o = s:option(Value, "data_root",
|
||||||
local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
|
translate("Docker Root Dir"))
|
||||||
for i, v in ipairs(interfaces) do
|
o.placeholder = "/opt/docker/"
|
||||||
allowed_interface:value(v, v)
|
o:depends("remote_endpoint", 0)
|
||||||
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"))
|
o = s:option(Value, "bip",
|
||||||
-- allowed_container.placeholder = "container name_or_id"
|
translate("Default bridge"),
|
||||||
if containers_list then
|
translate("Configure the default bridge network"))
|
||||||
for i, v in ipairs(containers_list) do
|
o.placeholder = "172.17.0.1/16"
|
||||||
if v.State == "running" and v.NetworkSettings and v.NetworkSettings.Networks and v.NetworkSettings.Networks.bridge and v.NetworkSettings.Networks.bridge.IPAddress then
|
o.default = "172.17.0.1/16"
|
||||||
allowed_container:value(v.Id:sub(1,12), v.Names[1]:sub(2) .. " | " .. v.NetworkSettings.Networks.bridge.IPAddress)
|
o.datatype = "ipaddr"
|
||||||
end
|
o:depends("remote_endpoint", 0)
|
||||||
end
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
local dockerd_enable = section_dockerman:taboption("daemon", Flag, "daemon_ea", translate("Enable"))
|
return m
|
||||||
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
|
|
||||||
|
|
|
@ -3,28 +3,26 @@ LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
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 docker = require "luci.model.docker"
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
|
|
||||||
local containers, volumes
|
local m, s, o
|
||||||
local res = dk.volumes:list()
|
|
||||||
if res.code <300 then volumes = res.body.Volumes else return end
|
local res, containers, volumes
|
||||||
res = dk.containers:list({query = {all=true}})
|
|
||||||
if res.code <300 then containers = res.body else return end
|
|
||||||
|
|
||||||
function get_volumes()
|
function get_volumes()
|
||||||
local data = {}
|
local data = {}
|
||||||
for i, v in ipairs(volumes) do
|
for i, v in ipairs(volumes) do
|
||||||
-- local index = v.CreatedAt .. v.Name
|
|
||||||
local index = v.Name
|
local index = v.Name
|
||||||
data[index]={}
|
data[index]={}
|
||||||
data[index]["_selected"] = 0
|
data[index]["_selected"] = 0
|
||||||
data[index]["_nameraw"] = v.Name
|
data[index]["_nameraw"] = v.Name
|
||||||
data[index]["_name"] = v.Name:sub(1,12)
|
data[index]["_name"] = v.Name:sub(1,12)
|
||||||
|
|
||||||
for ci,cv in ipairs(containers) do
|
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
|
for vi, vv in ipairs(cv.Mounts) do
|
||||||
if v.Name == vv.Name then
|
if v.Name == vv.Name then
|
||||||
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
|
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
|
||||||
|
@ -34,6 +32,7 @@ function get_volumes()
|
||||||
end
|
end
|
||||||
data[index]["_driver"] = v.Driver
|
data[index]["_driver"] = v.Driver
|
||||||
data[index]["_mountpoint"] = nil
|
data[index]["_mountpoint"] = nil
|
||||||
|
|
||||||
for v1 in v.Mountpoint:gmatch('[^/]+') do
|
for v1 in v.Mountpoint:gmatch('[^/]+') do
|
||||||
if v1 == index then
|
if v1 == index then
|
||||||
data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..."
|
data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..."
|
||||||
|
@ -43,59 +42,82 @@ function get_volumes()
|
||||||
end
|
end
|
||||||
data[index]["_created"] = v.CreatedAt
|
data[index]["_created"] = v.CreatedAt
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
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()
|
local volume_list = get_volumes()
|
||||||
|
|
||||||
-- m = Map("docker", translate("Docker"))
|
|
||||||
m = SimpleForm("docker", translate("Docker"))
|
m = SimpleForm("docker", translate("Docker"))
|
||||||
m.submit=false
|
m.submit=false
|
||||||
m.reset=false
|
m.reset=false
|
||||||
|
|
||||||
|
s = m:section(Table, volume_list, translate("Volumes"))
|
||||||
|
|
||||||
volume_table = m:section(Table, volume_list, translate("Volumes"))
|
o = s:option(Flag, "_selected","")
|
||||||
|
o.disabled = 0
|
||||||
volume_selecter = volume_table:option(Flag, "_selected","")
|
o.enabled = 1
|
||||||
volume_selecter.disabled = 0
|
o.default = 0
|
||||||
volume_selecter.enabled = 1
|
o.write = function(self, section, value)
|
||||||
volume_selecter.default = 0
|
|
||||||
|
|
||||||
volume_id = volume_table:option(DummyValue, "_name", translate("Name"))
|
|
||||||
volume_table:option(DummyValue, "_driver", translate("Driver"))
|
|
||||||
volume_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
|
|
||||||
volume_table:option(DummyValue, "_mountpoint", translate("Mount Point"))
|
|
||||||
volume_table:option(DummyValue, "_created", translate("Created"))
|
|
||||||
volume_selecter.write = function(self, section, value)
|
|
||||||
volume_list[section]._selected = value
|
volume_list[section]._selected = value
|
||||||
end
|
end
|
||||||
|
|
||||||
docker_status = m:section(SimpleSection)
|
o = s:option(DummyValue, "_name", translate("Name"))
|
||||||
docker_status.template = "dockerman/apply_widget"
|
|
||||||
docker_status.err=docker:read_status()
|
|
||||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
|
||||||
if docker_status.err then docker:clear_status() end
|
|
||||||
|
|
||||||
action = m:section(Table,{{}})
|
o = s:option(DummyValue, "_driver", translate("Driver"))
|
||||||
action.notitle=true
|
|
||||||
action.rowcolors=false
|
o = s:option(DummyValue, "_containers", translate("Containers"))
|
||||||
action.template="cbi/nullsection"
|
o.rawhtml = true
|
||||||
btnremove = action:option(Button, "remove")
|
|
||||||
btnremove.inputtitle= translate("Remove")
|
o = s:option(DummyValue, "_mountpoint", translate("Mount Point"))
|
||||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnremove.inputstyle = "remove"
|
o = s:option(DummyValue, "_created", translate("Created"))
|
||||||
btnremove.forcewrite = true
|
|
||||||
btnremove.write = function(self, section)
|
s = m:section(SimpleSection)
|
||||||
|
s.template = "dockerman/apply_widget"
|
||||||
|
s.err=docker:read_status()
|
||||||
|
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ")
|
||||||
|
if s.err then
|
||||||
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
|
||||||
|
s = m:section(Table,{{}})
|
||||||
|
s.notitle=true
|
||||||
|
s.rowcolors=false
|
||||||
|
s.template="cbi/nullsection"
|
||||||
|
|
||||||
|
o = s:option(Button, "remove")
|
||||||
|
o.inputtitle= translate("Remove")
|
||||||
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
|
o.inputstyle = "remove"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section)
|
||||||
local volume_selected = {}
|
local volume_selected = {}
|
||||||
-- 遍历table中sectionid
|
|
||||||
local volume_table_sids = volume_table:cfgsections()
|
for k in pairs(volume_list) do
|
||||||
for _, volume_table_sid in ipairs(volume_table_sids) do
|
if volume_list[k]._selected == 1 then
|
||||||
-- 得到选中项的名字
|
volume_selected[#volume_selected+1] = k
|
||||||
if volume_list[volume_table_sid]._selected == 1 then
|
|
||||||
-- volume_selected[#volume_selected+1] = volume_id:cfgvalue(volume_table_sid)
|
|
||||||
volume_selected[#volume_selected+1] = volume_table_sid
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if next(volume_selected) ~= nil then
|
if next(volume_selected) ~= nil then
|
||||||
local success = true
|
local success = true
|
||||||
docker:clear_status()
|
docker:clear_status()
|
||||||
|
@ -109,8 +131,12 @@ btnremove.write = function(self, section)
|
||||||
docker:append_status("done\n")
|
docker:append_status("done\n")
|
||||||
end
|
end
|
||||||
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"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
|
@ -3,39 +3,60 @@ LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
require "luci.util"
|
|
||||||
local docker = require "luci.docker"
|
local docker = require "luci.docker"
|
||||||
|
local fs = require "nixio.fs"
|
||||||
local uci = (require "luci.model.uci").cursor()
|
local uci = (require "luci.model.uci").cursor()
|
||||||
|
|
||||||
local _docker = {}
|
local _docker = {}
|
||||||
|
_docker.options = {}
|
||||||
|
|
||||||
--pull image and return iamge id
|
--pull image and return iamge id
|
||||||
local update_image = function(self, image_name)
|
local update_image = function(self, image_name)
|
||||||
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
||||||
_docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n")
|
_docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n")
|
||||||
local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb)
|
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
|
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")
|
_docker:append_status("done\n")
|
||||||
else
|
else
|
||||||
res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)
|
res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
new_image_id = self.images:inspect({name = image_name}).body.Id
|
new_image_id = self.images:inspect({name = image_name}).body.Id
|
||||||
return new_image_id, res
|
return new_image_id, res
|
||||||
end
|
end
|
||||||
|
|
||||||
local table_equal = function(t1, t2)
|
local table_equal = function(t1, t2)
|
||||||
if not t1 then return true end
|
if not t1 then
|
||||||
if not t2 then return false end
|
return true
|
||||||
if #t1 ~= #t2 then return false end
|
|
||||||
for i, v in ipairs(t1) do
|
|
||||||
if t1[i] ~= t2[i] then return false end
|
|
||||||
end
|
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
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local table_subtract = function(t1, t2)
|
local table_subtract = function(t1, t2)
|
||||||
if not t1 or next(t1) == nil then return nil end
|
if not t1 or next(t1) == nil then
|
||||||
if not t2 or next(t2) == nil then return t1 end
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not t2 or next(t2) == nil then
|
||||||
|
return t1
|
||||||
|
end
|
||||||
|
|
||||||
local res = {}
|
local res = {}
|
||||||
for _, v1 in ipairs(t1) do
|
for _, v1 in ipairs(t1) do
|
||||||
local found = false
|
local found = false
|
||||||
|
@ -49,12 +70,19 @@ local table_subtract = function(t1, t2)
|
||||||
table.insert(res, v1)
|
table.insert(res, v1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return next(res) == nil and nil or res
|
return next(res) == nil and nil or res
|
||||||
end
|
end
|
||||||
|
|
||||||
local map_subtract = function(t1, t2)
|
local map_subtract = function(t1, t2)
|
||||||
if not t1 or next(t1) == nil then return nil end
|
if not t1 or next(t1) == nil then
|
||||||
if not t2 or next(t2) == nil then return t1 end
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not t2 or next(t2) == nil then
|
||||||
|
return t1
|
||||||
|
end
|
||||||
|
|
||||||
local res = {}
|
local res = {}
|
||||||
for k1, v1 in pairs(t1) do
|
for k1, v1 in pairs(t1) do
|
||||||
local found = false
|
local found = false
|
||||||
|
@ -64,15 +92,9 @@ local map_subtract = function(t1, t2)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not found then
|
if not found then
|
||||||
res[k1] = v1
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -81,6 +103,7 @@ end
|
||||||
|
|
||||||
_docker.clear_empty_tables = function ( t )
|
_docker.clear_empty_tables = function ( t )
|
||||||
local k, v
|
local k, v
|
||||||
|
|
||||||
if next(t) == nil then
|
if next(t) == nil then
|
||||||
t = nil
|
t = nil
|
||||||
else
|
else
|
||||||
|
@ -90,23 +113,39 @@ _docker.clear_empty_tables = function ( t )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
-- return create_body, extra_network
|
|
||||||
local get_config = function(container_config, image_config)
|
local get_config = function(container_config, image_config)
|
||||||
local config = container_config.Config
|
local config = container_config.Config
|
||||||
local old_host_config = container_config.HostConfig
|
local old_host_config = container_config.HostConfig
|
||||||
local old_network_setting = container_config.NetworkSettings.Networks or {}
|
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 config.WorkingDir == image_config.WorkingDir then
|
||||||
if table_equal(config.Cmd, image_config.Cmd) then config.Cmd = nil end
|
config.WorkingDir = ""
|
||||||
if table_equal(config.Entrypoint, image_config.Entrypoint) then config.Entrypoint = nil end
|
end
|
||||||
if table_equal(config.ExposedPorts, image_config.ExposedPorts) then config.ExposedPorts = nil 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.Env = table_subtract(config.Env, image_config.Env)
|
||||||
config.Labels = table_subtract(config.Labels, image_config.Labels)
|
config.Labels = table_subtract(config.Labels, image_config.Labels)
|
||||||
config.Volumes = map_subtract(config.Volumes, image_config.Volumes)
|
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
|
if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then
|
||||||
config.ExposedPorts = {}
|
config.ExposedPorts = {}
|
||||||
for p, v in pairs(old_host_config.PortBindings) do
|
for p, v in pairs(old_host_config.PortBindings) do
|
||||||
|
@ -114,10 +153,10 @@ local get_config = function(container_config, image_config)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle network config, we need only one network, extras need to network connect action
|
|
||||||
local network_setting = {}
|
local network_setting = {}
|
||||||
local multi_network = false
|
local multi_network = false
|
||||||
local extra_network = {}
|
local extra_network = {}
|
||||||
|
|
||||||
for k, v in pairs(old_network_setting) do
|
for k, v in pairs(old_network_setting) do
|
||||||
if multi_network then
|
if multi_network then
|
||||||
extra_network[k] = v
|
extra_network[k] = v
|
||||||
|
@ -127,12 +166,8 @@ local get_config = function(container_config, image_config)
|
||||||
multi_network = true
|
multi_network = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle hostconfig
|
|
||||||
local host_config = old_host_config
|
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 = {}
|
host_config.Mounts = {}
|
||||||
-- for volumes
|
|
||||||
for i, v in ipairs(container_config.Mounts) do
|
for i, v in ipairs(container_config.Mounts) do
|
||||||
if v.Type == "volume" then
|
if v.Type == "volume" then
|
||||||
table.insert(host_config.Mounts, {
|
table.insert(host_config.Mounts, {
|
||||||
|
@ -145,30 +180,37 @@ local get_config = function(container_config, image_config)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- merge configs
|
|
||||||
local create_body = config
|
local create_body = config
|
||||||
create_body["HostConfig"] = host_config
|
create_body["HostConfig"] = host_config
|
||||||
create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
|
create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
|
||||||
create_body = _docker.clear_empty_tables(create_body) or {}
|
create_body = _docker.clear_empty_tables(create_body) or {}
|
||||||
extra_network = _docker.clear_empty_tables(extra_network) or {}
|
extra_network = _docker.clear_empty_tables(extra_network) or {}
|
||||||
|
|
||||||
return create_body, extra_network
|
return create_body, extra_network
|
||||||
end
|
end
|
||||||
|
|
||||||
local upgrade = function(self, request)
|
local upgrade = function(self, request)
|
||||||
_docker:clear_status()
|
_docker:clear_status()
|
||||||
-- get image name, image id, container name, configuration information
|
|
||||||
local container_info = self.containers:inspect({id = request.id})
|
local container_info = self.containers:inspect({id = request.id})
|
||||||
|
|
||||||
if container_info.code > 300 and type(container_info.body) == "table" then
|
if container_info.code > 300 and type(container_info.body) == "table" then
|
||||||
return container_info
|
return container_info
|
||||||
end
|
end
|
||||||
|
|
||||||
local image_name = container_info.body.Config.Image
|
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 old_image_id = container_info.body.Image
|
||||||
local container_name = container_info.body.Name:sub(2)
|
local container_name = container_info.body.Name:sub(2)
|
||||||
|
|
||||||
local image_id, res = update_image(self, image_name)
|
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
|
if image_id == old_image_id then
|
||||||
return {code = 305, body = {message = "Already up to date"}}
|
return {code = 305, body = {message = "Already up to date"}}
|
||||||
end
|
end
|
||||||
|
@ -189,7 +231,6 @@ local upgrade = function(self, request)
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle config
|
|
||||||
local image_config = self.images:inspect({id = old_image_id}).body.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)
|
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 .. "...")
|
_docker:append_status("Container: Create" .. " " .. container_name .. "...")
|
||||||
create_body = _docker.clear_empty_tables(create_body)
|
create_body = _docker.clear_empty_tables(create_body)
|
||||||
res = self.containers:create({name = container_name, body = 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")
|
_docker:append_status("done\n")
|
||||||
|
|
||||||
-- extra networks need to network connect action
|
-- extra networks need to network connect action
|
||||||
for k, v in pairs(extra_network) do
|
for k, v in pairs(extra_network) do
|
||||||
_docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
|
_docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
|
||||||
res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}})
|
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")
|
_docker:append_status("done\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
_docker:clear_status()
|
_docker:clear_status()
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
local duplicate_config = function (self, request)
|
local duplicate_config = function (self, request)
|
||||||
local container_info = self.containers:inspect({id = request.id})
|
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 old_image_id = container_info.body.Image
|
||||||
local image_config = self.images:inspect({id = old_image_id}).body.Config
|
local image_config = self.images:inspect({id = old_image_id}).body.Config
|
||||||
|
|
||||||
return get_config(container_info.body, image_config)
|
return get_config(container_info.body, image_config)
|
||||||
end
|
end
|
||||||
|
|
||||||
_docker.new = function(option)
|
_docker.new = function()
|
||||||
local option = option or {}
|
local host = nil
|
||||||
local remote = uci:get("dockerman", "local", "remote_endpoint")
|
local port = nil
|
||||||
options = {
|
local socket_path = nil
|
||||||
host = (remote == "true") and (option.host or uci:get("dockerman", "local", "remote_host")) or nil,
|
local debug_path = 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,
|
local remote = uci:get_bool("dockerd", "globals", "remote_endpoint")
|
||||||
debug_path = option.debug_path or uci:get("dockerman", "local", "debug_path")
|
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)
|
local _new = docker.new(_docker.options)
|
||||||
_new.options.status_path = uci:get("dockerman", "local", "status_path")
|
|
||||||
_new.containers_upgrade = upgrade
|
_new.containers_upgrade = upgrade
|
||||||
_new.containers_duplicate_config = duplicate_config
|
_new.containers_duplicate_config = duplicate_config
|
||||||
|
|
||||||
return _new
|
return _new
|
||||||
end
|
end
|
||||||
_docker.options={}
|
|
||||||
_docker.options.status_path = uci:get("dockerman", "local", "status_path")
|
|
||||||
|
|
||||||
_docker.append_status=function(self,val)
|
_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+")
|
local file_docker_action_status=io.open(self.options.status_path, "a+")
|
||||||
file_docker_action_status:write(val)
|
file_docker_action_status:write(val)
|
||||||
file_docker_action_status:close()
|
file_docker_action_status:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
_docker.write_status=function(self,val)
|
_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+")
|
local file_docker_action_status=io.open(self.options.status_path, "w+")
|
||||||
file_docker_action_status:write(val)
|
file_docker_action_status:write(val)
|
||||||
file_docker_action_status:close()
|
file_docker_action_status:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
_docker.read_status=function(self)
|
_docker.read_status=function(self)
|
||||||
return nixio.fs.readfile(self.options.status_path)
|
return fs.readfile(self.options.status_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
_docker.clear_status=function(self)
|
_docker.clear_status=function(self)
|
||||||
nixio.fs.remove(self.options.status_path)
|
fs.remove(self.options.status_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
local status_cb = function(res, source, handler)
|
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 buf = _docker:read_status()
|
||||||
local num = 0
|
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"
|
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
|
if num == 0 then
|
||||||
buf = buf .. str
|
buf = buf .. str
|
||||||
end
|
end
|
||||||
|
@ -311,31 +384,32 @@ _docker.import_image_show_status_cb = function(res, source)
|
||||||
local buf = _docker:read_status()
|
local buf = _docker:read_status()
|
||||||
local num = 0
|
local num = 0
|
||||||
local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n"
|
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
|
if num == 0 then
|
||||||
buf = buf .. str
|
buf = buf .. str
|
||||||
end
|
end
|
||||||
_docker:write_status(buf)
|
_docker:write_status(buf)
|
||||||
end
|
end
|
||||||
|
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)
|
_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 not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then
|
||||||
if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if uci:get("dockerd", "globals", "remote_endpoint") == "true" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local ip = require "luci.ip"
|
local ip = require "luci.ip"
|
||||||
local if_name = "docker_"..name
|
local if_name = "docker_"..name
|
||||||
local dev_name = "macvlan_"..name
|
local dev_name = "macvlan_"..name
|
||||||
local net_mask = tostring(ip.new(subnet):mask())
|
local net_mask = tostring(ip.new(subnet):mask())
|
||||||
local lan_interfaces
|
local lan_interfaces
|
||||||
|
|
||||||
-- add macvlan device
|
-- add macvlan device
|
||||||
uci:delete("network", dev_name)
|
uci:delete("network", dev_name)
|
||||||
uci:set("network", dev_name, "device")
|
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, "ifname", device)
|
||||||
uci:set("network", dev_name, "type", "macvlan")
|
uci:set("network", dev_name, "type", "macvlan")
|
||||||
uci:set("network", dev_name, "mode", "bridge")
|
uci:set("network", dev_name, "mode", "bridge")
|
||||||
|
|
||||||
-- add macvlan interface
|
-- add macvlan interface
|
||||||
uci:delete("network", if_name)
|
uci:delete("network", if_name)
|
||||||
uci:set("network", if_name, "interface")
|
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)
|
uci:set("firewall", s[".name"], "network", interfaces)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
uci:commit("firewall")
|
uci:commit("firewall")
|
||||||
uci:commit("network")
|
uci:commit("network")
|
||||||
|
|
||||||
os.execute("ifup " .. if_name)
|
os.execute("ifup " .. if_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
_docker.remove_macvlan_interface = function(name)
|
_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 not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then
|
||||||
if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if uci:get("dockerd", "globals", "remote_endpoint") == "true" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local if_name = "docker_"..name
|
local if_name = "docker_"..name
|
||||||
local dev_name = "macvlan_"..name
|
local dev_name = "macvlan_"..name
|
||||||
uci:foreach("firewall", "zone", function(s)
|
uci:foreach("firewall", "zone", function(s)
|
||||||
|
@ -387,10 +470,12 @@ _docker.remove_macvlan_interface = function(name)
|
||||||
uci:set("firewall", s[".name"], "network", interfaces)
|
uci:set("firewall", s[".name"], "network", interfaces)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
uci:commit("firewall")
|
|
||||||
uci:delete("network", dev_name)
|
uci:delete("network", dev_name)
|
||||||
uci:delete("network", if_name)
|
uci:delete("network", if_name)
|
||||||
uci:commit("network")
|
uci:commit("network")
|
||||||
|
uci:commit("firewall")
|
||||||
|
|
||||||
os.execute("ip link del " .. if_name)
|
os.execute("ip link del " .. if_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">//<![CDATA[
|
<script type="text/javascript">//<![CDATA[
|
||||||
var xhr = new XHR(),
|
var xhr = new XHR(),
|
||||||
uci_apply_rollback = <%=math.max(luci.config and luci.config.apply and luci.config.apply.rollback or 30, 30)%>,
|
uci_apply_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 {
|
else {
|
||||||
document.body.classList.remove('apply-overlay-active');
|
document.body.classList.remove('apply-overlay-active');
|
||||||
|
|
||||||
if (was_xhr_poll_running)
|
if (was_xhr_poll_running)
|
||||||
XHR.run();
|
XHR.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var loading_msg="Loading.."
|
var loading_msg="Loading.."
|
||||||
function uci_confirm_docker() {
|
function uci_confirm_docker() {
|
||||||
var tt;
|
var tt;
|
||||||
|
@ -85,7 +86,8 @@ function uci_confirm_docker() {
|
||||||
var call = function(r, resjson, duration) {
|
var call = function(r, resjson, duration) {
|
||||||
if (r && r.status === 200 ) {
|
if (r && r.status === 200 ) {
|
||||||
var indicator = document.querySelector('.uci_change_indicator');
|
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.%>');
|
docker_status_message('notice', '<%:Docker actions done.%>');
|
||||||
document.body.classList.remove('apply-overlay-active');
|
document.body.classList.remove('apply-overlay-active');
|
||||||
window.clearTimeout(tt);
|
window.clearTimeout(tt);
|
||||||
|
@ -102,13 +104,15 @@ function uci_confirm_docker() {
|
||||||
var tick = function() {
|
var tick = function() {
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
|
|
||||||
docker_status_message('notice',
|
docker_status_message(
|
||||||
'<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;">' +
|
'notice',
|
||||||
loading_msg + '</span>');
|
'<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);
|
tt = window.setTimeout(tick, 200);
|
||||||
ts = now;
|
ts = now;
|
||||||
};
|
};
|
||||||
|
|
||||||
tick();
|
tick();
|
||||||
/* wait a few seconds for the settings to become effective */
|
/* wait a few seconds for the settings to become effective */
|
||||||
window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 , 1));
|
window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 , 1));
|
||||||
|
@ -125,9 +129,12 @@ function fnSubmitForm(el){
|
||||||
|
|
||||||
<% if self.err then -%>
|
<% 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>');
|
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()
|
docker_status_message()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
<%- end %>
|
<%- end %>
|
||||||
|
|
||||||
window.onload= function (){
|
window.onload= function (){
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/docker/container/"+container_id+'/'+item
|
document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/docker/container/"+container_id+'/'+item
|
||||||
if (action === item) {
|
if (action === item) {
|
||||||
document.getElementById("cbi-tab-container_" + item).className="cbi-tab"
|
document.getElementById("cbi-tab-container_" + item).className="cbi-tab"
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
document.getElementById("cbi-tab-container_" + item).className="cbi-tab-disabled"
|
document.getElementById("cbi-tab-container_" + item).className="cbi-tab-disabled"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
<div id="upload-container" class="cbi-value cbi-value-last">
|
<div id="upload-container" class="cbi-value cbi-value-last">
|
||||||
<label class="cbi-value-title" for="archive"><%:Upload%></label>
|
<label class="cbi-value-title" for="archive"><%:Upload%></label>
|
||||||
<div class="cbi-value-field">
|
<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%>" />
|
<input type="button"" class="btn cbi-button cbi-button-action important" id="download" name="download" value="<%:Download%>" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
let btnUpload = document.getElementById('upload')
|
let btnUpload = document.getElementById('upload')
|
||||||
btnUpload.onclick = function (e) {
|
btnUpload.onclick = function (e) {
|
||||||
|
@ -22,9 +22,12 @@
|
||||||
let uploadPath = document.getElementById('path').value
|
let uploadPath = document.getElementById('path').value
|
||||||
if (!uploadArchive.value || !uploadPath) {
|
if (!uploadArchive.value || !uploadPath) {
|
||||||
docker_status_message('warning', "<%:Please input the PATH and select the file !%>")
|
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()
|
docker_status_message()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let fileName = uploadArchive.files[0].name
|
let fileName = uploadArchive.files[0].name
|
||||||
|
@ -42,20 +45,27 @@
|
||||||
else {
|
else {
|
||||||
docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText)
|
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()
|
docker_status_message()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
xhr.send(formData)
|
xhr.send(formData)
|
||||||
}
|
}
|
||||||
|
|
||||||
let btnDownload = document.getElementById('download')
|
let btnDownload = document.getElementById('download')
|
||||||
btnDownload.onclick = function (e) {
|
btnDownload.onclick = function (e) {
|
||||||
let downloadPath = document.getElementById('path').value
|
let downloadPath = document.getElementById('path').value
|
||||||
if (!downloadPath) {
|
if (!downloadPath) {
|
||||||
docker_status_message('warning', "<%:Please input the PATH !%>")
|
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()
|
docker_status_message()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.open('<%=luci.dispatcher.build_url("admin/docker/container_get_archive")%>?id=<%=self.container%>&path=' + encodeURIComponent(downloadPath))
|
window.open('<%=luci.dispatcher.build_url("admin/docker/container_get_archive")%>?id=<%=self.container%>&path=' + encodeURIComponent(downloadPath))
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
let last_bw_tx
|
let last_bw_tx
|
||||||
let last_bw_rx
|
let last_bw_rx
|
||||||
let interval = 3
|
let interval = 3
|
||||||
|
|
||||||
function progressbar(v, m, pc, np, f) {
|
function progressbar(v, m, pc, np, f) {
|
||||||
m = m || 100
|
m = m || 100
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
if (valISrc.value == "") {
|
if (valISrc.value == "") {
|
||||||
document.getElementById("file_import").click()
|
document.getElementById("file_import").click()
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
let formData = new FormData()
|
let formData = new FormData()
|
||||||
formData.append('src', valISrc.value)
|
formData.append('src', valISrc.value)
|
||||||
formData.append('tag', valITag.value)
|
formData.append('tag', valITag.value)
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
xhr.send(formData)
|
xhr.send(formData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileimport = document.getElementById('file_import')
|
let fileimport = document.getElementById('file_import')
|
||||||
fileimport.onchange = function (e) {
|
fileimport.onchange = function (e) {
|
||||||
let fileimport = document.getElementById('file_import')
|
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%>:", "")
|
let new_tag = prompt("<%:New tag%>\n<%:Image%>" + "ID: " + image_id + "\n<%:Please input new tag%>:", "")
|
||||||
if (new_tag) {
|
if (new_tag) {
|
||||||
(new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_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) {
|
function (r) {
|
||||||
if (r.status == 201) {
|
if (r.status == 201) {
|
||||||
location.reload()
|
location.reload()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
|
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()
|
docker_status_message()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let un_tag = function (tag) {
|
let un_tag = function (tag) {
|
||||||
if (tag.match("<none>")) return
|
if (tag.match("<none>"))
|
||||||
|
return
|
||||||
if (confirm("<%:Remove tag%>: " + tag + " ?")) {
|
if (confirm("<%:Remove tag%>: " + tag + " ?")) {
|
||||||
(new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_untag')%>",
|
(new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_untag')%>",
|
||||||
{ tag: tag },
|
{
|
||||||
|
tag: tag
|
||||||
|
},
|
||||||
function (r) {
|
function (r) {
|
||||||
if (r.status == 200) {
|
if (r.status == 200) {
|
||||||
location.reload()
|
location.reload()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
|
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()
|
docker_status_message()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
document.getElementById("file_load").click()
|
document.getElementById("file_load").click()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileLoad = document.getElementById('file_load')
|
let fileLoad = document.getElementById('file_load')
|
||||||
fileLoad.onchange = function(e){
|
fileLoad.onchange = function(e){
|
||||||
let fileLoad = document.getElementById('file_load')
|
let fileLoad = document.getElementById('file_load')
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function close_reslov_dialog() {
|
function close_reslov_dialog() {
|
||||||
document.body.classList.remove('dialog-reslov-active')
|
document.body.classList.remove('dialog-reslov-active')
|
||||||
|
@ -57,16 +58,21 @@
|
||||||
|
|
||||||
function reslov_container() {
|
function reslov_container() {
|
||||||
let s = document.getElementById('cmd-line-status')
|
let s = document.getElementById('cmd-line-status')
|
||||||
if (!s) return
|
|
||||||
|
if (!s)
|
||||||
|
return
|
||||||
|
|
||||||
let cmd_line = document.getElementById("dialog_reslov_text").value;
|
let cmd_line = document.getElementById("dialog_reslov_text").value;
|
||||||
if (cmd_line == null || cmd_line == "") {
|
if (cmd_line == null || cmd_line == "") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_line = cmd_line.replace(/(^\s*)/g,"")
|
cmd_line = cmd_line.replace(/(^\s*)/g,"")
|
||||||
if (!cmd_line.match(/^docker\s+(run|create)/)) {
|
if (!cmd_line.match(/^docker\s+(run|create)/)) {
|
||||||
s.innerHTML = "<font color='red'><%:Command line Error%></font>"
|
s.innerHTML = "<font color='red'><%:Command line Error%></font>"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let reg_space = /\s+/g
|
let reg_space = /\s+/g
|
||||||
let reg_muti_line= /\\\s*\n/g
|
let reg_muti_line= /\\\s*\n/g
|
||||||
// reg_rem =/(?<!\\)`#.+(?<!\\)`/g // the command has `# `
|
// reg_rem =/(?<!\\)`#.+(?<!\\)`/g // the command has `# `
|
||||||
|
@ -90,6 +96,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<%+cbi/valueheader%>
|
<%+cbi/valueheader%>
|
||||||
|
|
||||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Command line%>" onclick="show_reslov_dialog()" />
|
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Command line%>" onclick="show_reslov_dialog()" />
|
||||||
|
|
||||||
<%+cbi/valuefooter%>
|
<%+cbi/valuefooter%>
|
||||||
|
|
|
@ -126,11 +126,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
|
||||||
<div class="block pure-g">
|
<div class="block pure-g">
|
||||||
<div class="pure-u-2-5">
|
<div class="pure-u-2-5">
|
||||||
<div class="img-con">
|
<div class="img-con">
|
||||||
<svg role="img" viewBox="0 0 24 24">
|
<img src="<%=resource%>/dockerman/containers.svg" />
|
||||||
<title>Docker icon</title>
|
|
||||||
<path
|
|
||||||
d="M4.82 17.275c-.684 0-1.304-.56-1.304-1.24s.56-1.243 1.305-1.243c.748 0 1.31.56 1.31 1.242s-.622 1.24-1.305 1.24zm16.012-6.763c-.135-.992-.75-1.8-1.56-2.42l-.315-.25-.254.31c-.494.56-.69 1.553-.63 2.295.06.562.24 1.12.554 1.554-.254.13-.568.25-.81.377-.57.187-1.124.25-1.68.25H.097l-.06.37c-.12 1.182.06 2.42.562 3.54l.244.435v.06c1.5 2.483 4.17 3.6 7.078 3.6 5.594 0 10.182-2.42 12.357-7.633 1.425.062 2.864-.31 3.54-1.676l.18-.31-.3-.187c-.81-.494-1.92-.56-2.85-.31l-.018.002zm-8.008-.992h-2.428v2.42h2.43V9.518l-.002.003zm0-3.043h-2.428v2.42h2.43V6.48l-.002-.003zm0-3.104h-2.428v2.42h2.43v-2.42h-.002zm2.97 6.147H13.38v2.42h2.42V9.518l-.007.003zm-8.998 0H4.383v2.42h2.422V9.518l-.01.003zm3.03 0h-2.4v2.42H9.84V9.518l-.015.003zm-6.03 0H1.4v2.42h2.428V9.518l-.03.003zm6.03-3.043h-2.4v2.42H9.84V6.48l-.015-.003zm-3.045 0H4.387v2.42H6.8V6.48l-.016-.003z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-3-5">
|
<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="block pure-g">
|
||||||
<div class="pure-u-2-5">
|
<div class="pure-u-2-5">
|
||||||
<div class="img-con">
|
<div class="img-con">
|
||||||
<svg id="icon-hub" viewBox="0 -4 42 50" stroke-width="2" fill-rule="nonzero" width="100%" height="100%">
|
<img src="<%=resource%>/dockerman/images.svg" />
|
||||||
<path
|
|
||||||
d="M37.176371,36.2324812 C37.1920117,36.8041095 36.7372743,37.270685 36.1684891,37.270685 L3.74335204,37.2703476 C3.17827583,37.2703476 2.72400056,36.8091818 2.72400056,36.2397767 L2.72400056,19.6131383 C1.4312007,18.4881431 0.662551336,16.8884326 0.662551336,15.1618249 L0.664207893,14.69503 C0.63774183,14.4532127 0.650524255,14.2942438 0.711604827,14.1238231 L5.10793246,1.20935468 C5.24853286,0.797020623 5.63848594,0.511627907 6.06681069,0.511627907 L34.0728364,0.511627907 C34.5091607,0.511627907 34.889927,0.793578201 35.0316653,1.20921034 L39.4428567,14.1234095 C39.4871296,14.273204 39.5020782,14.4249444 39.4884726,14.5493649 L39.4884726,15.1505835 C39.4884726,16.9959517 38.6190601,18.6883031 37.1764746,19.7563084 L37.176371,36.2324812 Z M35.1376208,35.209311 L35.1376208,20.7057152 C34.7023924,20.8097593 34.271333,20.8633641 33.8336069,20.8633641 C32.0046019,20.8633641 30.3013756,19.9547008 29.2437221,18.4771538 C28.1860473,19.954695 26.4828515,20.8633641 24.6538444,20.8633641 C22.824803,20.8633641 21.1216155,19.9547157 20.0639591,18.4771544 C19.0062842,19.9546953 17.3030887,20.8633641 15.4740818,20.8633641 C13.6450404,20.8633641 11.9418529,19.9547157 10.8841965,18.4771544 C9.82652161,19.9546953 8.12332608,20.8633641 6.29431919,20.8633641 C5.76735555,20.8633641 5.24095778,20.7883418 4.73973398,20.644674 L4.73973398,35.209311 L35.1376208,35.209311 Z M30.2720226,15.6557626 C30.5154632,17.4501192 32.0503909,18.8018554 33.845083,18.8018554 C35.7286794,18.8018554 37.285413,17.3395134 37.4474599,15.4751932 L30.2280765,15.4751932 C30.2470638,15.532987 30.2617919,15.5932958 30.2720226,15.6557626 Z M21.0484306,15.4751932 C21.0674179,15.532987 21.0821459,15.5932958 21.0923767,15.6557626 C21.3358173,17.4501192 22.8707449,18.8018554 24.665437,18.8018554 C26.4601001,18.8018554 27.9950169,17.4501481 28.2378191,15.6611556 C28.2451225,15.5981318 28.2590045,15.5358056 28.2787375,15.4751932 L21.0484306,15.4751932 Z M11.9238102,15.6557626 C12.1672508,17.4501192 13.7021785,18.8018554 15.4968705,18.8018554 C17.2915336,18.8018554 18.8264505,17.4501481 19.0692526,15.6611556 C19.0765561,15.5981318 19.0904381,15.5358056 19.110171,15.4751932 L11.8798641,15.4751932 C11.8988514,15.532987 11.9135795,15.5932958 11.9238102,15.6557626 Z M6.31682805,18.8018317 C8.11149114,18.8018317 9.64640798,17.4501244 9.88921012,15.6611319 C9.89651357,15.5981081 9.91039559,15.5357819 9.93012856,15.4751696 L2.70318796,15.4751696 C2.86612006,17.3346852 4.42809696,18.8018317 6.31682805,18.8018317 Z M3.09670082,13.4139924 L37.04257,13.4139924 L33.3489482,2.57204736 L6.80119239,2.57204736 L3.09670082,13.4139924 Z"
|
|
||||||
id="Fill-1"></path>
|
|
||||||
<rect id="Rectangle-3" x="14" y="26" width="6" height="10"></rect>
|
|
||||||
<path d="M20,26 L20,36 L26,36 L26,26 L20,26 Z" id="Rectangle-3"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-3-5">
|
<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="block pure-g">
|
||||||
<div class="pure-u-2-5">
|
<div class="pure-u-2-5">
|
||||||
<div class="img-con">
|
<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">
|
<img src="<%=resource%>/dockerman/networks.svg" />
|
||||||
<path d="M7.452,24.152h3.435v5.701h0.633c0.001,0,0.001,0,0.002,0h0.636v-5.701h3.51v-1.059h17.124v1.104h3.178v5.656h0.619
|
|
||||||
c0,0,0,0,0.002,0h0.619v-5.656h3.736v-0.856c0-0.012,0.006-0.021,0.006-0.032c0-0.072,0-0.143,0-0.215h5.721v-1.316h-5.721
|
|
||||||
c0-0.054,0-0.108,0-0.164c0-0.011-0.006-0.021-0.006-0.032v-0.832h-8.154v1.028h-7.911v-2.652h-0.689c-0.001,0-0.001,0-0.002,0
|
|
||||||
h-0.678v2.652h-7.846v-1.104H7.452v1.104H1.114v1.316h6.338V24.152z" />
|
|
||||||
<path
|
|
||||||
d="M21.484,16.849h5.204v-2.611h7.133V1.555H14.588v12.683h6.896V16.849z M16.537,12.288V3.505h15.335v8.783H16.537z" />
|
|
||||||
<rect x="18.682" y="16.898" width="10.809" height="0.537" />
|
|
||||||
<path
|
|
||||||
d="M0,43.971h6.896v2.611H12.1v-2.611h7.134V31.287H0V43.971z M1.95,33.236h15.334v8.785H1.95V33.236z" />
|
|
||||||
<rect x="4.095" y="46.631" width="10.808" height="0.537" />
|
|
||||||
<path
|
|
||||||
d="M29.491,30.994v12.684h6.895v2.611h5.205v-2.611h7.133V30.994H29.491z M46.774,41.729H31.44v-8.783h15.334V41.729z" />
|
|
||||||
<rect x="33.584" y="46.338" width="10.809" height="0.537" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-3-5">
|
<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="block pure-g">
|
||||||
<div class="pure-u-2-5">
|
<div class="pure-u-2-5">
|
||||||
<div class="img-con">
|
<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">
|
<img src="<%=resource%>/dockerman/volumes.svg" />
|
||||||
<path
|
|
||||||
d="M52.354,8.51C51.196,4.22,42.577,0,27.5,0C12.423,0,3.803,4.22,2.646,8.51C2.562,8.657,2.5,8.818,2.5,9v0.5V21v0.5V22v11
|
|
||||||
v0.5V34v12c0,0.162,0.043,0.315,0.117,0.451C3.798,51.346,14.364,55,27.5,55c13.106,0,23.655-3.639,24.875-8.516
|
|
||||||
C52.455,46.341,52.5,46.176,52.5,46V34v-0.5V33V22v-0.5V21V9.5V9C52.5,8.818,52.438,8.657,52.354,8.51z M50.421,33.985
|
|
||||||
c-0.028,0.121-0.067,0.241-0.116,0.363c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369
|
|
||||||
c-0.066,0.093-0.141,0.185-0.219,0.277c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236
|
|
||||||
c-0.164,0.143-0.335,0.285-0.526,0.426c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463
|
|
||||||
c-0.068,0.041-0.141,0.08-0.212,0.12c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061
|
|
||||||
c-0.375,0.177-0.767,0.351-1.186,0.519c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17
|
|
||||||
c-0.017,0.002-0.034,0.004-0.051,0.007c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032
|
|
||||||
c-0.605,0.063-1.217,0.121-1.847,0.167c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076
|
|
||||||
C29.137,40.984,28.327,41,27.5,41s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076
|
|
||||||
c-0.293-0.017-0.595-0.028-0.883-0.049c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032
|
|
||||||
c-0.693-0.076-1.368-0.163-2.026-0.259c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17
|
|
||||||
c-0.012-0.004-0.024-0.009-0.036-0.014c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061
|
|
||||||
c-0.336-0.162-0.647-0.328-0.945-0.497c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463
|
|
||||||
c-0.086-0.06-0.175-0.119-0.257-0.18c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236
|
|
||||||
c-0.134-0.13-0.252-0.26-0.363-0.392c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369
|
|
||||||
c-0.054-0.099-0.102-0.198-0.143-0.297c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,33.823,4.5,33.661,4.5,33.5
|
|
||||||
c0-0.113,0.013-0.226,0.031-0.338c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.028,0.026,0.063,0.051,0.092,0.077
|
|
||||||
c0.218,0.192,0.44,0.383,0.69,0.567C9.049,28.786,16.582,31,27.5,31c10.872,0,18.386-2.196,22.169-5.028
|
|
||||||
c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001v7.424c-0.042,0.143-0.056,0.294-0.031,0.445c0.019,0.112,0.031,0.225,0.031,0.338
|
|
||||||
C50.5,33.661,50.459,33.823,50.421,33.985z M50.5,13.293v7.424c-0.042,0.143-0.056,0.294-0.031,0.445
|
|
||||||
c0.019,0.112,0.031,0.225,0.031,0.338c0,0.161-0.041,0.323-0.079,0.485c-0.028,0.121-0.067,0.241-0.116,0.363
|
|
||||||
c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369c-0.066,0.093-0.141,0.185-0.219,0.277
|
|
||||||
c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236c-0.164,0.143-0.335,0.285-0.526,0.426
|
|
||||||
c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463c-0.068,0.041-0.141,0.08-0.212,0.12
|
|
||||||
c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061c-0.375,0.177-0.767,0.351-1.186,0.519
|
|
||||||
c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17c-0.017,0.002-0.034,0.004-0.051,0.007
|
|
||||||
c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032c-0.605,0.063-1.217,0.121-1.847,0.167
|
|
||||||
c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076C29.137,28.984,28.327,29,27.5,29
|
|
||||||
s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076c-0.293-0.017-0.595-0.028-0.883-0.049
|
|
||||||
c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032c-0.693-0.076-1.368-0.163-2.026-0.259
|
|
||||||
c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17c-0.012-0.004-0.024-0.009-0.036-0.014
|
|
||||||
c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061c-0.336-0.162-0.647-0.328-0.945-0.497
|
|
||||||
c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463c-0.086-0.06-0.175-0.119-0.257-0.18
|
|
||||||
c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236c-0.134-0.13-0.252-0.26-0.363-0.392
|
|
||||||
c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369c-0.054-0.099-0.102-0.198-0.143-0.297
|
|
||||||
c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,21.823,4.5,21.661,4.5,21.5c0-0.113,0.013-0.226,0.031-0.338
|
|
||||||
c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.12,0.109,0.257,0.216,0.387,0.324c0.072,0.06,0.139,0.12,0.215,0.18
|
|
||||||
c0.3,0.236,0.624,0.469,0.975,0.696c0.073,0.047,0.155,0.093,0.231,0.14c0.294,0.183,0.605,0.362,0.932,0.538
|
|
||||||
c0.121,0.065,0.242,0.129,0.367,0.193c0.365,0.186,0.748,0.367,1.151,0.542c0.066,0.029,0.126,0.059,0.193,0.087
|
|
||||||
c0.469,0.199,0.967,0.389,1.485,0.573c0.143,0.051,0.293,0.099,0.44,0.149c0.412,0.139,0.838,0.272,1.279,0.401
|
|
||||||
c0.159,0.046,0.315,0.094,0.478,0.138c0.585,0.162,1.189,0.316,1.823,0.458c0.087,0.02,0.181,0.036,0.269,0.055
|
|
||||||
c0.559,0.122,1.139,0.235,1.735,0.341c0.202,0.036,0.407,0.07,0.613,0.104c0.567,0.093,1.151,0.178,1.75,0.256
|
|
||||||
c0.154,0.02,0.301,0.043,0.457,0.062c0.744,0.09,1.514,0.167,2.305,0.233c0.195,0.016,0.398,0.028,0.596,0.042
|
|
||||||
c0.633,0.046,1.28,0.084,1.942,0.114c0.241,0.011,0.481,0.022,0.727,0.031C25.712,18.979,26.59,19,27.5,19s1.788-0.021,2.65-0.05
|
|
||||||
c0.245-0.009,0.485-0.02,0.727-0.031c0.662-0.03,1.309-0.068,1.942-0.114c0.198-0.015,0.4-0.026,0.596-0.042
|
|
||||||
c0.791-0.065,1.561-0.143,2.305-0.233c0.156-0.019,0.303-0.042,0.457-0.062c0.599-0.078,1.182-0.163,1.75-0.256
|
|
||||||
c0.206-0.034,0.411-0.068,0.613-0.104c0.596-0.106,1.176-0.219,1.735-0.341c0.088-0.019,0.182-0.036,0.269-0.055
|
|
||||||
c0.634-0.142,1.238-0.297,1.823-0.458c0.163-0.045,0.319-0.092,0.478-0.138c0.441-0.129,0.867-0.262,1.279-0.401
|
|
||||||
c0.147-0.05,0.297-0.098,0.44-0.149c0.518-0.184,1.017-0.374,1.485-0.573c0.067-0.028,0.127-0.058,0.193-0.087
|
|
||||||
c0.403-0.176,0.786-0.356,1.151-0.542c0.125-0.064,0.247-0.128,0.367-0.193c0.327-0.175,0.638-0.354,0.932-0.538
|
|
||||||
c0.076-0.047,0.158-0.093,0.231-0.14c0.351-0.227,0.675-0.459,0.975-0.696c0.075-0.06,0.142-0.12,0.215-0.18
|
|
||||||
C50.243,13.509,50.38,13.402,50.5,13.293z M27.5,2c13.555,0,23,3.952,23,7.5s-9.445,7.5-23,7.5s-23-3.952-23-7.5S13.945,2,27.5,2z
|
|
||||||
M50.5,45.703c-0.014,0.044-0.024,0.089-0.032,0.135C49.901,49.297,40.536,53,27.5,53S5.099,49.297,4.532,45.838
|
|
||||||
c-0.008-0.045-0.019-0.089-0.032-0.131v-8.414c0.028,0.026,0.063,0.051,0.092,0.077c0.218,0.192,0.44,0.383,0.69,0.567
|
|
||||||
C9.049,40.786,16.582,43,27.5,43c10.872,0,18.386-2.196,22.169-5.028c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001V45.703z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-3-5">
|
<div class="pure-u-3-5">
|
||||||
|
|
|
@ -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'
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -2,10 +2,10 @@
|
||||||
"luci-app-dockerman": {
|
"luci-app-dockerman": {
|
||||||
"description": "Grant UCI access for luci-app-dockerman",
|
"description": "Grant UCI access for luci-app-dockerman",
|
||||||
"read": {
|
"read": {
|
||||||
"uci": [ "dockerman" ]
|
"uci": [ "dockerd" ]
|
||||||
},
|
},
|
||||||
"write": {
|
"write": {
|
||||||
"uci": [ "dockerman" ]
|
"uci": [ "dockerd" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
LuCI - Lua Configuration Interface
|
LuCI - Lua Configuration Interface
|
||||||
Copyright 2019 lisaac <https://github.com/lisaac/luci-lib-docker>
|
Copyright 2019 lisaac <https://github.com/lisaac/luci-lib-docker>
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
require "nixio.util"
|
require "nixio.util"
|
||||||
require "luci.util"
|
require "luci.util"
|
||||||
local jsonc = require "luci.jsonc"
|
local jsonc = require "luci.jsonc"
|
||||||
|
@ -15,44 +16,55 @@ local json_parse = jsonc.parse
|
||||||
|
|
||||||
local chunksource = function(sock, buffer)
|
local chunksource = function(sock, buffer)
|
||||||
buffer = buffer or ""
|
buffer = buffer or ""
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
local output
|
local output
|
||||||
local _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
|
local _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
|
||||||
|
|
||||||
if not count then
|
if not count then
|
||||||
local newblock, code = sock:recv(1024)
|
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
|
buffer = buffer .. newblock
|
||||||
_, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
|
_, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
count = tonumber(count, 16)
|
count = tonumber(count, 16)
|
||||||
if not count then
|
if not count then
|
||||||
return nil, -1, "invalid encoding"
|
return nil, -1, "invalid encoding"
|
||||||
elseif count == 0 then -- finial
|
elseif count == 0 then -- finial
|
||||||
return nil
|
return nil
|
||||||
elseif count <= #buffer - endp then
|
elseif count <= #buffer - endp then -- data >= count
|
||||||
--data >= count
|
|
||||||
output = buffer:sub(endp + 1, endp + count)
|
output = buffer:sub(endp + 1, endp + count)
|
||||||
if count == #buffer - endp then -- [data]
|
if count == #buffer - endp then -- [data]
|
||||||
buffer = buffer:sub(endp + count + 1)
|
buffer = buffer:sub(endp + count + 1)
|
||||||
count, code = sock:recvall(2) --read \r\n
|
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
|
elseif count + 1 == #buffer - endp then -- [data]\r
|
||||||
buffer = buffer:sub(endp + count + 2)
|
buffer = buffer:sub(endp + count + 2)
|
||||||
count, code = sock:recvall(1) --read \n
|
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]...
|
else -- [data]\r\n[count]\r\n[data]...
|
||||||
buffer = buffer:sub(endp + count + 3) -- cut buffer
|
buffer = buffer:sub(endp + count + 3) -- cut buffer
|
||||||
end
|
end
|
||||||
return output
|
return output
|
||||||
else
|
else -- data < count
|
||||||
-- data < count
|
|
||||||
output = buffer:sub(endp + 1, endp + count)
|
output = buffer:sub(endp + 1, endp + count)
|
||||||
buffer = buffer:sub(endp + count + 1)
|
buffer = buffer:sub(endp + count + 1)
|
||||||
local remain, code = sock:recvall(count - #output) --need read remaining
|
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
|
output = output .. remain
|
||||||
count, code = sock:recvall(2) --read \r\n
|
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
|
return output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -74,21 +86,23 @@ local docker_stream_filter = function(buffer)
|
||||||
return ""
|
return ""
|
||||||
end
|
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 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 =
|
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))
|
||||||
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
|
if valid_length > #buffer + 8 then
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
return stream_type .. ": " .. string.sub(buffer, 9, valid_length + 8)
|
return stream_type .. ": " .. string.sub(buffer, 9, valid_length + 8)
|
||||||
-- return string.sub(buffer, 9, valid_length + 8)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local open_socket = function(req_options)
|
local open_socket = function(req_options)
|
||||||
local socket
|
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
|
if req_options.socket_path then
|
||||||
socket = nixio.socket("unix", "stream")
|
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
|
elseif req_options.host and req_options.port then
|
||||||
socket = nixio.connect(req_options.host, req_options.port)
|
socket = nixio.connect(req_options.host, req_options.port)
|
||||||
end
|
end
|
||||||
|
@ -99,11 +113,17 @@ local open_socket = function(req_options)
|
||||||
end
|
end
|
||||||
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
|
if docker_socket:send(req_header) == 0 then
|
||||||
return {
|
return {
|
||||||
headers={code=498,message="bad path", protocol="HTTP/1.1"},
|
headers={
|
||||||
body={message="can\'t send data to socket"}
|
code=498,
|
||||||
|
message="bad path",
|
||||||
|
protocol="HTTP/1.1"
|
||||||
|
},
|
||||||
|
body={
|
||||||
|
message="can\'t send data to socket"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
end
|
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
|
elseif req_body and type(req_body) == "table" then
|
||||||
-- json
|
-- json
|
||||||
docker_socket:send(json_stringify(req_body))
|
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
|
elseif req_body then
|
||||||
docker_socket:send(req_body)
|
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
|
end
|
||||||
|
|
||||||
local linesrc = docker_socket:linesource()
|
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
|
if not line then
|
||||||
docker_socket:close()
|
docker_socket:close()
|
||||||
return {
|
return {
|
||||||
headers = {code=499, message="bad socket path", protocol="HTTP/1.1"},
|
headers = {
|
||||||
body = {message="no data receive from socket"}
|
code=499,
|
||||||
|
message="bad socket path",
|
||||||
|
protocol="HTTP/1.1"
|
||||||
|
},
|
||||||
|
body = {
|
||||||
|
message="no data receive from socket"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
local response = {code = 0, headers = {}, body = {}}
|
|
||||||
|
local response = {
|
||||||
|
code = 0,
|
||||||
|
headers = {},
|
||||||
|
body = {}
|
||||||
|
}
|
||||||
|
|
||||||
local p, code, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
|
local p, code, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
|
||||||
response.protocol = p
|
response.protocol = p
|
||||||
response.code = tonumber(code)
|
response.code = tonumber(code)
|
||||||
response.message = msg
|
response.message = msg
|
||||||
line = linesrc()
|
line = linesrc()
|
||||||
|
|
||||||
while line and line ~= "" do
|
while line and line ~= "" do
|
||||||
local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
|
local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
|
||||||
if key and key ~= "Status" then
|
if key and key ~= "Status" then
|
||||||
if type(response.headers[key]) == "string" 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
|
elseif type(response.headers[key]) == "table" then
|
||||||
response.headers[key][#response.headers[key] + 1] = val
|
response.headers[key][#response.headers[key] + 1] = val
|
||||||
else
|
else
|
||||||
|
@ -154,9 +193,11 @@ local send_http_socket = function(docker_socket, req_header, req_body, callback)
|
||||||
end
|
end
|
||||||
line = linesrc()
|
line = linesrc()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle response body
|
-- handle response body
|
||||||
local body_buffer = linesrc(true)
|
local body_buffer = linesrc(true)
|
||||||
response.body = {}
|
response.body = {}
|
||||||
|
|
||||||
if type(callback) ~= "function" then
|
if type(callback) ~= "function" then
|
||||||
if response.headers["Transfer-Encoding"] == "chunked" then
|
if response.headers["Transfer-Encoding"] == "chunked" then
|
||||||
local source = chunksource(docker_socket, body_buffer)
|
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
|
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 "")
|
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 = (http_method or "GET") .. " " .. path .. " " .. options.protocol .. "\r\n"
|
||||||
header = header .. "Host: " .. options.host .. "\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
|
elseif request and request.body and type(request.body) == "string" then
|
||||||
header = header .. "Content-Length: " .. #request.body .. "\r\n"
|
header = header .. "Content-Length: " .. #request.body .. "\r\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
header = header .. "\r\n"
|
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
|
return header
|
||||||
end
|
end
|
||||||
|
|
||||||
local call_docker = function(options, http_method, api_group, api_action, name_or_id, request, callback)
|
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 req_body = request and request.body or nil
|
||||||
local docker_socket = open_socket(req_options)
|
local docker_socket = open_socket(req_options)
|
||||||
|
|
||||||
if docker_socket then
|
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
|
else
|
||||||
return {
|
return {
|
||||||
headers = {code=497, message="bad socket path or host", protocol="HTTP/1.1"},
|
headers = {
|
||||||
body = {message="can\'t connect to socket"}
|
code=497,
|
||||||
|
message="bad socket path or host",
|
||||||
|
protocol="HTTP/1.1"
|
||||||
|
},
|
||||||
|
body = {
|
||||||
|
message="can\'t connect to socket"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local gen_api = function(_table, http_method, api_group, api_action)
|
local gen_api = function(_table, http_method, api_group, api_action)
|
||||||
local _api_action
|
local _api_action
|
||||||
|
|
||||||
if api_action == "get_archive" or api_action == "put_archive" then
|
if api_action == "get_archive" or api_action == "put_archive" then
|
||||||
_api_action = "archive"
|
api_action = "archive"
|
||||||
elseif api_action == "df" then
|
elseif api_action == "df" then
|
||||||
_api_action = "system/df"
|
_api_action = "system/df"
|
||||||
elseif api_action ~= "list" and api_action ~= "inspect" and api_action ~= "remove" then
|
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 fp = function(self, request, callback)
|
||||||
local name_or_id = request and (request.name or request.id or request.name_or_id) or nil
|
local name_or_id = request and (request.name or request.id or request.name_or_id) or nil
|
||||||
|
|
||||||
if api_action == "list" then
|
if api_action == "list" then
|
||||||
if (name_or_id ~= "" and name_or_id ~= nil) then
|
if (name_or_id ~= "" and name_or_id ~= nil) then
|
||||||
if api_group == "images" then
|
if api_group == "images" then
|
||||||
|
@ -277,7 +339,13 @@ local gen_api = function(_table, http_method, api_group, api_action)
|
||||||
end
|
end
|
||||||
elseif api_action == "logs" then
|
elseif api_action == "logs" then
|
||||||
local body_buffer = ""
|
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
|
if response.code >= 200 and response.code < 300 then
|
||||||
for i, v in ipairs(response.body) do
|
for i, v in ipairs(response.body) do
|
||||||
body_buffer = body_buffer .. docker_stream_filter(response.body[i])
|
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
|
end
|
||||||
return response
|
return response
|
||||||
end
|
end
|
||||||
|
|
||||||
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.headers and response.headers["Content-Type"] == "application/json" then
|
if response.headers and response.headers["Content-Type"] == "application/json" then
|
||||||
if #response.body == 1 then
|
if #response.body == 1 then
|
||||||
response.body = json_parse(response.body[1])
|
response.body = json_parse(response.body[1])
|
||||||
|
@ -308,7 +378,13 @@ local gen_api = function(_table, http_method, api_group, api_action)
|
||||||
end
|
end
|
||||||
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, "GET", "containers", "list")
|
||||||
gen_api(_docker, "POST", "containers", "create")
|
gen_api(_docker, "POST", "containers", "create")
|
||||||
|
@ -370,6 +446,7 @@ gen_api(_docker, "GET", nil, "df")
|
||||||
function _docker.new(options)
|
function _docker.new(options)
|
||||||
local docker = {}
|
local docker = {}
|
||||||
local _options = options or {}
|
local _options = options or {}
|
||||||
|
|
||||||
docker.options = {
|
docker.options = {
|
||||||
socket_path = _options.socket_path or nil,
|
socket_path = _options.socket_path or nil,
|
||||||
host = _options.socket_path and "localhost" or _options.host,
|
host = _options.socket_path and "localhost" or _options.host,
|
||||||
|
@ -384,6 +461,7 @@ function _docker.new(options)
|
||||||
debug = _options.debug or false,
|
debug = _options.debug or false,
|
||||||
debug_path = _options.debug and _options.debug_path or nil
|
debug_path = _options.debug and _options.debug_path or nil
|
||||||
}
|
}
|
||||||
|
|
||||||
setmetatable(
|
setmetatable(
|
||||||
docker,
|
docker,
|
||||||
{
|
{
|
||||||
|
@ -396,6 +474,7 @@ function _docker.new(options)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
setmetatable(
|
setmetatable(
|
||||||
docker.containers,
|
docker.containers,
|
||||||
{
|
{
|
||||||
|
@ -406,6 +485,7 @@ function _docker.new(options)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
setmetatable(
|
setmetatable(
|
||||||
docker.networks,
|
docker.networks,
|
||||||
{
|
{
|
||||||
|
@ -416,6 +496,7 @@ function _docker.new(options)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
setmetatable(
|
setmetatable(
|
||||||
docker.images,
|
docker.images,
|
||||||
{
|
{
|
||||||
|
@ -426,6 +507,7 @@ function _docker.new(options)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
setmetatable(
|
setmetatable(
|
||||||
docker.volumes,
|
docker.volumes,
|
||||||
{
|
{
|
||||||
|
@ -436,6 +518,7 @@ function _docker.new(options)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
setmetatable(
|
setmetatable(
|
||||||
docker.exec,
|
docker.exec,
|
||||||
{
|
{
|
||||||
|
@ -446,6 +529,7 @@ function _docker.new(options)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return docker
|
return docker
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue