koishi-plugin-pm2 0.1.4 → 0.3.0
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/dist/index.js +9 -9
- package/dist/style.css +1 -1
- package/lib/index.d.ts +48 -9
- package/lib/index.js +190 -67
- package/package.json +1 -1
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.log-list[data-v-
|
|
1
|
+
.log-list[data-v-8d10b629]{color:var(--terminal-fg);background-color:var(--terminal-bg)}.log-list[data-v-8d10b629] .el-scrollbar__view{padding:1rem}.log-list .line.start[data-v-8d10b629]{margin-top:1rem}.log-list .line.start[data-v-8d10b629]:before{content:"";position:absolute;left:0;right:0;top:-.5rem;border-top:1px solid var(--terminal-separator)}.log-list .line[data-v-8d10b629]:first-child{margin-top:0}.log-list .line[data-v-8d10b629]:first-child:before{display:none}.log-list .line[data-v-8d10b629]{padding:0 .5rem;border-radius:2px;font-size:14px;line-height:20px;white-space:pre-wrap;word-break:break-all;position:relative}.log-list .line[data-v-8d10b629]:hover{color:var(--terminal-fg-hover);background-color:var(--terminal-bg-hover)}.log-list .line[data-v-8d10b629] ::selection{background-color:var(--terminal-bg-selection)}.alert-dialog-actions{margin-top:20px;display:flex;justify-content:center}.action-dialog-loading[data-v-fe53df72]{padding:24px 0}.action-result-list[data-v-fe53df72]{display:flex;flex-direction:column;gap:12px}.action-result-scrollbar[data-v-fe53df72]{margin:0 -4px}.action-result-row[data-v-fe53df72]{border:var(--fg3) 1px solid;border-radius:4px;padding:12px 14px;margin:2px}.action-result-meta[data-v-fe53df72]{font-size:12px;color:var(--fg2);margin-bottom:6px}.action-result-payload[data-v-fe53df72]{margin:0;font-family:Fira Code,SFMono-Regular,Consolas,monospace;font-size:13px;white-space:pre-wrap;word-break:break-word}.section-empty[data-v-fe53df72]{margin:10px 0 0;color:var(--fg2);font-size:13px}.expand-section[data-v-41921f53]{padding:10px 20px;border-top:var(--k-status-divider, var(--k-color-divider-dark)) 1px solid}.expand-section[data-v-41921f53]:first-of-type{border-top:none}[data-v-41921f53] .el-table__expanded-cell{padding:0!important}.expand-title[data-v-41921f53]{margin:4px 0;font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.expand-header[data-v-41921f53]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:8px}.expand-actions[data-v-41921f53]{display:flex;gap:8px}.section-empty[data-v-41921f53]{margin:10px 0 0;color:var(--fg2);font-size:13px}.namespace-name[data-v-41921f53]{display:inline-flex;align-items:center;font-weight:600;margin-left:-12px}[data-v-41921f53] .namespace-row td{padding-top:4px;padding-bottom:4px;border-bottom:0!important}[data-v-41921f53] .namespace-row+.el-table__row td{border-top:0!important}[data-v-41921f53] .namespace-row+.el-table__expanded-row{display:none!important;height:0!important;line-height:0!important;font-size:0!important}[data-v-41921f53] .namespace-row+.el-table__expanded-row td,[data-v-41921f53] .namespace-row+.el-table__expanded-row .cell{padding:0!important;border:0!important;height:0!important;line-height:0!important;font-size:0!important}[data-v-41921f53] .cluster-row+.el-table__expanded-row{display:none!important;height:0!important;line-height:0!important;font-size:0!important}.cluster-name[data-v-41921f53]{display:inline-flex;align-items:baseline;gap:8px;line-height:1.2}.cluster-title[data-v-41921f53]{font-weight:600}.cluster-count[data-v-41921f53]{font-size:12px;color:var(--fg2)}[data-v-41921f53] .cluster-row.cluster-expanded td{border-bottom:0!important}[data-v-41921f53] .cluster-row.cluster-expanded+.el-table__expanded-row+.el-table__row td{border-top:0!important}[data-v-41921f53] .cluster-row td{padding-top:4px;padding-bottom:4px}[data-v-41921f53] .cluster-row+.el-table__expanded-row td,[data-v-41921f53] .cluster-row+.el-table__expanded-row .cell{padding:0!important;border:0!important;height:0!important;line-height:0!important;font-size:0!important}.process-name[data-v-41921f53]{display:inline-flex;align-items:baseline;gap:6px;line-height:1.2}.process-name.cluster-process[data-v-41921f53]{padding-left:12px}.process-id[data-v-41921f53]{font-size:12px;color:var(--fg2)}.metrics-container[data-v-41921f53]{display:flex;flex-wrap:wrap;gap:10px}.metric-card[data-v-41921f53]{border:var(--k-status-divider, var(--k-color-divider-dark)) 1px solid;border-radius:4px;padding:10px;flex:1 1 150px;box-sizing:border-box;text-align:center;max-width:200px}.metric-name[data-v-41921f53]{font-size:12px;color:var(--fg2)}.metric-value[data-v-41921f53]{font-size:16px;font-weight:700;display:flex;justify-content:center;gap:4px}.metric-unit[data-v-41921f53]{font-size:16px;color:var(--fg2)}.metric-cell[data-v-41921f53]{position:relative;padding:4px 6px;border-radius:6px;background-repeat:no-repeat;background-position:center;background-size:100% 100%}.metric-text[data-v-41921f53]{position:relative;z-index:1}.actions-container[data-v-41921f53]{display:flex;flex-wrap:wrap;gap:8px}[data-v-41921f53] .actions-container .el-button+.el-button{margin-left:0}.action-chip[data-v-41921f53]{border:var(--k-status-divider, var(--k-color-divider-dark)) 1px solid;display:inline-flex;align-items:center;gap:6px}.action-name[data-v-41921f53]{font-weight:600;margin-right:6px}.status-cell[data-v-41921f53]{display:flex;align-items:center;gap:8px;overflow:visible}[data-v-41921f53] .status-column,[data-v-41921f53] .status-column .cell{overflow:visible}.status-wrap[data-v-41921f53]{position:relative;display:inline-flex;overflow:visible;padding-top:4px;padding-right:4px}.status-badges[data-v-41921f53]{position:absolute;top:-4px;right:-4px;display:inline-flex;align-items:center;gap:2px}.status-badge[data-v-41921f53]{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;border:var(--fg4, var(--k-color-border)) 1px solid;border-radius:999px;font-size:10px;font-weight:700;color:var(--fg2);background:var(--bg1);box-shadow:0 0 0 1px var(--bg2)}.status-badge.auto[data-v-41921f53],.status-badge.cron[data-v-41921f53]{color:var(--k-color-success, var(--k-color-primary));border-color:currentColor}.alert-table[data-v-41921f53]{width:100%}.alert-row-actions[data-v-41921f53]{display:inline-flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:2px;width:100%}.alert-row-actions[data-v-41921f53] .el-button{min-width:32px;padding:2px 4px}.alert-row-actions[data-v-41921f53] .el-button+.el-button{margin-left:0}.alert-message[data-v-41921f53]{display:inline-block;max-width:100%;white-space:pre-wrap;word-break:break-word}.alert-message.placeholder[data-v-41921f53]{color:var(--fg2);font-style:italic}.log-view[data-v-41921f53]{padding:1rem;max-height:60vh;overflow-y:auto;white-space:pre-wrap;word-break:break-all}.process-scroll[data-v-41921f53] .el-scrollbar__wrap{overflow-x:auto;overflow-y:auto;-webkit-overflow-scrolling:touch}.el-button--success[data-v-41921f53]{--el-button-hover-text-color: var(--k-color-success);--el-button-hover-border-color: var(--k-color-success)}.el-button--info[data-v-41921f53]{--el-button-hover-text-color: var(--k-color-info);--el-button-hover-border-color: var(--k-color-info)}.el-button--danger[data-v-41921f53]{--el-button-hover-text-color: var(--k-color-danger);--el-button-hover-border-color: var(--k-color-danger)}
|
package/lib/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ declare module '@koishijs/console' {
|
|
|
14
14
|
'pm2/test-alert'(alert: PM2.Alert): Promise<void>;
|
|
15
15
|
'pm2/remove-alert'(alert: PM2.Alert): Promise<void>;
|
|
16
16
|
'pm2/clear-alerts'(name: string, events?: string[]): Promise<void>;
|
|
17
|
+
'pm2/save'(): Promise<void>;
|
|
17
18
|
'pm2/patch-log'(id: number | string, logs: string[]): void;
|
|
18
19
|
}
|
|
19
20
|
interface Client {
|
|
@@ -50,28 +51,41 @@ export declare class PM2 extends Service {
|
|
|
50
51
|
api: API;
|
|
51
52
|
bus: EventEmitter | null;
|
|
52
53
|
logs: LogManager;
|
|
54
|
+
metricHistory: Map<string, PM2.ProcessHistory>;
|
|
53
55
|
list: () => Promise<PM2.Process[]>;
|
|
54
56
|
trigger: (id: number | string, actionName: string) => Promise<PM2.MonitorActionResult[]>;
|
|
55
57
|
constructor(ctx: Context, config: PM2.Config);
|
|
56
58
|
_list(): Promise<PM2.Process[]>;
|
|
59
|
+
private recordHistory;
|
|
57
60
|
_trigger(id: number | string, actionName: string): Promise<any[]>;
|
|
58
|
-
alert(alert: PM2.Alert, env: PM2.
|
|
59
|
-
event: string;
|
|
60
|
-
manually: boolean;
|
|
61
|
-
}): Promise<string[]>;
|
|
61
|
+
alert(alert: PM2.Alert, env: PM2.AlertEnv): Promise<string[]>;
|
|
62
62
|
}
|
|
63
63
|
export declare namespace PM2 {
|
|
64
64
|
const inject: {
|
|
65
65
|
required: string[];
|
|
66
66
|
optional: string[];
|
|
67
67
|
};
|
|
68
|
+
const EventMap: {
|
|
69
|
+
readonly restart: "restart";
|
|
70
|
+
readonly delete: "delete";
|
|
71
|
+
readonly stop: "stop";
|
|
72
|
+
readonly reload: "reload";
|
|
73
|
+
readonly 'reload:graceful': "graceful reload";
|
|
74
|
+
readonly start: "start";
|
|
75
|
+
readonly exit: "exit";
|
|
76
|
+
readonly online: "online";
|
|
77
|
+
readonly 'restart:overlimit': "restart overlimit";
|
|
78
|
+
readonly 'exit:code': "exit";
|
|
79
|
+
readonly 'log:out': "";
|
|
80
|
+
readonly 'log:err': "";
|
|
81
|
+
};
|
|
82
|
+
type EventMap = typeof EventMap;
|
|
68
83
|
interface Alert {
|
|
69
84
|
name: string;
|
|
70
|
-
event:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
};
|
|
85
|
+
event: keyof EventMap;
|
|
86
|
+
eq?: number;
|
|
87
|
+
neq?: number;
|
|
88
|
+
regexp?: string;
|
|
75
89
|
sid?: string;
|
|
76
90
|
cid: string;
|
|
77
91
|
message?: string;
|
|
@@ -81,6 +95,8 @@ export declare namespace PM2 {
|
|
|
81
95
|
logSyncInterval: number;
|
|
82
96
|
listSyncInterval: number;
|
|
83
97
|
listSyncTimeout: number;
|
|
98
|
+
metricsInterval: number;
|
|
99
|
+
metricsHistorySize: number;
|
|
84
100
|
actionTimeout: number;
|
|
85
101
|
logTailLines: number;
|
|
86
102
|
ignoreStoppingExit: boolean;
|
|
@@ -127,6 +143,15 @@ export declare namespace PM2 {
|
|
|
127
143
|
};
|
|
128
144
|
[key: string]: any;
|
|
129
145
|
}
|
|
146
|
+
interface AlertEnv extends Env {
|
|
147
|
+
event: string;
|
|
148
|
+
/** Whether the event is triggered manually */
|
|
149
|
+
manually: boolean;
|
|
150
|
+
/** The type of process:event */
|
|
151
|
+
raw_event?: string;
|
|
152
|
+
/** Log data */
|
|
153
|
+
data?: string;
|
|
154
|
+
}
|
|
130
155
|
interface Process {
|
|
131
156
|
/** Process ID */
|
|
132
157
|
pm_id: number;
|
|
@@ -148,6 +173,7 @@ export declare namespace PM2 {
|
|
|
148
173
|
cpu: number;
|
|
149
174
|
memory: number;
|
|
150
175
|
};
|
|
176
|
+
history?: ProcessHistory;
|
|
151
177
|
alerts?: Alert[];
|
|
152
178
|
}
|
|
153
179
|
interface MonitorActionResult {
|
|
@@ -163,5 +189,18 @@ export declare namespace PM2 {
|
|
|
163
189
|
};
|
|
164
190
|
at: number;
|
|
165
191
|
}
|
|
192
|
+
interface MetricSample {
|
|
193
|
+
time: number;
|
|
194
|
+
cpu: number;
|
|
195
|
+
memory: number;
|
|
196
|
+
}
|
|
197
|
+
interface MonitorSample {
|
|
198
|
+
time: number;
|
|
199
|
+
value: number;
|
|
200
|
+
}
|
|
201
|
+
interface ProcessHistory {
|
|
202
|
+
samples: MetricSample[];
|
|
203
|
+
monitors: Record<string, MonitorSample[]>;
|
|
204
|
+
}
|
|
166
205
|
}
|
|
167
206
|
export default PM2;
|
package/lib/index.js
CHANGED
|
@@ -157,7 +157,7 @@ function parsePlatform(target) {
|
|
|
157
157
|
__name(parsePlatform, "parsePlatform");
|
|
158
158
|
|
|
159
159
|
// src/locales/en-US.yml
|
|
160
|
-
var en_US_default = { pm2: { alerts: { restart: "[PM2] Process {name} restarted.", delete: "[PM2] Process {name} deleted.", stop: "[PM2] Process {name} stopped.", reload: "[PM2] Process {name} reloaded.", "graceful
|
|
160
|
+
var en_US_default = { pm2: { alerts: { restart: "[PM2] Process {name} restarted.", delete: "[PM2] Process {name} deleted.", stop: "[PM2] Process {name} stopped.", reload: "[PM2] Process {name} reloaded.", "reload:graceful": "[PM2] Process {name} gracefully reloaded.", start: "[PM2] Process {name} started.", exit: "[PM2] Process {name} exited.", online: "[PM2] Process {name} is online.", "restart:overlimit": "[PM2] Process {name} reached restart overlimit.", "exit:code": "[PM2] Process {name} exited with code {code}.", "log:out": "[PM2] Process {name} logged stdout:\n{data}\n", "log:err": "[PM2] Process {name} logged stderr:\n{data}\n" } }, commands: { "pm2.list": { description: "Display the list of PM2 processes.", messages: { output: "- PM2 Process List\n{context.map(proc => '[' + String(proc.pm_id) + '] ' + proc.name + ' ' + proc.pm2_env.status + (proc.pm2_env.status === 'online' ? ' ' + String(Math.floor((Date.now() - proc.pm2_env.pm_uptime) / 1000 / 60 / 60 / 24)) + 'd' : '')).join('\\n')}\n" } }, "pm2.save": { description: "Dump process list.", messages: { success: "Success.", failed: "Failed" } }, "pm2.start": { description: "Start a PM2 process by id or name.", messages: { output: "Process {name} started.", "not-found": "Process {0} not found." } }, "pm2.restart": { description: "Restart a PM2 process by id or name.", messages: { output: "Process {name} restarted.", "not-found": "Process {0} not found." } }, "pm2.reload": { description: "Reload a PM2 process by id or name.", messages: { output: "Process {name} reloaded.", "not-found": "Process {0} not found." } }, "pm2.stop": { description: "Stop a PM2 process by id or name.", messages: { output: "Process {name} stopped.", "not-found": "Process {0} not found." } }, "pm2.delete": { description: "Delete a PM2 process by id or name.", messages: { output: "Process {name} deleted.", "not-found": "Process {0} not found." } } } };
|
|
161
161
|
|
|
162
162
|
// src/index.ts
|
|
163
163
|
var LogManager = class {
|
|
@@ -174,14 +174,39 @@ var LogManager = class {
|
|
|
174
174
|
async init() {
|
|
175
175
|
const onLog = /* @__PURE__ */ __name((packet, type) => {
|
|
176
176
|
const id = packet.process.pm_id ?? "PM2";
|
|
177
|
-
|
|
177
|
+
const targets = [String(id), packet.process.name, packet.process.namespace, "all"];
|
|
178
178
|
const data = packet.data.replace(/\n$/, "");
|
|
179
|
-
|
|
179
|
+
if (type === "out" || type === "err") {
|
|
180
|
+
for (const alert of this.pm2.config.alerts) {
|
|
181
|
+
if ((alert.name === packet.process.name || alert.name === "*") && alert.event === `log:${type}`) {
|
|
182
|
+
const regexp = new RegExp(alert.regexp ?? "");
|
|
183
|
+
if (regexp.test(data)) {
|
|
184
|
+
this.pm2.list().then((list) => {
|
|
185
|
+
const proc = list.find((p) => p.pm_id === packet.process.pm_id && p.name === packet.process.name);
|
|
186
|
+
if (proc) {
|
|
187
|
+
return this.pm2.alert(alert, {
|
|
188
|
+
...proc.pm2_env,
|
|
189
|
+
event: alert.event,
|
|
190
|
+
data,
|
|
191
|
+
manually: false
|
|
192
|
+
}).catch((err) => this.ctx.logger.warn("failed to deliver pm2 log alert:", err));
|
|
193
|
+
}
|
|
194
|
+
}, (err) => this.ctx.logger.warn("failed to fetch pm2 process for log alert:", err));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const formatted = LogUtils.format({
|
|
180
200
|
app_name: packet.process.pm_id + "|" + packet.process.name,
|
|
181
201
|
type,
|
|
182
202
|
path: ""
|
|
183
|
-
}, data)
|
|
184
|
-
|
|
203
|
+
}, data);
|
|
204
|
+
for (const target of targets) {
|
|
205
|
+
if (!target) continue;
|
|
206
|
+
if (!this.listeners[target]) continue;
|
|
207
|
+
this.buffers[target].push(formatted);
|
|
208
|
+
this.broadcasters[target]();
|
|
209
|
+
}
|
|
185
210
|
}, "onLog");
|
|
186
211
|
this.pm2.bus.on("log:out", (packet) => onLog(packet, "out"));
|
|
187
212
|
this.pm2.bus.on("log:err", (packet) => onLog(packet, "err"));
|
|
@@ -194,58 +219,57 @@ var LogManager = class {
|
|
|
194
219
|
}
|
|
195
220
|
}
|
|
196
221
|
async start(id, cid, exclusive) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this.
|
|
201
|
-
|
|
202
|
-
|
|
222
|
+
const key = String(id);
|
|
223
|
+
this.listeners[key] ||= /* @__PURE__ */ new Set();
|
|
224
|
+
if (!this.listeners[key].size) {
|
|
225
|
+
this.buffers[key] = [];
|
|
226
|
+
this.broadcasters[key] = this.ctx.throttle(() => {
|
|
227
|
+
if (!this.buffers[key].length) return;
|
|
228
|
+
const buffer = this.buffers[key].splice(0);
|
|
203
229
|
const toRemoved = [];
|
|
204
|
-
Object.values(this.ctx.console.clients).filter((client) => this.listeners[
|
|
230
|
+
Object.values(this.ctx.console.clients).filter((client) => this.listeners[key].has(client.id)).forEach((client) => {
|
|
205
231
|
if (client.pm2?.lastHeartbeat && Date.now() - client.pm2.lastHeartbeat > this.pm2.config.listSyncTimeout) {
|
|
206
232
|
toRemoved.push(client.id);
|
|
207
233
|
return;
|
|
208
234
|
}
|
|
209
235
|
client.send({
|
|
210
236
|
type: "pm2/patch-log",
|
|
211
|
-
body: [
|
|
237
|
+
body: [key, buffer]
|
|
212
238
|
});
|
|
213
239
|
});
|
|
214
240
|
toRemoved.forEach((cid2) => this.stopAll(cid2));
|
|
215
241
|
}, 200);
|
|
216
242
|
}
|
|
217
|
-
this.listeners[
|
|
243
|
+
this.listeners[key].add(cid);
|
|
218
244
|
if (this.pm2.config.logTailLines) {
|
|
219
245
|
const entries = [];
|
|
220
|
-
if (
|
|
246
|
+
if (key === "PM2" || key === "pm2") {
|
|
221
247
|
this.pushEntry(entries, {
|
|
222
248
|
path: this.pm2.api.pm2_home + "/pm2.log",
|
|
223
249
|
app_name: "PM2",
|
|
224
250
|
type: "PM2"
|
|
225
251
|
});
|
|
226
252
|
} else {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (proc.pm2_env &&
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
248
|
-
}
|
|
253
|
+
const list = await this.pm2._list();
|
|
254
|
+
const isAll = key === "all";
|
|
255
|
+
const isNumeric = !isNaN(Number(key));
|
|
256
|
+
for (const proc of list) {
|
|
257
|
+
if (!proc?.pm2_env) continue;
|
|
258
|
+
const match = isAll || proc.pm2_env.name === key || proc.pm2_env.namespace === key || isNumeric && proc.pm2_env.pm_id === Number(key);
|
|
259
|
+
if (!match) continue;
|
|
260
|
+
if (proc.pm2_env.pm_out_log_path && exclusive !== "err") {
|
|
261
|
+
this.pushEntry(entries, {
|
|
262
|
+
path: proc.pm2_env.pm_out_log_path,
|
|
263
|
+
app_name: proc.pm2_env.pm_id + "|" + proc.pm2_env.name,
|
|
264
|
+
type: "out"
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
if (proc.pm2_env.pm_err_log_path && exclusive !== "out") {
|
|
268
|
+
this.pushEntry(entries, {
|
|
269
|
+
path: proc.pm2_env.pm_err_log_path,
|
|
270
|
+
app_name: proc.pm2_env.pm_id + "|" + proc.pm2_env.name,
|
|
271
|
+
type: "err"
|
|
272
|
+
});
|
|
249
273
|
}
|
|
250
274
|
}
|
|
251
275
|
}
|
|
@@ -255,20 +279,21 @@ var LogManager = class {
|
|
|
255
279
|
}
|
|
256
280
|
}
|
|
257
281
|
stop(id, cid) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
delete this.
|
|
262
|
-
delete this.
|
|
282
|
+
const key = String(id);
|
|
283
|
+
this.listeners[key]?.delete(cid);
|
|
284
|
+
if (this.listeners[key]?.size === 0) {
|
|
285
|
+
delete this.listeners[key];
|
|
286
|
+
delete this.buffers[key];
|
|
287
|
+
delete this.broadcasters[key];
|
|
263
288
|
}
|
|
264
289
|
}
|
|
265
290
|
stopAll(cid) {
|
|
266
291
|
for (const id in this.listeners) {
|
|
267
|
-
this.stop(
|
|
292
|
+
this.stop(id, cid);
|
|
268
293
|
}
|
|
269
294
|
}
|
|
270
295
|
};
|
|
271
|
-
var PM2 = class extends import_koishi.Service {
|
|
296
|
+
var PM2 = class _PM2 extends import_koishi.Service {
|
|
272
297
|
constructor(ctx, config) {
|
|
273
298
|
super(ctx, "pm2");
|
|
274
299
|
this.ctx = ctx;
|
|
@@ -298,6 +323,13 @@ var PM2 = class extends import_koishi.Service {
|
|
|
298
323
|
});
|
|
299
324
|
});
|
|
300
325
|
await that.logs.init();
|
|
326
|
+
if (this.config.metricsInterval) {
|
|
327
|
+
this.ctx.setInterval(() => {
|
|
328
|
+
this.recordHistory().catch((err) => {
|
|
329
|
+
this.ctx.logger.warn("failed to record pm2 metrics:", err);
|
|
330
|
+
});
|
|
331
|
+
}, this.config.metricsInterval);
|
|
332
|
+
}
|
|
301
333
|
this.bus.on("process:event", (packet) => {
|
|
302
334
|
if (that.config.ignoreStoppingExit && packet.event === "exit" && packet.process.status === "stopping") return;
|
|
303
335
|
if (that.config.ignoreStoppedExit && packet.event === "exit" && packet.process.status === "stopped") return;
|
|
@@ -311,12 +343,12 @@ var PM2 = class extends import_koishi.Service {
|
|
|
311
343
|
ctx.on("dispose", () => this.api.close());
|
|
312
344
|
ctx.on("pm2/process-event", async (event, env, manually) => {
|
|
313
345
|
for (const alert of this.config.alerts) {
|
|
314
|
-
if ((alert.name === env.name || alert.name === "*") &&
|
|
315
|
-
if (event === "exit" &&
|
|
316
|
-
if (!(0, import_koishi.isNullable)(alert.
|
|
317
|
-
if (!(0, import_koishi.isNullable)(alert.
|
|
346
|
+
if ((alert.name === env.name || alert.name === "*") && _PM2.EventMap[alert.event] === event) {
|
|
347
|
+
if (alert.event === "exit:code" && event === "exit") {
|
|
348
|
+
if (!(0, import_koishi.isNullable)(alert.eq) && env.exit_code !== alert.eq) continue;
|
|
349
|
+
if (!(0, import_koishi.isNullable)(alert.neq) && env.exit_code === alert.neq) continue;
|
|
318
350
|
}
|
|
319
|
-
this.alert(alert, { ...env, event, manually }).catch((err) => {
|
|
351
|
+
this.alert(alert, { ...env, event: alert.event, raw_event: event, manually }).catch((err) => {
|
|
320
352
|
ctx.logger.warn("failed to deliver pm2 alert:", err);
|
|
321
353
|
});
|
|
322
354
|
}
|
|
@@ -331,6 +363,7 @@ var PM2 = class extends import_koishi.Service {
|
|
|
331
363
|
return that.list().then((list) => list.map((proc) => {
|
|
332
364
|
delete proc.pm2_env.env;
|
|
333
365
|
proc.alerts = (that.config.alerts || []).filter((alert) => alert.name === proc.name || alert.name === "*");
|
|
366
|
+
proc.history = that.metricHistory.get(proc.name);
|
|
334
367
|
return proc;
|
|
335
368
|
}));
|
|
336
369
|
});
|
|
@@ -363,7 +396,7 @@ var PM2 = class extends import_koishi.Service {
|
|
|
363
396
|
});
|
|
364
397
|
addListener("pm2/test-alert", async (alert) => {
|
|
365
398
|
const proc = await that.list().then((procs) => procs.find((p) => p.name === alert.name || alert.name === "*"));
|
|
366
|
-
await that.alert(alert, { ...proc.pm2_env, event: alert.event
|
|
399
|
+
await that.alert(alert, { ...proc.pm2_env, event: alert.event, manually: true });
|
|
367
400
|
});
|
|
368
401
|
addListener("pm2/remove-alert", async (alert) => {
|
|
369
402
|
if (!alert?.name) return;
|
|
@@ -379,6 +412,14 @@ var PM2 = class extends import_koishi.Service {
|
|
|
379
412
|
});
|
|
380
413
|
that.ctx.scope.update(that.config, false);
|
|
381
414
|
});
|
|
415
|
+
addListener("pm2/save", () => {
|
|
416
|
+
return new Promise((resolve2, reject) => {
|
|
417
|
+
that.api.dump((err) => {
|
|
418
|
+
if (err) return reject(err);
|
|
419
|
+
resolve2();
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
});
|
|
382
423
|
ctx.on("console/connection", (client) => {
|
|
383
424
|
if (!(client.id in ctx.console.clients)) {
|
|
384
425
|
that.logs.stopAll(client.id);
|
|
@@ -389,10 +430,23 @@ var PM2 = class extends import_koishi.Service {
|
|
|
389
430
|
const list = await that.list();
|
|
390
431
|
return session.text(".output", list);
|
|
391
432
|
});
|
|
433
|
+
ctx.command("pm2.save").action(async ({ session }) => {
|
|
434
|
+
try {
|
|
435
|
+
await new Promise((resolve2, reject) => {
|
|
436
|
+
this.api.dump((err) => {
|
|
437
|
+
if (err) return reject(err);
|
|
438
|
+
resolve2();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
} catch {
|
|
442
|
+
return session.text(".failed");
|
|
443
|
+
}
|
|
444
|
+
return session.text(".success");
|
|
445
|
+
});
|
|
392
446
|
for (const action of ["start", "restart", "reload", "stop", "delete"]) {
|
|
393
|
-
ctx.command(`pm2.${action} <
|
|
447
|
+
ctx.command(`pm2.${action} <key>`).action(async ({ session }, key) => {
|
|
394
448
|
const procs = await new Promise((resolve2, reject) => {
|
|
395
|
-
that.api[action](
|
|
449
|
+
that.api[action](key, (err, res) => {
|
|
396
450
|
if (err) return reject(err);
|
|
397
451
|
resolve2(res);
|
|
398
452
|
});
|
|
@@ -400,7 +454,7 @@ var PM2 = class extends import_koishi.Service {
|
|
|
400
454
|
if (procs.length) {
|
|
401
455
|
return session.text(".output", procs[0]);
|
|
402
456
|
} else {
|
|
403
|
-
return session.text(".not-found",
|
|
457
|
+
return session.text(".not-found", [key]);
|
|
404
458
|
}
|
|
405
459
|
});
|
|
406
460
|
}
|
|
@@ -409,6 +463,7 @@ var PM2 = class extends import_koishi.Service {
|
|
|
409
463
|
api;
|
|
410
464
|
bus = null;
|
|
411
465
|
logs;
|
|
466
|
+
metricHistory = /* @__PURE__ */ new Map();
|
|
412
467
|
list;
|
|
413
468
|
trigger;
|
|
414
469
|
async _list() {
|
|
@@ -419,6 +474,43 @@ var PM2 = class extends import_koishi.Service {
|
|
|
419
474
|
});
|
|
420
475
|
});
|
|
421
476
|
}
|
|
477
|
+
async recordHistory() {
|
|
478
|
+
const list = await this._list();
|
|
479
|
+
const now = Date.now();
|
|
480
|
+
const maxSamples = Math.max(1, this.config.metricsHistorySize);
|
|
481
|
+
const seen = /* @__PURE__ */ new Set();
|
|
482
|
+
for (const proc of list) {
|
|
483
|
+
if (!proc?.name) continue;
|
|
484
|
+
const history = this.metricHistory.get(proc.name) ?? { samples: [], monitors: {} };
|
|
485
|
+
if (proc.monit) {
|
|
486
|
+
history.samples.push({
|
|
487
|
+
time: now,
|
|
488
|
+
cpu: proc.monit.cpu ?? 0,
|
|
489
|
+
memory: proc.monit.memory ?? 0
|
|
490
|
+
});
|
|
491
|
+
if (history.samples.length > maxSamples) {
|
|
492
|
+
history.samples.splice(0, history.samples.length - maxSamples);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const monitor = proc.pm2_env?.axm_monitor || {};
|
|
496
|
+
for (const [name, data] of Object.entries(monitor)) {
|
|
497
|
+
if (!data?.historic) continue;
|
|
498
|
+
const value = typeof data.value === "number" ? data.value : Number(data.value);
|
|
499
|
+
if (!Number.isFinite(value)) continue;
|
|
500
|
+
const monitorHistory = history.monitors[name] ?? [];
|
|
501
|
+
monitorHistory.push({ time: now, value });
|
|
502
|
+
if (monitorHistory.length > maxSamples) {
|
|
503
|
+
monitorHistory.splice(0, monitorHistory.length - maxSamples);
|
|
504
|
+
}
|
|
505
|
+
history.monitors[name] = monitorHistory;
|
|
506
|
+
}
|
|
507
|
+
this.metricHistory.set(proc.name, history);
|
|
508
|
+
seen.add(proc.name);
|
|
509
|
+
}
|
|
510
|
+
for (const key of this.metricHistory.keys()) {
|
|
511
|
+
if (!seen.has(key)) this.metricHistory.delete(key);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
422
514
|
async _trigger(id, actionName) {
|
|
423
515
|
return new Promise((resolve2, reject) => {
|
|
424
516
|
let counter = 0, processCount = 0, timeout = null;
|
|
@@ -440,7 +532,7 @@ var PM2 = class extends import_koishi.Service {
|
|
|
440
532
|
}, this.config.actionTimeout);
|
|
441
533
|
this.api.Client.executeRemote("msgProcess", {
|
|
442
534
|
msg: actionName,
|
|
443
|
-
|
|
535
|
+
name: String(id)
|
|
444
536
|
}, function(err, data) {
|
|
445
537
|
if (err) return reject(err);
|
|
446
538
|
if (!data?.process_count) reject(new Error("No process received command."));
|
|
@@ -452,7 +544,7 @@ var PM2 = class extends import_koishi.Service {
|
|
|
452
544
|
const [platform, channelId] = parsePlatform(alert.cid);
|
|
453
545
|
const bot = this.ctx.bots[alert.sid] ?? this.ctx.bots.find((bot2) => bot2.platform === platform);
|
|
454
546
|
if (!bot) {
|
|
455
|
-
this.ctx.logger.warn(`Cannot find bot for PM2 alert: ${alert.name}/${alert.event
|
|
547
|
+
this.ctx.logger.warn(`Cannot find bot for PM2 alert: ${alert.name}/${alert.event} to ${alert.cid}`);
|
|
456
548
|
return;
|
|
457
549
|
}
|
|
458
550
|
if (alert.message) {
|
|
@@ -462,7 +554,7 @@ var PM2 = class extends import_koishi.Service {
|
|
|
462
554
|
if (this.ctx.get("database")) {
|
|
463
555
|
locales.push(...await this.ctx.get("database").getChannel(platform, channelId, ["locales"]).then((channel) => channel?.locales ?? []));
|
|
464
556
|
}
|
|
465
|
-
const message = this.ctx.i18n.render(locales, [`pm2.alerts.${alert.event
|
|
557
|
+
const message = this.ctx.i18n.render(locales, [`pm2.alerts.${alert.event}`], env);
|
|
466
558
|
return bot.sendMessage(channelId, message);
|
|
467
559
|
}
|
|
468
560
|
}
|
|
@@ -472,22 +564,53 @@ var PM2 = class extends import_koishi.Service {
|
|
|
472
564
|
required: ["console"],
|
|
473
565
|
optional: ["database", "console.services.status"]
|
|
474
566
|
};
|
|
475
|
-
PM22.
|
|
476
|
-
|
|
567
|
+
PM22.EventMap = {
|
|
568
|
+
restart: "restart",
|
|
569
|
+
delete: "delete",
|
|
570
|
+
stop: "stop",
|
|
571
|
+
reload: "reload",
|
|
572
|
+
"reload:graceful": "graceful reload",
|
|
573
|
+
start: "start",
|
|
574
|
+
exit: "exit",
|
|
575
|
+
online: "online",
|
|
576
|
+
"restart:overlimit": "restart overlimit",
|
|
577
|
+
"exit:code": "exit",
|
|
578
|
+
"log:out": "",
|
|
579
|
+
"log:err": ""
|
|
580
|
+
};
|
|
581
|
+
const AlertSchema = import_koishi.Schema.intersect([
|
|
582
|
+
import_koishi.Schema.object({
|
|
477
583
|
name: import_koishi.Schema.string().description("The name of the process. Use `*` to match all processes."),
|
|
478
|
-
event: import_koishi.Schema.union(
|
|
479
|
-
|
|
584
|
+
event: import_koishi.Schema.union(Object.keys(PM22.EventMap)).default("exit").description("The type of process:event."),
|
|
585
|
+
sid: import_koishi.Schema.string().description("platform:selfId").required(false),
|
|
586
|
+
cid: import_koishi.Schema.string().description("platform:channelId").required(),
|
|
587
|
+
message: import_koishi.Schema.string().description("The custom i18n message to send with the notification.").required(false).role("textarea")
|
|
588
|
+
}),
|
|
589
|
+
import_koishi.Schema.union([
|
|
590
|
+
import_koishi.Schema.object({
|
|
591
|
+
event: import_koishi.Schema.const("exit:code").required(),
|
|
480
592
|
eq: import_koishi.Schema.number().required(false),
|
|
481
593
|
neq: import_koishi.Schema.number().required(false)
|
|
482
|
-
})
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
594
|
+
}),
|
|
595
|
+
import_koishi.Schema.object({
|
|
596
|
+
event: import_koishi.Schema.const("log:out").required(),
|
|
597
|
+
regexp: import_koishi.Schema.string().description("The regular expression to match the stdout log content.")
|
|
598
|
+
}),
|
|
599
|
+
import_koishi.Schema.object({
|
|
600
|
+
event: import_koishi.Schema.const("log:err").required(),
|
|
601
|
+
regexp: import_koishi.Schema.string().description("The regular expression to match the stderr log content.")
|
|
602
|
+
}),
|
|
603
|
+
import_koishi.Schema.object({})
|
|
604
|
+
])
|
|
605
|
+
]).description("PM2 process event alert configuration.");
|
|
606
|
+
PM22.Config = import_koishi.Schema.object({
|
|
607
|
+
alerts: import_koishi.Schema.array(AlertSchema).default([]).hidden(),
|
|
487
608
|
logSyncInterval: import_koishi.Schema.number().description("The interval (in milliseconds) to sync logs from PM2.").default(200),
|
|
488
609
|
listSyncInterval: import_koishi.Schema.number().description("The interval (in milliseconds) to sync process list from PM2.").default(1e3),
|
|
489
|
-
listSyncTimeout: import_koishi.Schema.number().description("The timeout (in milliseconds) acts as heartbeat for clients requesting process list.").default(
|
|
490
|
-
|
|
610
|
+
listSyncTimeout: import_koishi.Schema.number().description("The timeout (in milliseconds) acts as heartbeat for clients requesting process list.").default(1e3 * 30),
|
|
611
|
+
metricsInterval: import_koishi.Schema.number().description("The interval (in milliseconds) to sample CPU/memory metrics.").default(1e3 * 60),
|
|
612
|
+
metricsHistorySize: import_koishi.Schema.number().description("The number of historical metric samples to keep.").default(60),
|
|
613
|
+
actionTimeout: import_koishi.Schema.number().description("The timeout (in milliseconds) for PM2 monitor actions.").default(1e3 * 10),
|
|
491
614
|
logTailLines: import_koishi.Schema.number().description("The number of log lines to tail when starting log streaming.").default(100),
|
|
492
615
|
ignoreStoppingExit: import_koishi.Schema.boolean().description("Disable exit alerts when a process is stopping.").default(true),
|
|
493
616
|
ignoreStoppedExit: import_koishi.Schema.boolean().description("Disable exit alerts when a process has stopped.").default(false)
|