luci-app-docker: refresh source code

This commit is contained in:
CN_SZTL 2020-03-17 21:09:04 +08:00
parent 1a65bbb6f1
commit 415e56d9a2
No known key found for this signature in database
GPG Key ID: 6850B6345C862176
41 changed files with 1302 additions and 373 deletions

View File

@ -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

View File

@ -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(" ","&nbsp;")
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

View File

@ -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(" ","&nbsp;")
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

View File

@ -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(" ","&nbsp;")
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

View File

@ -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>","&lt;none&gt;")
-- 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("&lt;none&gt;")) 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(" ","&nbsp;")
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

View File

@ -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(" ","&nbsp;")
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

View File

@ -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(" ","&nbsp;")
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

View File

@ -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(" ","&nbsp;")
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

View File

@ -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

View File

@ -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(" ","&nbsp;")
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

View File

@ -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

View File

@ -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()
})

View File

@ -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%>

View File

@ -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)

View File

@ -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 -->

View File

@ -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) {

View File

@ -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>

View File

@ -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))

View File

@ -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>

View File

@ -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>

View File

@ -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 %>

View File

@ -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)
}

View File

@ -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);
}

View File

@ -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>

View File

@ -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 "控制台"

View File

@ -0,0 +1,5 @@
config section 'local'
option ea 'true'
option en_wan 'false'
option data_root '/opt/docker'
option log_level 'warn'

View File

@ -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'

View 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
}

View 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

View 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

View 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

View File

@ -0,0 +1,23 @@
local running = (luci.sys.call("pidof portainer >/dev/null") == 0)
local button = ""
if running then
button = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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

View File

@ -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>

View 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-servicePaaS产品它使用操作系统级容器虚拟化来交付软件包。"
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必读只需执行一次流程"

View File

@ -0,0 +1,4 @@
config docker
option wan_mode '0'

View 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

View 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

View File

@ -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
}

View 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

Binary file not shown.