@bernierllc/retry-suite 0.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/LICENSE +5 -0
- package/README.md +654 -0
- package/dist/admin/admin-server.d.ts +30 -0
- package/dist/admin/admin-server.js +348 -0
- package/dist/components/retry-dashboard.d.ts +17 -0
- package/dist/components/retry-dashboard.js +406 -0
- package/dist/config/configuration-manager.d.ts +20 -0
- package/dist/config/configuration-manager.js +309 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +32 -0
- package/dist/integrations/email-integration.d.ts +12 -0
- package/dist/integrations/email-integration.js +215 -0
- package/dist/integrations/integration-manager.d.ts +15 -0
- package/dist/integrations/integration-manager.js +108 -0
- package/dist/integrations/slack-integration.d.ts +10 -0
- package/dist/integrations/slack-integration.js +94 -0
- package/dist/integrations/webhook-integration.d.ts +10 -0
- package/dist/integrations/webhook-integration.js +82 -0
- package/dist/retry-suite.d.ts +34 -0
- package/dist/retry-suite.js +328 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.js +9 -0
- package/package.json +71 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.AdminServer = void 0;
|
|
11
|
+
// Mock Express since dependency may not be available
|
|
12
|
+
const express = () => {
|
|
13
|
+
const app = {
|
|
14
|
+
use: jest.fn(),
|
|
15
|
+
get: jest.fn(),
|
|
16
|
+
post: jest.fn(),
|
|
17
|
+
put: jest.fn(),
|
|
18
|
+
listen: jest.fn((port, host, callback) => {
|
|
19
|
+
if (typeof host === 'function') {
|
|
20
|
+
callback = host;
|
|
21
|
+
host = 'localhost';
|
|
22
|
+
}
|
|
23
|
+
if (callback)
|
|
24
|
+
callback();
|
|
25
|
+
return {
|
|
26
|
+
close: jest.fn((cb) => cb && cb()),
|
|
27
|
+
on: jest.fn()
|
|
28
|
+
};
|
|
29
|
+
})
|
|
30
|
+
};
|
|
31
|
+
return app;
|
|
32
|
+
};
|
|
33
|
+
express.json = () => (req, res, next) => next();
|
|
34
|
+
express.static = () => (req, res, next) => next();
|
|
35
|
+
// Mock middleware functions since dependencies may not be available
|
|
36
|
+
const cors = () => (req, res, next) => {
|
|
37
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
38
|
+
res.header('Access-Control-Allow-Headers', '*');
|
|
39
|
+
res.header('Access-Control-Allow-Methods', '*');
|
|
40
|
+
next();
|
|
41
|
+
};
|
|
42
|
+
const helmet = () => (req, res, next) => {
|
|
43
|
+
res.header('X-DNS-Prefetch-Control', 'off');
|
|
44
|
+
next();
|
|
45
|
+
};
|
|
46
|
+
const compression = () => (req, res, next) => next();
|
|
47
|
+
// Mock HTTP server
|
|
48
|
+
const createServer = (app) => ({
|
|
49
|
+
listen: app.listen,
|
|
50
|
+
close: jest.fn((cb) => cb && cb()),
|
|
51
|
+
on: jest.fn()
|
|
52
|
+
});
|
|
53
|
+
// Mock WebSocket Server
|
|
54
|
+
const MockWebSocketServer = class {
|
|
55
|
+
constructor(options) { }
|
|
56
|
+
on(event, handler) { }
|
|
57
|
+
close(callback) {
|
|
58
|
+
if (callback)
|
|
59
|
+
callback();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const WebSocket = {
|
|
63
|
+
Server: MockWebSocketServer,
|
|
64
|
+
OPEN: 1
|
|
65
|
+
};
|
|
66
|
+
// Mock path module
|
|
67
|
+
const path = {
|
|
68
|
+
join: (...args) => args.join('/')
|
|
69
|
+
};
|
|
70
|
+
class AdminServer {
|
|
71
|
+
constructor(config, retrySuite) {
|
|
72
|
+
this.server = null;
|
|
73
|
+
this.wss = null;
|
|
74
|
+
this.connectedClients = new Set();
|
|
75
|
+
this.config = config;
|
|
76
|
+
this.retrySuite = retrySuite;
|
|
77
|
+
this.app = express();
|
|
78
|
+
this.setupMiddleware();
|
|
79
|
+
this.setupRoutes();
|
|
80
|
+
}
|
|
81
|
+
setupMiddleware() {
|
|
82
|
+
this.app.use(cors());
|
|
83
|
+
this.app.use(express.json());
|
|
84
|
+
this.app.use(helmet());
|
|
85
|
+
this.app.use(compression());
|
|
86
|
+
if (this.config.auth.enabled) {
|
|
87
|
+
this.app.use('/api', this.authMiddleware.bind(this));
|
|
88
|
+
}
|
|
89
|
+
this.app.use(express.static());
|
|
90
|
+
}
|
|
91
|
+
setupRoutes() {
|
|
92
|
+
// Health check
|
|
93
|
+
this.app.get('/health', (req, res) => {
|
|
94
|
+
res.json({
|
|
95
|
+
status: 'healthy',
|
|
96
|
+
timestamp: new Date(),
|
|
97
|
+
version: '0.1.0'
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
// API routes
|
|
101
|
+
this.app.get('/api/metrics', this.handleGetMetrics.bind(this));
|
|
102
|
+
this.app.get('/api/retries', this.handleGetRetries.bind(this));
|
|
103
|
+
this.app.get('/api/retries/:id', this.handleGetRetry.bind(this));
|
|
104
|
+
this.app.post('/api/retries/:id/cancel', this.handleCancelRetry.bind(this));
|
|
105
|
+
this.app.post('/api/retries/:id/pause', this.handlePauseRetry.bind(this));
|
|
106
|
+
this.app.post('/api/retries/:id/resume', this.handleResumeRetry.bind(this));
|
|
107
|
+
this.app.get('/api/alerts', this.handleGetAlerts.bind(this));
|
|
108
|
+
this.app.get('/api/reports/performance', this.handleGetPerformanceReport.bind(this));
|
|
109
|
+
this.app.get('/api/config', this.handleGetConfig.bind(this));
|
|
110
|
+
this.app.put('/api/config', this.handleUpdateConfig.bind(this));
|
|
111
|
+
// Serve React app for all other routes
|
|
112
|
+
this.app.get('*', (req, res) => {
|
|
113
|
+
res.json({
|
|
114
|
+
message: 'Retry Suite Admin API',
|
|
115
|
+
endpoints: {
|
|
116
|
+
health: '/health',
|
|
117
|
+
metrics: '/api/metrics',
|
|
118
|
+
retries: '/api/retries',
|
|
119
|
+
alerts: '/api/alerts',
|
|
120
|
+
reports: '/api/reports/performance',
|
|
121
|
+
config: '/api/config'
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
authMiddleware(req, res, next) {
|
|
127
|
+
if (!this.config.auth.enabled) {
|
|
128
|
+
next();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const auth = req.headers.authorization;
|
|
132
|
+
if (!auth || !auth.startsWith('Basic ')) {
|
|
133
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const credentials = Buffer.from(auth.slice(6), 'base64').toString();
|
|
137
|
+
const [username, password] = credentials.split(':');
|
|
138
|
+
if (username === this.config.auth.username &&
|
|
139
|
+
password === this.config.auth.password) {
|
|
140
|
+
next();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
res.status(401).json({ error: 'Invalid credentials' });
|
|
144
|
+
}
|
|
145
|
+
async handleGetMetrics(req, res) {
|
|
146
|
+
try {
|
|
147
|
+
const metrics = await this.retrySuite.getMetrics();
|
|
148
|
+
res.json(metrics);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
res.status(500).json({ error: 'Failed to get metrics' });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async handleGetRetries(req, res) {
|
|
155
|
+
try {
|
|
156
|
+
// Get all retry statuses
|
|
157
|
+
const retries = Array.from(this.retrySuite.retryStates.values());
|
|
158
|
+
res.json(retries);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
res.status(500).json({ error: 'Failed to get retries' });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async handleGetRetry(req, res) {
|
|
165
|
+
try {
|
|
166
|
+
const { id } = req.params;
|
|
167
|
+
const retry = await this.retrySuite.getRetryStatus(id);
|
|
168
|
+
if (!retry) {
|
|
169
|
+
res.status(404).json({ error: 'Retry not found' });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
res.json(retry);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
res.status(500).json({ error: 'Failed to get retry' });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async handleCancelRetry(req, res) {
|
|
179
|
+
try {
|
|
180
|
+
const { id } = req.params;
|
|
181
|
+
const result = await this.retrySuite.cancelRetry(id);
|
|
182
|
+
res.json(result);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
res.status(500).json({ error: 'Failed to cancel retry' });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async handlePauseRetry(req, res) {
|
|
189
|
+
try {
|
|
190
|
+
const { id } = req.params;
|
|
191
|
+
const result = await this.retrySuite.pauseRetry(id);
|
|
192
|
+
res.json(result);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
res.status(500).json({ error: 'Failed to pause retry' });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async handleResumeRetry(req, res) {
|
|
199
|
+
try {
|
|
200
|
+
const { id } = req.params;
|
|
201
|
+
const result = await this.retrySuite.resumeRetry(id);
|
|
202
|
+
res.json(result);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
res.status(500).json({ error: 'Failed to resume retry' });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async handleGetAlerts(req, res) {
|
|
209
|
+
try {
|
|
210
|
+
const alerts = await this.retrySuite.getAlerts();
|
|
211
|
+
res.json(alerts);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
res.status(500).json({ error: 'Failed to get alerts' });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async handleGetPerformanceReport(req, res) {
|
|
218
|
+
try {
|
|
219
|
+
const { startDate, endDate } = req.query;
|
|
220
|
+
const options = {
|
|
221
|
+
startDate: startDate ? new Date(startDate) : undefined,
|
|
222
|
+
endDate: endDate ? new Date(endDate) : undefined
|
|
223
|
+
};
|
|
224
|
+
const report = await this.retrySuite.getPerformanceReport(options);
|
|
225
|
+
res.json(report);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
res.status(500).json({ error: 'Failed to get performance report' });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async handleGetConfig(req, res) {
|
|
232
|
+
try {
|
|
233
|
+
const config = this.retrySuite.getConfiguration();
|
|
234
|
+
// Remove sensitive information
|
|
235
|
+
const sanitizedConfig = { ...config };
|
|
236
|
+
if (sanitizedConfig.admin?.auth?.password) {
|
|
237
|
+
sanitizedConfig.admin.auth.password = '[REDACTED]';
|
|
238
|
+
}
|
|
239
|
+
res.json(sanitizedConfig);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
res.status(500).json({ error: 'Failed to get configuration' });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async handleUpdateConfig(req, res) {
|
|
246
|
+
try {
|
|
247
|
+
const result = await this.retrySuite.updateConfiguration(req.body);
|
|
248
|
+
res.json(result);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
res.status(500).json({ error: 'Failed to update configuration' });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
setupWebSocket() {
|
|
255
|
+
if (!this.server)
|
|
256
|
+
return;
|
|
257
|
+
this.wss = new WebSocket.Server({ server: this.server });
|
|
258
|
+
this.wss.on('connection', (ws) => {
|
|
259
|
+
this.connectedClients.add(ws);
|
|
260
|
+
ws.on('close', () => {
|
|
261
|
+
this.connectedClients.delete(ws);
|
|
262
|
+
});
|
|
263
|
+
ws.on('error', (error) => {
|
|
264
|
+
console.error('WebSocket error:', error);
|
|
265
|
+
this.connectedClients.delete(ws);
|
|
266
|
+
});
|
|
267
|
+
// Send initial data
|
|
268
|
+
this.sendRealTimeUpdate(ws, 'connection', { status: 'connected' });
|
|
269
|
+
});
|
|
270
|
+
// Setup periodic updates
|
|
271
|
+
this.setupRealTimeUpdates();
|
|
272
|
+
}
|
|
273
|
+
setupRealTimeUpdates() {
|
|
274
|
+
setInterval(async () => {
|
|
275
|
+
if (this.connectedClients.size === 0)
|
|
276
|
+
return;
|
|
277
|
+
try {
|
|
278
|
+
const metrics = await this.retrySuite.getMetrics();
|
|
279
|
+
const retries = Array.from(this.retrySuite.retryStates.values());
|
|
280
|
+
const update = {
|
|
281
|
+
type: 'metrics_update',
|
|
282
|
+
data: { metrics, retries },
|
|
283
|
+
timestamp: new Date()
|
|
284
|
+
};
|
|
285
|
+
this.broadcastToClients(update);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
console.error('Failed to send real-time updates:', error);
|
|
289
|
+
}
|
|
290
|
+
}, 5000); // Update every 5 seconds
|
|
291
|
+
}
|
|
292
|
+
sendRealTimeUpdate(ws, type, data) {
|
|
293
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
294
|
+
ws.send(JSON.stringify({
|
|
295
|
+
type,
|
|
296
|
+
data,
|
|
297
|
+
timestamp: new Date()
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
broadcastToClients(data) {
|
|
302
|
+
const message = JSON.stringify(data);
|
|
303
|
+
this.connectedClients.forEach((ws) => {
|
|
304
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
305
|
+
ws.send(message);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
this.connectedClients.delete(ws);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
async start(port) {
|
|
313
|
+
return new Promise((resolve, reject) => {
|
|
314
|
+
this.server = createServer(this.app);
|
|
315
|
+
this.server.listen(port, this.config.host, () => {
|
|
316
|
+
console.log(`Retry Suite Admin Server running on http://${this.config.host}:${port}`);
|
|
317
|
+
this.setupWebSocket();
|
|
318
|
+
resolve();
|
|
319
|
+
});
|
|
320
|
+
this.server.on('error', (error) => {
|
|
321
|
+
console.error('Server error:', error);
|
|
322
|
+
reject(error);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async stop() {
|
|
327
|
+
return new Promise((resolve) => {
|
|
328
|
+
if (this.wss) {
|
|
329
|
+
this.wss.close(() => {
|
|
330
|
+
console.log('WebSocket server closed');
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
if (this.server) {
|
|
334
|
+
this.server.close(() => {
|
|
335
|
+
console.log('Admin server stopped');
|
|
336
|
+
resolve();
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
resolve();
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
getUrl() {
|
|
345
|
+
return `http://${this.config.host}:${this.config.port}`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
exports.AdminServer = AdminServer;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { RetryMetrics, Alert, RetryStatus } from '../types';
|
|
3
|
+
export interface RetryDashboardProps {
|
|
4
|
+
apiBaseUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const RetryDashboard: React.FC<RetryDashboardProps>;
|
|
7
|
+
export declare const MetricsOverview: React.FC<{
|
|
8
|
+
metrics: RetryMetrics | null;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const AlertsList: React.FC<{
|
|
11
|
+
alerts: Alert[];
|
|
12
|
+
onAcknowledge: (alertId: string) => void;
|
|
13
|
+
}>;
|
|
14
|
+
export declare const RetriesList: React.FC<{
|
|
15
|
+
retries: RetryStatus[];
|
|
16
|
+
onAction: (retryId: string, action: string) => void;
|
|
17
|
+
}>;
|