luci-app-docker: refresh source code
This commit is contained in:
parent
1a65bbb6f1
commit
415e56d9a2
@ -1,18 +1,13 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=Docker Manager interface for LuCI
|
||||
LUCI_DEPENDS:=+luci-lib-docker +docker-ce +e2fsprogs +fdisk
|
||||
LUCI_DEPENDS:=+luci-lib-docker +docker-ce +e2fsprogs +fdisk +ttyd
|
||||
PKG_NAME:=luci-app-dockerman
|
||||
PKG_VERSION:=v0.3.0
|
||||
PKG_RELEASE:=leanmod-3
|
||||
PKG_VERSION:=v0.4.7
|
||||
PKG_RELEASE:=leanmod
|
||||
PKG_MAINTAINER:=lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
PKG_LICENSE:=AGPL-3.0
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
define Package/$(PKG_NAME)/postinst
|
||||
#!/bin/sh
|
||||
rm -rf /tmp/luci-indexcache /tmp/luci-modulecache
|
||||
endef
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
@ -13,8 +13,16 @@ function index()
|
||||
entry({"admin", "services","docker"}, firstchild(), "Docker", 40).dependent = false
|
||||
entry({"admin","services","docker","overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true
|
||||
|
||||
local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
|
||||
if not nixio.fs.access(socket) then return end
|
||||
local remote = luci.model.uci.cursor():get("dockerman", "local", "remote_endpoint")
|
||||
if remote == nil then
|
||||
local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
|
||||
if socket and not nixio.fs.access(socket) then return end
|
||||
elseif remote == "true" then
|
||||
local host = luci.model.uci.cursor():get("dockerman", "local", "remote_host")
|
||||
local port = luci.model.uci.cursor():get("dockerman", "local", "remote_port")
|
||||
if not host or not port then return end
|
||||
end
|
||||
|
||||
if (require "luci.model.docker").new():_ping().code ~= 200 then return end
|
||||
entry({"admin","services","docker","containers"},form("dockerman/containers"),_("Containers"),1).leaf=true
|
||||
entry({"admin","services","docker","images"},form("dockerman/images"),_("Images"),2).leaf=true
|
||||
@ -27,27 +35,33 @@ function index()
|
||||
entry({"admin","services","docker","container_stats"},call("action_get_container_stats")).leaf=true
|
||||
entry({"admin","services","docker","container_get_archive"},call("download_archive")).leaf=true
|
||||
entry({"admin","services","docker","container_put_archive"},call("upload_archive")).leaf=true
|
||||
entry({"admin","services","docker","images_save"},call("save_images")).leaf=true
|
||||
entry({"admin","services","docker","images_load"},call("load_images")).leaf=true
|
||||
entry({"admin","services","docker","images_import"},call("import_images")).leaf=true
|
||||
entry({"admin","services","docker","images_get_tags"},call("get_image_tags")).leaf=true
|
||||
entry({"admin","services","docker","images_tag"},call("tag_image")).leaf=true
|
||||
entry({"admin","services","docker","images_untag"},call("untag_image")).leaf=true
|
||||
entry({"admin","services","docker","confirm"},call("action_confirm")).leaf=true
|
||||
|
||||
end
|
||||
|
||||
|
||||
function action_events()
|
||||
local logs = ""
|
||||
local dk = docker.new()
|
||||
local query ={}
|
||||
query["until"] = os.time()
|
||||
local events = dk:events({query = query})
|
||||
for _, v in ipairs(events.body) do
|
||||
if v.Type == "container" then
|
||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. (v.Action or "null") .. " Container ID:".. (v.Actor.ID or "null") .. " Container Name:" .. (v.Actor.Attributes.name or "null")
|
||||
elseif v.Type == "network" then
|
||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Container ID:"..( v.Actor.Attributes.container or "null" ) .. " Network Name:" .. (v.Actor.Attributes.name or "null") .. " Network type:".. v.Actor.Attributes.type or ""
|
||||
elseif v.Type == "image" then
|
||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Image:".. (v.Actor.ID or "null").. " Image Name:" .. (v.Actor.Attributes.name or "null")
|
||||
local events = dk:events({query = query})
|
||||
if events.code == 200 then
|
||||
for _, v in ipairs(events.body) do
|
||||
if v and v.Type == "container" then
|
||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. (v.Action or "null") .. " Container ID:".. (v.Actor.ID or "null") .. " Container Name:" .. (v.Actor.Attributes.name or "null")
|
||||
elseif v.Type == "network" then
|
||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Container ID:"..( v.Actor.Attributes.container or "null" ) .. " Network Name:" .. (v.Actor.Attributes.name or "null") .. " Network type:".. v.Actor.Attributes.type or ""
|
||||
elseif v.Type == "image" then
|
||||
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Image:".. (v.Actor.ID or "null").. " Image Name:" .. (v.Actor.Attributes.name or "null")
|
||||
end
|
||||
end
|
||||
end
|
||||
luci.template.render("dockerman/logs", {self={syslog = logs, title="Docker Events"}})
|
||||
luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}})
|
||||
end
|
||||
|
||||
local calculate_cpu_percent = function(d)
|
||||
@ -133,14 +147,14 @@ function action_get_container_stats(container_id)
|
||||
else
|
||||
luci.http.status(404, "No container name or id")
|
||||
luci.http.prepare_content("text/plain")
|
||||
luci.http.write("No container name or id")
|
||||
luci.http.write("No container name or id")
|
||||
end
|
||||
end
|
||||
|
||||
function action_confirm()
|
||||
local status_path=luci.model.uci.cursor():get("dockerman", "local", "status_path")
|
||||
local data = nixio.fs.readfile(status_path)
|
||||
local data = docker:read_status()
|
||||
if data then
|
||||
data = data:gsub("\n","<br>"):gsub(" "," ")
|
||||
code = 202
|
||||
msg = data
|
||||
else
|
||||
@ -148,7 +162,6 @@ function action_confirm()
|
||||
msg = "finish"
|
||||
data = "finish"
|
||||
end
|
||||
-- luci.util.perror(data)
|
||||
luci.http.status(code, msg)
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({info = data})
|
||||
@ -185,7 +198,7 @@ function upload_archive(container_id)
|
||||
local dk = docker.new()
|
||||
local ltn12 = require "luci.ltn12"
|
||||
|
||||
rec_send = function(sinkout)
|
||||
local rec_send = function(sinkout)
|
||||
luci.http.setfilehandler(function (meta, chunk, eof)
|
||||
if chunk then
|
||||
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
|
||||
@ -199,3 +212,173 @@ function upload_archive(container_id)
|
||||
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})
|
||||
-- res.body = {"stream":"Loaded image ID: sha256:1399d3d81f80d68832e85ed6ba5f94436ca17966539ba715f661bd36f3caf08f\n"}
|
||||
local msg = res and res.body and ( res.body.message or res.body.stream or res.body.error)or nil
|
||||
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
|
||||
-- -- res.body = {"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
|
||||
-- msg = res.body.status or res.body.error
|
||||
-- elseif not msg and #res.body >= 1 then
|
||||
-- -- res.body = [...{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}]
|
||||
-- msg = res.body[#res.body].status or res.body[#res.body].error
|
||||
-- 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
|
||||
|
||||
@ -10,7 +10,7 @@ local dk = docker.new()
|
||||
container_id = arg[1]
|
||||
local action = arg[2] or "info"
|
||||
|
||||
local images, networks, containers_info
|
||||
local images, networks, container_info
|
||||
if not container_id then return end
|
||||
local res = dk.containers:inspect({id = container_id})
|
||||
if res.code < 300 then container_info = res.body else return end
|
||||
@ -94,6 +94,36 @@ local get_links = function(d)
|
||||
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
|
||||
@ -115,7 +145,7 @@ local start_stop_remove = function(m, cmd)
|
||||
res = dk.containers_upgrade(dk, {id = container_id})
|
||||
end
|
||||
if res and res.code >= 300 then
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/container/"..container_id))
|
||||
else
|
||||
docker:clear_status()
|
||||
@ -133,8 +163,8 @@ m.redirect = luci.dispatcher.build_url("admin/services/docker/containers")
|
||||
-- m:append(Template("dockerman/container"))
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
-- luci.util.perror(docker_status.err)
|
||||
docker_status.err=docker:read_status()
|
||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
|
||||
@ -165,7 +195,7 @@ btnupgrade.inputstyle = "reload"
|
||||
btnstop.forcewrite = true
|
||||
btnduplicate=action_section:option(Button, "_duplicate")
|
||||
btnduplicate.template = "dockerman/cbi/inlinebutton"
|
||||
btnduplicate.inputtitle=translate("Duplicate")
|
||||
btnduplicate.inputtitle=translate("Duplicate/Edit")
|
||||
btnduplicate.inputstyle = "add"
|
||||
btnstop.forcewrite = true
|
||||
btnremove=action_section:option(Button, "_remove")
|
||||
@ -209,13 +239,16 @@ if action == "info" then
|
||||
table_info["06start"] = container_info.State.Status == "running" and {_key = translate("Start Time"), _value = container_info.State and container_info.State.StartedAt or "-"} or {_key = translate("Finish Time"), _value = container_info.State and container_info.State.FinishedAt or "-"}
|
||||
table_info["07healthy"] = {_key = translate("Healthy"), _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"}
|
||||
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["09device"] = {_key = translate("Device"), _value = get_device(container_info) or "-"}
|
||||
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
|
||||
@ -322,9 +355,10 @@ if action == "info" then
|
||||
if table_info[section]._button and table_info[section]._value ~= nil then
|
||||
btn_update.inputtitle=table_info[section]._button
|
||||
self.template = "cbi/button"
|
||||
self.inputstyle = "edit"
|
||||
Button.render(self, section, scope)
|
||||
else
|
||||
self.template = "dockerman/cbi/dummyvalue"
|
||||
self.template = "cbi/dvalue"
|
||||
self.default=""
|
||||
DummyValue.render(self, section, scope)
|
||||
end
|
||||
@ -361,7 +395,7 @@ if action == "info" then
|
||||
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("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
else
|
||||
docker:clear_status()
|
||||
end
|
||||
@ -418,11 +452,10 @@ elseif action == "edit" then
|
||||
Memory = tonumber(memory),
|
||||
CpuShares = tonumber(data.cpushares)
|
||||
}
|
||||
docker:clear_status()
|
||||
docker:append_status("Containers: update " .. container_id .. "...")
|
||||
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("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
else
|
||||
docker:clear_status()
|
||||
end
|
||||
@ -453,6 +486,61 @@ elseif action == "logs" then
|
||||
logsection.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("which docker"):match("^.+docker") or nil
|
||||
local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil
|
||||
if cmd_docker and cmd_ttyd then
|
||||
local consolesection= m:section(SimpleSection)
|
||||
local cmd = "/bin/sh"
|
||||
local uid
|
||||
local vcommand = consolesection:option(Value, "command", translate("Command"))
|
||||
vcommand:value("/bin/sh", "/bin/sh")
|
||||
vcommand:value("/bin/ash", "/bin/ash")
|
||||
vcommand:value("/bin/bash", "/bin/bash")
|
||||
vcommand.default = "/bin/sh"
|
||||
vcommand.forcewrite = true
|
||||
vcommand.write = function(self, section, value)
|
||||
cmd = value
|
||||
end
|
||||
local vuid = consolesection:option(Value, "uid", translate("UID"))
|
||||
vuid.forcewrite = true
|
||||
vuid.write = function(self, section, value)
|
||||
uid = value
|
||||
end
|
||||
local btn_connect = consolesection:option(Button, "connect")
|
||||
btn_connect.render = function(self, section, scope)
|
||||
self.inputstyle = "add"
|
||||
self.title = " "
|
||||
self.inputtitle = translate("Connect")
|
||||
Button.render(self, section, scope)
|
||||
end
|
||||
btn_connect.write = function(self, section)
|
||||
local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil
|
||||
local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil
|
||||
if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$") then return end
|
||||
local kill_ttyd = 'netstat -lnpt | grep ":7682[ \t].*ttyd$" | awk \'{print $NF}\' | awk -F\'/\' \'{print "kill -9 " $1}\' | sh > /dev/null'
|
||||
local hosts
|
||||
local remote = uci:get("dockerman", "local", "remote_endpoint")
|
||||
local socket_path = (remote == "false") and uci:get("dockerman", "local", "socket_path") or nil
|
||||
local host = (remote == "true") and uci:get("dockerman", "local", "remote_host") or nil
|
||||
local port = (remote == "true") and uci:get("dockerman", "local", "remote_port") or nil
|
||||
if remote and host and port then
|
||||
hosts = host .. ':'.. port
|
||||
elseif socket_path then
|
||||
hosts = "unix://" .. socket_path
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
local start_cmd = cmd_ttyd .. ' -d 2 -p 7682 '.. cmd_docker .. ' -H "'.. hosts ..'" exec -it ' .. (uid and uid ~= "" and (" -u ".. uid .. ' ') or "").. container_id .. ' ' .. cmd .. ' &'
|
||||
local res = luci.util.exec(start_cmd)
|
||||
local console = consolesection:option(DummyValue, "console")
|
||||
console.container_id = container_id
|
||||
console.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
|
||||
@ -469,7 +557,7 @@ elseif action == "stats" then
|
||||
container_top=response.body
|
||||
stat_section = m:section(SimpleSection)
|
||||
stat_section.container_id = container_id
|
||||
stat_section.template = "dockerman/stats"
|
||||
stat_section.template = "dockerman/container_stats"
|
||||
table_stats = {cpu={key=translate("CPU Useage"),value='-'},memory={key=translate("Memory Useage"),value='-'}}
|
||||
stat_section = m:section(Table, table_stats, translate("Stats"))
|
||||
stat_section:option(DummyValue, "key", translate("Stats")).width="33%"
|
||||
@ -484,5 +572,4 @@ m.submit = false
|
||||
m.reset = false
|
||||
end
|
||||
|
||||
|
||||
return m
|
||||
|
||||
@ -27,7 +27,8 @@ function get_containers()
|
||||
data[index]={}
|
||||
data[index]["_selected"] = 0
|
||||
data[index]["_id"] = v.Id:sub(1,12)
|
||||
data[index]["_name"] = v.Names[1]:sub(2)
|
||||
data[index]["name"] = v.Names[1]:sub(2)
|
||||
data[index]["_name"] = '<a href='..luci.dispatcher.build_url("admin/services/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. v.Names[1]:sub(2).."</a>"
|
||||
data[index]["_status"] = v.Status
|
||||
if v.Status:find("^Up") then
|
||||
data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>"
|
||||
@ -54,7 +55,7 @@ function get_containers()
|
||||
end
|
||||
for ii,iv in ipairs(images) do
|
||||
if iv.Id == v.ImageID then
|
||||
data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":none")
|
||||
data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>")
|
||||
end
|
||||
end
|
||||
|
||||
@ -74,8 +75,8 @@ m.reset=false
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
-- luci.util.perror(docker_status.err)
|
||||
docker_status.err=docker:read_status()
|
||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
c_table = m:section(Table, c_lists, translate("Containers"))
|
||||
@ -90,11 +91,7 @@ container_selecter.default = 0
|
||||
container_id = c_table:option(DummyValue, "_id", translate("ID"))
|
||||
container_id.width="10%"
|
||||
container_name = c_table:option(DummyValue, "_name", translate("Container Name"))
|
||||
container_name.width="20%"
|
||||
container_name.template = "dockerman/cbi/dummyvalue"
|
||||
container_name.href = function (self, section)
|
||||
return luci.dispatcher.build_url("admin/services/docker/container/" .. urlencode(container_id:cfgvalue(section)))
|
||||
end
|
||||
container_name.rawhtml = true
|
||||
container_status = c_table:option(DummyValue, "_status", translate("Status"))
|
||||
container_status.width="15%"
|
||||
container_status.rawhtml=true
|
||||
@ -103,11 +100,7 @@ container_ip.width="15%"
|
||||
container_ports = c_table:option(DummyValue, "_ports", translate("Ports"))
|
||||
container_ports.width="10%"
|
||||
container_image = c_table:option(DummyValue, "_image", translate("Image"))
|
||||
container_image.template = "dockerman/cbi/dummyvalue"
|
||||
container_image.width="10%"
|
||||
-- container_image.href = function (self, section)
|
||||
-- return luci.dispatcher.build_url("admin/services/docker/image/" .. urlencode(c_lists[section]._image_id))
|
||||
-- end
|
||||
container_command = c_table:option(DummyValue, "_command", translate("Command"))
|
||||
container_command.width="20%"
|
||||
|
||||
@ -116,18 +109,13 @@ container_selecter.write=function(self, section, value)
|
||||
end
|
||||
|
||||
local start_stop_remove = function(m,cmd)
|
||||
-- luci.template.render("admin_uci/apply", {
|
||||
-- changes = next(changes) and changes,
|
||||
-- configs = reload
|
||||
-- })
|
||||
|
||||
local c_selected = {}
|
||||
-- 遍历table中sectionid
|
||||
local c_table_sids = c_table:cfgsections()
|
||||
for _, c_table_sid in ipairs(c_table_sids) do
|
||||
-- 得到选中项的名字
|
||||
if c_lists[c_table_sid]._selected == 1 then
|
||||
c_selected[#c_selected+1] = container_name:cfgvalue(c_table_sid)
|
||||
c_selected[#c_selected+1] = c_lists[c_table_sid].name --container_name:cfgvalue(c_table_sid)
|
||||
end
|
||||
end
|
||||
if #c_selected >0 then
|
||||
@ -138,9 +126,9 @@ local start_stop_remove = function(m,cmd)
|
||||
local res = dk.containers[cmd](dk, {id = cont})
|
||||
if res and res.code >= 300 then
|
||||
success = false
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
|
||||
else
|
||||
docker:append_status("done<br>")
|
||||
docker:append_status("done\n")
|
||||
end
|
||||
end
|
||||
if success then docker:clear_status() end
|
||||
|
||||
@ -20,19 +20,25 @@ function get_images()
|
||||
local index = v.Created .. v.Id
|
||||
data[index]={}
|
||||
data[index]["_selected"] = 0
|
||||
data[index]["_id"] = v.Id:sub(8,20)
|
||||
if v.RepoTags then
|
||||
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
|
||||
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--:match("<none>") and nil or v1
|
||||
end
|
||||
end
|
||||
else
|
||||
_,_, data[index]["_tags"] = v.RepoDigests[1]:find("^(.-)@.+")
|
||||
data[index]["_tags"]=data[index]["_tags"]..":none"
|
||||
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>")
|
||||
-- data[index]["_tags"] = '<a href="javascript:handle_tag(\''..data[index]["_id"]..'\')">' .. data[index]["_tags"] .. '</a>'
|
||||
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=/cgi-bin/luci/admin/services/docker/container/"..cv.Id.." >".. cv.Names[1]:sub(2).."</a>"
|
||||
'<a href='..luci.dispatcher.build_url("admin/services/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"
|
||||
@ -49,65 +55,55 @@ m.template = "dockerman/cbi/xsimpleform"
|
||||
m.submit=false
|
||||
m.reset=false
|
||||
|
||||
local pull_value={{_image_tag_name="", _registry="index.docker.io"}}
|
||||
local pull_section = m:section(Table,pull_value, translate("Pull Image"))
|
||||
local pull_value={_image_tag_name="", _registry="index.docker.io"}
|
||||
local pull_section = m:section(SimpleSection, translate("Pull Image"))
|
||||
pull_section.template="cbi/nullsection"
|
||||
local tag_name = pull_section:option(Value, "_image_tag_name")
|
||||
tag_name.template = "dockerman/cbi/inlinevalue"
|
||||
tag_name.placeholder="hello-world:latest"
|
||||
local registry = pull_section:option(Value, "_registry")
|
||||
registry.template = "dockerman/cbi/inlinevalue"
|
||||
registry:value("index.docker.io", "Docker Hub")
|
||||
registry:value("hub-mirror.c.163.com", "163 Mirror")
|
||||
registry:value("mirror.ccs.tencentyun.com", "Tencent Mirror")
|
||||
registry:value("docker.mirrors.ustc.edu.cn", "USTC Mirror")
|
||||
local action_pull = pull_section:option(Button, "_pull")
|
||||
action_pull.inputtitle= translate("Pull")
|
||||
action_pull.template = "dockerman/cbi/inlinebutton"
|
||||
action_pull.inputstyle = "add"
|
||||
tag_name.write = function(self, section,value)
|
||||
tag_name.write = function(self, section, value)
|
||||
local hastag = value:find(":")
|
||||
if not hastag then
|
||||
value = value .. ":latest"
|
||||
end
|
||||
pull_value[section]["_image_tag_name"] = value
|
||||
end
|
||||
registry.write = function(self, section,value)
|
||||
pull_value[section]["_registry"] = value
|
||||
pull_value["_image_tag_name"] = value
|
||||
end
|
||||
action_pull.write = function(self, section)
|
||||
local tag = pull_value[section]["_image_tag_name"]
|
||||
local server = pull_value[section]["_registry"]
|
||||
--去掉协议前缀和后缀
|
||||
local _,_,tmp = server:find(".-://([%.%w%-%_]+)")
|
||||
if not tmp then
|
||||
_,_,server = server:find("([%.%w%-%_]+)")
|
||||
end
|
||||
local tag = pull_value["_image_tag_name"]
|
||||
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
||||
if tag and tag ~= "" then
|
||||
docker:clear_status()
|
||||
docker:append_status("Images: " .. "pulling" .. " " .. tag .. "...")
|
||||
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
|
||||
local res = dk.images:create({query = {fromImage=tag}, header={["X-Registry-Auth"] = x_auth}})
|
||||
if res and res.code >=300 then
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n")
|
||||
-- local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server})) , header={["X-Registry-Auth"] = x_auth}
|
||||
local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb)
|
||||
-- {"errorDetail": {"message": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }, "error": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }
|
||||
if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then
|
||||
docker:clear_status()
|
||||
else
|
||||
docker:append_status("done<br>")
|
||||
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("fail code: 400 please input the name of image name!")
|
||||
docker:append_status("code: 400 please input the name of image name!")
|
||||
end
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/images"))
|
||||
end
|
||||
|
||||
image_table = m:section(Table, image_list, translate("Images"))
|
||||
-- local import_section = m:section(SimpleSection, translate("Import Images"))
|
||||
-- local im = import_section:option(DummyValue, "_image_import")
|
||||
-- im.template = "dockerman/images_import"
|
||||
|
||||
image_selecter = image_table:option(Flag, "_selected","")
|
||||
local image_table = m:section(Table, image_list, translate("Images"))
|
||||
|
||||
local image_selecter = image_table:option(Flag, "_selected","")
|
||||
image_selecter.disabled = 0
|
||||
image_selecter.enabled = 1
|
||||
image_selecter.default = 0
|
||||
|
||||
image_id = image_table:option(DummyValue, "_id", translate("ID"))
|
||||
local image_id = image_table:option(DummyValue, "_id", translate("ID"))
|
||||
image_id.rawhtml = true
|
||||
image_table:option(DummyValue, "_tags", translate("RepoTags")).rawhtml = true
|
||||
image_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
|
||||
image_table:option(DummyValue, "_size", translate("Size"))
|
||||
@ -123,7 +119,7 @@ local remove_action = function(force)
|
||||
for _, image_table_sid in ipairs(image_table_sids) do
|
||||
-- 得到选中项的名字
|
||||
if image_list[image_table_sid]._selected == 1 then
|
||||
image_selected[#image_selected+1] = image_id:cfgvalue(image_table_sid)
|
||||
image_selected[#image_selected+1] = (image_list[image_table_sid]["_tags"]:match("<br>") or image_list[image_table_sid]["_tags"]:match("<none>")) and image_list[image_table_sid].id or image_list[image_table_sid].tag
|
||||
end
|
||||
end
|
||||
if next(image_selected) ~= nil then
|
||||
@ -135,10 +131,10 @@ local remove_action = function(force)
|
||||
if force then query = {force = true} end
|
||||
local msg = dk.images:remove({id = img, query = query})
|
||||
if msg.code ~= 200 then
|
||||
docker:append_status("fail code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "<br>")
|
||||
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
||||
success = false
|
||||
else
|
||||
docker:append_status("done<br>")
|
||||
docker:append_status("done\n")
|
||||
end
|
||||
end
|
||||
if success then docker:clear_status() end
|
||||
@ -146,17 +142,18 @@ local remove_action = function(force)
|
||||
end
|
||||
end
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
local docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
docker_status.err = docker:read_status()
|
||||
docker_status.err = docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
action = m:section(Table,{{}})
|
||||
local action = m:section(Table,{{}})
|
||||
action.notitle=true
|
||||
action.rowcolors=false
|
||||
action.template="cbi/nullsection"
|
||||
|
||||
btnremove = action:option(Button, "remove")
|
||||
local btnremove = action:option(Button, "remove")
|
||||
btnremove.inputtitle= translate("Remove")
|
||||
btnremove.template = "dockerman/cbi/inlinebutton"
|
||||
btnremove.inputstyle = "remove"
|
||||
@ -165,7 +162,7 @@ btnremove.write = function(self, section)
|
||||
remove_action()
|
||||
end
|
||||
|
||||
btnforceremove = action:option(Button, "forceremove")
|
||||
local btnforceremove = action:option(Button, "forceremove")
|
||||
btnforceremove.inputtitle= translate("Force Remove")
|
||||
btnforceremove.template = "dockerman/cbi/inlinebutton"
|
||||
btnforceremove.inputstyle = "remove"
|
||||
@ -173,4 +170,55 @@ btnforceremove.forcewrite = true
|
||||
btnforceremove.write = function(self, section)
|
||||
remove_action(true)
|
||||
end
|
||||
|
||||
local btnsave = action:option(Button, "save")
|
||||
btnsave.inputtitle= translate("Save")
|
||||
btnsave.template = "dockerman/cbi/inlinebutton"
|
||||
btnsave.inputstyle = "edit"
|
||||
btnsave.forcewrite = true
|
||||
btnsave.write = function (self, section)
|
||||
local image_selected = {}
|
||||
local image_table_sids = image_table:cfgsections()
|
||||
for _, image_table_sid in ipairs(image_table_sids) do
|
||||
if image_list[image_table_sid]._selected == 1 then
|
||||
image_selected[#image_selected+1] = image_list[image_table_sid].id --image_id:cfgvalue(image_table_sid)
|
||||
end
|
||||
end
|
||||
if next(image_selected) ~= nil then
|
||||
local names
|
||||
for _,img in ipairs(image_selected) do
|
||||
names = names and (names .. "&names=".. img) or img
|
||||
end
|
||||
local first
|
||||
local cb = function(res, chunk)
|
||||
if res.code == 200 then
|
||||
if not first then
|
||||
first = true
|
||||
luci.http.header('Content-Disposition', 'inline; filename="images.tar"')
|
||||
luci.http.header('Content-Type', 'application\/x-tar')
|
||||
end
|
||||
luci.ltn12.pump.all(chunk, luci.http.write)
|
||||
else
|
||||
if not first then
|
||||
first = true
|
||||
luci.http.prepare_content("text/plain")
|
||||
end
|
||||
luci.ltn12.pump.all(chunk, luci.http.write)
|
||||
end
|
||||
end
|
||||
docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...")
|
||||
local msg = dk.images:get({query = {names = names}}, cb)
|
||||
if msg.code ~= 200 then
|
||||
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
||||
success = false
|
||||
else
|
||||
docker:clear_status()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local btnload = action:option(Button, "load")
|
||||
btnload.inputtitle= translate("Load")
|
||||
btnload.template = "dockerman/images_load"
|
||||
btnload.inputstyle = "add"
|
||||
return m
|
||||
|
||||
@ -69,7 +69,8 @@ end
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
docker_status.err=docker:read_status()
|
||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
action = m:section(Table,{{}})
|
||||
@ -97,7 +98,7 @@ btnremove.write = function(self, section)
|
||||
for _, network_table_sid in ipairs(network_table_sids) do
|
||||
-- 得到选中项的名字
|
||||
if network_list[network_table_sid]._selected == 1 then
|
||||
network_selected[#network_selected+1] = network_name:cfgvalue(network_table_sid)
|
||||
network_selected[#network_selected+1] = network_list[network_table_sid]._id --network_name:cfgvalue(network_table_sid)
|
||||
end
|
||||
end
|
||||
if next(network_selected) ~= nil then
|
||||
@ -107,10 +108,10 @@ btnremove.write = function(self, section)
|
||||
docker:append_status("Networks: " .. "remove" .. " " .. net .. "...")
|
||||
local res = dk.networks["remove"](dk, {id = net})
|
||||
if res and res.code >= 300 then
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
|
||||
success = false
|
||||
else
|
||||
docker:append_status("done<br>")
|
||||
docker:append_status("done\n")
|
||||
end
|
||||
end
|
||||
if success then
|
||||
|
||||
@ -4,7 +4,6 @@ Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
||||
]]--
|
||||
|
||||
require "luci.util"
|
||||
require "math"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local docker = require "luci.model.docker"
|
||||
local dk = docker.new()
|
||||
@ -16,149 +15,128 @@ local networks = dk.networks:list().body
|
||||
local containers = dk.containers:list({query = {all=true}}).body
|
||||
|
||||
local is_quot_complete = function(str)
|
||||
require "math"
|
||||
if not str then return true end
|
||||
local num = 0, w
|
||||
for w in str:gmatch("[\"\']") do
|
||||
for w in str:gmatch("\"") do
|
||||
num = num + 1
|
||||
end
|
||||
if math.fmod(num, 2) ~= 0 then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
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
|
||||
|
||||
-- reslvo default config
|
||||
local default_config = { }
|
||||
if cmd_line and cmd_line:match("^docker.+") then
|
||||
local key = nil, _key
|
||||
--cursor = 0: docker run
|
||||
--cursor = 1: resloving para
|
||||
--cursor = 2: resloving image
|
||||
--cursor > 2: resloving command
|
||||
local cursor = 0
|
||||
for w in cmd_line:gmatch("[^%s]+") do
|
||||
-- skip '\'
|
||||
if w == '\\' then
|
||||
elseif _key then
|
||||
-- there is a value that unpair quotation marks:
|
||||
-- "i was a ok man"
|
||||
-- now we only get: "i
|
||||
if _key == "mount" or _key == "link" or _key == "env" or _key == "dns" or _key == "port" or _key == "device" or _key == "tmpfs" then
|
||||
default_config[_key][#default_config[_key]] = default_config[_key][#default_config[_key]] .. " " .. w
|
||||
if is_quot_complete(default_config[_key][#default_config[_key]]) then
|
||||
-- clear quotation marks
|
||||
default_config[_key][#default_config[_key]] = default_config[_key][#default_config[_key]]:gsub("[\"\']", "")
|
||||
_key = nil
|
||||
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|'
|
||||
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|P|publish_all|restart|runtime|shm_size|sig_proxy|stop_signal|stop_timeout|ulimit|u|user|userns|uts|volume_driver|w|workdir|'
|
||||
local key_abb = {net='network',a='attach',c='cpu-shares',d='detach',e='env',h='hostname',i='interactive',l='label',m='memory',p='publish',P='publish_all',t='tty',u='user',v='volume',w='workdir'}
|
||||
local key_with_list = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|'
|
||||
local key = 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") 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 == "" then key = nil end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if key then
|
||||
key = key:gsub("-","_")
|
||||
if key_no_val:match("|"..key.."|") then
|
||||
key = key_abb[key] or key
|
||||
config[key] = true
|
||||
val = nil
|
||||
key = nil
|
||||
elseif key_with_val:match("|"..key.."|") then
|
||||
key = key_abb[key] or key
|
||||
if key == "cap_add" then config.privileged = true end
|
||||
else
|
||||
key = nil
|
||||
val = nil
|
||||
end
|
||||
else
|
||||
default_config[_key] = default_config[_key] .. " ".. w
|
||||
if is_quot_complete(default_config[_key]) then
|
||||
-- clear quotation marks
|
||||
default_config[_key] = default_config[_key]:gsub("[\"\']", "")
|
||||
_key = nil
|
||||
end
|
||||
end
|
||||
-- start with '-'
|
||||
elseif w:match("^%-+.+") and cursor <= 1 then
|
||||
--key=value
|
||||
local val
|
||||
key, val = w:match("^%-+(.-)=(.+)")
|
||||
-- -dit
|
||||
if not key then key = w:match("^%-+(.+)") end
|
||||
|
||||
if not key then
|
||||
key = w:match("^%-(.+)")
|
||||
if key:match("i") or key:match("t") or key:match("d") then
|
||||
if key:match("i") then default_config["interactive"] = true end
|
||||
if key:match("t") then default_config["tty"] = true end
|
||||
-- clear key
|
||||
key = nil
|
||||
end
|
||||
end
|
||||
|
||||
if key == "v" or key == "volume" then
|
||||
key = "mount"
|
||||
elseif key == "p" or key == "publish" then
|
||||
key = "port"
|
||||
elseif key == "e" then
|
||||
key = "env"
|
||||
elseif key == "dns" then
|
||||
key = "dns"
|
||||
elseif key == "net" then
|
||||
key = "network"
|
||||
elseif key == "h" or key == "hostname" then
|
||||
key = "hostname"
|
||||
elseif key == "cpu-shares" then
|
||||
key = "cpushares"
|
||||
elseif key == "m" then
|
||||
key = "memory"
|
||||
elseif key == "blkio-weight" then
|
||||
key = "blkioweight"
|
||||
elseif key == "privileged" then
|
||||
default_config["privileged"] = true
|
||||
config.image = w
|
||||
key = nil
|
||||
elseif key == "cap-add" then
|
||||
default_config["privileged"] = true
|
||||
val = nil
|
||||
is_cmd = true
|
||||
end
|
||||
--key=value
|
||||
if val then
|
||||
if key == "mount" or key == "link" or key == "env" or key == "dns" or key == "port" or key == "device" or key == "tmpfs" then
|
||||
if not default_config[key] then default_config[key] = {} end
|
||||
table.insert( default_config[key], val )
|
||||
-- clear quotation marks
|
||||
default_config[key][#default_config[key]] = default_config[key][#default_config[key]]:gsub("[\"\']", "")
|
||||
elseif (key or _key) and not is_cmd then
|
||||
val = w
|
||||
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 key_with_list:match(key) then
|
||||
if not config[key] then config[key] = {} end
|
||||
if _key then
|
||||
config[key][#config[key]] = config[key][#config[key]] .. " " .. w
|
||||
else
|
||||
default_config[key] = val
|
||||
-- clear quotation marks
|
||||
default_config[key] = default_config[key]:gsub("[\"\']", "")
|
||||
table.insert( config[key], val )
|
||||
end
|
||||
-- if there are " or ' in val and separate by space, we need keep the _key to link with next w
|
||||
if is_quot_complete(val) then
|
||||
if is_quot_complete(config[key][#config[key]]) then
|
||||
-- clear quotation marks
|
||||
config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "")
|
||||
_key = nil
|
||||
else
|
||||
_key = key
|
||||
end
|
||||
-- clear key
|
||||
key = nil
|
||||
end
|
||||
cursor = 1
|
||||
-- value
|
||||
elseif key and type(key) == "string" and cursor == 1 then
|
||||
if key == "mount" or key == "link" or key == "env" or key == "dns" or key == "port" or key == "device" or key == "tmpfs" then
|
||||
if not default_config[key] then default_config[key] = {} end
|
||||
table.insert( default_config[key], w )
|
||||
-- clear quotation marks
|
||||
default_config[key][#default_config[key]] = default_config[key][#default_config[key]]:gsub("[\"\']", "")
|
||||
else
|
||||
default_config[key] = w
|
||||
-- clear quotation marks
|
||||
default_config[key] = default_config[key]:gsub("[\"\']", "")
|
||||
end
|
||||
if key == "cpus" or key == "cpushare" or key == "memory" or key == "blkioweight" or key == "device" or key == "tmpfs" then
|
||||
default_config["advance"] = 1
|
||||
end
|
||||
-- if there are " or ' in val and separate by space, we need keep the _key to link with next w
|
||||
if is_quot_complete(w) then
|
||||
_key = nil
|
||||
else
|
||||
_key = key
|
||||
config[key] = (config[key] and (config[key] .. " ") or "") .. val
|
||||
if is_quot_complete(config[key]) then
|
||||
-- clear quotation marks
|
||||
config[key] = config[key]:gsub("[\"\']", "")
|
||||
_key = nil
|
||||
else
|
||||
_key = key
|
||||
end
|
||||
end
|
||||
key = nil
|
||||
cursor = 1
|
||||
--image and command
|
||||
elseif cursor >= 1 and key == nil then
|
||||
if cursor == 1 then
|
||||
default_config["image"] = w
|
||||
elseif cursor > 1 then
|
||||
default_config["command"] = (default_config["command"] and (default_config["command"] .. " " )or "") .. w
|
||||
end
|
||||
cursor = cursor + 1
|
||||
val = nil
|
||||
end
|
||||
end
|
||||
return config
|
||||
end
|
||||
-- reslvo default config
|
||||
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})
|
||||
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
|
||||
@ -170,17 +148,24 @@ elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
|
||||
default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil
|
||||
-- default_config.network = create_body.HostConfig.NetworkMode == "default" and "bridge" or create_body.HostConfig.NetworkMode
|
||||
-- if container has leave original network, and add new network, .HostConfig.NetworkMode is INcorrect, so using first child of .NetworkingConfig.EndpointsConfig
|
||||
default_config.network = next(create_body.NetworkingConfig.EndpointsConfig)
|
||||
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.mount = create_body.HostConfig.Binds
|
||||
default_config.volume = create_body.HostConfig.Binds
|
||||
|
||||
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.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then
|
||||
default_config.port = {}
|
||||
default_config.publish = {}
|
||||
for k, v in pairs(create_body.HostConfig.PortBindings) do
|
||||
table.insert( default_config.port, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
|
||||
table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
|
||||
end
|
||||
end
|
||||
|
||||
@ -188,9 +173,9 @@ elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
|
||||
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.cpushares = create_body.HostConfig.CpuShares
|
||||
default_config.cpu_shares = create_body.HostConfig.CpuShares
|
||||
default_config.memory = create_body.HostConfig.Memory
|
||||
default_config.blkioweight = create_body.HostConfig.BlkioWeight
|
||||
default_config.blkio_weight = create_body.HostConfig.BlkioWeight
|
||||
|
||||
if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then
|
||||
default_config.device = {}
|
||||
@ -198,8 +183,12 @@ elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
|
||||
table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") )
|
||||
end
|
||||
end
|
||||
|
||||
default_config.tmpfs = create_body.HostConfig.Tmpfs
|
||||
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
|
||||
|
||||
@ -212,16 +201,17 @@ m.redirect = luci.dispatcher.build_url("admin", "services","docker", "containers
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
docker_status.err=docker:read_status()
|
||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
local s = m:section(SimpleSection, translate("New Container"))
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
|
||||
local d = s:option(DummyValue,"cmd_line", translate("Resolv CLI"))
|
||||
local d = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
|
||||
d.rawhtml = true
|
||||
d.template = "dockerman/resolv_container"
|
||||
d.template = "dockerman/newcontainer_resolve"
|
||||
|
||||
d = s:option(Value, "name", translate("Container Name"))
|
||||
d.rmempty = true
|
||||
@ -302,17 +292,17 @@ d.placeholder = "TZ=Asia/Shanghai"
|
||||
d.rmempty = true
|
||||
d.default = default_config.env or nil
|
||||
|
||||
d = s:option(DynamicList, "mount", translate("Bind Mount(-v)"), translate("Bind mount a volume"))
|
||||
d = s:option(DynamicList, "volume", translate("Bind Mount(-v)"), translate("Bind mount a volume"))
|
||||
d.template = "dockerman/cbi/xdynlist"
|
||||
d.placeholder = "/media:/media:slave"
|
||||
d.rmempty = true
|
||||
d.default = default_config.mount or nil
|
||||
d.default = default_config.volume or nil
|
||||
|
||||
local d_ports = s:option(DynamicList, "port", translate("Exposed Ports(-p)"), translate("Publish container's port(s) to the host"))
|
||||
d_ports.template = "dockerman/cbi/xdynlist"
|
||||
d_ports.placeholder = "2200:22/tcp"
|
||||
d_ports.rmempty = true
|
||||
d_ports.default = default_config.port or nil
|
||||
local d_publish = s:option(DynamicList, "publish", translate("Exposed Ports(-p)"), translate("Publish container's port(s) to the host"))
|
||||
d_publish.template = "dockerman/cbi/xdynlist"
|
||||
d_publish.placeholder = "2200:22/tcp"
|
||||
d_publish.rmempty = true
|
||||
d_publish.default = default_config.publish or nil
|
||||
|
||||
d = s:option(Value, "command", translate("Run command"))
|
||||
d.placeholder = "/bin/sh init.sh"
|
||||
@ -344,6 +334,13 @@ d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.default = default_config.tmpfs or nil
|
||||
|
||||
d = s:option(DynamicList, "sysctl", translate("Sysctl(--sysctl)"), translate("Sysctls (kernel parameters) options"))
|
||||
d.template = "dockerman/cbi/xdynlist"
|
||||
d.placeholder = "net.ipv4.ip_forward=1"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.default = default_config.sysctl or nil
|
||||
|
||||
d = s:option(Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
|
||||
d.placeholder = "1.5"
|
||||
d.rmempty = true
|
||||
@ -351,12 +348,12 @@ d:depends("advance", 1)
|
||||
d.datatype="ufloat"
|
||||
d.default = default_config.cpus or nil
|
||||
|
||||
d = 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."))
|
||||
d = s:option(Value, "cpu_shares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
|
||||
d.placeholder = "1024"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.datatype="uinteger"
|
||||
d.default = default_config.cpushares or nil
|
||||
d.default = default_config.cpu_shares or nil
|
||||
|
||||
d = s:option(Value, "memory", translate("Memory"), translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."))
|
||||
d.placeholder = "128m"
|
||||
@ -364,12 +361,12 @@ d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.default = default_config.memory or nil
|
||||
|
||||
d = s:option(Value, "blkioweight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
|
||||
d = s:option(Value, "blkio_weight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
|
||||
d.placeholder = "500"
|
||||
d.rmempty = true
|
||||
d:depends("advance", 1)
|
||||
d.datatype="uinteger"
|
||||
d.default = default_config.blkioweight or nil
|
||||
d.default = default_config.blkio_weight or nil
|
||||
|
||||
|
||||
for _, v in ipairs (networks) do
|
||||
@ -385,7 +382,7 @@ for _, v in ipairs (networks) do
|
||||
end
|
||||
|
||||
if v.Driver == "bridge" then
|
||||
d_ports:depends("network", v.Name)
|
||||
d_publish:depends("network", v.Name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -406,13 +403,23 @@ m.handle = function(self, state, data)
|
||||
local restart = data.restart
|
||||
local env = data.env
|
||||
local dns = data.dns
|
||||
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 network = data.network
|
||||
local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
|
||||
local mount = data.mount
|
||||
local volume = data.volume
|
||||
local memory = data.memory or 0
|
||||
local cpushares = data.cpushares or 0
|
||||
local cpu_shares = data.cpu_shares or 0
|
||||
local cpus = data.cpus or 0
|
||||
local blkioweight = data.blkioweight or 500
|
||||
local blkio_weight = data.blkio_weight or 500
|
||||
|
||||
local portbindings = {}
|
||||
local exposedports = {}
|
||||
@ -420,8 +427,9 @@ m.handle = function(self, state, data)
|
||||
tmp = data.tmpfs
|
||||
if type(tmp) == "table" then
|
||||
for i, v in ipairs(tmp)do
|
||||
local _,_, k,v1 = v:find("(.-):(.+)")
|
||||
if k and v1 then
|
||||
local k= v:match("([^:]+)")
|
||||
local v1 = v:match(".-:([^:]+)") or ""
|
||||
if k then
|
||||
tmpfs[k]=v1
|
||||
end
|
||||
end
|
||||
@ -451,7 +459,7 @@ m.handle = function(self, state, data)
|
||||
end
|
||||
end
|
||||
|
||||
tmp = data.port or {}
|
||||
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+)")
|
||||
@ -497,14 +505,14 @@ m.handle = function(self, state, data)
|
||||
create_body.ExposedPorts = (next(exposedports) ~= nil) and exposedports or nil
|
||||
create_body.HostConfig = create_body.HostConfig or {}
|
||||
create_body.HostConfig.Dns = dns
|
||||
create_body.HostConfig.Binds = (#mount ~= 0) and mount or nil
|
||||
create_body.HostConfig.Binds = (#volume ~= 0) and volume or nil
|
||||
create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 }
|
||||
create_body.HostConfig.Privileged = privileged and true or false
|
||||
create_body.HostConfig.PortBindings = (next(portbindings) ~= nil) and portbindings or nil
|
||||
create_body.HostConfig.Memory = tonumber(memory)
|
||||
create_body.HostConfig.CpuShares = tonumber(cpushares)
|
||||
create_body.HostConfig.CpuShares = tonumber(cpu_shares)
|
||||
create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9
|
||||
create_body.HostConfig.BlkioWeight = tonumber(blkioweight)
|
||||
create_body.HostConfig.BlkioWeight = tonumber(blkio_weight)
|
||||
if create_body.HostConfig.NetworkMode ~= network then
|
||||
-- network mode changed, need to clear duplicate config
|
||||
create_body.NetworkingConfig = nil
|
||||
@ -529,23 +537,21 @@ m.handle = function(self, state, data)
|
||||
-- no ip + no duplicate config
|
||||
create_body.NetworkingConfig = nil
|
||||
end
|
||||
|
||||
create_body["HostConfig"]["Tmpfs"] = (next(tmpfs) ~= nil) and tmpfs or nil
|
||||
create_body["HostConfig"]["Devices"] = (next(device) ~= nil) and device or nil
|
||||
create_body["HostConfig"]["Sysctls"] = (next(sysctl) ~= nil) and sysctl or nil
|
||||
|
||||
if network == "bridge" and next(link) ~= nil then
|
||||
create_body["HostConfig"]["Links"] = link
|
||||
end
|
||||
local pull_image = function(image)
|
||||
local server = "index.docker.io"
|
||||
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
||||
docker:append_status("Images: " .. "pulling" .. " " .. image .. "...")
|
||||
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
|
||||
local res = dk.images:create({query = {fromImage=image}, header={["X-Registry-Auth"]=x_auth}})
|
||||
if res and res.code == 200 then
|
||||
docker:append_status("done<br>")
|
||||
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
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
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/services/docker/newcontainer"))
|
||||
end
|
||||
end
|
||||
@ -571,7 +577,7 @@ m.handle = function(self, state, data)
|
||||
docker:clear_status()
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/containers"))
|
||||
else
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newcontainer"))
|
||||
end
|
||||
end
|
||||
|
||||
@ -14,7 +14,8 @@ m.redirect = luci.dispatcher.build_url("admin", "services","docker", "networks")
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
docker_status.err=docker:read_status()
|
||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
s = m:section(SimpleSection, translate("New Network"))
|
||||
@ -190,13 +191,13 @@ m.handle = function(self, state, data)
|
||||
end
|
||||
end
|
||||
|
||||
docker:append_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
|
||||
docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
|
||||
local res = dk.networks:create({body = create_body})
|
||||
if res and res.code == 201 then
|
||||
docker:clear_status()
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/networks"))
|
||||
else
|
||||
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
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/services/docker/newnetwork"))
|
||||
end
|
||||
end
|
||||
|
||||
@ -13,12 +13,12 @@ function byte_format(byte)
|
||||
if byte > 1024 and i < 5 then
|
||||
byte = byte / 1024
|
||||
else
|
||||
return string.format("%.2f %s", byte, suff[i])
|
||||
end
|
||||
return string.format("%.2f %s", byte, suff[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local m = Map("dockerman", translate("Docker"))
|
||||
local map_dockerman = Map("dockerman", translate("Docker"))
|
||||
local docker_info_table = {}
|
||||
-- docker_info_table['0OperatingSystem'] = {_key=translate("Operating System"),_value='-'}
|
||||
-- docker_info_table['1Architecture'] = {_key=translate("Architecture"),_value='-'}
|
||||
@ -29,20 +29,20 @@ 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)
|
||||
local s = map_dockerman:section(Table, docker_info_table)
|
||||
s:option(DummyValue, "_key", translate("Info"))
|
||||
s:option(DummyValue, "_value")
|
||||
|
||||
s = m:section(SimpleSection)
|
||||
s = map_dockerman:section(SimpleSection)
|
||||
s.containers_running = '-'
|
||||
s.images_used = '-'
|
||||
s.containers_total = '-'
|
||||
s.images_total = '-'
|
||||
s.networks_total = '-'
|
||||
s.volumes_total = '-'
|
||||
local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
|
||||
if nixio.fs.access(socket) and (require "luci.model.docker").new():_ping().code == 200 then
|
||||
-- local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
|
||||
if (require "luci.model.docker").new():_ping().code == 200 then
|
||||
local dk = docker.new()
|
||||
local containers_list = dk.containers:list({query = {all=true}}).body
|
||||
local images_list = dk.images:list().body
|
||||
@ -59,6 +59,9 @@ if nixio.fs.access(socket) and (require "luci.model.docker").new():_ping().code
|
||||
docker_info_table['6MemTotal']._value = byte_format(docker_info.body.MemTotal)
|
||||
docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir
|
||||
docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress
|
||||
for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do
|
||||
docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v)
|
||||
end
|
||||
|
||||
s.images_used = 0
|
||||
for i, v in ipairs(images_list) do
|
||||
@ -78,17 +81,69 @@ if nixio.fs.access(socket) and (require "luci.model.docker").new():_ping().code
|
||||
end
|
||||
s.template = "dockerman/overview"
|
||||
|
||||
--tabs
|
||||
tab_section = map_dockerman:section(SimpleSection)
|
||||
tab_section.tabs = {
|
||||
dockerman = translate("DockerMan"),
|
||||
}
|
||||
tab_section.default_tab = "dockerman"
|
||||
tab_section.template="dockerman/overview_tab"
|
||||
|
||||
s = m:section(NamedSection, "local", "section", translate("Setting"))
|
||||
local section_dockerman = map_dockerman:section(NamedSection, "local", "section")
|
||||
section_dockerman.config = "dockerman"
|
||||
section_dockerman.template = "dockerman/cbi/namedsection"
|
||||
local socket_path = section_dockerman:option(Value, "socket_path", translate("Docker Socket Path"))
|
||||
socket_path.default = "/var/run/docker.sock"
|
||||
socket_path.placeholder = "/var/run/docker.sock"
|
||||
socket_path.rmempty = false
|
||||
|
||||
socket_path = s:option(Value, "socket_path", translate("Socket Path"))
|
||||
status_path = s:option(Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file"))
|
||||
wan_mode = s:option(Flag, "wan_mode", translate("Enable WAN access Dokcer"), translate("Enable WAN access docker mapped ports (need reload Docker-ce service)"))
|
||||
wan_mode.enabled="true"
|
||||
wan_mode.disabled="false"
|
||||
debug = s:option(Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path"))
|
||||
local remote_endpoint = section_dockerman:option(Flag, "remote_endpoint", translate("Remote Endpoint"), translate("Dockerman connect to remote endpoint"))
|
||||
remote_endpoint.rmempty = false
|
||||
remote_endpoint.enabled = "true"
|
||||
remote_endpoint.disabled = "false"
|
||||
|
||||
local remote_host = section_dockerman:option(Value, "remote_host", translate("Remote Host"))
|
||||
remote_host.placeholder = "10.1.1.2"
|
||||
-- remote_host:depends("remote_endpoint", "true")
|
||||
|
||||
local remote_port = section_dockerman:option(Value, "remote_port", translate("Remote Port"))
|
||||
remote_port.placeholder = "2375"
|
||||
remote_port.default = "2375"
|
||||
-- remote_port:depends("remote_endpoint", "true")
|
||||
|
||||
local status_path = section_dockerman:option(Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file"))
|
||||
local debug = section_dockerman:option(Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path"))
|
||||
debug.enabled="true"
|
||||
debug.disabled="false"
|
||||
debug_path = s:option(Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile"))
|
||||
local debug_path = section_dockerman:option(Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile"))
|
||||
|
||||
return m
|
||||
local map_dockerd
|
||||
if nixio.fs.access("/etc/config/dockerd") and nixio.fs.access("/usr/bin/dockerd") then
|
||||
-- map_dockerman:chain("dockerd")
|
||||
tab_section.tabs.docker_daemon = translate("Docker Daemon")
|
||||
tab_section.default_tab = "docker_daemon"
|
||||
map_dockerd = Map("dockerd","")
|
||||
local section_dockerd = map_dockerd:section(NamedSection, "local", "section")
|
||||
section_dockerd.config = "docker_daemon"
|
||||
section_dockerd.template = "dockerman/cbi/namedsection"
|
||||
local dockerd_enable = section_dockerd:option(Flag, "ea", translate("Enable"))
|
||||
dockerd_enable.enabled = "true"
|
||||
dockerd_enable.rmempty = true
|
||||
local data_root = section_dockerd:option(Value, "data_root", translate("Docker Root Dir"))
|
||||
data_root.placeholder = "/opt/docker/"
|
||||
local hosts = section_dockerd:option(DynamicList, "hosts", translate("Server Host"), translate('Daemon unix socket (unix:///var/run/docker.sock) or TCP Remote Hosts (tcp://0.0.0.0:2375), default: unix:///var/run/docker.sock'))
|
||||
hosts.placeholder = "unix:///var/run/docker.sock | tcp://0.0.0.0:2375"
|
||||
hosts.rmempty = true
|
||||
local registry_mirrors = section_dockerd:option(DynamicList, "registry_mirrors", translate("Registry Mirrors"))
|
||||
registry_mirrors.placeholder = "https://hub-mirror.c.163.com"
|
||||
local wan_enable = section_dockerd:option(Flag, "en_wan", translate("Enable WAN access"), translate("Enable WAN access container mapped ports"))
|
||||
wan_enable.enabled = "true"
|
||||
wan_enable.rmempty = true
|
||||
local log_level = section_dockerd:option(ListValue, "log_level", translate("Log Level"), translate('Set the logging level'))
|
||||
log_level:value("debug", "debug")
|
||||
log_level:value("info", "info")
|
||||
log_level:value("warn", "warn")
|
||||
log_level:value("error", "error")
|
||||
log_level:value("fatal", "fatal")
|
||||
end
|
||||
return map_dockerman, map_dockerd
|
||||
|
||||
@ -28,7 +28,7 @@ function get_volumes()
|
||||
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=/cgi-bin/luci/admin/services/docker/container/"..cv.Id.." >".. cv.Names[1]:sub(2).."</a>"
|
||||
'<a href='..luci.dispatcher.build_url("admin/services/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2)..'</a>'
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -73,7 +73,8 @@ end
|
||||
|
||||
docker_status = m:section(SimpleSection)
|
||||
docker_status.template = "dockerman/apply_widget"
|
||||
docker_status.err=nixio.fs.readfile(dk.options.status_path)
|
||||
docker_status.err=docker:read_status()
|
||||
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ")
|
||||
if docker_status.err then docker:clear_status() end
|
||||
|
||||
action = m:section(Table,{{}})
|
||||
@ -103,10 +104,10 @@ btnremove.write = function(self, section)
|
||||
docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...")
|
||||
local msg = dk.volumes["remove"](dk, {id = vol})
|
||||
if msg.code ~= 204 then
|
||||
docker:append_status("fail code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "<br>")
|
||||
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
|
||||
success = false
|
||||
else
|
||||
docker:append_status("done<br>")
|
||||
docker:append_status("done\n")
|
||||
end
|
||||
end
|
||||
if success then docker:clear_status() end
|
||||
|
||||
@ -11,16 +11,14 @@ local _docker = {}
|
||||
|
||||
--pull image and return iamge id
|
||||
local update_image = function(self, image_name)
|
||||
local server = "index.docker.io"
|
||||
|
||||
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
||||
_docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...")
|
||||
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
|
||||
local res = self.images:create({query = {fromImage=image_name}, header={["X-Registry-Auth"]=x_auth}})
|
||||
if res and res.code < 300 then
|
||||
_docker:append_status("done<br>")
|
||||
_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
|
||||
_docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
|
||||
res.code = 500
|
||||
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
|
||||
@ -81,9 +79,25 @@ local map_subtract = function(t1, t2)
|
||||
return next(res) ~= nil and res or nil
|
||||
end
|
||||
|
||||
local function clear_empty_tables( 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] = clear_empty_tables(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- return create_body, extra_network
|
||||
local get_config = function(old_config, old_host_config, old_network_setting, image_config)
|
||||
local config = old_config
|
||||
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
|
||||
@ -117,12 +131,27 @@ local get_config = function(old_config, old_host_config, old_network_setting, im
|
||||
local host_config = old_host_config
|
||||
if host_config.PortBindings and next(host_config.PortBindings) == nil then host_config.PortBindings = nil end
|
||||
host_config.LogConfig = nil
|
||||
host_config.Mounts = {}
|
||||
-- for volumes
|
||||
for i, v in ipairs(container_config.Mounts) do
|
||||
if v.Type == "volume" then
|
||||
table.insert(host_config.Mounts, {
|
||||
Type = v.Type,
|
||||
Target = v.Destination,
|
||||
Source = v.Source:match("([^/]+)\/_data"),
|
||||
BindOptions = v.Type == "bind" and {Propagation = v.Propagation} or nil,
|
||||
ReadOnly = not v.RW
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- merge configs
|
||||
local create_body = config
|
||||
create_body["HostConfig"] = host_config
|
||||
create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
|
||||
|
||||
create_body = clear_empty_tables(create_body) or {}
|
||||
extra_network = clear_empty_tables(extra_network) or {}
|
||||
return create_body, extra_network
|
||||
end
|
||||
|
||||
@ -137,12 +166,9 @@ local upgrade = function(self, request)
|
||||
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 old_config = container_info.body.Config
|
||||
local old_host_config = container_info.body.HostConfig
|
||||
local old_network_setting = container_info.body.NetworkSettings.Networks or {}
|
||||
|
||||
local image_id, res = update_image(self, image_name)
|
||||
if res and res.code > 300 then return res end
|
||||
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
|
||||
@ -150,7 +176,7 @@ local upgrade = function(self, request)
|
||||
_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<br>")
|
||||
_docker:append_status("done\n")
|
||||
else
|
||||
return res
|
||||
end
|
||||
@ -158,32 +184,31 @@ local upgrade = function(self, request)
|
||||
_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<br>")
|
||||
_docker:append_status("done\n")
|
||||
else
|
||||
return res
|
||||
end
|
||||
|
||||
-- handle config
|
||||
local image_config = self.images:inspect({id = old_image_id}).body.Config
|
||||
local create_body, extra_network = get_config(old_config, old_host_config, old_network_setting, image_config)
|
||||
local create_body, extra_network = get_config(container_info.body, image_config)
|
||||
|
||||
-- create new container
|
||||
_docker:append_status("Container: Create" .. " " .. container_name .. "...")
|
||||
res = self.containers:create({name = container_name, body = create_body})
|
||||
if res and res.code > 300 then return res end
|
||||
_docker:append_status("done<br>")
|
||||
_docker:append_status("done\n")
|
||||
|
||||
-- extra networks need to network connect action
|
||||
for k, v in pairs(extra_network) do
|
||||
if v.IPAMConfig and next(v.IPAMConfig) == nil then v.IPAMConfig =nil end
|
||||
if v.DriverOpts and next(v.DriverOpts) == nil then v.DriverOpts =nil end
|
||||
if v.Aliases and next(v.Aliases) == nil then v.Aliases =nil end
|
||||
|
||||
-- if v.IPAMConfig and next(v.IPAMConfig) == nil then v.IPAMConfig =nil end
|
||||
-- if v.DriverOpts and next(v.DriverOpts) == nil then v.DriverOpts =nil end
|
||||
-- if v.Aliases and next(v.Aliases) == nil then v.Aliases =nil end
|
||||
_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<br>")
|
||||
_docker:append_status("done\n")
|
||||
end
|
||||
_docker:clear_status()
|
||||
return res
|
||||
@ -193,20 +218,23 @@ 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 old_config = container_info.body.Config
|
||||
local old_host_config = container_info.body.HostConfig
|
||||
local old_network_setting = container_info.body.NetworkSettings.Networks or {}
|
||||
-- local old_config = container_info.body.Config
|
||||
-- local old_host_config = container_info.body.HostConfig
|
||||
-- local old_network_setting = container_info.body.NetworkSettings.Networks or {}
|
||||
local image_config = self.images:inspect({id = old_image_id}).body.Config
|
||||
return get_config(old_config, old_host_config, old_network_setting, image_config)
|
||||
return get_config(container_info.body, image_config)
|
||||
end
|
||||
|
||||
_docker.new = function(option)
|
||||
local option = option or {}
|
||||
local remote = uci:get("dockerman", "local", "remote_endpoint")
|
||||
options = {
|
||||
socket_path = option.socket_path or uci:get("dockerman", "local", "socket_path"),
|
||||
host = (remote == "true") and (option.host or uci:get("dockerman", "local", "remote_host")) or nil,
|
||||
port = (remote == "true") and (option.port or uci:get("dockerman", "local", "remote_port")) or nil,
|
||||
debug = option.debug or uci:get("dockerman", "local", "debug") == 'true' and true or false,
|
||||
debug_path = option.debug_path or uci:get("dockerman", "local", "debug_path")
|
||||
}
|
||||
options.socket_path = (remote ~= "true" or not options.host or not options.port) and (option.socket_path or uci:get("dockerman", "local", "socket_path") or "/var/run/docker.sock") or nil
|
||||
local _new = docker.new(options)
|
||||
_new.options.status_path = uci:get("dockerman", "local", "status_path")
|
||||
_new.containers_upgrade = upgrade
|
||||
@ -217,13 +245,92 @@ _docker.options={}
|
||||
_docker.options.status_path = uci:get("dockerman", "local", "status_path")
|
||||
|
||||
_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 nixio.fs.readfile(self.options.status_path)
|
||||
end
|
||||
|
||||
_docker.clear_status=function(self)
|
||||
nixio.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.print_status_cb = function(res, source)
|
||||
-- return status_cb(res, source, function(step)
|
||||
-- luci.util.perror(step)
|
||||
-- end
|
||||
-- )
|
||||
-- end
|
||||
|
||||
return _docker
|
||||
|
||||
@ -87,6 +87,7 @@ function uci_confirm_docker() {
|
||||
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;
|
||||
}
|
||||
@ -102,8 +103,8 @@ function uci_confirm_docker() {
|
||||
var now = Date.now();
|
||||
|
||||
docker_status_message('notice',
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> ' +
|
||||
loading_msg);
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> <span style="white-space:pre-line; font-family: \'Courier New\', Courier, monospace;">' +
|
||||
loading_msg + '</span>');
|
||||
|
||||
tt = window.setTimeout(tick, 200);
|
||||
ts = now;
|
||||
@ -123,7 +124,7 @@ function fnSubmitForm(el){
|
||||
}
|
||||
|
||||
<% if self.err then -%>
|
||||
docker_status_message('warning', `<%=self.err%>`);
|
||||
docker_status_message('warning', '<span style="white-space:pre-line; font-family: \'Courier New\', Courier, monospace;">'+`<%=self.err%>`+'</span>');
|
||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
||||
docker_status_message()
|
||||
})
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
<%+cbi/valueheader%>
|
||||
<% if self.href then %><a href="<%=self:href(section)%>"><% end -%>
|
||||
<%
|
||||
local val = self:cfgvalue(section) or self.default or ""
|
||||
if not self.rawhtml then
|
||||
write(pcdata(val))
|
||||
else
|
||||
write(val)
|
||||
end
|
||||
%>
|
||||
<%- if self.href then %></a><%end%>
|
||||
<input type="hidden" id="<%=cbid%>" value="<%=pcdata(self:cfgvalue(section) or self.default or "")%>" />
|
||||
<%+cbi/valuefooter%>
|
||||
@ -1,11 +1,11 @@
|
||||
<div style="display: inline-block;">
|
||||
<%- if self.title then -%>
|
||||
<!-- <%- 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 -%>
|
||||
<%- end -%> -->
|
||||
<%- if self.password then -%>
|
||||
<input type="password" style="position:absolute; left:-100000px" aria-hidden="true"<%=
|
||||
attr("name", "password." .. cbid)
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
<% 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 -->
|
||||
@ -4,6 +4,7 @@
|
||||
<li id="cbi-tab-container_edit"><a id="a-cbi-tab-container_edit" href=""><%:Edit%></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_logs"><a id="a-cbi-tab-container_logs" href=""><%:Logs%></a></li>
|
||||
</ul>
|
||||
|
||||
@ -13,7 +14,7 @@
|
||||
let path = p.split(re)
|
||||
let container_id = path[1].split('/')[0] || path[1]
|
||||
let action = path[1].split('/')[1] || "info"
|
||||
let actions=["info","edit","stats","file","logs"]
|
||||
let actions=["info","edit","stats","file","console","logs"]
|
||||
actions.forEach(function(item) {
|
||||
document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/services/docker/container/"+container_id+'/'+item
|
||||
if (action === item) {
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
<div class="cbi-map">
|
||||
<iframe id="terminal" style="width: 100%; min-height: 500px; border: none; border-radius: 3px;"></iframe>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.getElementById("terminal").src = "http://" + window.location.hostname + ":7682";
|
||||
</script>
|
||||
@ -7,7 +7,7 @@
|
||||
<br>
|
||||
<label class="cbi-value-title" for="path"><%:Path%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input type="text" class="cbi-input-text" name="path" placeholder="/home/myfiles" id="path" />
|
||||
<input type="text" class="cbi-input-text" name="path" value="/tmp/" id="path" />
|
||||
</div>
|
||||
<br>
|
||||
<div class="cbi-value-field">
|
||||
@ -21,7 +21,10 @@
|
||||
let uploadArchive = document.getElementById('upload_archive')
|
||||
let uploadPath = document.getElementById('path').value
|
||||
if (!uploadArchive.value || !uploadPath) {
|
||||
alert("<%:Please input the PATH and select the file !%>")
|
||||
docker_status_message('warning', "<%:Please input the PATH and select the file !%>")
|
||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
||||
docker_status_message()
|
||||
})
|
||||
return
|
||||
}
|
||||
let fileName = uploadArchive.files[0].name
|
||||
@ -34,11 +37,14 @@
|
||||
xhr.onload = function() {
|
||||
if (xhr.status == 200) {
|
||||
uploadArchive.value = ''
|
||||
alert("<%:Upload Success%> !")
|
||||
docker_status_message('notice', "<%:Upload Success%>")
|
||||
}
|
||||
else {
|
||||
alert("<%:Upload Error%>:" + xhr.statusText)
|
||||
docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText)
|
||||
}
|
||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
||||
docker_status_message()
|
||||
})
|
||||
}
|
||||
xhr.send(formData)
|
||||
}
|
||||
@ -46,7 +52,10 @@
|
||||
btnDownload.onclick = function (e) {
|
||||
let downloadPath = document.getElementById('path').value
|
||||
if (!downloadPath) {
|
||||
alert("<%:Please input the PATH !%>")
|
||||
docker_status_message('warning', "<%:Please input the PATH !%>")
|
||||
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
|
||||
docker_status_message()
|
||||
})
|
||||
return
|
||||
}
|
||||
window.open("/cgi-bin/luci/admin/services/docker/container_get_archive/?id=<%=self.container%>&path=" + encodeURIComponent(downloadPath))
|
||||
|
||||
@ -0,0 +1,90 @@
|
||||
<!-- this page has no effect -->
|
||||
|
||||
<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=" 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 type="text/javascript">
|
||||
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", "<%=url('admin/services/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", "<%=url('admin/services/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("<%=url('admin/services/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("<%=url('admin/services/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>
|
||||
@ -0,0 +1,29 @@
|
||||
<div style="display: inline-block;">
|
||||
<input type="button"" class="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 type="text/javascript">
|
||||
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", "/cgi-bin/luci/admin/services/docker/images_load", true)
|
||||
xhr.onload = function() {
|
||||
location.reload()
|
||||
}
|
||||
xhr.send(formData)
|
||||
}
|
||||
</script>
|
||||
@ -1,10 +1,11 @@
|
||||
<% if self.title == translate("Docker Events") then %>
|
||||
<% if self.title == "Events" then %>
|
||||
<%+header%>
|
||||
<h2 name="content"><%:Docker%></h2>
|
||||
<legend><%:Events%></legend>
|
||||
<% end %>
|
||||
<h2><a id="content" name="content"><%=self.title%></a></h2>
|
||||
<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 == translate("Docker Events") then %>
|
||||
<% if self.title == "Events" then %>
|
||||
<%+footer%>
|
||||
<% end %>
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
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
|
||||
@ -70,7 +71,7 @@
|
||||
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(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ")
|
||||
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 = "/cgi-bin/luci/admin/services/docker/newcontainer/" + encodeURI(cmd_line)
|
||||
}
|
||||
@ -75,7 +75,6 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
|
||||
overflow-y: hidden;
|
||||
border: 1px solid rgba(0, 0, 0, .05);
|
||||
border-radius: .375rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
<br>
|
||||
<h2><%:Setting%></h2>
|
||||
<ul class="cbi-tabmenu">
|
||||
<% for k, v in pairs(self.tabs) do
|
||||
local class = k == self.default_tab and "cbi-tab" or "cbi-tab-disabled"
|
||||
local id = "tab.overview-tab." .. k
|
||||
%>
|
||||
<li id=<%=id%> class=<%=class%> >
|
||||
<a onclick="this.blur(); return cbi_t_switch('overview-tab', '<%=k%>')" href=""><%=v%></a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<script type="text/javascript">
|
||||
window.onload = function () {
|
||||
<% for k, v in pairs(self.tabs) do
|
||||
local display = k == self.default_tab and "block" or "none"
|
||||
local tid = "cbi-"..k.."-local"
|
||||
local sid = "cbi-"..k.."-local"
|
||||
local cid = "container.overview-tab." .. k
|
||||
%>
|
||||
mount_point_table = document.getElementById("<%=tid%>") || document.getElementById("<%=sid%>") || null
|
||||
if (mount_point_table) {
|
||||
mount_point_table.setAttribute("style", "display: <%=display%>;")
|
||||
mount_point_table.setAttribute("id", "<%=cid%>")
|
||||
cbi_t_add('overview-tab', '<%=k%>')
|
||||
// cbi_init();
|
||||
}
|
||||
<% end %>
|
||||
|
||||
}
|
||||
</script>
|
||||
@ -28,8 +28,8 @@ msgstr "停止"
|
||||
msgid "Upgrade"
|
||||
msgstr "升级容器"
|
||||
|
||||
msgid "Duplicate"
|
||||
msgstr "复制容器"
|
||||
msgid "Duplicate/Edit"
|
||||
msgstr "复制/编辑容器"
|
||||
|
||||
msgid "Remove"
|
||||
msgstr "移除"
|
||||
@ -139,7 +139,7 @@ msgstr "网关"
|
||||
msgid "New"
|
||||
msgstr "新建"
|
||||
|
||||
msgid "Resolv CLI"
|
||||
msgid "Resolve CLI"
|
||||
msgstr "解析命令行"
|
||||
|
||||
msgid "Docker Image"
|
||||
@ -241,21 +241,18 @@ msgstr "Docker根目录"
|
||||
msgid "Index Server Address"
|
||||
msgstr "默认服务器地址"
|
||||
|
||||
msgid "Socket Path"
|
||||
msgstr "Socket路径"
|
||||
msgid "Registry Mirrors"
|
||||
msgstr "仓库镜像"
|
||||
|
||||
msgid "Docker Socket Path"
|
||||
msgstr "Docker Socket 路径"
|
||||
|
||||
msgid "Action Status Tempfile Path"
|
||||
msgstr "Docker 动作状态的临时文件路径"
|
||||
msgstr "Docker 操作状态临时文件路径"
|
||||
|
||||
msgid "Where you want to save the docker status file"
|
||||
msgstr "保存docker status文件的位置"
|
||||
|
||||
msgid "Enable WAN access Dokcer"
|
||||
msgstr "允许 WAN 访问 Dokcer"
|
||||
|
||||
msgid "Enable WAN access docker mapped ports (need reload Docker-ce service)"
|
||||
msgstr "允许 WAN 访问 Dokcer 映射后的端口(易受攻击!)。<br /><br />如已更改此选项,需要点击应用并保存后重启docker服务。<br />推荐禁用该选项后,用系统防火墙选择性映射 172.17.0.X:XX 端口到 WAN"
|
||||
|
||||
msgid "Enable Debug"
|
||||
msgstr "启用调试"
|
||||
|
||||
@ -268,8 +265,23 @@ msgstr "调试临时文件路径"
|
||||
msgid "Where you want to save the debug tempfile"
|
||||
msgstr "保存调试临时文件的位置"
|
||||
|
||||
msgid "Edit"
|
||||
msgstr "编辑"
|
||||
msgid "Log Level"
|
||||
msgstr "日志等级"
|
||||
|
||||
msgid "Set the logging level"
|
||||
msgstr "设置日志等级"
|
||||
|
||||
msgid "Enable WAN access"
|
||||
msgstr "允许 WAN 访问"
|
||||
|
||||
msgid "Enable WAN access container mapped ports"
|
||||
msgstr "允许 WAN 访问容器映射的端口"
|
||||
|
||||
msgid "Server Host"
|
||||
msgstr "服务端"
|
||||
|
||||
msgid "Daemon unix socket (unix:///var/run/docker.sock) or TCP Remote Hosts (tcp://0.0.0.0:2375), default: unix:///var/run/docker.sock"
|
||||
msgstr "后台 Unix socket (unix:///var/run/docker.sock) 或 TCP 远程服务端 (tcp://0.0.0.0:2375),默认: unix:///var/run/docker.sock"
|
||||
|
||||
msgid "Stats"
|
||||
msgstr "状态"
|
||||
@ -349,6 +361,12 @@ msgstr "上传"
|
||||
msgid "Download"
|
||||
msgstr "下载"
|
||||
|
||||
msgid "Import"
|
||||
msgstr "导入"
|
||||
|
||||
msgid "Export"
|
||||
msgstr "导出"
|
||||
|
||||
msgid "Path"
|
||||
msgstr "路径"
|
||||
|
||||
@ -357,3 +375,39 @@ msgstr "上传错误"
|
||||
|
||||
msgid "Upload Success"
|
||||
msgstr "上传成功"
|
||||
|
||||
msgid "Remote Endpoint"
|
||||
msgstr "远程节点"
|
||||
|
||||
msgid "Remote Host"
|
||||
msgstr "远程节点域名"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "远程节点端口"
|
||||
|
||||
msgid "Import Images"
|
||||
msgstr "导入镜像"
|
||||
|
||||
msgid "Load"
|
||||
msgstr "载入"
|
||||
|
||||
msgid "Save"
|
||||
msgstr "保存"
|
||||
|
||||
msgid "Remove tag"
|
||||
msgstr "移除镜像标签"
|
||||
|
||||
msgid "New tag"
|
||||
msgstr "新镜像标签"
|
||||
|
||||
msgid "Container detail"
|
||||
msgstr "容器详情"
|
||||
|
||||
msgid "Please input new tag"
|
||||
msgstr "请输入新镜像标签名称"
|
||||
|
||||
msgid "Error"
|
||||
msgstr "错误"
|
||||
|
||||
msgid "Console"
|
||||
msgstr "控制台"
|
||||
@ -0,0 +1,5 @@
|
||||
config section 'local'
|
||||
option ea 'true'
|
||||
option en_wan 'false'
|
||||
option data_root '/opt/docker'
|
||||
option log_level 'warn'
|
||||
@ -1,6 +1,5 @@
|
||||
config section 'local'
|
||||
option socket_path '/var/run/docker.sock'
|
||||
option status_path '/tmp/.docker_action_status'
|
||||
option wan_mode 'false'
|
||||
option debug_path '/tmp/.docker_debug'
|
||||
option debug 'false'
|
||||
|
||||
66
package/ctcgfw/luci-app-dockerman/root/etc/init.d/dockerd
Executable file
66
package/ctcgfw/luci-app-dockerman/root/etc/init.d/dockerd
Executable file
@ -0,0 +1,66 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
USE_PROCD=1
|
||||
START=99
|
||||
NAME=dockerd_local
|
||||
DOCKERD_CONF="/etc/docker/daemon.json"
|
||||
|
||||
append_list_item() {
|
||||
append "$2" "$1" "\",\""
|
||||
}
|
||||
|
||||
get_config() {
|
||||
config_load $1
|
||||
config_get ea "local" ea
|
||||
config_get en_wan "local" en_wan
|
||||
config_get data_root "local" data_root '/opt/docker'
|
||||
config_get log_level "local" log_level 'warn'
|
||||
config_list_foreach "local" registry_mirrors append_list_item registry_mirrors
|
||||
config_list_foreach "local" hosts append_list_item hosts
|
||||
}
|
||||
|
||||
gen_config(){
|
||||
cat <<-EOF >$DOCKERD_CONF
|
||||
{
|
||||
"data-root": "$data_root",
|
||||
"log-level": "$log_level",
|
||||
"registry-mirrors": ["$registry_mirrors"],
|
||||
"hosts": ["$hosts"]
|
||||
}
|
||||
EOF
|
||||
sed -i 's/\[\"\"\]/\[\]/g' $DOCKERD_CONF
|
||||
}
|
||||
|
||||
_start() {
|
||||
local nofile=$(cat /proc/sys/fs/nr_open)
|
||||
get_config dockerd
|
||||
gen_config
|
||||
if [ -n "$en_wan" ]; then
|
||||
iptables -D DOCKER-USER ! -i br-lan -m conntrack --ctstate NEW,INVALID -o docker0 -j DROP >/dev/null 2>&1
|
||||
iptables -D DOCKER-USER ! -i br-lan -m conntrack --ctstate ESTABLISHED,RELATED -o docker0 -j RETURN >/dev/null 2>&1
|
||||
else
|
||||
iptables -D DOCKER-USER ! -i br-lan -m conntrack --ctstate NEW,INVALID -o docker0 -j DROP >/dev/null 2>&1
|
||||
iptables -D DOCKER-USER ! -i br-lan -m conntrack --ctstate ESTABLISHED,RELATED -o docker0 -j RETURN >/dev/null 2>&1
|
||||
iptables -I DOCKER-USER ! -i br-lan -m conntrack --ctstate ESTABLISHED,RELATED -o docker0 -j RETURN >/dev/null 2>&1
|
||||
iptables -I DOCKER-USER ! -i br-lan -m conntrack --ctstate NEW,INVALID -o docker0 -j DROP >/dev/null 2>&1
|
||||
fi
|
||||
if [ -n "$ea" ]; then
|
||||
procd_open_instance $NAME
|
||||
procd_set_param stderr 1
|
||||
procd_set_param command /usr/bin/dockerd
|
||||
procd_set_param limits nofile="${nofile} ${nofile}"
|
||||
procd_close_instance
|
||||
fi
|
||||
}
|
||||
|
||||
start_service() {
|
||||
_start
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger dockerd
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
restart
|
||||
}
|
||||
14
package/ctcgfw/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman
Executable file
14
package/ctcgfw/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
set uhttpd.main.script_timeout="600"
|
||||
commit uhttpd
|
||||
delete ucitrack.@dockerd[-1]
|
||||
add ucitrack dockerd
|
||||
set ucitrack.@dockerd[-1].init=dockerd
|
||||
commit ucitrack
|
||||
EOF
|
||||
/etc/init.d/dockerd enable >/dev/null 2>&1
|
||||
/etc/init.d/uhttpd restart >/dev/null 2>&1
|
||||
rm -fr /tmp/luci-indexcache /tmp/luci-modulecache >/dev/null 2>&1
|
||||
exit 0
|
||||
17
package/lean/luci-app-docker/Makefile
Normal file
17
package/lean/luci-app-docker/Makefile
Normal file
@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=Luci for Docker-CE
|
||||
LUCI_DEPENDS:=+docker-ce +e2fsprogs +fdisk
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_VERSION:=1
|
||||
PKG_RELEASE:=9
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
17
package/lean/luci-app-docker/luasrc/controller/docker.lua
Normal file
17
package/lean/luci-app-docker/luasrc/controller/docker.lua
Normal file
@ -0,0 +1,17 @@
|
||||
module("luci.controller.docker", package.seeall)
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/dockerd") then
|
||||
return
|
||||
end
|
||||
|
||||
entry({"admin", "services", "docker"}, cbi("docker"), _("Docker CE Container"), 199).dependent = true
|
||||
entry({"admin","services","docker","status"},call("act_status")).leaf=true
|
||||
end
|
||||
|
||||
function act_status()
|
||||
local e={}
|
||||
e.running=luci.sys.call("pgrep /usr/bin/dockerd >/dev/null")==0
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
23
package/lean/luci-app-docker/luasrc/model/cbi/docker.lua
Normal file
23
package/lean/luci-app-docker/luasrc/model/cbi/docker.lua
Normal file
@ -0,0 +1,23 @@
|
||||
local running = (luci.sys.call("pidof portainer >/dev/null") == 0)
|
||||
local button = ""
|
||||
|
||||
if running then
|
||||
button = " <br /><br /><input type=\"button\" value=\" " .. translate("Open Portainer Docker Admin") .. " \" onclick=\"window.open('http://'+window.location.hostname+':" .. 9999 .. "')\"/><br />"
|
||||
end
|
||||
|
||||
m = Map("dockerd", "Docker CE", translate("Docker is a set of platform-as-a-service (PaaS) products that use OS-level virtualization to deliver software in packages called containers.") .. button)
|
||||
|
||||
|
||||
m:section(SimpleSection).template = "docker/docker_status"
|
||||
|
||||
s = m:section(TypedSection, "docker")
|
||||
s.anonymous = true
|
||||
|
||||
wan_mode = s:option(Flag, "wan_mode", translate("Enable WAN access Dokcer"), translate("Enable WAN access docker mapped ports"))
|
||||
wan_mode.default = 0
|
||||
wan_mode.rmempty = false
|
||||
|
||||
o=s:option(DummyValue,"readme",translate(" "))
|
||||
o.description=translate("<a href=\"../../../../DockerReadme.pdf\" target=\"_blank\" />"..translate("Download DockerReadme.pdf").."</a>")
|
||||
|
||||
return m
|
||||
@ -0,0 +1,22 @@
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(3, '<%=url([[admin]], [[services]], [[docker]], [[status]])%>', null,
|
||||
function(x, data) {
|
||||
var tb = document.getElementById('docker_status');
|
||||
if (data && tb) {
|
||||
if (data.running) {
|
||||
var links = '<em><b><font color=green>Docker CE <%:RUNNING%></font></b></em>';
|
||||
tb.innerHTML = links;
|
||||
} else {
|
||||
tb.innerHTML = '<em><b><font color=red>Docker CE <%:NOT RUNNING%></font></b></em>';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
|
||||
<fieldset class="cbi-section">
|
||||
<p id="docker_status">
|
||||
<em><%:Collecting data...%></em>
|
||||
</p>
|
||||
</fieldset>
|
||||
39
package/lean/luci-app-docker/po/zh_Hans/docker.po
Normal file
39
package/lean/luci-app-docker/po/zh_Hans/docker.po
Normal file
@ -0,0 +1,39 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Luci ARP Bind\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-06-23 20:16+0800\n"
|
||||
"PO-Revision-Date: 2015-06-23 20:17+0800\n"
|
||||
"Last-Translator: coolsnowwolf <coolsnowwolf@gmail.com>\n"
|
||||
"Language-Team: PandoraBox Team\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.8.1\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
msgid "Docker CE Container"
|
||||
msgstr "Docker CE 容器"
|
||||
|
||||
msgid "Open Portainer Docker Admin"
|
||||
msgstr "打开 Portainer Docker 管理页面"
|
||||
|
||||
msgid "Docker is a set of platform-as-a-service (PaaS) products that use OS-level virtualization to deliver software in packages called containers."
|
||||
msgstr "Docker是一组平台即服务(platform-as-a-service,PaaS)产品,它使用操作系统级容器虚拟化来交付软件包。"
|
||||
|
||||
msgid "Enable WAN access Dokcer"
|
||||
msgstr "允许 WAN 访问 Dokcer"
|
||||
|
||||
msgid "Enable WAN access docker mapped ports"
|
||||
msgstr "允许 WAN 访问 Dokcer 映射后的端口(易受攻击!)。<br /><br />推荐禁用该选项后,用系统防火墙选择性映射 172.17.0.X:XX 端口到 WAN"
|
||||
|
||||
msgid "Docker Readme First"
|
||||
msgstr "Docker 初始化无脑配置教程"
|
||||
|
||||
msgid "Download DockerReadme.pdf"
|
||||
msgstr "下载 Docker 初始化无脑配置教程"
|
||||
|
||||
msgid "Please download DockerReadme.pdf to read when first-running"
|
||||
msgstr "初次在OpenWrt中运行Docker必读(只需执行一次流程)"
|
||||
4
package/lean/luci-app-docker/root/etc/config/dockerd
Normal file
4
package/lean/luci-app-docker/root/etc/config/dockerd
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
config docker
|
||||
option wan_mode '0'
|
||||
|
||||
19
package/lean/luci-app-docker/root/etc/docker-init
Normal file
19
package/lean/luci-app-docker/root/etc/docker-init
Normal file
@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
dtype=`fdisk -l /dev/sda | grep 'Disklabel type' | awk '{print $3}'`
|
||||
partid="0"
|
||||
|
||||
if [ "$dtype" = "gpt" ]
|
||||
then
|
||||
partid=`echo "n
|
||||
w
|
||||
" | fdisk /dev/sda | grep 'Created a new partition' | awk '{print $5}'`
|
||||
|
||||
elif [ "$dtype" = "dos" ]
|
||||
then
|
||||
partid=`echo "n
|
||||
p
|
||||
w
|
||||
" | fdisk /dev/sda | grep 'Created a new partition' | awk '{print $5}'`
|
||||
fi
|
||||
|
||||
echo "y" | mkfs.ext4 /dev/sda$partid
|
||||
3
package/lean/luci-app-docker/root/etc/docker-web
Normal file
3
package/lean/luci-app-docker/root/etc/docker-web
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
docker run -d --restart=always --name="portainer" -p 9999:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer
|
||||
@ -5,9 +5,9 @@ START=25
|
||||
|
||||
start_service() {
|
||||
local nofile=$(cat /proc/sys/fs/nr_open)
|
||||
local wanmode=$(uci get dockerman.local.wan_mode)
|
||||
|
||||
if [ $wanmode = "true" ] ;then
|
||||
local wanmode=$(uci get dockerd.@docker[0].wan_mode)
|
||||
|
||||
if [ $wanmode = "1" ] ;then
|
||||
dockerwan=" "
|
||||
else
|
||||
dockerwan="--iptables=false"
|
||||
@ -18,5 +18,5 @@ start_service() {
|
||||
procd_set_param command /usr/bin/dockerd $dockerwan
|
||||
procd_set_param limits nofile="${nofile} ${nofile}"
|
||||
procd_close_instance
|
||||
|
||||
|
||||
}
|
||||
11
package/lean/luci-app-docker/root/etc/uci-defaults/docker
Normal file
11
package/lean/luci-app-docker/root/etc/uci-defaults/docker
Normal file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@dockerd[-1]
|
||||
add ucitrack dockerd
|
||||
set ucitrack.@dockerd[-1].init=dockerd
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
||||
BIN
package/lean/luci-app-docker/root/www/DockerReadme.pdf
Normal file
BIN
package/lean/luci-app-docker/root/www/DockerReadme.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user