luci-app-dockerman: initial checkin
[project/luci.git] / applications / luci-app-dockerman / luasrc / model / cbi / dockerman / images.lua
1 --[[
2 LuCI - Lua Configuration Interface
3 Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
4 ]]--
5
6 require "luci.util"
7 local uci = luci.model.uci.cursor()
8 local docker = require "luci.model.docker"
9 local dk = docker.new()
10
11 local containers, images
12 local res = dk.images:list()
13 if res.code <300 then images = res.body else return end
14 res = dk.containers:list({query = {all=true}})
15 if res.code <300 then containers = res.body else return end
16
17 function get_images()
18 local data = {}
19 for i, v in ipairs(images) do
20 local index = v.Created .. v.Id
21 data[index]={}
22 data[index]["_selected"] = 0
23 data[index]["id"] = v.Id:sub(8)
24 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>'
25 if v.RepoTags and next(v.RepoTags)~=nil then
26 for i, v1 in ipairs(v.RepoTags) do
27 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>'))
28 if not data[index]["tag"] then
29 data[index]["tag"] = v1--:match("<none>") and nil or v1
30 end
31 end
32 else
33 data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+")
34 data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>"
35 end
36 data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","&lt;none&gt;")
37 -- data[index]["_tags"] = '<a href="javascript:handle_tag(\''..data[index]["_id"]..'\')">' .. data[index]["_tags"] .. '</a>'
38 for ci,cv in ipairs(containers) do
39 if v.Id == cv.ImageID then
40 data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
41 '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2).."</a>"
42 end
43 end
44 data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
45 data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created)
46 end
47 return data
48 end
49
50 local image_list = get_images()
51
52 -- m = Map("docker", translate("Docker"))
53 m = SimpleForm("docker", translate("Docker"))
54 m.submit=false
55 m.reset=false
56
57 local pull_value={_image_tag_name="", _registry="index.docker.io"}
58 local pull_section = m:section(SimpleSection, translate("Pull Image"))
59 pull_section.template="cbi/nullsection"
60 local tag_name = pull_section:option(Value, "_image_tag_name")
61 tag_name.template = "dockerman/cbi/inlinevalue"
62 tag_name.placeholder="lisaac/luci:latest"
63 local action_pull = pull_section:option(Button, "_pull")
64 action_pull.inputtitle= translate("Pull")
65 action_pull.template = "dockerman/cbi/inlinebutton"
66 action_pull.inputstyle = "add"
67 tag_name.write = function(self, section, value)
68 local hastag = value:find(":")
69 if not hastag then
70 value = value .. ":latest"
71 end
72 pull_value["_image_tag_name"] = value
73 end
74 action_pull.write = function(self, section)
75 local tag = pull_value["_image_tag_name"]
76 local json_stringify = luci.jsonc and luci.jsonc.stringify
77 if tag and tag ~= "" then
78 docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n")
79 -- local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server})) , header={["X-Registry-Auth"] = x_auth}
80 local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb)
81 -- {"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" }
82 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
83 docker:clear_status()
84 else
85 docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
86 end
87 else
88 docker:append_status("code: 400 please input the name of image name!")
89 end
90 luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
91 end
92
93 local import_section = m:section(SimpleSection, translate("Import Images"))
94 local im = import_section:option(DummyValue, "_image_import")
95 im.template = "dockerman/images_import"
96
97 local image_table = m:section(Table, image_list, translate("Images"))
98
99 local image_selecter = image_table:option(Flag, "_selected","")
100 image_selecter.disabled = 0
101 image_selecter.enabled = 1
102 image_selecter.default = 0
103
104 local image_id = image_table:option(DummyValue, "_id", translate("ID"))
105 image_id.rawhtml = true
106 image_table:option(DummyValue, "_tags", translate("RepoTags")).rawhtml = true
107 image_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
108 image_table:option(DummyValue, "_size", translate("Size"))
109 image_table:option(DummyValue, "_created", translate("Created"))
110 image_selecter.write = function(self, section, value)
111 image_list[section]._selected = value
112 end
113
114 local remove_action = function(force)
115 local image_selected = {}
116 -- 遍历table中sectionid
117 local image_table_sids = image_table:cfgsections()
118 for _, image_table_sid in ipairs(image_table_sids) do
119 -- 得到选中项的名字
120 if image_list[image_table_sid]._selected == 1 then
121 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
122 end
123 end
124 if next(image_selected) ~= nil then
125 local success = true
126 docker:clear_status()
127 for _,img in ipairs(image_selected) do
128 docker:append_status("Images: " .. "remove" .. " " .. img .. "...")
129 local query
130 if force then query = {force = true} end
131 local msg = dk.images:remove({id = img, query = query})
132 if msg.code ~= 200 then
133 docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
134 success = false
135 else
136 docker:append_status("done\n")
137 end
138 end
139 if success then docker:clear_status() end
140 luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
141 end
142 end
143
144 local docker_status = m:section(SimpleSection)
145 docker_status.template = "dockerman/apply_widget"
146 docker_status.err = docker:read_status()
147 docker_status.err = docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
148 if docker_status.err then docker:clear_status() end
149
150 local action = m:section(Table,{{}})
151 action.notitle=true
152 action.rowcolors=false
153 action.template="cbi/nullsection"
154
155 local btnremove = action:option(Button, "remove")
156 btnremove.inputtitle= translate("Remove")
157 btnremove.template = "dockerman/cbi/inlinebutton"
158 btnremove.inputstyle = "remove"
159 btnremove.forcewrite = true
160 btnremove.write = function(self, section)
161 remove_action()
162 end
163
164 local btnforceremove = action:option(Button, "forceremove")
165 btnforceremove.inputtitle= translate("Force Remove")
166 btnforceremove.template = "dockerman/cbi/inlinebutton"
167 btnforceremove.inputstyle = "remove"
168 btnforceremove.forcewrite = true
169 btnforceremove.write = function(self, section)
170 remove_action(true)
171 end
172
173 local btnsave = action:option(Button, "save")
174 btnsave.inputtitle= translate("Save")
175 btnsave.template = "dockerman/cbi/inlinebutton"
176 btnsave.inputstyle = "edit"
177 btnsave.forcewrite = true
178 btnsave.write = function (self, section)
179 local image_selected = {}
180 local image_table_sids = image_table:cfgsections()
181 for _, image_table_sid in ipairs(image_table_sids) do
182 if image_list[image_table_sid]._selected == 1 then
183 image_selected[#image_selected+1] = image_list[image_table_sid].id --image_id:cfgvalue(image_table_sid)
184 end
185 end
186 if next(image_selected) ~= nil then
187 local names
188 for _,img in ipairs(image_selected) do
189 names = names and (names .. "&names=".. img) or img
190 end
191 local first
192 local cb = function(res, chunk)
193 if res.code == 200 then
194 if not first then
195 first = true
196 luci.http.header('Content-Disposition', 'inline; filename="images.tar"')
197 luci.http.header('Content-Type', 'application\/x-tar')
198 end
199 luci.ltn12.pump.all(chunk, luci.http.write)
200 else
201 if not first then
202 first = true
203 luci.http.prepare_content("text/plain")
204 end
205 luci.ltn12.pump.all(chunk, luci.http.write)
206 end
207 end
208 docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...")
209 local msg = dk.images:get({query = {names = names}}, cb)
210 if msg.code ~= 200 then
211 docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
212 success = false
213 else
214 docker:clear_status()
215 end
216 end
217 end
218
219 local btnload = action:option(Button, "load")
220 btnload.inputtitle= translate("Load")
221 btnload.template = "dockerman/images_load"
222 btnload.inputstyle = "add"
223 return m