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.
Files changed (41) hide show
  1. package/README.md +330 -321
  2. package/bin/ezpm2gui.js +8 -8
  3. package/bin/ezpm2gui.ts +51 -51
  4. package/bin/generate-ecosystem.js +35 -35
  5. package/bin/generate-ecosystem.ts +56 -56
  6. package/dist/server/config/cron-jobs.json +1 -0
  7. package/dist/server/config/project-configs.json +235 -236
  8. package/dist/server/config/remote-connections.json +3 -0
  9. package/dist/server/index.js +42 -3
  10. package/dist/server/routes/deployApplication.js +47 -45
  11. package/dist/server/routes/logStreaming.js +31 -24
  12. package/dist/server/routes/modules.js +55 -0
  13. package/dist/server/routes/pageAuth.js +3 -3
  14. package/dist/server/routes/remoteConnections.js +13 -9
  15. package/dist/server/routes/remoteMetrics.d.ts +3 -0
  16. package/dist/server/routes/remoteMetrics.js +84 -0
  17. package/dist/server/services/ProjectSetupService.d.ts +1 -1
  18. package/dist/server/services/ProjectSetupService.js +25 -9
  19. package/dist/server/utils/metrics-history.d.ts +21 -0
  20. package/dist/server/utils/metrics-history.js +68 -0
  21. package/dist/server/utils/remote-metrics-db.d.ts +29 -0
  22. package/dist/server/utils/remote-metrics-db.js +134 -0
  23. package/dist/server/utils/remote-metrics-poller.d.ts +8 -0
  24. package/dist/server/utils/remote-metrics-poller.js +67 -0
  25. package/package.json +86 -73
  26. package/scripts/postinstall.js +36 -36
  27. package/src/client/build/asset-manifest.json +6 -6
  28. package/src/client/build/favicon.ico +2 -2
  29. package/src/client/build/index.html +1 -1
  30. package/src/client/build/logo192.svg +7 -7
  31. package/src/client/build/logo512.svg +7 -7
  32. package/src/client/build/manifest.json +24 -24
  33. package/src/client/build/static/css/main.9decb204.css +5 -0
  34. package/src/client/build/static/css/main.9decb204.css.map +1 -0
  35. package/src/client/build/static/js/main.28a4a583.js +3 -0
  36. package/src/client/build/static/js/main.28a4a583.js.map +1 -0
  37. package/src/client/build/static/css/main.775772ee.css +0 -5
  38. package/src/client/build/static/css/main.775772ee.css.map +0 -1
  39. package/src/client/build/static/js/main.cbcb09c9.js +0 -3
  40. package/src/client/build/static/js/main.cbcb09c9.js.map +0 -1
  41. /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,8 @@
1
+ declare class RemoteMetricsPoller {
2
+ private timer;
3
+ start(): void;
4
+ stop(): void;
5
+ private poll;
6
+ }
7
+ export declare const remoteMetricsPoller: RemoteMetricsPoller;
8
+ export {};
@@ -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.6.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
- "build:server": "tsc",
16
- "build:client": "cd src/client && npm run build",
17
- "build:bin": "tsc --project tsconfig.bin.json",
18
- "prepare": "npm run build",
19
- "test": "echo \"Error: no test specified\" && exit 1",
20
- "postinstall": "node scripts/postinstall.js"
21
- },
22
- "keywords": [
23
- "pm2",
24
- "gui",
25
- "monitor",
26
- "process-manager",
27
- "dashboard"
28
- ],
29
- "author": "Chandan Bhagat",
30
- "license": "AGPL-3.0-or-later",
31
- "repository": {
32
- "type": "git",
33
- "url": "git+https://github.com/thechandanbhagat/ezpm2gui.git"
34
- },
35
- "description": "A modern web-based GUI for PM2 process manager",
36
- "files": [
37
- "dist/",
38
- "bin/",
39
- "src/client/build/",
40
- "scripts/postinstall.js"
41
- ],
42
- "dependencies": {
43
- "@tailwindcss/postcss": "^4.1.14",
44
- "@types/node-cron": "^3.0.11",
45
- "axios": "^1.9.0",
46
- "dotenv": "^16.5.0",
47
- "chart.js": "^4.4.9",
48
- "cron-parser": "^5.4.0",
49
- "express": "^4.18.3",
50
- "node-cron": "^4.2.1",
51
- "pm2": "^6.0.5",
52
- "react": "^19.1.0",
53
- "react-dom": "^19.1.0",
54
- "react-scripts": "^5.0.1",
55
- "socket.io": "^4.8.1",
56
- "ssh2": "^1.16.0",
57
- "uuid": "^11.0.5"
58
- },
59
- "devDependencies": {
60
- "@types/express": "^4.17.21",
61
- "@types/node": "^22.15.17",
62
- "@types/react": "^19.1.4",
63
- "@types/react-dom": "^19.1.5",
64
- "@types/socket.io": "^3.0.2",
65
- "@types/ssh2": "^1.15.5",
66
- "@types/uuid": "^10.0.0",
67
- "concurrently": "^9.1.2",
68
- "cross-env": "^10.1.0",
69
- "nodemon": "^3.1.10",
70
- "ts-node": "^10.9.2",
71
- "typescript": "^5.8.3"
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
+ }
@@ -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.775772ee.css",
4
- "main.js": "/static/js/main.cbcb09c9.js",
3
+ "main.css": "/static/css/main.9decb204.css",
4
+ "main.js": "/static/js/main.28a4a583.js",
5
5
  "index.html": "/index.html",
6
- "main.775772ee.css.map": "/static/css/main.775772ee.css.map",
7
- "main.cbcb09c9.js.map": "/static/js/main.cbcb09c9.js.map"
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.775772ee.css",
11
- "static/js/main.cbcb09c9.js"
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.cbcb09c9.js"></script><link href="/static/css/main.775772ee.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
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>