From: Paul Donald Date: Mon, 26 Jan 2026 03:29:25 +0000 (+0100) Subject: luci-app-dockerman: drop Lua X-Git-Url: http://git.cdn.openwrt.org/?a=commitdiff_plain;h=cc6fd25150526b37458997d51ad50156938237ab;p=project%2Fluci.git luci-app-dockerman: drop Lua follow-up to JS conversion Signed-off-by: Paul Donald --- diff --git a/applications/luci-app-dockerman/luasrc/controller/dockerman.lua b/applications/luci-app-dockerman/luasrc/controller/dockerman.lua deleted file mode 100644 index ac0668b073..0000000000 --- a/applications/luci-app-dockerman/luasrc/controller/dockerman.lua +++ /dev/null @@ -1,459 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -local docker = require "luci.model.docker" - -module("luci.controller.dockerman",package.seeall) - -function index() - entry({"admin", "docker"}, - firstchild(), - _("Docker"), - 40).acl_depends = { "luci-app-dockerman" } - - entry({"admin", "docker", "config"},cbi("dockerman/configuration"),_("Configuration"), 1).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") or "/var/run/docker.sock" - if socket and not nixio.fs.access(socket) then - return - end - end - - if (require "luci.model.docker").new():_ping().code ~= 200 then - return - end - - entry({"admin", "docker", "overview"}, form("dockerman/overview"),_("Overview"), 2).leaf=true - entry({"admin", "docker", "containers"}, form("dockerman/containers"), _("Containers"), 3).leaf=true - entry({"admin", "docker", "images"}, form("dockerman/images"), _("Images"), 4).leaf=true - entry({"admin", "docker", "networks"}, form("dockerman/networks"), _("Networks"), 5).leaf=true - entry({"admin", "docker", "volumes"}, form("dockerman/volumes"), _("Volumes"), 6).leaf=true - entry({"admin", "docker", "events"}, call("action_events"), _("Events"), 7) - - entry({"admin", "docker", "newcontainer"}, form("dockerman/newcontainer")).leaf=true - entry({"admin", "docker", "newnetwork"}, form("dockerman/newnetwork")).leaf=true - entry({"admin", "docker", "container"}, form("dockerman/container")).leaf=true - - entry({"admin", "docker", "container_stats"}, call("action_get_container_stats")).leaf=true - entry({"admin", "docker", "container_get_archive"}, call("download_archive")).leaf=true - entry({"admin", "docker", "container_put_archive"}, call("upload_archive")).leaf=true - entry({"admin", "docker", "images_save"}, call("save_images")).leaf=true - entry({"admin", "docker", "images_load"}, call("load_images")).leaf=true - entry({"admin", "docker", "images_import"}, call("import_images")).leaf=true - entry({"admin", "docker", "images_get_tags"}, call("get_image_tags")).leaf=true - entry({"admin", "docker", "images_tag"}, call("tag_image")).leaf=true - entry({"admin", "docker", "images_untag"}, call("untag_image")).leaf=true - entry({"admin", "docker", "confirm"}, call("action_confirm")).leaf=true -end - -function action_events() - local logs = "" - local query ={} - - local dk = docker.new() - query["until"] = os.time() - local events = dk:events({query = query}) - - if events.code == 200 then - for _, v in ipairs(events.body) do - local date = "unknown" - if v and v.time then - date = os.date("%Y-%m-%d %H:%M:%S", v.time) - end - - local name = v.Actor.Attributes.name or "unknown" - local action = v.Action or "unknown" - - if v and v.Type == "container" then - local id = v.Actor.ID or "unknown" - logs = logs .. string.format("[%s] %s %s Container ID: %s Container Name: %s\n", date, v.Type, action, id, name) - elseif v.Type == "network" then - local container = v.Actor.Attributes.container or "unknown" - local network = v.Actor.Attributes.type or "unknown" - logs = logs .. string.format("[%s] %s %s Container ID: %s Network Name: %s Network type: %s\n", date, v.Type, action, container, name, network) - elseif v.Type == "image" then - local id = v.Actor.ID or "unknown" - logs = logs .. string.format("[%s] %s %s Image: %s Image name: %s\n", date, v.Type, action, id, name) - end - end - end - - luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}}) -end - -local calculate_cpu_percent = function(d) - if type(d) ~= "table" then - return - end - - local cpu_count = tonumber(d["cpu_stats"]["online_cpus"]) - local cpu_percent = 0.0 - local cpu_delta = tonumber(d["cpu_stats"]["cpu_usage"]["total_usage"]) - tonumber(d["precpu_stats"]["cpu_usage"]["total_usage"]) - local system_delta = tonumber(d["cpu_stats"]["system_cpu_usage"]) - tonumber(d["precpu_stats"]["system_cpu_usage"]) - if system_delta > 0.0 then - cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count) - end - - return cpu_percent -end - -local get_memory = function(d) - if type(d) ~= "table" then - return - end - - local limit =tonumber(d["memory_stats"]["limit"]) - local usage = tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"]) - - return usage, limit -end - -local get_rx_tx = function(d) - if type(d) ~="table" then - return - end - - local data = {} - if type(d["networks"]) == "table" then - for e, v in pairs(d["networks"]) do - data[e] = { - bw_tx = tonumber(v.tx_bytes), - bw_rx = tonumber(v.rx_bytes) - } - end - end - - return data -end - -function action_get_container_stats(container_id) - if container_id then - local dk = docker.new() - local response = dk.containers:inspect({id = container_id}) - if response.code == 200 and response.body.State.Running then - response = dk.containers:stats({id = container_id, query = {stream = false}}) - if response.code == 200 then - local container_stats = response.body - local cpu_percent = calculate_cpu_percent(container_stats) - local mem_useage, mem_limit = get_memory(container_stats) - local bw_rxtx = get_rx_tx(container_stats) - luci.http.status(response.code, response.body.message) - luci.http.prepare_content("application/json") - luci.http.write_json({ - cpu_percent = cpu_percent, - memory = { - mem_useage = mem_useage, - mem_limit = mem_limit - }, - bw_rxtx = bw_rxtx - }) - else - luci.http.status(response.code, response.body.message) - luci.http.prepare_content("text/plain") - luci.http.write(response.body.message) - end - else - if response.code == 200 then - luci.http.status(500, "container "..container_id.." not running") - luci.http.prepare_content("text/plain") - luci.http.write("Container "..container_id.." not running") - else - luci.http.status(response.code, response.body.message) - luci.http.prepare_content("text/plain") - luci.http.write(response.body.message) - end - end - else - luci.http.status(404, "No container name or id") - luci.http.prepare_content("text/plain") - luci.http.write("No container name or id") - end -end - -function action_confirm() - local data = docker:read_status() - if data then - data = data:gsub("\n","
"):gsub(" "," ") - code = 202 - msg = data - else - code = 200 - msg = "finish" - data = "finish" - end - - luci.http.status(code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({info = data}) -end - -function download_archive() - local id = luci.http.formvalue("id") - local path = luci.http.formvalue("path") - local dk = docker.new() - local first - - local cb = function(res, chunk) - if res.code == 200 then - if not first then - first = true - luci.http.header('Content-Disposition', 'inline; filename="archive.tar"') - luci.http.header('Content-Type', 'application\/x-tar') - end - luci.ltn12.pump.all(chunk, luci.http.write) - else - if not first then - first = true - luci.http.prepare_content("text/plain") - end - luci.ltn12.pump.all(chunk, luci.http.write) - end - end - - local res = dk.containers:get_archive({ - id = id, - query = { - path = path - } - }, cb) -end - -function upload_archive(container_id) - local path = luci.http.formvalue("upload-path") - local dk = docker.new() - local ltn12 = require "luci.ltn12" - - local rec_send = function(sinkout) - luci.http.setfilehandler(function (meta, chunk, eof) - if chunk then - ltn12.pump.step(ltn12.source.string(chunk), sinkout) - end - end) - end - - local res = dk.containers:put_archive({ - id = container_id, - query = { - path = path - }, - body = rec_send - }) - - local msg = res and res.body and res.body.message or nil - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) -end - -function save_images(container_id) - local names = luci.http.formvalue("names") - local dk = docker.new() - local first - - local cb = function(res, chunk) - if res.code == 200 then - if not first then - first = true - luci.http.status(res.code, res.message) - 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: saving" .. " " .. container_id .. "...") - local res = dk.images:get({ - id = container_id, - query = { - names = names - } - }, cb) - docker:clear_status() - - local msg = res and res.body and res.body.message or nil - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) -end - -function load_images() - local path = luci.http.formvalue("upload-path") - local dk = docker.new() - local ltn12 = require "luci.ltn12" - - local rec_send = function(sinkout) - luci.http.setfilehandler(function (meta, chunk, eof) - if chunk then - ltn12.pump.step(ltn12.source.string(chunk), sinkout) - end - end) - end - - docker:write_status("Images: loading...") - local res = dk.images:load({body = rec_send}) - local msg = res and res.body and ( res.body.message or res.body.stream or res.body.error ) or nil - if res.code == 200 and msg and msg:match("Loaded image ID") then - docker:clear_status() - luci.http.status(res.code, msg) - else - docker:append_status("code:" .. res.code.." ".. msg) - luci.http.status(300, msg) - end - - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) -end - -function import_images() - local src = luci.http.formvalue("src") - local itag = luci.http.formvalue("tag") - local dk = docker.new() - local ltn12 = require "luci.ltn12" - - local rec_send = function(sinkout) - luci.http.setfilehandler(function (meta, chunk, eof) - if chunk then - ltn12.pump.step(ltn12.source.string(chunk), sinkout) - end - end) - end - - docker:write_status("Images: importing".. " ".. itag .."...\n") - local repo = itag and itag:match("^([^:]+)") - local tag = itag and itag:match("^[^:]-:([^:]+)") - local res = dk.images:create({ - query = { - fromSrc = src or "-", - repo = repo or nil, - tag = tag or nil - }, - body = not src and rec_send or nil - }, docker.import_image_show_status_cb) - - local msg = res and res.body and ( res.body.message )or nil - if not msg and #res.body == 0 then - msg = res.body.status or res.body.error - elseif not msg and #res.body >= 1 then - msg = res.body[#res.body].status or res.body[#res.body].error - end - - if res.code == 200 and msg and msg:match("sha256:") then - docker:clear_status() - else - docker:append_status("code:" .. res.code.." ".. msg) - end - - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) -end - -function get_image_tags(image_id) - if not image_id then - luci.http.status(400, "no image id") - luci.http.prepare_content("application/json") - luci.http.write_json({message = "no image id"}) - return - end - - local dk = docker.new() - local res = dk.images:inspect({ - id = image_id - }) - local msg = res and res.body and res.body.message or nil - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - - if res.code == 200 then - local tags = res.body.RepoTags - luci.http.write_json({tags = tags}) - else - local msg = res and res.body and res.body.message or nil - luci.http.write_json({message = msg}) - end -end - -function tag_image(image_id) - local src = luci.http.formvalue("tag") - local image_id = image_id or luci.http.formvalue("id") - - if type(src) ~= "string" or not image_id then - luci.http.status(400, "no image id or tag") - luci.http.prepare_content("application/json") - luci.http.write_json({message = "no image id or tag"}) - return - end - - local repo = src:match("^([^:]+)") - local tag = src:match("^[^:]-:([^:]+)") - local dk = docker.new() - local res = dk.images:tag({ - id = image_id, - query={ - repo=repo, - tag=tag - } - }) - local msg = res and res.body and res.body.message or nil - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - - if res.code == 201 then - local tags = res.body.RepoTags - luci.http.write_json({tags = tags}) - else - local msg = res and res.body and res.body.message or nil - luci.http.write_json({message = msg}) - end -end - -function untag_image(tag) - local tag = tag or luci.http.formvalue("tag") - - if not tag then - luci.http.status(400, "no tag name") - luci.http.prepare_content("application/json") - luci.http.write_json({message = "no tag name"}) - return - end - - local dk = docker.new() - local res = dk.images:inspect({name = tag}) - - if res.code == 200 then - local tags = res.body.RepoTags - if #tags > 1 then - local r = dk.images:remove({name = tag}) - local msg = r and r.body and r.body.message or nil - luci.http.status(r.code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) - else - luci.http.status(500, "Cannot remove the last tag") - luci.http.prepare_content("application/json") - luci.http.write_json({message = "Cannot remove the last tag"}) - end - else - 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 diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua deleted file mode 100644 index 6fd831d370..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua +++ /dev/null @@ -1,72 +0,0 @@ --- Copyright 2021 Florian Eckert --- Licensed to the public under the Apache License 2.0. - -local m, s, o - -m = Map("dockerd", - translate("Docker - Configuration"), - translate("DockerMan is a simple docker manager client for LuCI")) - -s = m:section(NamedSection, "globals", "section", translate("Global settings")) - -o = s:option(Flag, "remote_endpoint", - translate("Remote Endpoint"), - translate("Connect to remote endpoint")) -o.rmempty = false - -o = s:option(Value, "remote_host", - translate("Remote Host"), - translate("Host or IP Address for the connection to a remote docker instance")) -o.datatype = "host" -o.rmempty = false -o.optional = false -o.placeholder = "10.1.1.2" -o:depends("remote_endpoint", 1) - -o = s:option(Value, "remote_port", - translate("Remote Port")) -o.placeholder = "2375" -o.datatype = "port" -o.rmempty = false -o.optional = false -o:depends("remote_endpoint", 1) - -if nixio.fs.access("/usr/bin/dockerd") then - o = s:option(Value, "data_root", - translate("Docker Root Dir")) - o.placeholder = "/opt/docker/" - o:depends("remote_endpoint", 0) - - o = s:option(Value, "bip", - translate("Default bridge"), - translate("Configure the default bridge network")) - o.placeholder = "172.17.0.1/16" - o.datatype = "ipaddr" - o:depends("remote_endpoint", 0) - - o = s:option(DynamicList, "registry_mirrors", - translate("Registry Mirrors"), - translate("It replaces the daemon registry mirrors with a new set of registry mirrors")) - o.placeholder = translate("Example: https://hub-mirror.c.163.com") - o:depends("remote_endpoint", 0) - - o = s:option(ListValue, "log_level", - translate("Log Level"), - translate('Set the logging level')) - o:value("debug", translate("Debug")) - o:value("", translate("Info")) -- This is the default debug level from the deamon is optin is not set - o:value("warn", translate("Warning")) - o:value("error", translate("Error")) - o:value("fatal", translate("Fatal")) - o.rmempty = true - o:depends("remote_endpoint", 0) - - o = s:option(DynamicList, "hosts", - translate("Client connection"), - translate('Specifies where the Docker daemon will listen for client connections (default: unix:///var/run/docker.sock)')) - o.placeholder = translate("Example: tcp://0.0.0.0:2375") - o.rmempty = true - o:depends("remote_endpoint", 0) -end - -return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua deleted file mode 100644 index 5caad4f93f..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua +++ /dev/null @@ -1,798 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -require "luci.util" - -local docker = require "luci.model.docker" -local dk = docker.new() - -container_id = arg[1] -local action = arg[2] or "info" - -local m, s, o -local images, networks, container_info, res - -if not container_id then - return -end - -res = dk.containers:inspect({id = container_id}) -if res.code < 300 then - container_info = res.body -else - return -end - -res = dk.networks:list() -if res.code < 300 then - networks = res.body -else - return -end - -local get_ports = function(d) - local data - - if d.HostConfig and d.HostConfig.PortBindings then - for inter, out in pairs(d.HostConfig.PortBindings) do - data = (data and (data .. "
") or "") .. out[1]["HostPort"] .. ":" .. inter - end - end - - return data -end - -local get_env = function(d) - local data - - if d.Config and d.Config.Env then - for _,v in ipairs(d.Config.Env) do - data = (data and (data .. "
") or "") .. v - end - end - - return data -end - -local get_command = function(d) - local data - - if d.Config and d.Config.Cmd then - for _,v in ipairs(d.Config.Cmd) do - data = (data and (data .. " ") or "") .. v - end - end - - return data -end - -local get_mounts = function(d) - local data - - if d.Mounts then - for _,v in ipairs(d.Mounts) do - local v_sorce_d, v_dest_d - local v_sorce = "" - local v_dest = "" - for v_sorce_d in v["Source"]:gmatch('[^/]+') do - if v_sorce_d and #v_sorce_d > 12 then - v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..." - else - v_sorce = v_sorce .."/".. v_sorce_d - end - end - for v_dest_d in v["Destination"]:gmatch('[^/]+') do - if v_dest_d and #v_dest_d > 12 then - v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..." - else - v_dest = v_dest .."/".. v_dest_d - end - end - data = (data and (data .. "
") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "") - end - end - - return data -end - -local get_device = function(d) - local data - - if d.HostConfig and d.HostConfig.Devices then - for _,v in ipairs(d.HostConfig.Devices) do - data = (data and (data .. "
") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "") - end - end - - return data -end - -local get_links = function(d) - local data - - if d.HostConfig and d.HostConfig.Links then - for _,v in ipairs(d.HostConfig.Links) do - data = (data and (data .. "
") or "") .. v - end - end - - return data -end - -local get_tmpfs = function(d) - local data - - if d.HostConfig and d.HostConfig.Tmpfs then - for k, v in pairs(d.HostConfig.Tmpfs) do - data = (data and (data .. "
") or "") .. k .. (v~="" and ":" or "")..v - end - end - - return data -end - -local get_dns = function(d) - local data - - if d.HostConfig and d.HostConfig.Dns then - for _, v in ipairs(d.HostConfig.Dns) do - data = (data and (data .. "
") or "") .. v - end - end - - return data -end - -local get_sysctl = function(d) - local data - - if d.HostConfig and d.HostConfig.Sysctls then - for k, v in pairs(d.HostConfig.Sysctls) do - data = (data and (data .. "
") or "") .. k..":"..v - end - end - - return data -end - -local get_networks = function(d) - local data={} - - if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then - for k,v in pairs(d.NetworkSettings.Networks) do - data[k] = v.IPAddress or "" - end - end - - return data -end - - -local start_stop_remove = function(m, cmd) - local res - - docker:clear_status() - docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...") - - if cmd ~= "upgrade" then - res = dk.containers[cmd](dk, {id = container_id}) - else - res = dk.containers_upgrade(dk, {id = container_id}) - end - - if res and res.code >= 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) - else - docker:clear_status() - if cmd ~= "remove" and cmd ~= "upgrade" then - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) - else - luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) - end - end -end - -m=SimpleForm("docker", - translatef("Docker - Container (%s)", container_info.Name:sub(2)), - translate("On this page, the selected container can be managed.")) -m.redirect = luci.dispatcher.build_url("admin/docker/containers") - -s = m:section(SimpleSection) -s.template = "dockerman/apply_widget" -s.err=docker:read_status() -s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") -if s.err then - docker:clear_status() -end - -s = m:section(Table,{{}}) -s.notitle=true -s.rowcolors=false -s.template = "cbi/nullsection" - -o = s:option(Button, "_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 - -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 - -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 - -o = s:option(Button, "_kill") -o.template = "dockerman/cbi/inlinebutton" -o.inputtitle=translate("Kill") -o.inputstyle = "reset" -o.forcewrite = true -o.write = function(self, section) - start_stop_remove(m,"kill") -end - -o = s:option(Button, "_upgrade") -o.template = "dockerman/cbi/inlinebutton" -o.inputtitle=translate("Upgrade") -o.inputstyle = "reload" -o.forcewrite = true -o.write = function(self, section) - start_stop_remove(m,"upgrade") -end - -o = s:option(Button, "_duplicate") -o.template = "dockerman/cbi/inlinebutton" -o.inputtitle=translate("Duplicate/Edit") -o.inputstyle = "add" -o.forcewrite = true -o.write = function(self, section) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id)) -end - -o = s:option(Button, "_remove") -o.template = "dockerman/cbi/inlinebutton" -o.inputtitle=translate("Remove") -o.inputstyle = "remove" -o.forcewrite = true -o.write = function(self, section) - start_stop_remove(m,"remove") -end - -s = m:section(SimpleSection) -s.template = "dockerman/container" - -if action == "info" then - m.submit = false - m.reset = false - table_info = { - ["01name"] = { - _key = translate("Name"), - _value = container_info.Name:sub(2) or "-", - _button=translate("Update") - }, - ["02id"] = { - _key = translate("ID"), - _value = container_info.Id or "-" - }, - ["03image"] = { - _key = translate("Image"), - _value = container_info.Config.Image .. "
" .. container_info.Image - }, - ["04status"] = { - _key = translate("Status"), - _value = container_info.State and container_info.State.Status or "-" - }, - ["05created"] = { - _key = translate("Created"), - _value = container_info.Created or "-" - }, - } - - if container_info.State.Status == "running" then - table_info["06start"] = { - _key = translate("Start Time"), - _value = container_info.State and container_info.State.StartedAt or "-" - } - else - table_info["06start"] = { - _key = translate("Finish Time"), - _value = container_info.State and container_info.State.FinishedAt or "-" - } - end - - table_info["07healthy"] = { - _key = translate("Healthy"), - _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-" - } - table_info["08restart"] = { - _key = translate("Restart Policy"), - _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", - _button=translate("Update") - } - table_info["081user"] = { - _key = translate("User"), - _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-" - } - table_info["09mount"] = { - _key = translate("Mount/Volume"), - _value = get_mounts(container_info) or "-" - } - table_info["10cmd"] = { - _key = translate("Command"), - _value = get_command(container_info) or "-" - } - table_info["11env"] = { - _key = translate("Env"), - _value = get_env(container_info) or "-" - } - table_info["12ports"] = { - _key = translate("Ports"), - _value = get_ports(container_info) or "-" - } - table_info["13links"] = { - _key = translate("Links"), - _value = get_links(container_info) or "-" - } - table_info["14device"] = { - _key = translate("Device"), - _value = get_device(container_info) or "-" - } - table_info["15tmpfs"] = { - _key = translate("Tmpfs"), - _value = get_tmpfs(container_info) or "-" - } - table_info["16dns"] = { - _key = translate("DNS"), - _value = get_dns(container_info) or "-" - } - table_info["17sysctl"] = { - _key = translate("Sysctl"), - _value = get_sysctl(container_info) or "-" - } - - info_networks = get_networks(container_info) - list_networks = {} - for _, v in ipairs (networks) do - if v.Name then - local parent = v.Options and v.Options.parent or nil - local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil - ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil - local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") - list_networks[v.Name] = network_name - end - end - - if type(info_networks)== "table" then - for k,v in pairs(info_networks) do - table_info["14network"..k] = { - _key = translate("Network"), - value = k.. (v~="" and (" | ".. v) or ""), - _button=translate("Disconnect") - } - list_networks[k]=nil - end - end - - table_info["15connect"] = { - _key = translate("Connect Network"), - _value = list_networks ,_opts = "", - _button=translate("Connect") - } - - s = m:section(Table,table_info) - s.nodescr=true - s.formvalue=function(self, section) - return table_info - end - - o = s:option(DummyValue, "_key", translate("Info")) - o.width = "20%" - - o = s:option(ListValue, "_value") - o.render = function(self, section, scope) - if table_info[section]._key == translate("Name") then - self:reset_values() - self.template = "cbi/value" - self.size = 30 - self.keylist = {} - self.vallist = {} - self.default=table_info[section]._value - Value.render(self, section, scope) - elseif table_info[section]._key == translate("Restart Policy") then - self.template = "cbi/lvalue" - self:reset_values() - self.size = nil - self:value("no", "No") - self:value("unless-stopped", "Unless stopped") - self:value("always", "Always") - self:value("on-failure", "On failure") - self.default=table_info[section]._value - ListValue.render(self, section, scope) - elseif table_info[section]._key == translate("Connect Network") then - self.template = "cbi/lvalue" - self:reset_values() - self.size = nil - for k,v in pairs(list_networks) do - if k ~= "host" then - self:value(k,v) - end - end - self.default=table_info[section]._value - ListValue.render(self, section, scope) - else - self:reset_values() - self.rawhtml=true - self.template = "cbi/dvalue" - self.default=table_info[section]._value - DummyValue.render(self, section, scope) - end - end - o.forcewrite = true - o.write = function(self, section, value) - table_info[section]._value=value - end - o.validate = function(self, value) - return value - end - - o = s:option(Value, "_opts") - o.forcewrite = true - o.write = function(self, section, value) - table_info[section]._opts=value - end - o.validate = function(self, value) - return value - end - o.render = function(self, section, scope) - if table_info[section]._key==translate("Connect Network") then - self.template = "cbi/value" - self.keylist = {} - self.vallist = {} - self.placeholder = "10.1.1.254" - self.datatype = "ip4addr" - self.default=table_info[section]._opts - Value.render(self, section, scope) - else - self.rawhtml=true - self.template = "cbi/dvalue" - self.default=table_info[section]._opts - DummyValue.render(self, section, scope) - end - end - - o = s:option(Button, "_button") - o.forcewrite = true - o.render = function(self, section, scope) - if table_info[section]._button and table_info[section]._value ~= nil then - self.inputtitle=table_info[section]._button - self.template = "cbi/button" - self.inputstyle = "edit" - Button.render(self, section, scope) - else - self.template = "cbi/dvalue" - self.default="" - DummyValue.render(self, section, scope) - end - end - o.write = function(self, section, value) - local res - - docker:clear_status() - - if section == "01name" then - docker:append_status("Containers: rename " .. container_id .. "...") - local new_name = table_info[section]._value - res = dk.containers:rename({ - id = container_id, - query = { - name=new_name - } - }) - elseif section == "08restart" then - docker:append_status("Containers: update " .. container_id .. "...") - local new_restart = table_info[section]._value - res = dk.containers:update({ - id = container_id, - body = { - RestartPolicy = { - Name = new_restart - } - } - }) - elseif table_info[section]._key == translate("Network") then - local _,_,leave_network - - _, _, leave_network = table_info[section]._value:find("(.-) | .+") - leave_network = leave_network or table_info[section]._value - docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...") - res = dk.networks:disconnect({ - name = leave_network, - body = { - Container = container_id - } - }) - elseif section == "15connect" then - local connect_network = table_info[section]._value - local network_opiton - if connect_network ~= "none" - and connect_network ~= "bridge" - and connect_network ~= "host" then - - network_opiton = table_info[section]._opts ~= "" and { - IPAMConfig={ - IPv4Address=table_info[section]._opts - } - } or nil - end - docker:append_status("Network: connect " .. connect_network .. container_id .. "...") - res = dk.networks:connect({ - name = connect_network, - body = { - Container = container_id, - EndpointConfig= network_opiton - } - }) - end - - if res and res.code > 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - else - docker:clear_status() - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info")) - end -elseif action == "resources" then - s = m:section(SimpleSection) - o = s:option( Value, "cpus", - translate("CPUs"), - translate("Number of CPUs. Number is a fractional number. 0.000 means no limit.")) - o.placeholder = "1.5" - o.rmempty = true - o.datatype="ufloat" - o.default = container_info.HostConfig.NanoCpus / (10^9) - - o = s:option(Value, "cpushares", - translate("CPU Shares Weight"), - translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024.")) - o.placeholder = "1024" - o.rmempty = true - o.datatype="uinteger" - o.default = container_info.HostConfig.CpuShares - - o = s:option(Value, "memory", - translate("Memory"), - translate("Memory limit (format: []). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.")) - o.placeholder = "128m" - o.rmempty = true - o.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0 - - o = s:option(Value, "blkioweight", - translate("Block IO Weight"), - translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000.")) - o.placeholder = "500" - o.rmempty = true - o.datatype="uinteger" - o.default = container_info.HostConfig.BlkioWeight - - m.handle = function(self, state, data) - if state == FORM_VALID then - local memory = data.memory - if memory and memory ~= 0 then - _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") - if n then - unit = unit and unit:sub(1,1):upper() or "B" - if unit == "M" then - memory = tonumber(n) * 1024 * 1024 - elseif unit == "G" then - memory = tonumber(n) * 1024 * 1024 * 1024 - elseif unit == "K" then - memory = tonumber(n) * 1024 - else - memory = tonumber(n) - end - end - end - - request_body = { - BlkioWeight = tonumber(data.blkioweight), - NanoCPUs = tonumber(data.cpus)*10^9, - Memory = tonumber(memory), - CpuShares = tonumber(data.cpushares) - } - - docker:write_status("Containers: update " .. container_id .. "...") - local res = dk.containers:update({id = container_id, body = request_body}) - if res and res.code >= 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - else - docker:clear_status() - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources")) - end - end - -elseif action == "file" then - s = m:section(SimpleSection) - s.template = "dockerman/container_file" - s.container = container_id - m.submit = false - m.reset = false -elseif action == "inspect" then - s = m:section(SimpleSection) - s.syslog = luci.jsonc.stringify(container_info, true) - s.title = translate("Container Inspect") - s.template = "dockerman/logs" - m.submit = false - m.reset = false -elseif action == "logs" then - local logs = "" - local query ={ - stdout = 1, - stderr = 1, - tail = 1000 - } - - s = m:section(SimpleSection) - - logs = dk.containers:logs({id = container_id, query = query}) - if logs.code == 200 then - s.syslog=logs.body - else - s.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body - end - - s.title=translate("Container Logs") - s.template = "dockerman/logs" - m.submit = false - m.reset = false -elseif action == "console" then - m.submit = false - m.reset = false - local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil - local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil - - if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then - local cmd = "/bin/sh" - local uid - - s = m:section(SimpleSection) - - o = s:option(Value, "command", translate("Command")) - o:value("/bin/sh", "/bin/sh") - o:value("/bin/ash", "/bin/ash") - o:value("/bin/bash", "/bin/bash") - o.default = "/bin/sh" - o.forcewrite = true - o.write = function(self, section, value) - cmd = value - end - - o = s:option(Value, "uid", translate("UID")) - o.forcewrite = true - o.write = function(self, section, value) - uid = value - end - - o = s:option(Button, "connect") - o.render = function(self, section, scope) - self.inputstyle = "add" - self.title = " " - self.inputtitle = translate("Connect") - Button.render(self, section, scope) - end - o.write = function(self, section) - local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil - local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil - - if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$")then - return - end - - local pid = luci.util.trim(luci.util.exec("netstat -lnpt | grep :7682 | grep ttyd | tr -s ' ' | cut -d ' ' -f7 | cut -d'/' -f1")) - if pid and pid ~= "" then - luci.util.exec("kill -9 " .. pid) - end - - local hosts - local uci = require "luci.model.uci".cursor() - local remote = uci:get_bool("dockerd", "globals", "remote_endpoint") or false - local host = nil - local port = nil - local socket = nil - - if remote then - host = uci:get("dockerd", "globals", "remote_host") or nil - port = uci:get("dockerd", "globals", "remote_port") or nil - else - socket = uci:get("dockerd", "globals", "socket_path") or "/var/run/docker.sock" - end - - if remote and host and port then - hosts = host .. ':'.. port - elseif socket then - hosts = socket - else - return - end - - if uid and uid ~= "" then - uid = "-u " .. uid - else - uid = "" - end - - local start_cmd = string.format('%s -d 2 --once -p 7682 %s -H "unix://%s" exec -it %s %s %s&', cmd_ttyd, cmd_docker, hosts, uid, container_id, cmd) - - os.execute(start_cmd) - - o = s:option(DummyValue, "console") - o.container_id = container_id - o.template = "dockerman/container_console" - end - end -elseif action == "stats" then - local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}}) - local container_top - - if response.code ~= 409 then - if response.code ~= 200 then - response = dk.containers:top({id = container_id}) - end - - if response.code ~= 200 then - response = dk.containers:top({id = container_id, query = {ps_args="-ww"}}) - end - - if response.code == 200 then - container_top = response.body - end - - local table_stats = { - cpu = { - key=translate("CPU Usage"), - value='-' - }, - memory = { - key=translate("Memory Usage"), - value='-' - } - } - s = m:section(Table, table_stats, translate("Stats")) - s:option(DummyValue, "key", translate("Stats")).width="33%" - s:option(DummyValue, "value") - - s = m:section(SimpleSection) - s.container_id = container_id - s.template = "dockerman/container_stats" - end - - if type(container_top) == "table" then - local top_section = m:section(Table, container_top.Processes, translate("TOP")) - for i, v in ipairs(container_top.Titles) do - top_section:option(DummyValue, i, translate(v)) - end - end - - m.submit = false - m.reset = false -end - -return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua deleted file mode 100644 index 2a337a62a9..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua +++ /dev/null @@ -1,236 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -local http = require "luci.http" -local docker = require "luci.model.docker" - -local m, s, o -local images, networks, containers, res - -local dk = docker.new() -res = dk.images:list() -if res.code <300 then - images = res.body -else - return -end - -res = dk.networks:list() -if res.code <300 then - networks = res.body -else - return -end - -res = dk.containers:list({ - query = { - all=true - } -}) -if res.code <300 then - containers = res.body -else - return -end - -local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode - -function get_containers() - local data = {} - - if type(containers) ~= "table" then - return nil - end - - for i, v in ipairs(containers) do - local index = v.Id - - data[index]={} - data[index]["_selected"] = 0 - data[index]["_id"] = v.Id:sub(1,12) - data[index]["_name"] = v.Names[1]:sub(2) - data[index]["_status"] = v.Status - - if v.Status:find("^Up") then - data[index]["_status"] = ''.. data[index]["_status"] .. "" - else - data[index]["_status"] = ''.. data[index]["_status"] .. "" - 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 ('') 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 "" 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] or ""):gsub("(.-)@.+", "%1") .. ":") - end - end - - data[index]["_image_id"] = v.ImageID:sub(8,20) - data[index]["_command"] = v.Command - end - - return data -end - -local container_list = get_containers() - -m = SimpleForm("docker", - translate("Docker - Containers"), - translate("This page displays all containers that have been created on the connected docker host.")) -m.submit=false -m.reset=false - -s = m:section(SimpleSection) -s.template = "dockerman/apply_widget" -s.err=docker:read_status() -s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") -if s.err then - docker:clear_status() -end - -s = m:section(Table, container_list, translate("Containers overview")) -s.addremove = false -s.sectionhead = translate("Containers") -s.sortable = false -s.template = "cbi/tblsection" -s.extedit = luci.dispatcher.build_url("admin", "docker", "container","%s") - -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 container_selected = {} - - for k in pairs(container_list) do - if container_list[k]._selected == 1 then - container_selected[#container_selected + 1] = container_list[k]._name - end - end - - if #container_selected > 0 then - local success = true - - docker:clear_status() - for _, cont in ipairs(container_selected) do - docker:append_status("Containers: " .. cmd .. " " .. cont .. "...") - local res = dk.containers[cmd](dk, {id = cont}) - if res and res.code >= 300 then - success = false - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") - else - docker:append_status("done\n") - end - end - - if success then - docker:clear_status() - end - - luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) - end -end - -s = m:section(Table,{{}}) -s.notitle=true -s.rowcolors=false -s.template="cbi/nullsection" - -o = s:option(Button, "_new") -o.inputtitle= translate("Add") -o.template = "dockerman/cbi/inlinebutton" -o.inputstyle = "add" -o.forcewrite = true -o.write = function(self, section) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) -end - -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 - -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 - -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 - -o = s:option(Button, "_kill") -o.template = "dockerman/cbi/inlinebutton" -o.inputtitle=translate("Kill") -o.inputstyle = "reset" -o.forcewrite = true -o.write = function(self, section) - start_stop_remove(m,"kill") -end - -o = s:option(Button, "_remove") -o.template = "dockerman/cbi/inlinebutton" -o.inputtitle=translate("Remove") -o.inputstyle = "remove" -o.forcewrite = true -o.write = function(self, section) - start_stop_remove(m,"remove") -end - -return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua deleted file mode 100644 index 2b84de3b8f..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua +++ /dev/null @@ -1,280 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -local docker = require "luci.model.docker" -local dk = docker.new() - -local containers, images, res -local m, s, o - -res = dk.images:list() -if res.code < 300 then - images = res.body -else - return -end - -res = dk.containers:list({ - query = { - all=true - } -}) -if res.code < 300 then - containers = res.body -else - return -end - -function get_images() - local data = {} - - for i, v in ipairs(images) do - local index = v.Created .. v.Id - - data[index]={} - data[index]["_selected"] = 0 - data[index]["id"] = v.Id:sub(8) - data[index]["_id"] = '' .. v.Id:sub(8,20) .. '' - - if v.RepoTags and next(v.RepoTags)~=nil then - for i, v1 in ipairs(v.RepoTags) do - data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "
" )or "") .. ((v1:match("") or (#v.RepoTags == 1)) and v1 or ('' .. v1 .. '')) - - if not data[index]["tag"] then - data[index]["tag"] = v1 - end - end - else - data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+") - data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "" ).. ":" - end - - data[index]["_tags"] = data[index]["_tags"]:gsub("","<none>") - for ci,cv in ipairs(containers) do - if v.Id == cv.ImageID then - data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. - ''.. cv.Names[1]:sub(2).."" - end - end - - data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB" - data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created) - end - - return data -end - -local image_list = get_images() - -m = SimpleForm("docker", - translate("Docker - Images"), - translate("On this page all images are displayed that are available on the system and with which a container can be created.")) -m.submit=false -m.reset=false - -local pull_value={ - _image_tag_name="", - _registry="index.docker.io" -} - -s = m:section(SimpleSection, - translate("Pull Image"), - translate("By entering a valid image name with the corresponding version, the docker image can be downloaded from the configured registry.")) -s.template="cbi/nullsection" - -o = s:option(Value, "_image_tag_name") -o.template = "dockerman/cbi/inlinevalue" -o.placeholder="lisaac/luci:latest" -o.write = function(self, section, value) - local hastag = value:find(":") - - if not hastag then - value = value .. ":latest" - end - pull_value["_image_tag_name"] = value -end - -o = s:option(Button, "_pull") -o.inputtitle= translate("Pull") -o.template = "dockerman/cbi/inlinebutton" -o.inputstyle = "add" -o.write = function(self, section) - local tag = pull_value["_image_tag_name"] - local json_stringify = luci.jsonc and luci.jsonc.stringify - - if tag and tag ~= "" then - docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n") - local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb) - - 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 - -s = m:section(SimpleSection, - translate("Import Image"), - translate("When pressing the Import button, both a local image can be loaded onto the system and a valid image tar can be downloaded from remote.")) - -o = s:option(DummyValue, "_image_import") -o.template = "dockerman/images_import" - -s = m:section(Table, image_list, translate("Images overview")) - -o = s:option(Flag, "_selected","") -o.disabled = 0 -o.enabled = 1 -o.default = 0 -o.write = function(self, section, value) - image_list[section]._selected = value -end - -o = s:option(DummyValue, "_tags", translate("RepoTags")) -o.rawhtml = true - -o = s:option(DummyValue, "_containers", translate("Containers")) -o.rawhtml = true - -o = s:option(DummyValue, "_size", translate("Size")) - -o = s:option(DummyValue, "_created", translate("Created")) - -o = s:option(DummyValue, "_id", translate("ID")) -o.rawhtml = true - -local remove_action = function(force) - local image_selected = {} - - for k in pairs(image_list) do - if image_list[k]._selected == 1 then - image_selected[#image_selected+1] = (image_list[k]["_tags"]:match("
") or image_list[k]["_tags"]:match("<none>")) and image_list[k].id or image_list[k].tag - end - end - - if next(image_selected) ~= nil then - local success = true - - docker:clear_status() - for _, img in ipairs(image_selected) do - 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 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: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 - -s = m:section(SimpleSection) -s.template = "dockerman/apply_widget" -s.err = docker:read_status() -s.err = s.err and s.err:gsub("\n","
"):gsub(" "," ") -if s.err then - docker:clear_status() -end - -s = m:section(Table,{{}}) -s.notitle=true -s.rowcolors=false -s.template="cbi/nullsection" - -o = s:option(Button, "remove") -o.inputtitle= translate("Remove") -o.template = "dockerman/cbi/inlinebutton" -o.inputstyle = "remove" -o.forcewrite = true -o.write = function(self, section) - remove_action() -end - -o = s:option(Button, "forceremove") -o.inputtitle= translate("Force Remove") -o.template = "dockerman/cbi/inlinebutton" -o.inputstyle = "remove" -o.forcewrite = true -o.write = function(self, section) - remove_action(true) -end - -o = s:option(Button, "save") -o.inputtitle= translate("Save") -o.template = "dockerman/cbi/inlinebutton" -o.inputstyle = "edit" -o.forcewrite = true -o.write = function (self, section) - local image_selected = {} - - 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 diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua deleted file mode 100644 index f54acbd16d..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua +++ /dev/null @@ -1,154 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -local docker = require "luci.model.docker" - -local m, s, o -local networks, dk, res - -dk = docker.new() -res = dk.networks:list() -if res.code < 300 then - networks = res.body -else - return -end - -local get_networks = function () - local data = {} - - if type(networks) ~= "table" then - return nil - end - - for i, v in ipairs(networks) do - local index = v.Created .. v.Id - - data[index]={} - data[index]["_selected"] = 0 - data[index]["_id"] = v.Id:sub(1,12) - data[index]["_name"] = v.Name - data[index]["_driver"] = v.Driver - - if v.Driver == "bridge" then - data[index]["_interface"] = v.Options["com.docker.network.bridge.name"] - elseif v.Driver == "macvlan" then - data[index]["_interface"] = v.Options.parent - end - - data[index]["_subnet"] = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil - data[index]["_gateway"] = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil - end - - return data -end - -local network_list = get_networks() - -m = SimpleForm("docker", - translate("Docker - Networks"), - translate("This page displays all docker networks that have been created on the connected docker host.")) -m.submit=false -m.reset=false - -s = m:section(Table, network_list, translate("Networks overview")) -s.nodescr=true - -o = s:option(Flag, "_selected","") -o.template = "dockerman/cbi/xfvalue" -o.disabled = 0 -o.enabled = 1 -o.default = 0 -o.render = function(self, section, scope) - self.disable = 0 - if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then - self.disable = 1 - end - Flag.render(self, section, scope) -end -o.write = function(self, section, value) - network_list[section]._selected = value -end - -o = s:option(DummyValue, "_id", translate("ID")) - -o = s:option(DummyValue, "_name", translate("Network Name")) - -o = s:option(DummyValue, "_driver", translate("Driver")) - -o = s:option(DummyValue, "_interface", translate("Parent Interface")) - -o = s:option(DummyValue, "_subnet", translate("Subnet")) - -o = s:option(DummyValue, "_gateway", translate("Gateway")) - -s = m:section(SimpleSection) -s.template = "dockerman/apply_widget" -s.err = docker:read_status() -s.err = s.err and s.err:gsub("\n","
"):gsub(" "," ") -if s.err then - docker:clear_status() -end - -s = m:section(Table,{{}}) -s.notitle=true -s.rowcolors=false -s.template="cbi/nullsection" - -o = s:option(Button, "_new") -o.inputtitle= translate("New") -o.template = "dockerman/cbi/inlinebutton" -o.notitle=true -o.inputstyle = "add" -o.forcewrite = true -o.write = function(self, section) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) -end - -o = s:option(Button, "_remove") -o.inputtitle= translate("Remove") -o.template = "dockerman/cbi/inlinebutton" -o.inputstyle = "remove" -o.forcewrite = true -o.write = function(self, section) - local network_selected = {} - local network_name_selected = {} - local network_driver_selected = {} - - for k in pairs(network_list) do - if network_list[k]._selected == 1 then - network_selected[#network_selected + 1] = network_list[k]._id - network_name_selected[#network_name_selected + 1] = network_list[k]._name - network_driver_selected[#network_driver_selected + 1] = network_list[k]._driver - end - end - - if next(network_selected) ~= nil then - local success = true - docker:clear_status() - - for ii, net in ipairs(network_selected) do - docker:append_status("Networks: " .. "remove" .. " " .. net .. "...") - local res = dk.networks["remove"](dk, {id = net}) - - if res and res.code >= 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") - success = false - else - docker:append_status("done\n") - if network_driver_selected[ii] == "macvlan" then - docker.remove_macvlan_interface(network_name_selected[ii]) - end - end - end - - if success then - docker:clear_status() - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) - end -end - -return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua deleted file mode 100644 index 5d38a352e0..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua +++ /dev/null @@ -1,902 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -local docker = require "luci.model.docker" - -local m, s, o - -local dk = docker.new() - -local cmd_line = table.concat(arg, '/') -local create_body = {} - -local images = dk.images:list().body -local networks = dk.networks:list().body -local containers = dk.containers:list({ - query = { - all=true - } -}).body - -local is_quot_complete = function(str) - local num = 0, w - require "math" - - if not str then - return true - end - - local num = 0, w - for w in str:gmatch("\"") do - num = num + 1 - end - - if math.fmod(num, 2) ~= 0 then - return false - end - - num = 0 - for w in str:gmatch("\'") do - num = num + 1 - end - - if math.fmod(num, 2) ~= 0 then - return false - end - - return true -end - -function contains(list, x) - for _, v in pairs(list) do - if v == x then - return true - end - end - return false -end - -local resolve_cli = function(cmd_line) - local config = { - advance = 1 - } - - local key_no_val = { - 't', - 'd', - 'i', - 'tty', - 'rm', - 'read_only', - 'interactive', - 'init', - 'help', - 'detach', - 'privileged', - 'P', - 'publish_all', - } - - local key_with_val = { - 'sysctl', - 'add_host', - 'a', - 'attach', - 'blkio_weight_device', - 'cap_add', - 'cap_drop', - 'device', - 'device_cgroup_rule', - 'device_read_bps', - 'device_read_iops', - 'device_write_bps', - 'device_write_iops', - 'dns', - 'dns_option', - 'dns_search', - 'e', - 'env', - 'env_file', - 'expose', - 'group_add', - 'l', - 'label', - 'label_file', - 'link', - 'link_local_ip', - 'log_driver', - 'log_opt', - 'network_alias', - 'p', - 'publish', - 'security_opt', - 'storage_opt', - 'tmpfs', - 'v', - 'volume', - 'volumes_from', - 'blkio_weight', - 'cgroup_parent', - 'cidfile', - 'cpu_period', - 'cpu_quota', - 'cpu_rt_period', - 'cpu_rt_runtime', - 'c', - 'cpu_shares', - 'cpus', - 'cpuset_cpus', - 'cpuset_mems', - 'detach_keys', - 'disable_content_trust', - 'domainname', - 'entrypoint', - 'gpus', - 'health_cmd', - 'health_interval', - 'health_retries', - 'health_start_period', - 'health_timeout', - 'h', - 'hostname', - 'ip', - 'ip6', - 'ipc', - 'isolation', - 'kernel_memory', - 'log_driver', - 'mac_address', - 'm', - 'memory', - 'memory_reservation', - 'memory_swap', - 'memory_swappiness', - 'mount', - 'name', - 'network', - 'no_healthcheck', - 'oom_kill_disable', - 'oom_score_adj', - 'pid', - 'pids_limit', - 'restart', - 'runtime', - 'shm_size', - 'sig_proxy', - 'stop_signal', - 'stop_timeout', - 'ulimit', - 'u', - 'user', - 'userns', - 'uts', - 'volume_driver', - 'w', - 'workdir' - } - - local key_abb = { - net='network', - a='attach', - c='cpu-shares', - d='detach', - e='env', - h='hostname', - i='interactive', - l='label', - m='memory', - p='publish', - P='publish_all', - t='tty', - u='user', - v='volume', - w='workdir' - } - - local key_with_list = { - 'sysctl', - 'add_host', - 'a', - 'attach', - 'blkio_weight_device', - 'cap_add', - 'cap_drop', - 'device', - 'device_cgroup_rule', - 'device_read_bps', - 'device_read_iops', - 'device_write_bps', - 'device_write_iops', - 'dns', - 'dns_optiondns_search', - 'e', - 'env', - 'env_file', - 'expose', - 'group_add', - 'l', - 'label', - 'label_file', - 'link', - 'link_local_ip', - 'log_driver', - 'log_opt', - 'network_alias', - 'p', - 'publish', - 'security_opt', - 'storage_opt', - 'tmpfs', - 'v', - 'volume', - 'volumes_from', - } - - local key = nil - local _key = nil - local val = nil - local is_cmd = false - - cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)") - for w in cmd_line:gmatch("[^%s]+") do - if w =='\\' then - elseif not key and not _key and not is_cmd then - --key=val - key, val = w:match("^%-%-([%lP%-]-)=(.+)") - if not key then - --key val - key = w:match("^%-%-([%lP%-]+)") - if not key then - -- -v val - key = w:match("^%-([%lP%-]+)") - if key then - -- for -dit - if key:match("i") or key:match("t") or key:match("d") then - if key:match("i") then - config[key_abb["i"]] = true - key:gsub("i", "") - end - if key:match("t") then - config[key_abb["t"]] = true - key:gsub("t", "") - end - if key:match("d") then - config[key_abb["d"]] = true - key:gsub("d", "") - end - if key:match("P") then - config[key_abb["P"]] = true - key:gsub("P", "") - end - if key == "" then - key = nil - end - end - end - end - end - if key then - key = key:gsub("-","_") - key = key_abb[key] or key - if contains(key_no_val, key) then - config[key] = true - val = nil - key = nil - elseif contains(key_with_val, key) then - -- if key == "cap_add" then config.privileged = true end - else - key = nil - val = nil - end - else - config.image = w - key = nil - val = nil - is_cmd = true - end - elseif (key or _key) and not is_cmd then - if key == "mount" then - -- we need resolve mount options here - -- type=bind,source=/source,target=/app - local _type = w:match("^type=([^,]+),") or "bind" - local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or "" - local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or "" - local ro = w:match(",readonly") and "ro" or nil - - if source and target then - if _type ~= "tmpfs" then - local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil - val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or "")) - else - local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil - local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil - key = "tmpfs" - val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "") - if not config[key] then - config[key] = {} - end - table.insert( config[key], val ) - key = nil - val = nil - end - end - else - val = w - end - elseif is_cmd then - config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w - end - if (key or _key) and val then - key = _key or key - if contains(key_with_list, key) then - if not config[key] then - config[key] = {} - end - if _key then - config[key][#config[key]] = config[key][#config[key]] .. " " .. w - else - table.insert( config[key], val ) - end - if is_quot_complete(config[key][#config[key]]) then - config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "") - _key = nil - else - _key = key - end - else - config[key] = (config[key] and (config[key] .. " ") or "") .. val - if is_quot_complete(config[key]) then - config[key] = config[key]:gsub("[\"\']", "") - _key = nil - else - _key = key - end - end - key = nil - val = nil - end - end - - return config -end - -local default_config = {} - -if cmd_line and cmd_line:match("^DOCKERCLI.+") then - default_config = resolve_cli(cmd_line) -elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then - local container_id = cmd_line:match("^duplicate/(.+)") - create_body = dk:containers_duplicate_config({id = container_id}) or {} - - if not create_body.HostConfig then - create_body.HostConfig = {} - end - - if next(create_body) ~= nil then - default_config.name = nil - default_config.image = create_body.Image - default_config.hostname = create_body.Hostname - default_config.tty = create_body.Tty and true or false - default_config.interactive = create_body.OpenStdin and true or false - default_config.privileged = create_body.HostConfig.Privileged and true or false - default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil - default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil - default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil - default_config.link = create_body.HostConfig.Links - default_config.env = create_body.Env - default_config.dns = create_body.HostConfig.Dns - default_config.volume = create_body.HostConfig.Binds - default_config.cap_add = create_body.HostConfig.CapAdd - default_config.publish_all = create_body.HostConfig.PublishAllPorts - - if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then - default_config.sysctl = {} - for k, v in pairs(create_body.HostConfig.Sysctls) do - table.insert( default_config.sysctl, k.."="..v ) - end - end - - if create_body.HostConfig.LogConfig and create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then - default_config.log_opt = {} - for k, v in pairs(create_body.HostConfig.LogConfig.Config) do - table.insert( default_config.log_opt, k.."="..v ) - end - end - - if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then - default_config.publish = {} - for k, v in pairs(create_body.HostConfig.PortBindings) do - table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") ) - end - end - - default_config.user = create_body.User or nil - default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil - default_config.advance = 1 - default_config.cpus = create_body.HostConfig.NanoCPUs - default_config.cpu_shares = create_body.HostConfig.CpuShares - default_config.memory = create_body.HostConfig.Memory - default_config.blkio_weight = create_body.HostConfig.BlkioWeight - - if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then - default_config.device = {} - for _, v in ipairs(create_body.HostConfig.Devices) do - table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") ) - end - end - - if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then - default_config.tmpfs = {} - for k, v in pairs(create_body.HostConfig.Tmpfs) do - table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v ) - end - end - end -end - -m = SimpleForm("docker", translate("Docker - Containers")) -m.redirect = luci.dispatcher.build_url("admin", "docker", "containers") - -s = m:section(SimpleSection) -s.template = "dockerman/apply_widget" -s.err=docker:read_status() -s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") -if s.err then - docker:clear_status() -end - -s = m:section(SimpleSection, translate("Create new docker container")) -s.addremove = true -s.anonymous = true - -o = s:option(DummyValue,"cmd_line", translate("Resolve CLI")) -o.rawhtml = true -o.template = "dockerman/newcontainer_resolve" - -o = s:option(Value, "name", translate("Container Name")) -o.rmempty = true -o.default = default_config.name or nil - -o = s:option(Flag, "interactive", translate("Interactive (-i)")) -o.rmempty = true -o.disabled = 0 -o.enabled = 1 -o.default = default_config.interactive and 1 or 0 - -o = s:option(Flag, "tty", translate("TTY (-t)")) -o.rmempty = true -o.disabled = 0 -o.enabled = 1 -o.default = default_config.tty and 1 or 0 - -o = s:option(Value, "image", translate("Docker Image")) -o.rmempty = true -o.default = default_config.image or nil -for _, v in ipairs (images) do - if v.RepoTags then - o:value(v.RepoTags[1], v.RepoTags[1]) - end -end - -o = s:option(Flag, "_force_pull", translate("Always pull image first")) -o.rmempty = true -o.disabled = 0 -o.enabled = 1 -o.default = 0 - -o = s:option(Flag, "privileged", translate("Privileged")) -o.rmempty = true -o.disabled = 0 -o.enabled = 1 -o.default = default_config.privileged and 1 or 0 - -o = s:option(ListValue, "restart", translate("Restart Policy")) -o.rmempty = true -o:value("no", "No") -o:value("unless-stopped", "Unless stopped") -o:value("always", "Always") -o:value("on-failure", "On failure") -o.default = default_config.restart or "unless-stopped" - -local d_network = s:option(ListValue, "network", translate("Networks")) -d_network.rmempty = true -d_network.default = default_config.network or "bridge" - -local d_ip = s:option(Value, "ip", translate("IPv4 Address")) -d_ip.datatype="ip4addr" -d_ip:depends("network", "nil") -d_ip.default = default_config.ip or nil - -o = s:option(DynamicList, "link", translate("Links with other containers")) -o.placeholder = "container_name:alias" -o.rmempty = true -o:depends("network", "bridge") -o.default = default_config.link or nil - -o = s:option(DynamicList, "dns", translate("Set custom DNS servers")) -o.placeholder = "8.8.8.8" -o.rmempty = true -o.default = default_config.dns or nil - -o = s:option(Value, "user", - translate("User(-u)"), - translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])")) -o.placeholder = "1000:1000" -o.rmempty = true -o.default = default_config.user or nil - -o = s:option(DynamicList, "env", - translate("Environmental Variable(-e)"), - translate("Set environment variables to inside the container")) -o.placeholder = "TZ=Asia/Shanghai" -o.rmempty = true -o.default = default_config.env or nil - -o = s:option(DynamicList, "volume", - translate("Bind Mount(-v)"), - translate("Bind mount a volume")) -o.placeholder = "/media:/media:slave" -o.rmempty = true -o.default = default_config.volume or nil - -local d_publish = s:option(DynamicList, "publish", - translate("Exposed Ports(-p)"), - translate("Publish container's port(s) to the host")) -d_publish.placeholder = "2200:22/tcp" -d_publish.rmempty = true -d_publish.default = default_config.publish or nil - -o = s:option(Value, "command", translate("Run command")) -o.placeholder = "/bin/sh init.sh" -o.rmempty = true -o.default = default_config.command or nil - -o = s:option(Flag, "advance", translate("Advance")) -o.rmempty = true -o.disabled = 0 -o.enabled = 1 -o.default = default_config.advance or 0 - -o = s:option(Value, "hostname", - translate("Host Name"), - translate("The hostname to use for the container")) -o.rmempty = true -o.default = default_config.hostname or nil -o:depends("advance", 1) - -o = s:option(Flag, "publish_all", - translate("Exposed All Ports(-P)"), - translate("Allocates an ephemeral host port for all of a container's exposed ports")) -o.rmempty = true -o.disabled = 0 -o.enabled = 1 -o.default = default_config.publish_all and 1 or 0 -o:depends("advance", 1) - -o = s:option(DynamicList, "device", - translate("Device(--device)"), - translate("Add host device to the container")) -o.placeholder = "/dev/sda:/dev/xvdc:rwm" -o.rmempty = true -o:depends("advance", 1) -o.default = default_config.device or nil - -o = s:option(DynamicList, "tmpfs", - translate("Tmpfs(--tmpfs)"), - translate("Mount tmpfs directory")) -o.placeholder = "/run:rw,noexec,nosuid,size=65536k" -o.rmempty = true -o:depends("advance", 1) -o.default = default_config.tmpfs or nil - -o = s:option(DynamicList, "sysctl", - translate("Sysctl(--sysctl)"), - translate("Sysctls (kernel parameters) options")) -o.placeholder = "net.ipv4.ip_forward=1" -o.rmempty = true -o:depends("advance", 1) -o.default = default_config.sysctl or nil - -o = s:option(DynamicList, "cap_add", - translate("CAP-ADD(--cap-add)"), - translate("A list of kernel capabilities to add to the container")) -o.placeholder = "NET_ADMIN" -o.rmempty = true -o:depends("advance", 1) -o.default = default_config.cap_add or nil - -o = s:option(Value, "cpus", - translate("CPUs"), - translate("Number of CPUs. Number is a fractional number. 0.000 means no limit")) -o.placeholder = "1.5" -o.rmempty = true -o:depends("advance", 1) -o.datatype="ufloat" -o.default = default_config.cpus or nil - -o = s:option(Value, "cpu_shares", - translate("CPU Shares Weight"), - translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024")) -o.placeholder = "1024" -o.rmempty = true -o:depends("advance", 1) -o.datatype="uinteger" -o.default = default_config.cpu_shares or nil - -o = s:option(Value, "memory", - translate("Memory"), - translate("Memory limit (format: []). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M")) -o.placeholder = "128m" -o.rmempty = true -o:depends("advance", 1) -o.default = default_config.memory or nil - -o = s:option(Value, "blkio_weight", - translate("Block IO Weight"), - translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000")) -o.placeholder = "500" -o.rmempty = true -o:depends("advance", 1) -o.datatype="uinteger" -o.default = default_config.blkio_weight or nil - -o = s:option(DynamicList, "log_opt", - translate("Log driver options"), - translate("The logging configuration for this container")) -o.placeholder = "max-size=1m" -o.rmempty = true -o:depends("advance", 1) -o.default = default_config.log_opt or nil - -for _, v in ipairs (networks) do - if v.Name then - local parent = v.Options and v.Options.parent or nil - local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil - ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil - local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") - d_network:value(v.Name, network_name) - - if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then - d_ip:depends("network", v.Name) - end - - if v.Driver == "bridge" then - d_publish:depends("network", v.Name) - end - end -end - -m.handle = function(self, state, data) - if state ~= FORM_VALID then - return - end - - local tmp - local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S")) - local hostname = data.hostname - local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false - local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false - local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false - local image = data.image - local user = data.user - - if image and not image:match(".-:.+") then - image = image .. ":latest" - end - - local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false - local restart = data.restart - local env = data.env - local dns = data.dns - local cap_add = data.cap_add - local sysctl = {} - - tmp = data.sysctl - if type(tmp) == "table" then - for i, v in ipairs(tmp) do - local k,v1 = v:match("(.-)=(.+)") - if k and v1 then - sysctl[k]=v1 - end - end - end - - local log_opt = {} - tmp = data.log_opt - if type(tmp) == "table" then - for i, v in ipairs(tmp) do - local k,v1 = v:match("(.-)=(.+)") - if k and v1 then - log_opt[k]=v1 - end - end - end - - local network = data.network - local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil - local volume = data.volume - local memory = data.memory or 0 - local cpu_shares = data.cpu_shares or 0 - local cpus = data.cpus or 0 - local blkio_weight = data.blkio_weight or nil - - local portbindings = {} - local exposedports = {} - - local tmpfs = {} - tmp = data.tmpfs - if type(tmp) == "table" then - for i, v in ipairs(tmp)do - local k= v:match("([^:]+)") - local v1 = v:match(".-:([^:]+)") or "" - if k then - tmpfs[k]=v1 - end - end - end - - local device = {} - tmp = data.device - if type(tmp) == "table" then - for i, v in ipairs(tmp) do - local t = {} - local _,_, h, c, p = v:find("(.-):(.-):(.+)") - if h and c then - t['PathOnHost'] = h - t['PathInContainer'] = c - t['CgroupPermissions'] = p or "rwm" - else - local _,_, h, c = v:find("(.-):(.+)") - if h and c then - t['PathOnHost'] = h - t['PathInContainer'] = c - t['CgroupPermissions'] = "rwm" - else - t['PathOnHost'] = v - t['PathInContainer'] = v - t['CgroupPermissions'] = "rwm" - end - end - - if next(t) ~= nil then - table.insert( device, t ) - end - end - end - - tmp = data.publish or {} - for i, v in ipairs(tmp) do - for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do - local _,_,p= v2:find("^%d+/(%w+)") - if p == nil then - v2=v2..'/tcp' - end - portbindings[v2] = {{HostPort=v1}} - exposedports[v2] = {HostPort=v1} - end - end - - local link = data.link - tmp = data.command - local command = {} - if tmp ~= nil then - for v in string.gmatch(tmp, "[^%s]+") do - command[#command+1] = v - end - end - - if memory ~= 0 then - _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") - if n then - unit = unit and unit:sub(1,1):upper() or "B" - if unit == "M" then - memory = tonumber(n) * 1024 * 1024 - elseif unit == "G" then - memory = tonumber(n) * 1024 * 1024 * 1024 - elseif unit == "K" then - memory = tonumber(n) * 1024 - else - memory = tonumber(n) - end - end - end - - create_body.Hostname = network ~= "host" and (hostname or name) or nil - create_body.Tty = tty and true or false - create_body.OpenStdin = interactive and true or false - create_body.User = user - create_body.Cmd = command - create_body.Env = env - create_body.Image = image - create_body.ExposedPorts = exposedports - create_body.HostConfig = create_body.HostConfig or {} - create_body.HostConfig.Dns = dns - create_body.HostConfig.Binds = volume - create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 } - create_body.HostConfig.Privileged = privileged and true or false - create_body.HostConfig.PortBindings = portbindings - create_body.HostConfig.Memory = tonumber(memory) - create_body.HostConfig.CpuShares = tonumber(cpu_shares) - create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9 - create_body.HostConfig.BlkioWeight = tonumber(blkio_weight) - create_body.HostConfig.PublishAllPorts = publish_all - - if create_body.HostConfig.NetworkMode ~= network then - create_body.NetworkingConfig = nil - end - - create_body.HostConfig.NetworkMode = network - - if ip then - if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then - for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do - if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then - v.IPAMConfig.IPv4Address = ip - else - create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } - end - break - end - else - create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } } - end - elseif not create_body.NetworkingConfig then - create_body.NetworkingConfig = nil - end - - create_body["HostConfig"]["Tmpfs"] = tmpfs - create_body["HostConfig"]["Devices"] = device - create_body["HostConfig"]["Sysctls"] = sysctl - create_body["HostConfig"]["CapAdd"] = cap_add - create_body["HostConfig"]["LogConfig"] = next(log_opt) ~= nil and { Config = log_opt } or nil - - if network == "bridge" then - create_body["HostConfig"]["Links"] = link - end - - local pull_image = function(image) - local json_stringify = luci.jsonc and luci.jsonc.stringify - docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n") - local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb) - 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 ".. image or res.body[#res.body].status == "Status: Image is up to date for ".. image)) then - docker:append_status("done\n") - else - res.code = (res.code == 200) and 500 or res.code - docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) - end - end - - docker:clear_status() - local exist_image = false - - if image then - for _, v in ipairs (images) do - if v.RepoTags and v.RepoTags[1] == image then - exist_image = true - break - end - end - if not exist_image then - pull_image(image) - elseif data._force_pull == 1 then - pull_image(image) - end - end - - create_body = docker.clear_empty_tables(create_body) - - docker:append_status("Container: " .. "create" .. " " .. name .. "...") - local res = dk.containers:create({name = name, body = create_body}) - if res and res.code == 201 then - docker:clear_status() - luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) - else - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) - end -end - -return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua deleted file mode 100644 index a9cd67e1a1..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua +++ /dev/null @@ -1,246 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -local docker = require "luci.model.docker" - -local m, s, o - -local dk = docker.new() - -m = SimpleForm("docker", translate("Docker - Network")) -m.redirect = luci.dispatcher.build_url("admin", "docker", "networks") - -s = m:section(SimpleSection) -s.template = "dockerman/apply_widget" -s.err=docker:read_status() -s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") -if s.err then - docker:clear_status() -end - -s = m:section(SimpleSection, translate("Create new docker network")) -s.addremove = true -s.anonymous = true - -o = s:option(Value, "name", - translate("Network Name"), - translate("Name of the network that can be selected during container creation")) -o.rmempty = true - -o = s:option(ListValue, "driver", translate("Driver")) -o.rmempty = true -o:value("bridge", translate("Bridge device")) -o:value("macvlan", translate("MAC VLAN")) -o:value("ipvlan", translate("IP VLAN")) -o:value("overlay", translate("Overlay network")) - -o = s:option(Value, "parent", translate("Base device")) -o.rmempty = true -o:depends("driver", "macvlan") -local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {} -for _, v in ipairs(interfaces) do - o:value(v, v) -end - -o = s:option(ListValue, "macvlan_mode", translate("Mode")) -o.rmempty = true -o:depends("driver", "macvlan") -o.default="bridge" -o:value("bridge", translate("Bridge (Support direct communication between MAC VLANs)")) -o:value("private", translate("Private (Prevent communication between MAC VLANs)")) -o:value("vepa", translate("VEPA (Virtual Ethernet Port Aggregator)")) -o:value("passthru", translate("Pass-through (Mirror physical device to single MAC VLAN)")) - -o = s:option(ListValue, "ipvlan_mode", translate("Ipvlan Mode")) -o.rmempty = true -o:depends("driver", "ipvlan") -o.default="l3" -o:value("l2", translate("L2 bridge")) -o:value("l3", translate("L3 bridge")) - -o = s:option(Flag, "ingress", - translate("Ingress"), - translate("Ingress network is the network which provides the routing-mesh in swarm mode")) -o.rmempty = true -o.disabled = 0 -o.enabled = 1 -o.default = 0 -o:depends("driver", "overlay") - -o = s:option(DynamicList, "options", translate("Options")) -o.rmempty = true -o.placeholder="com.docker.network.driver.mtu=1500" - -o = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network")) -o.rmempty = true -o:depends("driver", "overlay") -o.disabled = 0 -o.enabled = 1 -o.default = 0 - -if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then - o = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt")) - o:depends("driver", "macvlan") - o.disabled = 0 - o.enabled = 1 - o.default = 1 -end - -o = s:option(Value, "subnet", translate("Subnet")) -o.rmempty = true -o.placeholder="10.1.0.0/16" -o.datatype="ip4addr" - -o = s:option(Value, "gateway", translate("Gateway")) -o.rmempty = true -o.placeholder="10.1.1.1" -o.datatype="ip4addr" - -o = s:option(Value, "ip_range", translate("IP range")) -o.rmempty = true -o.placeholder="10.1.1.0/24" -o.datatype="ip4addr" - -o = s:option(DynamicList, "aux_address", translate("Exclude IPs")) -o.rmempty = true -o.placeholder="my-route=10.1.1.1" - -o = s:option(Flag, "ipv6", translate("Enable IPv6")) -o.rmempty = true -o.disabled = 0 -o.enabled = 1 -o.default = 0 - -o = s:option(Value, "subnet6", translate("IPv6 Subnet")) -o.rmempty = true -o.placeholder="fe80::/10" -o.datatype="ip6addr" -o:depends("ipv6", 1) - -o = s:option(Value, "gateway6", translate("IPv6 Gateway")) -o.rmempty = true -o.placeholder="fe80::1" -o.datatype="ip6addr" -o:depends("ipv6", 1) - -m.handle = function(self, state, data) - if state == FORM_VALID then - local name = data.name - local driver = data.driver - - local internal = data.internal == 1 and true or false - - local subnet = data.subnet - local gateway = data.gateway - local ip_range = data.ip_range - - local aux_address = {} - local tmp = data.aux_address or {} - for i,v in ipairs(tmp) do - _,_,k1,v1 = v:find("(.-)=(.+)") - aux_address[k1] = v1 - end - - local options = {} - tmp = data.options or {} - for i,v in ipairs(tmp) do - _,_,k1,v1 = v:find("(.-)=(.+)") - options[k1] = v1 - end - - local ipv6 = data.ipv6 == 1 and true or false - - local create_body = { - Name = name, - Driver = driver, - EnableIPv6 = ipv6, - IPAM = { - Driver= "default" - }, - Internal = internal - } - - if subnet or gateway or ip_range then - create_body["IPAM"]["Config"] = { - { - Subnet = subnet, - Gateway = gateway, - IPRange = ip_range, - AuxAddress = aux_address, - AuxiliaryAddresses = aux_address - } - } - end - - if driver == "macvlan" then - create_body["Options"] = { - macvlan_mode = data.macvlan_mode, - parent = data.parent - } - elseif driver == "ipvlan" then - create_body["Options"] = { - ipvlan_mode = data.ipvlan_mode - } - elseif driver == "overlay" then - create_body["Ingress"] = data.ingerss == 1 and true or false - end - - if ipv6 and data.subnet6 and data.subnet6 then - if type(create_body["IPAM"]["Config"]) ~= "table" then - create_body["IPAM"]["Config"] = {} - end - local index = #create_body["IPAM"]["Config"] - create_body["IPAM"]["Config"][index+1] = { - Subnet = data.subnet6, - Gateway = data.gateway6 - } - end - - if next(options) ~= nil then - create_body["Options"] = create_body["Options"] or {} - for k, v in pairs(options) do - create_body["Options"][k] = v - end - end - - create_body = docker.clear_empty_tables(create_body) - docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...") - - local res = dk.networks:create({ - body = create_body - }) - - if res and res.code == 201 then - docker:write_status("Network: " .. "create macvlan interface...") - res = dk.networks:inspect({ - name = create_body.Name - }) - - if driver == "macvlan" and - data.op_macvlan ~= 0 and - res.code == 200 and - res.body and - res.body.IPAM and - res.body.IPAM.Config and - res.body.IPAM.Config[1] and - res.body.IPAM.Config[1].Gateway and - res.body.IPAM.Config[1].Subnet then - - docker.create_macvlan_interface(data.name, - data.parent, - res.body.IPAM.Config[1].Gateway, - res.body.IPAM.Config[1].Subnet) - 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 - -return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua deleted file mode 100644 index 0506670cdb..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua +++ /dev/null @@ -1,92 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -local docker = require "luci.model.docker" - -local m, s, o - -function byte_format(byte) - local suff = {"B", "KB", "MB", "GB", "TB"} - for i=1, 5 do - if byte > 1024 and i < 5 then - byte = byte / 1024 - else - return string.format("%.2f %s", byte, suff[i]) - end - end -end - -m = SimpleForm("dockerd", - translate("Docker - Overview"), - translate("An overview with the relevant data is displayed here with which the LuCI docker client is connected.")) -m.submit=false -m.reset=false - -local docker_info_table = {} -docker_info_table['3ServerVersion'] = {_key=translate("Docker Version"),_value='-'} -docker_info_table['4ApiVersion'] = {_key=translate("Api Version"),_value='-'} -docker_info_table['5NCPU'] = {_key=translate("CPUs"),_value='-'} -docker_info_table['6MemTotal'] = {_key=translate("Total Memory"),_value='-'} -docker_info_table['7DockerRootDir'] = {_key=translate("Docker Root Dir"),_value='-'} -docker_info_table['8IndexServerAddress'] = {_key=translate("Index Server Address"),_value='-'} -docker_info_table['9RegistryMirrors'] = {_key=translate("Registry Mirrors"),_value='-'} - -s = m:section(Table, docker_info_table) -s:option(DummyValue, "_key", translate("Info")) -s:option(DummyValue, "_value") - -s = m:section(SimpleSection) -s.template = "dockerman/overview" - -s.containers_running = '-' -s.images_used = '-' -s.containers_total = '-' -s.images_total = '-' -s.networks_total = '-' -s.volumes_total = '-' - -if docker.new():_ping().code == 200 then - local dk = docker.new() - local containers_list = dk.containers:list({query = {all=true}}).body - local images_list = dk.images:list().body - local vol = dk.volumes:list() - local volumes_list = vol and vol.body and vol.body.Volumes or {} - local networks_list = dk.networks:list().body or {} - local docker_info = dk:info() - - docker_info_table['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 or {}) 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 - -return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua deleted file mode 100644 index 5fbd55f7b5..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua +++ /dev/null @@ -1,142 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -local docker = require "luci.model.docker" -local dk = docker.new() - -local m, s, o - -local res, containers, volumes - -function get_volumes() - local data = {} - for i, v in ipairs(volumes) do - local index = v.Name - data[index]={} - data[index]["_selected"] = 0 - data[index]["_nameraw"] = v.Name - data[index]["_name"] = v.Name:sub(1,12) - - for ci,cv in ipairs(containers) do - if cv.Mounts and type(cv.Mounts) ~= "table" then - break - end - for vi, vv in ipairs(cv.Mounts) do - if v.Name == vv.Name then - data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. - ''.. cv.Names[1]:sub(2)..'' - end - end - end - data[index]["_driver"] = v.Driver - data[index]["_mountpoint"] = nil - - for v1 in v.Mountpoint:gmatch('[^/]+') do - if v1 == index then - data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..." - else - data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1 - end - end - data[index]["_created"] = v.CreatedAt - end - - return data -end - -res = dk.volumes:list() -if res.code <300 then - volumes = res.body.Volumes -else - return -end - -res = dk.containers:list({ - query = { - all=true - } -}) -if res.code <300 then - containers = res.body -else - return -end - -local volume_list = get_volumes() - -m = SimpleForm("docker", translate("Docker - Volumes")) -m.submit=false -m.reset=false - -s = m:section(Table, volume_list, translate("Volumes overview")) - -o = s:option(Flag, "_selected","") -o.disabled = 0 -o.enabled = 1 -o.default = 0 -o.write = function(self, section, value) - volume_list[section]._selected = value -end - -o = s:option(DummyValue, "_name", translate("Name")) - -o = s:option(DummyValue, "_driver", translate("Driver")) - -o = s:option(DummyValue, "_containers", translate("Containers")) -o.rawhtml = true - -o = s:option(DummyValue, "_mountpoint", translate("Mount Point")) - -o = s:option(DummyValue, "_created", translate("Created")) - -s = m:section(SimpleSection) -s.template = "dockerman/apply_widget" -s.err=docker:read_status() -s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") -if s.err then - docker:clear_status() -end - -s = m:section(Table,{{}}) -s.notitle=true -s.rowcolors=false -s.template="cbi/nullsection" - -o = s:option(Button, "remove") -o.inputtitle= translate("Remove") -o.template = "dockerman/cbi/inlinebutton" -o.inputstyle = "remove" -o.forcewrite = true -o.write = function(self, section) - local volume_selected = {} - - 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 diff --git a/applications/luci-app-dockerman/luasrc/model/docker.lua b/applications/luci-app-dockerman/luasrc/model/docker.lua deleted file mode 100644 index a0c74c0e41..0000000000 --- a/applications/luci-app-dockerman/luasrc/model/docker.lua +++ /dev/null @@ -1,482 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -local docker = require "luci.docker" -local fs = require "nixio.fs" -local uci = (require "luci.model.uci").cursor() - -local _docker = {} -_docker.options = {} - ---pull image and return iamge id -local update_image = function(self, image_name) - local json_stringify = luci.jsonc and luci.jsonc.stringify - _docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n") - local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb) - - if res and res.code == 200 and (#res.body > 0 and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image_name)) then - _docker:append_status("done\n") - else - res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message) - end - - new_image_id = self.images:inspect({name = image_name}).body.Id - return new_image_id, res -end - -local table_equal = function(t1, t2) - if not t1 then - return true - end - - if not t2 then - return false - end - - if #t1 ~= #t2 then - return false - end - - for i, v in ipairs(t1) do - if t1[i] ~= t2[i] then - return false - end - end - - return true -end - -local table_subtract = function(t1, t2) - if not t1 or next(t1) == nil then - return nil - end - - if not t2 or next(t2) == nil then - return t1 - end - - local res = {} - for _, v1 in ipairs(t1) do - local found = false - for _, v2 in ipairs(t2) do - if v1 == v2 then - found= true - break - end - end - if not found then - table.insert(res, v1) - end - end - - return next(res) == nil and nil or res -end - -local map_subtract = function(t1, t2) - if not t1 or next(t1) == nil then - return nil - end - - if not t2 or next(t2) == nil then - return t1 - end - - local res = {} - for k1, v1 in pairs(t1) do - local found = false - for k2, v2 in ipairs(t2) do - if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then - found= true - break - end - end - - if not found then - res[k1] = v1 - end - end - - return next(res) ~= nil and res or nil -end - -_docker.clear_empty_tables = function ( t ) - local k, v - - if next(t) == nil then - t = nil - else - for k, v in pairs(t) do - if type(v) == 'table' then - t[k] = _docker.clear_empty_tables(v) - end - end - end - - return t -end - -local get_config = function(container_config, image_config) - local config = container_config.Config - local old_host_config = container_config.HostConfig - local old_network_setting = container_config.NetworkSettings.Networks or {} - - if config.WorkingDir == image_config.WorkingDir then - config.WorkingDir = "" - end - - if config.User == image_config.User then - config.User = "" - end - - if table_equal(config.Cmd, image_config.Cmd) then - config.Cmd = nil - end - - if table_equal(config.Entrypoint, image_config.Entrypoint) then - config.Entrypoint = nil - end - - if table_equal(config.ExposedPorts, image_config.ExposedPorts) then - config.ExposedPorts = nil - end - - 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 - -local upgrade = function(self, request) - _docker:clear_status() - - 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) - 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 .. "...") - res = self.containers:stop({name = container_name}) - if res and res.code < 305 then - _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 - -local duplicate_config = function (self, request) - local container_info = self.containers:inspect({id = request.id}) - if container_info.code > 300 and type(container_info.body) == "table" then - return nil - end - - local old_image_id = container_info.body.Image - local image_config = self.images:inspect({id = old_image_id}).body.Config - - return get_config(container_info.body, image_config) -end - -_docker.new = function() - local host = nil - local port = nil - local socket_path = nil - local debug_path = nil - - local remote = uci:get_bool("dockerd", "globals", "remote_endpoint") - if remote then - host = uci:get("dockerd", "globals", "remote_host") or nil - port = uci:get("dockerd", "globals", "remote_port") or nil - else - socket_path = uci:get("dockerd", "globals", "socket_path") or "/var/run/docker.sock" - end - - local debug = uci:get_bool("dockerd", "globals", "debug") - if debug then - debug_path = uci:get("dockerd", "globals", "debug_path") or "/tmp/.docker_debug" - end - - local status_path = uci:get("dockerd", "globals", "status_path") or "/tmp/.docker_status" - - _docker.options = { - host = host, - port = port, - socket_path = socket_path, - debug = debug, - debug_path = debug_path, - status_path = status_path - } - - local _new = docker.new(_docker.options) - _new.containers_upgrade = upgrade - _new.containers_duplicate_config = duplicate_config - - return _new -end - -_docker.append_status=function(self,val) - if not val then - return - end - local file_docker_action_status=io.open(self.options.status_path, "a+") - file_docker_action_status:write(val) - file_docker_action_status:close() -end - -_docker.write_status=function(self,val) - if not val then - return - end - local file_docker_action_status=io.open(self.options.status_path, "w+") - file_docker_action_status:write(val) - file_docker_action_status:close() -end - -_docker.read_status=function(self) - return fs.readfile(self.options.status_path) -end - -_docker.clear_status=function(self) - fs.remove(self.options.status_path) -end - -local status_cb = function(res, source, handler) - res.body = res.body or {} - while true do - local chunk = source() - if chunk then - --standard output to res.body - table.insert(res.body, chunk) - handler(chunk) - else - return - end - end -end - ---{"status":"Pulling from library\/debian","id":"latest"} ---{"status":"Pulling fs layer","progressDetail":[],"id":"50e431f79093"} ---{"status":"Downloading","progressDetail":{"total":50381971,"current":2029978},"id":"50e431f79093","progress":"[==> ] 2.03MB\/50.38MB"} ---{"status":"Download complete","progressDetail":[],"id":"50e431f79093"} ---{"status":"Extracting","progressDetail":{"total":50381971,"current":17301504},"id":"50e431f79093","progress":"[=================> ] 17.3MB\/50.38MB"} ---{"status":"Pull complete","progressDetail":[],"id":"50e431f79093"} ---{"status":"Digest: sha256:a63d0b2ecbd723da612abf0a8bdb594ee78f18f691d7dc652ac305a490c9b71a"} ---{"status":"Status: Downloaded newer image for debian:latest"} -_docker.pull_image_show_status_cb = function(res, source) - return status_cb(res, source, function(chunk) - local json_parse = luci.jsonc.parse - local step = json_parse(chunk) - if type(step) == "table" then - local buf = _docker:read_status() - local num = 0 - local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" - if step.id then - buf, num = buf:gsub("\t"..step.id .. ": .-\n", str) - end - if num == 0 then - buf = buf .. str - end - _docker:write_status(buf) - 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":"Importing","progressDetail":{"current":1572391,"total":3821714},"progress":"[====================\u003e ] 1.572MB/3.822MB"} ---{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"} -_docker.import_image_show_status_cb = function(res, source) - return status_cb(res, source, function(chunk) - local json_parse = luci.jsonc.parse - local step = json_parse(chunk) - if type(step) == "table" then - local buf = _docker:read_status() - local num = 0 - local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" - if step.status then - buf, num = buf:gsub("\t"..step.status .. " .-\n", str) - end - if num == 0 then - buf = buf .. str - end - _docker:write_status(buf) - end - end) -end - -_docker.create_macvlan_interface = function(name, device, gateway, subnet) - if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then - return - end - - if uci:get("dockerd", "globals", "remote_endpoint") == "true" then - return - end - - local ip = require "luci.ip" - local if_name = "docker_"..name - local dev_name = "macvlan_"..name - local net_mask = tostring(ip.new(subnet):mask()) - local lan_interfaces - - -- add macvlan device - uci:delete("network", dev_name) - uci:set("network", dev_name, "device") - uci:set("network", dev_name, "name", dev_name) - uci:set("network", dev_name, "ifname", device) - uci:set("network", dev_name, "type", "macvlan") - uci:set("network", dev_name, "mode", "bridge") - - -- add macvlan interface - uci:delete("network", if_name) - uci:set("network", if_name, "interface") - uci:set("network", if_name, "proto", "static") - uci:set("network", if_name, "ifname", dev_name) - uci:set("network", if_name, "ipaddr", gateway) - uci:set("network", if_name, "netmask", net_mask) - uci:foreach("firewall", "zone", function(s) - if s.name == "lan" then - local interfaces - if type(s.network) == "table" then - interfaces = table.concat(s.network, " ") - uci:delete("firewall", s[".name"], "network") - else - interfaces = s.network and s.network or "" - 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 - -_docker.remove_macvlan_interface = function(name) - if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then - return - end - - if uci:get("dockerd", "globals", "remote_endpoint") == "true" then - return - end - - local if_name = "docker_"..name - local dev_name = "macvlan_"..name - uci:foreach("firewall", "zone", function(s) - if s.name == "lan" then - local interfaces - if type(s.network) == "table" then - interfaces = table.concat(s.network, " ") - else - interfaces = s.network and s.network or "" - end - interfaces = interfaces and interfaces:gsub(if_name, "") - interfaces = interfaces and interfaces:gsub("%s+", " ") - uci:set("firewall", s[".name"], "network", interfaces) - 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 - -return _docker diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm deleted file mode 100644 index b9ae1c6bdc..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm +++ /dev/null @@ -1,147 +0,0 @@ - - - diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm deleted file mode 100644 index a061a6dba6..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm +++ /dev/null @@ -1,7 +0,0 @@ -
- <% if self:cfgvalue(section) ~= false then %> - " type="submit"" <% if self.disable then %>disabled <% end %><%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> /> - <% else %> - - - <% end %> -
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm deleted file mode 100644 index e4b0cf7a08..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm +++ /dev/null @@ -1,33 +0,0 @@ -
- - <%- if self.password then -%> - /> - <%- end -%> - 0, "data-choices", { self.keylist, self.vallist }) - %> /> - <%- if self.password then -%> -
∗
- <% end %> -
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm deleted file mode 100644 index 244d2c10ad..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm +++ /dev/null @@ -1,9 +0,0 @@ -<% if self:cfgvalue(self.section) then section = self.section %> -
- <%+cbi/tabmenu%> -
- <%+cbi/ucisection%> -
-
-<% end %> - diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm deleted file mode 100644 index 04f7bc2ee8..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm +++ /dev/null @@ -1,10 +0,0 @@ -<%+cbi/valueheader%> - /> - disabled <% end %><%= - attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) .. - ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked") - %> /> - > -<%+cbi/valuefooter%> diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm deleted file mode 100644 index f9b2f4344a..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm +++ /dev/null @@ -1,28 +0,0 @@ -
- - - diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm deleted file mode 100644 index 030acf74d1..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm +++ /dev/null @@ -1,6 +0,0 @@ -
- -
- diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm deleted file mode 100644 index 53f2c84f66..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm +++ /dev/null @@ -1,73 +0,0 @@ -
- -
- -
-
- -
- -
-
-
- - -
-
- - diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm deleted file mode 100644 index 28eaadab44..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm +++ /dev/null @@ -1,81 +0,0 @@ - diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm deleted file mode 100644 index 8c27eadb69..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm +++ /dev/null @@ -1,104 +0,0 @@ - - -
- - -
- - diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm deleted file mode 100644 index 1beb784cda..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm +++ /dev/null @@ -1,30 +0,0 @@ -
- - -
- diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm deleted file mode 100644 index c66637d890..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm +++ /dev/null @@ -1,13 +0,0 @@ -<% if self.title == "Events" then %> -<%+header%> -

