@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/queue.js
CHANGED
|
@@ -5,8 +5,11 @@ import config from './config.js';
|
|
|
5
5
|
|
|
6
6
|
const QUEUE_FILE = path.join(config.dataDir, 'queue.json');
|
|
7
7
|
const QUEUE_BACKUP = path.join(config.dataDir, 'queue.backup.json');
|
|
8
|
-
const SAVE_INTERVAL = 5000;
|
|
9
|
-
const MAX_QUEUE_SIZE = 1000;
|
|
8
|
+
const SAVE_INTERVAL = config.saveInterval || 5000;
|
|
9
|
+
const MAX_QUEUE_SIZE = config.maxQueueSize || 1000;
|
|
10
|
+
|
|
11
|
+
// ✅ Mode silencieux par défaut (logs désactivés sauf si MCP_DEBUG=true)
|
|
12
|
+
const SILENT_MODE = process.env.MCP_DEBUG !== 'true';
|
|
10
13
|
|
|
11
14
|
const jobQueue = {};
|
|
12
15
|
const logHistory = [];
|
|
@@ -14,37 +17,39 @@ const MAX_LOGS = 500;
|
|
|
14
17
|
|
|
15
18
|
let saveTimer = null;
|
|
16
19
|
let isDirty = false;
|
|
20
|
+
let dirtyRevision = 0;
|
|
21
|
+
|
|
22
|
+
function markDirty() {
|
|
23
|
+
isDirty = true;
|
|
24
|
+
dirtyRevision++;
|
|
25
|
+
}
|
|
17
26
|
|
|
18
27
|
// Charger la queue au démarrage
|
|
19
28
|
async function loadQueue() {
|
|
20
29
|
try {
|
|
21
30
|
const data = await fs.readFile(QUEUE_FILE, 'utf-8');
|
|
22
31
|
const savedQueue = JSON.parse(data);
|
|
23
|
-
|
|
24
|
-
// Restaurer les tâches non terminées
|
|
32
|
+
|
|
25
33
|
for (const [id, job] of Object.entries(savedQueue)) {
|
|
26
|
-
// Convertir les dates string en objets Date
|
|
27
34
|
if (job.createdAt) job.createdAt = new Date(job.createdAt);
|
|
28
35
|
if (job.updatedAt) job.updatedAt = new Date(job.updatedAt);
|
|
29
36
|
if (job.reminderAt) job.reminderAt = new Date(job.reminderAt);
|
|
30
|
-
|
|
31
|
-
// Marquer les tâches running comme crashed
|
|
37
|
+
|
|
32
38
|
if (job.status === 'running') {
|
|
33
39
|
job.status = 'crashed';
|
|
34
40
|
job.crashedAt = new Date();
|
|
35
41
|
job.canRetry = true;
|
|
36
42
|
log('warn', `Tâche ${id} marquée comme crashed (reprise après redémarrage)`);
|
|
37
43
|
}
|
|
38
|
-
|
|
44
|
+
|
|
39
45
|
jobQueue[id] = job;
|
|
40
46
|
}
|
|
41
|
-
|
|
47
|
+
|
|
42
48
|
log('info', `${Object.keys(jobQueue).length} tâches restaurées depuis la sauvegarde`);
|
|
43
49
|
} catch (err) {
|
|
44
50
|
if (err.code !== 'ENOENT') {
|
|
45
51
|
log('error', `Erreur lors du chargement de la queue: ${err.message}`);
|
|
46
|
-
|
|
47
|
-
// Essayer de charger la sauvegarde de secours
|
|
52
|
+
|
|
48
53
|
try {
|
|
49
54
|
const backupData = await fs.readFile(QUEUE_BACKUP, 'utf-8');
|
|
50
55
|
const backupQueue = JSON.parse(backupData);
|
|
@@ -59,58 +64,73 @@ async function loadQueue() {
|
|
|
59
64
|
|
|
60
65
|
let isSaving = false;
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
let saveLock = null;
|
|
68
|
+
|
|
63
69
|
async function saveQueue() {
|
|
64
|
-
if (!isDirty
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
if (!isDirty) return;
|
|
71
|
+
|
|
72
|
+
// Attendre le save précédent puis revalider : il a peut-être déjà persisté
|
|
73
|
+
// toutes les mutations connues au moment de cet appel.
|
|
74
|
+
if (saveLock) {
|
|
75
|
+
await saveLock;
|
|
76
|
+
if (!isDirty) return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
saveLock = (async () => {
|
|
80
|
+
isSaving = true;
|
|
81
|
+
let tmpPath = null;
|
|
82
|
+
const revisionAtStart = dirtyRevision;
|
|
69
83
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
try {
|
|
85
|
+
await fs.copyFile(QUEUE_FILE, QUEUE_BACKUP);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
// Ignorer si le fichier n'existe pas
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
const filteredQueue = {};
|
|
92
|
+
|
|
93
|
+
for (const [id, job] of Object.entries(jobQueue)) {
|
|
94
|
+
const age = now - new Date(job.createdAt).getTime();
|
|
95
|
+
const isRecent = age < 86400000;
|
|
96
|
+
const isActive = ['pending', 'running', 'crashed'].includes(job.status);
|
|
97
|
+
|
|
98
|
+
if (isActive || isRecent) {
|
|
99
|
+
filteredQueue[id] = job;
|
|
100
|
+
}
|
|
86
101
|
}
|
|
102
|
+
|
|
103
|
+
// Écriture atomique : un crash ne peut plus laisser queue.json tronqué.
|
|
104
|
+
tmpPath = `${QUEUE_FILE}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
105
|
+
await fs.writeFile(tmpPath, JSON.stringify(filteredQueue, null, 2));
|
|
106
|
+
await fs.rename(tmpPath, QUEUE_FILE);
|
|
107
|
+
tmpPath = null;
|
|
108
|
+
|
|
109
|
+
// Une mutation a pu arriver pendant les awaits ci-dessus. Ne jamais
|
|
110
|
+
// effacer son dirty flag : le prochain tick la persistera.
|
|
111
|
+
isDirty = dirtyRevision !== revisionAtStart;
|
|
112
|
+
log('debug', `Queue sauvegardée (${Object.keys(filteredQueue).length} tâches, dirty=${isDirty})`);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
isDirty = true;
|
|
115
|
+
if (tmpPath) await fs.rm(tmpPath, { force: true }).catch(() => {});
|
|
116
|
+
log('error', `Erreur lors de la sauvegarde de la queue: ${err.message}`);
|
|
117
|
+
} finally {
|
|
118
|
+
isSaving = false;
|
|
119
|
+
saveLock = null;
|
|
87
120
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
QUEUE_FILE,
|
|
92
|
-
JSON.stringify(filteredQueue, null, 2)
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
isDirty = false;
|
|
96
|
-
log('debug', `Queue sauvegardée (${Object.keys(filteredQueue).length} tâches)`);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
log('error', `Erreur lors de la sauvegarde de la queue: ${err.message}`);
|
|
99
|
-
} finally {
|
|
100
|
-
isSaving = false;
|
|
101
|
-
}
|
|
121
|
+
})();
|
|
122
|
+
|
|
123
|
+
return saveLock;
|
|
102
124
|
}
|
|
103
125
|
|
|
104
|
-
// Démarrer la sauvegarde automatique
|
|
105
126
|
function startAutoSave() {
|
|
106
127
|
if (saveTimer) clearInterval(saveTimer);
|
|
107
|
-
|
|
128
|
+
|
|
108
129
|
saveTimer = setInterval(() => {
|
|
109
130
|
saveQueue();
|
|
110
131
|
}, SAVE_INTERVAL);
|
|
111
132
|
}
|
|
112
133
|
|
|
113
|
-
// Arrêter la sauvegarde automatique
|
|
114
134
|
function stopAutoSave() {
|
|
115
135
|
if (saveTimer) {
|
|
116
136
|
clearInterval(saveTimer);
|
|
@@ -124,31 +144,30 @@ function log(level, message) {
|
|
|
124
144
|
if (logHistory.length > MAX_LOGS) {
|
|
125
145
|
logHistory.shift();
|
|
126
146
|
}
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
if (['error', 'warn', 'info'].includes(level)) {
|
|
147
|
+
|
|
148
|
+
// ✅ N'afficher les logs que si MCP_DEBUG=true
|
|
149
|
+
if (!SILENT_MODE && ['error', 'warn', 'info'].includes(level)) {
|
|
130
150
|
const prefix = {
|
|
131
151
|
error: '[❌ ERROR]',
|
|
132
152
|
warn: '[⚠️ WARN]',
|
|
133
153
|
info: '[ℹ️ INFO]',
|
|
134
154
|
debug: '[🔧 DEBUG]'
|
|
135
155
|
}[level] || `[${level.toUpperCase()}]`;
|
|
136
|
-
|
|
137
|
-
|
|
156
|
+
|
|
157
|
+
// ✅ TOUJOURS utiliser stderr (pas stdout)
|
|
158
|
+
console.error(`${prefix} ${new Date().toISOString().split('T')[1].split('.')[0]} - ${message}`);
|
|
138
159
|
}
|
|
139
160
|
}
|
|
140
161
|
|
|
141
162
|
function addJob(details) {
|
|
142
|
-
// Vérifier la taille de la queue
|
|
143
163
|
if (Object.keys(jobQueue).length >= MAX_QUEUE_SIZE) {
|
|
144
|
-
// Nettoyer les vieilles tâches terminées
|
|
145
164
|
cleanOldJobs();
|
|
146
|
-
|
|
165
|
+
|
|
147
166
|
if (Object.keys(jobQueue).length >= MAX_QUEUE_SIZE) {
|
|
148
167
|
throw new Error(`Queue pleine (${MAX_QUEUE_SIZE} tâches max)`);
|
|
149
168
|
}
|
|
150
169
|
}
|
|
151
|
-
|
|
170
|
+
|
|
152
171
|
const id = uuidv4().split('-')[0];
|
|
153
172
|
const job = {
|
|
154
173
|
id,
|
|
@@ -164,7 +183,7 @@ function addJob(details) {
|
|
|
164
183
|
}
|
|
165
184
|
|
|
166
185
|
jobQueue[id] = job;
|
|
167
|
-
|
|
186
|
+
markDirty();
|
|
168
187
|
log('info', `Nouvelle tâche ${id} (${job.type}) ajoutée.`);
|
|
169
188
|
return jobQueue[id];
|
|
170
189
|
}
|
|
@@ -174,8 +193,7 @@ function updateJobStatus(id, status, data = {}) {
|
|
|
174
193
|
const oldStatus = jobQueue[id].status;
|
|
175
194
|
jobQueue[id].status = status;
|
|
176
195
|
jobQueue[id].updatedAt = new Date();
|
|
177
|
-
|
|
178
|
-
// Copier toutes les données supplémentaires
|
|
196
|
+
|
|
179
197
|
Object.assign(jobQueue[id], data);
|
|
180
198
|
|
|
181
199
|
if (data.error) {
|
|
@@ -186,12 +204,12 @@ function updateJobStatus(id, status, data = {}) {
|
|
|
186
204
|
jobQueue[id].completedAt = new Date();
|
|
187
205
|
const duration = jobQueue[id].completedAt - jobQueue[id].createdAt;
|
|
188
206
|
jobQueue[id].duration = duration;
|
|
189
|
-
log('info', `Tâche ${id} terminée en ${(duration/1000).toFixed(2)}s`);
|
|
207
|
+
log('info', `Tâche ${id} terminée en ${(duration / 1000).toFixed(2)}s`);
|
|
190
208
|
} else {
|
|
191
209
|
log('info', `Tâche ${id}: ${oldStatus} -> ${status}`);
|
|
192
210
|
}
|
|
193
211
|
|
|
194
|
-
|
|
212
|
+
markDirty();
|
|
195
213
|
}
|
|
196
214
|
}
|
|
197
215
|
|
|
@@ -207,7 +225,7 @@ function getLogs(filter = {}) {
|
|
|
207
225
|
if (!filter || Object.keys(filter).length === 0) {
|
|
208
226
|
return logHistory;
|
|
209
227
|
}
|
|
210
|
-
|
|
228
|
+
|
|
211
229
|
return logHistory.filter(log => {
|
|
212
230
|
if (filter.level && log.level !== filter.level) return false;
|
|
213
231
|
if (filter.since && new Date(log.timestamp) < new Date(filter.since)) return false;
|
|
@@ -216,47 +234,57 @@ function getLogs(filter = {}) {
|
|
|
216
234
|
});
|
|
217
235
|
}
|
|
218
236
|
|
|
219
|
-
// Nettoyer les vieilles tâches terminées
|
|
220
237
|
function cleanOldJobs() {
|
|
221
238
|
const now = Date.now();
|
|
222
239
|
const toDelete = [];
|
|
223
|
-
|
|
240
|
+
const MAX_AGE = 86400000;
|
|
241
|
+
|
|
224
242
|
for (const [id, job] of Object.entries(jobQueue)) {
|
|
225
|
-
const
|
|
226
|
-
|
|
243
|
+
const createdAt = job.createdAt ? new Date(job.createdAt).getTime() : now;
|
|
244
|
+
if (isNaN(createdAt)) {
|
|
245
|
+
log('warn', `Tâche ${id} a une date de création invalide, conservation`);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const age = now - createdAt;
|
|
250
|
+
|
|
251
|
+
if (age < 0) {
|
|
252
|
+
log('warn', `Tâche ${id} a une date dans le futur, conservation`);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const isOld = age > MAX_AGE;
|
|
227
257
|
const isCompleted = ['completed', 'failed'].includes(job.status);
|
|
228
|
-
|
|
258
|
+
|
|
229
259
|
if (isOld && isCompleted) {
|
|
230
260
|
toDelete.push(id);
|
|
231
261
|
}
|
|
232
262
|
}
|
|
233
|
-
|
|
263
|
+
|
|
234
264
|
for (const id of toDelete) {
|
|
235
265
|
delete jobQueue[id];
|
|
236
266
|
}
|
|
237
|
-
|
|
267
|
+
|
|
238
268
|
if (toDelete.length > 0) {
|
|
239
269
|
log('info', `${toDelete.length} vieilles tâches supprimées de la queue`);
|
|
240
|
-
|
|
270
|
+
markDirty();
|
|
241
271
|
}
|
|
242
272
|
}
|
|
243
273
|
|
|
244
|
-
// Réessayer une tâche échouée ou crashed
|
|
245
274
|
async function retryJob(id) {
|
|
246
275
|
const job = jobQueue[id];
|
|
247
276
|
if (!job) {
|
|
248
277
|
throw new Error(`Tâche ${id} introuvable`);
|
|
249
278
|
}
|
|
250
|
-
|
|
279
|
+
|
|
251
280
|
if (!['failed', 'crashed'].includes(job.status)) {
|
|
252
281
|
throw new Error(`La tâche ${id} ne peut pas être réessayée (statut: ${job.status})`);
|
|
253
282
|
}
|
|
254
|
-
|
|
283
|
+
|
|
255
284
|
if (job.retryCount >= job.maxRetries) {
|
|
256
285
|
throw new Error(`La tâche ${id} a atteint le nombre max de tentatives (${job.maxRetries})`);
|
|
257
286
|
}
|
|
258
|
-
|
|
259
|
-
// Créer une nouvelle tâche basée sur l'ancienne
|
|
287
|
+
|
|
260
288
|
const newJob = {
|
|
261
289
|
...job,
|
|
262
290
|
id: uuidv4().split('-')[0],
|
|
@@ -268,29 +296,77 @@ async function retryJob(id) {
|
|
|
268
296
|
error: null,
|
|
269
297
|
output: null
|
|
270
298
|
};
|
|
271
|
-
|
|
299
|
+
|
|
272
300
|
delete newJob.failedAt;
|
|
273
301
|
delete newJob.crashedAt;
|
|
274
302
|
delete newJob.completedAt;
|
|
275
|
-
|
|
303
|
+
|
|
276
304
|
jobQueue[newJob.id] = newJob;
|
|
277
|
-
|
|
278
|
-
|
|
305
|
+
markDirty();
|
|
306
|
+
|
|
279
307
|
log('info', `Tâche ${id} réessayée -> nouvelle tâche ${newJob.id} (tentative ${newJob.retryCount}/${newJob.maxRetries})`);
|
|
280
|
-
|
|
308
|
+
|
|
281
309
|
return newJob;
|
|
282
310
|
}
|
|
283
311
|
|
|
284
|
-
// Obtenir les tâches crashées qui peuvent être reprises
|
|
285
312
|
function getCrashedJobs() {
|
|
286
|
-
return Object.values(jobQueue).filter(job =>
|
|
287
|
-
job.status === 'crashed' &&
|
|
313
|
+
return Object.values(jobQueue).filter(job =>
|
|
314
|
+
job.status === 'crashed' &&
|
|
288
315
|
job.canRetry &&
|
|
289
316
|
job.retryCount < job.maxRetries
|
|
290
317
|
);
|
|
291
318
|
}
|
|
292
319
|
|
|
293
|
-
|
|
320
|
+
function getRetryableJobs(statusFilter = null) {
|
|
321
|
+
return Object.values(jobQueue).filter(job => {
|
|
322
|
+
if (!['failed', 'crashed'].includes(job.status)) return false;
|
|
323
|
+
if (statusFilter && job.status !== statusFilter) return false;
|
|
324
|
+
if ((job.retryCount || 0) >= (job.maxRetries || 3)) return false;
|
|
325
|
+
return true;
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Purge des jobs terminés/crashés.
|
|
331
|
+
* options = { status?: 'crashed'|'failed'|'completed'|'all_terminal', olderThanDays?: number, dryRun?: bool }
|
|
332
|
+
*/
|
|
333
|
+
function purgeJobs(options = {}) {
|
|
334
|
+
const olderThanDays = options.olderThanDays ?? 0;
|
|
335
|
+
const dryRun = options.dryRun === true;
|
|
336
|
+
const status = options.status || 'crashed';
|
|
337
|
+
const now = Date.now();
|
|
338
|
+
const maxAge = olderThanDays > 0 ? olderThanDays * 86400000 : 0;
|
|
339
|
+
|
|
340
|
+
const terminal = new Set(['completed', 'failed', 'crashed', 'partial']);
|
|
341
|
+
const toDelete = [];
|
|
342
|
+
|
|
343
|
+
for (const [id, job] of Object.entries(jobQueue)) {
|
|
344
|
+
let match = false;
|
|
345
|
+
if (status === 'all_terminal') {
|
|
346
|
+
match = terminal.has(job.status);
|
|
347
|
+
} else {
|
|
348
|
+
match = job.status === status;
|
|
349
|
+
}
|
|
350
|
+
if (!match) continue;
|
|
351
|
+
|
|
352
|
+
if (maxAge > 0) {
|
|
353
|
+
const created = job.createdAt ? new Date(job.createdAt).getTime() : now;
|
|
354
|
+
if (now - created < maxAge) continue;
|
|
355
|
+
}
|
|
356
|
+
toDelete.push(id);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!dryRun) {
|
|
360
|
+
for (const id of toDelete) delete jobQueue[id];
|
|
361
|
+
if (toDelete.length) {
|
|
362
|
+
markDirty();
|
|
363
|
+
log('info', `Purge: ${toDelete.length} tâche(s) supprimée(s) (status=${status})`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return { purged: toDelete.length, ids: toDelete, dryRun };
|
|
368
|
+
}
|
|
369
|
+
|
|
294
370
|
function getStats() {
|
|
295
371
|
const stats = {
|
|
296
372
|
total: Object.keys(jobQueue).length,
|
|
@@ -299,79 +375,58 @@ function getStats() {
|
|
|
299
375
|
avgDuration: 0,
|
|
300
376
|
successRate: 0
|
|
301
377
|
};
|
|
302
|
-
|
|
378
|
+
|
|
303
379
|
let totalDuration = 0;
|
|
304
380
|
let completedCount = 0;
|
|
305
|
-
|
|
381
|
+
|
|
306
382
|
for (const job of Object.values(jobQueue)) {
|
|
307
|
-
// Par statut
|
|
308
383
|
stats.byStatus[job.status] = (stats.byStatus[job.status] || 0) + 1;
|
|
309
|
-
|
|
310
|
-
// Par type
|
|
311
384
|
stats.byType[job.type] = (stats.byType[job.type] || 0) + 1;
|
|
312
|
-
|
|
313
|
-
// Durée moyenne
|
|
385
|
+
|
|
314
386
|
if (job.duration) {
|
|
315
387
|
totalDuration += job.duration;
|
|
316
388
|
completedCount++;
|
|
317
389
|
}
|
|
318
390
|
}
|
|
319
|
-
|
|
391
|
+
|
|
320
392
|
if (completedCount > 0) {
|
|
321
393
|
stats.avgDuration = Math.round(totalDuration / completedCount);
|
|
322
394
|
}
|
|
323
|
-
|
|
395
|
+
|
|
324
396
|
const totalFinished = (stats.byStatus.completed || 0) + (stats.byStatus.failed || 0);
|
|
325
397
|
if (totalFinished > 0) {
|
|
326
398
|
stats.successRate = Math.round((stats.byStatus.completed || 0) / totalFinished * 100);
|
|
327
399
|
}
|
|
328
|
-
|
|
400
|
+
|
|
329
401
|
return stats;
|
|
330
402
|
}
|
|
331
403
|
|
|
332
|
-
// Initialiser le module
|
|
333
404
|
async function init() {
|
|
334
405
|
await loadQueue();
|
|
335
406
|
startAutoSave();
|
|
336
|
-
|
|
337
|
-
// Nettoyer périodiquement
|
|
338
|
-
setInterval(cleanOldJobs, 3600000); // Toutes les heures
|
|
407
|
+
setInterval(cleanOldJobs, 3600000);
|
|
339
408
|
}
|
|
340
409
|
|
|
341
|
-
// Arrêter proprement
|
|
342
410
|
async function shutdown() {
|
|
343
411
|
log('info', 'Arrêt du gestionnaire de queue...');
|
|
344
412
|
stopAutoSave();
|
|
345
413
|
await saveQueue();
|
|
346
414
|
}
|
|
347
415
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
process.on('SIGTERM', async () => {
|
|
355
|
-
await shutdown();
|
|
356
|
-
process.exit(0);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// Démarrer automatiquement
|
|
360
|
-
init().catch(err => {
|
|
361
|
-
console.error('Erreur lors de l\'initialisation de la queue:', err);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
export default {
|
|
365
|
-
addJob,
|
|
366
|
-
updateJobStatus,
|
|
367
|
-
getJob,
|
|
368
|
-
getQueue,
|
|
369
|
-
getLogs,
|
|
416
|
+
export default {
|
|
417
|
+
addJob,
|
|
418
|
+
updateJobStatus,
|
|
419
|
+
getJob,
|
|
420
|
+
getQueue,
|
|
421
|
+
getLogs,
|
|
370
422
|
log,
|
|
371
423
|
retryJob,
|
|
372
424
|
getCrashedJobs,
|
|
425
|
+
getRetryableJobs,
|
|
426
|
+
purgeJobs,
|
|
373
427
|
getStats,
|
|
374
428
|
cleanOldJobs,
|
|
375
429
|
saveQueue,
|
|
376
|
-
shutdown
|
|
430
|
+
shutdown,
|
|
431
|
+
init
|
|
377
432
|
};
|