+++ /dev/null
---[[
-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(" "," ")
- 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
+++ /dev/null
--- 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
+++ /dev/null
---[[
-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(" "," ")
-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
+++ /dev/null
---[[
-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(" "," ")
-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
+++ /dev/null
---[[
-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>","<none>")
- for ci,cv in ipairs(containers) do
- if v.Id == cv.ImageID then
- data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
- '<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("<none>")) and image_list[k].id or image_list[k].tag
- end
- end
-
- if next(image_selected) ~= nil then
- local success = true
-
- docker:clear_status()
- for _, img in ipairs(image_selected) do
- local query
- docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
-
- if force then
- query = {force = true}
- end
-
- local msg = dk.images:remove({
- id = img,
- query = query
- })
- if msg.code ~= 200 then
- docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
- success = false
- else
- docker:append_status("done\n")
- end
- end
-
- if success then
- docker:clear_status()
- end
-
- luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
- end
-end
-
-s = m:section(SimpleSection)
-s.template = "dockerman/apply_widget"
-s.err = docker:read_status()
-s.err = s.err and s.err:gsub("\n","<br />"):gsub(" "," ")
-if s.err then
- docker:clear_status()
-end
-
-s = m:section(Table,{{}})
-s.notitle=true
-s.rowcolors=false
-s.template="cbi/nullsection"
-
-o = s:option(Button, "remove")
-o.inputtitle= translate("Remove")
-o.template = "dockerman/cbi/inlinebutton"
-o.inputstyle = "remove"
-o.forcewrite = true
-o.write = function(self, section)
- 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
+++ /dev/null
---[[
-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(" "," ")
-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
+++ /dev/null
---[[
-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(" "," ")
-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
+++ /dev/null
---[[
-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(" "," ")
-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
+++ /dev/null
---[[
-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
+++ /dev/null
---[[
-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(" "," ")
-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
+++ /dev/null
---[[
-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
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<% 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 -->
+++ /dev/null
-<%+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%>
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<% 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 %>
+++ /dev/null
-<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%>
+++ /dev/null
-<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>
+++ /dev/null
-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
+++ /dev/null
---[[
-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