container.lua 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. --[[
  2. LuCI - Lua Configuration Interface
  3. Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
  4. ]]--
  5. require "luci.util"
  6. local docker = require "luci.model.docker"
  7. local dk = docker.new()
  8. container_id = arg[1]
  9. local action = arg[2] or "info"
  10. local images, networks, container_info
  11. if not container_id then return end
  12. local res = dk.containers:inspect({id = container_id})
  13. if res.code < 300 then container_info = res.body else return end
  14. res = dk.networks:list()
  15. if res.code < 300 then networks = res.body else return end
  16. local get_ports = function(d)
  17. local data
  18. if d.HostConfig and d.HostConfig.PortBindings then
  19. for inter, out in pairs(d.HostConfig.PortBindings) do
  20. data = (data and (data .. "<br>") or "") .. out[1]["HostPort"] .. ":" .. inter
  21. end
  22. end
  23. return data
  24. end
  25. local get_env = function(d)
  26. local data
  27. if d.Config and d.Config.Env then
  28. for _,v in ipairs(d.Config.Env) do
  29. data = (data and (data .. "<br>") or "") .. v
  30. end
  31. end
  32. return data
  33. end
  34. local get_command = function(d)
  35. local data
  36. if d.Config and d.Config.Cmd then
  37. for _,v in ipairs(d.Config.Cmd) do
  38. data = (data and (data .. " ") or "") .. v
  39. end
  40. end
  41. return data
  42. end
  43. local get_mounts = function(d)
  44. local data
  45. if d.Mounts then
  46. for _,v in ipairs(d.Mounts) do
  47. local v_sorce_d, v_dest_d
  48. local v_sorce = ""
  49. local v_dest = ""
  50. for v_sorce_d in v["Source"]:gmatch('[^/]+') do
  51. if v_sorce_d and #v_sorce_d > 12 then
  52. v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..."
  53. else
  54. v_sorce = v_sorce .."/".. v_sorce_d
  55. end
  56. end
  57. for v_dest_d in v["Destination"]:gmatch('[^/]+') do
  58. if v_dest_d and #v_dest_d > 12 then
  59. v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..."
  60. else
  61. v_dest = v_dest .."/".. v_dest_d
  62. end
  63. end
  64. data = (data and (data .. "<br>") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "")
  65. end
  66. end
  67. return data
  68. end
  69. local get_device = function(d)
  70. local data
  71. if d.HostConfig and d.HostConfig.Devices then
  72. for _,v in ipairs(d.HostConfig.Devices) do
  73. data = (data and (data .. "<br>") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "")
  74. end
  75. end
  76. return data
  77. end
  78. local get_links = function(d)
  79. local data
  80. if d.HostConfig and d.HostConfig.Links then
  81. for _,v in ipairs(d.HostConfig.Links) do
  82. data = (data and (data .. "<br>") or "") .. v
  83. end
  84. end
  85. return data
  86. end
  87. local get_tmpfs = function(d)
  88. local data
  89. if d.HostConfig and d.HostConfig.Tmpfs then
  90. for k, v in pairs(d.HostConfig.Tmpfs) do
  91. data = (data and (data .. "<br>") or "") .. k .. (v~="" and ":" or "")..v
  92. end
  93. end
  94. return data
  95. end
  96. local get_dns = function(d)
  97. local data
  98. if d.HostConfig and d.HostConfig.Dns then
  99. for _, v in ipairs(d.HostConfig.Dns) do
  100. data = (data and (data .. "<br>") or "") .. v
  101. end
  102. end
  103. return data
  104. end
  105. local get_sysctl = function(d)
  106. local data
  107. if d.HostConfig and d.HostConfig.Sysctls then
  108. for k, v in pairs(d.HostConfig.Sysctls) do
  109. data = (data and (data .. "<br>") or "") .. k..":"..v
  110. end
  111. end
  112. return data
  113. end
  114. local get_networks = function(d)
  115. local data={}
  116. if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then
  117. for k,v in pairs(d.NetworkSettings.Networks) do
  118. data[k] = v.IPAddress or ""
  119. end
  120. end
  121. return data
  122. end
  123. local start_stop_remove = function(m, cmd)
  124. docker:clear_status()
  125. docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...")
  126. local res
  127. if cmd ~= "upgrade" then
  128. res = dk.containers[cmd](dk, {id = container_id})
  129. else
  130. res = dk.containers_upgrade(dk, {id = container_id})
  131. end
  132. if res and res.code >= 300 then
  133. docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
  134. luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
  135. else
  136. docker:clear_status()
  137. if cmd ~= "remove" and cmd ~= "upgrade" then
  138. luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
  139. else
  140. luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
  141. end
  142. end
  143. end
  144. m=SimpleForm("docker", container_info.Name:sub(2), translate("Docker Container") )
  145. m.redirect = luci.dispatcher.build_url("admin/docker/containers")
  146. -- m:append(Template("dockerman/container"))
  147. docker_status = m:section(SimpleSection)
  148. docker_status.template = "dockerman/apply_widget"
  149. docker_status.err=docker:read_status()
  150. docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
  151. if docker_status.err then docker:clear_status() end
  152. action_section = m:section(Table,{{}})
  153. action_section.notitle=true
  154. action_section.rowcolors=false
  155. action_section.template = "cbi/nullsection"
  156. btnstart=action_section:option(Button, "_start")
  157. btnstart.template = "dockerman/cbi/inlinebutton"
  158. btnstart.inputtitle=translate("Start")
  159. btnstart.inputstyle = "apply"
  160. btnstart.forcewrite = true
  161. btnrestart=action_section:option(Button, "_restart")
  162. btnrestart.template = "dockerman/cbi/inlinebutton"
  163. btnrestart.inputtitle=translate("Restart")
  164. btnrestart.inputstyle = "reload"
  165. btnrestart.forcewrite = true
  166. btnstop=action_section:option(Button, "_stop")
  167. btnstop.template = "dockerman/cbi/inlinebutton"
  168. btnstop.inputtitle=translate("Stop")
  169. btnstop.inputstyle = "reset"
  170. btnstop.forcewrite = true
  171. btnkill=action_section:option(Button, "_kill")
  172. btnkill.template = "dockerman/cbi/inlinebutton"
  173. btnkill.inputtitle=translate("Kill")
  174. btnkill.inputstyle = "reset"
  175. btnkill.forcewrite = true
  176. btnupgrade=action_section:option(Button, "_upgrade")
  177. btnupgrade.template = "dockerman/cbi/inlinebutton"
  178. btnupgrade.inputtitle=translate("Upgrade")
  179. btnupgrade.inputstyle = "reload"
  180. btnstop.forcewrite = true
  181. btnduplicate=action_section:option(Button, "_duplicate")
  182. btnduplicate.template = "dockerman/cbi/inlinebutton"
  183. btnduplicate.inputtitle=translate("Duplicate/Edit")
  184. btnduplicate.inputstyle = "add"
  185. btnstop.forcewrite = true
  186. btnremove=action_section:option(Button, "_remove")
  187. btnremove.template = "dockerman/cbi/inlinebutton"
  188. btnremove.inputtitle=translate("Remove")
  189. btnremove.inputstyle = "remove"
  190. btnremove.forcewrite = true
  191. btnstart.write = function(self, section)
  192. start_stop_remove(m,"start")
  193. end
  194. btnrestart.write = function(self, section)
  195. start_stop_remove(m,"restart")
  196. end
  197. btnupgrade.write = function(self, section)
  198. start_stop_remove(m,"upgrade")
  199. end
  200. btnremove.write = function(self, section)
  201. start_stop_remove(m,"remove")
  202. end
  203. btnstop.write = function(self, section)
  204. start_stop_remove(m,"stop")
  205. end
  206. btnkill.write = function(self, section)
  207. start_stop_remove(m,"kill")
  208. end
  209. btnduplicate.write = function(self, section)
  210. luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id))
  211. end
  212. tab_section = m:section(SimpleSection)
  213. tab_section.template = "dockerman/container"
  214. if action == "info" then
  215. m.submit = false
  216. m.reset = false
  217. table_info = {
  218. ["01name"] = {_key = translate("Name"), _value = container_info.Name:sub(2) or "-", _button=translate("Update")},
  219. ["02id"] = {_key = translate("ID"), _value = container_info.Id or "-"},
  220. ["03image"] = {_key = translate("Image"), _value = container_info.Config.Image .. "<br>" .. container_info.Image},
  221. ["04status"] = {_key = translate("Status"), _value = container_info.State and container_info.State.Status or "-"},
  222. ["05created"] = {_key = translate("Created"), _value = container_info.Created or "-"},
  223. }
  224. 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 "-"}
  225. table_info["07healthy"] = {_key = translate("Healthy"), _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"}
  226. 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")}
  227. table_info["081user"] = {_key = translate("User"), _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"}
  228. table_info["09mount"] = {_key = translate("Mount/Volume"), _value = get_mounts(container_info) or "-"}
  229. table_info["10cmd"] = {_key = translate("Command"), _value = get_command(container_info) or "-"}
  230. table_info["11env"] = {_key = translate("Env"), _value = get_env(container_info) or "-"}
  231. table_info["12ports"] = {_key = translate("Ports"), _value = get_ports(container_info) or "-"}
  232. table_info["13links"] = {_key = translate("Links"), _value = get_links(container_info) or "-"}
  233. table_info["14device"] = {_key = translate("Device"), _value = get_device(container_info) or "-"}
  234. table_info["15tmpfs"] = {_key = translate("Tmpfs"), _value = get_tmpfs(container_info) or "-"}
  235. table_info["16dns"] = {_key = translate("DNS"), _value = get_dns(container_info) or "-"}
  236. table_info["17sysctl"] = {_key = translate("Sysctl"), _value = get_sysctl(container_info) or "-"}
  237. info_networks = get_networks(container_info)
  238. list_networks = {}
  239. for _, v in ipairs (networks) do
  240. if v.Name then
  241. local parent = v.Options and v.Options.parent or nil
  242. local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
  243. ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil
  244. local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "")
  245. list_networks[v.Name] = network_name
  246. end
  247. end
  248. if type(info_networks)== "table" then
  249. for k,v in pairs(info_networks) do
  250. table_info["14network"..k] = {
  251. _key = translate("Network"), _value = k.. (v~="" and (" | ".. v) or ""), _button=translate("Disconnect")
  252. }
  253. list_networks[k]=nil
  254. end
  255. end
  256. table_info["15connect"] = {_key = translate("Connect Network"), _value = list_networks ,_opts = "", _button=translate("Connect")}
  257. d_info = m:section(Table,table_info)
  258. d_info.nodescr=true
  259. d_info.formvalue=function(self, section)
  260. return table_info
  261. end
  262. dv_key = d_info:option(DummyValue, "_key", translate("Info"))
  263. dv_key.width = "20%"
  264. dv_value = d_info:option(ListValue, "_value")
  265. dv_value.render = function(self, section, scope)
  266. if table_info[section]._key == translate("Name") then
  267. self:reset_values()
  268. self.template = "cbi/value"
  269. self.size = 30
  270. self.keylist = {}
  271. self.vallist = {}
  272. self.default=table_info[section]._value
  273. Value.render(self, section, scope)
  274. elseif table_info[section]._key == translate("Restart Policy") then
  275. self.template = "cbi/lvalue"
  276. self:reset_values()
  277. self.size = nil
  278. self:value("no", "No")
  279. self:value("unless-stopped", "Unless stopped")
  280. self:value("always", "Always")
  281. self:value("on-failure", "On failure")
  282. self.default=table_info[section]._value
  283. ListValue.render(self, section, scope)
  284. elseif table_info[section]._key == translate("Connect Network") then
  285. self.template = "cbi/lvalue"
  286. self:reset_values()
  287. self.size = nil
  288. for k,v in pairs(list_networks) do
  289. if k ~= "host" then
  290. self:value(k,v)
  291. end
  292. end
  293. self.default=table_info[section]._value
  294. ListValue.render(self, section, scope)
  295. else
  296. self:reset_values()
  297. self.rawhtml=true
  298. self.template = "cbi/dvalue"
  299. self.default=table_info[section]._value
  300. DummyValue.render(self, section, scope)
  301. end
  302. end
  303. dv_value.forcewrite = true -- for write function using simpleform
  304. dv_value.write = function(self, section, value)
  305. table_info[section]._value=value
  306. end
  307. dv_value.validate = function(self, value)
  308. return value
  309. end
  310. dv_opts = d_info:option(Value, "_opts")
  311. dv_opts.forcewrite = true -- for write function using simpleform
  312. dv_opts.write = function(self, section, value)
  313. table_info[section]._opts=value
  314. end
  315. dv_opts.validate = function(self, value)
  316. return value
  317. end
  318. dv_opts.render = function(self, section, scope)
  319. if table_info[section]._key==translate("Connect Network") then
  320. self.template = "cbi/value"
  321. self.keylist = {}
  322. self.vallist = {}
  323. self.placeholder = "10.1.1.254"
  324. self.datatype = "ip4addr"
  325. self.default=table_info[section]._opts
  326. Value.render(self, section, scope)
  327. else
  328. self.rawhtml=true
  329. self.template = "cbi/dvalue"
  330. self.default=table_info[section]._opts
  331. DummyValue.render(self, section, scope)
  332. end
  333. end
  334. btn_update = d_info:option(Button, "_button")
  335. btn_update.forcewrite = true
  336. btn_update.render = function(self, section, scope)
  337. if table_info[section]._button and table_info[section]._value ~= nil then
  338. btn_update.inputtitle=table_info[section]._button
  339. self.template = "cbi/button"
  340. self.inputstyle = "edit"
  341. Button.render(self, section, scope)
  342. else
  343. self.template = "cbi/dvalue"
  344. self.default=""
  345. DummyValue.render(self, section, scope)
  346. end
  347. end
  348. btn_update.write = function(self, section, value)
  349. local res
  350. docker:clear_status()
  351. if section == "01name" then
  352. docker:append_status("Containers: rename " .. container_id .. "...")
  353. local new_name = table_info[section]._value
  354. res = dk.containers:rename({id = container_id, query = {name=new_name}})
  355. elseif section == "08restart" then
  356. docker:append_status("Containers: update " .. container_id .. "...")
  357. local new_restart = table_info[section]._value
  358. res = dk.containers:update({id = container_id, body = {RestartPolicy = {Name = new_restart}}})
  359. elseif table_info[section]._key == translate("Network") then
  360. local _,_,leave_network = table_info[section]._value:find("(.-) | .+")
  361. leave_network = leave_network or table_info[section]._value
  362. docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...")
  363. res = dk.networks:disconnect({name = leave_network, body = {Container = container_id}})
  364. elseif section == "15connect" then
  365. local connect_network = table_info[section]._value
  366. local network_opiton
  367. if connect_network ~= "none" and connect_network ~= "bridge" and connect_network ~= "host" then
  368. network_opiton = table_info[section]._opts ~= "" and {
  369. IPAMConfig={
  370. IPv4Address=table_info[section]._opts
  371. }
  372. } or nil
  373. end
  374. docker:append_status("Network: connect " .. connect_network .. container_id .. "...")
  375. res = dk.networks:connect({name = connect_network, body = {Container = container_id, EndpointConfig= network_opiton}})
  376. end
  377. if res and res.code > 300 then
  378. docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
  379. else
  380. docker:clear_status()
  381. end
  382. luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info"))
  383. end
  384. -- info end
  385. elseif action == "resources" then
  386. local resources_section= m:section(SimpleSection)
  387. d = resources_section:option( Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
  388. d.placeholder = "1.5"
  389. d.rmempty = true
  390. d.datatype="ufloat"
  391. d.default = container_info.HostConfig.NanoCpus / (10^9)
  392. d = resources_section: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."))
  393. d.placeholder = "1024"
  394. d.rmempty = true
  395. d.datatype="uinteger"
  396. d.default = container_info.HostConfig.CpuShares
  397. d = resources_section: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."))
  398. d.placeholder = "128m"
  399. d.rmempty = true
  400. d.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0
  401. d = resources_section:option(Value, "blkioweight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
  402. d.placeholder = "500"
  403. d.rmempty = true
  404. d.datatype="uinteger"
  405. d.default = container_info.HostConfig.BlkioWeight
  406. m.handle = function(self, state, data)
  407. if state == FORM_VALID then
  408. local memory = data.memory
  409. if memory and memory ~= 0 then
  410. _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
  411. if n then
  412. unit = unit and unit:sub(1,1):upper() or "B"
  413. if unit == "M" then
  414. memory = tonumber(n) * 1024 * 1024
  415. elseif unit == "G" then
  416. memory = tonumber(n) * 1024 * 1024 * 1024
  417. elseif unit == "K" then
  418. memory = tonumber(n) * 1024
  419. else
  420. memory = tonumber(n)
  421. end
  422. end
  423. end
  424. request_body = {
  425. BlkioWeight = tonumber(data.blkioweight),
  426. NanoCPUs = tonumber(data.cpus)*10^9,
  427. Memory = tonumber(memory),
  428. CpuShares = tonumber(data.cpushares)
  429. }
  430. docker:write_status("Containers: update " .. container_id .. "...")
  431. local res = dk.containers:update({id = container_id, body = request_body})
  432. if res and res.code >= 300 then
  433. docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
  434. else
  435. docker:clear_status()
  436. end
  437. luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources"))
  438. end
  439. end
  440. elseif action == "file" then
  441. local filesection= m:section(SimpleSection)
  442. m.submit = false
  443. m.reset = false
  444. filesection.template = "dockerman/container_file"
  445. filesection.container = container_id
  446. elseif action == "inspect" then
  447. local inspectsection= m:section(SimpleSection)
  448. inspectsection.syslog = luci.jsonc.stringify(container_info, true)
  449. inspectsection.title = translate("Container Inspect")
  450. inspectsection.template = "dockerman/logs"
  451. m.submit = false
  452. m.reset = false
  453. elseif action == "logs" then
  454. local logsection= m:section(SimpleSection)
  455. local logs = ""
  456. local query ={
  457. stdout = 1,
  458. stderr = 1,
  459. tail = 1000
  460. }
  461. local logs = dk.containers:logs({id = container_id, query = query})
  462. if logs.code == 200 then
  463. logsection.syslog=logs.body
  464. else
  465. logsection.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body
  466. end
  467. logsection.title=translate("Container Logs")
  468. logsection.template = "dockerman/logs"
  469. m.submit = false
  470. m.reset = false
  471. elseif action == "console" then
  472. m.submit = false
  473. m.reset = false
  474. local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil
  475. local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil
  476. if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then
  477. local consolesection= m:section(SimpleSection)
  478. local cmd = "/bin/sh"
  479. local uid
  480. local vcommand = consolesection:option(Value, "command", translate("Command"))
  481. vcommand:value("/bin/sh", "/bin/sh")
  482. vcommand:value("/bin/ash", "/bin/ash")
  483. vcommand:value("/bin/bash", "/bin/bash")
  484. vcommand.default = "/bin/sh"
  485. vcommand.forcewrite = true
  486. vcommand.write = function(self, section, value)
  487. cmd = value
  488. end
  489. local vuid = consolesection:option(Value, "uid", translate("UID"))
  490. vuid.forcewrite = true
  491. vuid.write = function(self, section, value)
  492. uid = value
  493. end
  494. local btn_connect = consolesection:option(Button, "connect")
  495. btn_connect.render = function(self, section, scope)
  496. self.inputstyle = "add"
  497. self.title = " "
  498. self.inputtitle = translate("Connect")
  499. Button.render(self, section, scope)
  500. end
  501. btn_connect.write = function(self, section)
  502. local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil
  503. local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil
  504. if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$") then return end
  505. local kill_ttyd = 'netstat -lnpt | grep ":7682[ \t].*ttyd$" | awk \'{print $NF}\' | awk -F\'/\' \'{print "kill -9 " $1}\' | sh > /dev/null'
  506. luci.util.exec(kill_ttyd)
  507. local hosts
  508. local uci = (require "luci.model.uci").cursor()
  509. local remote = uci:get("dockerman", "local", "remote_endpoint")
  510. local socket_path = (remote == "false" or not remote) and uci:get("dockerman", "local", "socket_path") or nil
  511. local host = (remote == "true") and uci:get("dockerman", "local", "remote_host") or nil
  512. local port = (remote == "true") and uci:get("dockerman", "local", "remote_port") or nil
  513. if remote and host and port then
  514. hosts = host .. ':'.. port
  515. elseif socket_path then
  516. hosts = "unix://" .. socket_path
  517. else
  518. return
  519. end
  520. local start_cmd = cmd_ttyd .. ' -d 2 --once -p 7682 '.. cmd_docker .. ' -H "'.. hosts ..'" exec -it ' .. (uid and uid ~= "" and (" -u ".. uid .. ' ') or "").. container_id .. ' ' .. cmd .. ' &'
  521. os.execute(start_cmd)
  522. local console = consolesection:option(DummyValue, "console")
  523. console.container_id = container_id
  524. console.template = "dockerman/container_console"
  525. end
  526. end
  527. elseif action == "stats" then
  528. local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}})
  529. local container_top
  530. if response.code == 200 then
  531. container_top=response.body
  532. else
  533. response = dk.containers:top({id = container_id})
  534. if response.code == 200 then
  535. container_top=response.body
  536. end
  537. end
  538. if type(container_top) == "table" then
  539. container_top=response.body
  540. stat_section = m:section(SimpleSection)
  541. stat_section.container_id = container_id
  542. stat_section.template = "dockerman/container_stats"
  543. table_stats = {cpu={key=translate("CPU Useage"),value='-'},memory={key=translate("Memory Useage"),value='-'}}
  544. stat_section = m:section(Table, table_stats, translate("Stats"))
  545. stat_section:option(DummyValue, "key", translate("Stats")).width="33%"
  546. stat_section:option(DummyValue, "value")
  547. top_section= m:section(Table, container_top.Processes, translate("TOP"))
  548. for i, v in ipairs(container_top.Titles) do
  549. top_section:option(DummyValue, i, translate(v))
  550. end
  551. end
  552. m.submit = false
  553. m.reset = false
  554. end
  555. return m