@gagandeep023/api-gateway 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/dist/index.js ADDED
@@ -0,0 +1,698 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ AnalyticsService: () => AnalyticsService,
34
+ DEFAULT_API_KEYS: () => DEFAULT_API_KEYS,
35
+ DEFAULT_IP_RULES: () => DEFAULT_IP_RULES,
36
+ DEFAULT_RATE_LIMIT_CONFIG: () => DEFAULT_RATE_LIMIT_CONFIG,
37
+ GatewayDashboard: () => GatewayDashboard,
38
+ RateLimiterService: () => RateLimiterService,
39
+ createApiKeyAuth: () => createApiKeyAuth,
40
+ createGatewayMiddleware: () => createGatewayMiddleware,
41
+ createGatewayRoutes: () => createGatewayRoutes,
42
+ createIpFilter: () => createIpFilter,
43
+ createRateLimiter: () => createRateLimiter,
44
+ createRequestLogger: () => createRequestLogger
45
+ });
46
+ module.exports = __toCommonJS(src_exports);
47
+
48
+ // src/backend/middleware/gateway.ts
49
+ var import_express = require("express");
50
+
51
+ // src/backend/services/RateLimiterService.ts
52
+ var TokenBucket = class {
53
+ buckets = /* @__PURE__ */ new Map();
54
+ tryConsume(ip, maxTokens, refillRate) {
55
+ const now = Date.now();
56
+ let bucket = this.buckets.get(ip);
57
+ if (!bucket) {
58
+ bucket = { tokens: maxTokens, lastRefill: now };
59
+ this.buckets.set(ip, bucket);
60
+ }
61
+ const elapsed = (now - bucket.lastRefill) / 1e3;
62
+ bucket.tokens = Math.min(maxTokens, bucket.tokens + elapsed * refillRate);
63
+ bucket.lastRefill = now;
64
+ if (bucket.tokens >= 1) {
65
+ bucket.tokens -= 1;
66
+ const resetMs2 = bucket.tokens <= 0 ? Math.ceil(1 / refillRate * 1e3) : 0;
67
+ return { allowed: true, remaining: Math.floor(bucket.tokens), resetMs: resetMs2 };
68
+ }
69
+ const resetMs = Math.ceil((1 - bucket.tokens) / refillRate * 1e3);
70
+ return { allowed: false, remaining: 0, resetMs };
71
+ }
72
+ };
73
+ var SlidingWindowLog = class {
74
+ windows = /* @__PURE__ */ new Map();
75
+ tryConsume(ip, maxRequests, windowMs) {
76
+ const now = Date.now();
77
+ let state = this.windows.get(ip);
78
+ if (!state) {
79
+ state = { timestamps: [] };
80
+ this.windows.set(ip, state);
81
+ }
82
+ state.timestamps = state.timestamps.filter((t) => now - t < windowMs);
83
+ if (state.timestamps.length < maxRequests) {
84
+ state.timestamps.push(now);
85
+ return {
86
+ allowed: true,
87
+ remaining: maxRequests - state.timestamps.length,
88
+ resetMs: state.timestamps.length > 0 ? windowMs - (now - state.timestamps[0]) : windowMs
89
+ };
90
+ }
91
+ const oldestInWindow = state.timestamps[0];
92
+ const resetMs = windowMs - (now - oldestInWindow);
93
+ return { allowed: false, remaining: 0, resetMs };
94
+ }
95
+ };
96
+ var FixedWindowCounter = class {
97
+ windows = /* @__PURE__ */ new Map();
98
+ tryConsume(ip, maxRequests, windowMs) {
99
+ const now = Date.now();
100
+ let state = this.windows.get(ip);
101
+ if (!state || now - state.windowStart >= windowMs) {
102
+ state = { count: 0, windowStart: now };
103
+ this.windows.set(ip, state);
104
+ }
105
+ const resetMs = windowMs - (now - state.windowStart);
106
+ if (state.count < maxRequests) {
107
+ state.count++;
108
+ return { allowed: true, remaining: maxRequests - state.count, resetMs };
109
+ }
110
+ return { allowed: false, remaining: 0, resetMs };
111
+ }
112
+ };
113
+ var RateLimiterService = class {
114
+ tokenBucket = new TokenBucket();
115
+ slidingWindow = new SlidingWindowLog();
116
+ fixedWindow = new FixedWindowCounter();
117
+ globalWindow = new FixedWindowCounter();
118
+ config;
119
+ _rateLimitHits = 0;
120
+ constructor(config) {
121
+ this.config = config;
122
+ }
123
+ get rateLimitHits() {
124
+ return this._rateLimitHits;
125
+ }
126
+ checkLimit(ip, tier) {
127
+ const globalResult = this.globalWindow.tryConsume(
128
+ "__global__",
129
+ this.config.globalLimit.maxRequests,
130
+ this.config.globalLimit.windowMs
131
+ );
132
+ if (!globalResult.allowed) {
133
+ this._rateLimitHits++;
134
+ return { allowed: false, remaining: 0, resetMs: globalResult.resetMs, limit: this.config.globalLimit.maxRequests };
135
+ }
136
+ const tierConfig = this.config.tiers[tier] || this.config.tiers[this.config.defaultTier];
137
+ if (!tierConfig || tierConfig.algorithm === "none") {
138
+ return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };
139
+ }
140
+ let result;
141
+ switch (tierConfig.algorithm) {
142
+ case "tokenBucket":
143
+ result = this.tokenBucket.tryConsume(
144
+ ip,
145
+ tierConfig.maxRequests,
146
+ tierConfig.refillRate || 1
147
+ );
148
+ break;
149
+ case "slidingWindow":
150
+ result = this.slidingWindow.tryConsume(
151
+ ip,
152
+ tierConfig.maxRequests,
153
+ tierConfig.windowMs
154
+ );
155
+ break;
156
+ case "fixedWindow":
157
+ result = this.fixedWindow.tryConsume(
158
+ ip,
159
+ tierConfig.maxRequests,
160
+ tierConfig.windowMs
161
+ );
162
+ break;
163
+ default:
164
+ return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };
165
+ }
166
+ if (!result.allowed) {
167
+ this._rateLimitHits++;
168
+ }
169
+ return { ...result, limit: tierConfig.maxRequests };
170
+ }
171
+ getConfig() {
172
+ return this.config;
173
+ }
174
+ };
175
+
176
+ // src/backend/services/AnalyticsService.ts
177
+ var MAX_LOG_SIZE = 1e4;
178
+ var ACTIVE_WINDOW_MS = 3e5;
179
+ var AnalyticsService = class {
180
+ logs = [];
181
+ head = 0;
182
+ count = 0;
183
+ addLog(log) {
184
+ if (this.count < MAX_LOG_SIZE) {
185
+ this.logs.push(log);
186
+ this.count++;
187
+ } else {
188
+ this.logs[this.head] = log;
189
+ this.head = (this.head + 1) % MAX_LOG_SIZE;
190
+ }
191
+ }
192
+ getRecentLogs(limit = 20, offset = 0) {
193
+ const ordered = this.getOrderedLogs();
194
+ return ordered.slice(offset, offset + limit);
195
+ }
196
+ getAnalytics(rateLimitHits) {
197
+ const now = Date.now();
198
+ const oneMinuteAgo = now - 6e4;
199
+ const activeWindowStart = now - ACTIVE_WINDOW_MS;
200
+ const ordered = this.getOrderedLogs();
201
+ const recentLogs = ordered.filter((l) => l.timestamp > oneMinuteAgo);
202
+ const requestsPerMinute = recentLogs.length;
203
+ const endpointCounts = /* @__PURE__ */ new Map();
204
+ for (const log of ordered) {
205
+ const current = endpointCounts.get(log.path) || 0;
206
+ endpointCounts.set(log.path, current + 1);
207
+ }
208
+ const topEndpoints = Array.from(endpointCounts.entries()).map(([path, count]) => ({ path, count })).sort((a, b) => b.count - a.count).slice(0, 5);
209
+ const errorCount = ordered.filter((l) => l.statusCode >= 400).length;
210
+ const errorRate = this.count > 0 ? errorCount / this.count * 100 : 0;
211
+ const totalResponseTime = ordered.reduce((sum, l) => sum + l.responseTime, 0);
212
+ const avgResponseTime = this.count > 0 ? totalResponseTime / this.count : 0;
213
+ const activeLogs = ordered.filter((l) => l.timestamp > activeWindowStart);
214
+ const uniqueIps = new Set(activeLogs.map((l) => l.ip));
215
+ const keyUsePairs = /* @__PURE__ */ new Set();
216
+ for (const log of activeLogs) {
217
+ if (log.apiKey) {
218
+ keyUsePairs.add(`${log.ip}::${log.apiKey}`);
219
+ }
220
+ }
221
+ return {
222
+ totalRequests: this.count,
223
+ requestsPerMinute,
224
+ topEndpoints,
225
+ errorRate: Math.round(errorRate * 100) / 100,
226
+ avgResponseTime: Math.round(avgResponseTime * 100) / 100,
227
+ activeClients: uniqueIps.size,
228
+ activeKeyUses: keyUsePairs.size,
229
+ rateLimitHits
230
+ };
231
+ }
232
+ getOrderedLogs() {
233
+ if (this.count < MAX_LOG_SIZE) {
234
+ return [...this.logs].reverse();
235
+ }
236
+ const tail = this.logs.slice(0, this.head);
237
+ const headPart = this.logs.slice(this.head);
238
+ return [...headPart, ...tail].reverse();
239
+ }
240
+ };
241
+
242
+ // src/config/defaults.ts
243
+ var DEFAULT_RATE_LIMIT_CONFIG = {
244
+ tiers: {
245
+ free: { algorithm: "tokenBucket", maxRequests: 100, windowMs: 6e4, refillRate: 10 },
246
+ pro: { algorithm: "slidingWindow", maxRequests: 1e3, windowMs: 6e4 },
247
+ unlimited: { algorithm: "none" }
248
+ },
249
+ defaultTier: "free",
250
+ globalLimit: { maxRequests: 1e4, windowMs: 6e4 }
251
+ };
252
+ var DEFAULT_IP_RULES = {
253
+ allowlist: [],
254
+ blocklist: [],
255
+ mode: "blocklist"
256
+ };
257
+ var DEFAULT_API_KEYS = {
258
+ keys: []
259
+ };
260
+
261
+ // src/backend/middleware/apiKeyAuth.ts
262
+ function createApiKeyAuth(getKeys) {
263
+ return function apiKeyAuth(req, res, next) {
264
+ const apiKey = req.header("X-API-Key") || req.query.apiKey;
265
+ if (!apiKey) {
266
+ req.clientId = req.ip || "unknown";
267
+ req.tier = "free";
268
+ next();
269
+ return;
270
+ }
271
+ const config = getKeys();
272
+ const keyEntry = config.keys.find((k) => k.key === apiKey && k.active);
273
+ if (!keyEntry) {
274
+ res.status(401).json({ error: "Invalid or revoked API key" });
275
+ return;
276
+ }
277
+ req.clientId = keyEntry.id;
278
+ req.tier = keyEntry.tier;
279
+ req.apiKeyValue = keyEntry.key;
280
+ next();
281
+ };
282
+ }
283
+
284
+ // src/backend/middleware/ipFilter.ts
285
+ function createIpFilter(getRules) {
286
+ return function ipFilter(req, res, next) {
287
+ const rules = getRules();
288
+ const clientIp = req.ip || req.socket.remoteAddress || "unknown";
289
+ if (rules.mode === "allowlist" && rules.allowlist.length > 0) {
290
+ if (!rules.allowlist.includes(clientIp)) {
291
+ res.status(403).json({ error: "IP not in allowlist" });
292
+ return;
293
+ }
294
+ }
295
+ if (rules.mode === "blocklist" && rules.blocklist.length > 0) {
296
+ if (rules.blocklist.includes(clientIp)) {
297
+ res.status(403).json({ error: "IP is blocked" });
298
+ return;
299
+ }
300
+ }
301
+ next();
302
+ };
303
+ }
304
+
305
+ // src/backend/middleware/rateLimiter.ts
306
+ function createRateLimiter(service) {
307
+ return function rateLimiter(req, res, next) {
308
+ const ip = req.ip || req.socket.remoteAddress || "unknown";
309
+ const tier = req.tier || "free";
310
+ const result = service.checkLimit(ip, tier);
311
+ if (result.limit > 0) {
312
+ res.setHeader("X-RateLimit-Limit", result.limit);
313
+ res.setHeader("X-RateLimit-Remaining", Math.max(0, result.remaining));
314
+ res.setHeader("X-RateLimit-Reset", Math.ceil(result.resetMs / 1e3));
315
+ }
316
+ if (!result.allowed) {
317
+ res.status(429).json({
318
+ error: "Rate limit exceeded",
319
+ retryAfter: Math.ceil(result.resetMs / 1e3)
320
+ });
321
+ return;
322
+ }
323
+ next();
324
+ };
325
+ }
326
+
327
+ // src/backend/middleware/requestLogger.ts
328
+ function createRequestLogger(analytics) {
329
+ return function requestLogger(req, res, next) {
330
+ const start = Date.now();
331
+ res.on("finish", () => {
332
+ const responseTime = Date.now() - start;
333
+ analytics.addLog({
334
+ timestamp: Date.now(),
335
+ method: req.method,
336
+ path: req.originalUrl,
337
+ statusCode: res.statusCode,
338
+ responseTime,
339
+ clientId: req.clientId || req.ip || "unknown",
340
+ ip: req.ip || req.socket.remoteAddress || "unknown",
341
+ apiKey: req.apiKeyValue || void 0
342
+ });
343
+ });
344
+ next();
345
+ };
346
+ }
347
+
348
+ // src/backend/middleware/gateway.ts
349
+ function createGatewayMiddleware(userConfig) {
350
+ const config = {
351
+ rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,
352
+ ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,
353
+ apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS
354
+ };
355
+ const rateLimiterService = new RateLimiterService(config.rateLimits);
356
+ const analyticsService = new AnalyticsService();
357
+ const router = (0, import_express.Router)();
358
+ router.use(createRequestLogger(analyticsService));
359
+ router.use(createApiKeyAuth(() => config.apiKeys));
360
+ router.use(createIpFilter(() => config.ipRules));
361
+ router.use(createRateLimiter(rateLimiterService));
362
+ return {
363
+ rateLimiterService,
364
+ analyticsService,
365
+ middleware: router,
366
+ config
367
+ };
368
+ }
369
+
370
+ // src/backend/routes/gateway.ts
371
+ var import_express2 = require("express");
372
+ var import_crypto = __toESM(require("crypto"));
373
+ function createGatewayRoutes(options) {
374
+ const { rateLimiterService, analyticsService, config } = options;
375
+ const router = (0, import_express2.Router)();
376
+ router.get("/analytics", (_req, res) => {
377
+ const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);
378
+ res.json(analytics);
379
+ });
380
+ router.get("/analytics/live", (_req, res) => {
381
+ res.setHeader("Content-Type", "text/event-stream");
382
+ res.setHeader("Cache-Control", "no-cache");
383
+ res.setHeader("Connection", "keep-alive");
384
+ res.setHeader("X-Accel-Buffering", "no");
385
+ res.setHeader("Access-Control-Allow-Origin", "*");
386
+ res.flushHeaders();
387
+ const send = () => {
388
+ const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);
389
+ res.write(`data: ${JSON.stringify(analytics)}
390
+
391
+ `);
392
+ };
393
+ send();
394
+ const interval = setInterval(send, 5e3);
395
+ _req.on("close", () => {
396
+ clearInterval(interval);
397
+ });
398
+ });
399
+ router.get("/config", (_req, res) => {
400
+ const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);
401
+ res.json({
402
+ rateLimits: config.rateLimits,
403
+ ipRules: config.ipRules,
404
+ activeKeys: config.apiKeys.keys.filter((k) => k.active).length,
405
+ activeKeyUses: analytics.activeKeyUses
406
+ });
407
+ });
408
+ router.post("/keys", (req, res) => {
409
+ const { name, tier } = req.body;
410
+ if (!name) {
411
+ res.status(400).json({ error: "Name is required" });
412
+ return;
413
+ }
414
+ const newKey = {
415
+ id: `key_${String(config.apiKeys.keys.length + 1).padStart(3, "0")}`,
416
+ key: `gw_live_${import_crypto.default.randomBytes(16).toString("hex")}`,
417
+ name,
418
+ tier: tier || "free",
419
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
420
+ active: true
421
+ };
422
+ config.apiKeys.keys.push(newKey);
423
+ res.status(201).json(newKey);
424
+ });
425
+ router.delete("/keys/:keyId", (req, res) => {
426
+ const { keyId } = req.params;
427
+ const key = config.apiKeys.keys.find((k) => k.id === keyId);
428
+ if (!key) {
429
+ res.status(404).json({ error: "API key not found" });
430
+ return;
431
+ }
432
+ key.active = false;
433
+ res.json({ message: "API key revoked", id: keyId });
434
+ });
435
+ router.get("/logs", (req, res) => {
436
+ const limit = parseInt(req.query.limit) || 20;
437
+ const offset = parseInt(req.query.offset) || 0;
438
+ const logs = analyticsService.getRecentLogs(limit, offset);
439
+ res.json({ logs, limit, offset });
440
+ });
441
+ return router;
442
+ }
443
+
444
+ // src/frontend/GatewayDashboard.tsx
445
+ var import_react = require("react");
446
+ var import_recharts = require("recharts");
447
+ var import_jsx_runtime = require("react/jsx-runtime");
448
+ function useGatewayApi(apiBaseUrl, path) {
449
+ const [data, setData] = (0, import_react.useState)(null);
450
+ (0, import_react.useEffect)(() => {
451
+ fetch(`${apiBaseUrl}${path}`).then((r) => r.json()).then(setData).catch(() => {
452
+ });
453
+ }, [apiBaseUrl, path]);
454
+ return { data };
455
+ }
456
+ function GatewayDashboard({ apiBaseUrl }) {
457
+ const [analytics, setAnalytics] = (0, import_react.useState)(null);
458
+ const [rpmHistory, setRpmHistory] = (0, import_react.useState)([]);
459
+ const { data: config } = useGatewayApi(apiBaseUrl, "/gateway/config");
460
+ const { data: logsData } = useGatewayApi(apiBaseUrl, "/gateway/logs?limit=20");
461
+ const eventSourceRef = (0, import_react.useRef)(null);
462
+ (0, import_react.useEffect)(() => {
463
+ const es = new EventSource(`${apiBaseUrl}/gateway/analytics/live`);
464
+ eventSourceRef.current = es;
465
+ es.onmessage = (event) => {
466
+ const data = JSON.parse(event.data);
467
+ setAnalytics(data);
468
+ setRpmHistory((prev) => {
469
+ const next = [
470
+ ...prev,
471
+ { time: (/* @__PURE__ */ new Date()).toLocaleTimeString(), rpm: data.requestsPerMinute }
472
+ ];
473
+ return next.slice(-20);
474
+ });
475
+ };
476
+ return () => {
477
+ es.close();
478
+ };
479
+ }, [apiBaseUrl]);
480
+ const getMethodClass = (method) => {
481
+ switch (method) {
482
+ case "GET":
483
+ return "gw-method-get";
484
+ case "POST":
485
+ return "gw-method-post";
486
+ case "DELETE":
487
+ return "gw-method-delete";
488
+ default:
489
+ return "gw-method-get";
490
+ }
491
+ };
492
+ const getStatusClass = (code) => {
493
+ if (code === 429) return "gw-status-rate-limit";
494
+ if (code >= 400) return "gw-status-error";
495
+ return "gw-status-ok";
496
+ };
497
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-dashboard", children: [
498
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-header", children: [
499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "API Gateway Dashboard" }),
500
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Real-time monitoring for the API gateway and rate limiter" }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-status-badge", children: [
502
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-status-dot" }),
503
+ "Live"
504
+ ] })
505
+ ] }),
506
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stats-grid", children: [
507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stat-card", children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-label", children: "Total Requests" }),
509
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-value", children: analytics?.totalRequests ?? 0 })
510
+ ] }),
511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stat-card", children: [
512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-label", children: "Requests / Min" }),
513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-value gw-accent", children: analytics?.requestsPerMinute ?? 0 })
514
+ ] }),
515
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stat-card", children: [
516
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-label", children: "Error Rate" }),
517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `gw-stat-value ${analytics && analytics.errorRate > 5 ? "gw-danger" : ""}`, children: [
518
+ analytics?.errorRate ?? 0,
519
+ "%"
520
+ ] })
521
+ ] }),
522
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stat-card", children: [
523
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-label", children: "Avg Response Time" }),
524
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stat-value", children: [
525
+ analytics?.avgResponseTime ?? 0,
526
+ "ms"
527
+ ] })
528
+ ] })
529
+ ] }),
530
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stats-grid", style: { marginBottom: 32 }, children: [
531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stat-card", children: [
532
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-label", children: "Rate Limit Hits" }),
533
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-value gw-warning", children: analytics?.rateLimitHits ?? 0 })
534
+ ] }),
535
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stat-card", children: [
536
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-label", children: "Active IPs" }),
537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-value", children: analytics?.activeClients ?? 0 })
538
+ ] }),
539
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stat-card", children: [
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-label", children: "Active Key Sessions" }),
541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-value", children: analytics?.activeKeyUses ?? 0 })
542
+ ] }),
543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-stat-card", children: [
544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-label", children: "Rate Limit Tiers" }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-stat-value", children: config ? Object.keys(config.rateLimits.tiers).length : 0 })
546
+ ] })
547
+ ] }),
548
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-charts-row", children: [
549
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-chart-card", children: [
550
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-chart-title", children: "Requests Per Minute" }),
551
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.ResponsiveContainer, { width: "100%", height: 250, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_recharts.LineChart, { data: rpmHistory, children: [
552
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: "var(--gw-border, #2a2a2a)" }),
553
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.XAxis, { dataKey: "time", tick: { fill: "var(--gw-text-muted, #888)", fontSize: 11 } }),
554
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.YAxis, { tick: { fill: "var(--gw-text-muted, #888)", fontSize: 11 } }),
555
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
556
+ import_recharts.Tooltip,
557
+ {
558
+ contentStyle: { background: "var(--gw-bg-card, #1a1a1a)", border: "1px solid var(--gw-border, #2a2a2a)", borderRadius: 8 },
559
+ labelStyle: { color: "var(--gw-text-muted, #888)" }
560
+ }
561
+ ),
562
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
563
+ import_recharts.Line,
564
+ {
565
+ type: "monotone",
566
+ dataKey: "rpm",
567
+ stroke: "var(--gw-accent, #64ffda)",
568
+ strokeWidth: 2,
569
+ dot: false,
570
+ activeDot: { r: 4, fill: "var(--gw-accent, #64ffda)" }
571
+ }
572
+ )
573
+ ] }) })
574
+ ] }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-chart-card", children: [
576
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-chart-title", children: "Top Endpoints" }),
577
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.ResponsiveContainer, { width: "100%", height: 250, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
578
+ import_recharts.BarChart,
579
+ {
580
+ data: analytics?.topEndpoints ?? [],
581
+ layout: "vertical",
582
+ children: [
583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: "var(--gw-border, #2a2a2a)" }),
584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.XAxis, { type: "number", tick: { fill: "var(--gw-text-muted, #888)", fontSize: 11 } }),
585
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
586
+ import_recharts.YAxis,
587
+ {
588
+ dataKey: "path",
589
+ type: "category",
590
+ tick: { fill: "var(--gw-text-muted, #888)", fontSize: 10 },
591
+ width: 120
592
+ }
593
+ ),
594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
595
+ import_recharts.Tooltip,
596
+ {
597
+ contentStyle: { background: "var(--gw-bg-card, #1a1a1a)", border: "1px solid var(--gw-border, #2a2a2a)", borderRadius: 8 }
598
+ }
599
+ ),
600
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.Bar, { dataKey: "count", fill: "var(--gw-accent, #64ffda)", radius: [0, 4, 4, 0] })
601
+ ]
602
+ }
603
+ ) })
604
+ ] })
605
+ ] }),
606
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-logs-section", children: [
607
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gw-logs-title", children: "Recent Requests" }),
608
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "gw-logs-table", children: [
609
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
610
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Time" }),
611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Method" }),
612
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Path" }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Status" }),
614
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Duration" }),
615
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "IP" })
616
+ ] }) }),
617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tbody", { children: [
618
+ (logsData?.logs ?? []).map((log, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: new Date(log.timestamp).toLocaleTimeString() }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `gw-method-badge ${getMethodClass(log.method)}`, children: log.method }) }),
621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: log.path }),
622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: getStatusClass(log.statusCode), children: log.statusCode }),
623
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
624
+ log.responseTime,
625
+ "ms"
626
+ ] }),
627
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: log.ip })
628
+ ] }, i)),
629
+ (!logsData?.logs || logsData.logs.length === 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { colSpan: 6, style: { textAlign: "center", color: "var(--gw-text-muted, #666)" }, children: "No requests logged yet. Make some API calls to see data here." }) })
630
+ ] })
631
+ ] })
632
+ ] }),
633
+ config && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-config-section", children: [
634
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-config-card", children: [
635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "Rate Limit Tiers" }),
636
+ Object.entries(config.rateLimits.tiers).map(([name, tier]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-tier-item", children: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-name", children: name }),
638
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-detail", children: tier.algorithm === "none" ? "unlimited" : `${tier.maxRequests} req / ${(tier.windowMs || 6e4) / 1e3}s` })
639
+ ] }, name))
640
+ ] }),
641
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-config-card", children: [
642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "IP Rules" }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-tier-item", children: [
644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-name", children: "Mode" }),
645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-detail", children: config.ipRules.mode })
646
+ ] }),
647
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-tier-item", children: [
648
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-name", children: "Allowlist" }),
649
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-detail", children: config.ipRules.allowlist.length === 0 ? "empty" : config.ipRules.allowlist.length + " IPs" })
650
+ ] }),
651
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-tier-item", children: [
652
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-name", children: "Blocklist" }),
653
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-detail", children: config.ipRules.blocklist.length === 0 ? "empty" : config.ipRules.blocklist.length + " IPs" })
654
+ ] })
655
+ ] }),
656
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-config-card", children: [
657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "Global Limit" }),
658
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-tier-item", children: [
659
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-name", children: "Max Requests" }),
660
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "gw-tier-detail", children: [
661
+ config.rateLimits.globalLimit.maxRequests,
662
+ " / ",
663
+ config.rateLimits.globalLimit.windowMs / 1e3,
664
+ "s"
665
+ ] })
666
+ ] }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-tier-item", children: [
668
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-name", children: "Default Tier" }),
669
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-detail", children: config.rateLimits.defaultTier })
670
+ ] }),
671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-tier-item", children: [
672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-name", children: "Active Keys" }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-detail", children: config.activeKeys })
674
+ ] }),
675
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gw-tier-item", children: [
676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-name", children: "Active Key Sessions" }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gw-tier-detail", children: config.activeKeyUses })
678
+ ] })
679
+ ] })
680
+ ] })
681
+ ] });
682
+ }
683
+ // Annotate the CommonJS export names for ESM import in node:
684
+ 0 && (module.exports = {
685
+ AnalyticsService,
686
+ DEFAULT_API_KEYS,
687
+ DEFAULT_IP_RULES,
688
+ DEFAULT_RATE_LIMIT_CONFIG,
689
+ GatewayDashboard,
690
+ RateLimiterService,
691
+ createApiKeyAuth,
692
+ createGatewayMiddleware,
693
+ createGatewayRoutes,
694
+ createIpFilter,
695
+ createRateLimiter,
696
+ createRequestLogger
697
+ });
698
+ //# sourceMappingURL=index.js.map