koishi-plugin-docker-control 0.1.4 → 0.1.6

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.
@@ -10,13 +10,19 @@ const permission_check_1 = require("../utils/permission-check");
10
10
  */
11
11
  function formatNet(bytes) {
12
12
  const num = parseFloat(bytes);
13
- if (isNaN(num))
14
- return '-';
13
+ if (isNaN(num)) {
14
+ // 如果无法解析为数字,可能已经包含单位,直接返回
15
+ return bytes;
16
+ }
17
+ if (num === 0)
18
+ return '0B';
15
19
  if (num < 1024)
16
- return bytes + 'B';
20
+ return num.toFixed(0) + 'B';
17
21
  if (num < 1024 * 1024)
18
- return (num / 1024).toFixed(1) + 'KB';
19
- return (num / 1024 / 1024).toFixed(2) + 'MB';
22
+ return (num / 1024).toFixed(2) + 'KB';
23
+ if (num < 1024 * 1024 * 1024)
24
+ return (num / 1024 / 1024).toFixed(2) + 'MB';
25
+ return (num / 1024 / 1024 / 1024).toFixed(2) + 'GB';
20
26
  }
21
27
  /**
22
28
  * 格式化容器搜索结果
@@ -271,7 +277,8 @@ function registerControlCommands(ctx, getService, config) {
271
277
  lines.push('性能监控:');
272
278
  lines.push(` CPU: ${stats.cpuPercent}`);
273
279
  lines.push(` 内存: ${stats.memoryPercent} (${stats.memoryUsage} / ${stats.memoryLimit})`);
274
- lines.push(` 网络: ${formatNet(stats.networkIn)} / ${formatNet(stats.networkOut)}`);
280
+ lines.push(` 网络 ↓: ${formatNet(stats.networkIn)}`);
281
+ lines.push(` 网络 ↑: ${formatNet(stats.networkOut)}`);
275
282
  lines.push(` 进程: ${stats.pids}`);
276
283
  }
277
284
  // 添加端口映射
@@ -17,6 +17,8 @@ export declare class MonitorManager {
17
17
  private config;
18
18
  /** 容器状态映射: nodeId -> containerId -> State */
19
19
  private states;
20
+ /** 容器名称映射: nodeId -> containerName -> containerId,用于容器更新时跨容器ID追踪 */
21
+ private nameIndex;
20
22
  /** 全局回调 */
21
23
  private callback?;