<%:Docker%>

-
-

<%:Events%>

-<% end %> -
- -
-<% if self.title == "Events" then %> -
-<%+footer%> -<% end %> diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm deleted file mode 100644 index abf9442e79..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm +++ /dev/null @@ -1,102 +0,0 @@ - - - -<%+cbi/valueheader%> - - - -<%+cbi/valuefooter%> diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm deleted file mode 100644 index e491fc5126..0000000000 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm +++ /dev/null @@ -1,197 +0,0 @@ - - -
-
-
-
-
- -
-
-
-

<%:Containers%>

-

- <%- if self.containers_total ~= "-" then -%><%- end -%> - <%=self.containers_running%> - /<%=self.containers_total%> - <%- if self.containers_total ~= "-" then -%><%- end -%> -

-
-
-
-
-
-
-
- -
-
-
-

<%:Images%>

-

- <%- if self.images_total ~= "-" then -%><%- end -%> - <%=self.images_used%> - /<%=self.images_total%> - <%- if self.images_total ~= "-" then -%><%- end -%> -

-
-
-
-
-
-
-
- -
-
-
-

<%:Networks%>

-

- <%- if self.networks_total ~= "-" then -%><%- end -%> - <%=self.networks_total%> - - <%- if self.networks_total ~= "-" then -%><%- end -%> -

