ezpm2gui 1.8.0 → 1.9.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 +355 -330
- 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/project-configs.json +235 -235
- package/dist/server/index.js +4 -4
- package/dist/server/routes/deployApplication.js +4 -4
- package/dist/server/routes/logStreaming.js +35 -4
- package/dist/server/routes/remoteConnections.js +44 -12
- package/dist/server/utils/encryption.d.ts +22 -0
- package/dist/server/utils/encryption.js +53 -0
- package/dist/server/utils/remote-connection.js +3 -3
- package/dist/server/utils/remote-metrics-db.js +59 -59
- package/package.json +86 -86
- 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.2836d066.css +5 -0
- package/src/client/build/static/css/main.2836d066.css.map +1 -0
- package/src/client/build/static/js/{main.28a4a583.js → main.d5c19622.js} +3 -3
- package/src/client/build/static/js/{main.28a4a583.js.map → main.d5c19622.js.map} +1 -1
- package/dist/server/config/cron-jobs.json +0 -1
- package/dist/server/config/remote-connections.json +0 -3
- package/src/client/build/static/css/main.9decb204.css +0 -5
- package/src/client/build/static/css/main.9decb204.css.map +0 -1
- /package/src/client/build/static/js/{main.28a4a583.js.LICENSE.txt → main.d5c19622.js.LICENSE.txt} +0 -0
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const express_1 = __importDefault(require("express"));
|
|
7
7
|
const remote_connection_1 = require("../utils/remote-connection");
|
|
8
|
+
const encryption_1 = require("../utils/encryption");
|
|
8
9
|
const router = express_1.default.Router();
|
|
9
10
|
// @group Security : Validate remote log file paths before shell interpolation
|
|
10
11
|
const SHELL_UNSAFE_CHARS = /['"`;$|&<>(){}\\\n\r\0]/;
|
|
@@ -26,6 +27,29 @@ const safeLogLines = (raw) => {
|
|
|
26
27
|
return 200;
|
|
27
28
|
return Math.min(n, MAX_LOG_LINES);
|
|
28
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Decrypts a field that may be either a plain string (legacy/internal)
|
|
32
|
+
* or an EncryptedPayload object produced by the client-side hybrid encryptor.
|
|
33
|
+
*/
|
|
34
|
+
function decryptField(value) {
|
|
35
|
+
if (!value)
|
|
36
|
+
return undefined;
|
|
37
|
+
if (typeof value === 'string')
|
|
38
|
+
return value;
|
|
39
|
+
try {
|
|
40
|
+
return (0, encryption_1.decryptTransitPayload)(value);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new Error('Failed to decrypt field — possible tampering or key mismatch');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Expose RSA public key so the client can encrypt sensitive fields before sending.
|
|
48
|
+
* GET /api/remote/public-key
|
|
49
|
+
*/
|
|
50
|
+
router.get('/public-key', (_req, res) => {
|
|
51
|
+
res.json({ publicKey: (0, encryption_1.getRSAPublicKey)() });
|
|
52
|
+
});
|
|
29
53
|
/**
|
|
30
54
|
* Connect to an existing remote server
|
|
31
55
|
* POST /api/remote/:connectionId/connect
|
|
@@ -848,17 +872,21 @@ router.get('/connections', async (req, res) => {
|
|
|
848
872
|
*/
|
|
849
873
|
router.post('/connections', (req, res) => {
|
|
850
874
|
try {
|
|
851
|
-
const
|
|
852
|
-
if (!
|
|
875
|
+
const body = req.body;
|
|
876
|
+
if (!body.name || !body.host || !body.username) {
|
|
853
877
|
return res.status(400).json({
|
|
854
878
|
success: false,
|
|
855
879
|
error: 'Missing required connection parameters: name, host, or username'
|
|
856
880
|
});
|
|
857
881
|
}
|
|
858
|
-
//
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
882
|
+
// Decrypt sensitive fields that may have been encrypted by the client
|
|
883
|
+
const connectionConfig = {
|
|
884
|
+
...body,
|
|
885
|
+
port: body.port || 22,
|
|
886
|
+
password: decryptField(body.password),
|
|
887
|
+
privateKey: decryptField(body.privateKey),
|
|
888
|
+
passphrase: decryptField(body.passphrase),
|
|
889
|
+
};
|
|
862
890
|
// Create the connection
|
|
863
891
|
const connectionId = remote_connection_1.remoteConnectionManager.createConnection(connectionConfig);
|
|
864
892
|
res.status(201).json({
|
|
@@ -881,17 +909,21 @@ router.post('/connections', (req, res) => {
|
|
|
881
909
|
router.put('/connections/:connectionId', async (req, res) => {
|
|
882
910
|
try {
|
|
883
911
|
const { connectionId } = req.params;
|
|
884
|
-
const
|
|
885
|
-
if (!
|
|
912
|
+
const body = req.body;
|
|
913
|
+
if (!body.name || !body.host || !body.username) {
|
|
886
914
|
return res.status(400).json({
|
|
887
915
|
success: false,
|
|
888
916
|
error: 'Missing required connection parameters: name, host, or username'
|
|
889
917
|
});
|
|
890
918
|
}
|
|
891
|
-
//
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
919
|
+
// Decrypt sensitive fields that may have been encrypted by the client
|
|
920
|
+
const connectionConfig = {
|
|
921
|
+
...body,
|
|
922
|
+
port: body.port || 22,
|
|
923
|
+
password: decryptField(body.password),
|
|
924
|
+
privateKey: decryptField(body.privateKey),
|
|
925
|
+
passphrase: decryptField(body.passphrase),
|
|
926
|
+
};
|
|
895
927
|
// Update the connection
|
|
896
928
|
const success = await remote_connection_1.remoteConnectionManager.updateConnection(connectionId, connectionConfig);
|
|
897
929
|
if (!success) {
|
|
@@ -10,3 +10,25 @@ export declare function encrypt(text: string): string;
|
|
|
10
10
|
* @returns The decrypted text
|
|
11
11
|
*/
|
|
12
12
|
export declare function decrypt(encryptedText: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Returns the server's RSA public key in PEM (SPKI) format.
|
|
15
|
+
* Expose this via a GET endpoint so clients can encrypt before sending.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getRSAPublicKey(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Hybrid-encrypted payload sent by the client.
|
|
20
|
+
* - encryptedKey : RSA-OAEP encrypted 256-bit AES key (base64)
|
|
21
|
+
* - iv : 12-byte AES-GCM IV (base64)
|
|
22
|
+
* - data : AES-256-GCM ciphertext + 16-byte auth-tag (base64)
|
|
23
|
+
*/
|
|
24
|
+
export interface EncryptedPayload {
|
|
25
|
+
encryptedKey: string;
|
|
26
|
+
iv: string;
|
|
27
|
+
data: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Decrypts a hybrid-encrypted payload produced by the client.
|
|
31
|
+
* 1. Unwraps the AES key with RSA-OAEP (SHA-256)
|
|
32
|
+
* 2. Decrypts the data with AES-256-GCM
|
|
33
|
+
*/
|
|
34
|
+
export declare function decryptTransitPayload(payload: EncryptedPayload): string;
|
|
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.encrypt = encrypt;
|
|
7
7
|
exports.decrypt = decrypt;
|
|
8
|
+
exports.getRSAPublicKey = getRSAPublicKey;
|
|
9
|
+
exports.decryptTransitPayload = decryptTransitPayload;
|
|
8
10
|
/**
|
|
9
11
|
* Utility for encrypting and decrypting sensitive data
|
|
10
12
|
*/
|
|
@@ -58,3 +60,54 @@ function decrypt(encryptedText) {
|
|
|
58
60
|
return '';
|
|
59
61
|
}
|
|
60
62
|
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// RSA-OAEP key pair for in-transit encryption
|
|
65
|
+
// A fresh key pair is generated once per server process and kept in memory.
|
|
66
|
+
// The public key is shared with clients so they can encrypt sensitive fields
|
|
67
|
+
// before sending them over the network. The private key never leaves the server.
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
let _rsaPrivateKey = null;
|
|
70
|
+
let _rsaPublicKeyPem = null;
|
|
71
|
+
function getOrCreateRSAKeyPair() {
|
|
72
|
+
if (!_rsaPrivateKey || !_rsaPublicKeyPem) {
|
|
73
|
+
const { privateKey, publicKey } = crypto_1.default.generateKeyPairSync('rsa', {
|
|
74
|
+
modulusLength: 2048,
|
|
75
|
+
});
|
|
76
|
+
_rsaPrivateKey = privateKey;
|
|
77
|
+
_rsaPublicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
|
|
78
|
+
}
|
|
79
|
+
return { publicKeyPem: _rsaPublicKeyPem, privateKey: _rsaPrivateKey };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Returns the server's RSA public key in PEM (SPKI) format.
|
|
83
|
+
* Expose this via a GET endpoint so clients can encrypt before sending.
|
|
84
|
+
*/
|
|
85
|
+
function getRSAPublicKey() {
|
|
86
|
+
return getOrCreateRSAKeyPair().publicKeyPem;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Decrypts a hybrid-encrypted payload produced by the client.
|
|
90
|
+
* 1. Unwraps the AES key with RSA-OAEP (SHA-256)
|
|
91
|
+
* 2. Decrypts the data with AES-256-GCM
|
|
92
|
+
*/
|
|
93
|
+
function decryptTransitPayload(payload) {
|
|
94
|
+
const { privateKey } = getOrCreateRSAKeyPair();
|
|
95
|
+
// Step 1 — decrypt the AES-256 key with RSA-OAEP / SHA-256
|
|
96
|
+
const encryptedAesKey = Buffer.from(payload.encryptedKey, 'base64');
|
|
97
|
+
const aesKey = crypto_1.default.privateDecrypt({
|
|
98
|
+
key: privateKey,
|
|
99
|
+
padding: crypto_1.default.constants.RSA_PKCS1_OAEP_PADDING,
|
|
100
|
+
oaepHash: 'sha256',
|
|
101
|
+
}, encryptedAesKey);
|
|
102
|
+
// Step 2 — decrypt data with AES-256-GCM
|
|
103
|
+
// WebCrypto appends the 16-byte auth tag at the end of the ciphertext buffer
|
|
104
|
+
const iv = Buffer.from(payload.iv, 'base64');
|
|
105
|
+
const encryptedBuf = Buffer.from(payload.data, 'base64');
|
|
106
|
+
const authTag = encryptedBuf.slice(encryptedBuf.length - 16);
|
|
107
|
+
const ciphertext = encryptedBuf.slice(0, encryptedBuf.length - 16);
|
|
108
|
+
const decipher = crypto_1.default.createDecipheriv('aes-256-gcm', aesKey, iv);
|
|
109
|
+
decipher.setAuthTag(authTag);
|
|
110
|
+
let decrypted = decipher.update(ciphertext, undefined, 'utf8');
|
|
111
|
+
decrypted += decipher.final('utf8');
|
|
112
|
+
return decrypted;
|
|
113
|
+
}
|
|
@@ -78,9 +78,9 @@ class RemoteConnection extends events_1.EventEmitter {
|
|
|
78
78
|
host: this.config.host,
|
|
79
79
|
port: this.config.port || 22,
|
|
80
80
|
username: this.config.username,
|
|
81
|
-
// Add reasonable timeouts
|
|
82
|
-
readyTimeout:
|
|
83
|
-
keepaliveInterval:
|
|
81
|
+
// Add reasonable timeouts optimized for VPN/remote scenarios
|
|
82
|
+
readyTimeout: 20000, // 20 seconds to establish connection (was 10s)
|
|
83
|
+
keepaliveInterval: 10000 // Send keepalive every 10 seconds (was 30s) for aggressive firewalls
|
|
84
84
|
};
|
|
85
85
|
// Debug output
|
|
86
86
|
console.log(`Connecting to ${this.config.host}:${this.config.port} as ${this.config.username}`);
|
|
@@ -22,24 +22,24 @@ class RemoteMetricsDB {
|
|
|
22
22
|
}
|
|
23
23
|
// @group RemoteMetricsDB : Create tables and indexes on first run
|
|
24
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);
|
|
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
43
|
`);
|
|
44
44
|
}
|
|
45
45
|
// @group RemoteMetricsDB : Delete records older than 30 days to keep DB lean
|
|
@@ -49,18 +49,18 @@ class RemoteMetricsDB {
|
|
|
49
49
|
}
|
|
50
50
|
// @group RemoteMetricsDB : Persist a single data-point
|
|
51
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 (?, ?, ?, ?, ?, ?, ?, ?)
|
|
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
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
57
|
}
|
|
58
58
|
// @group RemoteMetricsDB : Bulk-insert multiple data-points in one transaction (efficient)
|
|
59
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 (?, ?, ?, ?, ?, ?, ?, ?)
|
|
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
64
|
`);
|
|
65
65
|
const tx = this.db.transaction(() => {
|
|
66
66
|
for (const row of rows) {
|
|
@@ -71,59 +71,59 @@ class RemoteMetricsDB {
|
|
|
71
71
|
}
|
|
72
72
|
// @group RemoteMetricsDB : Return all (connection_id, connection_name) pairs that have data
|
|
73
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
|
|
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
78
|
`).all();
|
|
79
79
|
}
|
|
80
80
|
// @group RemoteMetricsDB : Return distinct process names recorded for a connection
|
|
81
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
|
|
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
87
|
`).all(connectionId);
|
|
88
88
|
return rows.map(r => r.process_name);
|
|
89
89
|
}
|
|
90
90
|
// @group RemoteMetricsDB : Return metric rows for a specific process within a time range
|
|
91
91
|
getMetrics(connectionId, processName, from, to, maxPoints = 500) {
|
|
92
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 ?
|
|
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
96
|
`).get(connectionId, processName, from, to).cnt;
|
|
97
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
|
|
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
102
|
`).all(connectionId, processName, from, to);
|
|
103
103
|
}
|
|
104
104
|
// Downsample: pick every Nth row so we stay under maxPoints
|
|
105
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 ?
|
|
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
112
|
`).all(connectionId, processName, from, to, step, maxPoints);
|
|
113
113
|
}
|
|
114
114
|
// @group RemoteMetricsDB : Return latest snapshot for all processes on a connection
|
|
115
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
|
|
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
127
|
`).all(connectionId, connectionId);
|
|
128
128
|
}
|
|
129
129
|
close() {
|
package/package.json
CHANGED
|
@@ -1,86 +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
|
-
"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
|
+
{
|
|
2
|
+
"name": "ezpm2gui",
|
|
3
|
+
"version": "1.9.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.2836d066.css",
|
|
4
|
+
"main.js": "/static/js/main.d5c19622.js",
|
|
5
5
|
"index.html": "/index.html",
|
|
6
|
-
"main.
|
|
7
|
-
"main.
|
|
6
|
+
"main.2836d066.css.map": "/static/css/main.2836d066.css.map",
|
|
7
|
+
"main.d5c19622.js.map": "/static/js/main.d5c19622.js.map"
|
|
8
8
|
},
|
|
9
9
|
"entrypoints": [
|
|
10
|
-
"static/css/main.
|
|
11
|
-
"static/js/main.
|
|
10
|
+
"static/css/main.2836d066.css",
|
|
11
|
+
"static/js/main.d5c19622.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.d5c19622.js"></script><link href="/static/css/main.2836d066.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|