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.
@@ -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('docker启动', '容器启动', 'docker开启', '容器开启')
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('docker停止', '容器停止', 'docker关闭', '容器关闭')
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('docker重启', '容器重启')
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('docker详情', '容器详情', 'docker检查', '容器检查')
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
  }
@@ -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('docker节点', '容器节点').action(async () => {
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('docker节点详情', '容器节点详情')
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('docker查找', '容器查找', 'docker搜索', '容器搜索')
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('docker执行', '容器执行', 'dockerexec', 'dockercmd', 'docker命令', '容器命令')
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('docker帮助', 'docker帮助', '容器帮助').action(async () => {
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',
@@ -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('docker列表', '容器列表', 'dockercs', '容器查看', 'docker查看')
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 决定
@@ -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('docker日志', '容器日志', 'docker查看日志', '容器查看日志', 'dockerlogs')
19
+ .alias('容器日志', '查看日志', '日志')
20
20
  .option('lines', '-n <lines:number> 显示最后 N 行')
21
21
  .option('timestamp', '-t 显示时间戳')
22
22
  .option('all', '-a 显示全部(不截断)')
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 资源列表指令(镜像、网络、存储卷)
3
+ */
4
+ import { Context } from 'koishi';
5
+ export declare function registerResourceCommands(ctx: Context, getService: () => any, config?: any): void;
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 容器更新相关指令(检查更新、备份、修改环境变量)
3
+ */
4
+ import { Context } from 'koishi';
5
+ export declare function registerUpdateCommands(ctx: Context, getService: () => any): void;