ezpm2gui 1.5.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 (45) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +330 -295
  3. package/bin/ezpm2gui.js +10 -10
  4. package/bin/ezpm2gui.ts +51 -51
  5. package/bin/generate-ecosystem.js +36 -36
  6. package/bin/generate-ecosystem.ts +56 -56
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/server/config/cron-jobs.json +1 -0
  10. package/dist/server/config/project-configs.json +235 -236
  11. package/dist/server/config/remote-connections.json +3 -0
  12. package/dist/server/index.js +44 -3
  13. package/dist/server/routes/deployApplication.js +47 -45
  14. package/dist/server/routes/logStreaming.js +31 -24
  15. package/dist/server/routes/modules.js +55 -0
  16. package/dist/server/routes/pageAuth.d.ts +3 -0
  17. package/dist/server/routes/pageAuth.js +177 -0
  18. package/dist/server/routes/remoteConnections.js +13 -9
  19. package/dist/server/routes/remoteMetrics.d.ts +3 -0
  20. package/dist/server/routes/remoteMetrics.js +84 -0
  21. package/dist/server/services/ProjectSetupService.d.ts +1 -1
  22. package/dist/server/services/ProjectSetupService.js +25 -9
  23. package/dist/server/utils/metrics-history.d.ts +21 -0
  24. package/dist/server/utils/metrics-history.js +68 -0
  25. package/dist/server/utils/remote-metrics-db.d.ts +29 -0
  26. package/dist/server/utils/remote-metrics-db.js +134 -0
  27. package/dist/server/utils/remote-metrics-poller.d.ts +8 -0
  28. package/dist/server/utils/remote-metrics-poller.js +67 -0
  29. package/package.json +86 -73
  30. package/scripts/postinstall.js +36 -36
  31. package/src/client/build/asset-manifest.json +6 -6
  32. package/src/client/build/favicon.ico +2 -2
  33. package/src/client/build/index.html +1 -1
  34. package/src/client/build/logo192.svg +7 -7
  35. package/src/client/build/logo512.svg +7 -7
  36. package/src/client/build/manifest.json +24 -24
  37. package/src/client/build/static/css/main.9decb204.css +5 -0
  38. package/src/client/build/static/css/main.9decb204.css.map +1 -0
  39. package/src/client/build/static/js/main.28a4a583.js +3 -0
  40. package/src/client/build/static/js/main.28a4a583.js.map +1 -0
  41. package/src/client/build/static/css/main.2d095544.css +0 -5
  42. package/src/client/build/static/css/main.2d095544.css.map +0 -1
  43. package/src/client/build/static/js/main.17e17668.js +0 -3
  44. package/src/client/build/static/js/main.17e17668.js.map +0 -1
  45. /package/src/client/build/static/js/{main.17e17668.js.LICENSE.txt → main.28a4a583.js.LICENSE.txt} +0 -0
@@ -78,8 +78,9 @@ class ProjectSetupService {
78
78
  this.log('Could not detect project type');
79
79
  return null;
80
80
  }
