@fkom13/mcp-sftp-orchestrator 6.0.0 → 11.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.
- package/.env.example +27 -15
- package/CHANGELOG.md +174 -0
- package/README.fr.md +356 -0
- package/README.md +314 -60
- package/apis.js +20 -64
- package/atomicJsonStore.js +117 -0
- package/compareEngine.js +132 -0
- package/config.js +40 -4
- package/diagnose.js +89 -0
- package/diffEngine.js +141 -0
- package/diffFormatter.js +114 -0
- package/fileOps.js +275 -0
- package/fleet.js +170 -0
- package/groups.js +25 -0
- package/guide.js +236 -0
- package/history.js +5 -44
- package/infraTopology.js +336 -0
- package/inventory.js +151 -0
- package/lhm.plugin.json +7 -0
- package/notes.js +3 -0
- package/package.json +11 -26
- package/policies.js +8 -0
- package/projects.js +108 -0
- package/queue.js +180 -125
- package/server.js +2552 -469
- package/servers.js +30 -33
- package/sftp.js +284 -86
- package/shellSessions.js +265 -0
- package/snapshotManager.js +320 -0
- package/sourceAdapter.js +410 -0
- package/ssh.js +184 -86
- package/sshPool.js +161 -148
- package/sshTrust.js +214 -0
- package/toolMetadata.js +76 -0
- package/tunnels.js +243 -0
- package/utils.js +215 -0
- package/workSession.js +139 -0
- package/publish1github.md +0 -216
- package/test_features.js +0 -112
package/ssh.js
CHANGED
|
@@ -4,6 +4,8 @@ import queue from './queue.js';
|
|
|
4
4
|
import serverManager from './servers.js';
|
|
5
5
|
import sshPool from './sshPool.js';
|
|
6
6
|
import config from './config.js';
|
|
7
|
+
import policies from './policies.js';
|
|
8
|
+
import utils from './utils.js';
|
|
7
9
|
|
|
8
10
|
const STREAMING_COMMANDS = [
|
|
9
11
|
/^pm2\s+logs?\b/i,
|
|
@@ -25,32 +27,56 @@ function isStreamingCommand(cmd) {
|
|
|
25
27
|
|
|
26
28
|
// Configuration des réponses automatiques pour les prompts interactifs
|
|
27
29
|
const INTERACTIVE_RESPONSES = {
|
|
28
|
-
// Patterns courants et leurs réponses
|
|
29
30
|
'continue connecting': 'yes',
|
|
30
31
|
'Are you sure': 'yes',
|
|
31
|
-
'password:': null, // Sera géré séparément
|
|
32
|
-
'Password:': null,
|
|
33
32
|
'Do you want to continue': 'y',
|
|
33
|
+
'[y/n]': 'y',
|
|
34
|
+
'(yes/no)': 'yes',
|
|
35
|
+
'(y/N)': 'y',
|
|
36
|
+
'(Y/n)': 'y',
|
|
34
37
|
'Overwrite': 'y',
|
|
35
|
-
'Save': 'y'
|
|
38
|
+
'Save': 'y',
|
|
39
|
+
'accept': 'yes',
|
|
40
|
+
'confirm': 'yes',
|
|
41
|
+
'password:': 'PASSWORD_REQUIRED',
|
|
42
|
+
'Password:': 'PASSWORD_REQUIRED',
|
|
43
|
+
'sudo': 'PASSWORD_REQUIRED'
|
|
36
44
|
};
|
|
37
45
|
|
|
38
46
|
// Détecte si une sortie contient un prompt interactif
|
|
39
|
-
function detectInteractivePrompt(output) {
|
|
47
|
+
function detectInteractivePrompt(output, customResponses = {}) {
|
|
40
48
|
const lastLine = output.split('\n').pop().toLowerCase();
|
|
41
|
-
|
|
49
|
+
const lastLines = output.split('\n').slice(-5).join('\n').toLowerCase();
|
|
50
|
+
|
|
51
|
+
// 1. Check custom responses first (supporte regex)
|
|
52
|
+
for (const [pattern, response] of Object.entries(customResponses)) {
|
|
53
|
+
try {
|
|
54
|
+
const regex = new RegExp(pattern, 'i');
|
|
55
|
+
if (regex.test(lastLines)) {
|
|
56
|
+
return { pattern, response, needsInput: true };
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
if (lastLines.includes(pattern.toLowerCase())) {
|
|
60
|
+
return { pattern, response, needsInput: true };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. Check built-in patterns
|
|
42
66
|
for (const [pattern, response] of Object.entries(INTERACTIVE_RESPONSES)) {
|
|
43
|
-
if (
|
|
67
|
+
if (lastLines.includes(pattern.toLowerCase())) {
|
|
44
68
|
return { pattern, response, needsInput: true };
|
|
45
69
|
}
|
|
46
70
|
}
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
if (lastLine.endsWith(':') || lastLine.endsWith('?') ||
|
|
50
|
-
lastLine.includes('[y/n]') || lastLine.includes('(yes/no)')
|
|
71
|
+
|
|
72
|
+
// 3. Generic prompt detection
|
|
73
|
+
if (lastLine.endsWith(':') || lastLine.endsWith('?') ||
|
|
74
|
+
lastLine.includes('[y/n]') || lastLine.includes('(yes/no)') ||
|
|
75
|
+
lastLine.match(/^\s*\[\d+\]/) || lastLine.match(/^\s*\d+[.)]\s/) ||
|
|
76
|
+
lastLine.match(/choose|select|pick|enter|input/i)) {
|
|
51
77
|
return { pattern: 'generic', response: null, needsInput: true };
|
|
52
78
|
}
|
|
53
|
-
|
|
79
|
+
|
|
54
80
|
return { needsInput: false };
|
|
55
81
|
}
|
|
56
82
|
|
|
@@ -60,14 +86,23 @@ async function executeCommand(jobId) {
|
|
|
60
86
|
if (!job) return queue.log('error', `Tâche introuvable: ${jobId}`);
|
|
61
87
|
|
|
62
88
|
let connection = null;
|
|
63
|
-
|
|
89
|
+
|
|
64
90
|
try {
|
|
65
91
|
const serverConfig = await serverManager.getServer(job.alias);
|
|
92
|
+
|
|
93
|
+
if (!job.skip_policy) {
|
|
94
|
+
const policyCheck = await policies.check(job.cmd);
|
|
95
|
+
if (policyCheck.blocked) {
|
|
96
|
+
queue.updateJobStatus(jobId, 'failed', { error: `Commande bloquée par la politique de sécurité (pattern: "${policyCheck.pattern}"). Utilisez skip_policy:true pour forcer.` });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
66
101
|
queue.updateJobStatus(jobId, 'running');
|
|
67
102
|
|
|
68
103
|
// Utiliser le pool de connexions si pas de mode interactif
|
|
69
104
|
const usePool = !job.interactive && job.persistent !== false;
|
|
70
|
-
|
|
105
|
+
|
|
71
106
|
if (usePool) {
|
|
72
107
|
// Obtenir une connexion du pool
|
|
73
108
|
connection = await sshPool.getConnection(job.alias, serverConfig);
|
|
@@ -76,9 +111,10 @@ async function executeCommand(jobId) {
|
|
|
76
111
|
// Créer une connexion dédiée pour les commandes interactives
|
|
77
112
|
await executeWithNewConnection(serverConfig, job, jobId);
|
|
78
113
|
}
|
|
79
|
-
|
|
114
|
+
|
|
80
115
|
} catch (err) {
|
|
81
|
-
queue.
|
|
116
|
+
const existing = queue.getJob(jobId) || {};
|
|
117
|
+
queue.updateJobStatus(jobId, 'failed', { ...existing, error: err.message });
|
|
82
118
|
} finally {
|
|
83
119
|
// Libérer la connexion si elle vient du pool
|
|
84
120
|
if (connection) {
|
|
@@ -88,7 +124,7 @@ async function executeCommand(jobId) {
|
|
|
88
124
|
}
|
|
89
125
|
|
|
90
126
|
// Exécution avec une connexion du pool
|
|
91
|
-
async function executeWithPooledConnection(connection, job, jobId) {
|
|
127
|
+
async function executeWithPooledConnection(connection, job, jobId, updateQueue = true) {
|
|
92
128
|
return new Promise((resolve, reject) => {
|
|
93
129
|
const { client } = connection;
|
|
94
130
|
|
|
@@ -119,38 +155,45 @@ async function executeWithPooledConnection(connection, job, jobId) {
|
|
|
119
155
|
reject(err);
|
|
120
156
|
return;
|
|
121
157
|
}
|
|
122
|
-
|
|
158
|
+
|
|
123
159
|
let output = '';
|
|
124
160
|
let stderr = '';
|
|
125
161
|
let lineCount = 0;
|
|
126
162
|
const startTime = Date.now();
|
|
127
163
|
const maxLines = job.maxLines || 1000;
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
164
|
+
const userTimeout = job.timeout; // en secondes, 0 = infini
|
|
165
|
+
const timeoutMs = userTimeout === 0 ? 0 : (userTimeout ? userTimeout * 1000 : config.defaultCommandTimeout);
|
|
166
|
+
|
|
167
|
+
let timeoutId = null;
|
|
168
|
+
if (timeoutMs > 0) {
|
|
169
|
+
timeoutId = setTimeout(() => {
|
|
170
|
+
stream.write('\x03');
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
stream.close();
|
|
173
|
+
const duration = Date.now() - startTime;
|
|
174
|
+
const result = { output: output.trim(), stderr: stderr.trim(), exitCode: 124, duration, lineCount, timedOut: true };
|
|
175
|
+
if (updateQueue) queue.updateJobStatus(jobId, 'completed', result);
|
|
176
|
+
resolve(result);
|
|
177
|
+
}, 500);
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
}
|
|
180
|
+
|
|
141
181
|
stream.on('close', (code, signal) => {
|
|
142
|
-
clearTimeout(timeoutId);
|
|
182
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
143
183
|
const duration = Date.now() - startTime;
|
|
144
184
|
const result = { output: output.trim(), stderr: stderr.trim(), exitCode: code, signal, duration, lineCount };
|
|
185
|
+
|
|
186
|
+
// Important: clear any other timeouts if they exist (though timeoutId is the main one here)
|
|
187
|
+
|
|
145
188
|
if (code === 0 || code === 124) {
|
|
146
|
-
queue.updateJobStatus(jobId, 'completed', result);
|
|
189
|
+
if (updateQueue) queue.updateJobStatus(jobId, 'completed', result);
|
|
147
190
|
resolve(result);
|
|
148
191
|
} else {
|
|
149
|
-
queue.updateJobStatus(jobId, 'failed', { ...result, error: `Commande terminée avec code ${code}` });
|
|
192
|
+
if (updateQueue) queue.updateJobStatus(jobId, 'failed', { ...result, error: `Commande terminée avec code ${code}` });
|
|
150
193
|
reject(new Error(`Exit code: ${code}`));
|
|
151
194
|
}
|
|
152
195
|
});
|
|
153
|
-
|
|
196
|
+
|
|
154
197
|
stream.on('data', (data) => {
|
|
155
198
|
const chunk = data.toString();
|
|
156
199
|
output += chunk;
|
|
@@ -160,14 +203,14 @@ async function executeWithPooledConnection(connection, job, jobId) {
|
|
|
160
203
|
setTimeout(() => stream.close(), 500);
|
|
161
204
|
}
|
|
162
205
|
if (job.autoRespond) {
|
|
163
|
-
const prompt = detectInteractivePrompt(output);
|
|
164
|
-
if (prompt.needsInput && prompt.response) {
|
|
206
|
+
const prompt = detectInteractivePrompt(output, job.responses || {});
|
|
207
|
+
if (prompt.needsInput && prompt.response && prompt.response !== 'PASSWORD_REQUIRED') {
|
|
165
208
|
stream.write(prompt.response + '\n');
|
|
166
209
|
queue.log('info', `Réponse automatique au prompt: ${prompt.pattern}`);
|
|
167
210
|
}
|
|
168
211
|
}
|
|
169
212
|
});
|
|
170
|
-
|
|
213
|
+
|
|
171
214
|
stream.stderr.on('data', (data) => {
|
|
172
215
|
stderr += data.toString();
|
|
173
216
|
});
|
|
@@ -193,22 +236,27 @@ async function executeWithNewConnection(serverConfig, job, jobId) {
|
|
|
193
236
|
let responseSent = false;
|
|
194
237
|
|
|
195
238
|
const cleanupAndResolve = (status = 'completed') => {
|
|
196
|
-
clearTimeout(jobTimeout);
|
|
239
|
+
if (jobTimeout) clearTimeout(jobTimeout);
|
|
197
240
|
output = output.replace(END_MARKER, '').trim();
|
|
198
241
|
const result = { output, exitCode: 0 };
|
|
199
242
|
queue.updateJobStatus(jobId, status, result);
|
|
200
243
|
try {
|
|
201
244
|
stream.end();
|
|
202
245
|
conn.end();
|
|
203
|
-
} catch(e) {/* ignore */}
|
|
246
|
+
} catch (e) {/* ignore */ }
|
|
204
247
|
resolve(result);
|
|
205
248
|
};
|
|
206
249
|
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
250
|
+
const timeoutMs = job.timeout === 0 ? 0 : (job.timeout ? job.timeout * 1000 : config.interactiveCommandTimeout);
|
|
251
|
+
let jobTimeout = null;
|
|
252
|
+
if (timeoutMs > 0) {
|
|
253
|
+
jobTimeout = setTimeout(() => {
|
|
254
|
+
cleanupAndResolve('failed');
|
|
255
|
+
}, timeoutMs);
|
|
256
|
+
}
|
|
210
257
|
|
|
211
258
|
stream.on('close', () => {
|
|
259
|
+
if (jobTimeout) clearTimeout(jobTimeout);
|
|
212
260
|
if (!output.includes(END_MARKER)) {
|
|
213
261
|
cleanupAndResolve('failed');
|
|
214
262
|
}
|
|
@@ -226,7 +274,7 @@ async function executeWithNewConnection(serverConfig, job, jobId) {
|
|
|
226
274
|
if (!responseSent && job.autoRespond) {
|
|
227
275
|
const lowerChunk = chunk.toLowerCase();
|
|
228
276
|
let responseToSend = null;
|
|
229
|
-
|
|
277
|
+
|
|
230
278
|
// Chercher une réponse personnalisée
|
|
231
279
|
for (const [pattern, response] of Object.entries(responses)) {
|
|
232
280
|
if (lowerChunk.includes(pattern.toLowerCase())) {
|
|
@@ -235,7 +283,7 @@ async function executeWithNewConnection(serverConfig, job, jobId) {
|
|
|
235
283
|
break;
|
|
236
284
|
}
|
|
237
285
|
}
|
|
238
|
-
|
|
286
|
+
|
|
239
287
|
// Sinon, chercher une réponse par défaut
|
|
240
288
|
if (!responseToSend) {
|
|
241
289
|
for (const [pattern, response] of Object.entries(INTERACTIVE_RESPONSES)) {
|
|
@@ -270,11 +318,11 @@ async function executeWithNewConnection(serverConfig, job, jobId) {
|
|
|
270
318
|
reject(err);
|
|
271
319
|
});
|
|
272
320
|
|
|
273
|
-
const connectConfig = {
|
|
274
|
-
host: serverConfig.host,
|
|
275
|
-
port:
|
|
276
|
-
username: serverConfig.user,
|
|
277
|
-
readyTimeout: 20000
|
|
321
|
+
const connectConfig = {
|
|
322
|
+
host: serverConfig.host,
|
|
323
|
+
port: utils.resolveSshPort(serverConfig),
|
|
324
|
+
username: serverConfig.user,
|
|
325
|
+
readyTimeout: 20000
|
|
278
326
|
};
|
|
279
327
|
|
|
280
328
|
(async () => {
|
|
@@ -302,56 +350,70 @@ async function executeWithNewConnection(serverConfig, job, jobId) {
|
|
|
302
350
|
async function executeCommandSequence(jobId) {
|
|
303
351
|
const job = queue.getJob(jobId);
|
|
304
352
|
if (!job) return queue.log('error', `Tâche introuvable: ${jobId}`);
|
|
305
|
-
|
|
353
|
+
|
|
306
354
|
const results = [];
|
|
307
355
|
let connection = null;
|
|
308
|
-
|
|
356
|
+
|
|
309
357
|
try {
|
|
310
358
|
const serverConfig = await serverManager.getServer(job.alias);
|
|
311
359
|
queue.updateJobStatus(jobId, 'running');
|
|
312
|
-
|
|
360
|
+
|
|
313
361
|
// Obtenir une connexion du pool
|
|
314
362
|
connection = await sshPool.getConnection(job.alias, serverConfig);
|
|
315
|
-
|
|
363
|
+
|
|
316
364
|
// Exécuter chaque commande en séquence
|
|
317
365
|
for (let i = 0; i < job.commands.length; i++) {
|
|
318
366
|
const cmd = job.commands[i];
|
|
319
367
|
const stepJob = {
|
|
320
368
|
...job,
|
|
321
369
|
cmd: typeof cmd === 'string' ? cmd : cmd.command,
|
|
322
|
-
timeout: cmd.timeout
|
|
323
|
-
continueOnError: cmd.continueOnError
|
|
370
|
+
timeout: (typeof cmd === 'object' && cmd.timeout != null) ? cmd.timeout : job.timeout,
|
|
371
|
+
continueOnError: (typeof cmd === 'object' && cmd.continueOnError != null)
|
|
372
|
+
? cmd.continueOnError
|
|
373
|
+
: job.continueOnError
|
|
324
374
|
};
|
|
325
|
-
|
|
375
|
+
|
|
376
|
+
// Policy par étape (sauf skip_policy)
|
|
377
|
+
if (!job.skip_policy) {
|
|
378
|
+
const policyCheck = await policies.check(stepJob.cmd);
|
|
379
|
+
if (policyCheck.blocked) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`Étape ${i + 1} bloquée par la politique de sécurité (pattern: "${policyCheck.pattern}"). ` +
|
|
382
|
+
`Utilisez skip_policy:true pour forcer.`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
326
387
|
try {
|
|
327
|
-
queue.log('info', `Exécution étape ${i+1}/${job.commands.length}: ${stepJob.cmd}`);
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
388
|
+
queue.log('info', `Exécution étape ${i + 1}/${job.commands.length}: ${stepJob.cmd}`);
|
|
389
|
+
// Don't update the main queue for sub-steps with phantom IDs
|
|
390
|
+
const result = await executeWithPooledConnection(connection, stepJob, `${jobId}_step_${i}`, false);
|
|
391
|
+
results.push({
|
|
392
|
+
step: i + 1,
|
|
393
|
+
command: stepJob.cmd,
|
|
394
|
+
success: true,
|
|
395
|
+
...result
|
|
334
396
|
});
|
|
335
397
|
} catch (err) {
|
|
336
|
-
results.push({
|
|
337
|
-
step: i + 1,
|
|
338
|
-
command: stepJob.cmd,
|
|
339
|
-
success: false,
|
|
340
|
-
error: err.message
|
|
398
|
+
results.push({
|
|
399
|
+
step: i + 1,
|
|
400
|
+
command: stepJob.cmd,
|
|
401
|
+
success: false,
|
|
402
|
+
error: err.message
|
|
341
403
|
});
|
|
342
|
-
|
|
404
|
+
|
|
343
405
|
if (!stepJob.continueOnError) {
|
|
344
|
-
throw new Error(`Échec à l'étape ${i+1}: ${err.message}`);
|
|
406
|
+
throw new Error(`Échec à l'étape ${i + 1}: ${err.message}`);
|
|
345
407
|
}
|
|
346
408
|
}
|
|
347
409
|
}
|
|
348
|
-
|
|
410
|
+
|
|
349
411
|
queue.updateJobStatus(jobId, 'completed', { results });
|
|
350
|
-
|
|
412
|
+
|
|
351
413
|
} catch (err) {
|
|
352
|
-
queue.updateJobStatus(jobId, 'failed', {
|
|
353
|
-
error: err.message,
|
|
354
|
-
results
|
|
414
|
+
queue.updateJobStatus(jobId, 'failed', {
|
|
415
|
+
error: err.message,
|
|
416
|
+
results
|
|
355
417
|
});
|
|
356
418
|
} finally {
|
|
357
419
|
if (connection) {
|
|
@@ -419,7 +481,7 @@ function parseServicesStatus(output) {
|
|
|
419
481
|
|
|
420
482
|
try {
|
|
421
483
|
const sections = output.split(/---DOCKER---|---PM2---/);
|
|
422
|
-
const systemdRaw = sections[0] || '';
|
|
484
|
+
const systemdRaw = (sections[0] || '').replace(/^---SYSTEMD---\s*/, '');
|
|
423
485
|
const dockerRaw = sections[1] || '';
|
|
424
486
|
const pm2Raw = sections[2] || '';
|
|
425
487
|
|
|
@@ -467,26 +529,62 @@ function parseServicesStatus(output) {
|
|
|
467
529
|
}
|
|
468
530
|
|
|
469
531
|
function parseApiHealth(output) {
|
|
532
|
+
if (!output || typeof output !== 'string') {
|
|
533
|
+
return {
|
|
534
|
+
status: 'ERROR',
|
|
535
|
+
http_code: 0,
|
|
536
|
+
response_time_ms: 0,
|
|
537
|
+
error: 'Sortie invalide ou vide'
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
470
541
|
try {
|
|
471
|
-
const
|
|
542
|
+
const parts = output.trim().split(':');
|
|
543
|
+
if (parts.length !== 2) {
|
|
544
|
+
return {
|
|
545
|
+
status: 'ERROR',
|
|
546
|
+
http_code: 0,
|
|
547
|
+
response_time_ms: 0,
|
|
548
|
+
error: 'Format de réponse invalide',
|
|
549
|
+
raw_output: output
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const [codeStr, timeStr] = parts;
|
|
472
554
|
const http_code = parseInt(codeStr, 10);
|
|
473
555
|
const response_time_ms = parseFloat(timeStr) * 1000;
|
|
474
556
|
|
|
557
|
+
if (isNaN(http_code) || isNaN(response_time_ms)) {
|
|
558
|
+
return {
|
|
559
|
+
status: 'ERROR',
|
|
560
|
+
http_code: 0,
|
|
561
|
+
response_time_ms: 0,
|
|
562
|
+
error: 'Valeurs non numériques',
|
|
563
|
+
raw_output: output
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
475
567
|
return {
|
|
476
568
|
status: http_code >= 200 && http_code < 300 ? 'UP' : 'DOWN',
|
|
477
569
|
http_code: http_code,
|
|
478
570
|
response_time_ms: Math.round(response_time_ms)
|
|
479
571
|
};
|
|
480
572
|
} catch (e) {
|
|
481
|
-
return {
|
|
573
|
+
return {
|
|
574
|
+
status: 'ERROR',
|
|
575
|
+
http_code: 0,
|
|
576
|
+
response_time_ms: 0,
|
|
577
|
+
error: e.message,
|
|
578
|
+
raw_output: output
|
|
579
|
+
};
|
|
482
580
|
}
|
|
483
581
|
}
|
|
484
582
|
|
|
485
|
-
export default {
|
|
486
|
-
executeCommand,
|
|
487
|
-
executeCommandSequence,
|
|
488
|
-
getPoolStats,
|
|
489
|
-
parseSystemResources,
|
|
490
|
-
parseServicesStatus,
|
|
491
|
-
parseApiHealth
|
|
583
|
+
export default {
|
|
584
|
+
executeCommand,
|
|
585
|
+
executeCommandSequence,
|
|
586
|
+
getPoolStats,
|
|
587
|
+
parseSystemResources,
|
|
588
|
+
parseServicesStatus,
|
|
589
|
+
parseApiHealth
|
|
492
590
|
};
|