container.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  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 m, s, o
  11. local images, networks, container_info, res
  12. if not container_id then
  13. return
  14. end
  15. res = dk.containers:inspect({id = container_id})
  16. if res.code < 300 then
  17. container_info = res.body
  18. else
  19. return
  20. end
  21. res = dk.networks:list()
  22. if res.code < 300 then
  23. networks = res.body
  24. else
  25. return
  26. end
  27. local get_ports = function(d)
  28. local data
  29. if d.HostConfig and d.HostConfig.PortBindings then
  30. for inter, out in pairs(d.HostConfig.PortBindings) do
  31. data = (data and (data .. "<br />") or "") .. out[1]["HostPort"] .. ":" .. inter
  32. end
  33. end
  34. return data
  35. end
  36. local get_env = function(d)
  37. local data
  38. if d.Config and d.Config.Env then
  39. for _,v in ipairs(d.Config.Env) do
  40. data = (data and (data .. "<br />") or "") .. v
  41. end
  42. end
  43. return data
  44. end
  45. local get_command = function(d)
  46. local data
  47. if d.Config and d.Config.Cmd then
  48. for _,v in ipairs(d.Config.Cmd) do
  49. data = (data and (data .. " ") or "") .. v
  50. end
  51. end
  52. return data
  53. end
  54. local get_mounts = function(d)
  55. local data
  56. if d.Mounts then
  57. for _,v in ipairs(d.Mounts) do
  58. local v_sorce_d, v_dest_d
  59. local v_sorce = ""
  60. local v_dest = ""
  61. for v_sorce_d in v["Source"]:gmatch('[^/]+') do
  62. if v_sorce_d and #v_sorce_d > 12 then
  63. v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..."
  64. else
  65. v_sorce = v_sorce .."/".. v_sorce_d
  66. end
  67. end
  68. for v_dest_d in v["Destination"]:gmatch('[^/]+') do
  69. if v_dest_d and #v_dest_d > 12 then
  70. v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..."
  71. else
  72. v_dest = v_dest .."/".. v_dest_d
  73. end
  74. end
  75. data = (data and (data .. "<br />") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "")
  76. end
  77. end
  78. return data
  79. end
  80. local get_device = function(d)
  81. local data
  82. if d.HostConfig and d.HostConfig.Devices then
  83. for _,v in ipairs(d.HostConfig.Devices) do
  84. data = (data and (data .. "<br />") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "")
  85. end
  86. end
  87. return data
  88. end
  89. local get_links = function(d)
  90. local data
  91. if d.HostConfig and d.HostConfig.Links then
  92. for _,v in ipairs(d.HostConfig.Links) do
  93. data = (data and (data .. "<br />") or "") .. v
  94. end
  95. end
  96. return data
  97. end
  98. local get_tmpfs = function(d)
  99. local data
  100. if d.HostConfig and d.HostConfig.Tmpfs then
  101. for k, v in pairs(d.HostConfig.Tmpfs) do
  102. data = (data and (data .. "<br />") or "") .. k .. (v~="" and ":" or "")..v
  103. end
  104. end
  105. return data
  106. end
  107. local get_dns = function(d)
  108. local data
  109. if d.HostConfig and d.HostConfig.Dns then
  110. for _, v in ipairs(d.HostConfig.Dns) do
  111. data = (data and (data .. "<br />") or "") .. v
  112. end
  113. end
  114. return data
  115. end
  116. local get_sysctl = function(d)
  117. local data
  118. if d.HostConfig and d.HostConfig.Sysctls then
  119. for k, v in pairs(d.HostConfig.Sysctls) do
  120. data = (data and (data .. "<br />") or "") .. k..":"..v
  121. end
  122. end
  123. return data
  124. end
  125. local get_networks = function(d)
  126. local data={}
  127. if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then
  128. for k,v in pairs(d.NetworkSettings.Networks) do
  129. data[k] = v.IPAddress or ""
  130. end
  131. end
  132. return data
  133. end
  134. local start_stop_remove = function(m, cmd)
  135. local res
  136. docker:clear_status()
  137. docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...")
  138. if cmd ~= "upgrade" then
  139. res = dk.containers[cmd](dk, {id = container_id})
  140. else
  141. res = dk.containers_upgrade(dk, {id = container_id})
  142. end
  143. if res and res.code >= 300 then
  144. docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
  145. luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
  146. else
  147. docker:clear_status()
  148. if cmd ~= "remove" and cmd ~= "upgrade" then
  149. luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
  150. else
  151. luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
  152. end
  153. end
  154. end
  155. m=SimpleForm("docker",
  156. translatef("Docker - Container (%s)", container_info.Name:sub(2)),
  157. translate("On this page, the selected container can be managed."))
  158. m.redirect = luci.dispatcher.build_url("admin/docker/containers")
  159. s = m:section(SimpleSection)
  160. s.template = "dockerman/apply_widget"
  161. s.err=docker:read_status()
  162. s.err=s.err and s.err:gsub("\n","<br />"):gsub(" ","&#160;")
  163. if s.err then
  164. docker:clear_status()
  165. end
  166. s = m:section(Table,{{}})
  167. s.notitle=true
  168. s.rowcolors=false
  169. s.template = "cbi/nullsection"
  170. o = s:option(Button, "_start")
  171. o.template = "dockerman/cbi/inlinebutton"
  172. o.inputtitle=translate("Start")
  173. o.inputstyle = "apply"
  174. o.forcewrite = true
  175. o.write = function(self, section)
  176. start_stop_remove(m,"start")
  177. end
  178. o = s:option(Button, "_restart")
  179. o.template = "dockerman/cbi/inlinebutton"
  180. o.inputtitle=translate("Restart")
  181. o.inputstyle = "reload"
  182. o.forcewrite = true
  183. o.write = function(self, section)
  184. start_stop_remove(m,"restart")
  185. end
  186. o = s:option(Button, "_stop")
  187. o.template = "dockerman/cbi/inlinebutton"
  188. o.inputtitle=translate("Stop")
  189. o.inputstyle = "reset"
  190. o.forcewrite = true
  191. o.write = function(self, section)
  192. start_stop_remove(m,"stop")
  193. end
  194. o = s:option(Button, "_kill")
  195. o.template = "dockerman/cbi/inlinebutton"
  196. o.inputtitle=translate("Kill")
  197. o.inputstyle = "reset"
  198. o.forcewrite = true
  199. o.write = function(self, section)
  200. start_stop_remove(m,"kill")
  201. end
  202. o = s:option(Button, "_upgrade")
  203. o.template = "dockerman/cbi/inlinebutton"
  204. o.inputtitle=translate("Upgrade")
  205. o.inputstyle = "reload"
  206. o.forcewrite = true
  207. o.write = function(self, section)
  208. start_stop_remove(m,"upgrade")
  209. end
  210. o = s:option(Button, "_duplicate")
  211. o.template = "dockerman/cbi/inlinebutton"
  212. o.inputtitle=translate("Duplicate/Edit")
  213. o.inputstyle = "add"
  214. o.forcewrite = true
  215. o.write = function(self, section)
  216. luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id))
  217. end
  218. o = s:option(Button, "_remove")
  219. o.template = "dockerman/cbi/inlinebutton"
  220. o.inputtitle=translate("Remove")
  221. o.inputstyle = "remove"
  222. o.forcewrite = true
  223. o.write = function(self, section)
  224. start_stop_remove(m,"remove")
  225. end
  226. s = m:section(SimpleSection)
  227. s.template = "dockerman/container"
  228. if action == "info" then
  229. m.submit = false
  230. m.reset = false
  231. table_info = {
  232. ["01name"] = {
  233. _key = translate("Name"),
  234. _value = container_info.Name:sub(2) or "-",
  235. _button=translate("Update")
  236. },
  237. ["02id"] = {
  238. _key = translate("ID"),
  239. _value = container_info.Id or "-"
  240. },
  241. ["03image"] = {
  242. _key = translate("Image"),
  243. _value = container_info.Config.Image .. "<br />" .. container_info.Image
  244. },
  245. ["04status"] = {
  246. _key = translate("Status"),
  247. _value = container_info.State and container_info.State.Status or "-"
  248. },
  249. ["05created"] = {
  250. _key = translate("Created"),
  251. _value = container_info.Created or "-"
  252. },
  253. }
  254. if container_info.State.Status == "running" then
  255. table_info["06start"] = {
  256. _key = translate("Start Time"),
  257. _value = container_info.State and container_info.State.StartedAt or "-"
  258. }
  259. else
  260. table_info["06start"] = {
  261. _key = translate("Finish Time"),
  262. _value = container_info.State and container_info.State.FinishedAt or "-"
  263. }
  264. end
  265. table_info["07healthy"] = {
  266. _key = translate("Healthy"),
  267. _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"
  268. }
  269. table_info["08restart"] = {
  270. _key = translate("Restart Policy"),
  271. _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-",
  272. _button=translate("Update")
  273. }
  274. table_info["081user"] = {
  275. _key = translate("User"),
  276. _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"
  277. }
  278. table_info["09mount"] = {
  279. _key = translate("Mount/Volume"),
  280. _value = get_mounts(container_info) or "-"
  281. }
  282. table_info["10cmd"] = {
  283. _key = translate("Command"),
  284. _value = get_command(container_info) or "-"
  285. }
  286. table_info["11env"] = {
  287. _key = translate("Env"),
  288. _value = get_env(container_info) or "-"
  289. }
  290. table_info["12ports"] = {
  291. _key = translate("Ports"),
  292. _value = get_ports(container_info) or "-"
  293. }
  294. table_info["13links"] = {
  295. _key = translate("Links"),
  296. _value = get_links(container_info) or "-"
  297. }
  298. table_info["14device"] = {
  299. _key = translate("Device"),
  300. _value = get_device(container_info) or "-"
  301. }
  302. table_info["15tmpfs"] = {
  303. _key = translate("Tmpfs"),
  304. _value = get_tmpfs(container_info) or "-"
  305. }
  306. table_info["16dns"] = {
  307. _key = translate("DNS"),
  308. _value = get_dns(container_info) or "-"
  309. }
  310. table_info["17sysctl"] = {
  311. _key = translate("Sysctl"),
  312. _value = get_sysctl(container_info) or "-"
  313. }
  314. info_networks = get_networks(container_info)
  315. list_networks = {}
  316. for _, v in ipairs (networks) do
  317. if v.Name then
  318. local parent = v.Options and v.Options.parent or nil
  319. local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
  320. ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil
  321. local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "")
  322. list_networks[v.Name] = network_name
  323. end
  324. end
  325. if type(info_networks)== "table" then
  326. for k,v in pairs(info_networks) do
  327. table_info["14network"..k] = {
  328. _key = translate("Network"),
  329. value = k.. (v~="" and (" | ".. v) or ""),
  330. _button=translate("Disconnect")
  331. }
  332. list_networks[k]=nil
  333. end
  334. end
  335. table_info["15connect"] = {
  336. _key = translate("Connect Network"),
  337. _value = list_networks ,_opts = "",
  338. _button=translate("Connect")
  339. }
  340. s = m:section(Table,table_info)
  341. s.nodescr=true
  342. s.formvalue=function(self, section)
  343. return table_info
  344. end
  345. o = s:option(DummyValue, "_key", translate("Info"))
  346. o.width = "20%"
  347. o = s:option(ListValue, "_value")
  348. o.render = function(self, section, scope)
  349. if table_info[section]._key == translate("Name") then
  350. self:reset_values()
  351. self.template = "cbi/value"
  352. self.size = 30
  353. self.keylist = {}
  354. self.vallist = {}
  355. self.default=table_info[section]._value
  356. Value.render(self, section, scope)
  357. elseif table_info[section]._key == translate("Restart Policy") then
  358. self.template = "cbi/lvalue"
  359. self:reset_values()
  360. self.size = nil
  361. self:value("no", "No")
  362. self:value("unless-stopped", "Unless stopped")
  363. self:value("always", "Always")
  364. self:value("on-failure", "On failure")
  365. self.default=table_info[section]._value
  366. ListValue.render(self, section, scope)
  367. elseif table_info[section]._key == translate("Connect Network") then
  368. self.template = "cbi/lvalue"
  369. self:reset_values()
  370. self.size = nil
  371. for k,v in pairs(list_networks) do
  372. if k ~= "host" then
  373. self:value(k,v)
  374. end
  375. end
  376. self.default=table_info[section]._value
  377. ListValue.render(self, section, scope)
  378. else
  379. self:reset_values()
  380. self.rawhtml=true
  381. self.template = "cbi/dvalue"
  382. self.default=table_info[section]._value
  383. DummyValue.render(self, section, scope)
  384. end
  385. end
  386. o.forcewrite = true
  387. o.write = function(self, section, value)
  388. table_info[section]._value=value
  389. end
  390. o.validate = function(self, value)
  391. return value
  392. end
  393. o = s:option(Value, "_opts")
  394. o.forcewrite = true
  395. o.write = function(self, section, value)
  396. table_info[section]._opts=value
  397. end
  398. o.validate = function(self, value)
  399. return value
  400. end
  401. o.render = function(self, section, scope)
  402. if table_info[section]._key==translate("Connect Network") then
  403. self.template = "cbi/value"
  404. self.keylist = {}
  405. self.vallist = {}
  406. self.placeholder = "10.1.1.254"
  407. self.datatype = "ip4addr"
  408. self.default=table_info[section]._opts
  409. Value.render(self, section, scope)
  410. else
  411. self.rawhtml=true
  412. self.template = "cbi/dvalue"
  413. self.default=table_info[section]._opts
  414. DummyValue.render(self, section, scope)
  415. end
  416. end
  417. o = s:option(Button, "_button")
  418. o.forcewrite = true
  419. o.render = function(self, section, scope)
  420. if table_info[section]._button and table_info[section]._value ~= nil then
  421. self.inputtitle=table_info[section]._button
  422. self.template = "cbi/button"
  423. self.inputstyle = "edit"
  424. Button.render(self, section, scope)
  425. else
  426. self.template = "cbi/dvalue"
  427. self.default=""
  428. DummyValue.render(self, section, scope)
  429. end
  430. end
  431. o.write = function(self, section, value)
  432. local res
  433. docker:clear_status()
  434. if section == "01name" then
  435. docker:append_status("Containers: rename " .. container_id .. "...")
  436. local new_name = table_info[section]._value
  437. res = dk.containers:rename({
  438. id = container_id,
  439. query = {
  440. name=new_name
  441. }
  442. })
  443. elseif section == "08restart" then
  444. docker:append_status("Containers: update " .. container_id .. "...")
  445. local new_restart = table_info[section]._value
  446. res = dk.containers:update({
  447. id = container_id,
  448. body = {
  449. RestartPolicy = {
  450. Name = new_restart
  451. }
  452. }
  453. })
  454. elseif table_info[section]._key == translate("Network") then
  455. local _,_,leave_network
  456. _, _, leave_network = table_info[section]._value:find("(.-) | .+")
  457. leave_network = leave_network or table_info[section]._value
  458. docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...")
  459. res = dk.networks:disconnect({
  460. name = leave_network,
  461. body = {
  462. Container = container_id
  463. }
  464. })
  465. elseif section == "15connect" then
  466. local connect_network = table_info[section]._value
  467. local network_opiton
  468. if connect_network ~= "none"
  469. and connect_network ~= "bridge"
  470. and connect_network ~= "host" then
  471. network_opiton = table_info[section]._opts ~= "" and {
  472. IPAMConfig={
  473. IPv4Address=table_info[section]._opts
  474. }
  475. } or nil
  476. end
  477. docker:append_status("Network: connect " .. connect_network .. container_id .. "...")
  478. res = dk.networks:connect({
  479. name = connect_network,
  480. body = {
  481. Container = container_id,
  482. EndpointConfig= network_opiton
  483. }
  484. })
  485. end
  486. if res and res.code > 300 then
  487. docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
  488. else
  489. docker:clear_status()
  490. end
  491. luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info"))
  492. end
  493. elseif action == "resources" then
  494. s = m:section(SimpleSection)
  495. o = s:option( Value, "cpus",
  496. translate("CPUs"),
  497. translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
  498. o.placeholder = "1.5"
  499. o.rmempty = true
  500. o.datatype="ufloat"
  501. o.default = container_info.HostConfig.NanoCpus / (10^9)
  502. o = s:option(Value, "cpushares",
  503. translate("CPU Shares Weight"),
  504. translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
  505. o.placeholder = "1024"
  506. o.rmempty = true
  507. o.datatype="uinteger"
  508. o.default = container_info.HostConfig.CpuShares
  509. o = s:option(Value, "memory",
  510. translate("Memory"),
  511. translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."))
  512. o.placeholder = "128m"
  513. o.rmempty = true
  514. o.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0
  515. o = s:option(Value, "blkioweight",
  516. translate("Block IO Weight"),
  517. translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
  518. o.placeholder = "500"
  519. o.rmempty = true
  520. o.datatype="uinteger"
  521. o.default = container_info.HostConfig.BlkioWeight
  522. m.handle = function(self, state, data)
  523. if state == FORM_VALID then
  524. local memory = data.memory
  525. if memory and memory ~= 0 then
  526. _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
  527. if n then
  528. unit = unit and unit:sub(1,1):upper() or "B"
  529. if unit == "M" then
  530. memory = tonumber(n) * 1024 * 1024
  531. elseif unit == "G" then
  532. memory = tonumber(n) * 1024 * 1024 * 1024
  533. elseif unit == "K" then
  534. memory = tonumber(n) * 1024
  535. else
  536. memory = tonumber(n)
  537. end
  538. end
  539. end
  540. request_body = {
  541. BlkioWeight = tonumber(data.blkioweight),
  542. NanoCPUs = tonumber(data.cpus)*10^9,
  543. Memory = tonumber(memory),
  544. CpuShares = tonumber(data.cpushares)
  545. }
  546. docker:write_status("Containers: update " .. container_id .. "...")
  547. local res = dk.containers:update({id = container_id, body = request_body})
  548. if res and res.code >= 300 then
  549. docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
  550. else
  551. docker:clear_status()
  552. end
  553. luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources"))
  554. end
  555. end
  556. elseif action == "file" then
  557. s = m:section(SimpleSection)
  558. s.template = "dockerman/container_file"
  559. s.container = container_id
  560. m.submit = false
  561. m.reset = false
  562. elseif action == "inspect" then
  563. s = m:section(SimpleSection)
  564. s.syslog = luci.jsonc.stringify(container_info, true)
  565. s.title = translate("Container Inspect")
  566. s.template = "dockerman/logs"
  567. m.submit = false
  568. m.reset = false
  569. elseif action == "logs" then
  570. local logs = ""
  571. local query ={
  572. stdout = 1,
  573. stderr = 1,
  574. tail = 1000
  575. }
  576. s = m:section(SimpleSection)
  577. logs = dk.containers:logs({id = container_id, query = query})
  578. if logs.code == 200 then
  579. s.syslog=logs.body
  580. else
  581. s.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body
  582. end
  583. s.title=translate("Container Logs")
  584. s.template = "dockerman/logs"
  585. m.submit = false
  586. m.reset = false
  587. elseif action == "console" then
  588. m.submit = false
  589. m.reset = false
  590. local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil
  591. local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil
  592. if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then
  593. local cmd = "/bin/sh"
  594. local uid
  595. s = m:section(SimpleSection)
  596. o = s:option(Value, "command", translate("Command"))
  597. o:value("/bin/sh", "/bin/sh")
  598. o:value("/bin/ash", "/bin/ash")
  599. o:value("/bin/bash", "/bin/bash")
  600. o.default = "/bin/sh"
  601. o.forcewrite = true
  602. o.write = function(self, section, value)
  603. cmd = value
  604. end
  605. o = s:option(Value, "uid", translate("UID"))
  606. o.forcewrite = true
  607. o.write = function(self, section, value)
  608. uid = value
  609. end
  610. o = s:option(Button, "connect")
  611. o.render = function(self, section, scope)
  612. self.inputstyle = "add"
  613. self.title = " "
  614. self.inputtitle = translate("Connect")
  615. Button.render(self, section, scope)
  616. end
  617. o.write = function(self, section)
  618. local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil
  619. local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil
  620. if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$")then
  621. return
  622. end
  623. local pid = luci.util.trim(luci.util.exec("netstat -lnpt | grep :7682 | grep ttyd | tr -s ' ' | cut -d ' ' -f7 | cut -d'/' -f1"))
  624. if pid and pid ~= "" then
  625. luci.util.exec("kill -9 " .. pid)
  626. end
  627. local hosts
  628. local uci = require "luci.model.uci".cursor()
  629. local remote = uci:get_bool("dockerd", "globals", "remote_endpoint") or false
  630. local host = nil
  631. local port = nil
  632. local socket = nil
  633. if remote then
  634. host = uci:get("dockerd", "globals", "remote_host") or nil
  635. port = uci:get("dockerd", "globals", "remote_port") or nil
  636. else
  637. socket = uci:get("dockerd", "globals", "socket_path") or "/var/run/docker.sock"
  638. end
  639. if remote and host and port then
  640. hosts = host .. ':'.. port
  641. elseif socket then
  642. hosts = socket
  643. else
  644. return
  645. end
  646. if uid and uid ~= "" then
  647. uid = "-u " .. uid
  648. else
  649. uid = ""
  650. end
  651. local start_cmd = string.format('%s -d 2 --once -p 7682 %s -H "unix://%s" exec -it %s %s %s&', cmd_ttyd, cmd_docker, hosts, uid, container_id, cmd)
  652. os.execute(start_cmd)
  653. o = s:option(DummyValue, "console")
  654. o.container_id = container_id
  655. o.template = "dockerman/container_console"
  656. end
  657. end
  658. elseif action == "stats" then
  659. local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}})
  660. local container_top
  661. if response.code ~= 409 then
  662. if response.code ~= 200 then
  663. response = dk.containers:top({id = container_id})
  664. end
  665. if response.code ~= 200 then
  666. response = dk.containers:top({id = container_id, query = {ps_args="-ww"}})
  667. end
  668. if response.code == 200 then
  669. container_top = response.body
  670. end
  671. local table_stats = {
  672. cpu = {
  673. key=translate("CPU Usage"),
  674. value='-'
  675. },
  676. memory = {
  677. key=translate("Memory Usage"),
  678. value='-'
  679. }
  680. }
  681. s = m:section(Table, table_stats, translate("Stats"))
  682. s:option(DummyValue, "key", translate("Stats")).width="33%"
  683. s:option(DummyValue, "value")
  684. s = m:section(SimpleSection)
  685. s.container_id = container_id
  686. s.template = "dockerman/container_stats"
  687. end
  688. if type(container_top) == "table" then
  689. local top_section = m:section(Table, container_top.Processes, translate("TOP"))
  690. for i, v in ipairs(container_top.Titles) do
  691. top_section:option(DummyValue, i, translate(v))
  692. end
  693. end
  694. m.submit = false
  695. m.reset = false
  696. end
  697. return m