summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-dockerman/luasrc/view
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-dockerman/luasrc/view')
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm140
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm7
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm33
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm9
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm10
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/container.htm27
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm6
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm63
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm80
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm88
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm29
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm13
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm95
-rw-r--r--applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm280
14 files changed, 880 insertions, 0 deletions
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm
new file mode 100644
index 000000000..4deb6a88a
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm
@@ -0,0 +1,140 @@
+<style type="text/css">
+ #docker_apply_overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 0.7);
+ display: none;
+ z-index: 20000;
+ }
+
+ #docker_apply_overlay .alert-message {
+ position: relative;
+ top: 10%;
+ width: 60%;
+ margin: auto;
+ display: flex;
+ flex-wrap: wrap;
+ min-height: 32px;
+ align-items: center;
+ }
+
+ #docker_apply_overlay .alert-message > h4,
+ #docker_apply_overlay .alert-message > p,
+ #docker_apply_overlay .alert-message > div {
+ flex-basis: 100%;
+ }
+
+ #docker_apply_overlay .alert-message > img {
+ margin-right: 1em;
+ flex-basis: 32px;
+ }
+
+ body.apply-overlay-active {
+ overflow: hidden;
+ height: 100vh;
+ }
+
+ body.apply-overlay-active #docker_apply_overlay {
+ display: block;
+ }
+</style>
+<script type="text/javascript">//<![CDATA[
+ var xhr = new XHR(),
+ uci_apply_rollback = <%=math.max(luci.config and luci.config.apply and luci.config.apply.rollback or 30, 30)%>,
+ uci_apply_holdoff = <%=math.max(luci.config and luci.config.apply and luci.config.apply.holdoff or 4, 1)%>,
+ uci_apply_timeout = <%=math.max(luci.config and luci.config.apply and luci.config.apply.timeout or 5, 1)%>,
+ uci_apply_display = <%=math.max(luci.config and luci.config.apply and luci.config.apply.display or 1.5, 1)%>,
+ was_xhr_poll_running = false;
+
+function docker_status_message(type, content) {
+ document.getElementById('docker_apply_overlay') || document.body.insertAdjacentHTML("beforeend",'<div id="docker_apply_overlay"><div class="alert-message"></div></div>')
+ var overlay = document.getElementById('docker_apply_overlay')
+ message = overlay.querySelector('.alert-message');
+
+ if (message && type) {
+ if (!message.classList.contains(type)) {
+ message.classList.remove('notice');
+ message.classList.remove('warning');
+ message.classList.add(type);
+ }
+
+ if (content)
+ message.innerHTML = content;
+
+ document.body.classList.add('apply-overlay-active');
+ document.body.scrollTop = document.documentElement.scrollTop = 0;
+ if (!was_xhr_poll_running) {
+ was_xhr_poll_running = XHR.running();
+ XHR.halt();
+ }
+ }
+ else {
+ document.body.classList.remove('apply-overlay-active');
+
+ if (was_xhr_poll_running)
+ XHR.run();
+ }
+}
+var loading_msg="Loading.."
+function uci_confirm_docker() {
+ var tt;
+ docker_status_message('notice');
+ var call = function(r, resjson, duration) {
+ if (r && r.status === 200 ) {
+ 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;
+ }
+ loading_msg = resjson?resjson.info:loading_msg
+ // var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0);
+ var delay =1000
+ window.setTimeout(function() {
+ xhr.get('<%=url("admin/docker/confirm")%>', null, call, uci_apply_timeout * 1000);
+ }, delay);
+ };
+
+ var tick = function() {
+ var now = Date.now();
+
+ docker_status_message('notice',
+ '<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> <span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">' +
+ loading_msg + '</span>');
+
+ tt = window.setTimeout(tick, 200);
+ ts = now;
+ };
+ tick();
+ /* wait a few seconds for the settings to become effective */
+ window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 , 1));
+ }
+ // document.getElementsByTagName("form")[0].addEventListener("submit", (e)=>{
+ // uci_confirm_docker()
+ // })
+
+function fnSubmitForm(el){
+ if (el.id != "cbid.table.1._new") {
+ uci_confirm_docker()
+ }
+}
+
+<% if self.err then -%>
+ docker_status_message('warning', '<span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">'+`<%=self.err%>`+'</span>');
+ document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
+ docker_status_message()
+ })
+<%- end %>
+
+window.onload= function (){
+var buttons = document.querySelectorAll('input[type="submit"]');
+[].slice.call(buttons).forEach(function (el) {
+ el.onclick = fnSubmitForm.bind(this, el);
+});
+}
+
+//]]></script>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm
new file mode 100644
index 000000000..b1b193257
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm
@@ -0,0 +1,7 @@
+<div style="display: inline-block;">
+ <% if self:cfgvalue(section) ~= false then %>
+ <input class="cbi-button cbi-button-<%=self.inputstyle or "button" %>" type="submit"" <% if self.disable then %>disabled <% end %><%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> />
+ <% else %>
+ -
+ <% end %>
+</div>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm
new file mode 100644
index 000000000..51c97f5c9
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm
@@ -0,0 +1,33 @@
+<div style="display: inline-block;">
+ <!-- <%- 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 -%> -->
+ <%- if self.password then -%>
+ <input type="password" style="position:absolute; left:-100000px" aria-hidden="true"<%=
+ attr("name", "password." .. cbid)
+ %> />
+ <%- end -%>
+ <input data-update="change"<%=
+ attr("id", cbid) ..
+ attr("name", cbid) ..
+ attr("type", self.password and "password" or "text") ..
+ attr("class", self.password and "cbi-input-password" or "cbi-input-text") ..
+ attr("value", self:cfgvalue(section) or self.default) ..
+ ifattr(self.password, "autocomplete", "new-password") ..
+ ifattr(self.size, "size") ..
+ ifattr(self.placeholder, "placeholder") ..
+ ifattr(self.readonly, "readonly") ..
+ ifattr(self.maxlength, "maxlength") ..
+ ifattr(self.datatype, "data-type", self.datatype) ..
+ ifattr(self.datatype, "data-optional", self.optional or self.rmempty) ..
+ ifattr(self.combobox_manual, "data-manual", self.combobox_manual) ..
+ ifattr(#self.keylist > 0, "data-choices", { self.keylist, self.vallist })
+ %> />
+ <%- if self.password then -%>
+ <div class="cbi-button cbi-button-neutral" title="<%:Reveal/hide password%>" onclick="var e = this.previousElementSibling; e.type = (e.type === 'password') ? 'text' : 'password'">∗</div>
+ <% end %>
+</div>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm
new file mode 100644
index 000000000..244d2c10a
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm
@@ -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 -->
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm
new file mode 100644
index 000000000..04f7bc2ee
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm
@@ -0,0 +1,10 @@
+<%+cbi/valueheader%>
+ <input type="hidden" value="1"<%=
+ attr("name", "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option)
+ %> />
+ <input class="cbi-input-checkbox" data-update="click change" type="checkbox" <% if self.disable == 1 then %>disabled <% end %><%=
+ attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) ..
+ ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked")
+ %> />
+ <label<%= attr("for", cbid)%>></label>
+<%+cbi/valuefooter%>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm
new file mode 100644
index 000000000..dab1dee56
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm
@@ -0,0 +1,27 @@
+<br>
+<ul class="cbi-tabmenu">
+ <li id="cbi-tab-container_info"><a id="a-cbi-tab-container_info" href=""><%:Info%></a></li>
+ <li id="cbi-tab-container_resources"><a id="a-cbi-tab-container_resources" href=""><%:Resources%></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_inspect"><a id="a-cbi-tab-container_inspect" href=""><%:Inspect%></a></li>
+ <li id="cbi-tab-container_logs"><a id="a-cbi-tab-container_logs" href=""><%:Logs%></a></li>
+</ul>
+
+<script type="text/javascript">
+ let re = /\/admin\/docker\/container\//
+ let p = window.location.href
+ let path = p.split(re)
+ let container_id = path[1].split('/')[0] || path[1]
+ let action = path[1].split('/')[1] || "info"
+ let actions=["info","resources","stats","file","console","logs","inspect"]
+ actions.forEach(function(item) {
+ document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/docker/container/"+container_id+'/'+item
+ if (action === item) {
+ document.getElementById("cbi-tab-container_" + item).className="cbi-tab"
+ } else {
+ document.getElementById("cbi-tab-container_" + item).className="cbi-tab-disabled"
+ }
+ })
+</script>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm
new file mode 100644
index 000000000..636aec960
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm
@@ -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>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm
new file mode 100644
index 000000000..c0778329f
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm
@@ -0,0 +1,63 @@
+
+<div id="upload-container" class="cbi-value cbi-value-last">
+ <label class="cbi-value-title" for="archive"><%:Upload%></label>
+ <div class="cbi-value-field">
+ <input type="file" name="upload_archive" accept="application/x-tar" id="upload_archive" />
+ </div>
+ <br>
+ <label class="cbi-value-title" for="path"><%:Path%></label>
+ <div class="cbi-value-field">
+ <input type="text" class="cbi-input-text" name="path" value="/tmp/" id="path" />
+ </div>
+ <br>
+ <div class="cbi-value-field">
+ <input type="button"" class="cbi-button cbi-button-action important" id="upload" name="upload" value="<%:Upload%>" />
+ <input type="button"" class="cbi-button cbi-button-action important" id="download" name="download" value="<%:Download%>" />
+ </div>
+</div>
+<script type="text/javascript">
+ let btnUpload = document.getElementById('upload')
+ btnUpload.onclick = function (e) {
+ let uploadArchive = document.getElementById('upload_archive')
+ let uploadPath = document.getElementById('path').value
+ if (!uploadArchive.value || !uploadPath) {
+ 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
+ let formData = new FormData()
+ formData.append('upload-filename', fileName)
+ formData.append('upload-path', uploadPath)
+ formData.append('upload-archive', uploadArchive.files[0])
+ let xhr = new XMLHttpRequest()
+ xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/container_put_archive")%>/<%=self.container%>', true)
+ xhr.onload = function() {
+ if (xhr.status == 200) {
+ uploadArchive.value = ''
+ docker_status_message('notice', "<%:Upload Success%>")
+ }
+ else {
+ docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText)
+ }
+ document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
+ docker_status_message()
+ })
+ }
+ xhr.send(formData)
+ }
+ let btnDownload = document.getElementById('download')
+ btnDownload.onclick = function (e) {
+ let downloadPath = document.getElementById('path').value
+ if (!downloadPath) {
+ docker_status_message('warning', "<%:Please input the PATH !%>")
+ document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
+ docker_status_message()
+ })
+ return
+ }
+ window.open('<%=luci.dispatcher.build_url("admin/docker/container_get_archive")%>?id=<%=self.container%>&path=' + encodeURIComponent(downloadPath))
+ }
+</script>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm
new file mode 100644
index 000000000..b5600e497
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm
@@ -0,0 +1,80 @@
+<script type="text/javascript">//<![CDATA[
+ let last_bw_tx
+ let last_bw_rx
+ let interval = 3
+ function progressbar(v, m, pc, np, f) {
+ m = m || 100
+
+ return String.format(
+ '<div style="width:100%%; max-width:500px; position:relative; border:1px solid #999999">' +
+ '<div style="background-color:#CCCCCC; width:%d%%; height:15px">' +
+ '<div style="position:absolute; left:0; top:0; text-align:center; width:100%%; color:#000000">' +
+ '<small>%s '+(f?f:'/')+' %s ' + (np ? "" : '(%d%%)') + '</small>' +
+ '</div>' +
+ '</div>' +
+ '</div>', pc, v, m, pc, f
+ );
+ }
+
+ function niceBytes(bytes, decimals) {
+ if (bytes == 0) return '0 Bytes';
+ var k = 1000,
+ dm = decimals + 1 || 3,
+ sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
+ i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+ }
+
+ XHR.poll(interval, '<%=luci.dispatcher.build_url("admin/docker/container_stats")%>/<%=self.container_id%>', { status: 1 },
+ function (x, info) {
+ var e;
+
+ if (e = document.getElementById('cbi-table-cpu-value'))
+ e.innerHTML = progressbar(
+ (info.cpu_percent), 100, (info.cpu_percent ? info.cpu_percent : 0));
+ if (e = document.getElementById('cbi-table-memory-value'))
+ e.innerHTML = progressbar(
+ niceBytes(info.memory.mem_useage),
+ niceBytes(info.memory.mem_limit),
+ ((100 / (info.memory.mem_limit ? info.memory.mem_limit : 100)) * (info.memory.mem_useage ? info.memory.mem_useage : 0))
+ );
+
+ for (var eth in info.bw_rxtx) {
+ if (!document.getElementById("cbi-table-speed_" + eth + "-value")) {
+ let tab = document.getElementById("cbi-table-cpu").parentNode
+ let div = document.getElementById('cbi-table-cpu').cloneNode(true);
+ div.id = "cbi-table-speed_" + eth;
+ div.children[0].innerHTML = "<%:Upload/Download%>: " + eth
+ div.children[1].id = "cbi-table-speed_" + eth + "-value"
+ tab.appendChild(div)
+ }
+ if (!document.getElementById("cbi-table-network_" + eth + "-value")) {
+ let tab = document.getElementById("cbi-table-cpu").parentNode
+ let div = document.getElementById('cbi-table-cpu').cloneNode(true);
+ div.id = "cbi-table-network_" + eth;
+ div.children[0].innerHTML = "<%:TX/RX%>: " + eth
+ div.children[1].id = "cbi-table-network_" + eth + "-value"
+ tab.appendChild(div)
+ }
+ e = document.getElementById("cbi-table-network_" + eth + "-value")
+ e.innerHTML = progressbar(
+ '↑'+niceBytes(info.bw_rxtx[eth].bw_tx),
+ '↓'+niceBytes(info.bw_rxtx[eth].bw_rx),
+ null,
+ true, " "
+ );
+ e = document.getElementById("cbi-table-speed_" + eth + "-value")
+ if (! last_bw_tx) last_bw_tx = info.bw_rxtx[eth].bw_tx
+ if (! last_bw_rx) last_bw_rx = info.bw_rxtx[eth].bw_rx
+ e.innerHTML = progressbar(
+ '↑'+niceBytes((info.bw_rxtx[eth].bw_tx - last_bw_tx)/interval)+'/s',
+ '↓'+niceBytes((info.bw_rxtx[eth].bw_rx - last_bw_rx)/interval)+'/s',
+ null,
+ true, " "
+ );
+ last_bw_tx = info.bw_rxtx[eth].bw_tx
+ last_bw_rx = info.bw_rxtx[eth].bw_rx
+ }
+
+ });
+//]]></script>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm
new file mode 100644
index 000000000..eafb65490
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm
@@ -0,0 +1,88 @@
+<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", "<%=luci.dispatcher.build_url('admin/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", "<%=luci.dispatcher.build_url('admin/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("<%=luci.dispatcher.build_url('admin/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("<%=luci.dispatcher.build_url('admin/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>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm
new file mode 100644
index 000000000..4fe34e1ba
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm
@@ -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", '<%=luci.dispatcher.build_url("admin/docker/images_load")%>', true)
+ xhr.onload = function() {
+ location.reload()
+ }
+ xhr.send(formData)
+ }
+</script>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm
new file mode 100644
index 000000000..c66637d89
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm
@@ -0,0 +1,13 @@
+<% if self.title == "Events" then %>
+<%+header%>
+<h2 name="content"><%:Docker%></h2>
+<div class="cbi-section">
+<h3><%:Events%></h3>
+<% end %>
+<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 == "Events" then %>
+</div>
+<%+footer%>
+<% end %>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm
new file mode 100644
index 000000000..50d88740d
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm
@@ -0,0 +1,95 @@
+<style type="text/css">
+ #dialog_reslov {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 0.7);
+ display: none;
+ z-index: 20000;
+ }
+
+ #dialog_reslov .dialog_box {
+ position: relative;
+ background: rgba(255, 255, 255);
+ top: 10%;
+ width: 50%;
+ margin: auto;
+ display: flex;
+ flex-wrap: wrap;
+ height:auto;
+ align-items: center;
+ }
+
+ #dialog_reslov .dialog_line {
+ margin-top: .5em;
+ margin-bottom: .5em;
+ margin-left: 2em;
+ margin-right: 2em;
+ }
+
+ #dialog_reslov .dialog_box>h4,
+ #dialog_reslov .dialog_box>p,
+ #dialog_reslov .dialog_box>div {
+ flex-basis: 100%;
+ }
+
+ #dialog_reslov .dialog_box>img {
+ margin-right: 1em;
+ flex-basis: 32px;
+ }
+
+ body.dialog-reslov-active {
+ overflow: hidden;
+ height: 100vh;
+ }
+
+ body.dialog-reslov-active #dialog_reslov {
+ display: block;
+ }
+</style>
+<script type="text/javascript">
+ function close_reslov_dialog() {
+ document.body.classList.remove('dialog-reslov-active')
+ document.documentElement.style.overflowY = 'scroll'
+ }
+
+ function reslov_container() {
+ let s = document.getElementById('cmd-line-status')
+ if (!s) return
+ let cmd_line = document.getElementById("dialog_reslov_text").value;
+ 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
+ }
+ let reg_space = /\s+/g
+ 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(/^docker\s+(run|create)/,"DOCKERCLI").replace(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ")
+ console.log(cmd_line)
+ window.location.href = '<%=luci.dispatcher.build_url("admin/docker/newcontainer")%>/' + encodeURI(cmd_line)
+ }
+
+ function clear_text(){
+ let s = document.getElementById('cmd-line-status')
+ s.innerHTML = ""
+ }
+
+ function show_reslov_dialog() {
+ document.getElementById('dialog_reslov') || document.body.insertAdjacentHTML("beforeend", '<div id="dialog_reslov"><div class="dialog_box"><div class="dialog_line"></div><div class="dialog_line"><span><%:Plese input <docker create/run> command line:%></span><br><span id="cmd-line-status"></span></div><div class="dialog_line"><textarea class="cbi-input-textarea" id="dialog_reslov_text" style="width: 100%; height:100%;" rows="15" onkeyup="clear_text()"></textarea></div><div class="dialog_line" style="text-align: right;"><input type="button" class="cbi-button cbi-button-apply" type="submit" value="<%:Submit%>" onclick="reslov_container()" /> <input type="button" class="cbi-button cbi-button-reset" type="reset" value="<%:Cancel%>" onclick="close_reslov_dialog()" /></div><div class="dialog_line"></div></div></div>')
+ document.body.classList.add('dialog-reslov-active')
+ let s = document.getElementById('cmd-line-status')
+ s.innerHTML = ""
+ document.documentElement.style.overflowY = 'hidden'
+ }
+</script>
+<%+cbi/valueheader%>
+<input type="button" class="cbi-button cbi-button-apply" value="<%:Command line%>" onclick="show_reslov_dialog()" />
+
+<%+cbi/valuefooter%>
diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm
new file mode 100644
index 000000000..474009dfd
--- /dev/null
+++ b/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm
@@ -0,0 +1,280 @@
+<style>
+ /*!
+Pure v1.0.1
+Copyright 2013 Yahoo!
+Licensed under the BSD License.
+https://github.com/pure-css/pure/blob/master/LICENSE.md
+*/
+ .pure-g {
+ letter-spacing: -.31em;
+ text-rendering: optimizespeed;
+ font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-flow: row wrap;
+ -ms-flex-flow: row wrap;
+ flex-flow: row wrap;
+ -webkit-align-content: flex-start;
+ -ms-flex-line-pack: start;
+ align-content: flex-start
+ }
+
+ .pure-u {
+ display: inline-block;
+ zoom: 1;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto
+ }
+
+ .pure-g [class*=pure-u] {
+ font-family: sans-serif
+ }
+
+ .pure-u-1-4,
+ .pure-u-2-5,
+ .pure-u-3-5 {
+ display: inline-block;
+ zoom: 1;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto
+ }
+
+ .pure-u-1-4 {
+ width: 25%
+ }
+
+ .pure-u-2-5 {
+ width: 40%
+ }
+
+ .pure-u-3-5 {
+ width: 60%
+ }
+
+ .status {
+ margin: 1rem -0.5rem 1rem -0.5rem;
+ }
+
+ .block {
+ margin: 0.5rem 0.5rem;
+ padding: 0;
+ font-weight: normal;
+ font-style: normal;
+ line-height: 1;
+ font-family: inherit;
+ min-width: inherit;
+ overflow-x: auto;
+ overflow-y: hidden;
+ border: 1px solid rgba(0, 0, 0, .05);
+ border-radius: .375rem;
+ box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15);
+ }
+
+ .img-con {
+ margin: 1rem;
+ min-width: 4rem;
+ max-width: 4rem;
+ min-height: 4rem;
+ max-height: 4rem;
+ }
+
+ .block h4 {
+ font-size: .8125rem;
+ font-weight: 600;
+ margin: 1rem;
+ color: #8898aa !important;
+ line-height: 1.8em;
+ }
+
+ .cbi-section-table-cell {
+ position: relative;
+ }
+
+ @media screen and (max-width: 700px) {
+ .pure-u-1-4 {
+ width: 50%;
+ }
+
+ .cbi-button-add {
+ position: fixed;
+ padding: 0.3rem 0.5rem;
+ z-index: 1000;
+ width: 50px !important;
+ height: 50px;
+ bottom: 90px;
+ right: 5px;
+ font-size: 16px;
+ border-radius: 50%;
+ display: block;
+ background-color: #fb6340 !important;
+ border-color: #fb6340 !important;
+ box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .75);
+ }
+ }
+</style>
+
+<div class="pure-g status">
+ <div class="pure-u-1-4">
+ <div class="block pure-g">
+ <div class="pure-u-2-5">
+ <div class="img-con">
+ <svg role="img" viewBox="0 0 24 24">
+ <title>Docker icon</title>
+ <path
+ d="M4.82 17.275c-.684 0-1.304-.56-1.304-1.24s.56-1.243 1.305-1.243c.748 0 1.31.56 1.31 1.242s-.622 1.24-1.305 1.24zm16.012-6.763c-.135-.992-.75-1.8-1.56-2.42l-.315-.25-.254.31c-.494.56-.69 1.553-.63 2.295.06.562.24 1.12.554 1.554-.254.13-.568.25-.81.377-.57.187-1.124.25-1.68.25H.097l-.06.37c-.12 1.182.06 2.42.562 3.54l.244.435v.06c1.5 2.483 4.17 3.6 7.078 3.6 5.594 0 10.182-2.42 12.357-7.633 1.425.062 2.864-.31 3.54-1.676l.18-.31-.3-.187c-.81-.494-1.92-.56-2.85-.31l-.018.002zm-8.008-.992h-2.428v2.42h2.43V9.518l-.002.003zm0-3.043h-2.428v2.42h2.43V6.48l-.002-.003zm0-3.104h-2.428v2.42h2.43v-2.42h-.002zm2.97 6.147H13.38v2.42h2.42V9.518l-.007.003zm-8.998 0H4.383v2.42h2.422V9.518l-.01.003zm3.03 0h-2.4v2.42H9.84V9.518l-.015.003zm-6.03 0H1.4v2.42h2.428V9.518l-.03.003zm6.03-3.043h-2.4v2.42H9.84V6.48l-.015-.003zm-3.045 0H4.387v2.42H6.8V6.48l-.016-.003z" />
+ </svg>
+ </div>
+ </div>
+ <div class="pure-u-3-5">
+ <h4 style="text-align: right; font-size: 1rem"><%:Containers%></h4>
+ <h4 style="text-align: right;">
+ <%- if self.containers_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/containers")%>'><%- end -%>
+ <span style="font-size: 2rem; color: #2dce89;"><%=self.containers_running%></span>
+ <span style="font-size: 1rem; color: #8898aa !important;">/<%=self.containers_total%></span>
+ <%- if self.containers_total ~= "-" then -%></a><%- end -%>
+ </h4>
+ </div>
+ </div>
+ </div>
+ <div class="pure-u-1-4">
+ <div class="block pure-g">
+ <div class="pure-u-2-5">
+ <div class="img-con">
+ <svg id="icon-hub" viewBox="0 -4 42 50" stroke-width="2" fill-rule="nonzero" width="100%" height="100%">
+ <path
+ d="M37.176371,36.2324812 C37.1920117,36.8041095 36.7372743,37.270685 36.1684891,37.270685 L3.74335204,37.2703476 C3.17827583,37.2703476 2.72400056,36.8091818 2.72400056,36.2397767 L2.72400056,19.6131383 C1.4312007,18.4881431 0.662551336,16.8884326 0.662551336,15.1618249 L0.664207893,14.69503 C0.63774183,14.4532127 0.650524255,14.2942438 0.711604827,14.1238231 L5.10793246,1.20935468 C5.24853286,0.797020623 5.63848594,0.511627907 6.06681069,0.511627907 L34.0728364,0.511627907 C34.5091607,0.511627907 34.889927,0.793578201 35.0316653,1.20921034 L39.4428567,14.1234095 C39.4871296,14.273204 39.5020782,14.4249444 39.4884726,14.5493649 L39.4884726,15.1505835 C39.4884726,16.9959517 38.6190601,18.6883031 37.1764746,19.7563084 L37.176371,36.2324812 Z M35.1376208,35.209311 L35.1376208,20.7057152 C34.7023924,20.8097593 34.271333,20.8633641 33.8336069,20.8633641 C32.0046019,20.8633641 30.3013756,19.9547008 29.2437221,18.4771538 C28.1860473,19.954695 26.4828515,20.8633641 24.6538444,20.8633641 C22.824803,20.8633641 21.1216155,19.9547157 20.0639591,18.4771544 C19.0062842,19.9546953 17.3030887,20.8633641 15.4740818,20.8633641 C13.6450404,20.8633641 11.9418529,19.9547157 10.8841965,18.4771544 C9.82652161,19.9546953 8.12332608,20.8633641 6.29431919,20.8633641 C5.76735555,20.8633641 5.24095778,20.7883418 4.73973398,20.644674 L4.73973398,35.209311 L35.1376208,35.209311 Z M30.2720226,15.6557626 C30.5154632,17.4501192 32.0503909,18.8018554 33.845083,18.8018554 C35.7286794,18.8018554 37.285413,17.3395134 37.4474599,15.4751932 L30.2280765,15.4751932 C30.2470638,15.532987 30.2617919,15.5932958 30.2720226,15.6557626 Z M21.0484306,15.4751932 C21.0674179,15.532987 21.0821459,15.5932958 21.0923767,15.6557626 C21.3358173,17.4501192 22.8707449,18.8018554 24.665437,18.8018554 C26.4601001,18.8018554 27.9950169,17.4501481 28.2378191,15.6611556 C28.2451225,15.5981318 28.2590045,15.5358056 28.2787375,15.4751932 L21.0484306,15.4751932 Z M11.9238102,15.6557626 C12.1672508,17.4501192 13.7021785,18.8018554 15.4968705,18.8018554 C17.2915336,18.8018554 18.8264505,17.4501481 19.0692526,15.6611556 C19.0765561,15.5981318 19.0904381,15.5358056 19.110171,15.4751932 L11.8798641,15.4751932 C11.8988514,15.532987 11.9135795,15.5932958 11.9238102,15.6557626 Z M6.31682805,18.8018317 C8.11149114,18.8018317 9.64640798,17.4501244 9.88921012,15.6611319 C9.89651357,15.5981081 9.91039559,15.5357819 9.93012856,15.4751696 L2.70318796,15.4751696 C2.86612006,17.3346852 4.42809696,18.8018317 6.31682805,18.8018317 Z M3.09670082,13.4139924 L37.04257,13.4139924 L33.3489482,2.57204736 L6.80119239,2.57204736 L3.09670082,13.4139924 Z"
+ id="Fill-1"></path>
+ <rect id="Rectangle-3" x="14" y="26" width="6" height="10"></rect>
+ <path d="M20,26 L20,36 L26,36 L26,26 L20,26 Z" id="Rectangle-3"></path>
+ </svg>
+ </div>
+ </div>
+ <div class="pure-u-3-5">
+ <h4 style="text-align: right; font-size: 1rem"><%:Images%></h4>
+ <h4 style="text-align: right;">
+ <%- if self.images_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/images")%>'><%- end -%>
+ <span style="font-size: 2rem; color: #2dce89;"><%=self.images_used%></span>
+ <span style="font-size: 1rem; color: #8898aa !important;">/<%=self.images_total%></span>
+ <%- if self.images_total ~= "-" then -%></a><%- end -%>
+ </h4>
+ </div>
+ </div>
+ </div>
+ <div class="pure-u-1-4">
+ <div class="block pure-g">
+ <div class="pure-u-2-5">
+ <div class="img-con">
+ <svg version="1.1" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 48.723 48.723" xml:space="preserve">
+ <path d="M7.452,24.152h3.435v5.701h0.633c0.001,0,0.001,0,0.002,0h0.636v-5.701h3.51v-1.059h17.124v1.104h3.178v5.656h0.619
+ c0,0,0,0,0.002,0h0.619v-5.656h3.736v-0.856c0-0.012,0.006-0.021,0.006-0.032c0-0.072,0-0.143,0-0.215h5.721v-1.316h-5.721
+ c0-0.054,0-0.108,0-0.164c0-0.011-0.006-0.021-0.006-0.032v-0.832h-8.154v1.028h-7.911v-2.652h-0.689c-0.001,0-0.001,0-0.002,0
+ h-0.678v2.652h-7.846v-1.104H7.452v1.104H1.114v1.316h6.338V24.152z" />
+ <path
+ d="M21.484,16.849h5.204v-2.611h7.133V1.555H14.588v12.683h6.896V16.849z M16.537,12.288V3.505h15.335v8.783H16.537z" />
+ <rect x="18.682" y="16.898" width="10.809" height="0.537" />
+ <path
+ d="M0,43.971h6.896v2.611H12.1v-2.611h7.134V31.287H0V43.971z M1.95,33.236h15.334v8.785H1.95V33.236z" />
+ <rect x="4.095" y="46.631" width="10.808" height="0.537" />
+ <path
+ d="M29.491,30.994v12.684h6.895v2.611h5.205v-2.611h7.133V30.994H29.491z M46.774,41.729H31.44v-8.783h15.334V41.729z" />
+ <rect x="33.584" y="46.338" width="10.809" height="0.537" />
+ </svg>
+ </div>
+ </div>
+ <div class="pure-u-3-5">
+ <h4 style="text-align: right; font-size: 1rem"><%:Networks%></h4>
+ <h4 style="text-align: right;">
+ <%- if self.networks_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/networks")%>'><%- end -%>
+ <span style="font-size: 2rem; color: #2dce89;"><%=self.networks_total%></span>
+ <!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
+ <%- if self.networks_total ~= "-" then -%></a><%- end -%>
+ </h4>
+ </div>
+ </div>
+ </div>
+ <div class="pure-u-1-4">
+ <div class="block pure-g">
+ <div class="pure-u-2-5">
+ <div class="img-con">
+ <svg x="0px" y="0px" viewBox="0 0 55 55" style="enable-background:new 0 0 55 55;" xml:space="preserve">
+ <path
+ d="M52.354,8.51C51.196,4.22,42.577,0,27.5,0C12.423,0,3.803,4.22,2.646,8.51C2.562,8.657,2.5,8.818,2.5,9v0.5V21v0.5V22v11
+ v0.5V34v12c0,0.162,0.043,0.315,0.117,0.451C3.798,51.346,14.364,55,27.5,55c13.106,0,23.655-3.639,24.875-8.516
+ C52.455,46.341,52.5,46.176,52.5,46V34v-0.5V33V22v-0.5V21V9.5V9C52.5,8.818,52.438,8.657,52.354,8.51z M50.421,33.985
+ c-0.028,0.121-0.067,0.241-0.116,0.363c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369
+ c-0.066,0.093-0.141,0.185-0.219,0.277c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236
+ c-0.164,0.143-0.335,0.285-0.526,0.426c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463
+ c-0.068,0.041-0.141,0.08-0.212,0.12c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061
+ c-0.375,0.177-0.767,0.351-1.186,0.519c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17
+ c-0.017,0.002-0.034,0.004-0.051,0.007c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032
+ c-0.605,0.063-1.217,0.121-1.847,0.167c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076
+ C29.137,40.984,28.327,41,27.5,41s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076
+ c-0.293-0.017-0.595-0.028-0.883-0.049c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032
+ c-0.693-0.076-1.368-0.163-2.026-0.259c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17
+ c-0.012-0.004-0.024-0.009-0.036-0.014c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061
+ c-0.336-0.162-0.647-0.328-0.945-0.497c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463
+ c-0.086-0.06-0.175-0.119-0.257-0.18c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236
+ c-0.134-0.13-0.252-0.26-0.363-0.392c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369
+ c-0.054-0.099-0.102-0.198-0.143-0.297c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,33.823,4.5,33.661,4.5,33.5
+ c0-0.113,0.013-0.226,0.031-0.338c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.028,0.026,0.063,0.051,0.092,0.077
+ c0.218,0.192,0.44,0.383,0.69,0.567C9.049,28.786,16.582,31,27.5,31c10.872,0,18.386-2.196,22.169-5.028
+ c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001v7.424c-0.042,0.143-0.056,0.294-0.031,0.445c0.019,0.112,0.031,0.225,0.031,0.338
+ C50.5,33.661,50.459,33.823,50.421,33.985z M50.5,13.293v7.424c-0.042,0.143-0.056,0.294-0.031,0.445
+ c0.019,0.112,0.031,0.225,0.031,0.338c0,0.161-0.041,0.323-0.079,0.485c-0.028,0.121-0.067,0.241-0.116,0.363
+ c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369c-0.066,0.093-0.141,0.185-0.219,0.277
+ c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236c-0.164,0.143-0.335,0.285-0.526,0.426
+ c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463c-0.068,0.041-0.141,0.08-0.212,0.12
+ c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061c-0.375,0.177-0.767,0.351-1.186,0.519
+ c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17c-0.017,0.002-0.034,0.004-0.051,0.007
+ c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032c-0.605,0.063-1.217,0.121-1.847,0.167
+ c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076C29.137,28.984,28.327,29,27.5,29
+ s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076c-0.293-0.017-0.595-0.028-0.883-0.049
+ c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032c-0.693-0.076-1.368-0.163-2.026-0.259
+ c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17c-0.012-0.004-0.024-0.009-0.036-0.014
+ c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061c-0.336-0.162-0.647-0.328-0.945-0.497
+ c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463c-0.086-0.06-0.175-0.119-0.257-0.18
+ c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236c-0.134-0.13-0.252-0.26-0.363-0.392
+ c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369c-0.054-0.099-0.102-0.198-0.143-0.297
+ c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,21.823,4.5,21.661,4.5,21.5c0-0.113,0.013-0.226,0.031-0.338
+ c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.12,0.109,0.257,0.216,0.387,0.324c0.072,0.06,0.139,0.12,0.215,0.18
+ c0.3,0.236,0.624,0.469,0.975,0.696c0.073,0.047,0.155,0.093,0.231,0.14c0.294,0.183,0.605,0.362,0.932,0.538
+ c0.121,0.065,0.242,0.129,0.367,0.193c0.365,0.186,0.748,0.367,1.151,0.542c0.066,0.029,0.126,0.059,0.193,0.087
+ c0.469,0.199,0.967,0.389,1.485,0.573c0.143,0.051,0.293,0.099,0.44,0.149c0.412,0.139,0.838,0.272,1.279,0.401
+ c0.159,0.046,0.315,0.094,0.478,0.138c0.585,0.162,1.189,0.316,1.823,0.458c0.087,0.02,0.181,0.036,0.269,0.055
+ c0.559,0.122,1.139,0.235,1.735,0.341c0.202,0.036,0.407,0.07,0.613,0.104c0.567,0.093,1.151,0.178,1.75,0.256
+ c0.154,0.02,0.301,0.043,0.457,0.062c0.744,0.09,1.514,0.167,2.305,0.233c0.195,0.016,0.398,0.028,0.596,0.042
+ c0.633,0.046,1.28,0.084,1.942,0.114c0.241,0.011,0.481,0.022,0.727,0.031C25.712,18.979,26.59,19,27.5,19s1.788-0.021,2.65-0.05
+ c0.245-0.009,0.485-0.02,0.727-0.031c0.662-0.03,1.309-0.068,1.942-0.114c0.198-0.015,0.4-0.026,0.596-0.042
+ c0.791-0.065,1.561-0.143,2.305-0.233c0.156-0.019,0.303-0.042,0.457-0.062c0.599-0.078,1.182-0.163,1.75-0.256
+ c0.206-0.034,0.411-0.068,0.613-0.104c0.596-0.106,1.176-0.219,1.735-0.341c0.088-0.019,0.182-0.036,0.269-0.055
+ c0.634-0.142,1.238-0.297,1.823-0.458c0.163-0.045,0.319-0.092,0.478-0.138c0.441-0.129,0.867-0.262,1.279-0.401
+ c0.147-0.05,0.297-0.098,0.44-0.149c0.518-0.184,1.017-0.374,1.485-0.573c0.067-0.028,0.127-0.058,0.193-0.087
+ c0.403-0.176,0.786-0.356,1.151-0.542c0.125-0.064,0.247-0.128,0.367-0.193c0.327-0.175,0.638-0.354,0.932-0.538
+ c0.076-0.047,0.158-0.093,0.231-0.14c0.351-0.227,0.675-0.459,0.975-0.696c0.075-0.06,0.142-0.12,0.215-0.18
+ C50.243,13.509,50.38,13.402,50.5,13.293z M27.5,2c13.555,0,23,3.952,23,7.5s-9.445,7.5-23,7.5s-23-3.952-23-7.5S13.945,2,27.5,2z
+ M50.5,45.703c-0.014,0.044-0.024,0.089-0.032,0.135C49.901,49.297,40.536,53,27.5,53S5.099,49.297,4.532,45.838
+ c-0.008-0.045-0.019-0.089-0.032-0.131v-8.414c0.028,0.026,0.063,0.051,0.092,0.077c0.218,0.192,0.44,0.383,0.69,0.567
+ C9.049,40.786,16.582,43,27.5,43c10.872,0,18.386-2.196,22.169-5.028c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001V45.703z" />
+ </svg>
+ </div>
+ </div>
+ <div class="pure-u-3-5">
+ <h4 style="text-align: right; font-size: 1rem"><%:Volumes%></h4>
+ <h4 style="text-align: right;">
+ <%- if self.volumes_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/volumes")%>'><%- end -%>
+ <span style="font-size: 2rem; color: #2dce89;"><%=self.volumes_total%></span>
+ <!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
+ <%- if self.volumes_total ~= "-" then -%></a><%- end -%>
+ </h4>
+ </div>
+ </div>
+ </div>
+</div>