images.lua 9.1 KB

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