ezpm2gui 1.6.0 → 1.8.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/README.md +330 -321
- package/bin/ezpm2gui.js +8 -8
- package/bin/ezpm2gui.ts +51 -51
- package/bin/generate-ecosystem.js +35 -35
- package/bin/generate-ecosystem.ts +56 -56
- package/dist/server/config/cron-jobs.json +1 -0
- package/dist/server/config/project-configs.json +235 -236
- package/dist/server/config/remote-connections.json +3 -0
- package/dist/server/index.js +42 -3
- package/dist/server/routes/deployApplication.js +47 -45
- package/dist/server/routes/logStreaming.js +31 -24
- package/dist/server/routes/modules.js +55 -0
- package/dist/server/routes/pageAuth.js +3 -3
- package/dist/server/routes/remoteConnections.js +13 -9
- package/dist/server/routes/remoteMetrics.d.ts +3 -0
- package/dist/server/routes/remoteMetrics.js +84 -0
- package/dist/server/services/ProjectSetupService.d.ts +1 -1
- package/dist/server/services/ProjectSetupService.js +25 -9
- package/dist/server/utils/metrics-history.d.ts +21 -0
- package/dist/server/utils/metrics-history.js +68 -0
- package/dist/server/utils/remote-metrics-db.d.ts +29 -0
- package/dist/server/utils/remote-metrics-db.js +134 -0
- package/dist/server/utils/remote-metrics-poller.d.ts +8 -0
- package/dist/server/utils/remote-metrics-poller.js +67 -0
- package/package.json +86 -73
- package/scripts/postinstall.js +36 -36
- package/src/client/build/asset-manifest.json +6 -6
- package/src/client/build/favicon.ico +2 -2
- package/src/client/build/index.html +1 -1
- package/src/client/build/logo192.svg +7 -7
- package/src/client/build/logo512.svg +7 -7
- package/src/client/build/manifest.json +24 -24
- package/src/client/build/static/css/main.9decb204.css +5 -0
- package/src/client/build/static/css/main.9decb204.css.map +1 -0
- package/src/client/build/static/js/main.28a4a583.js +3 -0
- package/src/client/build/static/js/main.28a4a583.js.map +1 -0
- package/src/client/build/static/css/main.775772ee.css +0 -5
- package/src/client/build/static/css/main.775772ee.css.map +0 -1
- package/src/client/build/static/js/main.cbcb09c9.js +0 -3
- package/src/client/build/static/js/main.cbcb09c9.js.map +0 -1
- /package/src/client/build/static/js/{main.cbcb09c9.js.LICENSE.txt → main.28a4a583.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.metricsHistory = void 0;
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
// @group Constants : Ring-buffer capacity — 60 points × 3 s = 3 minutes of history
|
|
9
|
+
const MAX_POINTS = 60;
|
|
10
|
+
// @group MetricsHistory : In-memory ring buffer; key = pm_id (string)
|
|
11
|
+
class MetricsHistoryStore {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.store = new Map();
|
|
14
|
+
}
|
|
15
|
+
// @group MetricsHistory : Record a snapshot from the PM2 process list
|
|
16
|
+
record(processList) {
|
|
17
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
18
|
+
const totalMem = os_1.default.totalmem();
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
for (const proc of processList) {
|
|
21
|
+
const pm_id = proc.pm_id;
|
|
22
|
+
seen.add(pm_id);
|
|
23
|
+
const cpu = (_b = (_a = proc.monit) === null || _a === void 0 ? void 0 : _a.cpu) !== null && _b !== void 0 ? _b : 0;
|
|
24
|
+
const memory = (_d = (_c = proc.monit) === null || _c === void 0 ? void 0 : _c.memory) !== null && _d !== void 0 ? _d : 0;
|
|
25
|
+
const point = {
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
cpu: parseFloat(cpu.toFixed(2)),
|
|
28
|
+
memory,
|
|
29
|
+
memoryMB: parseFloat((memory / 1048576).toFixed(2)),
|
|
30
|
+
memoryPercent: parseFloat(((memory / totalMem) * 100).toFixed(2)),
|
|
31
|
+
};
|
|
32
|
+
if (!this.store.has(pm_id)) {
|
|
33
|
+
this.store.set(pm_id, {
|
|
34
|
+
pm_id,
|
|
35
|
+
name: (_e = proc.name) !== null && _e !== void 0 ? _e : String(pm_id),
|
|
36
|
+
status: (_g = (_f = proc.pm2_env) === null || _f === void 0 ? void 0 : _f.status) !== null && _g !== void 0 ? _g : 'unknown',
|
|
37
|
+
history: [],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const entry = this.store.get(pm_id);
|
|
41
|
+
// Refresh mutable fields on every poll
|
|
42
|
+
entry.name = (_h = proc.name) !== null && _h !== void 0 ? _h : entry.name;
|
|
43
|
+
entry.status = (_k = (_j = proc.pm2_env) === null || _j === void 0 ? void 0 : _j.status) !== null && _k !== void 0 ? _k : entry.status;
|
|
44
|
+
// Append point, trim to ring-buffer size
|
|
45
|
+
entry.history.push(point);
|
|
46
|
+
if (entry.history.length > MAX_POINTS) {
|
|
47
|
+
entry.history.shift();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Remove processes that no longer exist in PM2
|
|
51
|
+
for (const id of this.store.keys()) {
|
|
52
|
+
if (!seen.has(id)) {
|
|
53
|
+
this.store.delete(id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// @group MetricsHistory : Return history for a single process; null if not found
|
|
58
|
+
getOne(pm_id) {
|
|
59
|
+
var _a;
|
|
60
|
+
return (_a = this.store.get(pm_id)) !== null && _a !== void 0 ? _a : null;
|
|
61
|
+
}
|
|
62
|
+
// @group MetricsHistory : Return history for all tracked processes
|
|
63
|
+
getAll() {
|
|
64
|
+
return Array.from(this.store.values());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// @group Exports : Singleton instance used across the server
|
|
68
|
+
exports.metricsHistory = new MetricsHistoryStore();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface RemoteMetricRow {
|
|
2
|
+
id: number;
|
|
3
|
+
connection_id: string;
|
|
4
|
+
connection_name: string;
|
|
5
|
+
process_name: string;
|
|
6
|
+
pm_id: number;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
cpu: number;
|
|
9
|
+
memory_bytes: number;
|
|
10
|
+
memory_mb: number;
|
|
11
|
+
}
|
|
12
|
+
declare class RemoteMetricsDB {
|
|
13
|
+
private db;
|
|
14
|
+
constructor();
|
|
15
|
+
private initSchema;
|
|
16
|
+
private purgeOld;
|
|
17
|
+
insert(row: Omit<RemoteMetricRow, 'id'>): void;
|
|
18
|
+
insertBatch(rows: Omit<RemoteMetricRow, 'id'>[]): void;
|
|
19
|
+
getConnectionsWithData(): {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
}[];
|
|
23
|
+
getProcessNames(connectionId: string): string[];
|
|
24
|
+
getMetrics(connectionId: string, processName: string, from: number, to: number, maxPoints?: number): RemoteMetricRow[];
|
|
25
|
+
getLatestSnapshot(connectionId: string): RemoteMetricRow[];
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare const remoteMetricsDB: RemoteMetricsDB;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.remoteMetricsDB = void 0;
|
|
7
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
// @group Configuration : SQLite DB lives alongside other server config files
|
|
11
|
+
const DB_DIR = path_1.default.join(__dirname, '../config');
|
|
12
|
+
const DB_PATH = path_1.default.join(DB_DIR, 'remote-metrics.db');
|
|
13
|
+
// @group RemoteMetricsDB : SQLite-backed persistent store for remote process metrics
|
|
14
|
+
class RemoteMetricsDB {
|
|
15
|
+
constructor() {
|
|
16
|
+
if (!fs_1.default.existsSync(DB_DIR))
|
|
17
|
+
fs_1.default.mkdirSync(DB_DIR, { recursive: true });
|
|
18
|
+
this.db = new better_sqlite3_1.default(DB_PATH);
|
|
19
|
+
this.db.pragma('journal_mode = WAL');
|
|
20
|
+
this.initSchema();
|
|
21
|
+
this.purgeOld();
|
|
22
|
+
}
|
|
23
|
+
// @group RemoteMetricsDB : Create tables and indexes on first run
|
|
24
|
+
initSchema() {
|
|
25
|
+
this.db.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS remote_metrics (
|
|
27
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
28
|
+
connection_id TEXT NOT NULL,
|
|
29
|
+
connection_name TEXT NOT NULL,
|
|
30
|
+
process_name TEXT NOT NULL,
|
|
31
|
+
pm_id INTEGER NOT NULL,
|
|
32
|
+
timestamp INTEGER NOT NULL,
|
|
33
|
+
cpu REAL NOT NULL,
|
|
34
|
+
memory_bytes INTEGER NOT NULL,
|
|
35
|
+
memory_mb REAL NOT NULL
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_rmet_conn_proc_ts
|
|
39
|
+
ON remote_metrics (connection_id, process_name, timestamp);
|
|
40
|
+
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_rmet_conn_ts
|
|
42
|
+
ON remote_metrics (connection_id, timestamp);
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
// @group RemoteMetricsDB : Delete records older than 30 days to keep DB lean
|
|
46
|
+
purgeOld() {
|
|
47
|
+
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
48
|
+
this.db.prepare('DELETE FROM remote_metrics WHERE timestamp < ?').run(cutoff);
|
|
49
|
+
}
|
|
50
|
+
// @group RemoteMetricsDB : Persist a single data-point
|
|
51
|
+
insert(row) {
|
|
52
|
+
this.db.prepare(`
|
|
53
|
+
INSERT INTO remote_metrics
|
|
54
|
+
(connection_id, connection_name, process_name, pm_id, timestamp, cpu, memory_bytes, memory_mb)
|
|
55
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
56
|
+
`).run(row.connection_id, row.connection_name, row.process_name, row.pm_id, row.timestamp, row.cpu, row.memory_bytes, row.memory_mb);
|
|
57
|
+
}
|
|
58
|
+
// @group RemoteMetricsDB : Bulk-insert multiple data-points in one transaction (efficient)
|
|
59
|
+
insertBatch(rows) {
|
|
60
|
+
const stmt = this.db.prepare(`
|
|
61
|
+
INSERT INTO remote_metrics
|
|
62
|
+
(connection_id, connection_name, process_name, pm_id, timestamp, cpu, memory_bytes, memory_mb)
|
|
63
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
64
|
+
`);
|
|
65
|
+
const tx = this.db.transaction(() => {
|
|
66
|
+
for (const row of rows) {
|
|
67
|
+
stmt.run(row.connection_id, row.connection_name, row.process_name, row.pm_id, row.timestamp, row.cpu, row.memory_bytes, row.memory_mb);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
tx();
|
|
71
|
+
}
|
|
72
|
+
// @group RemoteMetricsDB : Return all (connection_id, connection_name) pairs that have data
|
|
73
|
+
getConnectionsWithData() {
|
|
74
|
+
return this.db.prepare(`
|
|
75
|
+
SELECT DISTINCT connection_id AS id, connection_name AS name
|
|
76
|
+
FROM remote_metrics
|
|
77
|
+
ORDER BY name
|
|
78
|
+
`).all();
|
|
79
|
+
}
|
|
80
|
+
// @group RemoteMetricsDB : Return distinct process names recorded for a connection
|
|
81
|
+
getProcessNames(connectionId) {
|
|
82
|
+
const rows = this.db.prepare(`
|
|
83
|
+
SELECT DISTINCT process_name
|
|
84
|
+
FROM remote_metrics
|
|
85
|
+
WHERE connection_id = ?
|
|
86
|
+
ORDER BY process_name
|
|
87
|
+
`).all(connectionId);
|
|
88
|
+
return rows.map(r => r.process_name);
|
|
89
|
+
}
|
|
90
|
+
// @group RemoteMetricsDB : Return metric rows for a specific process within a time range
|
|
91
|
+
getMetrics(connectionId, processName, from, to, maxPoints = 500) {
|
|
92
|
+
// Bucket-reduce to avoid sending thousands of points for long ranges
|
|
93
|
+
const total = this.db.prepare(`
|
|
94
|
+
SELECT COUNT(*) AS cnt FROM remote_metrics
|
|
95
|
+
WHERE connection_id = ? AND process_name = ? AND timestamp BETWEEN ? AND ?
|
|
96
|
+
`).get(connectionId, processName, from, to).cnt;
|
|
97
|
+
if (total <= maxPoints) {
|
|
98
|
+
return this.db.prepare(`
|
|
99
|
+
SELECT * FROM remote_metrics
|
|
100
|
+
WHERE connection_id = ? AND process_name = ? AND timestamp BETWEEN ? AND ?
|
|
101
|
+
ORDER BY timestamp ASC
|
|
102
|
+
`).all(connectionId, processName, from, to);
|
|
103
|
+
}
|
|
104
|
+
// Downsample: pick every Nth row so we stay under maxPoints
|
|
105
|
+
const step = Math.ceil(total / maxPoints);
|
|
106
|
+
return this.db.prepare(`
|
|
107
|
+
SELECT * FROM remote_metrics
|
|
108
|
+
WHERE connection_id = ? AND process_name = ? AND timestamp BETWEEN ? AND ?
|
|
109
|
+
AND (id % ?) = 0
|
|
110
|
+
ORDER BY timestamp ASC
|
|
111
|
+
LIMIT ?
|
|
112
|
+
`).all(connectionId, processName, from, to, step, maxPoints);
|
|
113
|
+
}
|
|
114
|
+
// @group RemoteMetricsDB : Return latest snapshot for all processes on a connection
|
|
115
|
+
getLatestSnapshot(connectionId) {
|
|
116
|
+
return this.db.prepare(`
|
|
117
|
+
SELECT r.*
|
|
118
|
+
FROM remote_metrics r
|
|
119
|
+
INNER JOIN (
|
|
120
|
+
SELECT process_name, MAX(timestamp) AS max_ts
|
|
121
|
+
FROM remote_metrics
|
|
122
|
+
WHERE connection_id = ?
|
|
123
|
+
GROUP BY process_name
|
|
124
|
+
) latest ON r.process_name = latest.process_name AND r.timestamp = latest.max_ts
|
|
125
|
+
WHERE r.connection_id = ?
|
|
126
|
+
ORDER BY r.process_name
|
|
127
|
+
`).all(connectionId, connectionId);
|
|
128
|
+
}
|
|
129
|
+
close() {
|
|
130
|
+
this.db.close();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// @group Exports : Singleton used across the server
|
|
134
|
+
exports.remoteMetricsDB = new RemoteMetricsDB();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.remoteMetricsPoller = void 0;
|
|
4
|
+
const remote_connection_1 = require("./remote-connection");
|
|
5
|
+
const remote_metrics_db_1 = require("./remote-metrics-db");
|
|
6
|
+
// @group Configuration : Polling interval in milliseconds
|
|
7
|
+
const POLL_INTERVAL_MS = 30000; // 30 seconds
|
|
8
|
+
// @group RemoteMetricsPoller : Background service that samples PM2 metrics for every connected remote server
|
|
9
|
+
class RemoteMetricsPoller {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.timer = null;
|
|
12
|
+
}
|
|
13
|
+
// @group RemoteMetricsPoller : Start the periodic polling loop
|
|
14
|
+
start() {
|
|
15
|
+
if (this.timer)
|
|
16
|
+
return; // already running
|
|
17
|
+
console.log('[RemoteMetricsPoller] Starting — polling every', POLL_INTERVAL_MS / 1000, 's');
|
|
18
|
+
this.timer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
|
|
19
|
+
}
|
|
20
|
+
// @group RemoteMetricsPoller : Stop the polling loop
|
|
21
|
+
stop() {
|
|
22
|
+
if (this.timer) {
|
|
23
|
+
clearInterval(this.timer);
|
|
24
|
+
this.timer = null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// @group RemoteMetricsPoller : Poll all currently-connected remote servers and persist metrics
|
|
28
|
+
async poll() {
|
|
29
|
+
var _a;
|
|
30
|
+
const connectionsMap = remote_connection_1.remoteConnectionManager.getAllConnections();
|
|
31
|
+
const connected = Array.from(connectionsMap.entries()).filter(([, c]) => c.isConnected());
|
|
32
|
+
if (connected.length === 0)
|
|
33
|
+
return;
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
for (const [connectionId, conn] of connected) {
|
|
36
|
+
try {
|
|
37
|
+
const processes = await conn.getPM2Processes();
|
|
38
|
+
if (!Array.isArray(processes) || processes.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
const rows = processes
|
|
41
|
+
.filter(p => p && p.name)
|
|
42
|
+
.map(p => {
|
|
43
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
44
|
+
return ({
|
|
45
|
+
connection_id: connectionId,
|
|
46
|
+
connection_name: (_a = conn.name) !== null && _a !== void 0 ? _a : conn.host,
|
|
47
|
+
process_name: p.name,
|
|
48
|
+
pm_id: (_b = p.pm_id) !== null && _b !== void 0 ? _b : -1,
|
|
49
|
+
timestamp: now,
|
|
50
|
+
cpu: parseFloat(((_d = (_c = p.monit) === null || _c === void 0 ? void 0 : _c.cpu) !== null && _d !== void 0 ? _d : 0).toFixed(2)),
|
|
51
|
+
memory_bytes: ((_f = (_e = p.monit) === null || _e === void 0 ? void 0 : _e.memory) !== null && _f !== void 0 ? _f : 0),
|
|
52
|
+
memory_mb: parseFloat((((_h = (_g = p.monit) === null || _g === void 0 ? void 0 : _g.memory) !== null && _h !== void 0 ? _h : 0) / 1048576).toFixed(2)),
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
if (rows.length > 0) {
|
|
56
|
+
remote_metrics_db_1.remoteMetricsDB.insertBatch(rows);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
// Non-fatal — log and continue with other servers
|
|
61
|
+
console.warn(`[RemoteMetricsPoller] Failed to poll ${conn.host}:`, (_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// @group Exports : Singleton poller started once when the server boots
|
|
67
|
+
exports.remoteMetricsPoller = new RemoteMetricsPoller();
|
package/package.json
CHANGED
|
@@ -1,73 +1,86 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ezpm2gui",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"main": "dist/server/index.js",
|
|
5
|
-
"bin": {
|
|
6
|
-
"ezpm2gui": "bin/ezpm2gui.js",
|
|
7
|
-
"ezpm2gui-generate-ecosystem": "bin/generate-ecosystem.js"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"start": "node dist/server/index.js",
|
|
11
|
-
"dev": "concurrently --kill-others-on-fail --names \"server,client\" --prefix-colors \"cyan,green\" \"npm run dev:server\" \"npm run dev:client\"",
|
|
12
|
-
"dev:server": "nodemon --exec ts-node src/server/index.ts",
|
|
13
|
-
"dev:client": "cd src/client && npm run dev",
|
|
14
|
-
"build": "node scripts/build.js",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "ezpm2gui",
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"main": "dist/server/index.js",
|
|
5
|
+
"bin": {
|
|
6
|
+
"ezpm2gui": "bin/ezpm2gui.js",
|
|
7
|
+
"ezpm2gui-generate-ecosystem": "bin/generate-ecosystem.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node dist/server/index.js",
|
|
11
|
+
"dev": "concurrently --kill-others-on-fail --names \"server,client\" --prefix-colors \"cyan,green\" \"npm run dev:server\" \"npm run dev:client\"",
|
|
12
|
+
"dev:server": "nodemon --exec ts-node src/server/index.ts",
|
|
13
|
+
"dev:client": "cd src/client && npm run dev",
|
|
14
|
+
"build": "node scripts/build.js",
|
|
15
|
+
"mobile": "node scripts/deploy-mobile.js",
|
|
16
|
+
"mobile:dev": "node scripts/deploy-mobile.js dev",
|
|
17
|
+
"mobile:android": "node scripts/deploy-mobile.js android --debug --install",
|
|
18
|
+
"mobile:android:release": "node scripts/deploy-mobile.js android --release",
|
|
19
|
+
"mobile:debug": "powershell -ExecutionPolicy Bypass -File scripts/Debug-Mobile.ps1",
|
|
20
|
+
"mobile:debug:dump": "powershell -ExecutionPolicy Bypass -File scripts/Debug-Mobile.ps1 -Dump",
|
|
21
|
+
"mobile:ps": "powershell -ExecutionPolicy Bypass -File scripts/Deploy-Mobile.ps1",
|
|
22
|
+
"mobile:ps:release": "powershell -ExecutionPolicy Bypass -File scripts/Deploy-Mobile.ps1 -Mode release -Install -Launch",
|
|
23
|
+
"mobile:ps:debug": "powershell -ExecutionPolicy Bypass -File scripts/Deploy-Mobile.ps1 -Mode debug -Install -Launch",
|
|
24
|
+
"mobile:ios": "node scripts/deploy-mobile.js ios",
|
|
25
|
+
"mobile:ios:release": "node scripts/deploy-mobile.js ios --release",
|
|
26
|
+
"build:server": "tsc",
|
|
27
|
+
"build:client": "cd src/client && npm run build",
|
|
28
|
+
"build:bin": "tsc --project tsconfig.bin.json",
|
|
29
|
+
"prepare": "npm run build",
|
|
30
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
31
|
+
"postinstall": "node scripts/postinstall.js"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"pm2",
|
|
35
|
+
"gui",
|
|
36
|
+
"monitor",
|
|
37
|
+
"process-manager",
|
|
38
|
+
"dashboard"
|
|
39
|
+
],
|
|
40
|
+
"author": "Chandan Bhagat",
|
|
41
|
+
"license": "AGPL-3.0-or-later",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/thechandanbhagat/ezpm2gui.git"
|
|
45
|
+
},
|
|
46
|
+
"description": "A modern web-based GUI for PM2 process manager",
|
|
47
|
+
"files": [
|
|
48
|
+
"dist/",
|
|
49
|
+
"bin/",
|
|
50
|
+
"src/client/build/",
|
|
51
|
+
"scripts/postinstall.js"
|
|
52
|
+
],
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@tailwindcss/postcss": "^4.1.14",
|
|
55
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
56
|
+
"@types/node-cron": "^3.0.11",
|
|
57
|
+
"axios": "^1.9.0",
|
|
58
|
+
"better-sqlite3": "^12.9.0",
|
|
59
|
+
"chart.js": "^4.4.9",
|
|
60
|
+
"cron-parser": "^5.4.0",
|
|
61
|
+
"dotenv": "^16.5.0",
|
|
62
|
+
"express": "^4.18.3",
|
|
63
|
+
"node-cron": "^4.2.1",
|
|
64
|
+
"pm2": "^6.0.5",
|
|
65
|
+
"react": "^19.1.0",
|
|
66
|
+
"react-dom": "^19.1.0",
|
|
67
|
+
"react-scripts": "^5.0.1",
|
|
68
|
+
"socket.io": "^4.8.1",
|
|
69
|
+
"ssh2": "^1.16.0",
|
|
70
|
+
"uuid": "^11.0.5"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/express": "^4.17.21",
|
|
74
|
+
"@types/node": "^22.15.17",
|
|
75
|
+
"@types/react": "^19.1.4",
|
|
76
|
+
"@types/react-dom": "^19.1.5",
|
|
77
|
+
"@types/socket.io": "^3.0.2",
|
|
78
|
+
"@types/ssh2": "^1.15.5",
|
|
79
|
+
"@types/uuid": "^10.0.0",
|
|
80
|
+
"concurrently": "^9.1.2",
|
|
81
|
+
"cross-env": "^10.1.0",
|
|
82
|
+
"nodemon": "^3.1.10",
|
|
83
|
+
"ts-node": "^10.9.2",
|
|
84
|
+
"typescript": "^5.8.3"
|
|
85
|
+
}
|
|
86
|
+
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
const { execSync } = require('child_process');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
console.log('Running postinstall script...');
|
|
6
|
-
|
|
7
|
-
const isGlobalInstall = process.env.npm_config_global === 'true';
|
|
8
|
-
if (isGlobalInstall) {
|
|
9
|
-
console.log('Global installation detected, skipping client dependencies.');
|
|
10
|
-
process.exit(0);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Check if we're in a development environment
|
|
14
|
-
const clientDir = path.join(__dirname, '..', 'src', 'client');
|
|
15
|
-
const clientNodeModules = path.join(clientDir, 'node_modules');
|
|
16
|
-
const packageJsonPath = path.join(clientDir, 'package.json');
|
|
17
|
-
|
|
18
|
-
if (fs.existsSync(packageJsonPath) && !fs.existsSync(clientNodeModules)) {
|
|
19
|
-
console.log('Installing client dependencies...');
|
|
20
|
-
try {
|
|
21
|
-
// Change to client directory
|
|
22
|
-
process.chdir(clientDir);
|
|
23
|
-
|
|
24
|
-
// Install dependencies
|
|
25
|
-
execSync('npm install', { stdio: 'inherit' });
|
|
26
|
-
|
|
27
|
-
console.log('Client dependencies installed successfully.');
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error('Error installing client dependencies:', error.message);
|
|
30
|
-
console.error('Please run "cd src/client && npm install" manually.');
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
console.log('Client dependencies already installed or client package.json not found.');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log('Postinstall completed.');
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
console.log('Running postinstall script...');
|
|
6
|
+
|
|
7
|
+
const isGlobalInstall = process.env.npm_config_global === 'true';
|
|
8
|
+
if (isGlobalInstall) {
|
|
9
|
+
console.log('Global installation detected, skipping client dependencies.');
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Check if we're in a development environment
|
|
14
|
+
const clientDir = path.join(__dirname, '..', 'src', 'client');
|
|
15
|
+
const clientNodeModules = path.join(clientDir, 'node_modules');
|
|
16
|
+
const packageJsonPath = path.join(clientDir, 'package.json');
|
|
17
|
+
|
|
18
|
+
if (fs.existsSync(packageJsonPath) && !fs.existsSync(clientNodeModules)) {
|
|
19
|
+
console.log('Installing client dependencies...');
|
|
20
|
+
try {
|
|
21
|
+
// Change to client directory
|
|
22
|
+
process.chdir(clientDir);
|
|
23
|
+
|
|
24
|
+
// Install dependencies
|
|
25
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
26
|
+
|
|
27
|
+
console.log('Client dependencies installed successfully.');
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Error installing client dependencies:', error.message);
|
|
30
|
+
console.error('Please run "cd src/client && npm install" manually.');
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
console.log('Client dependencies already installed or client package.json not found.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('Postinstall completed.');
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": {
|
|
3
|
-
"main.css": "/static/css/main.
|
|
4
|
-
"main.js": "/static/js/main.
|
|
3
|
+
"main.css": "/static/css/main.9decb204.css",
|
|
4
|
+
"main.js": "/static/js/main.28a4a583.js",
|
|
5
5
|
"index.html": "/index.html",
|
|
6
|
-
"main.
|
|
7
|
-
"main.
|
|
6
|
+
"main.9decb204.css.map": "/static/css/main.9decb204.css.map",
|
|
7
|
+
"main.28a4a583.js.map": "/static/js/main.28a4a583.js.map"
|
|
8
8
|
},
|
|
9
9
|
"entrypoints": [
|
|
10
|
-
"static/css/main.
|
|
11
|
-
"static/js/main.
|
|
10
|
+
"static/css/main.9decb204.css",
|
|
11
|
+
"static/js/main.28a4a583.js"
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<!-- Simple favicon content - binary content approximated with HTML comment -->
|
|
2
|
-
<!-- This is a placeholder and should be replaced with a proper ICO file -->
|
|
1
|
+
<!-- Simple favicon content - binary content approximated with HTML comment -->
|
|
2
|
+
<!-- This is a placeholder and should be replaced with a proper ICO file -->
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#4a90e2"/><meta name="description" content="EZ PM2 GUI - A modern interface for PM2 process manager"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>EZ PM2 GUI - PM2 Process Manager</title><script defer="defer" src="/static/js/main.
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#4a90e2"/><meta name="description" content="EZ PM2 GUI - A modern interface for PM2 process manager"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>EZ PM2 GUI - PM2 Process Manager</title><script defer="defer" src="/static/js/main.28a4a583.js"></script><link href="/static/css/main.9decb204.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<rect width="192" height="192" rx="38.4" fill="#3F51B5"/>
|
|
3
|
-
<path d="M48 96C48 69.4904 69.4904 48 96 48C122.51 48 144 69.4904 144 96C144 122.51 122.51 144 96 144C69.4904 144 48 122.51 48 96Z" stroke="white" stroke-width="9.6"/>
|
|
4
|
-
<path d="M72 86.4H120" stroke="white" stroke-width="9.6" stroke-linecap="round"/>
|
|
5
|
-
<path d="M72 105.6H120" stroke="white" stroke-width="9.6" stroke-linecap="round"/>
|
|
6
|
-
<path d="M96 72V120" stroke="white" stroke-width="9.6" stroke-linecap="round"/>
|
|
7
|
-
</svg>
|
|
1
|
+
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="192" height="192" rx="38.4" fill="#3F51B5"/>
|
|
3
|
+
<path d="M48 96C48 69.4904 69.4904 48 96 48C122.51 48 144 69.4904 144 96C144 122.51 122.51 144 96 144C69.4904 144 48 122.51 48 96Z" stroke="white" stroke-width="9.6"/>
|
|
4
|
+
<path d="M72 86.4H120" stroke="white" stroke-width="9.6" stroke-linecap="round"/>
|
|
5
|
+
<path d="M72 105.6H120" stroke="white" stroke-width="9.6" stroke-linecap="round"/>
|
|
6
|
+
<path d="M96 72V120" stroke="white" stroke-width="9.6" stroke-linecap="round"/>
|
|
7
|
+
</svg>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<rect width="512" height="512" rx="102.4" fill="#3F51B5"/>
|
|
3
|
-
<path d="M128 256C128 185.307 185.307 128 256 128C326.693 128 384 185.307 384 256C384 326.693 326.693 384 256 384C185.307 384 128 326.693 128 256Z" stroke="white" stroke-width="25.6"/>
|
|
4
|
-
<path d="M192 230.4H320" stroke="white" stroke-width="25.6" stroke-linecap="round"/>
|
|
5
|
-
<path d="M192 281.6H320" stroke="white" stroke-width="25.6" stroke-linecap="round"/>
|
|
6
|
-
<path d="M256 192V320" stroke="white" stroke-width="25.6" stroke-linecap="round"/>
|
|
7
|
-
</svg>
|
|
1
|
+
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="512" height="512" rx="102.4" fill="#3F51B5"/>
|
|
3
|
+
<path d="M128 256C128 185.307 185.307 128 256 128C326.693 128 384 185.307 384 256C384 326.693 326.693 384 256 384C185.307 384 128 326.693 128 256Z" stroke="white" stroke-width="25.6"/>
|
|
4
|
+
<path d="M192 230.4H320" stroke="white" stroke-width="25.6" stroke-linecap="round"/>
|
|
5
|
+
<path d="M192 281.6H320" stroke="white" stroke-width="25.6" stroke-linecap="round"/>
|
|
6
|
+
<path d="M256 192V320" stroke="white" stroke-width="25.6" stroke-linecap="round"/>
|
|
7
|
+
</svg>
|