81
- async setupProject(projectPath, projectType) {
81
+ async setupProject(projectPath, projectType, onLog) {
82
82
  this.log(`Starting setup for ${projectType} project at: ${projectPath}`);
83
+ onLog === null || onLog === void 0 ? void 0 : onLog(`Starting ${projectType} project setup at: ${projectPath}`);
83
84
  const config = this.configs[projectType];
84
85
  if (!config) {
85
86
  throw new Error(`Unknown project type: ${projectType}`);
@@ -96,6 +97,7 @@ class ProjectSetupService {
96
97
  try {
97
98
  // Check if step should be skipped
98
99
  if (this.shouldSkipStep(step, projectPath)) {
100
+ onLog === null || onLog === void 0 ? void 0 : onLog(`[SKIP] ${step.name}`);
99
101
  result.steps.push({
100
102
  name: step.name,
101
103
  success: true,
@@ -106,24 +108,31 @@ class ProjectSetupService {
106
108
  continue;
107
109
  }
108
110
  this.log(`Executing step: ${step.name}`);
109
- const stepResult = await this.executeStep(step, projectPath, result.environment);
111
+ onLog === null || onLog === void 0 ? void 0 : onLog(`\n[STEP] ${step.name}`);
112
+ const stepResult = await this.executeStep(step, projectPath, result.environment, onLog);
110
113
  const duration = Date.now() - stepStart;
111
114
  result.steps.push({
112
115
  ...stepResult,
113
116
  duration
114
117
  });
115
118
  if (!stepResult.success && step.required) {
119
+ onLog === null || onLog === void 0 ? void 0 : onLog(`[ERROR] Step failed: ${step.name}`);
116
120
  result.success = false;
117
121
  result.errors.push(`Required step failed: ${step.name} - ${stepResult.error}`);
118
122
  break;
119
123
  }
120
124
  else if (!stepResult.success) {
125
+ onLog === null || onLog === void 0 ? void 0 : onLog(`[WARN] Optional step failed: ${step.name}`);
121
126
  result.warnings.push(`Optional step failed: ${step.name} - ${stepResult.error}`);
122
127
  }
128
+ else {
129
+ onLog === null || onLog === void 0 ? void 0 : onLog(`[OK] ${step.name} completed (${duration}ms)`);
130
+ }
123
131
  }
124
132
  catch (error) {
125
133
  const duration = Date.now() - stepStart;
126
134
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
135
+ onLog === null || onLog === void 0 ? void 0 : onLog(`[ERROR] ${step.name}: ${errorMessage}`);
127
136
  result.steps.push({
128
137
  name: step.name,
129
138
  success: false,
@@ -152,13 +161,13 @@ class ProjectSetupService {
152
161
  result.environment.PYTHON_INTERPRETER = venvPath;
153
162
  }
154
163
  }
155
- // Validate the setup
164
+ // Validate the setup (advisory only — step success/failure is the real gate)
156
165
  const validationResult = await this.validateSetup(projectPath, config);
157
166
  if (!validationResult.success) {
158
- result.success = false;
159
- result.errors.push(...validationResult.errors);
167
+ result.warnings.push(...validationResult.errors);
160
168
  }
161
169
  this.log(`Setup completed. Success: ${result.success}`);
170
+ onLog === null || onLog === void 0 ? void 0 : onLog(`\nSetup ${result.success ? 'completed successfully' : 'failed'}.`);
162
171
  return result;
163
172
  }
164
173
  shouldSkipStep(step, projectPath) {
@@ -216,7 +225,7 @@ class ProjectSetupService {
216
225
  }
217
226
  return false;
218
227
  }
219
- async executeStep(step, projectPath, environment) {
228
+ async executeStep(step, projectPath, environment, onLog) {
220
229
  const workingDir = step.workingDirectory === 'project' ? projectPath : process.cwd();
221
230
  let command = step.command;
222
231
  // Handle virtual environment activation for Python
@@ -245,13 +254,20 @@ class ProjectSetupService {
245
254
  (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
246
255
  const text = data.toString();
247
256
  output += text;
248
- // Log real-time output for debugging
249
- console.log(`[${step.name}] ${text.trim()}`);
257
+ for (const line of text.split('\n')) {
258
+ const trimmed = line.trim();
259
+ if (trimmed)
260
+ onLog === null || onLog === void 0 ? void 0 : onLog(trimmed);
261
+ }
250
262
  });
251
263
  (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
252
264
  const text = data.toString();
253
265
  error += text;
254
- console.error(`[${step.name}] ERROR: ${text.trim()}`);
266
+ for (const line of text.split('\n')) {
267
+ const trimmed = line.trim();
268
+ if (trimmed)
269
+ onLog === null || onLog === void 0 ? void 0 : onLog(`[WARN] ${trimmed}`);
270
+ }
255
271
  });
256
272
  child.on('close', (code) => {
257
273
  const success = code === 0;
@@ -0,0 +1,21 @@
1
+ export interface MetricPoint {
2
+ timestamp: number;
3
+ cpu: number;
4
+ memory: number;
5
+ memoryMB: number;
6
+ memoryPercent: number;
7
+ }
8
+ export interface ProcessHistory {
9
+ pm_id: number;
10
+ name: string;
11
+ status: string;
12
+ history: MetricPoint[];
13
+ }
14
+ declare class MetricsHistoryStore {
15
+ private readonly store;
16
+ record(processList: any[]): void;
17
+ getOne(pm_id: number): ProcessHistory | null;
18
+ getAll(): ProcessHistory[];
19
+ }
20
+ export declare const metricsHistory: MetricsHistoryStore;
21
+ export {};
@@ -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.5.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": "ISC",
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
+ }