-
-
-
-
-
-
-
- -
-
-
-

<%:Volumes%>

-

- <%- if self.volumes_total ~= "-" then -%><%- end -%> - <%=self.volumes_total%> - - <%- if self.volumes_total ~= "-" then -%><%- end -%> -

-
-
-
-
diff --git a/collections/luci-lib-docker/Makefile b/collections/luci-lib-docker/Makefile deleted file mode 100644 index 11221e5d75..0000000000 --- a/collections/luci-lib-docker/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -include $(TOPDIR)/rules.mk - -PKG_NAME:=luci-lib-docker -PKG_LICENSE:=AGPL-3.0 -PKG_MAINTAINER:=lisaac \ - Florian Eckert - -LUCI_TYPE:=col - -LUCI_TITLE:=LuCI library for docker -LUCI_DESCRIPTION:=Docker Engine API for LuCI - -LUCI_DEPENDS:=@(aarch64||arm||x86_64) +luci-lib-jsonc -LUCI_PKGARCH:=all - -include ../../luci.mk - -# call BuildPackage - OpenWrt buildroot signature diff --git a/collections/luci-lib-docker/luasrc/docker.lua b/collections/luci-lib-docker/luasrc/docker.lua deleted file mode 100644 index 346b0ef235..0000000000 --- a/collections/luci-lib-docker/luasrc/docker.lua +++ /dev/null @@ -1,537 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface -Copyright 2019 lisaac -]]-- - -require "nixio.util" -require "luci.util" -local jsonc = require "luci.jsonc" -local nixio = require "nixio" -local ltn12 = require "luci.ltn12" -local fs = require "nixio.fs" - -local urlencode = luci.util.urlencode or luci.http and luci.http.protocol and luci.http.protocol.urlencode -local json_stringify = jsonc.stringify -local json_parse = jsonc.parse - -local chunksource = function(sock, buffer) - buffer = buffer or "" - - return function() - local output - local _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n") - - while not count do - local newblock, code = sock:recv(1024) - if not newblock then - return nil, code - end - buffer = buffer .. newblock - _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n") - end - - count = tonumber(count, 16) - if not count then - return nil, -1, "invalid encoding" - elseif count == 0 then -- finial - return nil - elseif count <= #buffer - endp then -- data >= count - output = buffer:sub(endp + 1, endp + count) - if count == #buffer - endp then -- [data] - buffer = buffer:sub(endp + count + 1) - count, code = sock:recvall(2) --read \r\n - if not count then - return nil, code - end - elseif count + 1 == #buffer - endp then -- [data]\r - buffer = buffer:sub(endp + count + 2) - count, code = sock:recvall(1) --read \n - if not count then - return nil, code - end - else -- [data]\r\n[count]\r\n[data]... - buffer = buffer:sub(endp + count + 3) -- cut buffer - end - return output - else -- data < count - output = buffer:sub(endp + 1, endp + count) - buffer = buffer:sub(endp + count + 1) - local remain, code = sock:recvall(count - #output) --need read remaining - if not remain then - return nil, code - end - output = output .. remain - count, code = sock:recvall(2) --read \r\n - if not count then - return nil, code - end - return output - end - end -end - -local chunksink = function (sock) - return function(chunk, err) - if not chunk then - return sock:writeall("0\r\n\r\n") - else - return sock:writeall(("%X\r\n%s\r\n"):format(#chunk, tostring(chunk))) - end - end -end - -local docker_stream_filter = function(buffer) - buffer = buffer or "" - if #buffer < 8 then - return "" - end - local stream_type = ((string.byte(buffer, 1) == 1) and "stdout") or ((string.byte(buffer, 1) == 2) and "stderr") or ((string.byte(buffer, 1) == 0) and "stdin") or "stream_err" - local valid_length = tonumber(string.byte(buffer, 5)) * 256 * 256 * 256 + tonumber(string.byte(buffer, 6)) * 256 * 256 + tonumber(string.byte(buffer, 7)) * 256 + tonumber(string.byte(buffer, 8)) - if valid_length > #buffer + 8 then - return "" - end - return stream_type .. ": " .. string.sub(buffer, 9, valid_length + 8) -end - -local open_socket = function(req_options) - local socket - if type(req_options) ~= "table" then - return socket - end - if req_options.socket_path then - socket = nixio.socket("unix", "stream") - if socket:connect(req_options.socket_path) ~= true then - return nil - end - elseif req_options.host and req_options.port then - socket = nixio.connect(req_options.host, req_options.port) - end - if socket then - return socket - else - return nil - end -end - -local send_http_socket = function(options, docker_socket, req_header, req_body, callback) - if docker_socket:send(req_header) == 0 then - return { - headers={ - code=498, - message="bad path", - protocol="HTTP/1.1" - }, - body={ - message="can\'t send data to socket" - } - } - end - - if req_body and type(req_body) == "function" and req_header and req_header:match("chunked") then - -- chunked send - req_body(chunksink(docker_socket)) - elseif req_body and type(req_body) == "function" then - -- normal send by req_body function - req_body(docker_socket) - elseif req_body and type(req_body) == "table" then - -- json - docker_socket:send(json_stringify(req_body)) - if options.debug then - io.popen("echo '".. json_stringify(req_body) .. "' >> " .. options.debug_path) - end - elseif req_body then - docker_socket:send(req_body) - if options.debug then - io.popen("echo '".. req_body .. "' >> " .. options.debug_path) - end - end - - local linesrc = docker_socket:linesource() - -- read socket using source http://w3.impa.br/~diego/software/luasocket/ltn12.html - -- http://lua-users.org/wiki/FiltersSourcesAndSinks - -- handle response header - local line = linesrc() - if not line then - docker_socket:close() - return { - headers = { - code=499, - message="bad socket path", - protocol="HTTP/1.1" - }, - body = { - message="no data receive from socket" - } - } - end - - local response = { - code = 0, - headers = {}, - body = {} - } - - local p, code, msg = line:match("^([%w./]+) ([0-9]+) (.*)") - response.protocol = p - response.code = tonumber(code) - response.message = msg - line = linesrc() - - while line and line ~= "" do - local key, val = line:match("^([%w-]+)%s?:%s?(.*)") - if key and key ~= "Status" then - if type(response.headers[key]) == "string" then - response.headers[key] = { - response.headers[key], - val - } - elseif type(response.headers[key]) == "table" then - response.headers[key][#response.headers[key] + 1] = val - else - response.headers[key] = val - end - end - line = linesrc() - end - - -- handle response body - local body_buffer = linesrc(true) - response.body = {} - - if type(callback) ~= "function" then - if response.headers["Transfer-Encoding"] == "chunked" then - local source = chunksource(docker_socket, body_buffer) - code = ltn12.pump.all(source, (ltn12.sink.table(response.body))) and response.code or 555 - response.code = code - else - local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource()) - code = ltn12.pump.all(body_source, (ltn12.sink.table(response.body))) and response.code or 555 - 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 - -local gen_header = function(options, http_method, api_group, api_action, name_or_id, request) - local header, query, path - name_or_id = (name_or_id ~= "") and name_or_id or nil - - if request and type(request.query) == "table" then - local k, v - for k, v in pairs(request.query) do - if type(v) == "table" then - query = (query and query .. "&" or "?") .. k .. "=" .. urlencode(json_stringify(v)) - elseif type(v) == "boolean" then - query = (query and query .. "&" or "?") .. k .. "=" .. (v and "true" or "false") - elseif type(v) == "number" or type(v) == "string" then - query = (query and query .. "&" or "?") .. k .. "=" .. v - 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 - local k, v - for k, v in pairs(request.header) do - header = header .. k .. ": " .. v .. "\r\n" - end - end - - -- when requst_body is function, we need to custom header using custom header - if request and request.body and type(request.body) == "function" then - if not header:match("Content-Length:") then - header = header .. "Transfer-Encoding: chunked\r\n" - end - elseif http_method == "POST" and request and request.body and type(request.body) == "table" then - local conetnt_json = json_stringify(request.body) - 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 - -local call_docker = function(options, http_method, api_group, api_action, name_or_id, request, callback) - 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_body = request and request.body or nil - local docker_socket = open_socket(req_options) - - if docker_socket then - return send_http_socket(options, docker_socket, req_header, req_body, callback) - else - return { - headers = { - code=497, - message="bad socket path or host", - protocol="HTTP/1.1" - }, - body = { - message="can\'t connect to socket" - } - } - end -end - -local gen_api = function(_table, http_method, api_group, api_action) - local _api_action - - if api_action == "get_archive" or api_action == "put_archive" then - _api_action = "archive" - 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) - local name_or_id = request and (request.name or request.id or request.name_or_id) or nil - - if api_action == "list" then - if (name_or_id ~= "" and name_or_id ~= nil) then - if api_group == "images" then - 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 - -local _docker = { - containers = {}, - exec = {}, - images = {}, - networks = {}, - volumes = {} -} - -gen_api(_docker, "GET", "containers", "list") -gen_api(_docker, "POST", "containers", "create") -gen_api(_docker, "GET", "containers", "inspect") -gen_api(_docker, "GET", "containers", "top") -gen_api(_docker, "GET", "containers", "logs") -gen_api(_docker, "GET", "containers", "changes") -gen_api(_docker, "GET", "containers", "stats") -gen_api(_docker, "POST", "containers", "resize") -gen_api(_docker, "POST", "containers", "start") -gen_api(_docker, "POST", "containers", "stop") -gen_api(_docker, "POST", "containers", "restart") -gen_api(_docker, "POST", "containers", "kill") -gen_api(_docker, "POST", "containers", "update") -gen_api(_docker, "POST", "containers", "rename") -gen_api(_docker, "POST", "containers", "pause") -gen_api(_docker, "POST", "containers", "unpause") -gen_api(_docker, "POST", "containers", "update") -gen_api(_docker, "DELETE", "containers", "remove") -gen_api(_docker, "POST", "containers", "prune") -gen_api(_docker, "POST", "containers", "exec") -gen_api(_docker, "POST", "exec", "start") -gen_api(_docker, "POST", "exec", "resize") -gen_api(_docker, "GET", "exec", "inspect") -gen_api(_docker, "GET", "containers", "get_archive") -gen_api(_docker, "PUT", "containers", "put_archive") -gen_api(_docker, "GET", "containers", "export") --- TODO: attch - -gen_api(_docker, "GET", "images", "list") -gen_api(_docker, "POST", "images", "create") -gen_api(_docker, "GET", "images", "inspect") -gen_api(_docker, "GET", "images", "history") -gen_api(_docker, "POST", "images", "tag") -gen_api(_docker, "DELETE", "images", "remove") -gen_api(_docker, "GET", "images", "search") -gen_api(_docker, "POST", "images", "prune") -gen_api(_docker, "GET", "images", "get") -gen_api(_docker, "POST", "images", "load") - -gen_api(_docker, "GET", "networks", "list") -gen_api(_docker, "GET", "networks", "inspect") -gen_api(_docker, "DELETE", "networks", "remove") -gen_api(_docker, "POST", "networks", "create") -gen_api(_docker, "POST", "networks", "connect") -gen_api(_docker, "POST", "networks", "disconnect") -gen_api(_docker, "POST", "networks", "prune") - -gen_api(_docker, "GET", "volumes", "list") -gen_api(_docker, "GET", "volumes", "inspect") -gen_api(_docker, "DELETE", "volumes", "remove") -gen_api(_docker, "POST", "volumes", "create") - -gen_api(_docker, "GET", nil, "events") -gen_api(_docker, "GET", nil, "version") -gen_api(_docker, "GET", nil, "info") -gen_api(_docker, "GET", nil, "_ping") -gen_api(_docker, "GET", nil, "df") - -function _docker.new(options) - local docker = {} - local _options = options or {} - - docker.options = { - socket_path = _options.socket_path or nil, - host = _options.socket_path and "localhost" or _options.host, - port = not _options.socket_path and _options.port or nil, - tls = _options.tls or nil, - tls_cacert = _options.tls and _options.tls_cacert or nil, - tls_cert = _options.tls and _options.tls_cert or nil, - tls_key = _options.tls and _options.tls_key or nil, - version = _options.version or "v1.40", - user_agent = _options.user_agent or "LuCI", - protocol = _options.protocol or "HTTP/1.1", - debug = _options.debug or false, - debug_path = _options.debug and _options.debug_path or nil - } - - setmetatable( - docker, - { - __index = function(t, key) - if _docker[key] ~= nil then - return _docker[key] - else - return _docker.containers[key] - end - end - } - ) - - setmetatable( - docker.containers, - { - __index = function(t, key) - if key == "options" then - return docker.options - end - end - } - ) - - setmetatable( - docker.networks, - { - __index = function(t, key) - if key == "options" then - return docker.options - end - end - } - ) - - setmetatable( - docker.images, - { - __index = function(t, key) - if key == "options" then - return docker.options - end - end - } - ) - - setmetatable( - docker.volumes, - { - __index = function(t, key) - if key == "options" then - return docker.options - end - end - } - ) - - setmetatable( - docker.exec, - { - __index = function(t, key) - if key == "options" then - return docker.options - end - end - } - ) - - return docker -end - -return _docker