ezpm2gui 1.0.0 → 1.1.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 +202 -14
- package/bin/ezpm2gui.js +40 -44
- package/bin/ezpm2gui.ts +51 -0
- package/bin/generate-ecosystem.js +24 -22
- package/bin/generate-ecosystem.ts +56 -0
- package/dist/server/index.js +40 -20
- package/dist/server/routes/clusterManagement.d.ts +3 -0
- package/dist/server/routes/clusterManagement.js +152 -0
- package/dist/server/routes/deployApplication.d.ts +3 -0
- package/dist/server/routes/deployApplication.js +163 -0
- package/dist/server/routes/logStreaming.d.ts +5 -0
- package/dist/server/routes/logStreaming.js +102 -0
- package/dist/server/routes/modules.d.ts +3 -0
- package/dist/server/routes/modules.js +106 -0
- package/dist/server/routes/processConfig.d.ts +3 -0
- package/dist/server/routes/processConfig.js +118 -0
- package/dist/server/utils/pm2-connection.d.ts +16 -0
- package/dist/server/utils/pm2-connection.js +141 -0
- package/package.json +19 -5
- package/src/client/build/asset-manifest.json +6 -6
- package/src/client/build/index.html +1 -1
- package/src/client/build/static/css/main.672b8f26.css +2 -0
- package/src/client/build/static/css/main.672b8f26.css.map +1 -0
- package/src/client/build/static/js/main.1d7f99ff.js +156 -0
- package/src/client/build/static/js/{main.dde30e92.js.LICENSE.txt → main.1d7f99ff.js.LICENSE.txt} +19 -0
- package/src/client/build/static/js/main.1d7f99ff.js.map +1 -0
- package/ .npmignore +0 -39
- package/install.bat +0 -22
- package/install.sh +0 -23
- package/src/client/build/static/css/main.c1cbda3a.css +0 -2
- package/src/client/build/static/css/main.c1cbda3a.css.map +0 -1
- package/src/client/build/static/js/main.dde30e92.js +0 -3
- package/src/client/build/static/js/main.dde30e92.js.map +0 -1
- package/src/client/package-lock.json +0 -16192
- package/src/client/package.json +0 -39
- package/src/client/public/index.html +0 -20
- package/src/client/public/manifest.json +0 -25
- package/src/index.ts +0 -24
- package/src/server/index.ts +0 -240
- package/tsconfig.json +0 -18
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
const express_1 = require("express");
|
|
7
|
+
const pm2_1 = __importDefault(require("pm2"));
|
|
8
|
+
const router = (0, express_1.Router)();
|
|
9
|
+
// Get cluster information for a specific process
|
|
10
|
+
router.get('/:id', (req, res) => {
|
|
11
|
+
const { id } = req.params;
|
|
12
|
+
pm2_1.default.connect((err) => {
|
|
13
|
+
if (err) {
|
|
14
|
+
console.error(err);
|
|
15
|
+
res.status(500).json({ error: 'Failed to connect to PM2' });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
pm2_1.default.describe(id, (err, processDesc) => {
|
|
19
|
+
pm2_1.default.disconnect();
|
|
20
|
+
if (err || !processDesc || processDesc.length === 0) {
|
|
21
|
+
res.status(404).json({ error: 'Process not found' });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const process = processDesc[0];
|
|
25
|
+
const clusterInfo = {
|
|
26
|
+
pm_id: process.pm_id,
|
|
27
|
+
name: process.name,
|
|
28
|
+
instances: process.pm2_env.instances,
|
|
29
|
+
exec_mode: process.pm2_env.exec_mode,
|
|
30
|
+
isCluster: process.pm2_env.exec_mode === 'cluster_mode'
|
|
31
|
+
};
|
|
32
|
+
res.json(clusterInfo);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
// Scale process (change number of instances)
|
|
37
|
+
router.post('/:id/scale', (req, res) => {
|
|
38
|
+
const { id } = req.params;
|
|
39
|
+
const { instances } = req.body;
|
|
40
|
+
if (!instances || isNaN(parseInt(instances))) {
|
|
41
|
+
res.status(400).json({ error: 'Valid number of instances is required' });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
pm2_1.default.connect((err) => {
|
|
45
|
+
if (err) {
|
|
46
|
+
console.error(err);
|
|
47
|
+
res.status(500).json({ error: 'Failed to connect to PM2' });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Using PM2 API to scale a process
|
|
51
|
+
// TypeScript doesn't recognize 'scale' method, so we're using it with a type assertion
|
|
52
|
+
pm2_1.default.scale(id, parseInt(instances), (err) => {
|
|
53
|
+
pm2_1.default.disconnect();
|
|
54
|
+
if (err) {
|
|
55
|
+
console.error(err);
|
|
56
|
+
res.status(500).json({ error: 'Failed to scale process' });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
res.json({ success: true, message: `Process ${id} scaled to ${instances} instances` });
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// Reload instances (zero-downtime reload)
|
|
64
|
+
router.post('/:id/reload', (req, res) => {
|
|
65
|
+
const { id } = req.params;
|
|
66
|
+
pm2_1.default.connect((err) => {
|
|
67
|
+
if (err) {
|
|
68
|
+
console.error(err);
|
|
69
|
+
res.status(500).json({ error: 'Failed to connect to PM2' });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
pm2_1.default.reload(id, (err) => {
|
|
73
|
+
pm2_1.default.disconnect();
|
|
74
|
+
if (err) {
|
|
75
|
+
console.error(err);
|
|
76
|
+
res.status(500).json({ error: 'Failed to reload process' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
res.json({ success: true, message: `Process ${id} reloaded with zero-downtime` });
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
// Change execution mode between 'fork' and 'cluster'
|
|
84
|
+
router.post('/:id/exec-mode', (req, res) => {
|
|
85
|
+
const { id } = req.params;
|
|
86
|
+
const { mode } = req.body;
|
|
87
|
+
if (!mode || (mode !== 'fork' && mode !== 'cluster')) {
|
|
88
|
+
res.status(400).json({ error: 'Valid execution mode (fork or cluster) is required' });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
pm2_1.default.connect((err) => {
|
|
92
|
+
if (err) {
|
|
93
|
+
console.error(err);
|
|
94
|
+
res.status(500).json({ error: 'Failed to connect to PM2' });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// First get the current process information to preserve settings
|
|
98
|
+
pm2_1.default.describe(id, (err, processDesc) => {
|
|
99
|
+
if (err || !processDesc || processDesc.length === 0) {
|
|
100
|
+
pm2_1.default.disconnect();
|
|
101
|
+
res.status(404).json({ error: 'Process not found' });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const process = processDesc[0];
|
|
105
|
+
const pm2Env = process.pm2_env;
|
|
106
|
+
// Update the execution mode
|
|
107
|
+
const updateOptions = {
|
|
108
|
+
script: pm2Env.pm_exec_path,
|
|
109
|
+
name: process.name,
|
|
110
|
+
instances: pm2Env.instances || 1,
|
|
111
|
+
exec_mode: mode === 'cluster' ? 'cluster_mode' : 'fork_mode',
|
|
112
|
+
// Preserve other settings
|
|
113
|
+
cwd: pm2Env.pm_cwd,
|
|
114
|
+
watch: pm2Env.watch || false,
|
|
115
|
+
ignore_watch: pm2Env.ignore_watch || [],
|
|
116
|
+
env: pm2Env.env || {}
|
|
117
|
+
};
|
|
118
|
+
// Stop the existing process
|
|
119
|
+
pm2_1.default.stop(id, (stopErr) => {
|
|
120
|
+
if (stopErr) {
|
|
121
|
+
pm2_1.default.disconnect();
|
|
122
|
+
console.error(stopErr);
|
|
123
|
+
res.status(500).json({ error: 'Failed to stop process for exec mode change' });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Delete the existing process
|
|
127
|
+
pm2_1.default.del(id, (delErr) => {
|
|
128
|
+
if (delErr) {
|
|
129
|
+
pm2_1.default.disconnect();
|
|
130
|
+
console.error(delErr);
|
|
131
|
+
res.status(500).json({ error: 'Failed to delete process for exec mode change' });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Start with new settings
|
|
135
|
+
pm2_1.default.start(updateOptions, (startErr) => {
|
|
136
|
+
pm2_1.default.disconnect();
|
|
137
|
+
if (startErr) {
|
|
138
|
+
console.error(startErr);
|
|
139
|
+
res.status(500).json({ error: 'Failed to restart process with new exec mode' });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
res.json({
|
|
143
|
+
success: true,
|
|
144
|
+
message: `Process ${process.name} execution mode changed to ${mode}`
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
exports.default = router;
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
const express_1 = require("express");
|
|
7
|
+
const pm2_1 = __importDefault(require("pm2"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const router = (0, express_1.Router)();
|
|
11
|
+
// Deploy a new application
|
|
12
|
+
router.post('/', (req, res) => {
|
|
13
|
+
const { name, script, cwd, instances, exec_mode, autorestart, watch, max_memory_restart, env } = req.body;
|
|
14
|
+
// Validate required fields
|
|
15
|
+
if (!name || !script) {
|
|
16
|
+
return res.status(400).json({ error: 'Name and script path are required' });
|
|
17
|
+
}
|
|
18
|
+
// Validate script path exists
|
|
19
|
+
if (!fs_1.default.existsSync(script)) {
|
|
20
|
+
return res.status(400).json({ error: `Script file not found: ${script}` });
|
|
21
|
+
}
|
|
22
|
+
// Create deployment configuration
|
|
23
|
+
const appConfig = {
|
|
24
|
+
name,
|
|
25
|
+
script,
|
|
26
|
+
cwd: cwd || path_1.default.dirname(script),
|
|
27
|
+
instances: parseInt(instances) || 1,
|
|
28
|
+
exec_mode: exec_mode || 'fork',
|
|
29
|
+
autorestart: autorestart !== undefined ? autorestart : true,
|
|
30
|
+
watch: watch || false,
|
|
31
|
+
max_memory_restart: max_memory_restart || '150M',
|
|
32
|
+
env: env || {}
|
|
33
|
+
};
|
|
34
|
+
pm2_1.default.connect((err) => {
|
|
35
|
+
if (err) {
|
|
36
|
+
console.error(err);
|
|
37
|
+
return res.status(500).json({ error: 'Failed to connect to PM2' });
|
|
38
|
+
}
|
|
39
|
+
pm2_1.default.start(appConfig, (err) => {
|
|
40
|
+
pm2_1.default.disconnect();
|
|
41
|
+
if (err) {
|
|
42
|
+
console.error('PM2 start error:', err);
|
|
43
|
+
return res.status(500).json({
|
|
44
|
+
error: `Failed to deploy application: ${err.message || 'Unknown error'}`
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
res.json({
|
|
48
|
+
success: true,
|
|
49
|
+
message: `Application ${name} deployed successfully`
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
// Generate ecosystem.config.js file
|
|
55
|
+
router.post('/generate-ecosystem', (req, res) => {
|
|
56
|
+
pm2_1.default.connect(async (err) => {
|
|
57
|
+
if (err) {
|
|
58
|
+
console.error(err);
|
|
59
|
+
return res.status(500).json({ error: 'Failed to connect to PM2' });
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
// Get all processes
|
|
63
|
+
const listPromise = new Promise((resolve, reject) => {
|
|
64
|
+
pm2_1.default.list((err, processList) => {
|
|
65
|
+
if (err) {
|
|
66
|
+
reject(err);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
resolve(processList);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
const processList = await listPromise;
|
|
73
|
+
// Filter processes if needed
|
|
74
|
+
let filteredProcesses = processList;
|
|
75
|
+
if (req.body.includeAllProcesses === false) {
|
|
76
|
+
filteredProcesses = processList.filter(proc => proc.pm2_env.status === 'online');
|
|
77
|
+
}
|
|
78
|
+
// Create ecosystem config
|
|
79
|
+
const apps = filteredProcesses.map(proc => {
|
|
80
|
+
const pm2Env = proc.pm2_env;
|
|
81
|
+
return {
|
|
82
|
+
name: proc.name,
|
|
83
|
+
script: pm2Env.pm_exec_path,
|
|
84
|
+
cwd: pm2Env.pm_cwd,
|
|
85
|
+
instances: pm2Env.instances || 1,
|
|
86
|
+
exec_mode: pm2Env.exec_mode === 'cluster_mode' ? 'cluster' : 'fork',
|
|
87
|
+
autorestart: pm2Env.autorestart,
|
|
88
|
+
watch: pm2Env.watch,
|
|
89
|
+
ignore_watch: pm2Env.ignore_watch || [],
|
|
90
|
+
max_memory_restart: pm2Env.max_memory_restart || '150M',
|
|
91
|
+
env: pm2Env.env || {}
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
const ecosystemConfig = `module.exports = {
|
|
95
|
+
apps: ${JSON.stringify(apps, null, 2)}
|
|
96
|
+
};`;
|
|
97
|
+
// Create the file (either at specified path or default location)
|
|
98
|
+
const filePath = req.body.path || path_1.default.join(process.cwd(), 'ecosystem.config.js');
|
|
99
|
+
fs_1.default.writeFileSync(filePath, ecosystemConfig);
|
|
100
|
+
pm2_1.default.disconnect();
|
|
101
|
+
res.json({
|
|
102
|
+
success: true,
|
|
103
|
+
message: 'Ecosystem file generated successfully',
|
|
104
|
+
path: filePath
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
pm2_1.default.disconnect();
|
|
109
|
+
res.status(500).json({ error: error.message || 'Failed to generate ecosystem file' });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
// Preview ecosystem.config.js content
|
|
114
|
+
router.get('/generate-ecosystem-preview', (req, res) => {
|
|
115
|
+
pm2_1.default.connect(async (err) => {
|
|
116
|
+
if (err) {
|
|
117
|
+
console.error(err);
|
|
118
|
+
return res.status(500).json({ error: 'Failed to connect to PM2' });
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
// Get all processes
|
|
122
|
+
const listPromise = new Promise((resolve, reject) => {
|
|
123
|
+
pm2_1.default.list((err, processList) => {
|
|
124
|
+
if (err) {
|
|
125
|
+
reject(err);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
resolve(processList);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
const processList = await listPromise;
|
|
132
|
+
// Create ecosystem config
|
|
133
|
+
const apps = processList.map(proc => {
|
|
134
|
+
const pm2Env = proc.pm2_env;
|
|
135
|
+
return {
|
|
136
|
+
name: proc.name,
|
|
137
|
+
script: pm2Env.pm_exec_path,
|
|
138
|
+
cwd: pm2Env.pm_cwd,
|
|
139
|
+
instances: pm2Env.instances || 1,
|
|
140
|
+
exec_mode: pm2Env.exec_mode === 'cluster_mode' ? 'cluster' : 'fork',
|
|
141
|
+
autorestart: pm2Env.autorestart,
|
|
142
|
+
watch: pm2Env.watch,
|
|
143
|
+
ignore_watch: pm2Env.ignore_watch || [],
|
|
144
|
+
max_memory_restart: pm2Env.max_memory_restart || '150M',
|
|
145
|
+
env: pm2Env.env || {}
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
const ecosystemConfig = `module.exports = {
|
|
149
|
+
apps: ${JSON.stringify(apps, null, 2)}
|
|
150
|
+
};`;
|
|
151
|
+
pm2_1.default.disconnect();
|
|
152
|
+
res.json({
|
|
153
|
+
success: true,
|
|
154
|
+
content: ecosystemConfig
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
pm2_1.default.disconnect();
|
|
159
|
+
res.status(500).json({ error: error.message || 'Failed to generate ecosystem preview' });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
exports.default = router;
|
|
@@ -0,0 +1,102 @@
|
|
|
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.setupLogStreaming = void 0;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const pm2_1 = __importDefault(require("pm2"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const router = (0, express_1.Router)();
|
|
12
|
+
// This variable will hold references to active log streams
|
|
13
|
+
const activeStreams = {};
|
|
14
|
+
// Get active stream (or create a new one)
|
|
15
|
+
const getLogStream = (io, processId, logType) => {
|
|
16
|
+
const streamKey = `${processId}-${logType}`;
|
|
17
|
+
// If stream already exists, return it
|
|
18
|
+
if (activeStreams[streamKey]) {
|
|
19
|
+
return activeStreams[streamKey];
|
|
20
|
+
}
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
pm2_1.default.describe(processId, (err, processDesc) => {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
if (err || !processDesc || processDesc.length === 0) {
|
|
25
|
+
reject(new Error('Process not found'));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
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`];
|
|
29
|
+
if (!logPath || !fs_1.default.existsSync(logPath)) {
|
|
30
|
+
reject(new Error(`Log file not found: ${logPath}`));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Create a tail process to stream the log file
|
|
34
|
+
const tail = (0, child_process_1.spawn)('tail', ['-f', logPath]);
|
|
35
|
+
// Setup event handlers
|
|
36
|
+
tail.stdout.on('data', (data) => {
|
|
37
|
+
const lines = data.toString().split('\n').filter((line) => line.trim() !== '');
|
|
38
|
+
lines.forEach((line) => {
|
|
39
|
+
// Emit log line to all connected clients
|
|
40
|
+
io.emit('log-line', {
|
|
41
|
+
processId,
|
|
42
|
+
logType,
|
|
43
|
+
line
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
tail.stderr.on('data', (data) => {
|
|
48
|
+
console.error(`Tail error: ${data}`);
|
|
49
|
+
});
|
|
50
|
+
tail.on('close', (code) => {
|
|
51
|
+
console.log(`Tail process exited with code ${code}`);
|
|
52
|
+
delete activeStreams[streamKey];
|
|
53
|
+
});
|
|
54
|
+
// Store the tail process
|
|
55
|
+
activeStreams[streamKey] = tail;
|
|
56
|
+
resolve(tail);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
// Setup socket.io handlers for log streaming
|
|
61
|
+
const setupLogStreaming = (io) => {
|
|
62
|
+
io.on('connection', (socket) => {
|
|
63
|
+
console.log('Client connected for log streaming');
|
|
64
|
+
// Subscribe to log stream
|
|
65
|
+
socket.on('subscribe-logs', async ({ processId, logType }) => {
|
|
66
|
+
try {
|
|
67
|
+
const streamKey = `${processId}-${logType}`;
|
|
68
|
+
// Add socket to a room for this specific log stream
|
|
69
|
+
socket.join(streamKey);
|
|
70
|
+
console.log(`Client subscribed to logs: ${streamKey}`);
|
|
71
|
+
// Get or create the log stream
|
|
72
|
+
await getLogStream(io, processId, logType);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error('Error subscribing to logs:', error);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// Unsubscribe from log stream
|
|
79
|
+
socket.on('unsubscribe-logs', ({ processId, logType }) => {
|
|
80
|
+
const streamKey = `${processId}-${logType}`;
|
|
81
|
+
// Remove socket from the room
|
|
82
|
+
socket.leave(streamKey);
|
|
83
|
+
console.log(`Client unsubscribed from logs: ${streamKey}`);
|
|
84
|
+
// If no more clients in this room, stop the stream
|
|
85
|
+
const room = io.sockets.adapter.rooms.get(streamKey);
|
|
86
|
+
if (!room || room.size === 0) {
|
|
87
|
+
const stream = activeStreams[streamKey];
|
|
88
|
+
if (stream) {
|
|
89
|
+
console.log(`Stopping log stream: ${streamKey}`);
|
|
90
|
+
stream.kill();
|
|
91
|
+
delete activeStreams[streamKey];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// Cleanup on disconnect
|
|
96
|
+
socket.on('disconnect', () => {
|
|
97
|
+
console.log('Client disconnected from log streaming');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
exports.setupLogStreaming = setupLogStreaming;
|
|
102
|
+
exports.default = router;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const express_1 = require("express");
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const router = (0, express_1.Router)();
|
|
6
|
+
// List installed modules
|
|
7
|
+
router.get('/', (req, res) => {
|
|
8
|
+
const pm2Command = (0, child_process_1.spawn)('pm2', ['module:list']);
|
|
9
|
+
let output = '';
|
|
10
|
+
let errorOutput = '';
|
|
11
|
+
pm2Command.stdout.on('data', (data) => {
|
|
12
|
+
output += data.toString();
|
|
13
|
+
});
|
|
14
|
+
pm2Command.stderr.on('data', (data) => {
|
|
15
|
+
errorOutput += data.toString();
|
|
16
|
+
});
|
|
17
|
+
pm2Command.on('close', (code) => {
|
|
18
|
+
if (code !== 0) {
|
|
19
|
+
return res.status(500).json({
|
|
20
|
+
error: 'Failed to list PM2 modules',
|
|
21
|
+
details: errorOutput
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// Parse the output to get module information
|
|
25
|
+
try {
|
|
26
|
+
const moduleLines = output.split('\n').filter(line => line.includes('│') && !line.includes('Module') && line.trim() !== '');
|
|
27
|
+
const modules = moduleLines.map(line => {
|
|
28
|
+
const parts = line.split('│').map(part => part.trim()).filter(Boolean);
|
|
29
|
+
if (parts.length >= 3) {
|
|
30
|
+
return {
|
|
31
|
+
name: parts[0],
|
|
32
|
+
version: parts[1],
|
|
33
|
+
status: parts[2]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}).filter(Boolean);
|
|
38
|
+
res.json(modules);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
res.status(500).json({
|
|
42
|
+
error: 'Failed to parse PM2 modules',
|
|
43
|
+
details: error
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
// Install a module
|
|
49
|
+
router.post('/install', (req, res) => {
|
|
50
|
+
const { moduleName } = req.body;
|
|
51
|
+
if (!moduleName) {
|
|
52
|
+
return res.status(400).json({ error: 'Module name is required' });
|
|
53
|
+
}
|
|
54
|
+
const pm2Command = (0, child_process_1.spawn)('pm2', ['install', moduleName]);
|
|
55
|
+
let output = '';
|
|
56
|
+
let errorOutput = '';
|
|
57
|
+
pm2Command.stdout.on('data', (data) => {
|
|
58
|
+
output += data.toString();
|
|
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
|
+
}
|
|
70
|
+
res.json({
|
|
71
|
+
success: true,
|
|
72
|
+
message: `Successfully installed module: ${moduleName}`,
|
|
73
|
+
details: output
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
// Uninstall a module
|
|
78
|
+
router.delete('/:moduleName', (req, res) => {
|
|
79
|
+
const { moduleName } = req.params;
|
|
80
|
+
if (!moduleName) {
|
|
81
|
+
return res.status(400).json({ error: 'Module name is required' });
|
|
82
|
+
}
|
|
83
|
+
const pm2Command = (0, child_process_1.spawn)('pm2', ['uninstall', moduleName]);
|
|
84
|
+
let output = '';
|
|
85
|
+
let errorOutput = '';
|
|
86
|
+
pm2Command.stdout.on('data', (data) => {
|
|
87
|
+
output += data.toString();
|
|
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
|
+
}
|
|
99
|
+
res.json({
|
|
100
|
+
success: true,
|
|
101
|
+
message: `Successfully uninstalled module: ${moduleName}`,
|
|
102
|
+
details: output
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
exports.default = router;
|
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
const express_1 = require("express");
|
|
7
|
+
const pm2_1 = __importDefault(require("pm2"));
|
|
8
|
+
const router = (0, express_1.Router)();
|
|
9
|
+
// Get current configuration for a process
|
|
10
|
+
router.get('/:id', (req, res) => {
|
|
11
|
+
const { id } = req.params;
|
|
12
|
+
pm2_1.default.connect((err) => {
|
|
13
|
+
if (err) {
|
|
14
|
+
console.error(err);
|
|
15
|
+
res.status(500).json({ error: 'Failed to connect to PM2' });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
pm2_1.default.describe(id, (err, processDesc) => {
|
|
19
|
+
pm2_1.default.disconnect();
|
|
20
|
+
if (err || !processDesc || processDesc.length === 0) {
|
|
21
|
+
res.status(404).json({ error: 'Process not found' });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const process = processDesc[0];
|
|
25
|
+
const pm2Env = process.pm2_env;
|
|
26
|
+
// Extract configuration from PM2 environment
|
|
27
|
+
const config = {
|
|
28
|
+
name: process.name,
|
|
29
|
+
script: pm2Env.pm_exec_path,
|
|
30
|
+
cwd: pm2Env.pm_cwd,
|
|
31
|
+
interpreter: pm2Env.exec_interpreter,
|
|
32
|
+
instances: pm2Env.instances || 1,
|
|
33
|
+
exec_mode: pm2Env.exec_mode === 'cluster_mode' ? 'cluster' : 'fork',
|
|
34
|
+
autorestart: pm2Env.autorestart,
|
|
35
|
+
watch: pm2Env.watch,
|
|
36
|
+
ignore_watch: pm2Env.ignore_watch || [],
|
|
37
|
+
env: pm2Env.env || {},
|
|
38
|
+
max_memory_restart: pm2Env.max_memory_restart || '150M'
|
|
39
|
+
};
|
|
40
|
+
res.json(config);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
// Update configuration for a process
|
|
45
|
+
router.post('/:id', (req, res) => {
|
|
46
|
+
const { id } = req.params;
|
|
47
|
+
const config = req.body;
|
|
48
|
+
if (!config) {
|
|
49
|
+
res.status(400).json({ error: 'Configuration data is required' });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
pm2_1.default.connect(async (err) => {
|
|
53
|
+
if (err) {
|
|
54
|
+
console.error(err);
|
|
55
|
+
res.status(500).json({ error: 'Failed to connect to PM2' });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
// First get the current process to find its ecosystem file if any
|
|
60
|
+
const describePromise = new Promise((resolve, reject) => {
|
|
61
|
+
pm2_1.default.describe(id, (err, processDesc) => {
|
|
62
|
+
if (err || !processDesc || processDesc.length === 0) {
|
|
63
|
+
reject(new Error('Process not found'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
resolve(processDesc[0]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
const process = await describePromise;
|
|
70
|
+
// Update process via PM2 API
|
|
71
|
+
const updatePromise = new Promise((resolve, reject) => {
|
|
72
|
+
// Stop the process first
|
|
73
|
+
pm2_1.default.stop(id, (err) => {
|
|
74
|
+
if (err) {
|
|
75
|
+
reject(new Error(`Failed to stop process: ${err.message}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Delete the process
|
|
79
|
+
// TypeScript doesn't recognize 'del' method, so we're using it with a type assertion
|
|
80
|
+
pm2_1.default.del(id, (err) => {
|
|
81
|
+
if (err) {
|
|
82
|
+
reject(new Error(`Failed to delete process: ${err.message}`));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Start with new configuration
|
|
86
|
+
pm2_1.default.start({
|
|
87
|
+
name: config.name,
|
|
88
|
+
script: config.script,
|
|
89
|
+
cwd: config.cwd,
|
|
90
|
+
interpreter: config.interpreter,
|
|
91
|
+
instances: config.instances,
|
|
92
|
+
exec_mode: config.exec_mode,
|
|
93
|
+
autorestart: config.autorestart,
|
|
94
|
+
watch: config.watch,
|
|
95
|
+
ignore_watch: config.ignore_watch,
|
|
96
|
+
env: config.env,
|
|
97
|
+
max_memory_restart: config.max_memory_restart
|
|
98
|
+
}, (err) => {
|
|
99
|
+
if (err) {
|
|
100
|
+
reject(new Error(`Failed to start process with new config: ${err.message}`));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
resolve(true);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
await updatePromise;
|
|
109
|
+
pm2_1.default.disconnect();
|
|
110
|
+
res.json({ success: true, message: 'Configuration updated successfully' });
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
pm2_1.default.disconnect();
|
|
114
|
+
res.status(500).json({ error: error.message || 'Failed to update configuration' });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
exports.default = router;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connect to PM2 if not already connected
|
|
3
|
+
* Returns a promise that resolves when the connection is established
|
|
4
|
+
*/
|
|
5
|
+
export declare const connectToPM2: () => Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Safely disconnect from PM2
|
|
8
|
+
* Use this when shutting down the server
|
|
9
|
+
*/
|
|
10
|
+
export declare const disconnectFromPM2: () => Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Execute a PM2 command with automatic connection handling
|
|
13
|
+
* This will connect to PM2 if needed, run the command,
|
|
14
|
+
* and properly handle the result
|
|
15
|
+
*/
|
|
16
|
+
export declare const executePM2Command: <T>(command: (callback: (err: Error | null, result?: T) => void) => void) => Promise<T>;
|