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.
- package/lib/commands/control.js +13 -6
- package/lib/service/monitor.d.ts +6 -0
- package/lib/service/monitor.js +58 -5
- package/lib/utils/render-modern.d.ts +26 -0
- package/lib/utils/render-modern.js +772 -0
- package/lib/utils/render.d.ts +4 -4
- package/lib/utils/render.js +1167 -322
- package/package.json +1 -1
package/lib/commands/control.js
CHANGED
|
@@ -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
|
-
|
|
13
|
+
if (isNaN(num)) {
|
|
14
|
+
// 如果无法解析为数字,可能已经包含单位,直接返回
|
|
15
|
+
return bytes;
|
|
16
|
+
}
|
|
17
|
+
if (num === 0)
|
|
18
|
+
return '0B';
|
|
15
19
|
if (num < 1024)
|
|
16
|
-
return
|
|
20
|
+
return num.toFixed(0) + 'B';
|
|
17
21
|
if (num < 1024 * 1024)
|
|
18
|
-
return (num / 1024).toFixed(
|
|
19
|
-
|
|
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(`
|
|
280
|
+
lines.push(` 网络 ↓: ${formatNet(stats.networkIn)}`);
|
|
281
|
+
lines.push(` 网络 ↑: ${formatNet(stats.networkOut)}`);
|
|
275
282
|
lines.push(` 进程: ${stats.pids}`);
|
|
276
283
|
}
|
|
277
284
|
// 添加端口映射
|
package/lib/service/monitor.d.ts
CHANGED
|
@@ -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
|
}
|
package/lib/service/monitor.js
CHANGED
|
@@ -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
|
-
|
|
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';
|