22
24
  constructor(config?: {
@@ -34,5 +36,9 @@ export declare class MonitorManager {
34
36
  onProcessedEvent(callback: (event: ProcessedEvent) => void): () => void;
35
37
  private emit;
36
38
  private getContainerState;
39
+ private getContainerStateById;
40
+ private getContainerIdByName;
41
+ private removeContainerState;
42
+ private clearNameIndex;
37
43
  private cleanHistory;
38
44
  }
@@ -7,6 +7,8 @@ class MonitorManager {
7
7
  this.config = config;
8
8
  /** 容器状态映射: nodeId -> containerId -> State */
9
9
  this.states = new Map();
10
+ /** 容器名称映射: nodeId -> containerName -> containerId,用于容器更新时跨容器ID追踪 */
11
+ this.nameIndex = new Map();
10
12
  }
11
13
  /**
12
14
  * 处理原始 Docker 事件
@@ -20,8 +22,8 @@ class MonitorManager {
20
22
  return;
21
23
  const containerId = event.Actor.ID;
22
24
  const containerName = event.Actor.Attributes?.name || 'unknown';
23
- // 获取容器状态存储
24
- const state = this.getContainerState(node.id, containerId);
25
+ // 获取容器状态存储(同时更新名称索引)
26
+ const state = this.getContainerState(node.id, containerId, containerName);
25
27
  const now = Date.now();
26
28
  // ---------------------------------------------------------
27
29
  // 0. 屏蔽 restart 冗余事件
@@ -71,6 +73,8 @@ class MonitorManager {
71
73
  logger_1.monitorLogger.debug(`[${node.name}] ${containerName} 已停止 (${action}),等待 ${debounceWait}ms...`);
72
74
  state.stopTimer = setTimeout(() => {
73
75
  state.stopTimer = undefined;
76
+ // 清理名称索引
77
+ this.clearNameIndex(node.id, containerName);
74
78
  // 只有定时器真正走完了,才发送通知
75
79
  this.emit({
76
80
  eventType: `container.die`, // 统一使用 die
@@ -86,6 +90,18 @@ class MonitorManager {
86
90
  else if (action === 'start' || action === 'restart') {
87
91
  // [关键] 记录启动时间,用于屏蔽后续的 restart
88
92
  state.lastStartTime = now;
93
+ // [新功能] 检查是否有同名的旧容器正在等待防抖
94
+ const oldContainerId = this.getContainerIdByName(node.id, containerName);
95
+ if (oldContainerId && oldContainerId !== containerId) {
96
+ const oldState = this.getContainerStateById(node.id, oldContainerId);
97
+ if (oldState?.stopTimer) {
98
+ clearTimeout(oldState.stopTimer);
99
+ oldState.stopTimer = undefined;
100
+ logger_1.monitorLogger.info(`[${node.name}] ${containerName} 容器更新:取消旧容器 ${oldContainerId.slice(0, 12)} 的退出通知`);
101
+ // 清理旧容器的状态
102
+ this.removeContainerState(node.id, oldContainerId);
103
+ }
104
+ }
89
105
  if (state.stopTimer) {
90
106
  // 在防抖时间内恢复:取消报警
91
107
  clearTimeout(state.stopTimer);
@@ -118,17 +134,54 @@ class MonitorManager {
118
134
  this.callback(event);
119
135
  }
120
136
  }
121
- getContainerState(nodeId, containerId) {
137
+ getContainerState(nodeId, containerId, containerName) {
122
138
  if (!this.states.has(nodeId)) {
123
139
  this.states.set(nodeId, new Map());
124
140
  }
125
141
  const nodeStates = this.states.get(nodeId);
126
142
  if (!nodeStates.has(containerId)) {
127
143
  nodeStates.set(containerId, {
128
- history: []
144
+ history: [],
145
+ containerName
129
146
  });
130
147
  }
131
- return nodeStates.get(containerId);
148
+ const state = nodeStates.get(containerId);
149
+ // 更新名称索引
150
+ if (containerName) {
151
+ if (!this.nameIndex.has(nodeId)) {
152
+ this.nameIndex.set(nodeId, new Map());
153
+ }
154
+ this.nameIndex.get(nodeId).set(containerName, containerId);
155
+ state.containerName = containerName;
156
+ }
157
+ return state;
158
+ }
159
+ getContainerStateById(nodeId, containerId) {
160
+ const nodeStates = this.states.get(nodeId);
161
+ return nodeStates?.get(containerId);
162
+ }
163
+ getContainerIdByName(nodeId, containerName) {
164
+ const nameMap = this.nameIndex.get(nodeId);
165
+ return nameMap?.get(containerName);
166
+ }
167
+ removeContainerState(nodeId, containerId) {
168
+ const nodeStates = this.states.get(nodeId);
169
+ if (nodeStates) {
170
+ const state = nodeStates.get(containerId);
171
+ if (state?.containerName) {
172
+ const nameMap = this.nameIndex.get(nodeId);
173
+ if (nameMap?.get(state.containerName) === containerId) {
174
+ nameMap.delete(state.containerName);
175
+ }
176
+ }
177
+ nodeStates.delete(containerId);
178
+ }
179
+ }
180
+ clearNameIndex(nodeId, containerName) {
181
+ const nameMap = this.nameIndex.get(nodeId);
182
+ if (nameMap) {
183
+ nameMap.delete(containerName);
184
+ }
132
185
  }
133
186
  cleanHistory(state, now) {
134
187
  const window = this.config.flappingWindow || 300000; // 默认 5分钟
@@ -0,0 +1,26 @@
1
+ import { Context, h } from 'koishi';
2
+ import type { ContainerInfo } from '../types';
3
+ /**
4
+ * 渲染为图片(使用正确的 Koishi Puppeteer API)
5
+ */
6
+ export declare function renderToImage(ctx: Context, html: string): Promise<h>;
7
+ /**
8
+ * 生成容器列表 HTML(现代化)
9
+ */
10
+ export declare function generateListHtml(data: Array<{
11
+ node: any;
12
+ containers: ContainerInfo[];
13
+ }>, title?: string): string;
14
+ /**
15
+ * 生成节点列表 HTML(现代化)
16
+ */
17
+ export declare function generateNodesHtml(nodes: any[]): string;
18
+ /**
19
+ * 生成容器日志 HTML(现代化)
20
+ */
21
+ export declare function generateLogsHtml(nodeName: string, containerName: string, logs: string, lineCount: number): string;
22
+ /**
23
+ * 生成 Docker Compose 配置 HTML(现代化)
24
+ */
25
+ export declare function generateComposeHtml(nodeName: string, containerName: string, projectName: string, filePath: string, serviceCount: number, composeContent: string): string;
26
+ export { generateInspectHtml, generateResultHtml, generateExecHtml, generateImagesHtml, generateNetworksHtml, generateVolumesHtml } from './render';