mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-12 03:14:48 +08:00
454 lines
12 KiB
Diff
454 lines
12 KiB
Diff
--- a/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json
|
|
+++ b/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json
|
|
@@ -1,7 +1,7 @@
|
|
{
|
|
- "admin/services/dockerman": {
|
|
- "title": "Dockerman JS",
|
|
- "order": "60",
|
|
+ "admin/dockerman": {
|
|
+ "title": "Docker",
|
|
+ "order": "40",
|
|
"action": {
|
|
"type": "firstchild"
|
|
},
|
|
@@ -15,7 +15,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/overview": {
|
|
+ "admin/dockerman/overview": {
|
|
"title": "Overview",
|
|
"order": 1,
|
|
"action": {
|
|
@@ -27,7 +27,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/configuration": {
|
|
+ "admin/dockerman/configuration": {
|
|
"title": "Configuration",
|
|
"order": 2,
|
|
"action": {
|
|
@@ -39,17 +39,17 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/container/archive/*": {
|
|
+ "admin/dockerman/container/archive/*": {
|
|
"action": {
|
|
"type": "alias",
|
|
- "path": "admin/services/dockerman/containers"
|
|
+ "path": "admin/dockerman/containers"
|
|
},
|
|
"depends": {
|
|
"acl": [ "luci-app-dockerman" ]
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/container/archive/put/*": {
|
|
+ "admin/dockerman/container/archive/put/*": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -64,7 +64,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/container/archive/get/*": {
|
|
+ "admin/dockerman/container/archive/get/*": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -79,7 +79,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/container/export/*": {
|
|
+ "admin/dockerman/container/export/*": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -94,7 +94,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/container/*": {
|
|
+ "admin/dockerman/container/*": {
|
|
"title_hide": "Container",
|
|
"action": {
|
|
"type": "view",
|
|
@@ -105,7 +105,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/containers": {
|
|
+ "admin/dockerman/containers": {
|
|
"title": "Containers",
|
|
"order": 3,
|
|
"action": {
|
|
@@ -117,7 +117,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/containers/prune": {
|
|
+ "admin/dockerman/containers/prune": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -133,17 +133,17 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/container": {
|
|
+ "admin/dockerman/container": {
|
|
"action": {
|
|
"type": "alias",
|
|
- "path": "admin/services/dockerman/containers"
|
|
+ "path": "admin/dockerman/containers"
|
|
},
|
|
"depends": {
|
|
"acl": [ "luci-app-dockerman" ]
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/container_new": {
|
|
+ "admin/dockerman/container_new": {
|
|
"title_hide": "Container",
|
|
"action": {
|
|
"type": "view",
|
|
@@ -154,7 +154,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/images/build": {
|
|
+ "admin/dockerman/images/build": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -170,7 +170,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/images/build/prune": {
|
|
+ "admin/dockerman/images/build/prune": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -186,7 +186,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/images/get/*": {
|
|
+ "admin/dockerman/images/get/*": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -201,7 +201,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/images/load": {
|
|
+ "admin/dockerman/images/load": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -217,7 +217,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/images/prune": {
|
|
+ "admin/dockerman/images/prune": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -233,7 +233,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/images": {
|
|
+ "admin/dockerman/images": {
|
|
"title": "Images",
|
|
"order": 4,
|
|
"action": {
|
|
@@ -242,7 +242,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/images/create": {
|
|
+ "admin/dockerman/images/create": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -258,7 +258,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/images/push/*": {
|
|
+ "admin/dockerman/images/push/*": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -274,17 +274,17 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/image": {
|
|
+ "admin/dockerman/image": {
|
|
"action": {
|
|
"type": "alias",
|
|
- "path": "admin/services/dockerman/images"
|
|
+ "path": "admin/dockerman/images"
|
|
},
|
|
"depends": {
|
|
"acl": [ "luci-app-dockerman" ]
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/network/*": {
|
|
+ "admin/dockerman/network/*": {
|
|
"title_hide": "Network",
|
|
"action": {
|
|
"type": "view",
|
|
@@ -295,7 +295,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/networks": {
|
|
+ "admin/dockerman/networks": {
|
|
"title": "Networks",
|
|
"order": 5,
|
|
"action": {
|
|
@@ -307,7 +307,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/networks/prune": {
|
|
+ "admin/dockerman/networks/prune": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -323,7 +323,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/network_new": {
|
|
+ "admin/dockerman/network_new": {
|
|
"title_hide": "Network",
|
|
"action": {
|
|
"type": "view",
|
|
@@ -334,17 +334,17 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/network": {
|
|
+ "admin/dockerman/network": {
|
|
"action": {
|
|
"type": "alias",
|
|
- "path": "admin/services/dockerman/networks"
|
|
+ "path": "admin/dockerman/networks"
|
|
},
|
|
"depends": {
|
|
"acl": [ "luci-app-dockerman" ]
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/volumes": {
|
|
+ "admin/dockerman/volumes": {
|
|
"title": "Volumes",
|
|
"order": 6,
|
|
"action": {
|
|
@@ -356,7 +356,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/volumes/prune": {
|
|
+ "admin/dockerman/volumes/prune": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -372,7 +372,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/docker/events": {
|
|
+ "admin/dockerman/docker/events": {
|
|
"action": {
|
|
"type": "function",
|
|
"module": "luci.controller.docker",
|
|
@@ -388,7 +388,7 @@
|
|
}
|
|
},
|
|
|
|
- "admin/services/dockerman/events": {
|
|
+ "admin/dockerman/events": {
|
|
"title": "Events",
|
|
"order": 7,
|
|
"action": {
|
|
|
|
--- a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js
|
|
+++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js
|
|
@@ -437,7 +437,7 @@ const dv = view.extend({
|
|
outputText: '', // Initialize output text
|
|
|
|
get dockerman_url() {
|
|
- return L.url('admin/services/dockerman');
|
|
+ return L.url('admin/dockerman');
|
|
},
|
|
|
|
parseHeaders(headers, array) {
|
|
@@ -1280,13 +1280,14 @@ const ansiToHtml = function(text) {
|
|
// Escape HTML special characters
|
|
const escapeHtml = (str) => {
|
|
const map = {
|
|
+ ' ': ' ',
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
- return str.replace(/[&<>"']/g, m => map[m]);
|
|
+ return str.replace(/[&<>"' ]/g, m => map[m]);
|
|
};
|
|
|
|
// Split by ANSI escape sequences and process
|
|
|
|
--- /dev/null
|
|
+++ b/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman
|
|
@@ -0,0 +1,8 @@
|
|
+#!/bin/sh
|
|
+
|
|
+uci -q batch <<-EOF >/dev/null
|
|
+ set uhttpd.main.script_timeout="360"
|
|
+ commit uhttpd
|
|
+EOF
|
|
+
|
|
+/etc/init.d/dockerd restart
|
|
|
|
--- a/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js
|
|
+++ b/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js
|
|
@@ -66,6 +66,7 @@ return L.view.extend({
|
|
o.value('https://docker.io');
|
|
o.value('https://ghcr.io');
|
|
o.value('https://hub-mirror.c.163.com');
|
|
+ o.value('https://ustc-edu-cn.mirror.aliyuncs.com');
|
|
|
|
o = s.taboption('globals', form.ListValue, 'log_level',
|
|
_('Log Level'),
|
|
|
|
--- a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/api.js
|
|
+++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/api.js
|
|
@@ -116,6 +116,22 @@ function processLines(buffer, onChunk) {
|
|
return buffer;
|
|
}
|
|
|
|
+// Parse the docker multiplexed stream format, https://docs.docker.com/reference/api/engine/version/v1.43/#stream-format
|
|
+function parse_multiplexed_stream(buffer) {
|
|
+ const output = [];
|
|
+
|
|
+ while (buffer.length > 0) {
|
|
+ if (buffer.length < 8) break; // Not enough data for header
|
|
+ let stream_type = buffer.charCodeAt(0);
|
|
+ let payload_length = (buffer.charCodeAt(4) << 24) | (buffer.charCodeAt(5) << 16) | (buffer.charCodeAt(6) << 8) | buffer.charCodeAt(7);
|
|
+ if (buffer.length < 8 + payload_length) break; // Not enough data for payload
|
|
+ let payload = buffer.slice(8, 8 + payload_length);
|
|
+ output.push({ type: stream_type, payload: payload });
|
|
+ buffer = buffer.slice(8 + payload_length);
|
|
+ }
|
|
+
|
|
+ return output;
|
|
+}
|
|
|
|
function call_docker(method, path, options = {}) {
|
|
return loadPromise.then(() => {
|
|
@@ -228,20 +244,24 @@ function call_docker(method, path, options = {}) {
|
|
let parsed = safeText || text;
|
|
const contentType = response.headers.get('content-type') || '';
|
|
|
|
- // Try normal JSON parse first
|
|
- try {
|
|
- parsed = JSON.parse(text);
|
|
- } catch (err) {
|
|
- // If the payload is newline-delimited JSON (Docker events), split and parse each line
|
|
- if (['application/json',
|
|
- 'application/x-ndjson',
|
|
- 'application/json-seq'].includes(contentType) || safeText.includes('\n')) {
|
|
- const lines = safeText.split(/\r?\n/).filter(Boolean);
|
|
- try {
|
|
- parsed = lines.map(l => JSON.parse(l));
|
|
- } catch (err2) {
|
|
- // Fall back to raw text if parsing fails
|
|
- parsed = text;
|
|
+ if ("application/vnd.docker.multiplexed-stream" == contentType) {
|
|
+ parsed = parse_multiplexed_stream(safeText);
|
|
+ } else {
|
|
+ // Try normal JSON parse first
|
|
+ try {
|
|
+ parsed = JSON.parse(text);
|
|
+ } catch (err) {
|
|
+ // If the payload is newline-delimited JSON (Docker events), split and parse each line
|
|
+ if (['application/json',
|
|
+ 'application/x-ndjson',
|
|
+ 'application/json-seq'].includes(contentType) || safeText.includes('\n')) {
|
|
+ const lines = safeText.split(/\r?\n/).filter(Boolean);
|
|
+ try {
|
|
+ parsed = lines.map(l => JSON.parse(l));
|
|
+ } catch (err2) {
|
|
+ // Fall back to raw text if parsing fails
|
|
+ parsed = text;
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
|
|
--- a/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js
|
|
+++ b/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js
|
|
@@ -1685,7 +1685,10 @@ return dm2.dv.extend({
|
|
return false;
|
|
}
|
|
|
|
- const logText = response?.body || _('No logs available');
|
|
+ let logText = response?.body || _('No logs available');
|
|
+ if ("application/vnd.docker.multiplexed-stream" == response?.headers['content-type']) {
|
|
+ logText = (response?.body || []).map((frame)=>frame.payload).join('');
|
|
+ }
|
|
// Convert ANSI codes to HTML and set innerHTML
|
|
logsDiv.innerHTML = dm2.ansiToHtml(logText);
|
|
logsDiv.scrollTop = logsDiv.scrollHeight;
|
|
|
|
--- a/luci-app-dockerman/root/usr/share/rpcd/ucode/docker_rpc.uc
|
|
+++ b/luci-app-dockerman/root/usr/share/rpcd/ucode/docker_rpc.uc
|
|
@@ -100,6 +100,23 @@ function coerce_values_to_string(obj) {
|
|
return obj;
|
|
};
|
|
|
|
+// Parse the docker multiplexed stream format, https://docs.docker.com/reference/api/engine/version/v1.43/#stream-format
|
|
+function parse_multiplexed_stream(buffer) {
|
|
+ let output = [];
|
|
+
|
|
+ while (length(buffer) > 0) {
|
|
+ if (length(buffer) < 8) break; // Not enough data for header
|
|
+ let stream_type = ord(buffer, 0);
|
|
+ let payload_length = ord(buffer, 4) << 24 | ord(buffer, 5) << 16 | ord(buffer, 6) << 8 | ord(buffer, 7);
|
|
+ if (length(buffer) < 8 + payload_length) break; // Not enough data for payload
|
|
+ let payload = substr(buffer, 8, payload_length);
|
|
+ push(output, { type: stream_type, payload: payload });
|
|
+ buffer = substr(buffer, 8 + payload_length);
|
|
+ }
|
|
+
|
|
+ return output;
|
|
+};
|
|
+
|
|
function call_docker(method, path, options) {
|
|
options = options || {};
|
|
const headers = options.headers || {};
|
|
@@ -277,6 +294,8 @@ function call_docker(method, path, options) {
|
|
} else {
|
|
response_body = data;
|
|
}
|
|
+ } else if (response_headers['content-type'] === 'application/vnd.docker.multiplexed-stream' && response_body) {
|
|
+ response_body = parse_multiplexed_stream(response_body);
|
|
}
|
|
|
|
return {
|