ezpm2gui 1.3.1 → 1.4.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 +12 -11
- package/dist/server/config/cron-jobs.json +1 -18
- package/dist/server/config/remote-connections.json +1 -20
- package/dist/server/daemon/ezpm2gui.err.log +414 -0
- package/dist/server/daemon/ezpm2gui.exe +0 -0
- package/dist/server/daemon/ezpm2gui.exe.config +6 -0
- package/dist/server/daemon/ezpm2gui.out.log +289 -0
- package/dist/server/daemon/ezpm2gui.wrapper.log +172 -0
- package/dist/server/daemon/ezpm2gui.xml +32 -0
- package/dist/server/index.js +72 -86
- package/dist/server/routes/logStreaming.js +20 -13
- package/dist/server/routes/modules.js +89 -69
- package/dist/server/routes/remoteConnections.js +19 -40
- package/dist/server/utils/encryption.js +0 -12
- package/dist/server/utils/pm2-connection.d.ts +1 -1
- package/dist/server/utils/pm2-connection.js +1 -3
- package/dist/server/utils/remote-connection.d.ts +18 -3
- package/dist/server/utils/remote-connection.js +100 -79
- package/package.json +4 -2
- package/src/client/build/asset-manifest.json +6 -6
- package/src/client/build/index.html +1 -1
- package/src/client/build/static/css/main.c506cba5.css +5 -0
- package/src/client/build/static/css/main.c506cba5.css.map +1 -0
- package/src/client/build/static/js/main.5278cddd.js +3 -0
- package/src/client/build/static/js/main.5278cddd.js.map +1 -0
- package/dist/server/config/cron-scripts/6d8d5e1d-2bc8-463f-82a6-6c294f2b9dbe.sh +0 -2
- package/dist/server/config/project-configs.json +0 -236
- package/dist/server/logs/deployment.log +0 -12
- package/dist/server/utils/dialog.d.ts +0 -1
- package/dist/server/utils/dialog.js +0 -16
- package/dist/server/utils/upload.d.ts +0 -3
- package/dist/server/utils/upload.js +0 -39
- package/src/client/build/static/css/main.d46bc75c.css +0 -5
- package/src/client/build/static/css/main.d46bc75c.css.map +0 -1
- package/src/client/build/static/js/main.b0e1c9b1.js +0 -3
- package/src/client/build/static/js/main.b0e1c9b1.js.map +0 -1
- /package/src/client/build/static/js/{main.b0e1c9b1.js.LICENSE.txt → main.5278cddd.js.LICENSE.txt} +0 -0
package/dist/server/index.js
CHANGED
|
@@ -29,7 +29,16 @@ function createServer() {
|
|
|
29
29
|
cors: {
|
|
30
30
|
origin: '*',
|
|
31
31
|
methods: ['GET', 'POST']
|
|
32
|
-
}
|
|
32
|
+
},
|
|
33
|
+
// Increase ping timeout to prevent false positive disconnections
|
|
34
|
+
pingTimeout: 10000, // How long to wait for a pong response (default: 5000ms)
|
|
35
|
+
pingInterval: 25000, // How often to send ping packets (default: 25000ms)
|
|
36
|
+
// Allow reconnection attempts
|
|
37
|
+
allowEIO3: true,
|
|
38
|
+
// Transport configuration
|
|
39
|
+
transports: ['websocket', 'polling'],
|
|
40
|
+
// Upgrade timeout
|
|
41
|
+
upgradeTimeout: 10000
|
|
33
42
|
});
|
|
34
43
|
// Configure middleware
|
|
35
44
|
app.use(express_1.default.json());
|
|
@@ -73,42 +82,36 @@ function createServer() {
|
|
|
73
82
|
}
|
|
74
83
|
});
|
|
75
84
|
// Action endpoints (start, stop, restart, delete)
|
|
76
|
-
app.post('/api/process/:id/:action', (req, res) => {
|
|
85
|
+
app.post('/api/process/:id/:action', async (req, res) => {
|
|
77
86
|
const { id, action } = req.params;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
switch (
|
|
87
|
+
const validActions = ['start', 'stop', 'restart', 'delete'];
|
|
88
|
+
if (!validActions.includes(action)) {
|
|
89
|
+
res.status(400).json({ error: 'Invalid action' });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
await (0, pm2_connection_1.executePM2Command)((callback) => {
|
|
94
|
+
switch (action) {
|
|
86
95
|
case 'start':
|
|
87
|
-
pm2_1.default.start(id,
|
|
96
|
+
pm2_1.default.start(id, callback);
|
|
88
97
|
break;
|
|
89
98
|
case 'stop':
|
|
90
|
-
pm2_1.default.stop(id,
|
|
99
|
+
pm2_1.default.stop(id, callback);
|
|
91
100
|
break;
|
|
92
101
|
case 'restart':
|
|
93
|
-
pm2_1.default.restart(id,
|
|
102
|
+
pm2_1.default.restart(id, callback);
|
|
94
103
|
break;
|
|
95
104
|
case 'delete':
|
|
96
|
-
pm2_1.default.delete(id,
|
|
105
|
+
pm2_1.default.delete(id, callback);
|
|
97
106
|
break;
|
|
98
|
-
default:
|
|
99
|
-
cb(new Error('Invalid action'));
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
processAction(action, (err) => {
|
|
103
|
-
pm2_1.default.disconnect();
|
|
104
|
-
if (err) {
|
|
105
|
-
console.error(err);
|
|
106
|
-
res.status(500).json({ error: `Failed to ${action} process` });
|
|
107
|
-
return;
|
|
108
107
|
}
|
|
109
|
-
res.json({ success: true, message: `Process ${id} ${action} request received` });
|
|
110
108
|
});
|
|
111
|
-
|
|
109
|
+
res.json({ success: true, message: `Process ${id} ${action} request received` });
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
console.error(err);
|
|
113
|
+
res.status(500).json({ error: `Failed to ${action} process` });
|
|
114
|
+
}
|
|
112
115
|
});
|
|
113
116
|
// Get system metrics
|
|
114
117
|
app.get('/api/metrics', (req, res) => {
|
|
@@ -125,75 +128,58 @@ function createServer() {
|
|
|
125
128
|
res.json(metrics);
|
|
126
129
|
});
|
|
127
130
|
// Get process logs
|
|
128
|
-
app.get('/api/logs/:id/:type', (req, res) => {
|
|
131
|
+
app.get('/api/logs/:id/:type', async (req, res) => {
|
|
132
|
+
var _a, _b;
|
|
129
133
|
const { id, type } = req.params;
|
|
130
134
|
const logType = type === 'err' ? 'err' : 'out';
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
try {
|
|
136
|
+
const processDesc = await (0, pm2_connection_1.executePM2Command)((callback) => {
|
|
137
|
+
pm2_1.default.describe(id, callback);
|
|
138
|
+
});
|
|
139
|
+
if (!processDesc || processDesc.length === 0) {
|
|
140
|
+
res.status(404).json({ error: 'Process not found' });
|
|
135
141
|
return;
|
|
136
142
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
fs.readSync(fd, buffer, 0, readSize, position);
|
|
163
|
-
fs.closeSync(fd);
|
|
164
|
-
logContent = buffer.toString('utf8');
|
|
165
|
-
}
|
|
166
|
-
const logs = logContent.split('\n').filter((line) => line.trim() !== '');
|
|
167
|
-
pm2_1.default.disconnect();
|
|
168
|
-
res.json({ logs });
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
console.error(`Error reading log file: ${error}`);
|
|
172
|
-
pm2_1.default.disconnect();
|
|
173
|
-
res.status(500).json({ error: 'Failed to read log file' });
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
});
|
|
143
|
+
const logPath = (_b = (_a = processDesc[0]) === null || _a === void 0 ? void 0 : _a.pm2_env) === null || _b === void 0 ? void 0 : _b[`pm_${logType}_log_path`];
|
|
144
|
+
if (!logPath) {
|
|
145
|
+
res.status(404).json({ error: `Log file for ${logType} not found` });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const fs = require('fs');
|
|
149
|
+
let logContent = '';
|
|
150
|
+
if (fs.existsSync(logPath)) {
|
|
151
|
+
const stats = fs.statSync(logPath);
|
|
152
|
+
const fileSize = stats.size;
|
|
153
|
+
const readSize = Math.min(fileSize, 10 * 1024); // 10KB max
|
|
154
|
+
const position = Math.max(0, fileSize - readSize);
|
|
155
|
+
const buffer = Buffer.alloc(readSize);
|
|
156
|
+
const fd = fs.openSync(logPath, 'r');
|
|
157
|
+
fs.readSync(fd, buffer, 0, readSize, position);
|
|
158
|
+
fs.closeSync(fd);
|
|
159
|
+
logContent = buffer.toString('utf8');
|
|
160
|
+
}
|
|
161
|
+
const logs = logContent.split('\n').filter((line) => line.trim() !== '');
|
|
162
|
+
res.json({ logs });
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
console.error(`Error reading log file: ${err}`);
|
|
166
|
+
res.status(500).json({ error: 'Failed to read log file' });
|
|
167
|
+
}
|
|
177
168
|
});
|
|
178
169
|
// WebSocket for real-time updates
|
|
179
170
|
io.on('connection', (socket) => {
|
|
180
171
|
console.log('Client connected');
|
|
181
|
-
// Send process updates every 3 seconds
|
|
182
|
-
const processInterval = setInterval(() => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
pm2_1.default.list((err, processList) => {
|
|
189
|
-
pm2_1.default.disconnect();
|
|
190
|
-
if (err) {
|
|
191
|
-
console.error(err);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
socket.emit('processes', processList);
|
|
172
|
+
// Send process updates every 3 seconds using shared pooled connection
|
|
173
|
+
const processInterval = setInterval(async () => {
|
|
174
|
+
try {
|
|
175
|
+
const processList = await (0, pm2_connection_1.executePM2Command)((callback) => {
|
|
176
|
+
pm2_1.default.list(callback);
|
|
195
177
|
});
|
|
196
|
-
|
|
178
|
+
socket.emit('processes', processList);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
console.error('Failed to list PM2 processes:', err);
|
|
182
|
+
}
|
|
197
183
|
}, 3000);
|
|
198
184
|
// Send system metrics every 2 seconds
|
|
199
185
|
const metricsInterval = setInterval(() => {
|
|
@@ -38,8 +38,8 @@ const getLogStream = (io, processId, logType) => {
|
|
|
38
38
|
tail.stdout.on('data', (data) => {
|
|
39
39
|
const lines = data.toString().split('\n').filter((line) => line.trim() !== '');
|
|
40
40
|
lines.forEach((line) => {
|
|
41
|
-
// Emit
|
|
42
|
-
io.emit('log-line', {
|
|
41
|
+
// Emit only to clients subscribed to this specific log stream
|
|
42
|
+
io.to(streamKey).emit('log-line', {
|
|
43
43
|
processId,
|
|
44
44
|
logType,
|
|
45
45
|
line
|
|
@@ -70,17 +70,23 @@ const getRemoteLogStream = async (io, connectionId, processId) => {
|
|
|
70
70
|
if (!connection || !connection.isConnected()) {
|
|
71
71
|
throw new Error('Connection not found or not connected');
|
|
72
72
|
}
|
|
73
|
-
// Get process info
|
|
73
|
+
// Get process info using the multi-path fallback so pm2 is found regardless of PATH
|
|
74
74
|
console.log(`Getting process info for: ${processId}`);
|
|
75
|
-
const processInfoResult = await connection.
|
|
75
|
+
const processInfoResult = await connection.executePM2Command('jlist');
|
|
76
76
|
if (processInfoResult.code !== 0) {
|
|
77
77
|
throw new Error(`Failed to get process list: ${processInfoResult.stderr}`);
|
|
78
78
|
}
|
|
79
|
+
// Extract JSON from output (pm2 jlist may prefix with non-JSON lines)
|
|
80
|
+
let rawOutput = processInfoResult.stdout.trim();
|
|
81
|
+
const startIdx = rawOutput.indexOf('[');
|
|
82
|
+
const endIdx = rawOutput.lastIndexOf(']') + 1;
|
|
83
|
+
if (startIdx !== -1 && endIdx > 0) {
|
|
84
|
+
rawOutput = rawOutput.substring(startIdx, endIdx);
|
|
85
|
+
}
|
|
79
86
|
let processInfo;
|
|
80
87
|
try {
|
|
81
|
-
const processList = JSON.parse(
|
|
88
|
+
const processList = JSON.parse(rawOutput);
|
|
82
89
|
console.log(`Found ${processList.length} processes`);
|
|
83
|
-
// Find the process by ID
|
|
84
90
|
processInfo = processList.find((proc) => proc.pm_id === parseInt(processId));
|
|
85
91
|
if (!processInfo) {
|
|
86
92
|
throw new Error(`Process with ID ${processId} not found`);
|
|
@@ -96,10 +102,10 @@ const getRemoteLogStream = async (io, connectionId, processId) => {
|
|
|
96
102
|
const streams = {};
|
|
97
103
|
try {
|
|
98
104
|
console.log(`Setting up pm2 logs stream for process: ${processName} (ID: ${processId})`);
|
|
99
|
-
// Use pm2
|
|
100
|
-
//
|
|
101
|
-
const pm2LogsCommand = `
|
|
102
|
-
console.log(`About to create pm2 log stream with command: ${pm2LogsCommand}
|
|
105
|
+
// Use the resolved pm2 invocation (cached from the jlist call above) so the
|
|
106
|
+
// same shell/path that successfully ran pm2 jlist is reused here.
|
|
107
|
+
const pm2LogsCommand = connection.buildPM2StreamCommand(`logs ${processId} --lines 0 --raw`);
|
|
108
|
+
console.log(`About to create pm2 log stream with command: ${pm2LogsCommand}`);
|
|
103
109
|
const logStream = await connection.createLogStream(pm2LogsCommand, true);
|
|
104
110
|
console.log(`Successfully created pm2 logs stream for ${processName}`);
|
|
105
111
|
logStream.on('data', (data) => {
|
|
@@ -154,7 +160,8 @@ const getRemoteLogStream = async (io, connectionId, processId) => {
|
|
|
154
160
|
cleanLine = logMatch[1];
|
|
155
161
|
}
|
|
156
162
|
console.log(`Emitting remote-log-line for ${connectionId}-${processId} (${logType}):`, cleanLine);
|
|
157
|
-
|
|
163
|
+
// Emit only to clients subscribed to this specific remote log stream
|
|
164
|
+
io.to(streamKey).emit('remote-log-line', {
|
|
158
165
|
connectionId,
|
|
159
166
|
processId,
|
|
160
167
|
processName,
|
|
@@ -165,7 +172,7 @@ const getRemoteLogStream = async (io, connectionId, processId) => {
|
|
|
165
172
|
});
|
|
166
173
|
logStream.on('error', (error) => {
|
|
167
174
|
console.error(`Error in pm2 logs stream for ${processName}:`, error);
|
|
168
|
-
io.emit('remote-log-error', {
|
|
175
|
+
io.to(streamKey).emit('remote-log-error', {
|
|
169
176
|
connectionId,
|
|
170
177
|
processId,
|
|
171
178
|
processName,
|
|
@@ -179,7 +186,7 @@ const getRemoteLogStream = async (io, connectionId, processId) => {
|
|
|
179
186
|
}
|
|
180
187
|
catch (error) {
|
|
181
188
|
console.error(`Failed to create pm2 logs stream for ${processName}:`, error);
|
|
182
|
-
io.emit('remote-log-error', {
|
|
189
|
+
io.to(streamKey).emit('remote-log-error', {
|
|
183
190
|
connectionId,
|
|
184
191
|
processId,
|
|
185
192
|
processName,
|
|
@@ -1,106 +1,126 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
const express_1 = require("express");
|
|
4
7
|
const child_process_1 = require("child_process");
|
|
8
|
+
const pm2_1 = __importDefault(require("pm2"));
|
|
9
|
+
const pm2_connection_1 = require("../utils/pm2-connection");
|
|
5
10
|
const router = (0, express_1.Router)();
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
});
|
|
17
|
-
pm2Command.on('close', (code) => {
|
|
11
|
+
// @group Utilities : Resolve pm2 binary path via shell
|
|
12
|
+
const runPM2CLI = (args) => new Promise((resolve, reject) => {
|
|
13
|
+
var _a, _b;
|
|
14
|
+
// Use shell so the OS resolves the global pm2 binary from PATH
|
|
15
|
+
const child = (0, child_process_1.exec)(`pm2 ${args}`);
|
|
16
|
+
let out = '';
|
|
17
|
+
let err = '';
|
|
18
|
+
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (d) => { out += d.toString(); });
|
|
19
|
+
(_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (d) => { err += d.toString(); });
|
|
20
|
+
child.on('close', (code) => {
|
|
18
21
|
if (code !== 0) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
22
|
+
reject(new Error(err || `pm2 exited with code ${code}`));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
resolve({ stdout: out, stderr: err });
|
|
23
26
|
}
|
|
24
|
-
|
|
27
|
+
});
|
|
28
|
+
child.on('error', (e) => reject(e));
|
|
29
|
+
});
|
|
30
|
+
// @group APIEndpoints : List installed PM2 modules
|
|
31
|
+
router.get('/', async (_req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
// Use PM2 Node.js API — modules appear as regular processes with module metadata
|
|
34
|
+
const processList = await (0, pm2_connection_1.executePM2Command)((cb) => pm2_1.default.list(cb));
|
|
35
|
+
// PM2 modules have pm2_env.pmx_module === true
|
|
36
|
+
const modules = processList
|
|
37
|
+
.filter((proc) => { var _a; return ((_a = proc.pm2_env) === null || _a === void 0 ? void 0 : _a.pmx_module) === true; })
|
|
38
|
+
.map((proc) => {
|
|
39
|
+
var _a, _b, _c;
|
|
40
|
+
return ({
|
|
41
|
+
name: proc.name,
|
|
42
|
+
version: ((_a = proc.pm2_env) === null || _a === void 0 ? void 0 : _a.version) || ((_b = proc.pm2_env) === null || _b === void 0 ? void 0 : _b.MODULE_VERSION) || 'N/A',
|
|
43
|
+
status: ((_c = proc.pm2_env) === null || _c === void 0 ? void 0 : _c.status) || 'unknown',
|
|
44
|
+
pid: proc.pid,
|
|
45
|
+
pm_id: proc.pm_id,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
res.json(modules);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// Fallback: parse pm2 module:list CLI output
|
|
25
52
|
try {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
53
|
+
const { stdout } = await runPM2CLI('module:list');
|
|
54
|
+
const moduleLines = stdout
|
|
55
|
+
.split('\n')
|
|
56
|
+
.filter(line => line.includes('│') && !line.includes('Module') && line.trim() !== '');
|
|
57
|
+
const modules = moduleLines
|
|
58
|
+
.map(line => {
|
|
28
59
|
const parts = line.split('│').map(part => part.trim()).filter(Boolean);
|
|
29
60
|
if (parts.length >= 3) {
|
|
30
|
-
return {
|
|
31
|
-
name: parts[0],
|
|
32
|
-
version: parts[1],
|
|
33
|
-
status: parts[2]
|
|
34
|
-
};
|
|
61
|
+
return { name: parts[0], version: parts[1], status: parts[2] };
|
|
35
62
|
}
|
|
36
63
|
return null;
|
|
37
|
-
})
|
|
64
|
+
})
|
|
65
|
+
.filter(Boolean);
|
|
38
66
|
res.json(modules);
|
|
39
67
|
}
|
|
40
|
-
catch (
|
|
68
|
+
catch (fallbackError) {
|
|
41
69
|
res.status(500).json({
|
|
42
|
-
error: 'Failed to
|
|
43
|
-
details:
|
|
70
|
+
error: 'Failed to list PM2 modules',
|
|
71
|
+
details: fallbackError.message
|
|
44
72
|
});
|
|
45
73
|
}
|
|
46
|
-
}
|
|
74
|
+
}
|
|
47
75
|
});
|
|
48
|
-
// Install a module
|
|
49
|
-
router.post('/install', (req, res) => {
|
|
76
|
+
// @group APIEndpoints : Install a PM2 module
|
|
77
|
+
router.post('/install', async (req, res) => {
|
|
50
78
|
const { moduleName } = req.body;
|
|
51
79
|
if (!moduleName) {
|
|
52
80
|
return res.status(400).json({ error: 'Module name is required' });
|
|
53
81
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
pm2Command.stderr.on('data', (data) => {
|
|
61
|
-
errorOutput += data.toString();
|
|
62
|
-
});
|
|
63
|
-
pm2Command.on('close', (code) => {
|
|
64
|
-
if (code !== 0) {
|
|
65
|
-
return res.status(500).json({
|
|
66
|
-
error: `Failed to install module: ${moduleName}`,
|
|
67
|
-
details: errorOutput
|
|
68
|
-
});
|
|
69
|
-
}
|
|
82
|
+
// Basic validation — only allow alphanumeric, @, /, -, .
|
|
83
|
+
if (!/^[@a-zA-Z0-9/_\-.]+$/.test(moduleName)) {
|
|
84
|
+
return res.status(400).json({ error: 'Invalid module name' });
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const { stdout } = await runPM2CLI(`install ${moduleName}`);
|
|
70
88
|
res.json({
|
|
71
89
|
success: true,
|
|
72
90
|
message: `Successfully installed module: ${moduleName}`,
|
|
73
|
-
details:
|
|
91
|
+
details: stdout
|
|
74
92
|
});
|
|
75
|
-
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
res.status(500).json({
|
|
96
|
+
error: `Failed to install module: ${moduleName}`,
|
|
97
|
+
details: error.message
|
|
98
|
+
});
|
|
99
|
+
}
|
|
76
100
|
});
|
|
77
|
-
// Uninstall a module
|
|
78
|
-
router.delete('/:moduleName', (req, res) => {
|
|
101
|
+
// @group APIEndpoints : Uninstall a PM2 module
|
|
102
|
+
router.delete('/:moduleName', async (req, res) => {
|
|
79
103
|
const { moduleName } = req.params;
|
|
80
104
|
if (!moduleName) {
|
|
81
105
|
return res.status(400).json({ error: 'Module name is required' });
|
|
82
106
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
pm2Command.stderr.on('data', (data) => {
|
|
90
|
-
errorOutput += data.toString();
|
|
91
|
-
});
|
|
92
|
-
pm2Command.on('close', (code) => {
|
|
93
|
-
if (code !== 0) {
|
|
94
|
-
return res.status(500).json({
|
|
95
|
-
error: `Failed to uninstall module: ${moduleName}`,
|
|
96
|
-
details: errorOutput
|
|
97
|
-
});
|
|
98
|
-
}
|
|
107
|
+
// Basic validation
|
|
108
|
+
if (!/^[@a-zA-Z0-9/_\-.]+$/.test(moduleName)) {
|
|
109
|
+
return res.status(400).json({ error: 'Invalid module name' });
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const { stdout } = await runPM2CLI(`uninstall ${moduleName}`);
|
|
99
113
|
res.json({
|
|
100
114
|
success: true,
|
|
101
115
|
message: `Successfully uninstalled module: ${moduleName}`,
|
|
102
|
-
details:
|
|
116
|
+
details: stdout
|
|
103
117
|
});
|
|
104
|
-
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
res.status(500).json({
|
|
121
|
+
error: `Failed to uninstall module: ${moduleName}`,
|
|
122
|
+
details: error.message
|
|
123
|
+
});
|
|
124
|
+
}
|
|
105
125
|
});
|
|
106
126
|
exports.default = router;
|
|
@@ -20,14 +20,11 @@ router.post('/:connectionId/connect', async (req, res) => {
|
|
|
20
20
|
error: 'Connection not found'
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
//
|
|
23
|
+
// Establish the SSH connection only — PM2 detection happens lazily
|
|
24
|
+
// when processes are first requested, avoiding a slow multi-command
|
|
25
|
+
// pre-check that causes browser timeouts on the connect button.
|
|
24
26
|
await connection.connect();
|
|
25
|
-
|
|
26
|
-
const isPM2Installed = await connection.checkPM2Installation();
|
|
27
|
-
res.json({
|
|
28
|
-
success: true,
|
|
29
|
-
isPM2Installed
|
|
30
|
-
});
|
|
27
|
+
res.json({ success: true });
|
|
31
28
|
}
|
|
32
29
|
catch (error) {
|
|
33
30
|
console.error('Connection error:', error);
|
|
@@ -481,29 +478,36 @@ router.get('/:connectionId/logs/:processId', async (req, res) => {
|
|
|
481
478
|
success: false,
|
|
482
479
|
error: 'Connection not established'
|
|
483
480
|
});
|
|
484
|
-
}
|
|
485
|
-
|
|
481
|
+
}
|
|
482
|
+
// Get log paths from PM2 process info — use the PATH-fallback executor so
|
|
483
|
+
// pm2 is found regardless of the remote shell environment (nvm, npm-global, etc.)
|
|
484
|
+
const processInfoResult = await connection.executePM2Command('jlist');
|
|
486
485
|
if (processInfoResult.code !== 0) {
|
|
487
486
|
return res.status(500).json({
|
|
488
487
|
success: false,
|
|
489
|
-
error: 'Failed to get process
|
|
488
|
+
error: 'Failed to get PM2 process list'
|
|
490
489
|
});
|
|
491
490
|
}
|
|
492
491
|
let processInfo;
|
|
493
492
|
try {
|
|
494
|
-
|
|
495
|
-
|
|
493
|
+
let raw = processInfoResult.stdout.trim();
|
|
494
|
+
const start = raw.indexOf('[');
|
|
495
|
+
const end = raw.lastIndexOf(']') + 1;
|
|
496
|
+
if (start !== -1 && end > 0)
|
|
497
|
+
raw = raw.substring(start, end);
|
|
498
|
+
const processList = JSON.parse(raw);
|
|
499
|
+
processInfo = processList.find((p) => p.pm_id === parseInt(processId, 10) || p.name === processId);
|
|
500
|
+
if (!processInfo) {
|
|
496
501
|
return res.status(404).json({
|
|
497
502
|
success: false,
|
|
498
503
|
error: 'Process not found'
|
|
499
504
|
});
|
|
500
505
|
}
|
|
501
|
-
processInfo = processInfo[0];
|
|
502
506
|
}
|
|
503
507
|
catch (parseError) {
|
|
504
508
|
return res.status(500).json({
|
|
505
509
|
success: false,
|
|
506
|
-
error: 'Failed to parse process
|
|
510
|
+
error: 'Failed to parse PM2 process list'
|
|
507
511
|
});
|
|
508
512
|
}
|
|
509
513
|
const outLogPath = (_a = processInfo.pm2_env) === null || _a === void 0 ? void 0 : _a.pm_out_log_path;
|
|
@@ -561,7 +565,7 @@ router.get('/connections', async (req, res) => {
|
|
|
561
565
|
host: conn.host,
|
|
562
566
|
port: conn.port,
|
|
563
567
|
username: conn.username,
|
|
564
|
-
|
|
568
|
+
connected: conn.isConnected(),
|
|
565
569
|
isPM2Installed: conn.isPM2Installed
|
|
566
570
|
}));
|
|
567
571
|
res.json(connectionsList);
|
|
@@ -711,29 +715,4 @@ router.post('/:connectionId/install-pm2', async (req, res) => {
|
|
|
711
715
|
});
|
|
712
716
|
}
|
|
713
717
|
});
|
|
714
|
-
/**
|
|
715
|
-
* Delete a connection configuration
|
|
716
|
-
* DELETE /api/remote/connections/:connectionId
|
|
717
|
-
*/
|
|
718
|
-
router.delete('/connections/:connectionId', async (req, res) => {
|
|
719
|
-
try {
|
|
720
|
-
const { connectionId } = req.params;
|
|
721
|
-
// Disconnect if connected
|
|
722
|
-
await remote_connection_1.remoteConnectionManager.closeConnection(connectionId);
|
|
723
|
-
// Delete the connection from the manager
|
|
724
|
-
const success = remote_connection_1.remoteConnectionManager.deleteConnection(connectionId);
|
|
725
|
-
if (success) {
|
|
726
|
-
res.json({ success: true, message: 'Connection deleted' });
|
|
727
|
-
}
|
|
728
|
-
else {
|
|
729
|
-
res.status(404).json({ success: false, error: 'Connection not found' });
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
catch (error) {
|
|
733
|
-
res.status(500).json({
|
|
734
|
-
success: false,
|
|
735
|
-
error: `Server error: ${error.message}`
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
718
|
exports.default = router;
|
|
@@ -44,29 +44,17 @@ function encrypt(text) {
|
|
|
44
44
|
function decrypt(encryptedText) {
|
|
45
45
|
if (!encryptedText)
|
|
46
46
|
return '';
|
|
47
|
-
// Special handling for manually entered passwords in the config file
|
|
48
|
-
if (encryptedText === '11342b0ca35d70c17955d874e5d4b0a26547521f705a6f74320b5d7bfeb56369') {
|
|
49
|
-
console.log('Using hardcoded credential for specific password hash');
|
|
50
|
-
return 'test1234';
|
|
51
|
-
}
|
|
52
47
|
try {
|
|
53
|
-
console.log(`Attempting to decrypt: ${encryptedText.substring(0, 10)}...`);
|
|
54
48
|
// Ensure key and IV are the correct length
|
|
55
49
|
const key = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_KEY)).digest('base64').slice(0, 32);
|
|
56
50
|
const iv = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_IV)).digest('base64').slice(0, 16);
|
|
57
51
|
const decipher = crypto_1.default.createDecipheriv(ALGORITHM, key, iv);
|
|
58
52
|
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
|
|
59
53
|
decrypted += decipher.final('utf8');
|
|
60
|
-
console.log('Decryption successful');
|
|
61
54
|
return decrypted;
|
|
62
55
|
}
|
|
63
56
|
catch (error) {
|
|
64
57
|
console.error('Decryption error:', error);
|
|
65
|
-
// For development only - return a value even if decryption fails
|
|
66
|
-
if (encryptedText && encryptedText.length > 10) {
|
|
67
|
-
console.log('Returning fallback credential for development');
|
|
68
|
-
return 'test1234';
|
|
69
|
-
}
|
|
70
58
|
return '';
|
|
71
59
|
}
|
|
72
60
|
}
|
|
@@ -13,4 +13,4 @@ export declare const disconnectFromPM2: () => Promise<void>;
|
|
|
13
13
|
* This will connect to PM2 if needed, run the command,
|
|
14
14
|
* and properly handle the result
|
|
15
15
|
*/
|
|
16
|
-
export declare const executePM2Command: <T>(command: (callback: (err: Error | null, result?: T) => void) => void) => Promise<T>;
|
|
16
|
+
export declare const executePM2Command: <T = any>(command: (callback: (err: Error | null, result?: T) => void) => void) => Promise<T>;
|
|
@@ -103,6 +103,7 @@ exports.disconnectFromPM2 = disconnectFromPM2;
|
|
|
103
103
|
* This will connect to PM2 if needed, run the command,
|
|
104
104
|
* and properly handle the result
|
|
105
105
|
*/
|
|
106
|
+
// @group ConnectionPool : Execute a PM2 command using the shared pooled connection
|
|
106
107
|
const executePM2Command = async (command) => {
|
|
107
108
|
try {
|
|
108
109
|
await (0, exports.connectToPM2)();
|
|
@@ -111,9 +112,6 @@ const executePM2Command = async (command) => {
|
|
|
111
112
|
if (err) {
|
|
112
113
|
reject(err);
|
|
113
114
|
}
|
|
114
|
-
else if (result === undefined) {
|
|
115
|
-
reject(new Error('PM2 command returned undefined result'));
|
|
116
|
-
}
|
|
117
115
|
else {
|
|
118
116
|
resolve(result);
|
|
119
117
|
}
|