@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.
@@ -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
+ }>;