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,386 +2,444 @@
|
||||||
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)
|
||||||
|
e.dependent = false
|
||||||
|
e.acl_depends = { "luci-app-dockerman" }
|
||||||
|
|
||||||
local e = entry({"admin", "docker"}, firstchild(), "Docker", 40)
|
entry({"admin", "docker", "overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true
|
||||||
e.dependent = false
|
|
||||||
e.acl_depends = { "luci-app-dockerman" }
|
|
||||||
|
|
||||||
entry({"admin","docker","overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true
|
local remote = luci.model.uci.cursor():get_bool("dockerd", "globals", "remote_endpoint")
|
||||||
|
if remote then
|
||||||
|
local host = luci.model.uci.cursor():get("dockerd", "globals", "remote_host")
|
||||||
|
local port = luci.model.uci.cursor():get("dockerd", "globals", "remote_port")
|
||||||
|
if not host or not port then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local socket = luci.model.uci.cursor():get("dockerd", "globals", "socket_path")
|
||||||
|
if socket and not nixio.fs.access(socket) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local remote = luci.model.uci.cursor():get("dockerman", "local", "remote_endpoint")
|
if (require "luci.model.docker").new():_ping().code ~= 200 then
|
||||||
if remote == nil then
|
return
|
||||||
local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
|
end
|
||||||
if socket and not nixio.fs.access(socket) then return end
|
|
||||||
elseif remote == "true" then
|
|
||||||
local host = luci.model.uci.cursor():get("dockerman", "local", "remote_host")
|
|
||||||
local port = luci.model.uci.cursor():get("dockerman", "local", "remote_port")
|
|
||||||
if not host or not port then return end
|
|
||||||
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_get_archive"},call("download_archive")).leaf=true
|
entry({"admin", "docker", "container_stats"}, call("action_get_container_stats")).leaf=true
|
||||||
entry({"admin","docker","container_put_archive"},call("upload_archive")).leaf=true
|
entry({"admin", "docker", "container_get_archive"}, call("download_archive")).leaf=true
|
||||||
entry({"admin","docker","images_save"},call("save_images")).leaf=true
|
entry({"admin", "docker", "container_put_archive"}, call("upload_archive")).leaf=true
|
||||||
entry({"admin","docker","images_load"},call("load_images")).leaf=true
|
entry({"admin", "docker", "images_save"}, call("save_images")).leaf=true
|
||||||
entry({"admin","docker","images_import"},call("import_images")).leaf=true
|
entry({"admin", "docker", "images_load"}, call("load_images")).leaf=true
|
||||||
entry({"admin","docker","images_get_tags"},call("get_image_tags")).leaf=true
|
entry({"admin", "docker", "images_import"}, call("import_images")).leaf=true
|
||||||
entry({"admin","docker","images_tag"},call("tag_image")).leaf=true
|
entry({"admin", "docker", "images_get_tags"}, call("get_image_tags")).leaf=true
|
||||||
entry({"admin","docker","images_untag"},call("untag_image")).leaf=true
|
entry({"admin", "docker", "images_tag"}, call("tag_image")).leaf=true
|
||||||
entry({"admin","docker","confirm"},call("action_confirm")).leaf=true
|
entry({"admin", "docker", "images_untag"}, call("untag_image")).leaf=true
|
||||||
|
entry({"admin", "docker", "confirm"}, call("action_confirm")).leaf=true
|
||||||
end
|
end
|
||||||
|
|
||||||
function action_events()
|
function action_events()
|
||||||
local logs = ""
|
local logs = ""
|
||||||
local dk = docker.new()
|
local query ={}
|
||||||
local query ={}
|
|
||||||
query["until"] = os.time()
|
local dk = docker.new()
|
||||||
local events = dk:events({query = query})
|
query["until"] = os.time()
|
||||||
if events.code == 200 then
|
local events = dk:events({query = query})
|
||||||
for _, v in ipairs(events.body) do
|
|
||||||
if v and v.Type == "container" then
|
if events.code == 200 then
|
||||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. (v.Action or "null") .. " Container ID:".. (v.Actor.ID or "null") .. " Container Name:" .. (v.Actor.Attributes.name or "null")
|
for _, v in ipairs(events.body) do
|
||||||
elseif v.Type == "network" then
|
if v and v.Type == "container" then
|
||||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Container ID:"..( v.Actor.Attributes.container or "null" ) .. " Network Name:" .. (v.Actor.Attributes.name or "null") .. " Network type:".. v.Actor.Attributes.type or ""
|
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. (v.Action or "null") .. " Container ID:".. (v.Actor.ID or "null") .. " Container Name:" .. (v.Actor.Attributes.name or "null")
|
||||||
elseif v.Type == "image" then
|
elseif v.Type == "network" then
|
||||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Image:".. (v.Actor.ID or "null").. " Image Name:" .. (v.Actor.Attributes.name or "null")
|
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Container ID:"..( v.Actor.Attributes.container or "null" ) .. " Network Name:" .. (v.Actor.Attributes.name or "null") .. " Network type:".. v.Actor.Attributes.type or ""
|
||||||
end
|
elseif v.Type == "image" then
|
||||||
end
|
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Image:".. (v.Actor.ID or "null").. " Image Name:" .. (v.Actor.Attributes.name or "null")
|
||||||
end
|
end
|
||||||
luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}})
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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"])
|
||||||
if system_delta > 0.0 then
|
local cpu_percent = 0.0
|
||||||
cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count)
|
local cpu_delta = tonumber(d["cpu_stats"]["cpu_usage"]["total_usage"]) - tonumber(d["precpu_stats"]["cpu_usage"]["total_usage"])
|
||||||
end
|
local system_delta = tonumber(d["cpu_stats"]["system_cpu_usage"]) - tonumber(d["precpu_stats"]["system_cpu_usage"])
|
||||||
-- return cpu_percent .. "%"
|
if system_delta > 0.0 then
|
||||||
return cpu_percent
|
cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count)
|
||||||
|
end
|
||||||
|
|
||||||
|
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"
|
local data = {}
|
||||||
-- end
|
if type(d["networks"]) == "table" then
|
||||||
-- end
|
for e, v in pairs(d["networks"]) do
|
||||||
local data = {}
|
data[e] = {
|
||||||
if type(d["networks"]) == "table" then
|
bw_tx = tonumber(v.tx_bytes),
|
||||||
for e, v in pairs(d["networks"]) do
|
bw_rx = tonumber(v.rx_bytes)
|
||||||
data[e] = {
|
}
|
||||||
bw_tx = tonumber(v.tx_bytes),
|
end
|
||||||
bw_rx = tonumber(v.rx_bytes)
|
end
|
||||||
}
|
|
||||||
end
|
return data
|
||||||
end
|
|
||||||
return data
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function action_get_container_stats(container_id)
|
function action_get_container_stats(container_id)
|
||||||
if container_id then
|
if container_id then
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
local response = dk.containers:inspect({id = container_id})
|
local response = dk.containers:inspect({id = container_id})
|
||||||
if response.code == 200 and response.body.State.Running then
|
if response.code == 200 and response.body.State.Running then
|
||||||
response = dk.containers:stats({id = container_id, query = {stream = false}})
|
response = dk.containers:stats({id = container_id, query = {stream = false}})
|
||||||
if response.code == 200 then
|
if response.code == 200 then
|
||||||
local container_stats = response.body
|
local container_stats = response.body
|
||||||
local cpu_percent = calculate_cpu_percent(container_stats)
|
local cpu_percent = calculate_cpu_percent(container_stats)
|
||||||
local mem_useage, mem_limit = get_memory(container_stats)
|
local mem_useage, mem_limit = get_memory(container_stats)
|
||||||
local bw_rxtx = get_rx_tx(container_stats)
|
local bw_rxtx = get_rx_tx(container_stats)
|
||||||
luci.http.status(response.code, response.body.message)
|
luci.http.status(response.code, response.body.message)
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
luci.http.write_json({
|
luci.http.write_json({
|
||||||
cpu_percent = cpu_percent,
|
cpu_percent = cpu_percent,
|
||||||
memory = {
|
memory = {
|
||||||
mem_useage = mem_useage,
|
mem_useage = mem_useage,
|
||||||
mem_limit = mem_limit
|
mem_limit = mem_limit
|
||||||
},
|
},
|
||||||
bw_rxtx = bw_rxtx
|
bw_rxtx = bw_rxtx
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
luci.http.status(response.code, response.body.message)
|
luci.http.status(response.code, response.body.message)
|
||||||
luci.http.prepare_content("text/plain")
|
luci.http.prepare_content("text/plain")
|
||||||
luci.http.write(response.body.message)
|
luci.http.write(response.body.message)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if response.code == 200 then
|
if response.code == 200 then
|
||||||
luci.http.status(500, "container "..container_id.." not running")
|
luci.http.status(500, "container "..container_id.." not running")
|
||||||
luci.http.prepare_content("text/plain")
|
luci.http.prepare_content("text/plain")
|
||||||
luci.http.write("Container "..container_id.." not running")
|
luci.http.write("Container "..container_id.." not running")
|
||||||
else
|
else
|
||||||
luci.http.status(response.code, response.body.message)
|
luci.http.status(response.code, response.body.message)
|
||||||
luci.http.prepare_content("text/plain")
|
luci.http.prepare_content("text/plain")
|
||||||
luci.http.write(response.body.message)
|
luci.http.write(response.body.message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
luci.http.status(404, "No container name or id")
|
luci.http.status(404, "No container name or id")
|
||||||
luci.http.prepare_content("text/plain")
|
luci.http.prepare_content("text/plain")
|
||||||
luci.http.write("No container name or id")
|
luci.http.write("No container name or id")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function action_confirm()
|
function action_confirm()
|
||||||
local data = docker:read_status()
|
local data = docker:read_status()
|
||||||
if data then
|
if data then
|
||||||
data = data:gsub("\n","<br>"):gsub(" "," ")
|
data = data:gsub("\n","<br>"):gsub(" "," ")
|
||||||
code = 202
|
code = 202
|
||||||
msg = data
|
msg = data
|
||||||
else
|
else
|
||||||
code = 200
|
code = 200
|
||||||
msg = "finish"
|
msg = "finish"
|
||||||
data = "finish"
|
data = "finish"
|
||||||
end
|
end
|
||||||
luci.http.status(code, msg)
|
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.status(code, msg)
|
||||||
luci.http.write_json({info = data})
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({info = data})
|
||||||
end
|
end
|
||||||
|
|
||||||
function download_archive()
|
function download_archive()
|
||||||
local id = luci.http.formvalue("id")
|
local id = luci.http.formvalue("id")
|
||||||
local path = luci.http.formvalue("path")
|
local path = luci.http.formvalue("path")
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
local first
|
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
|
||||||
first = true
|
first = true
|
||||||
luci.http.header('Content-Disposition', 'inline; filename="archive.tar"')
|
luci.http.header('Content-Disposition', 'inline; filename="archive.tar"')
|
||||||
luci.http.header('Content-Type', 'application\/x-tar')
|
luci.http.header('Content-Type', 'application\/x-tar')
|
||||||
end
|
end
|
||||||
luci.ltn12.pump.all(chunk, luci.http.write)
|
luci.ltn12.pump.all(chunk, luci.http.write)
|
||||||
else
|
else
|
||||||
if not first then
|
if not first then
|
||||||
first = true
|
first = true
|
||||||
luci.http.prepare_content("text/plain")
|
luci.http.prepare_content("text/plain")
|
||||||
end
|
end
|
||||||
luci.ltn12.pump.all(chunk, luci.http.write)
|
luci.ltn12.pump.all(chunk, luci.http.write)
|
||||||
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)
|
||||||
local path = luci.http.formvalue("upload-path")
|
local path = luci.http.formvalue("upload-path")
|
||||||
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
|
||||||
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
|
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
|
||||||
end
|
end
|
||||||
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({
|
||||||
local msg = res and res.body and res.body.message or nil
|
id = container_id,
|
||||||
luci.http.status(res.code, msg)
|
query = {
|
||||||
luci.http.prepare_content("application/json")
|
path = path
|
||||||
luci.http.write_json({message = msg})
|
},
|
||||||
|
body = rec_send
|
||||||
|
})
|
||||||
|
|
||||||
|
local msg = res and res.body and res.body.message or nil
|
||||||
|
luci.http.status(res.code, msg)
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({message = msg})
|
||||||
end
|
end
|
||||||
|
|
||||||
function save_images(container_id)
|
function save_images(container_id)
|
||||||
local names = luci.http.formvalue("names")
|
local names = luci.http.formvalue("names")
|
||||||
local dk = docker.new()
|
local dk = docker.new()
|
||||||
local first
|
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
|
||||||
first = true
|
first = true
|
||||||
luci.http.status(res.code, res.message)
|
luci.http.status(res.code, res.message)
|
||||||
luci.http.header('Content-Disposition', 'inline; filename="images.tar"')
|
luci.http.header('Content-Disposition', 'inline; filename="images.tar"')
|
||||||
luci.http.header('Content-Type', 'application\/x-tar')
|
luci.http.header('Content-Type', 'application\/x-tar')
|
||||||
end
|
end
|
||||||
luci.ltn12.pump.all(chunk, luci.http.write)
|
luci.ltn12.pump.all(chunk, luci.http.write)
|
||||||
else
|
else
|
||||||
if not first then
|
if not first then
|
||||||
first = true
|
first = true
|
||||||
luci.http.prepare_content("text/plain")
|
luci.http.prepare_content("text/plain")
|
||||||
end
|
end
|
||||||
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 .. "...")
|
|
||||||
local res = dk.images:get({id = container_id, query = {names = names}}, cb)
|
docker:write_status("Images: saving" .. " " .. container_id .. "...")
|
||||||
docker:clear_status()
|
local res = dk.images:get({
|
||||||
local msg = res and res.body and res.body.message or nil
|
id = container_id,
|
||||||
luci.http.status(res.code, msg)
|
query = {
|
||||||
luci.http.prepare_content("application/json")
|
names = names
|
||||||
luci.http.write_json({message = msg})
|
}
|
||||||
|
}, cb)
|
||||||
|
docker:clear_status()
|
||||||
|
|
||||||
|
local msg = res and res.body and res.body.message or nil
|
||||||
|
luci.http.status(res.code, msg)
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({message = msg})
|
||||||
end
|
end
|
||||||
|
|
||||||
function load_images()
|
function load_images()
|
||||||
local path = luci.http.formvalue("upload-path")
|
local path = luci.http.formvalue("upload-path")
|
||||||
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
|
||||||
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
|
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
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()
|
luci.http.status(res.code, msg)
|
||||||
luci.http.status(res.code, msg)
|
else
|
||||||
else
|
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
|
||||||
|
|
||||||
function import_images()
|
function import_images()
|
||||||
local src = luci.http.formvalue("src")
|
local src = luci.http.formvalue("src")
|
||||||
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)
|
|
||||||
luci.http.setfilehandler(function (meta, chunk, eof)
|
local rec_send = function(sinkout)
|
||||||
if chunk then
|
luci.http.setfilehandler(function (meta, chunk, eof)
|
||||||
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
|
if chunk then
|
||||||
end
|
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
|
||||||
end)
|
end
|
||||||
end
|
end)
|
||||||
docker:write_status("Images: importing".. " ".. itag .."...\n")
|
end
|
||||||
local repo = itag and itag:match("^([^:]+)")
|
|
||||||
local tag = itag and itag:match("^[^:]-:([^:]+)")
|
docker:write_status("Images: importing".. " ".. itag .."...\n")
|
||||||
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 repo = itag and itag:match("^([^:]+)")
|
||||||
local msg = res and res.body and ( res.body.message )or nil
|
local tag = itag and itag:match("^[^:]-:([^:]+)")
|
||||||
if not msg and #res.body == 0 then
|
local res = dk.images:create({
|
||||||
-- res.body = {"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
|
query = {
|
||||||
msg = res.body.status or res.body.error
|
fromSrc = src or "-",
|
||||||
elseif not msg and #res.body >= 1 then
|
repo = repo or nil,
|
||||||
-- res.body = [...{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}]
|
tag = tag or nil
|
||||||
msg = res.body[#res.body].status or res.body[#res.body].error
|
},
|
||||||
end
|
body = not src and rec_send or nil
|
||||||
if res.code == 200 and msg and msg:match("sha256:") then
|
}, docker.import_image_show_status_cb)
|
||||||
docker:clear_status()
|
|
||||||
else
|
local msg = res and res.body and ( res.body.message )or nil
|
||||||
docker:append_status("code:" .. res.code.." ".. msg)
|
if not msg and #res.body == 0 then
|
||||||
end
|
msg = res.body.status or res.body.error
|
||||||
luci.http.status(res.code, msg)
|
elseif not msg and #res.body >= 1 then
|
||||||
luci.http.prepare_content("application/json")
|
msg = res.body[#res.body].status or res.body[#res.body].error
|
||||||
luci.http.write_json({message = msg})
|
end
|
||||||
|
|
||||||
|
if res.code == 200 and msg and msg:match("sha256:") then
|
||||||
|
docker:clear_status()
|
||||||
|
else
|
||||||
|
docker:append_status("code:" .. res.code.." ".. msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
luci.http.status(res.code, msg)
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({message = msg})
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_image_tags(image_id)
|
function get_image_tags(image_id)
|
||||||
if not image_id then
|
if not image_id then
|
||||||
luci.http.status(400, "no image id")
|
luci.http.status(400, "no image id")
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
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 res = dk.images:inspect({id = image_id})
|
local dk = docker.new()
|
||||||
local msg = res and res.body and res.body.message or nil
|
local res = dk.images:inspect({
|
||||||
luci.http.status(res.code, msg)
|
id = image_id
|
||||||
luci.http.prepare_content("application/json")
|
})
|
||||||
if res.code == 200 then
|
local msg = res and res.body and res.body.message or nil
|
||||||
local tags = res.body.RepoTags
|
luci.http.status(res.code, msg)
|
||||||
luci.http.write_json({tags = tags})
|
luci.http.prepare_content("application/json")
|
||||||
else
|
|
||||||
local msg = res and res.body and res.body.message or nil
|
if res.code == 200 then
|
||||||
luci.http.write_json({message = msg})
|
local tags = res.body.RepoTags
|
||||||
end
|
luci.http.write_json({tags = tags})
|
||||||
|
else
|
||||||
|
local msg = res and res.body and res.body.message or nil
|
||||||
|
luci.http.write_json({message = msg})
|
||||||
|
end
|
||||||
end
|
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
|
|
||||||
luci.http.status(400, "no image id or tag")
|
if type(src) ~= "string" or not image_id then
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.status(400, "no image id or tag")
|
||||||
luci.http.write_json({message = "no image id or tag"})
|
luci.http.prepare_content("application/json")
|
||||||
return
|
luci.http.write_json({message = "no image id or tag"})
|
||||||
end
|
return
|
||||||
local repo = src:match("^([^:]+)")
|
end
|
||||||
local tag = src:match("^[^:]-:([^:]+)")
|
|
||||||
local dk = docker.new()
|
local repo = src:match("^([^:]+)")
|
||||||
local res = dk.images:tag({id = image_id, query={repo=repo, tag=tag}})
|
local tag = src:match("^[^:]-:([^:]+)")
|
||||||
local msg = res and res.body and res.body.message or nil
|
local dk = docker.new()
|
||||||
luci.http.status(res.code, msg)
|
local res = dk.images:tag({
|
||||||
luci.http.prepare_content("application/json")
|
id = image_id,
|
||||||
if res.code == 201 then
|
query={
|
||||||
local tags = res.body.RepoTags
|
repo=repo,
|
||||||
luci.http.write_json({tags = tags})
|
tag=tag
|
||||||
else
|
}
|
||||||
local msg = res and res.body and res.body.message or nil
|
})
|
||||||
luci.http.write_json({message = msg})
|
local msg = res and res.body and res.body.message or nil
|
||||||
end
|
luci.http.status(res.code, msg)
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
|
||||||
|
if res.code == 201 then
|
||||||
|
local tags = res.body.RepoTags
|
||||||
|
luci.http.write_json({tags = tags})
|
||||||
|
else
|
||||||
|
local msg = res and res.body and res.body.message or nil
|
||||||
|
luci.http.write_json({message = msg})
|
||||||
|
end
|
||||||
end
|
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
|
|
||||||
luci.http.status(400, "no tag name")
|
if not tag then
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.status(400, "no tag name")
|
||||||
luci.http.write_json({message = "no tag name"})
|
luci.http.prepare_content("application/json")
|
||||||
return
|
luci.http.write_json({message = "no tag name"})
|
||||||
end
|
return
|
||||||
local dk = docker.new()
|
end
|
||||||
local res = dk.images:inspect({name = tag})
|
|
||||||
if res.code == 200 then
|
local dk = docker.new()
|
||||||
local tags = res.body.RepoTags
|
local res = dk.images:inspect({name = tag})
|
||||||
if #tags > 1 then
|
|
||||||
local r = dk.images:remove({name = tag})
|
if res.code == 200 then
|
||||||
local msg = r and r.body and r.body.message or nil
|
local tags = res.body.RepoTags
|
||||||
luci.http.status(r.code, msg)
|
if #tags > 1 then
|
||||||
luci.http.prepare_content("application/json")
|
local r = dk.images:remove({name = tag})
|
||||||
luci.http.write_json({message = msg})
|
local msg = r and r.body and r.body.message or nil
|
||||||
else
|
luci.http.status(r.code, msg)
|
||||||
luci.http.status(500, "Cannot remove the last tag")
|
luci.http.prepare_content("application/json")
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.write_json({message = msg})
|
||||||
luci.http.write_json({message = "Cannot remove the last tag"})
|
else
|
||||||
end
|
luci.http.status(500, "Cannot remove the last tag")
|
||||||
else
|
luci.http.prepare_content("application/json")
|
||||||
local msg = res and res.body and res.body.message or nil
|
luci.http.write_json({message = "Cannot remove the last tag"})
|
||||||
luci.http.status(res.code, msg)
|
end
|
||||||
luci.http.prepare_content("application/json")
|
else
|
||||||
luci.http.write_json({message = msg})
|
local msg = res and res.body and res.body.message or nil
|
||||||
end
|
luci.http.status(res.code, msg)
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json({message = msg})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,193 +3,229 @@ 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
|
|
||||||
for i, v in ipairs(containers) do
|
|
||||||
local index = v.Created .. v.Id
|
|
||||||
data[index]={}
|
|
||||||
data[index]["_selected"] = 0
|
|
||||||
data[index]["_id"] = v.Id:sub(1,12)
|
|
||||||
data[index]["name"] = v.Names[1]:sub(2)
|
|
||||||
data[index]["_name"] = '<a href='..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. v.Names[1]:sub(2).."</a>"
|
|
||||||
data[index]["_status"] = v.Status
|
|
||||||
if v.Status:find("^Up") then
|
|
||||||
data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>"
|
|
||||||
else
|
|
||||||
data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>"
|
|
||||||
end
|
|
||||||
if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then
|
|
||||||
for networkname, netconfig in pairs(v.NetworkSettings.Networks) do
|
|
||||||
data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- networkmode = v.HostConfig.NetworkMode ~= "default" and v.HostConfig.NetworkMode or "bridge"
|
|
||||||
-- data[index]["_network"] = v.NetworkSettings.Networks[networkmode].IPAddress or nil
|
|
||||||
-- local _, _, image = v.Image:find("^sha256:(.+)")
|
|
||||||
-- if image ~= nil then
|
|
||||||
-- image=image:sub(1,12)
|
|
||||||
-- end
|
|
||||||
if v.Ports and next(v.Ports) ~= nil then
|
|
||||||
data[index]["_ports"] = nil
|
|
||||||
for _,v2 in ipairs(v.Ports) do
|
|
||||||
data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "")
|
|
||||||
.. ((v2.PublicPort and v2.Type and v2.Type == "tcp") and ('<a href="javascript:void(0);" onclick="window.open((window.location.origin.match(/^(.+):\\d+$/) && window.location.origin.match(/^(.+):\\d+$/)[1] || window.location.origin) + \':\' + '.. v2.PublicPort ..', \'_blank\');">') or "")
|
|
||||||
.. (v2.PublicPort and (v2.PublicPort .. ":") or "") .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "")
|
|
||||||
.. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "</a>" or "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for ii,iv in ipairs(images) do
|
|
||||||
if iv.Id == v.ImageID then
|
|
||||||
data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
data[index]["_image_id"] = v.ImageID:sub(8,20)
|
if type(containers) ~= "table" then
|
||||||
data[index]["_command"] = v.Command
|
return nil
|
||||||
end
|
end
|
||||||
return data
|
|
||||||
|
for i, v in ipairs(containers) do
|
||||||
|
local index = v.Created .. v.Id
|
||||||
|
|
||||||
|
data[index]={}
|
||||||
|
data[index]["_selected"] = 0
|
||||||
|
data[index]["_id"] = v.Id:sub(1,12)
|
||||||
|
data[index]["name"] = v.Names[1]:sub(2)
|
||||||
|
data[index]["_name"] = '<a href='..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. v.Names[1]:sub(2).."</a>"
|
||||||
|
data[index]["_status"] = v.Status
|
||||||
|
|
||||||
|
if v.Status:find("^Up") then
|
||||||
|
data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>"
|
||||||
|
else
|
||||||
|
data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>"
|
||||||
|
end
|
||||||
|
|
||||||
|
if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then
|
||||||
|
for networkname, netconfig in pairs(v.NetworkSettings.Networks) do
|
||||||
|
data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if v.Ports and next(v.Ports) ~= nil then
|
||||||
|
data[index]["_ports"] = nil
|
||||||
|
for _,v2 in ipairs(v.Ports) do
|
||||||
|
data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "")
|
||||||
|
.. ((v2.PublicPort and v2.Type and v2.Type == "tcp") and ('<a href="javascript:void(0);" onclick="window.open((window.location.origin.match(/^(.+):\\d+$/) && window.location.origin.match(/^(.+):\\d+$/)[1] || window.location.origin) + \':\' + '.. v2.PublicPort ..', \'_blank\');">') or "")
|
||||||
|
.. (v2.PublicPort and (v2.PublicPort .. ":") or "") .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "")
|
||||||
|
.. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "</a>" or "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for ii,iv in ipairs(images) do
|
||||||
|
if iv.Id == v.ImageID then
|
||||||
|
data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
data[index]["_image_id"] = v.ImageID:sub(8,20)
|
||||||
|
data[index]["_command"] = v.Command
|
||||||
|
end
|
||||||
|
|
||||||
|
return data
|
||||||
end
|
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
|
end
|
||||||
c_selected[#c_selected+1] = c_lists[c_table_sid].name --container_name:cfgvalue(c_table_sid)
|
end
|
||||||
end
|
|
||||||
end
|
if #container_selected > 0 then
|
||||||
if #c_selected >0 then
|
local success = true
|
||||||
docker:clear_status()
|
|
||||||
local success = true
|
docker:clear_status()
|
||||||
for _,cont in ipairs(c_selected) do
|
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
|
||||||
success = false
|
success = false
|
||||||
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")
|
||||||
else
|
else
|
||||||
docker:append_status("done\n")
|
docker:append_status("done\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if success then docker:clear_status() end
|
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
|
if success then
|
||||||
end
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
|
||||||
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
|
||||||
|
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"
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
|
||||||
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"))
|
|
||||||
end
|
end
|
||||||
btnstart.write = function(self, section)
|
|
||||||
start_stop_remove(m,"start")
|
o = s:option(Button, "_start")
|
||||||
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
|
o.inputtitle=translate("Start")
|
||||||
|
o.inputstyle = "apply"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section)
|
||||||
|
start_stop_remove(m,"start")
|
||||||
end
|
end
|
||||||
btnrestart.write = function(self, section)
|
|
||||||
start_stop_remove(m,"restart")
|
o = s:option(Button, "_restart")
|
||||||
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
|
o.inputtitle=translate("Restart")
|
||||||
|
o.inputstyle = "reload"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section)
|
||||||
|
start_stop_remove(m,"restart")
|
||||||
end
|
end
|
||||||
btnremove.write = function(self, section)
|
|
||||||
start_stop_remove(m,"remove")
|
o = s:option(Button, "_stop")
|
||||||
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
|
o.inputtitle=translate("Stop")
|
||||||
|
o.inputstyle = "reset"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section)
|
||||||
|
start_stop_remove(m,"stop")
|
||||||
end
|
end
|
||||||
btnstop.write = function(self, section)
|
|
||||||
start_stop_remove(m,"stop")
|
o = s:option(Button, "_kill")
|
||||||
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
|
o.inputtitle=translate("Kill")
|
||||||
|
o.inputstyle = "reset"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section)
|
||||||
|
start_stop_remove(m,"kill")
|
||||||
end
|
end
|
||||||
btnkill.write = function(self, section)
|
|
||||||
start_stop_remove(m,"kill")
|
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
|
end
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
|
@ -3,221 +3,272 @@ 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
|
|
||||||
local index = v.Created .. v.Id
|
for i, v in ipairs(images) do
|
||||||
data[index]={}
|
local index = v.Created .. v.Id
|
||||||
data[index]["_selected"] = 0
|
|
||||||
data[index]["id"] = v.Id:sub(8)
|
data[index]={}
|
||||||
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]["_selected"] = 0
|
||||||
if v.RepoTags and next(v.RepoTags)~=nil then
|
data[index]["id"] = v.Id:sub(8)
|
||||||
for i, v1 in ipairs(v.RepoTags) do
|
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]["_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 v.RepoTags and next(v.RepoTags)~=nil then
|
||||||
data[index]["tag"] = v1--:match("<none>") and nil or v1
|
for i, v1 in ipairs(v.RepoTags) do
|
||||||
end
|
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>'))
|
||||||
end
|
|
||||||
else
|
if not data[index]["tag"] then
|
||||||
data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+")
|
data[index]["tag"] = v1
|
||||||
data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>"
|
end
|
||||||
end
|
end
|
||||||
data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","<none>")
|
else
|
||||||
-- data[index]["_tags"] = '<a href="javascript:handle_tag(\''..data[index]["_id"]..'\')">' .. data[index]["_tags"] .. '</a>'
|
data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+")
|
||||||
for ci,cv in ipairs(containers) do
|
data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>"
|
||||||
if v.Id == cv.ImageID then
|
end
|
||||||
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>"
|
data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","<none>")
|
||||||
end
|
for ci,cv in ipairs(containers) do
|
||||||
end
|
if v.Id == cv.ImageID then
|
||||||
data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
|
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
|
||||||
data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created)
|
'<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
|
||||||
return data
|
end
|
||||||
|
|
||||||
|
data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
|
||||||
|
data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created)
|
||||||
|
end
|
||||||
|
|
||||||
|
return data
|
||||||
end
|
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"
|
||||||
local hastag = value:find(":")
|
o.write = function(self, section, value)
|
||||||
if not hastag then
|
local hastag = value:find(":")
|
||||||
value = value .. ":latest"
|
|
||||||
end
|
if not hastag then
|
||||||
pull_value["_image_tag_name"] = value
|
value = value .. ":latest"
|
||||||
end
|
end
|
||||||
action_pull.write = function(self, section)
|
pull_value["_image_tag_name"] = value
|
||||||
local tag = pull_value["_image_tag_name"]
|
|
||||||
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
|
||||||
if tag and tag ~= "" then
|
|
||||||
docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n")
|
|
||||||
-- local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server})) , header={["X-Registry-Auth"] = x_auth}
|
|
||||||
local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb)
|
|
||||||
-- {"errorDetail": {"message": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }, "error": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }
|
|
||||||
if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then
|
|
||||||
docker:clear_status()
|
|
||||||
else
|
|
||||||
docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
docker:append_status("code: 400 please input the name of image name!")
|
|
||||||
end
|
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local import_section = m:section(SimpleSection, translate("Import Images"))
|
o = s:option(Button, "_pull")
|
||||||
local im = import_section:option(DummyValue, "_image_import")
|
o.inputtitle= translate("Pull")
|
||||||
im.template = "dockerman/images_import"
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
|
o.inputstyle = "add"
|
||||||
|
o.write = function(self, section)
|
||||||
|
local tag = pull_value["_image_tag_name"]
|
||||||
|
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
||||||
|
|
||||||
local image_table = m:section(Table, image_list, translate("Images"))
|
if tag and tag ~= "" then
|
||||||
|
docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n")
|
||||||
|
local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb)
|
||||||
|
|
||||||
local image_selecter = image_table:option(Flag, "_selected","")
|
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
|
||||||
image_selecter.disabled = 0
|
docker:clear_status()
|
||||||
image_selecter.enabled = 1
|
else
|
||||||
image_selecter.default = 0
|
docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
docker:append_status("code: 400 please input the name of image name!")
|
||||||
|
end
|
||||||
|
|
||||||
local image_id = image_table:option(DummyValue, "_id", translate("ID"))
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
|
||||||
image_id.rawhtml = true
|
|
||||||
image_table:option(DummyValue, "_tags", translate("RepoTags")).rawhtml = true
|
|
||||||
image_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
|
|
||||||
image_table:option(DummyValue, "_size", translate("Size"))
|
|
||||||
image_table:option(DummyValue, "_created", translate("Created"))
|
|
||||||
image_selecter.write = function(self, section, value)
|
|
||||||
image_list[section]._selected = value
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
s = m:section(SimpleSection, translate("Import Images"))
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "_image_import")
|
||||||
|
o.template = "dockerman/images_import"
|
||||||
|
|
||||||
|
s = m:section(Table, image_list, translate("Images"))
|
||||||
|
|
||||||
|
o = s:option(Flag, "_selected","")
|
||||||
|
o.disabled = 0
|
||||||
|
o.enabled = 1
|
||||||
|
o.default = 0
|
||||||
|
o.write = function(self, section, value)
|
||||||
|
image_list[section]._selected = value
|
||||||
|
end
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "_tags", translate("RepoTags"))
|
||||||
|
o.rawhtml = true
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "_containers", translate("Containers"))
|
||||||
|
o.rawhtml = true
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "_size", translate("Size"))
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "_created", translate("Created"))
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "_id", translate("ID"))
|
||||||
|
o.rawhtml = true
|
||||||
|
|
||||||
local remove_action = function(force)
|
local 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
|
end
|
||||||
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
|
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
|
docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
|
||||||
if force then query = {force = true} end
|
|
||||||
local msg = dk.images:remove({id = img, query = query})
|
if force then
|
||||||
if msg.code ~= 200 then
|
query = {force = true}
|
||||||
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
end
|
||||||
success = false
|
|
||||||
else
|
local msg = dk.images:remove({
|
||||||
docker:append_status("done\n")
|
id = img,
|
||||||
end
|
query = query
|
||||||
end
|
})
|
||||||
if success then docker:clear_status() end
|
if msg.code ~= 200 then
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
|
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
||||||
end
|
success = false
|
||||||
|
else
|
||||||
|
docker:append_status("done\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if success then
|
||||||
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
|
||||||
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
|
||||||
|
end
|
||||||
end
|
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()
|
||||||
local action = m:section(Table,{{}})
|
|
||||||
action.notitle=true
|
|
||||||
action.rowcolors=false
|
|
||||||
action.template="cbi/nullsection"
|
|
||||||
|
|
||||||
local btnremove = action:option(Button, "remove")
|
|
||||||
btnremove.inputtitle= translate("Remove")
|
|
||||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
|
||||||
btnremove.inputstyle = "remove"
|
|
||||||
btnremove.forcewrite = true
|
|
||||||
btnremove.write = function(self, section)
|
|
||||||
remove_action()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local btnforceremove = action:option(Button, "forceremove")
|
s = m:section(Table,{{}})
|
||||||
btnforceremove.inputtitle= translate("Force Remove")
|
s.notitle=true
|
||||||
btnforceremove.template = "dockerman/cbi/inlinebutton"
|
s.rowcolors=false
|
||||||
btnforceremove.inputstyle = "remove"
|
s.template="cbi/nullsection"
|
||||||
btnforceremove.forcewrite = true
|
|
||||||
btnforceremove.write = function(self, section)
|
o = s:option(Button, "remove")
|
||||||
remove_action(true)
|
o.inputtitle= translate("Remove")
|
||||||
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
|
o.inputstyle = "remove"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function(self, section)
|
||||||
|
remove_action()
|
||||||
end
|
end
|
||||||
|
|
||||||
local btnsave = action:option(Button, "save")
|
o = s:option(Button, "forceremove")
|
||||||
btnsave.inputtitle= translate("Save")
|
o.inputtitle= translate("Force Remove")
|
||||||
btnsave.template = "dockerman/cbi/inlinebutton"
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnsave.inputstyle = "edit"
|
o.inputstyle = "remove"
|
||||||
btnsave.forcewrite = true
|
o.forcewrite = true
|
||||||
btnsave.write = function (self, section)
|
o.write = function(self, section)
|
||||||
local image_selected = {}
|
remove_action(true)
|
||||||
local image_table_sids = image_table:cfgsections()
|
|
||||||
for _, image_table_sid in ipairs(image_table_sids) do
|
|
||||||
if image_list[image_table_sid]._selected == 1 then
|
|
||||||
image_selected[#image_selected+1] = image_list[image_table_sid].id --image_id:cfgvalue(image_table_sid)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if next(image_selected) ~= nil then
|
|
||||||
local names
|
|
||||||
for _,img in ipairs(image_selected) do
|
|
||||||
names = names and (names .. "&names=".. img) or img
|
|
||||||
end
|
|
||||||
local first
|
|
||||||
local cb = function(res, chunk)
|
|
||||||
if res.code == 200 then
|
|
||||||
if not first then
|
|
||||||
first = true
|
|
||||||
luci.http.header('Content-Disposition', 'inline; filename="images.tar"')
|
|
||||||
luci.http.header('Content-Type', 'application\/x-tar')
|
|
||||||
end
|
|
||||||
luci.ltn12.pump.all(chunk, luci.http.write)
|
|
||||||
else
|
|
||||||
if not first then
|
|
||||||
first = true
|
|
||||||
luci.http.prepare_content("text/plain")
|
|
||||||
end
|
|
||||||
luci.ltn12.pump.all(chunk, luci.http.write)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...")
|
|
||||||
local msg = dk.images:get({query = {names = names}}, cb)
|
|
||||||
if msg.code ~= 200 then
|
|
||||||
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
|
||||||
success = false
|
|
||||||
else
|
|
||||||
docker:clear_status()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local btnload = action:option(Button, "load")
|
o = s:option(Button, "save")
|
||||||
btnload.inputtitle= translate("Load")
|
o.inputtitle= translate("Save")
|
||||||
btnload.template = "dockerman/images_load"
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
btnload.inputstyle = "add"
|
o.inputstyle = "edit"
|
||||||
|
o.forcewrite = true
|
||||||
|
o.write = function (self, section)
|
||||||
|
local image_selected = {}
|
||||||
|
|
||||||
|
for k in pairs(image_list) do
|
||||||
|
if image_list[k]._selected == 1 then
|
||||||
|
image_selected[#image_selected + 1] = image_list[k].id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if next(image_selected) ~= nil then
|
||||||
|
local names, first
|
||||||
|
|
||||||
|
for _, img in ipairs(image_selected) do
|
||||||
|
names = names and (names .. "&names=".. img) or img
|
||||||
|
end
|
||||||
|
|
||||||
|
local cb = function(res, chunk)
|
||||||
|
if res.code == 200 then
|
||||||
|
if not first then
|
||||||
|
first = true
|
||||||
|
luci.http.header('Content-Disposition', 'inline; filename="images.tar"')
|
||||||
|
luci.http.header('Content-Type', 'application\/x-tar')
|
||||||
|
end
|
||||||
|
luci.ltn12.pump.all(chunk, luci.http.write)
|
||||||
|
else
|
||||||
|
if not first then
|
||||||
|
first = true
|
||||||
|
luci.http.prepare_content("text/plain")
|
||||||
|
end
|
||||||
|
luci.ltn12.pump.all(chunk, luci.http.write)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...")
|
||||||
|
local msg = dk.images:get({query = {names = names}}, cb)
|
||||||
|
|
||||||
|
if msg.code ~= 200 then
|
||||||
|
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
||||||
|
success = false
|
||||||
|
else
|
||||||
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
o = s:option(Button, "load")
|
||||||
|
o.inputtitle= translate("Load")
|
||||||
|
o.template = "dockerman/images_load"
|
||||||
|
o.inputstyle = "add"
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
|
@ -3,128 +3,150 @@ 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
|
||||||
for i, v in ipairs(networks) do
|
return nil
|
||||||
local index = v.Created .. v.Id
|
end
|
||||||
data[index]={}
|
|
||||||
data[index]["_selected"] = 0
|
for i, v in ipairs(networks) do
|
||||||
data[index]["_id"] = v.Id:sub(1,12)
|
local index = v.Created .. v.Id
|
||||||
data[index]["_name"] = v.Name
|
|
||||||
data[index]["_driver"] = v.Driver
|
data[index]={}
|
||||||
if v.Driver == "bridge" then
|
data[index]["_selected"] = 0
|
||||||
data[index]["_interface"] = v.Options["com.docker.network.bridge.name"]
|
data[index]["_id"] = v.Id:sub(1,12)
|
||||||
elseif v.Driver == "macvlan" then
|
data[index]["_name"] = v.Name
|
||||||
data[index]["_interface"] = v.Options.parent
|
data[index]["_driver"] = v.Driver
|
||||||
end
|
|
||||||
data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
|
if v.Driver == "bridge" then
|
||||||
data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil
|
data[index]["_interface"] = v.Options["com.docker.network.bridge.name"]
|
||||||
end
|
elseif v.Driver == "macvlan" then
|
||||||
return data
|
data[index]["_interface"] = v.Options.parent
|
||||||
|
end
|
||||||
|
|
||||||
|
data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
|
||||||
|
data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return data
|
||||||
end
|
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
|
||||||
|
o.write = function(self, section, value)
|
||||||
|
network_list[section]._selected = value
|
||||||
end
|
end
|
||||||
|
|
||||||
network_name = network_table:option(DummyValue, "_name", translate("Network Name"))
|
o = s:option(DummyValue, "_id", translate("ID"))
|
||||||
network_driver = network_table:option(DummyValue, "_driver", translate("Driver"))
|
|
||||||
network_interface = network_table:option(DummyValue, "_interface", translate("Parent Interface"))
|
|
||||||
network_subnet = network_table:option(DummyValue, "_subnet", translate("Subnet"))
|
|
||||||
network_gateway = network_table:option(DummyValue, "_gateway", translate("Gateway"))
|
|
||||||
|
|
||||||
network_selecter.write = function(self, section, value)
|
o = s:option(DummyValue, "_name", translate("Network Name"))
|
||||||
network_list[section]._selected = value
|
|
||||||
|
o = s:option(DummyValue, "_driver", translate("Driver"))
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "_interface", translate("Parent Interface"))
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "_subnet", translate("Subnet"))
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "_gateway", translate("Gateway"))
|
||||||
|
|
||||||
|
s = m:section(SimpleSection)
|
||||||
|
s.template = "dockerman/apply_widget"
|
||||||
|
s.err = docker:read_status()
|
||||||
|
s.err = s.err and s.err:gsub("\n","<br>"):gsub(" "," ")
|
||||||
|
if s.err then
|
||||||
|
docker:clear_status()
|
||||||
end
|
end
|
||||||
|
|
||||||
docker_status = m:section(SimpleSection)
|
s = m:section(Table,{{}})
|
||||||
docker_status.template = "dockerman/apply_widget"
|
s.notitle=true
|
||||||
docker_status.err=docker:read_status()
|
s.rowcolors=false
|
||||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
s.template="cbi/nullsection"
|
||||||
if docker_status.err then docker:clear_status() end
|
|
||||||
|
|
||||||
action = m:section(Table,{{}})
|
o = s:option(Button, "_new")
|
||||||
action.notitle=true
|
o.inputtitle= translate("New")
|
||||||
action.rowcolors=false
|
o.template = "dockerman/cbi/inlinebutton"
|
||||||
action.template="cbi/nullsection"
|
o.notitle=true
|
||||||
btnnew=action:option(Button, "_new")
|
o.inputstyle = "add"
|
||||||
btnnew.inputtitle= translate("New")
|
o.forcewrite = true
|
||||||
btnnew.template = "dockerman/cbi/inlinebutton"
|
o.write = function(self, section)
|
||||||
btnnew.notitle=true
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
|
||||||
btnnew.inputstyle = "add"
|
|
||||||
btnnew.forcewrite = true
|
|
||||||
btnnew.write = function(self, section)
|
|
||||||
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
|
||||||
local network_selected = {}
|
o.write = function(self, section)
|
||||||
local network_name_selected = {}
|
local network_selected = {}
|
||||||
local network_driver_selected = {}
|
local network_name_selected = {}
|
||||||
-- 遍历table中sectionid
|
local network_driver_selected = {}
|
||||||
local network_table_sids = network_table:cfgsections()
|
|
||||||
for _, network_table_sid in ipairs(network_table_sids) do
|
for k in pairs(network_list) do
|
||||||
-- 得到选中项的名字
|
if network_list[k]._selected == 1 then
|
||||||
if network_list[network_table_sid]._selected == 1 then
|
network_selected[#network_selected + 1] = network_list[k]._id
|
||||||
network_selected[#network_selected+1] = network_list[network_table_sid]._id --network_name:cfgvalue(network_table_sid)
|
network_name_selected[#network_name_selected + 1] = network_list[k]._name
|
||||||
network_name_selected[#network_name_selected+1] = network_list[network_table_sid]._name
|
network_driver_selected[#network_driver_selected + 1] = network_list[k]._driver
|
||||||
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
|
|
||||||
docker:append_status("Networks: " .. "remove" .. " " .. net .. "...")
|
for ii, net in ipairs(network_selected) do
|
||||||
local res = dk.networks["remove"](dk, {id = net})
|
docker:append_status("Networks: " .. "remove" .. " " .. net .. "...")
|
||||||
if res and res.code >= 300 then
|
local res = dk.networks["remove"](dk, {id = net})
|
||||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
|
|
||||||
success = false
|
if res and res.code >= 300 then
|
||||||
else
|
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
|
||||||
docker:append_status("done\n")
|
success = false
|
||||||
if network_driver_selected[ii] == "macvlan" then
|
else
|
||||||
docker.remove_macvlan_interface(network_name_selected[ii])
|
docker:append_status("done\n")
|
||||||
end
|
if network_driver_selected[ii] == "macvlan" then
|
||||||
end
|
docker.remove_macvlan_interface(network_name_selected[ii])
|
||||||
end
|
end
|
||||||
if success then
|
end
|
||||||
docker:clear_status()
|
end
|
||||||
end
|
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
|
if success then
|
||||||
end
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,219 +3,244 @@ 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
|
||||||
local name = data.name
|
local name = data.name
|
||||||
local driver = data.dirver
|
local driver = data.dirver
|
||||||
|
|
||||||
local internal = data.internal == 1 and true or false
|
local internal = data.internal == 1 and true or false
|
||||||
|
|
||||||
local subnet = data.subnet
|
local subnet = data.subnet
|
||||||
local gateway = data.gateway
|
local gateway = data.gateway
|
||||||
local ip_range = data.ip_range
|
local ip_range = data.ip_range
|
||||||
|
|
||||||
local aux_address = {}
|
local aux_address = {}
|
||||||
local tmp = data.aux_address or {}
|
local tmp = data.aux_address or {}
|
||||||
for i,v in ipairs(tmp) do
|
for i,v in ipairs(tmp) do
|
||||||
_,_,k1,v1 = v:find("(.-)=(.+)")
|
_,_,k1,v1 = v:find("(.-)=(.+)")
|
||||||
aux_address[k1] = v1
|
aux_address[k1] = v1
|
||||||
end
|
end
|
||||||
|
|
||||||
local options = {}
|
local options = {}
|
||||||
tmp = data.options or {}
|
tmp = data.options or {}
|
||||||
for i,v in ipairs(tmp) do
|
for i,v in ipairs(tmp) do
|
||||||
_,_,k1,v1 = v:find("(.-)=(.+)")
|
_,_,k1,v1 = v:find("(.-)=(.+)")
|
||||||
options[k1] = v1
|
options[k1] = v1
|
||||||
end
|
end
|
||||||
|
|
||||||
local ipv6 = data.ipv6 == 1 and true or false
|
local ipv6 = data.ipv6 == 1 and true or false
|
||||||
|
|
||||||
local create_body={
|
local create_body = {
|
||||||
Name = name,
|
Name = name,
|
||||||
Driver = driver,
|
Driver = driver,
|
||||||
EnableIPv6 = ipv6,
|
EnableIPv6 = ipv6,
|
||||||
IPAM = {
|
IPAM = {
|
||||||
Driver= "default"
|
Driver= "default"
|
||||||
},
|
},
|
||||||
Internal = internal
|
Internal = internal
|
||||||
}
|
}
|
||||||
|
|
||||||
if subnet or gateway or ip_range then
|
if subnet or gateway or ip_range then
|
||||||
create_body["IPAM"]["Config"] = {
|
create_body["IPAM"]["Config"] = {
|
||||||
{
|
{
|
||||||
Subnet = subnet,
|
Subnet = subnet,
|
||||||
Gateway = gateway,
|
Gateway = gateway,
|
||||||
IPRange = ip_range,
|
IPRange = ip_range,
|
||||||
AuxAddress = aux_address,
|
AuxAddress = aux_address,
|
||||||
AuxiliaryAddresses = aux_address
|
AuxiliaryAddresses = aux_address
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
if driver == "macvlan" then
|
|
||||||
create_body["Options"] = {
|
|
||||||
macvlan_mode = data.macvlan_mode,
|
|
||||||
parent = data.parent
|
|
||||||
}
|
|
||||||
elseif driver == "ipvlan" then
|
|
||||||
create_body["Options"] = {
|
|
||||||
ipvlan_mode = data.ipvlan_mode
|
|
||||||
}
|
|
||||||
elseif driver == "overlay" then
|
|
||||||
create_body["Ingress"] = data.ingerss == 1 and true or false
|
|
||||||
end
|
|
||||||
|
|
||||||
if ipv6 and data.subnet6 and data.subnet6 then
|
if driver == "macvlan" then
|
||||||
if type(create_body["IPAM"]["Config"]) ~= "table" then
|
create_body["Options"] = {
|
||||||
create_body["IPAM"]["Config"] = {}
|
macvlan_mode = data.macvlan_mode,
|
||||||
end
|
parent = data.parent
|
||||||
local index = #create_body["IPAM"]["Config"]
|
}
|
||||||
create_body["IPAM"]["Config"][index+1] = {
|
elseif driver == "ipvlan" then
|
||||||
Subnet = data.subnet6,
|
create_body["Options"] = {
|
||||||
Gateway = data.gateway6
|
ipvlan_mode = data.ipvlan_mode
|
||||||
}
|
}
|
||||||
end
|
elseif driver == "overlay" then
|
||||||
|
create_body["Ingress"] = data.ingerss == 1 and true or false
|
||||||
|
end
|
||||||
|
|
||||||
if next(options) ~= nil then
|
if ipv6 and data.subnet6 and data.subnet6 then
|
||||||
create_body["Options"] = create_body["Options"] or {}
|
if type(create_body["IPAM"]["Config"]) ~= "table" then
|
||||||
for k, v in pairs(options) do
|
create_body["IPAM"]["Config"] = {}
|
||||||
create_body["Options"][k] = v
|
end
|
||||||
end
|
local index = #create_body["IPAM"]["Config"]
|
||||||
end
|
create_body["IPAM"]["Config"][index+1] = {
|
||||||
|
Subnet = data.subnet6,
|
||||||
|
Gateway = data.gateway6
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
create_body = docker.clear_empty_tables(create_body)
|
if next(options) ~= nil then
|
||||||
docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
|
create_body["Options"] = create_body["Options"] or {}
|
||||||
local res = dk.networks:create({body = create_body})
|
for k, v in pairs(options) do
|
||||||
if res and res.code == 201 then
|
create_body["Options"][k] = v
|
||||||
docker:write_status("Network: " .. "create macvlan interface...")
|
end
|
||||||
res = dk.networks:inspect({ name = create_body.Name })
|
end
|
||||||
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]
|
create_body = docker.clear_empty_tables(create_body)
|
||||||
and res.body.IPAM.Config[1].Gateway and res.body.IPAM.Config[1].Subnet then
|
docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
|
||||||
docker.create_macvlan_interface(data.name, data.parent, res.body.IPAM.Config[1].Gateway, res.body.IPAM.Config[1].Subnet)
|
|
||||||
end
|
local res = dk.networks:create({
|
||||||
docker:clear_status()
|
body = create_body
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
|
})
|
||||||
else
|
|
||||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
|
if res and res.code == 201 then
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
|
docker:write_status("Network: " .. "create macvlan interface...")
|
||||||
end
|
res = dk.networks:inspect({
|
||||||
end
|
name = create_body.Name
|
||||||
|
})
|
||||||
|
|
||||||
|
if driver == "macvlan" and
|
||||||
|
data.op_macvlan ~= 0 and
|
||||||
|
res.code == 200 and
|
||||||
|
res.body and
|
||||||
|
res.body.IPAM and
|
||||||
|
res.body.IPAM.Config and
|
||||||
|
res.body.IPAM.Config[1] and
|
||||||
|
res.body.IPAM.Config[1].Gateway and
|
||||||
|
res.body.IPAM.Config[1].Subnet then
|
||||||
|
|
||||||
|
docker.create_macvlan_interface(data.name,
|
||||||
|
data.parent,
|
||||||
|
res.body.IPAM.Config[1].Gateway,
|
||||||
|
res.body.IPAM.Config[1].Subnet)
|
||||||
|
end
|
||||||
|
|
||||||
|
docker:clear_status()
|
||||||
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
|
||||||
|
else
|
||||||
|
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
|
||||||
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
|
@ -3,26 +3,29 @@ 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"}
|
||||||
for i=1, 5 do
|
for i=1, 5 do
|
||||||
if byte > 1024 and i < 5 then
|
if byte > 1024 and i < 5 then
|
||||||
byte = byte / 1024
|
byte = byte / 1024
|
||||||
else
|
else
|
||||||
return string.format("%.2f %s", byte, suff[i])
|
return string.format("%.2f %s", byte, suff[i])
|
||||||
end
|
end
|
||||||
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,124 +34,122 @@ 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 (require "luci.model.docker").new():_ping().code == 200 then
|
|
||||||
local dk = docker.new()
|
|
||||||
containers_list = dk.containers:list({query = {all=true}}).body
|
|
||||||
local images_list = dk.images:list().body
|
|
||||||
local vol = dk.volumes:list()
|
|
||||||
local volumes_list = vol and vol.body and vol.body.Volumes or {}
|
|
||||||
local networks_list = dk.networks:list().body or {}
|
|
||||||
local docker_info = dk:info()
|
|
||||||
-- docker_info_table['0OperatingSystem']._value = docker_info.body.OperatingSystem
|
|
||||||
-- docker_info_table['1Architecture']._value = docker_info.body.Architecture
|
|
||||||
-- docker_info_table['2KernelVersion']._value = docker_info.body.KernelVersion
|
|
||||||
docker_info_table['3ServerVersion']._value = docker_info.body.ServerVersion
|
|
||||||
docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"]
|
|
||||||
docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU)
|
|
||||||
docker_info_table['6MemTotal']._value = byte_format(docker_info.body.MemTotal)
|
|
||||||
if docker_info.body.DockerRootDir then
|
|
||||||
local statvfs = nixio.fs.statvfs(docker_info.body.DockerRootDir)
|
|
||||||
local size = statvfs and (statvfs.bavail * statvfs.bsize) or 0
|
|
||||||
docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(byte_format(size)) .. " " .. translate("Available") .. ")"
|
|
||||||
end
|
|
||||||
docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress
|
|
||||||
for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do
|
|
||||||
docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v)
|
|
||||||
end
|
|
||||||
|
|
||||||
s.images_used = 0
|
if docker.new():_ping().code == 200 then
|
||||||
for i, v in ipairs(images_list) do
|
local dk = docker.new()
|
||||||
for ci,cv in ipairs(containers_list) do
|
local containers_list = dk.containers:list({query = {all=true}}).body
|
||||||
if v.Id == cv.ImageID then
|
local images_list = dk.images:list().body
|
||||||
s.images_used = s.images_used + 1
|
local vol = dk.volumes:list()
|
||||||
break
|
local volumes_list = vol and vol.body and vol.body.Volumes or {}
|
||||||
end
|
local networks_list = dk.networks:list().body or {}
|
||||||
end
|
local docker_info = dk:info()
|
||||||
end
|
|
||||||
s.containers_running = tostring(docker_info.body.ContainersRunning)
|
docker_info_table['3ServerVersion']._value = docker_info.body.ServerVersion
|
||||||
s.images_used = tostring(s.images_used)
|
docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"]
|
||||||
s.containers_total = tostring(docker_info.body.Containers)
|
docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU)
|
||||||
s.images_total = tostring(#images_list)
|
docker_info_table['6MemTotal']._value = byte_format(docker_info.body.MemTotal)
|
||||||
s.networks_total = tostring(#networks_list)
|
if docker_info.body.DockerRootDir then
|
||||||
s.volumes_total = tostring(#volumes_list)
|
local statvfs = nixio.fs.statvfs(docker_info.body.DockerRootDir)
|
||||||
|
local size = statvfs and (statvfs.bavail * statvfs.bsize) or 0
|
||||||
|
docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(byte_format(size)) .. " " .. translate("Available") .. ")"
|
||||||
|
end
|
||||||
|
|
||||||
|
docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress
|
||||||
|
for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do
|
||||||
|
docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v)
|
||||||
|
end
|
||||||
|
|
||||||
|
s.images_used = 0
|
||||||
|
for i, v in ipairs(images_list) do
|
||||||
|
for ci,cv in ipairs(containers_list) do
|
||||||
|
if v.Id == cv.ImageID then
|
||||||
|
s.images_used = s.images_used + 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
s.containers_running = tostring(docker_info.body.ContainersRunning)
|
||||||
|
s.images_used = tostring(s.images_used)
|
||||||
|
s.containers_total = tostring(docker_info.body.Containers)
|
||||||
|
s.images_total = tostring(#images_list)
|
||||||
|
s.networks_total = tostring(#networks_list)
|
||||||
|
s.volumes_total = tostring(#volumes_list)
|
||||||
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"))
|
|
||||||
-- allowed_container.placeholder = "container name_or_id"
|
|
||||||
if containers_list then
|
|
||||||
for i, v in ipairs(containers_list) do
|
|
||||||
if v.State == "running" and v.NetworkSettings and v.NetworkSettings.Networks and v.NetworkSettings.Networks.bridge and v.NetworkSettings.Networks.bridge.IPAddress then
|
|
||||||
allowed_container:value(v.Id:sub(1,12), v.Names[1]:sub(2) .. " | " .. v.NetworkSettings.Networks.bridge.IPAddress)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local dockerd_enable = section_dockerman:taboption("daemon", Flag, "daemon_ea", translate("Enable"))
|
o = s:option(Value, "bip",
|
||||||
dockerd_enable.enabled = "true"
|
translate("Default bridge"),
|
||||||
dockerd_enable.rmempty = true
|
translate("Configure the default bridge network"))
|
||||||
local data_root = section_dockerman:taboption("daemon", Value, "daemon_data_root", translate("Docker Root Dir"))
|
o.placeholder = "172.17.0.1/16"
|
||||||
data_root.placeholder = "/opt/docker/"
|
o.default = "172.17.0.1/16"
|
||||||
local registry_mirrors = section_dockerman:taboption("daemon", DynamicList, "daemon_registry_mirrors", translate("Registry Mirrors"))
|
o.datatype = "ipaddr"
|
||||||
registry_mirrors:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com")
|
o:depends("remote_endpoint", 0)
|
||||||
|
|
||||||
local log_level = section_dockerman:taboption("daemon", ListValue, "daemon_log_level", translate("Log Level"), translate('Set the logging level'))
|
o = s:option(DynamicList, "registry_mirrors",
|
||||||
log_level:value("debug", "debug")
|
translate("Registry Mirrors"))
|
||||||
log_level:value("info", "info")
|
o:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com")
|
||||||
log_level:value("warn", "warn")
|
o:depends("remote_endpoint", 0)
|
||||||
log_level:value("error", "error")
|
|
||||||
log_level:value("fatal", "fatal")
|
o = s:option(ListValue, "log_level",
|
||||||
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'))
|
translate("Log Level"),
|
||||||
hosts:value("unix:///var/run/docker.sock", "unix:///var/run/docker.sock")
|
translate('Set the logging level'))
|
||||||
hosts:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375")
|
o:value("debug", "debug")
|
||||||
hosts.rmempty = true
|
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
|
||||||
return map_dockerman
|
|
||||||
|
return m
|
||||||
|
|
|
@ -3,114 +3,140 @@ 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
|
||||||
for vi, vv in ipairs(cv.Mounts) do
|
break
|
||||||
if v.Name == vv.Name then
|
end
|
||||||
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
|
for vi, vv in ipairs(cv.Mounts) do
|
||||||
'<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2)..'</a>'
|
if v.Name == vv.Name then
|
||||||
end
|
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
|
||||||
end
|
'<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2)..'</a>'
|
||||||
end
|
end
|
||||||
data[index]["_driver"] = v.Driver
|
end
|
||||||
data[index]["_mountpoint"] = nil
|
end
|
||||||
for v1 in v.Mountpoint:gmatch('[^/]+') do
|
data[index]["_driver"] = v.Driver
|
||||||
if v1 == index then
|
data[index]["_mountpoint"] = nil
|
||||||
data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..."
|
|
||||||
else
|
for v1 in v.Mountpoint:gmatch('[^/]+') do
|
||||||
data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1
|
if v1 == index then
|
||||||
end
|
data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..."
|
||||||
end
|
else
|
||||||
data[index]["_created"] = v.CreatedAt
|
data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1
|
||||||
end
|
end
|
||||||
return data
|
end
|
||||||
|
data[index]["_created"] = v.CreatedAt
|
||||||
|
end
|
||||||
|
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
res = dk.volumes:list()
|
||||||
|
if res.code <300 then
|
||||||
|
volumes = res.body.Volumes
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
res = dk.containers:list({
|
||||||
|
query = {
|
||||||
|
all=true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if res.code <300 then
|
||||||
|
containers = res.body
|
||||||
|
else
|
||||||
|
return
|
||||||
end
|
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_list[section]._selected = value
|
||||||
|
|
||||||
volume_id = volume_table:option(DummyValue, "_name", translate("Name"))
|
|
||||||
volume_table:option(DummyValue, "_driver", translate("Driver"))
|
|
||||||
volume_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
|
|
||||||
volume_table:option(DummyValue, "_mountpoint", translate("Mount Point"))
|
|
||||||
volume_table:option(DummyValue, "_created", translate("Created"))
|
|
||||||
volume_selecter.write = function(self, section, value)
|
|
||||||
volume_list[section]._selected = value
|
|
||||||
end
|
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)
|
||||||
local volume_selected = {}
|
s.template = "dockerman/apply_widget"
|
||||||
-- 遍历table中sectionid
|
s.err=docker:read_status()
|
||||||
local volume_table_sids = volume_table:cfgsections()
|
s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ")
|
||||||
for _, volume_table_sid in ipairs(volume_table_sids) do
|
if s.err then
|
||||||
-- 得到选中项的名字
|
docker:clear_status()
|
||||||
if volume_list[volume_table_sid]._selected == 1 then
|
|
||||||
-- volume_selected[#volume_selected+1] = volume_id:cfgvalue(volume_table_sid)
|
|
||||||
volume_selected[#volume_selected+1] = volume_table_sid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if next(volume_selected) ~= nil then
|
|
||||||
local success = true
|
|
||||||
docker:clear_status()
|
|
||||||
for _,vol in ipairs(volume_selected) do
|
|
||||||
docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...")
|
|
||||||
local msg = dk.volumes["remove"](dk, {id = vol})
|
|
||||||
if msg.code ~= 204 then
|
|
||||||
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
|
||||||
success = false
|
|
||||||
else
|
|
||||||
docker:append_status("done\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if success then docker:clear_status() end
|
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes"))
|
|
||||||
end
|
|
||||||
end
|
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 = {}
|
||||||
|
|
||||||
|
for k in pairs(volume_list) do
|
||||||
|
if volume_list[k]._selected == 1 then
|
||||||
|
volume_selected[#volume_selected+1] = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if next(volume_selected) ~= nil then
|
||||||
|
local success = true
|
||||||
|
docker:clear_status()
|
||||||
|
for _,vol in ipairs(volume_selected) do
|
||||||
|
docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...")
|
||||||
|
local msg = dk.volumes["remove"](dk, {id = vol})
|
||||||
|
if msg.code ~= 204 then
|
||||||
|
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
||||||
|
success = false
|
||||||
|
else
|
||||||
|
docker:append_status("done\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if success then
|
||||||
|
docker:clear_status()
|
||||||
|
end
|
||||||
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
|
@ -3,276 +3,347 @@ 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
|
|
||||||
_docker:append_status("done\n")
|
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
|
||||||
else
|
_docker:append_status("done\n")
|
||||||
res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)
|
else
|
||||||
end
|
res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)
|
||||||
new_image_id = self.images:inspect({name = image_name}).body.Id
|
end
|
||||||
return new_image_id, res
|
|
||||||
|
new_image_id = self.images:inspect({name = image_name}).body.Id
|
||||||
|
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
|
end
|
||||||
for i, v in ipairs(t1) do
|
|
||||||
if t1[i] ~= t2[i] then return false end
|
if not t2 then
|
||||||
end
|
return false
|
||||||
return true
|
end
|
||||||
|
|
||||||
|
if #t1 ~= #t2 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for i, v in ipairs(t1) do
|
||||||
|
if t1[i] ~= t2[i] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
end
|
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
|
||||||
local res = {}
|
end
|
||||||
for _, v1 in ipairs(t1) do
|
|
||||||
local found = false
|
if not t2 or next(t2) == nil then
|
||||||
for _, v2 in ipairs(t2) do
|
return t1
|
||||||
if v1 == v2 then
|
end
|
||||||
found= true
|
|
||||||
break
|
local res = {}
|
||||||
end
|
for _, v1 in ipairs(t1) do
|
||||||
end
|
local found = false
|
||||||
if not found then
|
for _, v2 in ipairs(t2) do
|
||||||
table.insert(res, v1)
|
if v1 == v2 then
|
||||||
end
|
found= true
|
||||||
end
|
break
|
||||||
return next(res) == nil and nil or res
|
end
|
||||||
|
end
|
||||||
|
if not found then
|
||||||
|
table.insert(res, v1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
local res = {}
|
end
|
||||||
for k1, v1 in pairs(t1) do
|
|
||||||
local found = false
|
|
||||||
for k2, v2 in ipairs(t2) do
|
|
||||||
if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then
|
|
||||||
found= true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not found then
|
|
||||||
res[k1] = v1
|
|
||||||
-- if v1 and type(v1) == "table" then
|
|
||||||
-- if next(v1) == nil then
|
|
||||||
-- res[k1] = { k = 'v' }
|
|
||||||
-- else
|
|
||||||
-- res[k1] = v1
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return next(res) ~= nil and res or nil
|
if not t2 or next(t2) == nil then
|
||||||
|
return t1
|
||||||
|
end
|
||||||
|
|
||||||
|
local res = {}
|
||||||
|
for k1, v1 in pairs(t1) do
|
||||||
|
local found = false
|
||||||
|
for k2, v2 in ipairs(t2) do
|
||||||
|
if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then
|
||||||
|
found= true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not found then
|
||||||
|
res[k1] = v1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return next(res) ~= nil and res or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
_docker.clear_empty_tables = function ( t )
|
_docker.clear_empty_tables = function ( t )
|
||||||
local k, v
|
local k, v
|
||||||
if next(t) == nil then
|
|
||||||
t = nil
|
if next(t) == nil then
|
||||||
else
|
t = nil
|
||||||
for k, v in pairs(t) do
|
else
|
||||||
if type(v) == 'table' then
|
for k, v in pairs(t) do
|
||||||
t[k] = _docker.clear_empty_tables(v)
|
if type(v) == 'table' then
|
||||||
end
|
t[k] = _docker.clear_empty_tables(v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return t
|
end
|
||||||
|
|
||||||
|
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 table_equal(config.Cmd, image_config.Cmd) then config.Cmd = nil end
|
|
||||||
if table_equal(config.Entrypoint, image_config.Entrypoint) then config.Entrypoint = nil end
|
|
||||||
if table_equal(config.ExposedPorts, image_config.ExposedPorts) then config.ExposedPorts = nil end
|
|
||||||
config.Env = table_subtract(config.Env, image_config.Env)
|
|
||||||
config.Labels = table_subtract(config.Labels, image_config.Labels)
|
|
||||||
config.Volumes = map_subtract(config.Volumes, image_config.Volumes)
|
|
||||||
-- subtract ports exposed in image from container
|
|
||||||
if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then
|
|
||||||
config.ExposedPorts = {}
|
|
||||||
for p, v in pairs(old_host_config.PortBindings) do
|
|
||||||
config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- handle network config, we need only one network, extras need to network connect action
|
if config.WorkingDir == image_config.WorkingDir then
|
||||||
local network_setting = {}
|
config.WorkingDir = ""
|
||||||
local multi_network = false
|
end
|
||||||
local extra_network = {}
|
|
||||||
for k, v in pairs(old_network_setting) do
|
|
||||||
if multi_network then
|
|
||||||
extra_network[k] = v
|
|
||||||
else
|
|
||||||
network_setting[k] = v
|
|
||||||
end
|
|
||||||
multi_network = true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- handle hostconfig
|
if config.User == image_config.User then
|
||||||
local host_config = old_host_config
|
config.User = ""
|
||||||
-- if host_config.PortBindings and next(host_config.PortBindings) == nil then host_config.PortBindings = nil end
|
end
|
||||||
-- host_config.LogConfig = nil
|
|
||||||
host_config.Mounts = {}
|
|
||||||
-- for volumes
|
|
||||||
for i, v in ipairs(container_config.Mounts) do
|
|
||||||
if v.Type == "volume" then
|
|
||||||
table.insert(host_config.Mounts, {
|
|
||||||
Type = v.Type,
|
|
||||||
Target = v.Destination,
|
|
||||||
Source = v.Source:match("([^/]+)\/_data"),
|
|
||||||
BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil,
|
|
||||||
ReadOnly = not v.RW
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
if table_equal(config.Cmd, image_config.Cmd) then
|
||||||
|
config.Cmd = nil
|
||||||
|
end
|
||||||
|
|
||||||
-- merge configs
|
if table_equal(config.Entrypoint, image_config.Entrypoint) then
|
||||||
local create_body = config
|
config.Entrypoint = nil
|
||||||
create_body["HostConfig"] = host_config
|
end
|
||||||
create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
|
|
||||||
create_body = _docker.clear_empty_tables(create_body) or {}
|
if table_equal(config.ExposedPorts, image_config.ExposedPorts) then
|
||||||
extra_network = _docker.clear_empty_tables(extra_network) or {}
|
config.ExposedPorts = nil
|
||||||
return create_body, extra_network
|
end
|
||||||
|
|
||||||
|
config.Env = table_subtract(config.Env, image_config.Env)
|
||||||
|
config.Labels = table_subtract(config.Labels, image_config.Labels)
|
||||||
|
config.Volumes = map_subtract(config.Volumes, image_config.Volumes)
|
||||||
|
|
||||||
|
if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then
|
||||||
|
config.ExposedPorts = {}
|
||||||
|
for p, v in pairs(old_host_config.PortBindings) do
|
||||||
|
config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local network_setting = {}
|
||||||
|
local multi_network = false
|
||||||
|
local extra_network = {}
|
||||||
|
|
||||||
|
for k, v in pairs(old_network_setting) do
|
||||||
|
if multi_network then
|
||||||
|
extra_network[k] = v
|
||||||
|
else
|
||||||
|
network_setting[k] = v
|
||||||
|
end
|
||||||
|
multi_network = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local host_config = old_host_config
|
||||||
|
host_config.Mounts = {}
|
||||||
|
for i, v in ipairs(container_config.Mounts) do
|
||||||
|
if v.Type == "volume" then
|
||||||
|
table.insert(host_config.Mounts, {
|
||||||
|
Type = v.Type,
|
||||||
|
Target = v.Destination,
|
||||||
|
Source = v.Source:match("([^/]+)\/_data"),
|
||||||
|
BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil,
|
||||||
|
ReadOnly = not v.RW
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local create_body = config
|
||||||
|
create_body["HostConfig"] = host_config
|
||||||
|
create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
|
||||||
|
create_body = _docker.clear_empty_tables(create_body) or {}
|
||||||
|
extra_network = _docker.clear_empty_tables(extra_network) or {}
|
||||||
|
|
||||||
|
return create_body, extra_network
|
||||||
end
|
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})
|
|
||||||
if container_info.code > 300 and type(container_info.body) == "table" then
|
|
||||||
return container_info
|
|
||||||
end
|
|
||||||
local image_name = container_info.body.Config.Image
|
|
||||||
if not image_name:match(".-:.+") then image_name = image_name .. ":latest" end
|
|
||||||
local old_image_id = container_info.body.Image
|
|
||||||
local container_name = container_info.body.Name:sub(2)
|
|
||||||
|
|
||||||
local image_id, res = update_image(self, image_name)
|
local container_info = self.containers:inspect({id = request.id})
|
||||||
if res and res.code ~= 200 then return res end
|
|
||||||
if image_id == old_image_id then
|
|
||||||
return {code = 305, body = {message = "Already up to date"}}
|
|
||||||
end
|
|
||||||
|
|
||||||
_docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...")
|
if container_info.code > 300 and type(container_info.body) == "table" then
|
||||||
res = self.containers:stop({name = container_name})
|
return container_info
|
||||||
if res and res.code < 305 then
|
end
|
||||||
_docker:append_status("done\n")
|
|
||||||
else
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
_docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...")
|
local image_name = container_info.body.Config.Image
|
||||||
res = self.containers:rename({name = container_name, query = { name = container_name .. "_old" }})
|
if not image_name:match(".-:.+") then
|
||||||
if res and res.code < 300 then
|
image_name = image_name .. ":latest"
|
||||||
_docker:append_status("done\n")
|
end
|
||||||
else
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
-- handle config
|
local old_image_id = container_info.body.Image
|
||||||
local image_config = self.images:inspect({id = old_image_id}).body.Config
|
local container_name = container_info.body.Name:sub(2)
|
||||||
local create_body, extra_network = get_config(container_info.body, image_config)
|
|
||||||
|
|
||||||
-- create new container
|
local image_id, res = update_image(self, image_name)
|
||||||
_docker:append_status("Container: Create" .. " " .. container_name .. "...")
|
if res and res.code ~= 200 then
|
||||||
create_body = _docker.clear_empty_tables(create_body)
|
return res
|
||||||
res = self.containers:create({name = container_name, body = create_body})
|
end
|
||||||
if res and res.code > 300 then return res end
|
|
||||||
_docker:append_status("done\n")
|
|
||||||
|
|
||||||
-- extra networks need to network connect action
|
if image_id == old_image_id then
|
||||||
for k, v in pairs(extra_network) do
|
return {code = 305, body = {message = "Already up to date"}}
|
||||||
_docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
|
end
|
||||||
res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}})
|
|
||||||
if res.code > 300 then return res end
|
|
||||||
|
|
||||||
_docker:append_status("done\n")
|
_docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...")
|
||||||
end
|
res = self.containers:stop({name = container_name})
|
||||||
_docker:clear_status()
|
if res and res.code < 305 then
|
||||||
return res
|
_docker:append_status("done\n")
|
||||||
|
else
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
_docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...")
|
||||||
|
res = self.containers:rename({name = container_name, query = { name = container_name .. "_old" }})
|
||||||
|
if res and res.code < 300 then
|
||||||
|
_docker:append_status("done\n")
|
||||||
|
else
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
local image_config = self.images:inspect({id = old_image_id}).body.Config
|
||||||
|
local create_body, extra_network = get_config(container_info.body, image_config)
|
||||||
|
|
||||||
|
-- create new container
|
||||||
|
_docker:append_status("Container: Create" .. " " .. container_name .. "...")
|
||||||
|
create_body = _docker.clear_empty_tables(create_body)
|
||||||
|
res = self.containers:create({name = container_name, body = create_body})
|
||||||
|
if res and res.code > 300 then
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
_docker:append_status("done\n")
|
||||||
|
|
||||||
|
-- extra networks need to network connect action
|
||||||
|
for k, v in pairs(extra_network) do
|
||||||
|
_docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
|
||||||
|
res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}})
|
||||||
|
if res.code > 300 then
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
_docker:append_status("done\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
_docker:clear_status()
|
||||||
|
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
|
||||||
local old_image_id = container_info.body.Image
|
return nil
|
||||||
local image_config = self.images:inspect({id = old_image_id}).body.Config
|
end
|
||||||
return get_config(container_info.body, image_config)
|
|
||||||
|
local old_image_id = container_info.body.Image
|
||||||
|
local image_config = self.images:inspect({id = old_image_id}).body.Config
|
||||||
|
|
||||||
|
return get_config(container_info.body, image_config)
|
||||||
end
|
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
|
||||||
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
|
port = uci:get("dockerd", "globals", "remote_port") or nil
|
||||||
local _new = docker.new(options)
|
else
|
||||||
_new.options.status_path = uci:get("dockerman", "local", "status_path")
|
socket_path = uci:get("dockerd", "globals", "socket_path") or "/var/run/docker.sock"
|
||||||
_new.containers_upgrade = upgrade
|
end
|
||||||
_new.containers_duplicate_config = duplicate_config
|
|
||||||
return _new
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
local _new = docker.new(_docker.options)
|
||||||
|
_new.containers_upgrade = upgrade
|
||||||
|
_new.containers_duplicate_config = duplicate_config
|
||||||
|
|
||||||
|
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
|
||||||
local file_docker_action_status=io.open(self.options.status_path, "a+")
|
return
|
||||||
file_docker_action_status:write(val)
|
end
|
||||||
file_docker_action_status:close()
|
local file_docker_action_status=io.open(self.options.status_path, "a+")
|
||||||
|
file_docker_action_status:write(val)
|
||||||
|
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
|
||||||
local file_docker_action_status=io.open(self.options.status_path, "w+")
|
return
|
||||||
file_docker_action_status:write(val)
|
end
|
||||||
file_docker_action_status:close()
|
local file_docker_action_status=io.open(self.options.status_path, "w+")
|
||||||
|
file_docker_action_status:write(val)
|
||||||
|
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)
|
||||||
res.body = res.body or {}
|
res.body = res.body or {}
|
||||||
while true do
|
while true do
|
||||||
local chunk = source()
|
local chunk = source()
|
||||||
if chunk then
|
if chunk then
|
||||||
--standard output to res.body
|
--standard output to res.body
|
||||||
table.insert(res.body, chunk)
|
table.insert(res.body, chunk)
|
||||||
handler(chunk)
|
handler(chunk)
|
||||||
else
|
else
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--{"status":"Pulling from library\/debian","id":"latest"}
|
--{"status":"Pulling from library\/debian","id":"latest"}
|
||||||
|
@ -284,114 +355,128 @@ end
|
||||||
--{"status":"Digest: sha256:a63d0b2ecbd723da612abf0a8bdb594ee78f18f691d7dc652ac305a490c9b71a"}
|
--{"status":"Digest: sha256:a63d0b2ecbd723da612abf0a8bdb594ee78f18f691d7dc652ac305a490c9b71a"}
|
||||||
--{"status":"Status: Downloaded newer image for debian:latest"}
|
--{"status":"Status: Downloaded newer image for debian:latest"}
|
||||||
_docker.pull_image_show_status_cb = function(res, source)
|
_docker.pull_image_show_status_cb = function(res, source)
|
||||||
return status_cb(res, source, function(chunk)
|
return status_cb(res, source, function(chunk)
|
||||||
local json_parse = luci.jsonc.parse
|
local json_parse = luci.jsonc.parse
|
||||||
local step = json_parse(chunk)
|
local step = json_parse(chunk)
|
||||||
if type(step) == "table" then
|
if type(step) == "table" then
|
||||||
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
|
||||||
if num == 0 then
|
buf, num = buf:gsub("\t"..step.id .. ": .-\n", str)
|
||||||
buf = buf .. str
|
end
|
||||||
end
|
if num == 0 then
|
||||||
_docker:write_status(buf)
|
buf = buf .. str
|
||||||
end
|
end
|
||||||
end)
|
_docker:write_status(buf)
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
--{"status":"Downloading from https://downloads.openwrt.org/releases/19.07.0/targets/x86/64/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz"}
|
--{"status":"Downloading from https://downloads.openwrt.org/releases/19.07.0/targets/x86/64/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz"}
|
||||||
--{"status":"Importing","progressDetail":{"current":1572391,"total":3821714},"progress":"[====================\u003e ] 1.572MB/3.822MB"}
|
--{"status":"Importing","progressDetail":{"current":1572391,"total":3821714},"progress":"[====================\u003e ] 1.572MB/3.822MB"}
|
||||||
--{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
|
--{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
|
||||||
_docker.import_image_show_status_cb = function(res, source)
|
_docker.import_image_show_status_cb = function(res, source)
|
||||||
return status_cb(res, source, function(chunk)
|
return status_cb(res, source, function(chunk)
|
||||||
local json_parse = luci.jsonc.parse
|
local json_parse = luci.jsonc.parse
|
||||||
local step = json_parse(chunk)
|
local step = json_parse(chunk)
|
||||||
if type(step) == "table" then
|
if type(step) == "table" then
|
||||||
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
|
||||||
if num == 0 then
|
buf, num = buf:gsub("\t"..step.status .. " .-\n", str)
|
||||||
buf = buf .. str
|
end
|
||||||
end
|
if num == 0 then
|
||||||
_docker:write_status(buf)
|
buf = buf .. str
|
||||||
end
|
end
|
||||||
end
|
_docker:write_status(buf)
|
||||||
)
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- _docker.print_status_cb = function(res, source)
|
|
||||||
-- return status_cb(res, source, function(step)
|
|
||||||
-- luci.util.perror(step)
|
|
||||||
-- end
|
|
||||||
-- )
|
|
||||||
-- end
|
|
||||||
|
|
||||||
_docker.create_macvlan_interface = function(name, device, gateway, subnet)
|
_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
|
||||||
local ip = require "luci.ip"
|
end
|
||||||
local if_name = "docker_"..name
|
|
||||||
local dev_name = "macvlan_"..name
|
if uci:get("dockerd", "globals", "remote_endpoint") == "true" then
|
||||||
local net_mask = tostring(ip.new(subnet):mask())
|
return
|
||||||
local lan_interfaces
|
end
|
||||||
-- add macvlan device
|
|
||||||
uci:delete("network", dev_name)
|
local ip = require "luci.ip"
|
||||||
uci:set("network", dev_name, "device")
|
local if_name = "docker_"..name
|
||||||
uci:set("network", dev_name, "name", dev_name)
|
local dev_name = "macvlan_"..name
|
||||||
uci:set("network", dev_name, "ifname", device)
|
local net_mask = tostring(ip.new(subnet):mask())
|
||||||
uci:set("network", dev_name, "type", "macvlan")
|
local lan_interfaces
|
||||||
uci:set("network", dev_name, "mode", "bridge")
|
|
||||||
-- add macvlan interface
|
-- add macvlan device
|
||||||
uci:delete("network", if_name)
|
uci:delete("network", dev_name)
|
||||||
uci:set("network", if_name, "interface")
|
uci:set("network", dev_name, "device")
|
||||||
uci:set("network", if_name, "proto", "static")
|
uci:set("network", dev_name, "name", dev_name)
|
||||||
uci:set("network", if_name, "ifname", dev_name)
|
uci:set("network", dev_name, "ifname", device)
|
||||||
uci:set("network", if_name, "ipaddr", gateway)
|
uci:set("network", dev_name, "type", "macvlan")
|
||||||
uci:set("network", if_name, "netmask", net_mask)
|
uci:set("network", dev_name, "mode", "bridge")
|
||||||
uci:foreach("firewall", "zone", function(s)
|
|
||||||
if s.name == "lan" then
|
-- add macvlan interface
|
||||||
local interfaces
|
uci:delete("network", if_name)
|
||||||
if type(s.network) == "table" then
|
uci:set("network", if_name, "interface")
|
||||||
interfaces = table.concat(s.network, " ")
|
uci:set("network", if_name, "proto", "static")
|
||||||
uci:delete("firewall", s[".name"], "network")
|
uci:set("network", if_name, "ifname", dev_name)
|
||||||
else
|
uci:set("network", if_name, "ipaddr", gateway)
|
||||||
interfaces = s.network and s.network or ""
|
uci:set("network", if_name, "netmask", net_mask)
|
||||||
end
|
uci:foreach("firewall", "zone", function(s)
|
||||||
interfaces = interfaces .. " " .. if_name
|
if s.name == "lan" then
|
||||||
interfaces = interfaces:gsub("%s+", " ")
|
local interfaces
|
||||||
uci:set("firewall", s[".name"], "network", interfaces)
|
if type(s.network) == "table" then
|
||||||
end
|
interfaces = table.concat(s.network, " ")
|
||||||
end)
|
uci:delete("firewall", s[".name"], "network")
|
||||||
uci:commit("firewall")
|
else
|
||||||
uci:commit("network")
|
interfaces = s.network and s.network or ""
|
||||||
os.execute("ifup " .. if_name)
|
end
|
||||||
|
interfaces = interfaces .. " " .. if_name
|
||||||
|
interfaces = interfaces:gsub("%s+", " ")
|
||||||
|
uci:set("firewall", s[".name"], "network", interfaces)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
uci:commit("firewall")
|
||||||
|
uci:commit("network")
|
||||||
|
|
||||||
|
os.execute("ifup " .. if_name)
|
||||||
end
|
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
|
||||||
local if_name = "docker_"..name
|
end
|
||||||
local dev_name = "macvlan_"..name
|
|
||||||
uci:foreach("firewall", "zone", function(s)
|
if uci:get("dockerd", "globals", "remote_endpoint") == "true" then
|
||||||
if s.name == "lan" then
|
return
|
||||||
local interfaces
|
end
|
||||||
if type(s.network) == "table" then
|
|
||||||
interfaces = table.concat(s.network, " ")
|
local if_name = "docker_"..name
|
||||||
else
|
local dev_name = "macvlan_"..name
|
||||||
interfaces = s.network and s.network or ""
|
uci:foreach("firewall", "zone", function(s)
|
||||||
end
|
if s.name == "lan" then
|
||||||
interfaces = interfaces and interfaces:gsub(if_name, "")
|
local interfaces
|
||||||
interfaces = interfaces and interfaces:gsub("%s+", " ")
|
if type(s.network) == "table" then
|
||||||
uci:set("firewall", s[".name"], "network", interfaces)
|
interfaces = table.concat(s.network, " ")
|
||||||
end
|
else
|
||||||
end)
|
interfaces = s.network and s.network or ""
|
||||||
uci:commit("firewall")
|
end
|
||||||
uci:delete("network", dev_name)
|
interfaces = interfaces and interfaces:gsub(if_name, "")
|
||||||
uci:delete("network", if_name)
|
interfaces = interfaces and interfaces:gsub("%s+", " ")
|
||||||
uci:commit("network")
|
uci:set("firewall", s[".name"], "network", interfaces)
|
||||||
os.execute("ip link del " .. if_name)
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
uci:delete("network", dev_name)
|
||||||
|
uci:delete("network", if_name)
|
||||||
|
uci:commit("network")
|
||||||
|
uci:commit("firewall")
|
||||||
|
|
||||||
|
os.execute("ip link del " .. if_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
return _docker
|
return _docker
|
||||||
|
|
|
@ -1,140 +1,147 @@
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
#docker_apply_overlay {
|
#docker_apply_overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 20000;
|
z-index: 20000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#docker_apply_overlay .alert-message {
|
#docker_apply_overlay .alert-message {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 10%;
|
top: 10%;
|
||||||
width: 60%;
|
width: 60%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#docker_apply_overlay .alert-message > h4,
|
#docker_apply_overlay .alert-message > h4,
|
||||||
#docker_apply_overlay .alert-message > p,
|
#docker_apply_overlay .alert-message > p,
|
||||||
#docker_apply_overlay .alert-message > div {
|
#docker_apply_overlay .alert-message > div {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#docker_apply_overlay .alert-message > img {
|
#docker_apply_overlay .alert-message > img {
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
flex-basis: 32px;
|
flex-basis: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.apply-overlay-active {
|
body.apply-overlay-active {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.apply-overlay-active #docker_apply_overlay {
|
body.apply-overlay-active #docker_apply_overlay {
|
||||||
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)%>,
|
||||||
uci_apply_holdoff = <%=math.max(luci.config and luci.config.apply and luci.config.apply.holdoff or 4, 1)%>,
|
uci_apply_holdoff = <%=math.max(luci.config and luci.config.apply and luci.config.apply.holdoff or 4, 1)%>,
|
||||||
uci_apply_timeout = <%=math.max(luci.config and luci.config.apply and luci.config.apply.timeout or 5, 1)%>,
|
uci_apply_timeout = <%=math.max(luci.config and luci.config.apply and luci.config.apply.timeout or 5, 1)%>,
|
||||||
uci_apply_display = <%=math.max(luci.config and luci.config.apply and luci.config.apply.display or 1.5, 1)%>,
|
uci_apply_display = <%=math.max(luci.config and luci.config.apply and luci.config.apply.display or 1.5, 1)%>,
|
||||||
was_xhr_poll_running = false;
|
was_xhr_poll_running = false;
|
||||||
|
|
||||||
function docker_status_message(type, content) {
|
function docker_status_message(type, content) {
|
||||||
document.getElementById('docker_apply_overlay') || document.body.insertAdjacentHTML("beforeend",'<div id="docker_apply_overlay"><div class="alert-message"></div></div>')
|
document.getElementById('docker_apply_overlay') || document.body.insertAdjacentHTML("beforeend",'<div id="docker_apply_overlay"><div class="alert-message"></div></div>')
|
||||||
var overlay = document.getElementById('docker_apply_overlay')
|
var overlay = document.getElementById('docker_apply_overlay')
|
||||||
message = overlay.querySelector('.alert-message');
|
message = overlay.querySelector('.alert-message');
|
||||||
|
|
||||||
if (message && type) {
|
if (message && type) {
|
||||||
if (!message.classList.contains(type)) {
|
if (!message.classList.contains(type)) {
|
||||||
message.classList.remove('notice');
|
message.classList.remove('notice');
|
||||||
message.classList.remove('warning');
|
message.classList.remove('warning');
|
||||||
message.classList.add(type);
|
message.classList.add(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content)
|
if (content)
|
||||||
message.innerHTML = content;
|
message.innerHTML = content;
|
||||||
|
|
||||||
document.body.classList.add('apply-overlay-active');
|
document.body.classList.add('apply-overlay-active');
|
||||||
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
||||||
if (!was_xhr_poll_running) {
|
if (!was_xhr_poll_running) {
|
||||||
was_xhr_poll_running = XHR.running();
|
was_xhr_poll_running = XHR.running();
|
||||||
XHR.halt();
|
XHR.halt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
document.body.classList.remove('apply-overlay-active');
|
document.body.classList.remove('apply-overlay-active');
|
||||||
|
if (was_xhr_poll_running)
|
||||||
|
XHR.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (was_xhr_poll_running)
|
var loading_msg="Loading.."
|
||||||
XHR.run();
|
function uci_confirm_docker() {
|
||||||
}
|
var tt;
|
||||||
}
|
docker_status_message('notice');
|
||||||
var loading_msg="Loading.."
|
var call = function(r, resjson, duration) {
|
||||||
function uci_confirm_docker() {
|
if (r && r.status === 200 ) {
|
||||||
var tt;
|
var indicator = document.querySelector('.uci_change_indicator');
|
||||||
docker_status_message('notice');
|
if (indicator)
|
||||||
var call = function(r, resjson, duration) {
|
indicator.style.display = 'none';
|
||||||
if (r && r.status === 200 ) {
|
docker_status_message('notice', '<%:Docker actions done.%>');
|
||||||
var indicator = document.querySelector('.uci_change_indicator');
|
document.body.classList.remove('apply-overlay-active');
|
||||||
if (indicator) indicator.style.display = 'none';
|
window.clearTimeout(tt);
|
||||||
docker_status_message('notice', '<%:Docker actions done.%>');
|
return;
|
||||||
document.body.classList.remove('apply-overlay-active');
|
}
|
||||||
window.clearTimeout(tt);
|
loading_msg = resjson?resjson.info:loading_msg
|
||||||
return;
|
// var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0);
|
||||||
}
|
var delay =1000
|
||||||
loading_msg = resjson?resjson.info:loading_msg
|
window.setTimeout(function() {
|
||||||
// var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0);
|
xhr.get('<%=url("admin/docker/confirm")%>', null, call, uci_apply_timeout * 1000);
|
||||||
var delay =1000
|
},delay);
|
||||||
window.setTimeout(function() {
|
};
|
||||||
xhr.get('<%=url("admin/docker/confirm")%>', null, call, uci_apply_timeout * 1000);
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
/* wait a few seconds for the settings to become effective */
|
|
||||||
window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 , 1));
|
|
||||||
}
|
|
||||||
// document.getElementsByTagName("form")[0].addEventListener("submit", (e)=>{
|
|
||||||
// uci_confirm_docker()
|
|
||||||
// })
|
|
||||||
|
|
||||||
function fnSubmitForm(el){
|
tick();
|
||||||
if (el.id != "cbid.table.1._new") {
|
/* wait a few seconds for the settings to become effective */
|
||||||
uci_confirm_docker()
|
window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 , 1));
|
||||||
}
|
}
|
||||||
}
|
// document.getElementsByTagName("form")[0].addEventListener("submit", (e)=>{
|
||||||
|
// uci_confirm_docker()
|
||||||
|
// })
|
||||||
|
|
||||||
<% if self.err then -%>
|
function fnSubmitForm(el){
|
||||||
docker_status_message('warning', '<span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">'+`<%=self.err%>`+'</span>');
|
if (el.id != "cbid.table.1._new") {
|
||||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
uci_confirm_docker()
|
||||||
docker_status_message()
|
}
|
||||||
})
|
}
|
||||||
<%- end %>
|
|
||||||
|
|
||||||
window.onload= function (){
|
<% if self.err then -%>
|
||||||
var buttons = document.querySelectorAll('input[type="submit"]');
|
docker_status_message('warning', '<span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">'+`<%=self.err%>`+'</span>');
|
||||||
[].slice.call(buttons).forEach(function (el) {
|
document.getElementById('docker_apply_overlay').addEventListener(
|
||||||
el.onclick = fnSubmitForm.bind(this, el);
|
"click",
|
||||||
});
|
(e)=>{
|
||||||
}
|
docker_status_message()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
<%- end %>
|
||||||
|
|
||||||
|
window.onload= function (){
|
||||||
|
var buttons = document.querySelectorAll('input[type="submit"]');
|
||||||
|
[].slice.call(buttons).forEach(function (el) {
|
||||||
|
el.onclick = fnSubmitForm.bind(this, el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//]]></script>
|
//]]></script>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<br>
|
<br>
|
||||||
<ul class="cbi-tabmenu">
|
<ul class="cbi-tabmenu">
|
||||||
<li id="cbi-tab-container_info"><a id="a-cbi-tab-container_info" href=""><%:Info%></a></li>
|
<li id="cbi-tab-container_info"><a id="a-cbi-tab-container_info" href=""><%:Info%></a></li>
|
||||||
<li id="cbi-tab-container_resources"><a id="a-cbi-tab-container_resources" href=""><%:Resources%></a></li>
|
<li id="cbi-tab-container_resources"><a id="a-cbi-tab-container_resources" href=""><%:Resources%></a></li>
|
||||||
<li id="cbi-tab-container_stats"><a id="a-cbi-tab-container_stats" href=""><%:Stats%></a></li>
|
<li id="cbi-tab-container_stats"><a id="a-cbi-tab-container_stats" href=""><%:Stats%></a></li>
|
||||||
<li id="cbi-tab-container_file"><a id="a-cbi-tab-container_file" href=""><%:File%></a></li>
|
<li id="cbi-tab-container_file"><a id="a-cbi-tab-container_file" href=""><%:File%></a></li>
|
||||||
<li id="cbi-tab-container_console"><a id="a-cbi-tab-container_console" href=""><%:Console%></a></li>
|
<li id="cbi-tab-container_console"><a id="a-cbi-tab-container_console" href=""><%:Console%></a></li>
|
||||||
<li id="cbi-tab-container_inspect"><a id="a-cbi-tab-container_inspect" href=""><%:Inspect%></a></li>
|
<li id="cbi-tab-container_inspect"><a id="a-cbi-tab-container_inspect" href=""><%:Inspect%></a></li>
|
||||||
<li id="cbi-tab-container_logs"><a id="a-cbi-tab-container_logs" href=""><%:Logs%></a></li>
|
<li id="cbi-tab-container_logs"><a id="a-cbi-tab-container_logs" href=""><%:Logs%></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
@ -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,6 +1,6 @@
|
||||||
<div class="cbi-map">
|
<div class="cbi-map">
|
||||||
<iframe id="terminal" style="width: 100%; min-height: 500px; border: none; border-radius: 3px;"></iframe>
|
<iframe id="terminal" style="width: 100%; min-height: 500px; border: none; border-radius: 3px;"></iframe>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
document.getElementById("terminal").src = "http://" + window.location.hostname + ":7682";
|
document.getElementById("terminal").src = "http://" + window.location.hostname + ":7682";
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,63 +1,73 @@
|
||||||
|
|
||||||
<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">
|
||||||
<input type="file" name="upload_archive" accept="application/x-tar" id="upload_archive" />
|
<input type="file" name="upload_archive" accept="application/x-tar" id="upload_archive" />
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<label class="cbi-value-title" for="path"><%:Path%></label>
|
<label class="cbi-value-title" for="path"><%:Path%></label>
|
||||||
<div class="cbi-value-field">
|
<div class="cbi-value-field">
|
||||||
<input type="text" class="cbi-input-text" name="path" value="/tmp/" id="path" />
|
<input type="text" class="cbi-input-text" name="path" value="/tmp/" id="path" />
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div class="cbi-value-field">
|
<div class="cbi-value-field">
|
||||||
<input type="button"" class="btn cbi-button cbi-button-action important" id="upload" name="upload" value="<%:Upload%>" />
|
<input type="button"" class="btn cbi-button cbi-button-action important" id="upload" name="upload" value="<%:Upload%>" />
|
||||||
<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) {
|
||||||
let uploadArchive = document.getElementById('upload_archive')
|
let uploadArchive = document.getElementById('upload_archive')
|
||||||
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(
|
||||||
docker_status_message()
|
"click",
|
||||||
})
|
(e)=>{
|
||||||
return
|
docker_status_message()
|
||||||
}
|
}
|
||||||
let fileName = uploadArchive.files[0].name
|
)
|
||||||
let formData = new FormData()
|
return
|
||||||
formData.append('upload-filename', fileName)
|
}
|
||||||
formData.append('upload-path', uploadPath)
|
let fileName = uploadArchive.files[0].name
|
||||||
formData.append('upload-archive', uploadArchive.files[0])
|
let formData = new FormData()
|
||||||
let xhr = new XMLHttpRequest()
|
formData.append('upload-filename', fileName)
|
||||||
xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/container_put_archive")%>/<%=self.container%>', true)
|
formData.append('upload-path', uploadPath)
|
||||||
xhr.onload = function() {
|
formData.append('upload-archive', uploadArchive.files[0])
|
||||||
if (xhr.status == 200) {
|
let xhr = new XMLHttpRequest()
|
||||||
uploadArchive.value = ''
|
xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/container_put_archive")%>/<%=self.container%>', true)
|
||||||
docker_status_message('notice', "<%:Upload Success%>")
|
xhr.onload = function() {
|
||||||
}
|
if (xhr.status == 200) {
|
||||||
else {
|
uploadArchive.value = ''
|
||||||
docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText)
|
docker_status_message('notice', "<%:Upload Success%>")
|
||||||
}
|
}
|
||||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
else {
|
||||||
docker_status_message()
|
docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText)
|
||||||
})
|
}
|
||||||
}
|
document.getElementById('docker_apply_overlay').addEventListener(
|
||||||
xhr.send(formData)
|
"click",
|
||||||
}
|
(e)=>{
|
||||||
let btnDownload = document.getElementById('download')
|
docker_status_message()
|
||||||
btnDownload.onclick = function (e) {
|
}
|
||||||
let downloadPath = document.getElementById('path').value
|
)
|
||||||
if (!downloadPath) {
|
}
|
||||||
docker_status_message('warning', "<%:Please input the PATH !%>")
|
xhr.send(formData)
|
||||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
}
|
||||||
docker_status_message()
|
|
||||||
})
|
let btnDownload = document.getElementById('download')
|
||||||
return
|
btnDownload.onclick = function (e) {
|
||||||
}
|
let downloadPath = document.getElementById('path').value
|
||||||
window.open('<%=luci.dispatcher.build_url("admin/docker/container_get_archive")%>?id=<%=self.container%>&path=' + encodeURIComponent(downloadPath))
|
if (!downloadPath) {
|
||||||
}
|
docker_status_message('warning', "<%:Please input the PATH !%>")
|
||||||
|
document.getElementById('docker_apply_overlay').addEventListener(
|
||||||
|
"click",
|
||||||
|
(e)=>{
|
||||||
|
docker_status_message()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.open('<%=luci.dispatcher.build_url("admin/docker/container_get_archive")%>?id=<%=self.container%>&path=' + encodeURIComponent(downloadPath))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,88 +1,104 @@
|
||||||
<input type="text" class="cbi-input-text" name="isrc" placeholder="http://host/image.tar" id="isrc" />
|
<input type="text" class="cbi-input-text" name="isrc" placeholder="http://host/image.tar" id="isrc" />
|
||||||
<input type="text" class="cbi-input-text" name="itag" placeholder="repository:tag" id="itag" />
|
<input type="text" class="cbi-input-text" name="itag" placeholder="repository:tag" id="itag" />
|
||||||
<div style="display: inline-block;">
|
<div style="display: inline-block;">
|
||||||
<input type="button"" class="btn cbi-button cbi-button-add" id="btnimport" name="import" value="<%:Import%>" />
|
<input type="button"" class="btn cbi-button cbi-button-add" id="btnimport" name="import" value="<%:Import%>" />
|
||||||
<input type="file" id="file_import" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" />
|
<input type="file" id="file_import" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
let btnImport = document.getElementById('btnimport')
|
let btnImport = document.getElementById('btnimport')
|
||||||
let valISrc = document.getElementById('isrc')
|
let valISrc = document.getElementById('isrc')
|
||||||
let valITag = document.getElementById('itag')
|
let valITag = document.getElementById('itag')
|
||||||
btnImport.onclick = function (e) {
|
btnImport.onclick = function (e) {
|
||||||
if (valISrc.value == "") {
|
if (valISrc.value == "") {
|
||||||
document.getElementById("file_import").click()
|
document.getElementById("file_import").click()
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
let formData = new FormData()
|
else {
|
||||||
formData.append('src', valISrc.value)
|
let formData = new FormData()
|
||||||
formData.append('tag', valITag.value)
|
formData.append('src', valISrc.value)
|
||||||
let xhr = new XMLHttpRequest()
|
formData.append('tag', valITag.value)
|
||||||
uci_confirm_docker()
|
let xhr = new XMLHttpRequest()
|
||||||
xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true)
|
uci_confirm_docker()
|
||||||
xhr.onload = function () {
|
xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true)
|
||||||
location.reload()
|
xhr.onload = function () {
|
||||||
}
|
location.reload()
|
||||||
xhr.send(formData)
|
}
|
||||||
}
|
xhr.send(formData)
|
||||||
}
|
}
|
||||||
let fileimport = document.getElementById('file_import')
|
}
|
||||||
fileimport.onchange = function (e) {
|
|
||||||
let fileimport = document.getElementById('file_import')
|
|
||||||
if (!fileimport.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let valITag = document.getElementById('itag')
|
|
||||||
let fileName = fileimport.files[0].name
|
|
||||||
let formData = new FormData()
|
|
||||||
formData.append('upload-filename', fileName)
|
|
||||||
formData.append('tag', valITag.value)
|
|
||||||
formData.append('upload-archive', fileimport.files[0])
|
|
||||||
let xhr = new XMLHttpRequest()
|
|
||||||
uci_confirm_docker()
|
|
||||||
xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true)
|
|
||||||
xhr.onload = function () {
|
|
||||||
fileimport.value = ''
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
xhr.send(formData)
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_tag = function (image_id) {
|
let fileimport = document.getElementById('file_import')
|
||||||
let new_tag = prompt("<%:New tag%>\n<%:Image%>" + "ID: " + image_id + "\n<%:Please input new tag%>:", "")
|
fileimport.onchange = function (e) {
|
||||||
if (new_tag) {
|
let fileimport = document.getElementById('file_import')
|
||||||
(new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_tag')%>",
|
if (!fileimport.value) {
|
||||||
{ id: image_id, tag: new_tag },
|
return
|
||||||
function (r) {
|
}
|
||||||
if (r.status == 201) {
|
let valITag = document.getElementById('itag')
|
||||||
location.reload()
|
let fileName = fileimport.files[0].name
|
||||||
}
|
let formData = new FormData()
|
||||||
else {
|
formData.append('upload-filename', fileName)
|
||||||
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
|
formData.append('tag', valITag.value)
|
||||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
formData.append('upload-archive', fileimport.files[0])
|
||||||
docker_status_message()
|
let xhr = new XMLHttpRequest()
|
||||||
})
|
uci_confirm_docker()
|
||||||
}
|
xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true)
|
||||||
})
|
xhr.onload = function () {
|
||||||
}
|
fileimport.value = ''
|
||||||
}
|
location.reload()
|
||||||
|
}
|
||||||
|
xhr.send(formData)
|
||||||
|
}
|
||||||
|
|
||||||
let un_tag = function (tag) {
|
let new_tag = function (image_id) {
|
||||||
if (tag.match("<none>")) return
|
let new_tag = prompt("<%:New tag%>\n<%:Image%>" + "ID: " + image_id + "\n<%:Please input new tag%>:", "")
|
||||||
if (confirm("<%:Remove tag%>: " + tag + " ?")) {
|
if (new_tag) {
|
||||||
(new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_untag')%>",
|
(new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_tag')%>",
|
||||||
{ tag: tag },
|
{
|
||||||
function (r) {
|
id: image_id,
|
||||||
if (r.status == 200) {
|
tag: new_tag
|
||||||
location.reload()
|
},
|
||||||
}
|
function (r) {
|
||||||
else {
|
if (r.status == 201) {
|
||||||
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
|
location.reload()
|
||||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
}
|
||||||
docker_status_message()
|
else {
|
||||||
})
|
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
|
||||||
}
|
document.getElementById('docker_apply_overlay').addEventListener(
|
||||||
})
|
"click",
|
||||||
}
|
(e)=>{
|
||||||
}
|
docker_status_message()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let un_tag = function (tag) {
|
||||||
|
if (tag.match("<none>"))
|
||||||
|
return
|
||||||
|
if (confirm("<%:Remove tag%>: " + tag + " ?")) {
|
||||||
|
(new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_untag')%>",
|
||||||
|
{
|
||||||
|
tag: tag
|
||||||
|
},
|
||||||
|
function (r) {
|
||||||
|
if (r.status == 200) {
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
|
||||||
|
document.getElementById('docker_apply_overlay').addEventListener(
|
||||||
|
"click",
|
||||||
|
(e)=>{
|
||||||
|
docker_status_message()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,29 +1,30 @@
|
||||||
<div style="display: inline-block;">
|
<div style="display: inline-block;">
|
||||||
<input type="button"" class="btn cbi-button cbi-button-add" id="btnload" name="load" value="<%:Load%>" />
|
<input type="button"" class="btn cbi-button cbi-button-add" id="btnload" name="load" value="<%:Load%>" />
|
||||||
<input type="file" id="file_load" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" accept="application/x-tar" />
|
<input type="file" id="file_load" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" accept="application/x-tar" />
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
let btnLoad = document.getElementById('btnload')
|
let btnLoad = document.getElementById('btnload')
|
||||||
btnLoad.onclick = function (e) {
|
btnLoad.onclick = function (e) {
|
||||||
document.getElementById("file_load").click()
|
document.getElementById("file_load").click()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
let fileLoad = document.getElementById('file_load')
|
|
||||||
fileLoad.onchange = function(e){
|
let fileLoad = document.getElementById('file_load')
|
||||||
let fileLoad = document.getElementById('file_load')
|
fileLoad.onchange = function(e){
|
||||||
if (!fileLoad.value) {
|
let fileLoad = document.getElementById('file_load')
|
||||||
return
|
if (!fileLoad.value) {
|
||||||
}
|
return
|
||||||
let fileName = fileLoad.files[0].name
|
}
|
||||||
let formData = new FormData()
|
let fileName = fileLoad.files[0].name
|
||||||
formData.append('upload-filename', fileName)
|
let formData = new FormData()
|
||||||
formData.append('upload-archive', fileLoad.files[0])
|
formData.append('upload-filename', fileName)
|
||||||
let xhr = new XMLHttpRequest()
|
formData.append('upload-archive', fileLoad.files[0])
|
||||||
uci_confirm_docker()
|
let xhr = new XMLHttpRequest()
|
||||||
xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/images_load")%>', true)
|
uci_confirm_docker()
|
||||||
xhr.onload = function() {
|
xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/images_load")%>', true)
|
||||||
location.reload()
|
xhr.onload = function() {
|
||||||
}
|
location.reload()
|
||||||
xhr.send(formData)
|
}
|
||||||
}
|
xhr.send(formData)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,95 +1,102 @@
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
#dialog_reslov {
|
#dialog_reslov {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 20000;
|
z-index: 20000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dialog_reslov .dialog_box {
|
#dialog_reslov .dialog_box {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: rgba(255, 255, 255);
|
background: rgba(255, 255, 255);
|
||||||
top: 10%;
|
top: 10%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
height:auto;
|
height:auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dialog_reslov .dialog_line {
|
#dialog_reslov .dialog_line {
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
margin-bottom: .5em;
|
margin-bottom: .5em;
|
||||||
margin-left: 2em;
|
margin-left: 2em;
|
||||||
margin-right: 2em;
|
margin-right: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dialog_reslov .dialog_box>h4,
|
#dialog_reslov .dialog_box>h4,
|
||||||
#dialog_reslov .dialog_box>p,
|
#dialog_reslov .dialog_box>p,
|
||||||
#dialog_reslov .dialog_box>div {
|
#dialog_reslov .dialog_box>div {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dialog_reslov .dialog_box>img {
|
#dialog_reslov .dialog_box>img {
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
flex-basis: 32px;
|
flex-basis: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dialog-reslov-active {
|
body.dialog-reslov-active {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dialog-reslov-active #dialog_reslov {
|
body.dialog-reslov-active #dialog_reslov {
|
||||||
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')
|
||||||
document.documentElement.style.overflowY = 'scroll'
|
document.documentElement.style.overflowY = 'scroll'
|
||||||
}
|
}
|
||||||
|
|
||||||
function reslov_container() {
|
function reslov_container() {
|
||||||
let s = document.getElementById('cmd-line-status')
|
let s = document.getElementById('cmd-line-status')
|
||||||
if (!s) return
|
|
||||||
let cmd_line = document.getElementById("dialog_reslov_text").value;
|
|
||||||
if (cmd_line == null || cmd_line == "") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cmd_line = cmd_line.replace(/(^\s*)/g,"")
|
|
||||||
if (!cmd_line.match(/^docker\s+(run|create)/)) {
|
|
||||||
s.innerHTML = "<font color='red'><%:Command line Error%></font>"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let reg_space = /\s+/g
|
|
||||||
let reg_muti_line= /\\\s*\n/g
|
|
||||||
// reg_rem =/(?<!\\)`#.+(?<!\\)`/g // the command has `# `
|
|
||||||
let reg_rem =/`#.+`/g// the command has `# `
|
|
||||||
cmd_line = cmd_line.replace(/^docker\s+(run|create)/,"DOCKERCLI").replace(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ")
|
|
||||||
console.log(cmd_line)
|
|
||||||
window.location.href = '<%=luci.dispatcher.build_url("admin/docker/newcontainer")%>/' + encodeURI(cmd_line)
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear_text(){
|
if (!s)
|
||||||
let s = document.getElementById('cmd-line-status')
|
return
|
||||||
s.innerHTML = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function show_reslov_dialog() {
|
let cmd_line = document.getElementById("dialog_reslov_text").value;
|
||||||
document.getElementById('dialog_reslov') || document.body.insertAdjacentHTML("beforeend", '<div id="dialog_reslov"><div class="dialog_box"><div class="dialog_line"></div><div class="dialog_line"><span><%:Plese input <docker create/run> command line:%></span><br><span id="cmd-line-status"></span></div><div class="dialog_line"><textarea class="cbi-input-textarea" id="dialog_reslov_text" style="width: 100%; height:100%;" rows="15" onkeyup="clear_text()"></textarea></div><div class="dialog_line" style="text-align: right;"><input type="button" class="btn cbi-button cbi-button-apply" type="submit" value="<%:Submit%>" onclick="reslov_container()" /> <input type="button" class="btn cbi-button cbi-button-reset" type="reset" value="<%:Cancel%>" onclick="close_reslov_dialog()" /></div><div class="dialog_line"></div></div></div>')
|
if (cmd_line == null || cmd_line == "") {
|
||||||
document.body.classList.add('dialog-reslov-active')
|
return
|
||||||
let s = document.getElementById('cmd-line-status')
|
}
|
||||||
s.innerHTML = ""
|
|
||||||
document.documentElement.style.overflowY = 'hidden'
|
cmd_line = cmd_line.replace(/(^\s*)/g,"")
|
||||||
}
|
if (!cmd_line.match(/^docker\s+(run|create)/)) {
|
||||||
|
s.innerHTML = "<font color='red'><%:Command line Error%></font>"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let reg_space = /\s+/g
|
||||||
|
let reg_muti_line= /\\\s*\n/g
|
||||||
|
// reg_rem =/(?<!\\)`#.+(?<!\\)`/g // the command has `# `
|
||||||
|
let reg_rem =/`#.+`/g// the command has `# `
|
||||||
|
cmd_line = cmd_line.replace(/^docker\s+(run|create)/,"DOCKERCLI").replace(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ")
|
||||||
|
console.log(cmd_line)
|
||||||
|
window.location.href = '<%=luci.dispatcher.build_url("admin/docker/newcontainer")%>/' + encodeURI(cmd_line)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear_text(){
|
||||||
|
let s = document.getElementById('cmd-line-status')
|
||||||
|
s.innerHTML = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_reslov_dialog() {
|
||||||
|
document.getElementById('dialog_reslov') || document.body.insertAdjacentHTML("beforeend", '<div id="dialog_reslov"><div class="dialog_box"><div class="dialog_line"></div><div class="dialog_line"><span><%:Plese input <docker create/run> command line:%></span><br><span id="cmd-line-status"></span></div><div class="dialog_line"><textarea class="cbi-input-textarea" id="dialog_reslov_text" style="width: 100%; height:100%;" rows="15" onkeyup="clear_text()"></textarea></div><div class="dialog_line" style="text-align: right;"><input type="button" class="btn cbi-button cbi-button-apply" type="submit" value="<%:Submit%>" onclick="reslov_container()" /> <input type="button" class="btn cbi-button cbi-button-reset" type="reset" value="<%:Cancel%>" onclick="close_reslov_dialog()" /></div><div class="dialog_line"></div></div></div>')
|
||||||
|
document.body.classList.add('dialog-reslov-active')
|
||||||
|
let s = document.getElementById('cmd-line-status')
|
||||||
|
s.innerHTML = ""
|
||||||
|
document.documentElement.style.overflowY = 'hidden'
|
||||||
|
}
|
||||||
</script>
|
</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%>
|
||||||
|
|
|
@ -1,280 +1,197 @@
|
||||||
<style>
|
<style>
|
||||||
/*!
|
/*!
|
||||||
Pure v1.0.1
|
Pure v1.0.1
|
||||||
Copyright 2013 Yahoo!
|
Copyright 2013 Yahoo!
|
||||||
Licensed under the BSD License.
|
Licensed under the BSD License.
|
||||||
https://github.com/pure-css/pure/blob/master/LICENSE.md
|
https://github.com/pure-css/pure/blob/master/LICENSE.md
|
||||||
*/
|
*/
|
||||||
.pure-g {
|
.pure-g {
|
||||||
letter-spacing: -.31em;
|
letter-spacing: -.31em;
|
||||||
text-rendering: optimizespeed;
|
text-rendering: optimizespeed;
|
||||||
font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif;
|
font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
display: flex;
|
display: flex;
|
||||||
-webkit-box-orient: horizontal;
|
-webkit-box-orient: horizontal;
|
||||||
-webkit-box-direction: normal;
|
-webkit-box-direction: normal;
|
||||||
-webkit-flex-flow: row wrap;
|
-webkit-flex-flow: row wrap;
|
||||||
-ms-flex-flow: row wrap;
|
-ms-flex-flow: row wrap;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
-webkit-align-content: flex-start;
|
-webkit-align-content: flex-start;
|
||||||
-ms-flex-line-pack: start;
|
-ms-flex-line-pack: start;
|
||||||
align-content: flex-start
|
align-content: flex-start
|
||||||
}
|
}
|
||||||
|
|
||||||
.pure-u {
|
.pure-u {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
zoom: 1;
|
zoom: 1;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
word-spacing: normal;
|
word-spacing: normal;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
text-rendering: auto
|
text-rendering: auto
|
||||||
}
|
}
|
||||||
|
|
||||||
.pure-g [class*=pure-u] {
|
.pure-g [class*=pure-u] {
|
||||||
font-family: sans-serif
|
font-family: sans-serif
|
||||||
}
|
}
|
||||||
|
|
||||||
.pure-u-1-4,
|
.pure-u-1-4,
|
||||||
.pure-u-2-5,
|
.pure-u-2-5,
|
||||||
.pure-u-3-5 {
|
.pure-u-3-5 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
zoom: 1;
|
zoom: 1;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
word-spacing: normal;
|
word-spacing: normal;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
text-rendering: auto
|
text-rendering: auto
|
||||||
}
|
}
|
||||||
|
|
||||||
.pure-u-1-4 {
|
.pure-u-1-4 {
|
||||||
width: 25%
|
width: 25%
|
||||||
}
|
}
|
||||||
|
|
||||||
.pure-u-2-5 {
|
.pure-u-2-5 {
|
||||||
width: 40%
|
width: 40%
|
||||||
}
|
}
|
||||||
|
|
||||||
.pure-u-3-5 {
|
.pure-u-3-5 {
|
||||||
width: 60%
|
width: 60%
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
margin: 1rem -0.5rem 1rem -0.5rem;
|
margin: 1rem -0.5rem 1rem -0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
margin: 0.5rem 0.5rem;
|
margin: 0.5rem 0.5rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
min-width: inherit;
|
min-width: inherit;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
border: 1px solid rgba(0, 0, 0, .05);
|
border: 1px solid rgba(0, 0, 0, .05);
|
||||||
border-radius: .375rem;
|
border-radius: .375rem;
|
||||||
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15);
|
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.img-con {
|
.img-con {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
min-width: 4rem;
|
min-width: 4rem;
|
||||||
max-width: 4rem;
|
max-width: 4rem;
|
||||||
min-height: 4rem;
|
min-height: 4rem;
|
||||||
max-height: 4rem;
|
max-height: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block h4 {
|
.block h4 {
|
||||||
font-size: .8125rem;
|
font-size: .8125rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
color: #8898aa !important;
|
color: #8898aa !important;
|
||||||
line-height: 1.8em;
|
line-height: 1.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cbi-section-table-cell {
|
.cbi-section-table-cell {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 700px) {
|
@media screen and (max-width: 700px) {
|
||||||
.pure-u-1-4 {
|
.pure-u-1-4 {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cbi-button-add {
|
.cbi-button-add {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.3rem 0.5rem;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
width: 50px !important;
|
width: 50px !important;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
bottom: 90px;
|
bottom: 90px;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #fb6340 !important;
|
background-color: #fb6340 !important;
|
||||||
border-color: #fb6340 !important;
|
border-color: #fb6340 !important;
|
||||||
box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .75);
|
box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .75);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="pure-g status">
|
<div class="pure-g status">
|
||||||
<div class="pure-u-1-4">
|
<div class="pure-u-1-4">
|
||||||
<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>
|
</div>
|
||||||
<path
|
</div>
|
||||||
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" />
|
<div class="pure-u-3-5">
|
||||||
</svg>
|
<h4 style="text-align: right; font-size: 1rem"><%:Containers%></h4>
|
||||||
</div>
|
<h4 style="text-align: right;">
|
||||||
</div>
|
<%- if self.containers_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/containers")%>'><%- end -%>
|
||||||
<div class="pure-u-3-5">
|
<span style="font-size: 2rem; color: #2dce89;"><%=self.containers_running%></span>
|
||||||
<h4 style="text-align: right; font-size: 1rem"><%:Containers%></h4>
|
<span style="font-size: 1rem; color: #8898aa !important;">/<%=self.containers_total%></span>
|
||||||
<h4 style="text-align: right;">
|
<%- if self.containers_total ~= "-" then -%></a><%- end -%>
|
||||||
<%- if self.containers_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/containers")%>'><%- end -%>
|
</h4>
|
||||||
<span style="font-size: 2rem; color: #2dce89;"><%=self.containers_running%></span>
|
</div>
|
||||||
<span style="font-size: 1rem; color: #8898aa !important;">/<%=self.containers_total%></span>
|
</div>
|
||||||
<%- if self.containers_total ~= "-" then -%></a><%- end -%>
|
</div>
|
||||||
</h4>
|
<div class="pure-u-1-4">
|
||||||
</div>
|
<div class="block pure-g">
|
||||||
</div>
|
<div class="pure-u-2-5">
|
||||||
</div>
|
<div class="img-con">
|
||||||
<div class="pure-u-1-4">
|
<img src="<%=resource%>/dockerman/images.svg" />
|
||||||
<div class="block pure-g">
|
</div>
|
||||||
<div class="pure-u-2-5">
|
</div>
|
||||||
<div class="img-con">
|
<div class="pure-u-3-5">
|
||||||
<svg id="icon-hub" viewBox="0 -4 42 50" stroke-width="2" fill-rule="nonzero" width="100%" height="100%">
|
<h4 style="text-align: right; font-size: 1rem"><%:Images%></h4>
|
||||||
<path
|
<h4 style="text-align: right;">
|
||||||
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"
|
<%- if self.images_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/images")%>'><%- end -%>
|
||||||
id="Fill-1"></path>
|
<span style="font-size: 2rem; color: #2dce89;"><%=self.images_used%></span>
|
||||||
<rect id="Rectangle-3" x="14" y="26" width="6" height="10"></rect>
|
<span style="font-size: 1rem; color: #8898aa !important;">/<%=self.images_total%></span>
|
||||||
<path d="M20,26 L20,36 L26,36 L26,26 L20,26 Z" id="Rectangle-3"></path>
|
<%- if self.images_total ~= "-" then -%></a><%- end -%>
|
||||||
</svg>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-3-5">
|
</div>
|
||||||
<h4 style="text-align: right; font-size: 1rem"><%:Images%></h4>
|
<div class="pure-u-1-4">
|
||||||
<h4 style="text-align: right;">
|
<div class="block pure-g">
|
||||||
<%- if self.images_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/images")%>'><%- end -%>
|
<div class="pure-u-2-5">
|
||||||
<span style="font-size: 2rem; color: #2dce89;"><%=self.images_used%></span>
|
<div class="img-con">
|
||||||
<span style="font-size: 1rem; color: #8898aa !important;">/<%=self.images_total%></span>
|
<img src="<%=resource%>/dockerman/networks.svg" />
|
||||||
<%- if self.images_total ~= "-" then -%></a><%- end -%>
|
</div>
|
||||||
</h4>
|
</div>
|
||||||
</div>
|
<div class="pure-u-3-5">
|
||||||
</div>
|
<h4 style="text-align: right; font-size: 1rem"><%:Networks%></h4>
|
||||||
</div>
|
<h4 style="text-align: right;">
|
||||||
<div class="pure-u-1-4">
|
<%- if self.networks_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/networks")%>'><%- end -%>
|
||||||
<div class="block pure-g">
|
<span style="font-size: 2rem; color: #2dce89;"><%=self.networks_total%></span>
|
||||||
<div class="pure-u-2-5">
|
<!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
|
||||||
<div class="img-con">
|
<%- if self.networks_total ~= "-" then -%></a><%- end -%>
|
||||||
<svg version="1.1" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 48.723 48.723" xml:space="preserve">
|
</h4>
|
||||||
<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
|
</div>
|
||||||
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
|
</div>
|
||||||
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
|
</div>
|
||||||
h-0.678v2.652h-7.846v-1.104H7.452v1.104H1.114v1.316h6.338V24.152z" />
|
<div class="pure-u-1-4">
|
||||||
<path
|
<div class="block pure-g">
|
||||||
d="M21.484,16.849h5.204v-2.611h7.133V1.555H14.588v12.683h6.896V16.849z M16.537,12.288V3.505h15.335v8.783H16.537z" />
|
<div class="pure-u-2-5">
|
||||||
<rect x="18.682" y="16.898" width="10.809" height="0.537" />
|
<div class="img-con">
|
||||||
<path
|
<img src="<%=resource%>/dockerman/volumes.svg" />
|
||||||
d="M0,43.971h6.896v2.611H12.1v-2.611h7.134V31.287H0V43.971z M1.95,33.236h15.334v8.785H1.95V33.236z" />
|
</div>
|
||||||
<rect x="4.095" y="46.631" width="10.808" height="0.537" />
|
</div>
|
||||||
<path
|
<div class="pure-u-3-5">
|
||||||
d="M29.491,30.994v12.684h6.895v2.611h5.205v-2.611h7.133V30.994H29.491z M46.774,41.729H31.44v-8.783h15.334V41.729z" />
|
<h4 style="text-align: right; font-size: 1rem"><%:Volumes%></h4>
|
||||||
<rect x="33.584" y="46.338" width="10.809" height="0.537" />
|
<h4 style="text-align: right;">
|
||||||
</svg>
|
<%- if self.volumes_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/volumes")%>'><%- end -%>
|
||||||
</div>
|
<span style="font-size: 2rem; color: #2dce89;"><%=self.volumes_total%></span>
|
||||||
</div>
|
<!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
|
||||||
<div class="pure-u-3-5">
|
<%- if self.volumes_total ~= "-" then -%></a><%- end -%>
|
||||||
<h4 style="text-align: right; font-size: 1rem"><%:Networks%></h4>
|
</h4>
|
||||||
<h4 style="text-align: right;">
|
</div>
|
||||||
<%- if self.networks_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/networks")%>'><%- end -%>
|
</div>
|
||||||
<span style="font-size: 2rem; color: #2dce89;"><%=self.networks_total%></span>
|
</div>
|
||||||
<!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
|
|
||||||
<%- if self.networks_total ~= "-" then -%></a><%- end -%>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-4">
|
|
||||||
<div class="block pure-g">
|
|
||||||
<div class="pure-u-2-5">
|
|
||||||
<div class="img-con">
|
|
||||||
<svg x="0px" y="0px" viewBox="0 0 55 55" style="enable-background:new 0 0 55 55;" xml:space="preserve">
|
|
||||||
<path
|
|
||||||
d="M52.354,8.51C51.196,4.22,42.577,0,27.5,0C12.423,0,3.803,4.22,2.646,8.51C2.562,8.657,2.5,8.818,2.5,9v0.5V21v0.5V22v11
|
|
||||||
v0.5V34v12c0,0.162,0.043,0.315,0.117,0.451C3.798,51.346,14.364,55,27.5,55c13.106,0,23.655-3.639,24.875-8.516
|
|
||||||
C52.455,46.341,52.5,46.176,52.5,46V34v-0.5V33V22v-0.5V21V9.5V9C52.5,8.818,52.438,8.657,52.354,8.51z M50.421,33.985
|
|
||||||
c-0.028,0.121-0.067,0.241-0.116,0.363c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369
|
|
||||||
c-0.066,0.093-0.141,0.185-0.219,0.277c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236
|
|
||||||
c-0.164,0.143-0.335,0.285-0.526,0.426c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463
|
|
||||||
c-0.068,0.041-0.141,0.08-0.212,0.12c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061
|
|
||||||
c-0.375,0.177-0.767,0.351-1.186,0.519c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17
|
|
||||||
c-0.017,0.002-0.034,0.004-0.051,0.007c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032
|
|
||||||
c-0.605,0.063-1.217,0.121-1.847,0.167c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076
|
|
||||||
C29.137,40.984,28.327,41,27.5,41s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076
|
|
||||||
c-0.293-0.017-0.595-0.028-0.883-0.049c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032
|
|
||||||
c-0.693-0.076-1.368-0.163-2.026-0.259c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17
|
|
||||||
c-0.012-0.004-0.024-0.009-0.036-0.014c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061
|
|
||||||
c-0.336-0.162-0.647-0.328-0.945-0.497c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463
|
|
||||||
c-0.086-0.06-0.175-0.119-0.257-0.18c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236
|
|
||||||
c-0.134-0.13-0.252-0.26-0.363-0.392c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369
|
|
||||||
c-0.054-0.099-0.102-0.198-0.143-0.297c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,33.823,4.5,33.661,4.5,33.5
|
|
||||||
c0-0.113,0.013-0.226,0.031-0.338c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.028,0.026,0.063,0.051,0.092,0.077
|
|
||||||
c0.218,0.192,0.44,0.383,0.69,0.567C9.049,28.786,16.582,31,27.5,31c10.872,0,18.386-2.196,22.169-5.028
|
|
||||||
c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001v7.424c-0.042,0.143-0.056,0.294-0.031,0.445c0.019,0.112,0.031,0.225,0.031,0.338
|
|
||||||
C50.5,33.661,50.459,33.823,50.421,33.985z M50.5,13.293v7.424c-0.042,0.143-0.056,0.294-0.031,0.445
|
|
||||||
c0.019,0.112,0.031,0.225,0.031,0.338c0,0.161-0.041,0.323-0.079,0.485c-0.028,0.121-0.067,0.241-0.116,0.363
|
|
||||||
c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369c-0.066,0.093-0.141,0.185-0.219,0.277
|
|
||||||
c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236c-0.164,0.143-0.335,0.285-0.526,0.426
|
|
||||||
c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463c-0.068,0.041-0.141,0.08-0.212,0.12
|
|
||||||
c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061c-0.375,0.177-0.767,0.351-1.186,0.519
|
|
||||||
c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17c-0.017,0.002-0.034,0.004-0.051,0.007
|
|
||||||
c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032c-0.605,0.063-1.217,0.121-1.847,0.167
|
|
||||||
c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076C29.137,28.984,28.327,29,27.5,29
|
|
||||||
s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076c-0.293-0.017-0.595-0.028-0.883-0.049
|
|
||||||
c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032c-0.693-0.076-1.368-0.163-2.026-0.259
|
|
||||||
c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17c-0.012-0.004-0.024-0.009-0.036-0.014
|
|
||||||
c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061c-0.336-0.162-0.647-0.328-0.945-0.497
|
|
||||||
c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463c-0.086-0.06-0.175-0.119-0.257-0.18
|
|
||||||
c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236c-0.134-0.13-0.252-0.26-0.363-0.392
|
|
||||||
c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369c-0.054-0.099-0.102-0.198-0.143-0.297
|
|
||||||
c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,21.823,4.5,21.661,4.5,21.5c0-0.113,0.013-0.226,0.031-0.338
|
|
||||||
c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.12,0.109,0.257,0.216,0.387,0.324c0.072,0.06,0.139,0.12,0.215,0.18
|
|
||||||
c0.3,0.236,0.624,0.469,0.975,0.696c0.073,0.047,0.155,0.093,0.231,0.14c0.294,0.183,0.605,0.362,0.932,0.538
|
|
||||||
c0.121,0.065,0.242,0.129,0.367,0.193c0.365,0.186,0.748,0.367,1.151,0.542c0.066,0.029,0.126,0.059,0.193,0.087
|
|
||||||
c0.469,0.199,0.967,0.389,1.485,0.573c0.143,0.051,0.293,0.099,0.44,0.149c0.412,0.139,0.838,0.272,1.279,0.401
|
|
||||||
c0.159,0.046,0.315,0.094,0.478,0.138c0.585,0.162,1.189,0.316,1.823,0.458c0.087,0.02,0.181,0.036,0.269,0.055
|
|
||||||
c0.559,0.122,1.139,0.235,1.735,0.341c0.202,0.036,0.407,0.07,0.613,0.104c0.567,0.093,1.151,0.178,1.75,0.256
|
|
||||||
c0.154,0.02,0.301,0.043,0.457,0.062c0.744,0.09,1.514,0.167,2.305,0.233c0.195,0.016,0.398,0.028,0.596,0.042
|
|
||||||
c0.633,0.046,1.28,0.084,1.942,0.114c0.241,0.011,0.481,0.022,0.727,0.031C25.712,18.979,26.59,19,27.5,19s1.788-0.021,2.65-0.05
|
|
||||||
c0.245-0.009,0.485-0.02,0.727-0.031c0.662-0.03,1.309-0.068,1.942-0.114c0.198-0.015,0.4-0.026,0.596-0.042
|
|
||||||
c0.791-0.065,1.561-0.143,2.305-0.233c0.156-0.019,0.303-0.042,0.457-0.062c0.599-0.078,1.182-0.163,1.75-0.256
|
|
||||||
c0.206-0.034,0.411-0.068,0.613-0.104c0.596-0.106,1.176-0.219,1.735-0.341c0.088-0.019,0.182-0.036,0.269-0.055
|
|
||||||
c0.634-0.142,1.238-0.297,1.823-0.458c0.163-0.045,0.319-0.092,0.478-0.138c0.441-0.129,0.867-0.262,1.279-0.401
|
|
||||||
c0.147-0.05,0.297-0.098,0.44-0.149c0.518-0.184,1.017-0.374,1.485-0.573c0.067-0.028,0.127-0.058,0.193-0.087
|
|
||||||
c0.403-0.176,0.786-0.356,1.151-0.542c0.125-0.064,0.247-0.128,0.367-0.193c0.327-0.175,0.638-0.354,0.932-0.538
|
|
||||||
c0.076-0.047,0.158-0.093,0.231-0.14c0.351-0.227,0.675-0.459,0.975-0.696c0.075-0.06,0.142-0.12,0.215-0.18
|
|
||||||
C50.243,13.509,50.38,13.402,50.5,13.293z M27.5,2c13.555,0,23,3.952,23,7.5s-9.445,7.5-23,7.5s-23-3.952-23-7.5S13.945,2,27.5,2z
|
|
||||||
M50.5,45.703c-0.014,0.044-0.024,0.089-0.032,0.135C49.901,49.297,40.536,53,27.5,53S5.099,49.297,4.532,45.838
|
|
||||||
c-0.008-0.045-0.019-0.089-0.032-0.131v-8.414c0.028,0.026,0.063,0.051,0.092,0.077c0.218,0.192,0.44,0.383,0.69,0.567
|
|
||||||
C9.049,40.786,16.582,43,27.5,43c10.872,0,18.386-2.196,22.169-5.028c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001V45.703z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-3-5">
|
|
||||||
<h4 style="text-align: right; font-size: 1rem"><%:Volumes%></h4>
|
|
||||||
<h4 style="text-align: right;">
|
|
||||||
<%- if self.volumes_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/volumes")%>'><%- end -%>
|
|
||||||
<span style="font-size: 2rem; color: #2dce89;"><%=self.volumes_total%></span>
|
|
||||||
<!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
|
|
||||||
<%- if self.volumes_total ~= "-" then -%></a><%- end -%>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -14,301 +15,376 @@ local json_stringify = jsonc.stringify
|
||||||
local json_parse = jsonc.parse
|
local json_parse = jsonc.parse
|
||||||
|
|
||||||
local chunksource = function(sock, buffer)
|
local chunksource = function(sock, buffer)
|
||||||
buffer = buffer or ""
|
buffer = buffer or ""
|
||||||
return function()
|
|
||||||
local output
|
return function()
|
||||||
local _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
|
local output
|
||||||
if not count then
|
local _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
|
||||||
local newblock, code = sock:recv(1024)
|
|
||||||
if not newblock then return nil, code end
|
if not count then
|
||||||
buffer = buffer .. newblock
|
local newblock, code = sock:recv(1024)
|
||||||
_, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
|
if not newblock then
|
||||||
end
|
return nil, code
|
||||||
count = tonumber(count, 16)
|
end
|
||||||
if not count then
|
buffer = buffer .. newblock
|
||||||
return nil, -1, "invalid encoding"
|
_, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
|
||||||
elseif count == 0 then -- finial
|
end
|
||||||
return nil
|
|
||||||
elseif count <= #buffer - endp then
|
count = tonumber(count, 16)
|
||||||
--data >= count
|
if not count then
|
||||||
output = buffer:sub(endp + 1, endp + count)
|
return nil, -1, "invalid encoding"
|
||||||
if count == #buffer - endp then -- [data]
|
elseif count == 0 then -- finial
|
||||||
buffer = buffer:sub(endp + count + 1)
|
return nil
|
||||||
count, code = sock:recvall(2) --read \r\n
|
elseif count <= #buffer - endp then -- data >= count
|
||||||
if not count then return nil, code end
|
output = buffer:sub(endp + 1, endp + count)
|
||||||
elseif count + 1 == #buffer - endp then -- [data]\r
|
if count == #buffer - endp then -- [data]
|
||||||
buffer = buffer:sub(endp + count + 2)
|
buffer = buffer:sub(endp + count + 1)
|
||||||
count, code = sock:recvall(1) --read \n
|
count, code = sock:recvall(2) --read \r\n
|
||||||
if not count then return nil, code end
|
if not count then
|
||||||
else -- [data]\r\n[count]\r\n[data]...
|
return nil, code
|
||||||
buffer = buffer:sub(endp + count + 3) -- cut buffer
|
end
|
||||||
end
|
elseif count + 1 == #buffer - endp then -- [data]\r
|
||||||
return output
|
buffer = buffer:sub(endp + count + 2)
|
||||||
else
|
count, code = sock:recvall(1) --read \n
|
||||||
-- data < count
|
if not count then
|
||||||
output = buffer:sub(endp + 1, endp + count)
|
return nil, code
|
||||||
buffer = buffer:sub(endp + count + 1)
|
end
|
||||||
local remain, code = sock:recvall(count - #output) --need read remaining
|
else -- [data]\r\n[count]\r\n[data]...
|
||||||
if not remain then return nil, code end
|
buffer = buffer:sub(endp + count + 3) -- cut buffer
|
||||||
output = output .. remain
|
end
|
||||||
count, code = sock:recvall(2) --read \r\n
|
return output
|
||||||
if not count then return nil, code end
|
else -- data < count
|
||||||
return output
|
output = buffer:sub(endp + 1, endp + count)
|
||||||
end
|
buffer = buffer:sub(endp + count + 1)
|
||||||
end
|
local remain, code = sock:recvall(count - #output) --need read remaining
|
||||||
|
if not remain then
|
||||||
|
return nil, code
|
||||||
|
end
|
||||||
|
output = output .. remain
|
||||||
|
count, code = sock:recvall(2) --read \r\n
|
||||||
|
if not count then
|
||||||
|
return nil, code
|
||||||
|
end
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local chunksink = function (sock)
|
local chunksink = function (sock)
|
||||||
return function(chunk, err)
|
return function(chunk, err)
|
||||||
if not chunk then
|
if not chunk then
|
||||||
return sock:writeall("0\r\n\r\n")
|
return sock:writeall("0\r\n\r\n")
|
||||||
else
|
else
|
||||||
return sock:writeall(("%X\r\n%s\r\n"):format(#chunk, tostring(chunk)))
|
return sock:writeall(("%X\r\n%s\r\n"):format(#chunk, tostring(chunk)))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local docker_stream_filter = function(buffer)
|
local docker_stream_filter = function(buffer)
|
||||||
buffer = buffer or ""
|
buffer = buffer or ""
|
||||||
if #buffer < 8 then
|
if #buffer < 8 then
|
||||||
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
|
||||||
if req_options.socket_path then
|
return socket
|
||||||
socket = nixio.socket("unix", "stream")
|
end
|
||||||
if socket:connect(req_options.socket_path) ~= true then return nil end
|
if req_options.socket_path then
|
||||||
elseif req_options.host and req_options.port then
|
socket = nixio.socket("unix", "stream")
|
||||||
socket = nixio.connect(req_options.host, req_options.port)
|
if socket:connect(req_options.socket_path) ~= true then
|
||||||
end
|
return nil
|
||||||
if socket then
|
end
|
||||||
return socket
|
elseif req_options.host and req_options.port then
|
||||||
else
|
socket = nixio.connect(req_options.host, req_options.port)
|
||||||
return nil
|
end
|
||||||
end
|
if socket then
|
||||||
|
return socket
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
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",
|
||||||
end
|
protocol="HTTP/1.1"
|
||||||
|
},
|
||||||
|
body={
|
||||||
|
message="can\'t send data to socket"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
if req_body and type(req_body) == "function" and req_header and req_header:match("chunked") then
|
if req_body and type(req_body) == "function" and req_header and req_header:match("chunked") then
|
||||||
-- chunked send
|
-- chunked send
|
||||||
req_body(chunksink(docker_socket))
|
req_body(chunksink(docker_socket))
|
||||||
elseif req_body and type(req_body) == "function" then
|
elseif req_body and type(req_body) == "function" then
|
||||||
-- normal send by req_body function
|
-- normal send by req_body function
|
||||||
req_body(docker_socket)
|
req_body(docker_socket)
|
||||||
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
|
||||||
elseif req_body then
|
io.popen("echo '".. json_stringify(req_body) .. "' >> " .. options.debug_path)
|
||||||
docker_socket:send(req_body)
|
end
|
||||||
if options.debug then io.popen("echo '".. req_body .. "' >> " .. options.debug_path) end
|
elseif req_body then
|
||||||
end
|
docker_socket:send(req_body)
|
||||||
|
if options.debug then
|
||||||
|
io.popen("echo '".. req_body .. "' >> " .. options.debug_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local linesrc = docker_socket:linesource()
|
local linesrc = docker_socket:linesource()
|
||||||
-- read socket using source http://w3.impa.br/~diego/software/luasocket/ltn12.html
|
-- read socket using source http://w3.impa.br/~diego/software/luasocket/ltn12.html
|
||||||
--http://lua-users.org/wiki/FiltersSourcesAndSinks
|
-- http://lua-users.org/wiki/FiltersSourcesAndSinks
|
||||||
-- handle response header
|
-- handle response header
|
||||||
local line = linesrc()
|
local line = linesrc()
|
||||||
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",
|
||||||
end
|
protocol="HTTP/1.1"
|
||||||
local response = {code = 0, headers = {}, body = {}}
|
},
|
||||||
|
body = {
|
||||||
|
message="no data receive from socket"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
local p, code, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
|
local response = {
|
||||||
response.protocol = p
|
code = 0,
|
||||||
response.code = tonumber(code)
|
headers = {},
|
||||||
response.message = msg
|
body = {}
|
||||||
line = linesrc()
|
}
|
||||||
while line and line ~= "" do
|
|
||||||
local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
|
local p, code, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
|
||||||
if key and key ~= "Status" then
|
response.protocol = p
|
||||||
if type(response.headers[key]) == "string" then
|
response.code = tonumber(code)
|
||||||
response.headers[key] = {response.headers[key], val}
|
response.message = msg
|
||||||
elseif type(response.headers[key]) == "table" then
|
line = linesrc()
|
||||||
response.headers[key][#response.headers[key] + 1] = val
|
|
||||||
else
|
while line and line ~= "" do
|
||||||
response.headers[key] = val
|
local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
|
||||||
end
|
if key and key ~= "Status" then
|
||||||
end
|
if type(response.headers[key]) == "string" then
|
||||||
line = linesrc()
|
response.headers[key] = {
|
||||||
end
|
response.headers[key],
|
||||||
-- handle response body
|
val
|
||||||
local body_buffer = linesrc(true)
|
}
|
||||||
response.body = {}
|
elseif type(response.headers[key]) == "table" then
|
||||||
if type(callback) ~= "function" then
|
response.headers[key][#response.headers[key] + 1] = val
|
||||||
if response.headers["Transfer-Encoding"] == "chunked" then
|
else
|
||||||
local source = chunksource(docker_socket, body_buffer)
|
response.headers[key] = val
|
||||||
code = ltn12.pump.all(source, (ltn12.sink.table(response.body))) and response.code or 555
|
end
|
||||||
response.code = code
|
end
|
||||||
else
|
line = linesrc()
|
||||||
local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
|
end
|
||||||
code = ltn12.pump.all(body_source, (ltn12.sink.table(response.body))) and response.code or 555
|
|
||||||
response.code = code
|
-- handle response body
|
||||||
end
|
local body_buffer = linesrc(true)
|
||||||
else
|
response.body = {}
|
||||||
if response.headers["Transfer-Encoding"] == "chunked" then
|
|
||||||
local source = chunksource(docker_socket, body_buffer)
|
if type(callback) ~= "function" then
|
||||||
callback(response, source)
|
if response.headers["Transfer-Encoding"] == "chunked" then
|
||||||
else
|
local source = chunksource(docker_socket, body_buffer)
|
||||||
local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
|
code = ltn12.pump.all(source, (ltn12.sink.table(response.body))) and response.code or 555
|
||||||
callback(response, body_source)
|
response.code = code
|
||||||
end
|
else
|
||||||
end
|
local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
|
||||||
docker_socket:close()
|
code = ltn12.pump.all(body_source, (ltn12.sink.table(response.body))) and response.code or 555
|
||||||
return response
|
response.code = code
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if response.headers["Transfer-Encoding"] == "chunked" then
|
||||||
|
local source = chunksource(docker_socket, body_buffer)
|
||||||
|
callback(response, source)
|
||||||
|
else
|
||||||
|
local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
|
||||||
|
callback(response, body_source)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
docker_socket:close()
|
||||||
|
return response
|
||||||
end
|
end
|
||||||
|
|
||||||
local gen_header = function(options, http_method, api_group, api_action, name_or_id, request)
|
local gen_header = function(options, http_method, api_group, api_action, name_or_id, request)
|
||||||
local header, query, path
|
local header, query, path
|
||||||
name_or_id = (name_or_id ~= "") and name_or_id or nil
|
name_or_id = (name_or_id ~= "") and name_or_id or nil
|
||||||
|
|
||||||
if request and type(request.query) == "table" then
|
if request and type(request.query) == "table" then
|
||||||
local k, v
|
local k, v
|
||||||
for k, v in pairs(request.query) do
|
for k, v in pairs(request.query) do
|
||||||
if type(v) == "table" then
|
if type(v) == "table" then
|
||||||
query = (query and query .. "&" or "?") .. k .. "=" .. urlencode(json_stringify(v))
|
query = (query and query .. "&" or "?") .. k .. "=" .. urlencode(json_stringify(v))
|
||||||
elseif type(v) == "boolean" then
|
elseif type(v) == "boolean" then
|
||||||
query = (query and query .. "&" or "?") .. k .. "=" .. (v and "true" or "false")
|
query = (query and query .. "&" or "?") .. k .. "=" .. (v and "true" or "false")
|
||||||
elseif type(v) == "number" or type(v) == "string" then
|
elseif type(v) == "number" or type(v) == "string" then
|
||||||
query = (query and query .. "&" or "?") .. k .. "=" .. v
|
query = (query and query .. "&" or "?") .. k .. "=" .. v
|
||||||
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 "")
|
|
||||||
header = (http_method or "GET") .. " " .. path .. " " .. options.protocol .. "\r\n"
|
|
||||||
header = header .. "Host: " .. options.host .. "\r\n"
|
|
||||||
header = header .. "User-Agent: " .. options.user_agent .. "\r\n"
|
|
||||||
header = header .. "Connection: close\r\n"
|
|
||||||
|
|
||||||
if request and type(request.header) == "table" then
|
path = (api_group and ("/" .. api_group) or "") .. (name_or_id and ("/" .. name_or_id) or "") .. (api_action and ("/" .. api_action) or "") .. (query or "")
|
||||||
local k, v
|
header = (http_method or "GET") .. " " .. path .. " " .. options.protocol .. "\r\n"
|
||||||
for k, v in pairs(request.header) do
|
header = header .. "Host: " .. options.host .. "\r\n"
|
||||||
header = header .. k .. ": " .. v .. "\r\n"
|
header = header .. "User-Agent: " .. options.user_agent .. "\r\n"
|
||||||
end
|
header = header .. "Connection: close\r\n"
|
||||||
end
|
|
||||||
|
|
||||||
-- when requst_body is function, we need to custom header using custom header
|
if request and type(request.header) == "table" then
|
||||||
if request and request.body and type(request.body) == "function" then
|
local k, v
|
||||||
if not header:match("Content-Length:") then
|
for k, v in pairs(request.header) do
|
||||||
header = header .. "Transfer-Encoding: chunked\r\n"
|
header = header .. k .. ": " .. v .. "\r\n"
|
||||||
end
|
end
|
||||||
elseif http_method == "POST" and request and request.body and type(request.body) == "table" then
|
end
|
||||||
local conetnt_json = json_stringify(request.body)
|
|
||||||
header = header .. "Content-Type: application/json\r\n"
|
-- when requst_body is function, we need to custom header using custom header
|
||||||
header = header .. "Content-Length: " .. #conetnt_json .. "\r\n"
|
if request and request.body and type(request.body) == "function" then
|
||||||
elseif request and request.body and type(request.body) == "string" then
|
if not header:match("Content-Length:") then
|
||||||
header = header .. "Content-Length: " .. #request.body .. "\r\n"
|
header = header .. "Transfer-Encoding: chunked\r\n"
|
||||||
end
|
end
|
||||||
header = header .. "\r\n"
|
elseif http_method == "POST" and request and request.body and type(request.body) == "table" then
|
||||||
if options.debug then io.popen("echo '".. header .. "' >> " .. options.debug_path) end
|
local conetnt_json = json_stringify(request.body)
|
||||||
return header
|
header = header .. "Content-Type: application/json\r\n"
|
||||||
|
header = header .. "Content-Length: " .. #conetnt_json .. "\r\n"
|
||||||
|
elseif request and request.body and type(request.body) == "string" then
|
||||||
|
header = header .. "Content-Length: " .. #request.body .. "\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
header = header .. "\r\n"
|
||||||
|
if options.debug then
|
||||||
|
io.popen("echo '".. header .. "' >> " .. options.debug_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
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,
|
||||||
local req_body = request and request.body or nil
|
http_method,
|
||||||
local docker_socket = open_socket(req_options)
|
api_group,
|
||||||
|
api_action,
|
||||||
|
name_or_id,
|
||||||
|
request)
|
||||||
|
|
||||||
if docker_socket then
|
local req_body = request and request.body or nil
|
||||||
return send_http_socket(docker_socket, req_header, req_body, callback)
|
local docker_socket = open_socket(req_options)
|
||||||
else
|
|
||||||
return {
|
if docker_socket then
|
||||||
headers = {code=497, message="bad socket path or host", protocol="HTTP/1.1"},
|
return send_http_socket(options, docker_socket, req_header, req_body, callback)
|
||||||
body = {message="can\'t connect to socket"}
|
else
|
||||||
}
|
return {
|
||||||
end
|
headers = {
|
||||||
|
code=497,
|
||||||
|
message="bad socket path or host",
|
||||||
|
protocol="HTTP/1.1"
|
||||||
|
},
|
||||||
|
body = {
|
||||||
|
message="can\'t connect to socket"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
|
||||||
_api_action = "archive"
|
|
||||||
elseif api_action == "df" then
|
|
||||||
_api_action = "system/df"
|
|
||||||
elseif api_action ~= "list" and api_action ~= "inspect" and api_action ~= "remove" then
|
|
||||||
_api_action = api_action
|
|
||||||
elseif (api_group == "containers" or api_group == "images" or api_group == "exec") and (api_action == "list" or api_action == "inspect") then
|
|
||||||
_api_action = "json"
|
|
||||||
end
|
|
||||||
|
|
||||||
local fp = function(self, request, callback)
|
if api_action == "get_archive" or api_action == "put_archive" then
|
||||||
local name_or_id = request and (request.name or request.id or request.name_or_id) or nil
|
api_action = "archive"
|
||||||
if api_action == "list" then
|
elseif api_action == "df" then
|
||||||
if (name_or_id ~= "" and name_or_id ~= nil) then
|
_api_action = "system/df"
|
||||||
if api_group == "images" then
|
elseif api_action ~= "list" and api_action ~= "inspect" and api_action ~= "remove" then
|
||||||
name_or_id = nil
|
_api_action = api_action
|
||||||
else
|
elseif (api_group == "containers" or api_group == "images" or api_group == "exec") and (api_action == "list" or api_action == "inspect") then
|
||||||
request.query = request and request.query or {}
|
_api_action = "json"
|
||||||
request.query.filters = request.query.filters or {}
|
end
|
||||||
request.query.filters.name = request.query.filters.name or {}
|
|
||||||
request.query.filters.name[#request.query.filters.name + 1] = name_or_id
|
|
||||||
name_or_id = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif api_action == "create" then
|
|
||||||
if (name_or_id ~= "" and name_or_id ~= nil) then
|
|
||||||
request.query = request and request.query or {}
|
|
||||||
request.query.name = request.query.name or name_or_id
|
|
||||||
name_or_id = nil
|
|
||||||
end
|
|
||||||
elseif api_action == "logs" then
|
|
||||||
local body_buffer = ""
|
|
||||||
local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
|
|
||||||
if response.code >= 200 and response.code < 300 then
|
|
||||||
for i, v in ipairs(response.body) do
|
|
||||||
body_buffer = body_buffer .. docker_stream_filter(response.body[i])
|
|
||||||
end
|
|
||||||
response.body = body_buffer
|
|
||||||
end
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
|
|
||||||
if response.headers and response.headers["Content-Type"] == "application/json" then
|
|
||||||
if #response.body == 1 then
|
|
||||||
response.body = json_parse(response.body[1])
|
|
||||||
else
|
|
||||||
local tmp = {}
|
|
||||||
for _, v in ipairs(response.body) do
|
|
||||||
tmp[#tmp+1] = json_parse(v)
|
|
||||||
end
|
|
||||||
response.body = tmp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
|
|
||||||
if api_group then
|
local fp = function(self, request, callback)
|
||||||
_table[api_group][api_action] = fp
|
local name_or_id = request and (request.name or request.id or request.name_or_id) or nil
|
||||||
else
|
|
||||||
_table[api_action] = fp
|
if api_action == "list" then
|
||||||
end
|
if (name_or_id ~= "" and name_or_id ~= nil) then
|
||||||
|
if api_group == "images" then
|
||||||
|
name_or_id = nil
|
||||||
|
else
|
||||||
|
request.query = request and request.query or {}
|
||||||
|
request.query.filters = request.query.filters or {}
|
||||||
|
request.query.filters.name = request.query.filters.name or {}
|
||||||
|
request.query.filters.name[#request.query.filters.name + 1] = name_or_id
|
||||||
|
name_or_id = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif api_action == "create" then
|
||||||
|
if (name_or_id ~= "" and name_or_id ~= nil) then
|
||||||
|
request.query = request and request.query or {}
|
||||||
|
request.query.name = request.query.name or name_or_id
|
||||||
|
name_or_id = nil
|
||||||
|
end
|
||||||
|
elseif api_action == "logs" then
|
||||||
|
local body_buffer = ""
|
||||||
|
local response = call_docker(self.options,
|
||||||
|
http_method,
|
||||||
|
api_group,
|
||||||
|
_api_action,
|
||||||
|
name_or_id,
|
||||||
|
request,
|
||||||
|
callback)
|
||||||
|
if response.code >= 200 and response.code < 300 then
|
||||||
|
for i, v in ipairs(response.body) do
|
||||||
|
body_buffer = body_buffer .. docker_stream_filter(response.body[i])
|
||||||
|
end
|
||||||
|
response.body = body_buffer
|
||||||
|
end
|
||||||
|
return response
|
||||||
|
end
|
||||||
|
|
||||||
|
local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
|
||||||
|
|
||||||
|
if response.headers and response.headers["Content-Type"] == "application/json" then
|
||||||
|
if #response.body == 1 then
|
||||||
|
response.body = json_parse(response.body[1])
|
||||||
|
else
|
||||||
|
local tmp = {}
|
||||||
|
for _, v in ipairs(response.body) do
|
||||||
|
tmp[#tmp+1] = json_parse(v)
|
||||||
|
end
|
||||||
|
response.body = tmp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return response
|
||||||
|
end
|
||||||
|
|
||||||
|
if api_group then
|
||||||
|
_table[api_group][api_action] = fp
|
||||||
|
else
|
||||||
|
_table[api_action] = fp
|
||||||
|
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")
|
||||||
|
@ -368,85 +444,93 @@ gen_api(_docker, "GET", nil, "_ping")
|
||||||
gen_api(_docker, "GET", nil, "df")
|
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 = {
|
|
||||||
socket_path = _options.socket_path or nil,
|
docker.options = {
|
||||||
host = _options.socket_path and "localhost" or _options.host,
|
socket_path = _options.socket_path or nil,
|
||||||
port = not _options.socket_path and _options.port or nil,
|
host = _options.socket_path and "localhost" or _options.host,
|
||||||
tls = _options.tls or nil,
|
port = not _options.socket_path and _options.port or nil,
|
||||||
tls_cacert = _options.tls and _options.tls_cacert or nil,
|
tls = _options.tls or nil,
|
||||||
tls_cert = _options.tls and _options.tls_cert or nil,
|
tls_cacert = _options.tls and _options.tls_cacert or nil,
|
||||||
tls_key = _options.tls and _options.tls_key or nil,
|
tls_cert = _options.tls and _options.tls_cert or nil,
|
||||||
version = _options.version or "v1.40",
|
tls_key = _options.tls and _options.tls_key or nil,
|
||||||
user_agent = _options.user_agent or "LuCI",
|
version = _options.version or "v1.40",
|
||||||
protocol = _options.protocol or "HTTP/1.1",
|
user_agent = _options.user_agent or "LuCI",
|
||||||
debug = _options.debug or false,
|
protocol = _options.protocol or "HTTP/1.1",
|
||||||
debug_path = _options.debug and _options.debug_path or nil
|
debug = _options.debug or false,
|
||||||
}
|
debug_path = _options.debug and _options.debug_path or nil
|
||||||
setmetatable(
|
}
|
||||||
docker,
|
|
||||||
{
|
setmetatable(
|
||||||
__index = function(t, key)
|
docker,
|
||||||
if _docker[key] ~= nil then
|
{
|
||||||
return _docker[key]
|
__index = function(t, key)
|
||||||
else
|
if _docker[key] ~= nil then
|
||||||
return _docker.containers[key]
|
return _docker[key]
|
||||||
end
|
else
|
||||||
end
|
return _docker.containers[key]
|
||||||
}
|
end
|
||||||
)
|
end
|
||||||
setmetatable(
|
}
|
||||||
docker.containers,
|
)
|
||||||
{
|
|
||||||
__index = function(t, key)
|
setmetatable(
|
||||||
if key == "options" then
|
docker.containers,
|
||||||
return docker.options
|
{
|
||||||
end
|
__index = function(t, key)
|
||||||
end
|
if key == "options" then
|
||||||
}
|
return docker.options
|
||||||
)
|
end
|
||||||
setmetatable(
|
end
|
||||||
docker.networks,
|
}
|
||||||
{
|
)
|
||||||
__index = function(t, key)
|
|
||||||
if key == "options" then
|
setmetatable(
|
||||||
return docker.options
|
docker.networks,
|
||||||
end
|
{
|
||||||
end
|
__index = function(t, key)
|
||||||
}
|
if key == "options" then
|
||||||
)
|
return docker.options
|
||||||
setmetatable(
|
end
|
||||||
docker.images,
|
end
|
||||||
{
|
}
|
||||||
__index = function(t, key)
|
)
|
||||||
if key == "options" then
|
|
||||||
return docker.options
|
setmetatable(
|
||||||
end
|
docker.images,
|
||||||
end
|
{
|
||||||
}
|
__index = function(t, key)
|
||||||
)
|
if key == "options" then
|
||||||
setmetatable(
|
return docker.options
|
||||||
docker.volumes,
|
end
|
||||||
{
|
end
|
||||||
__index = function(t, key)
|
}
|
||||||
if key == "options" then
|
)
|
||||||
return docker.options
|
|
||||||
end
|
setmetatable(
|
||||||
end
|
docker.volumes,
|
||||||
}
|
{
|
||||||
)
|
__index = function(t, key)
|
||||||
setmetatable(
|
if key == "options" then
|
||||||
docker.exec,
|
return docker.options
|
||||||
{
|
end
|
||||||
__index = function(t, key)
|
end
|
||||||
if key == "options" then
|
}
|
||||||
return docker.options
|
)
|
||||||
end
|
|
||||||
end
|
setmetatable(
|
||||||
}
|
docker.exec,
|
||||||
)
|
{
|
||||||
return docker
|
__index = function(t, key)
|
||||||
|
if key == "options" then
|
||||||
|
return docker.options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return docker
|
||||||
end
|
end
|
||||||
|
|
||||||
return _docker
|
return _docker
|
||||||
|
|
Loading…
Reference in a new issue