luci-app-dockerman: drop Lua
authorPaul Donald <newtwen+github@gmail.com>
Mon, 26 Jan 2026 03:29:25 +0000 (04:29 +0100)
committerPaul Donald <newtwen+github@gmail.com>
Wed, 4 Feb 2026 05:47:07 +0000 (06:47 +0100)
follow-up to JS conversion

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
27 files changed:
applications/luci-app-dockerman/luasrc/controller/dockerman.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua [deleted file]
applications/luci-app-dockerman/luasrc/model/docker.lua [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/container.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm [deleted file]
applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm [deleted file]
collections/luci-lib-docker/Makefile [deleted file]
collections/luci-lib-docker/luasrc/docker.lua [deleted file]

diff --git a/applications/luci-app-dockerman/luasrc/controller/dockerman.lua b/applications/luci-app-dockerman/luasrc/controller/dockerman.lua
deleted file mode 100644 (file)
index ac0668b..0000000
+++ /dev/null
@@ -1,459 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-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","<br />"):gsub(" ","&#160;")
-               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 (file)
index 6fd831d..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
--- Copyright 2021 Florian Eckert <fe@dev.tdt.de>
--- 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 (file)
index 5caad4f..0000000
+++ /dev/null
@@ -1,798 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-require "luci.util"
-
-local docker = require "luci.model.docker"
-local 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 .. "<br />") or "") .. out[1]["HostPort"] .. ":" .. inter
-               end
-       end
-
-       return data
-end
-
-local get_env = function(d)
-       local data
-
-       if d.Config and d.Config.Env then
-               for _,v in ipairs(d.Config.Env) do
-                       data = (data and (data .. "<br />") or "") .. v
-               end
-       end
-
-       return data
-end
-
-local get_command = function(d)
-       local data
-
-       if d.Config and d.Config.Cmd then
-               for _,v in ipairs(d.Config.Cmd) do
-                       data = (data and (data .. " ") or "") .. v
-               end
-       end
-
-       return data
-end
-
-local get_mounts = function(d)
-       local data
-
-       if d.Mounts then
-               for _,v in ipairs(d.Mounts) do
-                       local v_sorce_d, v_dest_d
-                       local v_sorce = ""
-                       local v_dest = ""
-                       for v_sorce_d in v["Source"]:gmatch('[^/]+') do
-                               if v_sorce_d and #v_sorce_d > 12 then
-                                       v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..."
-                               else
-                                       v_sorce = v_sorce .."/".. v_sorce_d
-                               end
-                       end
-                       for v_dest_d in v["Destination"]:gmatch('[^/]+') do
-                               if v_dest_d and #v_dest_d > 12 then
-                                       v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..."
-                               else
-                                       v_dest = v_dest .."/".. v_dest_d
-                               end
-                       end
-                       data = (data and (data .. "<br />") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "")
-               end
-       end
-
-       return data
-end
-
-local get_device = function(d)
-       local data
-
-       if d.HostConfig and d.HostConfig.Devices then
-               for _,v in ipairs(d.HostConfig.Devices) do
-                       data = (data and (data .. "<br />") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "")
-               end
-       end
-
-       return data
-end
-
-local get_links = function(d)
-       local data
-
-       if d.HostConfig and d.HostConfig.Links then
-               for _,v in ipairs(d.HostConfig.Links) do
-                       data = (data and (data .. "<br />") or "") .. v
-               end
-       end
-
-       return data
-end
-
-local get_tmpfs = function(d)
-       local data
-
-       if d.HostConfig and d.HostConfig.Tmpfs then
-               for k, v in pairs(d.HostConfig.Tmpfs) do
-                       data = (data and (data .. "<br />") or "") .. k .. (v~="" and ":" or "")..v
-               end
-       end
-
-       return data
-end
-
-local get_dns = function(d)
-       local data
-
-       if d.HostConfig and d.HostConfig.Dns then
-               for _, v in ipairs(d.HostConfig.Dns) do
-                       data = (data and (data .. "<br />") or "") .. v
-               end
-       end
-
-       return data
-end
-
-local get_sysctl = function(d)
-       local data
-
-       if d.HostConfig and d.HostConfig.Sysctls then
-               for k, v in pairs(d.HostConfig.Sysctls) do
-                       data = (data and (data .. "<br />") or "") .. k..":"..v
-               end
-       end
-
-       return data
-end
-
-local get_networks = function(d)
-       local data={}
-
-       if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then
-               for k,v in pairs(d.NetworkSettings.Networks) do
-                       data[k] = v.IPAddress or ""
-               end
-       end
-
-       return data
-end
-
-
-local start_stop_remove = function(m, cmd)
-       local res
-
-       docker:clear_status()
-       docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...")
-
-       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","<br />"):gsub(" ","&#160;")
-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 .. "<br />" .. container_info.Image
-               },
-               ["04status"] = {
-                       _key = translate("Status"),
-                       _value = container_info.State and container_info.State.Status or "-"
-               },
-               ["05created"] = {
-                       _key = translate("Created"),
-                       _value = container_info.Created or "-"
-               },
-       }
-
-       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>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."))
-       o.placeholder = "128m"
-       o.rmempty = true
-       o.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0
-
-       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 (file)
index 2a337a6..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-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"] = '<font color="green">'.. data[index]["_status"] .. "</font>"
-               else
-                       data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>"
-               end
-
-               if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then
-                       for networkname, netconfig in pairs(v.NetworkSettings.Networks) do
-                               data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "")
-                       end
-               end
-
-               if v.Ports and next(v.Ports) ~= nil then
-                       data[index]["_ports"] = nil
-                       for _,v2 in ipairs(v.Ports) do
-                               data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "")
-                                       .. ((v2.PublicPort and v2.Type and v2.Type == "tcp") and ('<a href="javascript:void(0);" onclick="window.open((window.location.origin.match(/^(.+):\\d+$/) && window.location.origin.match(/^(.+):\\d+$/)[1] || window.location.origin) + \':\' + '.. v2.PublicPort ..', \'_blank\');">') or "")
-                                       .. (v2.PublicPort and (v2.PublicPort .. ":") or "")  .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "")
-                                       .. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "</a>" or "")
-                       end
-               end
-
-               for ii,iv in ipairs(images) do
-                       if iv.Id == v.ImageID then
-                               data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or ((iv.RepoDigests[1] or ""):gsub("(.-)@.+", "%1") .. ":<none>")
-                       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","<br />"):gsub(" ","&#160;")
-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 (file)
index 2b84de3..0000000
+++ /dev/null
@@ -1,280 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-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"] = '<a href="javascript:new_tag(\''..v.Id:sub(8,20)..'\')" class="dockerman-link" title="'..translate("New tag")..'">' .. v.Id:sub(8,20) .. '</a>'
-
-               if v.RepoTags and next(v.RepoTags)~=nil then
-                       for i, v1 in ipairs(v.RepoTags) do
-                               data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "<br />" )or "") .. ((v1:match("<none>") or (#v.RepoTags == 1)) and v1 or ('<a href="javascript:un_tag(\''..v1..'\')" class="dockerman_link" title="'..translate("Remove tag")..'" >' .. v1 .. '</a>'))
-
-                               if not data[index]["tag"] then
-                                       data[index]["tag"] = v1
-                               end
-                       end
-               else
-                       data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+")
-                       data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or  "<none>" ).. ":<none>"
-               end
-
-               data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","&lt;none&gt;")
-               for ci,cv in ipairs(containers) do
-                       if v.Id == cv.ImageID then
-                               data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
-                               '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2).."</a>"
-                       end
-               end
-
-               data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
-               data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created)
-       end
-
-       return data
-end
-
-local image_list = get_images()
-
-m = 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("<br />") or image_list[k]["_tags"]:match("&lt;none&gt;")) and image_list[k].id or image_list[k].tag
-               end
-       end
-
-       if next(image_selected) ~= nil then
-               local success = true
-
-               docker:clear_status()
-               for _, img in ipairs(image_selected) do
-                       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","<br />"):gsub(" ","&#160;")
-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 (file)
index f54acbd..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-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","<br />"):gsub(" ","&#160;")
-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 (file)
index 5d38a35..0000000
+++ /dev/null
@@ -1,902 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-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","<br />"):gsub(" ","&#160;")
-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>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M"))
-o.placeholder = "128m"
-o.rmempty = true
-o:depends("advance", 1)
-o.default = default_config.memory or nil
-
-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 (file)
index a9cd67e..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-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","<br />"):gsub(" ","&#160;")
-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 (file)
index 0506670..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-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 (file)
index 5fbd55f..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-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 "")..
-                                       '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2)..'</a>'
-                               end
-                       end
-               end
-               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","<br />"):gsub(" ","&#160;")
-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 (file)
index a0c74c0..0000000
+++ /dev/null
@@ -1,482 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
-]]--
-
-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 (file)
index b9ae1c6..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-<style>
-       #docker_apply_overlay {
-               position: absolute;
-               top: 0;
-               left: 0;
-               bottom: 0;
-               right: 0;
-               background: rgba(0, 0, 0, 0.7);
-               display: none;
-               z-index: 20000;
-       }
-
-       #docker_apply_overlay .alert-message {
-               position: relative;
-               top: 10%;
-               width: 60%;
-               margin: auto;
-               display: flex;
-               flex-wrap: wrap;
-               min-height: 32px;
-               align-items: center;
-       }
-
-       #docker_apply_overlay .alert-message > h4,
-       #docker_apply_overlay .alert-message > p,
-       #docker_apply_overlay .alert-message > div {
-               flex-basis: 100%;
-       }
-
-       #docker_apply_overlay .alert-message > img {
-               margin-right: 1em;
-               flex-basis: 32px;
-       }
-
-       body.apply-overlay-active {
-               overflow: hidden;
-               height: 100vh;
-       }
-
-       body.apply-overlay-active #docker_apply_overlay {
-               display: block;
-       }
-</style>
-
-<script>
-       var xhr = new XHR(),
-       uci_apply_rollback = <%=math.max(luci.config and luci.config.apply and luci.config.apply.rollback or 90, 90)%>,
-       uci_apply_holdoff = <%=math.max(luci.config and luci.config.apply and luci.config.apply.holdoff or 4, 1)%>,
-       uci_apply_timeout = <%=math.max(luci.config and luci.config.apply and luci.config.apply.timeout or 5, 1)%>,
-       uci_apply_display = <%=math.max(luci.config and luci.config.apply and luci.config.apply.display or 1.5, 1)%>,
-       was_xhr_poll_running = false;
-
-       function docker_status_message(type, content) {
-               document.getElementById('docker_apply_overlay') || document.body.insertAdjacentHTML("beforeend",'<div id="docker_apply_overlay"><div class="alert-message"></div></div>')
-               var overlay = document.getElementById('docker_apply_overlay')
-               message = overlay.querySelector('.alert-message');
-
-               if (message && type) {
-                       if (!message.classList.contains(type)) {
-                               message.classList.remove('notice');
-                               message.classList.remove('warning');
-                               message.classList.add(type);
-                       }
-
-                       if (content)
-                               message.innerHTML = content;
-
-                       document.body.classList.add('apply-overlay-active');
-                       document.body.scrollTop = document.documentElement.scrollTop = 0;
-                       if (!was_xhr_poll_running) {
-                               was_xhr_poll_running = XHR.running();
-                               XHR.halt();
-                       }
-               }
-               else {
-                       document.body.classList.remove('apply-overlay-active');
-                       if (was_xhr_poll_running)
-                               XHR.run();
-               }
-       }
-
-       var loading_msg="Loading.."
-       function uci_confirm_docker() {
-               var tt;
-               docker_status_message('notice');
-               var call = function(r, resjson, duration) {
-                       if (r && r.status === 200 ) {
-                               var indicator = document.querySelector('.uci_change_indicator');
-                               if (indicator)
-                                       indicator.style.display = 'none';
-                               docker_status_message('notice', '<%:Docker actions done.%>');
-                               document.body.classList.remove('apply-overlay-active');
-                               window.clearTimeout(tt);
-                               return;
-                       }
-                       loading_msg = resjson?resjson.info:loading_msg
-                       // var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0);
-                       var delay =1000
-                       window.setTimeout(function() {
-                               xhr.get('<%=url("admin/docker/confirm")%>', null, call, uci_apply_timeout * 1000);
-                       },delay);
-               };
-
-               var tick = function() {
-                       var now = Date.now();
-
-                       docker_status_message(
-                               'notice',
-                               '<img src="<%=resource%>/icons/loading.svg" alt="" style="vertical-align:middle" /> <span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">' + loading_msg + '</span>'
-                       );
-
-                       tt = window.setTimeout(tick, 200);
-                       ts = now;
-               };
-
-               tick();
-               /* wait a few seconds for the settings to become effective */
-               window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 , 1));
-       }
-       // document.getElementsByTagName("form")[0].addEventListener("submit", (e)=>{
-       // uci_confirm_docker()
-       // })
-
-       function fnSubmitForm(el){
-               if (el.id != "cbid.table.1._new") {
-                       uci_confirm_docker()
-               }
-       }
-
-       <% if self.err then -%>
-               docker_status_message('warning', '<span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">'+`<%=self.err%>`+'</span>');
-               document.getElementById('docker_apply_overlay').addEventListener(
-                       "click",
-                       (e)=>{
-                               docker_status_message()
-                       }
-               )
-       <%- end %>
-
-       window.onload= function (){
-               var buttons = document.querySelectorAll('input[type="submit"]');
-               [].slice.call(buttons).forEach(function (el) {
-                       el.onclick = fnSubmitForm.bind(this, el);
-               });
-       }
-
-</script>
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 (file)
index a061a6d..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<div style="display: inline-block;">
-       <% if self:cfgvalue(section) ~= false then %>
-               <input class="btn cbi-button cbi-button-<%=self.inputstyle or "button" %>" type="submit"" <% if self.disable then %>disabled <% end %><%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> />
-       <% else %>
-               -
-       <% end %>
-</div>
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 (file)
index e4b0cf7..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<div style="display: inline-block;">
-       <!-- <%- if self.title then -%>
-       <label class="cbi-value-title"<%= attr("for", cbid) %>>
-               <%- if self.titleref then -%><a title="<%=self.titledesc or translate('Go to relevant configuration page')%>" class="cbi-title-ref" href="<%=self.titleref%>"><%- end -%>
-                       <%-=self.title-%>
-               <%- if self.titleref then -%></a><%- end -%>
-               </label>
-       <%- end -%> -->
-       <%- if self.password then -%>
-               <input type="password" style="position:absolute; left:-100000px" aria-hidden="true"<%=
-                       attr("name", "password." .. cbid)
-               %> />
-       <%- end -%>
-       <input data-update="change"<%=
-               attr("id", cbid) ..
-               attr("name", cbid) ..
-               attr("type", self.password and "password" or "text") ..
-               attr("class", self.password and "cbi-input-password" or "cbi-input-text") ..
-               attr("value", self:cfgvalue(section) or self.default) ..
-               ifattr(self.password, "autocomplete", "new-password") ..
-               ifattr(self.size, "size") ..
-               ifattr(self.placeholder, "placeholder") ..
-               ifattr(self.readonly, "readonly") ..
-               ifattr(self.maxlength, "maxlength") ..
-               ifattr(self.datatype, "data-type", self.datatype) ..
-               ifattr(self.datatype, "data-optional", self.optional or self.rmempty) ..
-               ifattr(self.combobox_manual, "data-manual", self.combobox_manual) ..
-               ifattr(#self.keylist > 0, "data-choices", { self.keylist, self.vallist })
-       %> />
-       <%- if self.password then -%>
-               <div class="btn cbi-button cbi-button-neutral" title="<%:Reveal/hide password%>" onclick="var e = this.previousElementSibling; e.type = (e.type === 'password') ? 'text' : 'password'">∗</div>
-       <% end %>
-</div>
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 (file)
index 244d2c1..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<% if self:cfgvalue(self.section) then section = self.section %>
-       <div class="cbi-section" id="cbi-<%=self.config%>-<%=section%>">
-               <%+cbi/tabmenu%>
-               <div class="cbi-section-node<% if self.tabs then %> cbi-section-node-tabbed<% end %>" id="cbi-<%=self.config%>-<%=section%>">
-                       <%+cbi/ucisection%>
-               </div>
-       </div>
-<% end %>
-<!-- /nsection -->
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 (file)
index 04f7bc2..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<%+cbi/valueheader%>
-       <input type="hidden" value="1"<%=
-               attr("name", "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option)
-       %> />
-       <input class="cbi-input-checkbox" data-update="click change" type="checkbox" <% if self.disable == 1 then %>disabled <% end %><%=
-               attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) ..
-               ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked")
-       %> />
-       <label<%= attr("for", cbid)%>></label>
-<%+cbi/valuefooter%>
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 (file)
index f9b2f43..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<br />
-<ul class="cbi-tabmenu">
-       <li id="cbi-tab-container_info"><a id="a-cbi-tab-container_info" href=""><%:Info%></a></li>
-       <li id="cbi-tab-container_resources"><a id="a-cbi-tab-container_resources" href=""><%:Resources%></a></li>
-       <li id="cbi-tab-container_stats"><a id="a-cbi-tab-container_stats" href=""><%:Stats%></a></li>
-       <li id="cbi-tab-container_file"><a id="a-cbi-tab-container_file" href=""><%:File%></a></li>
-       <li id="cbi-tab-container_console"><a id="a-cbi-tab-container_console" href=""><%:Console%></a></li>
-       <li id="cbi-tab-container_inspect"><a id="a-cbi-tab-container_inspect" href=""><%:Inspect%></a></li>
-       <li id="cbi-tab-container_logs"><a id="a-cbi-tab-container_logs" href=""><%:Logs%></a></li>
-</ul>
-
-<script>
-       let re = /\/admin\/docker\/container\//
-       let p = window.location.href
-       let path = p.split(re)
-       let container_id = path[1].split('/')[0] || path[1]
-       let action = path[1].split('/')[1] || "info"
-       let actions=["info","resources","stats","file","console","logs","inspect"]
-       actions.forEach(function(item) {
-               document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/docker/container/"+container_id+'/'+item
-               if (action === item) {
-                       document.getElementById("cbi-tab-container_" + item).className="cbi-tab"
-               }
-               else {
-                       document.getElementById("cbi-tab-container_" + item).className="cbi-tab-disabled"
-               }
-       })
-</script>
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 (file)
index 030acf7..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<div class="cbi-map">
-       <iframe id="terminal" style="width: 100%; min-height: 500px; border: none; border-radius: 3px;"></iframe>
-</div>
-<script>
-       document.getElementById("terminal").src = "http://" + window.location.hostname + ":7682";
-</script>
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 (file)
index 53f2c84..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<div id="upload-container" class="cbi-value cbi-value-last">
-       <label class="cbi-value-title" for="archive"><%:Upload%></label>
-       <div class="cbi-value-field">
-               <input type="file" name="upload_archive" accept="application/x-tar" id="upload_archive" />
-       </div>
-       <br />
-       <label class="cbi-value-title" for="path"><%:Path%></label>
-       <div class="cbi-value-field">
-               <input type="text" class="cbi-input-text" name="path" value="/tmp/" id="path" />
-       </div>
-       <br />
-       <div class="cbi-value-field">
-               <input type="button"" class="btn cbi-button cbi-button-action important" id="upload" name="upload" value="<%:Upload%>" />
-               <input type="button"" class="btn cbi-button cbi-button-action important" id="download" name="download" value="<%:Download%>" />
-       </div>
-</div>
-
-<script>
-       let btnUpload = document.getElementById('upload')
-       btnUpload.onclick = function (e) {
-               let uploadArchive = document.getElementById('upload_archive')
-               let uploadPath = document.getElementById('path').value
-               if (!uploadArchive.value || !uploadPath) {
-                       docker_status_message('warning', "<%:Please input the PATH and select the file !%>")
-                       document.getElementById('docker_apply_overlay').addEventListener(
-                               "click",
-                               (e)=>{
-                                       docker_status_message()
-                               }
-                       )
-                       return
-               }
-               let fileName = uploadArchive.files[0].name
-               let formData = new FormData()
-               formData.append('upload-filename', fileName)
-               formData.append('upload-path', uploadPath)
-               formData.append('upload-archive', uploadArchive.files[0])
-               let xhr = new XMLHttpRequest()
-               xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/container_put_archive")%>/<%=self.container%>', true)
-               xhr.onload = function() {
-                       if (xhr.status == 200) {
-                               uploadArchive.value = ''
-                               docker_status_message('notice', "<%:Upload Success%>")
-                       }
-                       else {
-                               docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText)
-                       }
-                       document.getElementById('docker_apply_overlay').addEventListener(
-                               "click",
-                               (e)=>{
-                                       docker_status_message()
-                               }
-                       )
-               }
-               xhr.send(formData)
-       }
-
-       let btnDownload = document.getElementById('download')
-       btnDownload.onclick = function (e) {
-               let downloadPath = document.getElementById('path').value
-               if (!downloadPath) {
-                       docker_status_message('warning', "<%:Please input the PATH !%>")
-                       document.getElementById('docker_apply_overlay').addEventListener(
-                               "click",
-                               (e)=>{
-                                       docker_status_message()
-                               }
-                       )
-                       return
-               }
-               window.open('<%=luci.dispatcher.build_url("admin/docker/container_get_archive")%>?id=<%=self.container%>&path=' + encodeURIComponent(downloadPath))
-       }
-</script>
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 (file)
index 28eaada..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<script>
-       let last_bw_tx
-       let last_bw_rx
-       let interval = 3
-
-       function progressbar(v, m, pc, np, f) {
-               m = m || 100
-
-               return String.format(
-                       '<div style="width:100%%; max-width:500px; position:relative; border:1px solid #999999">' +
-                       '<div style="background-color:#CCCCCC; width:%d%%; height:15px">' +
-                       '<div style="position:absolute; left:0; top:0; text-align:center; width:100%%; color:#000000">' +
-                       '<small>%s '+(f?f:'/')+' %s ' + (np ? "" : '(%d%%)') + '</small>' +
-                       '</div>' +
-                       '</div>' +
-                       '</div>', pc, v, m, pc, f
-               );
-       }
-
-       function niceBytes(bytes, decimals) {
-               if (bytes == 0) return '0 Bytes';
-               var k = 1000,
-                       dm = decimals + 1 || 3,
-                       sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
-                       i = Math.floor(Math.log(bytes) / Math.log(k));
-               return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
-       }
-
-       XHR.poll(interval, '<%=luci.dispatcher.build_url("admin/docker/container_stats")%>/<%=self.container_id%>', { status: 1 },
-               function (x, info) {
-                       var e;
-
-                       if (e = document.getElementById('cbi-table-cpu-value'))
-                               e.innerHTML = progressbar(
-                                       (info.cpu_percent), 100, (info.cpu_percent ? info.cpu_percent : 0));
-                       if (e = document.getElementById('cbi-table-memory-value'))
-                               e.innerHTML = progressbar(
-                                       niceBytes(info.memory.mem_useage),
-                                       niceBytes(info.memory.mem_limit),
-                                       ((100 / (info.memory.mem_limit ? info.memory.mem_limit : 100)) * (info.memory.mem_useage ? info.memory.mem_useage : 0))
-                               );
-
-                       for (var eth in info.bw_rxtx) {
-                               if (!document.getElementById("cbi-table-speed_" + eth + "-value")) {
-                                       let tab = document.getElementById("cbi-table-cpu").parentNode
-                                       let div = document.getElementById('cbi-table-cpu').cloneNode(true);
-                                       div.id = "cbi-table-speed_" + eth;
-                                       div.children[0].innerHTML = "<%:Upload/Download%>: " + eth
-                                       div.children[1].id = "cbi-table-speed_" + eth + "-value"
-                                       tab.appendChild(div)
-                               }
-                               if (!document.getElementById("cbi-table-network_" + eth + "-value")) {
-                                       let tab = document.getElementById("cbi-table-cpu").parentNode
-                                       let div = document.getElementById('cbi-table-cpu').cloneNode(true);
-                                       div.id = "cbi-table-network_" + eth;
-                                       div.children[0].innerHTML = "<%:TX/RX%>: " + eth
-                                       div.children[1].id = "cbi-table-network_" + eth + "-value"
-                                       tab.appendChild(div)
-                               }
-                               e = document.getElementById("cbi-table-network_" + eth + "-value")
-                               e.innerHTML = progressbar(
-                                       '↑'+niceBytes(info.bw_rxtx[eth].bw_tx),
-                                       '↓'+niceBytes(info.bw_rxtx[eth].bw_rx),
-                                       null,
-                                       true, " "
-                               );
-                               e = document.getElementById("cbi-table-speed_" + eth + "-value")
-                               if (! last_bw_tx) last_bw_tx = info.bw_rxtx[eth].bw_tx
-                               if (! last_bw_rx) last_bw_rx = info.bw_rxtx[eth].bw_rx
-                               e.innerHTML = progressbar(
-                                       '↑'+niceBytes((info.bw_rxtx[eth].bw_tx - last_bw_tx)/interval)+'/s',
-                                       '↓'+niceBytes((info.bw_rxtx[eth].bw_rx - last_bw_rx)/interval)+'/s',
-                                       null,
-                                       true, " "
-                               );
-                               last_bw_tx = info.bw_rxtx[eth].bw_tx
-                               last_bw_rx = info.bw_rxtx[eth].bw_rx
-                       }
-
-               });
-</script>
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 (file)
index 8c27ead..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-<input type="text" class="cbi-input-text" name="isrc" placeholder="http://host/image.tar" id="isrc" />
-<input type="text" class="cbi-input-text" name="itag" placeholder="repository:tag" id="itag" />
-<div style="display: inline-block;">
-       <input type="button"" class="btn cbi-button cbi-button-add" id="btnimport" name="import" value="<%:Import%>" />
-       <input type="file" id="file_import" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" />
-</div>
-
-<script>
-       let btnImport = document.getElementById('btnimport')
-       let valISrc = document.getElementById('isrc')
-       let valITag = document.getElementById('itag')
-       btnImport.onclick = function (e) {
-               if (valISrc.value == "") {
-                       document.getElementById("file_import").click()
-                       return
-               }
-               else {
-                       let formData = new FormData()
-                       formData.append('src', valISrc.value)
-                       formData.append('tag', valITag.value)
-                       let xhr = new XMLHttpRequest()
-                       uci_confirm_docker()
-                       xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true)
-                       xhr.onload = function () {
-                               location.reload()
-                       }
-                       xhr.send(formData)
-               }
-       }
-
-       let fileimport = document.getElementById('file_import')
-       fileimport.onchange = function (e) {
-               let fileimport = document.getElementById('file_import')
-               if (!fileimport.value) {
-                       return
-               }
-               let valITag = document.getElementById('itag')
-               let fileName = fileimport.files[0].name
-               let formData = new FormData()
-               formData.append('upload-filename', fileName)
-               formData.append('tag', valITag.value)
-               formData.append('upload-archive', fileimport.files[0])
-               let xhr = new XMLHttpRequest()
-               uci_confirm_docker()
-               xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true)
-               xhr.onload = function () {
-                       fileimport.value = ''
-                       location.reload()
-               }
-               xhr.send(formData)
-       }
-
-       let new_tag = function (image_id) {
-               let new_tag = prompt("<%:New tag%>\n<%:Image%>" + "ID: " + image_id + "\n<%:Please input new tag%>:", "")
-               if (new_tag) {
-                       (new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_tag')%>",
-                               {
-                                       id: image_id,
-                                       tag: new_tag
-                               },
-                               function (r) {
-                                       if (r.status == 201) {
-                                               location.reload()
-                                       }
-                                       else {
-                                               docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
-                                               document.getElementById('docker_apply_overlay').addEventListener(
-                                                       "click",
-                                                       (e)=>{
-                                                               docker_status_message()
-                                                       }
-                                               )
-                                       }
-                               }
-                       )
-               }
-       }
-
-       let un_tag = function (tag) {
-               if (tag.match("<none>"))
-                       return
-               if (confirm("<%:Remove tag%>: " + tag + " ?")) {
-                       (new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_untag')%>",
-                               {
-                                       tag: tag
-                               },
-                               function (r) {
-                                       if (r.status == 200) {
-                                               location.reload()
-                                       }
-                                       else {
-                                               docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
-                                               document.getElementById('docker_apply_overlay').addEventListener(
-                                                       "click",
-                                                       (e)=>{
-                                                               docker_status_message()
-                                                       }
-                                               )
-                                       }
-                               }
-                       )
-               }
-       }
-</script>
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 (file)
index 1beb784..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<div style="display: inline-block;">
-       <input type="button"" class="btn cbi-button cbi-button-add" id="btnload" name="load" value="<%:Load%>" />
-       <input type="file" id="file_load" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" accept="application/x-tar" />
-</div>
-<script>
-       let btnLoad = document.getElementById('btnload')
-       btnLoad.onclick = function (e) {
-               document.getElementById("file_load").click()
-               e.preventDefault()
-       }
-
-       let fileLoad = document.getElementById('file_load')
-       fileLoad.onchange = function(e){
-               let fileLoad = document.getElementById('file_load')
-               if (!fileLoad.value) {
-                       return
-               }
-               let fileName = fileLoad.files[0].name
-               let formData = new FormData()
-               formData.append('upload-filename', fileName)
-               formData.append('upload-archive', fileLoad.files[0])
-               let xhr = new XMLHttpRequest()
-               uci_confirm_docker()
-               xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/images_load")%>', true)
-               xhr.onload = function() {
-                       location.reload()
-               }
-       xhr.send(formData)
-       }
-</script>
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 (file)
index c66637d..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<% if self.title == "Events" then %>
-<%+header%>
-<h2 name="content"><%:Docker%></h2>
-<div class="cbi-section">
-<h3><%:Events%></h3>
-<% end %>
-<div id="content_syslog">
-<textarea readonly="readonly" wrap="off" rows="<%=self.syslog:cmatch('\n')+2%>" id="syslog"><%=self.syslog:pcdata()%></textarea>
-</div>
-<% if self.title == "Events" then %>
-</div>
-<%+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 (file)
index abf9442..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-<style>
-       #dialog_reslov {
-               position: absolute;
-               top: 0;
-               left: 0;
-               bottom: 0;
-               right: 0;
-               background: rgba(0, 0, 0, 0.7);
-               display: none;
-               z-index: 20000;
-       }
-
-       #dialog_reslov .dialog_box {
-               position: relative;
-               background: rgba(255, 255, 255);
-               top: 10%;
-               width: 50%;
-               margin: auto;
-               display: flex;
-               flex-wrap: wrap;
-               height:auto;
-               align-items: center;
-       }
-
-       #dialog_reslov .dialog_line {
-               margin-top: .5em;
-               margin-bottom: .5em;
-               margin-left: 2em;
-               margin-right: 2em;
-       }
-
-       #dialog_reslov .dialog_box>h4,
-       #dialog_reslov .dialog_box>p,
-       #dialog_reslov .dialog_box>div {
-               flex-basis: 100%;
-       }
-
-       #dialog_reslov .dialog_box>img {
-               margin-right: 1em;
-               flex-basis: 32px;
-       }
-
-       body.dialog-reslov-active {
-               overflow: hidden;
-               height: 100vh;
-       }
-
-       body.dialog-reslov-active #dialog_reslov {
-               display: block;
-       }
-</style>
-
-<script>
-       function close_reslov_dialog() {
-               document.body.classList.remove('dialog-reslov-active')
-               document.documentElement.style.overflowY = 'scroll'
-       }
-
-       function reslov_container() {
-               let s = document.getElementById('cmd-line-status')
-
-               if (!s)
-                       return
-
-               let cmd_line = document.getElementById("dialog_reslov_text").value;
-               if (cmd_line == null || cmd_line == "") {
-                       return
-               }
-
-               cmd_line = cmd_line.replace(/(^\s*)/g,"")
-               if (!cmd_line.match(/^docker\s+(run|create)/)) {
-                       s.innerHTML = "<font color='red'><%:Command line Error%></font>"
-                       return
-               }
-
-               let reg_space = /\s+/g
-               let reg_muti_line= /\\\s*\n/g
-               //   reg_rem =/(?<!\\)`#.+(?<!\\)`/g  // the command has `# `
-               let reg_rem =/`#.+`/g// the command has `# `
-               cmd_line = cmd_line.replace(/^docker\s+(run|create)/,"DOCKERCLI").replace(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ")
-               console.log(cmd_line)
-               window.location.href = '<%=luci.dispatcher.build_url("admin/docker/newcontainer")%>/' + encodeURI(cmd_line)
-       }
-
-       function clear_text(){
-               let s = document.getElementById('cmd-line-status')
-               s.innerHTML = ""
-       }
-
-       function show_reslov_dialog() {
-               document.getElementById('dialog_reslov') || document.body.insertAdjacentHTML("beforeend", '<div id="dialog_reslov"><div class="dialog_box"><div class="dialog_line"></div><div class="dialog_line"><span><%:Plese input <docker create/run> command line:%></span><br /><span id="cmd-line-status"></span></div><div class="dialog_line"><textarea class="cbi-input-textarea" id="dialog_reslov_text" style="width: 100%; height:100%;" rows="15" onkeyup="clear_text()"></textarea></div><div class="dialog_line" style="text-align: right;"><input type="button" class="btn cbi-button cbi-button-apply" type="submit" value="<%:Submit%>" onclick="reslov_container()" /> <input type="button" class="btn cbi-button cbi-button-reset" type="reset" value="<%:Cancel%>" onclick="close_reslov_dialog()" /></div><div class="dialog_line"></div></div></div>')
-               document.body.classList.add('dialog-reslov-active')
-               let s = document.getElementById('cmd-line-status')
-               s.innerHTML = ""
-               document.documentElement.style.overflowY = 'hidden'
-       }
-</script>
-<%+cbi/valueheader%>
-
-<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Command line%>" onclick="show_reslov_dialog()" />
-
-<%+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 (file)
index e491fc5..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-<style>
-       /*!
-       Pure v1.0.1
-       Copyright 2013 Yahoo!
-       Licensed under the BSD License.
-       https://github.com/pure-css/pure/blob/master/LICENSE.md
-       */
-       .pure-g {
-               letter-spacing: -.31em;
-               text-rendering: optimizespeed;
-               font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif;
-               display: -webkit-box;
-               display: -webkit-flex;
-               display: -ms-flexbox;
-               display: flex;
-               -webkit-box-orient: horizontal;
-               -webkit-box-direction: normal;
-               -webkit-flex-flow: row wrap;
-               -ms-flex-flow: row wrap;
-               flex-flow: row wrap;
-               -webkit-align-content: flex-start;
-               -ms-flex-line-pack: start;
-               align-content: flex-start
-       }
-
-       .pure-u {
-               display: inline-block;
-               zoom: 1;
-               letter-spacing: normal;
-               word-spacing: normal;
-               vertical-align: top;
-               text-rendering: auto
-       }
-
-       .pure-g [class*=pure-u] {
-               font-family: sans-serif
-       }
-
-       .pure-u-1-4,
-       .pure-u-2-5,
-       .pure-u-3-5 {
-               display: inline-block;
-               zoom: 1;
-               letter-spacing: normal;
-               word-spacing: normal;
-               vertical-align: top;
-               text-rendering: auto
-       }
-
-       .pure-u-1-4 {
-               width: 25%
-       }
-
-       .pure-u-2-5 {
-               width: 40%
-       }
-
-       .pure-u-3-5 {
-               width: 60%
-       }
-
-       .status {
-               margin: 1rem -0.5rem 1rem -0.5rem;
-       }
-
-       .block {
-               margin: 0.5rem 0.5rem;
-               padding: 0;
-               font-weight: normal;
-               font-style: normal;
-               line-height: 1;
-               font-family: inherit;
-               min-width: inherit;
-               overflow-x: auto;
-               overflow-y: hidden;
-               border: 1px solid rgba(0, 0, 0, .05);
-               border-radius: .375rem;
-               box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15);
-       }
-
-       .img-con {
-               margin: 1rem;
-               min-width: 4rem;
-               max-width: 4rem;
-               min-height: 4rem;
-               max-height: 4rem;
-       }
-
-       .block h4 {
-               font-size: .8125rem;
-               font-weight: 600;
-               margin: 1rem;
-               color: #8898aa !important;
-               line-height: 1.8em;
-       }
-
-       .cbi-section-table-cell {
-               position: relative;
-       }
-
-       @media screen and (max-width: 700px) {
-               .pure-u-1-4 {
-                       width: 50%;
-               }
-
-               .cbi-button-add {
-                       position: fixed;
-                       padding: 0.3rem 0.5rem;
-                       z-index: 1000;
-                       width: 50px !important;
-                       height: 50px;
-                       bottom: 90px;
-                       right: 5px;
-                       font-size: 16px;
-                       border-radius: 50%;
-                       display: block;
-                       background-color: #fb6340 !important;
-                       border-color: #fb6340 !important;
-                       box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .75);
-               }
-       }
-</style>
-
-<div class="pure-g status">
-       <div class="pure-u-1-4">
-               <div class="block pure-g">
-                       <div class="pure-u-2-5">
-                               <div class="img-con">
-                                       <img src="<%=resource%>/dockerman/containers.svg" />
-                               </div>
-                       </div>
-                       <div class="pure-u-3-5">
-                               <h4 style="text-align: right; font-size: 1rem"><%:Containers%></h4>
-                               <h4 style="text-align: right;">
-                                       <%- if self.containers_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/containers")%>'><%- end -%>
-                                       <span style="font-size: 2rem; color: #2dce89;"><%=self.containers_running%></span>
-                                       <span style="font-size: 1rem; color: #8898aa !important;">/<%=self.containers_total%></span>
-                               <%- if self.containers_total ~= "-" then -%></a><%- end -%>
-                               </h4>
-                       </div>
-               </div>
-       </div>
-       <div class="pure-u-1-4">
-               <div class="block pure-g">
-                       <div class="pure-u-2-5">
-                               <div class="img-con">
-                                       <img src="<%=resource%>/dockerman/images.svg" />
-                               </div>
-                       </div>
-                       <div class="pure-u-3-5">
-                               <h4 style="text-align: right; font-size: 1rem"><%:Images%></h4>
-                               <h4 style="text-align: right;">
-                                       <%- if self.images_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/images")%>'><%- end -%>
-                                       <span style="font-size: 2rem; color: #2dce89;"><%=self.images_used%></span>
-                                       <span style="font-size: 1rem; color: #8898aa !important;">/<%=self.images_total%></span>
-                                       <%- if self.images_total ~= "-" then -%></a><%- end -%>
-                               </h4>
-                       </div>
-               </div>
-       </div>
-       <div class="pure-u-1-4">
-               <div class="block pure-g">
-                       <div class="pure-u-2-5">
-                               <div class="img-con">
-                                       <img src="<%=resource%>/dockerman/networks.svg" />
-                               </div>
-                       </div>
-                       <div class="pure-u-3-5">
-                               <h4 style="text-align: right; font-size: 1rem"><%:Networks%></h4>
-                               <h4 style="text-align: right;">
-                                       <%- if self.networks_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/networks")%>'><%- end -%>
-                                       <span style="font-size: 2rem; color: #2dce89;"><%=self.networks_total%></span>
-                                       <!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
-                                       <%- if self.networks_total ~= "-" then -%></a><%- end -%>
-                               </h4>
-                       </div>
-               </div>
-       </div>
-       <div class="pure-u-1-4">
-               <div class="block pure-g">
-                       <div class="pure-u-2-5">
-                               <div class="img-con">
-                                       <img src="<%=resource%>/dockerman/volumes.svg" />
-                               </div>
-                       </div>
-                       <div class="pure-u-3-5">
-                               <h4 style="text-align: right; font-size: 1rem"><%:Volumes%></h4>
-                               <h4 style="text-align: right;">
-                                       <%- if self.volumes_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/volumes")%>'><%- end -%>
-                                       <span style="font-size: 2rem; color: #2dce89;"><%=self.volumes_total%></span>
-                                       <!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
-                                       <%- if self.volumes_total ~= "-" then -%></a><%- end -%>
-                               </h4>
-                       </div>
-               </div>
-       </div>
-</div>
diff --git a/collections/luci-lib-docker/Makefile b/collections/luci-lib-docker/Makefile
deleted file mode 100644 (file)
index 11221e5..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-include $(TOPDIR)/rules.mk
-
-PKG_NAME:=luci-lib-docker
-PKG_LICENSE:=AGPL-3.0
-PKG_MAINTAINER:=lisaac <lisaac.cn@gmail.com> \
-       Florian Eckert <fe@dev.tdt.de>
-
-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 (file)
index 346b0ef..0000000
+++ /dev/null
@@ -1,537 +0,0 @@
---[[
-LuCI - Lua Configuration Interface
-Copyright 2019 lisaac <https://github.com/lisaac/luci-lib-docker>
-]]--
-
-require "nixio.util"
-require "luci.util"
-local jsonc = require "luci.jsonc"
-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