koishi-plugin-docker-control 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/control.js +40 -5
- package/lib/commands/index.js +26 -7
- package/lib/commands/list.js +1 -1
- package/lib/commands/logs.js +1 -1
- package/lib/commands/resources.d.ts +5 -0
- package/lib/commands/resources.js +270 -0
- package/lib/commands/update.d.ts +5 -0
- package/lib/commands/update.js +179 -0
- package/lib/service/node.d.ts +130 -20
- package/lib/service/node.js +706 -43
- package/lib/utils/render.d.ts +51 -1
- package/lib/utils/render.js +268 -2
- package/package.json +3 -2
package/lib/commands/control.js
CHANGED
|
@@ -3,6 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerControlCommands = registerControlCommands;
|
|
4
4
|
const logger_1 = require("../utils/logger");
|
|
5
5
|
const render_1 = require("../utils/render");
|
|
6
|
+
/**
|
|
7
|
+
* 格式化网络流量显示
|
|
8
|
+
*/
|
|
9
|
+
function formatNet(bytes) {
|
|
10
|
+
const num = parseFloat(bytes);
|
|
11
|
+
if (isNaN(num))
|
|
12
|
+
return '-';
|
|
13
|
+
if (num < 1024)
|
|
14
|
+
return bytes + 'B';
|
|
15
|
+
if (num < 1024 * 1024)
|
|
16
|
+
return (num / 1024).toFixed(1) + 'KB';
|
|
17
|
+
return (num / 1024 / 1024).toFixed(2) + 'MB';
|
|
18
|
+
}
|
|
6
19
|
/**
|
|
7
20
|
* 格式化容器搜索结果
|
|
8
21
|
*/
|
|
@@ -35,7 +48,7 @@ function registerControlCommands(ctx, getService, config) {
|
|
|
35
48
|
*/
|
|
36
49
|
ctx
|
|
37
50
|
.command('docker.start <selector> <container>', '启动容器')
|
|
38
|
-
.alias('
|
|
51
|
+
.alias('容器启动', '启动', '容器开启')
|
|
39
52
|
.option('async', '-a 异步执行,不等待结果', { fallback: false })
|
|
40
53
|
.action(async ({ options }, selector, container) => {
|
|
41
54
|
logger_1.commandLogger.debug(`docker.start 被调用: selector=${selector}, container=${container}`);
|
|
@@ -84,7 +97,7 @@ function registerControlCommands(ctx, getService, config) {
|
|
|
84
97
|
*/
|
|
85
98
|
ctx
|
|
86
99
|
.command('docker.stop <selector> <container>', '停止容器')
|
|
87
|
-
.alias('
|
|
100
|
+
.alias('容器停止', '停止', '容器关闭')
|
|
88
101
|
.option('async', '-a 异步执行,不等待结果', { fallback: false })
|
|
89
102
|
.action(async ({ options }, selector, container) => {
|
|
90
103
|
logger_1.commandLogger.debug(`docker.stop 被调用: selector=${selector}, container=${container}`);
|
|
@@ -127,7 +140,7 @@ function registerControlCommands(ctx, getService, config) {
|
|
|
127
140
|
*/
|
|
128
141
|
ctx
|
|
129
142
|
.command('docker.restart <selector> <container>', '重启容器')
|
|
130
|
-
.alias('
|
|
143
|
+
.alias('容器重启', '重启')
|
|
131
144
|
.option('async', '-a 异步执行,不等待结果', { fallback: false })
|
|
132
145
|
.action(async ({ options }, selector, container) => {
|
|
133
146
|
logger_1.commandLogger.debug(`docker.restart 被调用: selector=${selector}, container=${container}`);
|
|
@@ -170,7 +183,7 @@ function registerControlCommands(ctx, getService, config) {
|
|
|
170
183
|
*/
|
|
171
184
|
ctx
|
|
172
185
|
.command('docker.inspect <selector> <container>', '查看容器详情')
|
|
173
|
-
.alias('
|
|
186
|
+
.alias('容器详情', '容器检查', '检查')
|
|
174
187
|
.action(async (_, selector, container) => {
|
|
175
188
|
const service = getService();
|
|
176
189
|
if (!service) {
|
|
@@ -179,8 +192,13 @@ function registerControlCommands(ctx, getService, config) {
|
|
|
179
192
|
try {
|
|
180
193
|
const { node, container: found } = await service.findContainer(selector, container);
|
|
181
194
|
const info = await node.getContainer(found.Id);
|
|
195
|
+
// 获取性能数据和端口映射
|
|
196
|
+
const [stats, ports] = await Promise.all([
|
|
197
|
+
node.getContainerStats(found.Id),
|
|
198
|
+
node.getContainerPorts(found.Id),
|
|
199
|
+
]);
|
|
182
200
|
if (useImageOutput && ctx.puppeteer) {
|
|
183
|
-
const html = (0, render_1.generateInspectHtml)(node.name, info);
|
|
201
|
+
const html = (0, render_1.generateInspectHtml)(node.name, info, stats, ports);
|
|
184
202
|
return await (0, render_1.renderToImage)(ctx, html);
|
|
185
203
|
}
|
|
186
204
|
const lines = [
|
|
@@ -192,6 +210,23 @@ function registerControlCommands(ctx, getService, config) {
|
|
|
192
210
|
`启动时间: ${info.State.StartedAt}`,
|
|
193
211
|
`重启次数: ${info.RestartCount || 0}`,
|
|
194
212
|
];
|
|
213
|
+
// 添加性能数据
|
|
214
|
+
if (stats) {
|
|
215
|
+
lines.push('');
|
|
216
|
+
lines.push('性能监控:');
|
|
217
|
+
lines.push(` CPU: ${stats.cpuPercent}`);
|
|
218
|
+
lines.push(` 内存: ${stats.memoryPercent} (${stats.memoryUsage} / ${stats.memoryLimit})`);
|
|
219
|
+
lines.push(` 网络: ${formatNet(stats.networkIn)} / ${formatNet(stats.networkOut)}`);
|
|
220
|
+
lines.push(` 进程: ${stats.pids}`);
|
|
221
|
+
}
|
|
222
|
+
// 添加端口映射
|
|
223
|
+
if (ports && ports.length > 0) {
|
|
224
|
+
lines.push('');
|
|
225
|
+
lines.push('端口映射:');
|
|
226
|
+
for (const port of ports) {
|
|
227
|
+
lines.push(` ${port}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
195
230
|
if (info.State.Health) {
|
|
196
231
|
lines.push(`健康状态: ${info.State.Health.Status}`);
|
|
197
232
|
}
|
package/lib/commands/index.js
CHANGED
|
@@ -5,6 +5,8 @@ const list_1 = require("./list");
|
|
|
5
5
|
const control_1 = require("./control");
|
|
6
6
|
const logs_1 = require("./logs");
|
|
7
7
|
const compose_1 = require("./compose");
|
|
8
|
+
const resources_1 = require("./resources");
|
|
9
|
+
const update_1 = require("./update");
|
|
8
10
|
const render_1 = require("../utils/render");
|
|
9
11
|
/**
|
|
10
12
|
* 注册所有指令
|
|
@@ -15,6 +17,8 @@ function registerCommands(ctx, getService, config) {
|
|
|
15
17
|
(0, control_1.registerControlCommands)(ctx, getService, config);
|
|
16
18
|
(0, logs_1.registerLogsCommand)(ctx, getService, config);
|
|
17
19
|
(0, compose_1.registerComposeCommand)(ctx, getService, config);
|
|
20
|
+
(0, resources_1.registerResourceCommands)(ctx, getService, config);
|
|
21
|
+
(0, update_1.registerUpdateCommands)(ctx, getService);
|
|
18
22
|
// 注册辅助指令
|
|
19
23
|
registerHelperCommands(ctx, getService, config);
|
|
20
24
|
}
|
|
@@ -26,7 +30,7 @@ function registerHelperCommands(ctx, getService, config) {
|
|
|
26
30
|
/**
|
|
27
31
|
* 查看节点列表
|
|
28
32
|
*/
|
|
29
|
-
ctx.command('docker.nodes', '查看节点').alias('
|
|
33
|
+
ctx.command('docker.nodes', '查看节点').alias('节点列表', '节点').action(async () => {
|
|
30
34
|
const service = getService();
|
|
31
35
|
if (!service) {
|
|
32
36
|
return 'Docker 服务未初始化';
|
|
@@ -58,7 +62,7 @@ function registerHelperCommands(ctx, getService, config) {
|
|
|
58
62
|
*/
|
|
59
63
|
ctx
|
|
60
64
|
.command('docker.node <selector>', '查看节点详情')
|
|
61
|
-
.alias('
|
|
65
|
+
.alias('节点详情', '查看节点')
|
|
62
66
|
.action(async (_, selector) => {
|
|
63
67
|
const service = getService();
|
|
64
68
|
if (!service) {
|
|
@@ -116,7 +120,7 @@ function registerHelperCommands(ctx, getService, config) {
|
|
|
116
120
|
*/
|
|
117
121
|
ctx
|
|
118
122
|
.command('docker.find <container>', '搜索容器')
|
|
119
|
-
.alias('
|
|
123
|
+
.alias('查找容器', '搜索', '查找')
|
|
120
124
|
.option('all', '-a 包含已停止的容器', { fallback: false })
|
|
121
125
|
.action(async ({ options }, container) => {
|
|
122
126
|
const service = getService();
|
|
@@ -145,7 +149,7 @@ function registerHelperCommands(ctx, getService, config) {
|
|
|
145
149
|
*/
|
|
146
150
|
ctx
|
|
147
151
|
.command('docker.exec <container> <cmd>', '在容器中执行命令')
|
|
148
|
-
.alias('
|
|
152
|
+
.alias('容器执行', '执行')
|
|
149
153
|
.option('node', '-n <node> 指定节点', { fallback: '' })
|
|
150
154
|
.action(async ({ options }, container, cmd) => {
|
|
151
155
|
const service = getService();
|
|
@@ -183,7 +187,7 @@ function registerHelperCommands(ctx, getService, config) {
|
|
|
183
187
|
/**
|
|
184
188
|
* 查看帮助
|
|
185
189
|
*/
|
|
186
|
-
ctx.command('docker.help', '查看帮助').alias('
|
|
190
|
+
ctx.command('docker.help', '查看帮助').alias('帮助').action(async () => {
|
|
187
191
|
return [
|
|
188
192
|
'=== Docker Control 帮助 ===',
|
|
189
193
|
'',
|
|
@@ -191,8 +195,8 @@ function registerHelperCommands(ctx, getService, config) {
|
|
|
191
195
|
' docker.nodes - 查看节点列表',
|
|
192
196
|
' docker.node <节点> - 查看节点详情',
|
|
193
197
|
'',
|
|
194
|
-
'【容器操作】
|
|
195
|
-
' docker.ls <节点> - 列出容器',
|
|
198
|
+
'【容器操作】',
|
|
199
|
+
' docker.ls <节点> - 列出容器 [-f image]',
|
|
196
200
|
' docker.start <节点> <容器> - 启动容器',
|
|
197
201
|
' docker.stop <节点> <容器> - 停止容器',
|
|
198
202
|
' docker.restart <节点> <容器> - 重启容器',
|
|
@@ -200,11 +204,26 @@ function registerHelperCommands(ctx, getService, config) {
|
|
|
200
204
|
' docker.inspect <节点> <容器> - 查看容器详情',
|
|
201
205
|
' docker.exec <节点> <容器> <命令> - 在容器内执行命令',
|
|
202
206
|
'',
|
|
207
|
+
'【更新操作】',
|
|
208
|
+
' docker.check <节点> <容器> - 检查镜像更新',
|
|
209
|
+
' docker.update <节点> <容器> [-b] - 更新容器 (-b 备份)',
|
|
210
|
+
' docker.backup <节点> <容器> [tag] - 备份容器为镜像',
|
|
211
|
+
' docker.set <节点> <容器> -e KEY=VALUE - 修改环境变量',
|
|
212
|
+
'',
|
|
213
|
+
'【资源操作】',
|
|
214
|
+
' docker.images <节点> - 查看镜像列表 [-f image]',
|
|
215
|
+
' docker.networks <节点> - 查看网络列表 [-f image]',
|
|
216
|
+
' docker.volumes <节点> - 查看存储卷列表 [-f image]',
|
|
217
|
+
'',
|
|
203
218
|
'【节点选择器】',
|
|
204
219
|
' all - 所有节点',
|
|
205
220
|
' @标签 - 指定标签的节点',
|
|
206
221
|
' 节点ID/名称 - 指定单个节点',
|
|
207
222
|
'',
|
|
223
|
+
'【输出格式】',
|
|
224
|
+
' -f simple - 文本格式(默认)',
|
|
225
|
+
' -f image - 图片格式(需要 puppeteer 插件)',
|
|
226
|
+
'',
|
|
208
227
|
'【通知事件类型】',
|
|
209
228
|
' container.start/stop/restart/die',
|
|
210
229
|
' container.health_status',
|
package/lib/commands/list.js
CHANGED
|
@@ -8,7 +8,7 @@ function registerListCommand(ctx, getService, config) {
|
|
|
8
8
|
const useImageOutput = config?.imageOutput === true;
|
|
9
9
|
ctx
|
|
10
10
|
.command('docker.ls [selector]', '列出容器')
|
|
11
|
-
.alias('
|
|
11
|
+
.alias('容器列表', '查看容器', '列表')
|
|
12
12
|
.option('all', '-a 列出所有容器,包括已停止', { fallback: false })
|
|
13
13
|
.option('format', '-f <format> 输出格式: simple|detail|json|image', {
|
|
14
14
|
fallback: null, // 由 config.imageOutput 决定
|
package/lib/commands/logs.js
CHANGED
|
@@ -16,7 +16,7 @@ function registerLogsCommand(ctx, getService, config) {
|
|
|
16
16
|
const useImageOutput = config?.imageOutput === true;
|
|
17
17
|
ctx
|
|
18
18
|
.command('docker.logs <node> <container>', '查看容器日志')
|
|
19
|
-
.alias('
|
|
19
|
+
.alias('容器日志', '查看日志', '日志')
|
|
20
20
|
.option('lines', '-n <lines:number> 显示最后 N 行')
|
|
21
21
|
.option('timestamp', '-t 显示时间戳')
|
|
22
22
|
.option('all', '-a 显示全部(不截断)')
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerResourceCommands = registerResourceCommands;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const render_1 = require("../utils/render");
|
|
6
|
+
function registerResourceCommands(ctx, getService, config) {
|
|
7
|
+
// 检查是否启用了图片输出
|
|
8
|
+
const useImageOutput = config?.imageOutput === true;
|
|
9
|
+
/**
|
|
10
|
+
* 查看镜像列表
|
|
11
|
+
*/
|
|
12
|
+
ctx
|
|
13
|
+
.command('docker.images [selector]', '查看镜像列表')
|
|
14
|
+
.alias('镜像列表', '查看镜像', '容器镜像')
|
|
15
|
+
.option('format', '-f <format> 输出格式: simple|image', {
|
|
16
|
+
fallback: null, // 由 config.imageOutput 决定
|
|
17
|
+
})
|
|
18
|
+
.action(async ({ options }, selector) => {
|
|
19
|
+
logger_1.commandLogger.debug(`docker.images 被调用: selector=${selector}, format=${options.format}`);
|
|
20
|
+
const service = getService();
|
|
21
|
+
if (!service) {
|
|
22
|
+
logger_1.commandLogger.debug('服务未初始化');
|
|
23
|
+
return 'Docker 服务未初始化';
|
|
24
|
+
}
|
|
25
|
+
if (!selector) {
|
|
26
|
+
return '请指定节点名称、ID 或标签,或使用 "all" 列出全部镜像\n例如: docker.images @web 或 docker.images all';
|
|
27
|
+
}
|
|
28
|
+
const format = options.format || (useImageOutput ? 'image' : 'simple');
|
|
29
|
+
// 图片渲染模式
|
|
30
|
+
if (format === 'image') {
|
|
31
|
+
logger_1.commandLogger.debug('使用图片渲染模式');
|
|
32
|
+
if (!ctx.puppeteer) {
|
|
33
|
+
return '错误: 未安装 koishi-plugin-puppeteer 插件,无法使用图片渲染';
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const nodes = service.getNodesBySelector(selector);
|
|
37
|
+
if (nodes.length === 0) {
|
|
38
|
+
return `未找到节点: ${selector}`;
|
|
39
|
+
}
|
|
40
|
+
const results = [];
|
|
41
|
+
for (const node of nodes) {
|
|
42
|
+
if (node.status !== 'connected')
|
|
43
|
+
continue;
|
|
44
|
+
const images = await node.listImages();
|
|
45
|
+
results.push({ node, images });
|
|
46
|
+
}
|
|
47
|
+
if (results.length === 0) {
|
|
48
|
+
return '所有指定节点均未连接';
|
|
49
|
+
}
|
|
50
|
+
const html = (0, render_1.generateImagesHtml)(results, `镜像列表 (${selector})`);
|
|
51
|
+
return await (0, render_1.renderToImage)(ctx, html);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
logger_1.commandLogger.error(`图片渲染失败: ${e.message}`);
|
|
55
|
+
return `错误: ${e.message}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// 文字模式
|
|
59
|
+
try {
|
|
60
|
+
const nodes = service.getNodesBySelector(selector);
|
|
61
|
+
if (nodes.length === 0) {
|
|
62
|
+
return `未找到节点: ${selector}`;
|
|
63
|
+
}
|
|
64
|
+
const lines = [];
|
|
65
|
+
for (const node of nodes) {
|
|
66
|
+
if (node.status !== 'connected') {
|
|
67
|
+
lines.push(`=== ${node.name} ===`);
|
|
68
|
+
lines.push(' (未连接)');
|
|
69
|
+
lines.push('');
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const images = await node.listImages();
|
|
73
|
+
lines.push(`=== ${node.name} (${images.length} 个镜像) ===`);
|
|
74
|
+
if (images.length === 0) {
|
|
75
|
+
lines.push(' (无镜像)');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
for (const img of images) {
|
|
79
|
+
const shortId = img.Id.slice(0, 12);
|
|
80
|
+
const tag = img.Tag === '<none>' ? '<none>' : img.Tag;
|
|
81
|
+
lines.push(` ${img.Repository}:${tag}`);
|
|
82
|
+
lines.push(` ID: ${shortId} | 大小: ${img.Size} | 创建: ${img.Created}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
lines.push('');
|
|
86
|
+
}
|
|
87
|
+
return lines.join('\n').trim();
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
logger_1.commandLogger.error(`列出镜像失败: ${e.message}`);
|
|
91
|
+
return `错误: ${e.message}`;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* 查看网络列表
|
|
96
|
+
*/
|
|
97
|
+
ctx
|
|
98
|
+
.command('docker.networks [selector]', '查看网络列表')
|
|
99
|
+
.alias('网络列表', '查看网络', '容器网络')
|
|
100
|
+
.option('format', '-f <format> 输出格式: simple|image', {
|
|
101
|
+
fallback: null, // 由 config.imageOutput 决定
|
|
102
|
+
})
|
|
103
|
+
.action(async ({ options }, selector) => {
|
|
104
|
+
logger_1.commandLogger.debug(`docker.networks 被调用: selector=${selector}, format=${options.format}`);
|
|
105
|
+
const service = getService();
|
|
106
|
+
if (!service) {
|
|
107
|
+
logger_1.commandLogger.debug('服务未初始化');
|
|
108
|
+
return 'Docker 服务未初始化';
|
|
109
|
+
}
|
|
110
|
+
if (!selector) {
|
|
111
|
+
return '请指定节点名称、ID 或标签,或使用 "all" 列出全部网络\n例如: docker.networks @web 或 docker.networks all';
|
|
112
|
+
}
|
|
113
|
+
const format = options.format || (useImageOutput ? 'image' : 'simple');
|
|
114
|
+
// 图片渲染模式
|
|
115
|
+
if (format === 'image') {
|
|
116
|
+
logger_1.commandLogger.debug('使用图片渲染模式');
|
|
117
|
+
if (!ctx.puppeteer) {
|
|
118
|
+
return '错误: 未安装 koishi-plugin-puppeteer 插件,无法使用图片渲染';
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const nodes = service.getNodesBySelector(selector);
|
|
122
|
+
if (nodes.length === 0) {
|
|
123
|
+
return `未找到节点: ${selector}`;
|
|
124
|
+
}
|
|
125
|
+
const results = [];
|
|
126
|
+
for (const node of nodes) {
|
|
127
|
+
if (node.status !== 'connected')
|
|
128
|
+
continue;
|
|
129
|
+
const networks = await node.listNetworks();
|
|
130
|
+
results.push({ node, networks });
|
|
131
|
+
}
|
|
132
|
+
if (results.length === 0) {
|
|
133
|
+
return '所有指定节点均未连接';
|
|
134
|
+
}
|
|
135
|
+
const html = (0, render_1.generateNetworksHtml)(results, `网络列表 (${selector})`);
|
|
136
|
+
return await (0, render_1.renderToImage)(ctx, html);
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
logger_1.commandLogger.error(`图片渲染失败: ${e.message}`);
|
|
140
|
+
return `错误: ${e.message}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// 文字模式
|
|
144
|
+
try {
|
|
145
|
+
const nodes = service.getNodesBySelector(selector);
|
|
146
|
+
if (nodes.length === 0) {
|
|
147
|
+
return `未找到节点: ${selector}`;
|
|
148
|
+
}
|
|
149
|
+
const lines = [];
|
|
150
|
+
for (const node of nodes) {
|
|
151
|
+
if (node.status !== 'connected') {
|
|
152
|
+
lines.push(`=== ${node.name} ===`);
|
|
153
|
+
lines.push(' (未连接)');
|
|
154
|
+
lines.push('');
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const networks = await node.listNetworks();
|
|
158
|
+
lines.push(`=== ${node.name} (${networks.length} 个网络) ===`);
|
|
159
|
+
if (networks.length === 0) {
|
|
160
|
+
lines.push(' (无网络)');
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
for (const net of networks) {
|
|
164
|
+
const shortId = net.Id.slice(0, 12);
|
|
165
|
+
lines.push(` ${net.Name}`);
|
|
166
|
+
lines.push(` ID: ${shortId} | 驱动: ${net.Driver} | 范围: ${net.Scope}`);
|
|
167
|
+
if (net.Subnet !== '-') {
|
|
168
|
+
lines.push(` 子网: ${net.Subnet} | 网关: ${net.Gateway}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
lines.push('');
|
|
173
|
+
}
|
|
174
|
+
return lines.join('\n').trim();
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
logger_1.commandLogger.error(`列出网络失败: ${e.message}`);
|
|
178
|
+
return `错误: ${e.message}`;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
/**
|
|
182
|
+
* 查看存储卷列表
|
|
183
|
+
*/
|
|
184
|
+
ctx
|
|
185
|
+
.command('docker.volumes [selector]', '查看存储卷列表')
|
|
186
|
+
.alias('存储卷列表', '查看存储卷', '容器存储卷', '容器卷')
|
|
187
|
+
.option('format', '-f <format> 输出格式: simple|image', {
|
|
188
|
+
fallback: null, // 由 config.imageOutput 决定
|
|
189
|
+
})
|
|
190
|
+
.action(async ({ options }, selector) => {
|
|
191
|
+
logger_1.commandLogger.debug(`docker.volumes 被调用: selector=${selector}, format=${options.format}`);
|
|
192
|
+
const service = getService();
|
|
193
|
+
if (!service) {
|
|
194
|
+
logger_1.commandLogger.debug('服务未初始化');
|
|
195
|
+
return 'Docker 服务未初始化';
|
|
196
|
+
}
|
|
197
|
+
if (!selector) {
|
|
198
|
+
return '请指定节点名称、ID 或标签,或使用 "all" 列出全部存储卷\n例如: docker.volumes @web 或 docker.volumes all';
|
|
199
|
+
}
|
|
200
|
+
const format = options.format || (useImageOutput ? 'image' : 'simple');
|
|
201
|
+
// 图片渲染模式
|
|
202
|
+
if (format === 'image') {
|
|
203
|
+
logger_1.commandLogger.debug('使用图片渲染模式');
|
|
204
|
+
if (!ctx.puppeteer) {
|
|
205
|
+
return '错误: 未安装 koishi-plugin-puppeteer 插件,无法使用图片渲染';
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const nodes = service.getNodesBySelector(selector);
|
|
209
|
+
if (nodes.length === 0) {
|
|
210
|
+
return `未找到节点: ${selector}`;
|
|
211
|
+
}
|
|
212
|
+
const results = [];
|
|
213
|
+
for (const node of nodes) {
|
|
214
|
+
if (node.status !== 'connected')
|
|
215
|
+
continue;
|
|
216
|
+
const volumes = await node.listVolumes();
|
|
217
|
+
results.push({ node, volumes });
|
|
218
|
+
}
|
|
219
|
+
if (results.length === 0) {
|
|
220
|
+
return '所有指定节点均未连接';
|
|
221
|
+
}
|
|
222
|
+
const html = (0, render_1.generateVolumesHtml)(results, `存储卷列表 (${selector})`);
|
|
223
|
+
return await (0, render_1.renderToImage)(ctx, html);
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
logger_1.commandLogger.error(`图片渲染失败: ${e.message}`);
|
|
227
|
+
return `错误: ${e.message}`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// 文字模式
|
|
231
|
+
try {
|
|
232
|
+
const nodes = service.getNodesBySelector(selector);
|
|
233
|
+
if (nodes.length === 0) {
|
|
234
|
+
return `未找到节点: ${selector}`;
|
|
235
|
+
}
|
|
236
|
+
const lines = [];
|
|
237
|
+
for (const node of nodes) {
|
|
238
|
+
if (node.status !== 'connected') {
|
|
239
|
+
lines.push(`=== ${node.name} ===`);
|
|
240
|
+
lines.push(' (未连接)');
|
|
241
|
+
lines.push('');
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const volumes = await node.listVolumes();
|
|
245
|
+
lines.push(`=== ${node.name} (${volumes.length} 个存储卷) ===`);
|
|
246
|
+
if (volumes.length === 0) {
|
|
247
|
+
lines.push(' (无存储卷)');
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
for (const vol of volumes) {
|
|
251
|
+
lines.push(` ${vol.Name}`);
|
|
252
|
+
lines.push(` 驱动: ${vol.Driver} | 范围: ${vol.Scope}`);
|
|
253
|
+
if (vol.Mountpoint !== '-') {
|
|
254
|
+
lines.push(` 挂载点: ${vol.Mountpoint}`);
|
|
255
|
+
}
|
|
256
|
+
if (vol.Size !== '-') {
|
|
257
|
+
lines.push(` 大小: ${vol.Size}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
lines.push('');
|
|
262
|
+
}
|
|
263
|
+
return lines.join('\n').trim();
|
|
264
|
+
}
|
|
265
|
+
catch (e) {
|
|
266
|
+
logger_1.commandLogger.error(`列出存储卷失败: ${e.message}`);
|
|
267
|
+
return `错误: ${e.message}`;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|