@gagandeep023/api-gateway 0.2.0 → 0.3.1
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/backend/index.js +3 -2
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs +3 -2
- package/dist/backend/index.mjs.map +1 -1
- package/dist/frontend/GatewayDashboard.css +18 -0
- package/dist/frontend/index.d.mts +2 -1
- package/dist/frontend/index.d.ts +2 -1
- package/dist/frontend/index.js +55 -27
- package/dist/frontend/index.js.map +1 -1
- package/dist/frontend/index.mjs +56 -28
- package/dist/frontend/index.mjs.map +1 -1
- package/dist/index.js +58 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +59 -30
- package/dist/index.mjs.map +1 -1
- package/dist/types/index.d.mts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/backend/middleware/gateway.ts","../src/backend/services/RateLimiterService.ts","../src/backend/services/AnalyticsService.ts","../src/config/defaults.ts","../src/backend/middleware/apiKeyAuth.ts","../src/backend/middleware/ipFilter.ts","../src/backend/middleware/rateLimiter.ts","../src/backend/middleware/requestLogger.ts","../src/backend/routes/gateway.ts","../src/frontend/GatewayDashboard.tsx"],"sourcesContent":["// Backend exports\nexport { createGatewayMiddleware } from './backend/middleware/gateway';\nexport type { GatewayInstances } from './backend/middleware/gateway';\nexport { createGatewayRoutes } from './backend/routes/gateway';\nexport type { GatewayRoutesOptions } from './backend/routes/gateway';\nexport { RateLimiterService } from './backend/services/RateLimiterService';\nexport { AnalyticsService } from './backend/services/AnalyticsService';\nexport { createApiKeyAuth } from './backend/middleware/apiKeyAuth';\nexport { createIpFilter } from './backend/middleware/ipFilter';\nexport { createRateLimiter } from './backend/middleware/rateLimiter';\nexport { createRequestLogger } from './backend/middleware/requestLogger';\n\n// Frontend exports\nexport { GatewayDashboard } from './frontend/GatewayDashboard';\nexport type { GatewayDashboardProps } from './frontend/GatewayDashboard';\n\n// Type exports\nexport type {\n ApiKey,\n ApiKeysConfig,\n TierConfig,\n RateLimitConfig,\n IpRules,\n BucketState,\n SlidingWindowState,\n FixedWindowState,\n RequestLog,\n GatewayAnalytics,\n GatewayConfig,\n GatewayMiddlewareConfig,\n} from './types';\n\n// Config exports\nexport {\n DEFAULT_RATE_LIMIT_CONFIG,\n DEFAULT_IP_RULES,\n DEFAULT_API_KEYS,\n} from './config/defaults';\n","import { Router } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport { DEFAULT_RATE_LIMIT_CONFIG, DEFAULT_IP_RULES, DEFAULT_API_KEYS } from '../../config/defaults';\nimport { createApiKeyAuth } from './apiKeyAuth';\nimport { createIpFilter } from './ipFilter';\nimport { createRateLimiter } from './rateLimiter';\nimport { createRequestLogger } from './requestLogger';\n\nexport interface GatewayInstances {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n middleware: Router;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances {\n const config: Required<GatewayMiddlewareConfig> = {\n rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,\n ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,\n apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,\n };\n\n const rateLimiterService = new RateLimiterService(config.rateLimits);\n const analyticsService = new AnalyticsService();\n\n const router = Router();\n router.use(createRequestLogger(analyticsService));\n router.use(createApiKeyAuth(() => config.apiKeys));\n router.use(createIpFilter(() => config.ipRules));\n router.use(createRateLimiter(rateLimiterService));\n\n return {\n rateLimiterService,\n analyticsService,\n middleware: router,\n config,\n };\n}\n","import type { BucketState, SlidingWindowState, FixedWindowState, TierConfig, RateLimitConfig } from '../../types';\n\nclass TokenBucket {\n private buckets = new Map<string, BucketState>();\n\n tryConsume(ip: string, maxTokens: number, refillRate: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let bucket = this.buckets.get(ip);\n\n if (!bucket) {\n bucket = { tokens: maxTokens, lastRefill: now };\n this.buckets.set(ip, bucket);\n }\n\n const elapsed = (now - bucket.lastRefill) / 1000;\n bucket.tokens = Math.min(maxTokens, bucket.tokens + elapsed * refillRate);\n bucket.lastRefill = now;\n\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n const resetMs = bucket.tokens <= 0 ? Math.ceil((1 / refillRate) * 1000) : 0;\n return { allowed: true, remaining: Math.floor(bucket.tokens), resetMs };\n }\n\n const resetMs = Math.ceil(((1 - bucket.tokens) / refillRate) * 1000);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass SlidingWindowLog {\n private windows = new Map<string, SlidingWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state) {\n state = { timestamps: [] };\n this.windows.set(ip, state);\n }\n\n state.timestamps = state.timestamps.filter(t => now - t < windowMs);\n\n if (state.timestamps.length < maxRequests) {\n state.timestamps.push(now);\n return {\n allowed: true,\n remaining: maxRequests - state.timestamps.length,\n resetMs: state.timestamps.length > 0 ? windowMs - (now - state.timestamps[0]) : windowMs,\n };\n }\n\n const oldestInWindow = state.timestamps[0];\n const resetMs = windowMs - (now - oldestInWindow);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass FixedWindowCounter {\n private windows = new Map<string, FixedWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state || now - state.windowStart >= windowMs) {\n state = { count: 0, windowStart: now };\n this.windows.set(ip, state);\n }\n\n const resetMs = windowMs - (now - state.windowStart);\n\n if (state.count < maxRequests) {\n state.count++;\n return { allowed: true, remaining: maxRequests - state.count, resetMs };\n }\n\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nexport class RateLimiterService {\n private tokenBucket = new TokenBucket();\n private slidingWindow = new SlidingWindowLog();\n private fixedWindow = new FixedWindowCounter();\n private globalWindow = new FixedWindowCounter();\n private config: RateLimitConfig;\n private _rateLimitHits = 0;\n\n constructor(config: RateLimitConfig) {\n this.config = config;\n }\n\n get rateLimitHits(): number {\n return this._rateLimitHits;\n }\n\n checkLimit(ip: string, tier: string): { allowed: boolean; remaining: number; resetMs: number; limit: number } {\n const globalResult = this.globalWindow.tryConsume(\n '__global__',\n this.config.globalLimit.maxRequests,\n this.config.globalLimit.windowMs\n );\n\n if (!globalResult.allowed) {\n this._rateLimitHits++;\n return { allowed: false, remaining: 0, resetMs: globalResult.resetMs, limit: this.config.globalLimit.maxRequests };\n }\n\n const tierConfig = this.config.tiers[tier] || this.config.tiers[this.config.defaultTier];\n\n if (!tierConfig || tierConfig.algorithm === 'none') {\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n let result: { allowed: boolean; remaining: number; resetMs: number };\n\n switch (tierConfig.algorithm) {\n case 'tokenBucket':\n result = this.tokenBucket.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.refillRate || 1\n );\n break;\n case 'slidingWindow':\n result = this.slidingWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n case 'fixedWindow':\n result = this.fixedWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n default:\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n if (!result.allowed) {\n this._rateLimitHits++;\n }\n\n return { ...result, limit: tierConfig.maxRequests! };\n }\n\n getConfig(): RateLimitConfig {\n return this.config;\n }\n}\n","import type { RequestLog, GatewayAnalytics } from '../../types';\n\nconst MAX_LOG_SIZE = 10000;\nconst ACTIVE_WINDOW_MS = 300000; // 5 minutes\n\nexport class AnalyticsService {\n private logs: RequestLog[] = [];\n private head = 0;\n private count = 0;\n\n addLog(log: RequestLog): void {\n if (this.count < MAX_LOG_SIZE) {\n this.logs.push(log);\n this.count++;\n } else {\n this.logs[this.head] = log;\n this.head = (this.head + 1) % MAX_LOG_SIZE;\n }\n }\n\n getRecentLogs(limit = 20, offset = 0): RequestLog[] {\n const ordered = this.getOrderedLogs();\n return ordered.slice(offset, offset + limit);\n }\n\n getAnalytics(rateLimitHits: number): GatewayAnalytics {\n const now = Date.now();\n const oneMinuteAgo = now - 60000;\n const activeWindowStart = now - ACTIVE_WINDOW_MS;\n const ordered = this.getOrderedLogs();\n\n const recentLogs = ordered.filter(l => l.timestamp > oneMinuteAgo);\n const requestsPerMinute = recentLogs.length;\n\n // Top endpoints\n const endpointCounts = new Map<string, number>();\n for (const log of ordered) {\n const current = endpointCounts.get(log.path) || 0;\n endpointCounts.set(log.path, current + 1);\n }\n const topEndpoints = Array.from(endpointCounts.entries())\n .map(([path, count]) => ({ path, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 5);\n\n // Error rate\n const errorCount = ordered.filter(l => l.statusCode >= 400).length;\n const errorRate = this.count > 0 ? (errorCount / this.count) * 100 : 0;\n\n // Average response time\n const totalResponseTime = ordered.reduce((sum, l) => sum + l.responseTime, 0);\n const avgResponseTime = this.count > 0 ? totalResponseTime / this.count : 0;\n\n // Active clients: unique IPs in last 5 minutes\n const activeLogs = ordered.filter(l => l.timestamp > activeWindowStart);\n const uniqueIps = new Set(activeLogs.map(l => l.ip));\n\n // Active key uses: unique (IP + apiKey) pairs in last 5 minutes\n const keyUsePairs = new Set<string>();\n for (const log of activeLogs) {\n if (log.apiKey) {\n keyUsePairs.add(`${log.ip}::${log.apiKey}`);\n }\n }\n\n return {\n totalRequests: this.count,\n requestsPerMinute,\n topEndpoints,\n errorRate: Math.round(errorRate * 100) / 100,\n avgResponseTime: Math.round(avgResponseTime * 100) / 100,\n activeClients: uniqueIps.size,\n activeKeyUses: keyUsePairs.size,\n rateLimitHits,\n };\n }\n\n private getOrderedLogs(): RequestLog[] {\n if (this.count < MAX_LOG_SIZE) {\n return [...this.logs].reverse();\n }\n const tail = this.logs.slice(0, this.head);\n const headPart = this.logs.slice(this.head);\n return [...headPart, ...tail].reverse();\n }\n}\n","import type { RateLimitConfig, IpRules, ApiKeysConfig } from '../types';\n\nexport const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig = {\n tiers: {\n free: { algorithm: 'tokenBucket', maxRequests: 100, windowMs: 60000, refillRate: 10 },\n pro: { algorithm: 'slidingWindow', maxRequests: 1000, windowMs: 60000 },\n unlimited: { algorithm: 'none' },\n },\n defaultTier: 'free',\n globalLimit: { maxRequests: 10000, windowMs: 60000 },\n};\n\nexport const DEFAULT_IP_RULES: IpRules = {\n allowlist: [],\n blocklist: [],\n mode: 'blocklist',\n};\n\nexport const DEFAULT_API_KEYS: ApiKeysConfig = {\n keys: [],\n};\n","import { Request, Response, NextFunction } from 'express';\nimport type { ApiKeysConfig } from '../../types';\n\nexport function createApiKeyAuth(getKeys: () => ApiKeysConfig) {\n return function apiKeyAuth(req: Request, res: Response, next: NextFunction): void {\n const apiKey = req.header('X-API-Key') || req.query.apiKey as string;\n\n if (!apiKey) {\n (req as any).clientId = req.ip || 'unknown';\n (req as any).tier = 'free';\n next();\n return;\n }\n\n const config = getKeys();\n const keyEntry = config.keys.find(k => k.key === apiKey && k.active);\n\n if (!keyEntry) {\n res.status(401).json({ error: 'Invalid or revoked API key' });\n return;\n }\n\n (req as any).clientId = keyEntry.id;\n (req as any).tier = keyEntry.tier;\n (req as any).apiKeyValue = keyEntry.key;\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport type { IpRules } from '../../types';\n\nexport function createIpFilter(getRules: () => IpRules) {\n return function ipFilter(req: Request, res: Response, next: NextFunction): void {\n const rules = getRules();\n const clientIp = req.ip || req.socket.remoteAddress || 'unknown';\n\n if (rules.mode === 'allowlist' && rules.allowlist.length > 0) {\n if (!rules.allowlist.includes(clientIp)) {\n res.status(403).json({ error: 'IP not in allowlist' });\n return;\n }\n }\n\n if (rules.mode === 'blocklist' && rules.blocklist.length > 0) {\n if (rules.blocklist.includes(clientIp)) {\n res.status(403).json({ error: 'IP is blocked' });\n return;\n }\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\n\nexport function createRateLimiter(service: RateLimiterService) {\n return function rateLimiter(req: Request, res: Response, next: NextFunction): void {\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const tier = (req as any).tier || 'free';\n\n const result = service.checkLimit(ip, tier);\n\n if (result.limit > 0) {\n res.setHeader('X-RateLimit-Limit', result.limit);\n res.setHeader('X-RateLimit-Remaining', Math.max(0, result.remaining));\n res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetMs / 1000));\n }\n\n if (!result.allowed) {\n res.status(429).json({\n error: 'Rate limit exceeded',\n retryAfter: Math.ceil(result.resetMs / 1000),\n });\n return;\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { AnalyticsService } from '../services/AnalyticsService';\n\nexport function createRequestLogger(analytics: AnalyticsService) {\n return function requestLogger(req: Request, res: Response, next: NextFunction): void {\n const start = Date.now();\n\n res.on('finish', () => {\n const responseTime = Date.now() - start;\n analytics.addLog({\n timestamp: Date.now(),\n method: req.method,\n path: req.originalUrl,\n statusCode: res.statusCode,\n responseTime,\n clientId: (req as any).clientId || req.ip || 'unknown',\n ip: req.ip || req.socket.remoteAddress || 'unknown',\n apiKey: (req as any).apiKeyValue || undefined,\n });\n });\n\n next();\n };\n}\n","import { Router, Request, Response } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport type { ApiKeysConfig, GatewayMiddlewareConfig } from '../../types';\nimport crypto from 'crypto';\n\nexport interface GatewayRoutesOptions {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayRoutes(options: GatewayRoutesOptions): Router {\n const { rateLimiterService, analyticsService, config } = options;\n const router = Router();\n\n // GET /analytics - Returns current analytics snapshot\n router.get('/analytics', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json(analytics);\n });\n\n // GET /analytics/live - SSE stream pushing analytics every 5 seconds\n router.get('/analytics/live', (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.flushHeaders();\n\n const send = (): void => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.write(`data: ${JSON.stringify(analytics)}\\n\\n`);\n };\n\n send();\n const interval = setInterval(send, 5000);\n\n _req.on('close', () => {\n clearInterval(interval);\n });\n });\n\n // GET /config - Returns current gateway config\n router.get('/config', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json({\n rateLimits: config.rateLimits,\n ipRules: config.ipRules,\n activeKeys: config.apiKeys.keys.filter(k => k.active).length,\n activeKeyUses: analytics.activeKeyUses,\n });\n });\n\n // POST /keys - Create a new API key\n router.post('/keys', (req: Request, res: Response) => {\n const { name, tier } = req.body;\n\n if (!name) {\n res.status(400).json({ error: 'Name is required' });\n return;\n }\n\n const newKey = {\n id: `key_${String(config.apiKeys.keys.length + 1).padStart(3, '0')}`,\n key: `gw_live_${crypto.randomBytes(16).toString('hex')}`,\n name,\n tier: tier || 'free',\n createdAt: new Date().toISOString(),\n active: true,\n };\n\n config.apiKeys.keys.push(newKey);\n res.status(201).json(newKey);\n });\n\n // DELETE /keys/:keyId - Revoke an API key\n router.delete('/keys/:keyId', (req: Request, res: Response) => {\n const { keyId } = req.params;\n const key = config.apiKeys.keys.find(k => k.id === keyId);\n\n if (!key) {\n res.status(404).json({ error: 'API key not found' });\n return;\n }\n\n key.active = false;\n res.json({ message: 'API key revoked', id: keyId });\n });\n\n // GET /logs - Returns recent request logs (paginated)\n router.get('/logs', (req: Request, res: Response) => {\n const limit = parseInt(req.query.limit as string) || 20;\n const offset = parseInt(req.query.offset as string) || 0;\n const logs = analyticsService.getRecentLogs(limit, offset);\n res.json({ logs, limit, offset });\n });\n\n return router;\n}\n","import { useState, useEffect, useRef } from 'react';\nimport { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';\nimport type { GatewayAnalytics, GatewayConfig, RequestLog, ApiKey } from '../types';\n\nexport interface GatewayDashboardProps {\n apiBaseUrl: string;\n}\n\ninterface LogsResponse {\n logs: RequestLog[];\n limit: number;\n offset: number;\n}\n\ninterface CreatedKey extends ApiKey {\n justCreated?: boolean;\n}\n\nfunction useGatewayApi<T>(apiBaseUrl: string, path: string): { data: T | null } {\n const [data, setData] = useState<T | null>(null);\n useEffect(() => {\n fetch(`${apiBaseUrl}${path}`)\n .then(r => r.json())\n .then(setData)\n .catch(() => {});\n }, [apiBaseUrl, path]);\n return { data };\n}\n\nexport function GatewayDashboard({ apiBaseUrl }: GatewayDashboardProps) {\n const [analytics, setAnalytics] = useState<GatewayAnalytics | null>(null);\n const [rpmHistory, setRpmHistory] = useState<{ time: string; rpm: number }[]>([]);\n const { data: config } = useGatewayApi<GatewayConfig>(apiBaseUrl, '/gateway/config');\n const { data: logsData } = useGatewayApi<LogsResponse>(apiBaseUrl, '/gateway/logs?limit=20');\n const eventSourceRef = useRef<EventSource | null>(null);\n const [keyName, setKeyName] = useState('');\n const [keyTier, setKeyTier] = useState('free');\n const [createdKeys, setCreatedKeys] = useState<CreatedKey[]>([]);\n const [keyError, setKeyError] = useState('');\n const [keyLoading, setKeyLoading] = useState(false);\n const [copiedKeyId, setCopiedKeyId] = useState<string | null>(null);\n\n const handleCreateKey = async () => {\n if (!keyName.trim()) {\n setKeyError('Name is required');\n return;\n }\n setKeyError('');\n setKeyLoading(true);\n try {\n const res = await fetch(`${apiBaseUrl}/gateway/keys`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name: keyName.trim(), tier: keyTier }),\n });\n if (!res.ok) {\n const err = await res.json();\n setKeyError(err.error || 'Failed to create key');\n return;\n }\n const newKey: ApiKey = await res.json();\n setCreatedKeys(prev => [{ ...newKey, justCreated: true }, ...prev]);\n setKeyName('');\n } catch {\n setKeyError('Network error');\n } finally {\n setKeyLoading(false);\n }\n };\n\n const handleRevokeKey = async (keyId: string) => {\n try {\n const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: 'DELETE' });\n if (res.ok) {\n setCreatedKeys(prev => prev.map(k => k.id === keyId ? { ...k, active: false } : k));\n }\n } catch {}\n };\n\n const handleCopyKey = (key: string, keyId: string) => {\n navigator.clipboard.writeText(key).then(() => {\n setCopiedKeyId(keyId);\n setTimeout(() => setCopiedKeyId(null), 2000);\n });\n };\n\n useEffect(() => {\n const es = new EventSource(`${apiBaseUrl}/gateway/analytics/live`);\n eventSourceRef.current = es;\n\n es.onmessage = (event) => {\n const data: GatewayAnalytics = JSON.parse(event.data);\n setAnalytics(data);\n setRpmHistory(prev => {\n const next = [\n ...prev,\n { time: new Date().toLocaleTimeString(), rpm: data.requestsPerMinute },\n ];\n return next.slice(-20);\n });\n };\n\n return () => {\n es.close();\n };\n }, [apiBaseUrl]);\n\n const getMethodClass = (method: string) => {\n switch (method) {\n case 'GET': return 'gw-method-get';\n case 'POST': return 'gw-method-post';\n case 'DELETE': return 'gw-method-delete';\n default: return 'gw-method-get';\n }\n };\n\n const getStatusClass = (code: number) => {\n if (code === 429) return 'gw-status-rate-limit';\n if (code >= 400) return 'gw-status-error';\n return 'gw-status-ok';\n };\n\n return (\n <div className=\"gw-dashboard\">\n <div className=\"gw-header\">\n <h1>API Gateway Dashboard</h1>\n <p>Real-time monitoring for the API gateway and rate limiter</p>\n <div className=\"gw-status-badge\">\n <span className=\"gw-status-dot\" />\n Live\n </div>\n </div>\n\n {/* Stats Grid */}\n <div className=\"gw-stats-grid\">\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Total Requests</div>\n <div className=\"gw-stat-value\">{analytics?.totalRequests ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Requests / Min</div>\n <div className=\"gw-stat-value gw-accent\">\n {analytics?.requestsPerMinute ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Error Rate</div>\n <div className={`gw-stat-value ${analytics && analytics.errorRate > 5 ? 'gw-danger' : ''}`}>\n {analytics?.errorRate ?? 0}%\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Avg Response Time</div>\n <div className=\"gw-stat-value\">{analytics?.avgResponseTime ?? 0}ms</div>\n </div>\n </div>\n\n {/* Second Row Stats */}\n <div className=\"gw-stats-grid\" style={{ marginBottom: 32 }}>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Hits</div>\n <div className=\"gw-stat-value gw-warning\">\n {analytics?.rateLimitHits ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active IPs</div>\n <div className=\"gw-stat-value\">{analytics?.activeClients ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active Key Sessions</div>\n <div className=\"gw-stat-value\">{analytics?.activeKeyUses ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Tiers</div>\n <div className=\"gw-stat-value\">\n {config ? Object.keys(config.rateLimits.tiers).length : 0}\n </div>\n </div>\n </div>\n\n {/* Charts Row */}\n <div className=\"gw-charts-row\">\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Requests Per Minute</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <LineChart data={rpmHistory}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis dataKey=\"time\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n labelStyle={{ color: 'var(--gw-text-muted, #888)' }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rpm\"\n stroke=\"var(--gw-accent, #64ffda)\"\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4, fill: 'var(--gw-accent, #64ffda)' }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Top Endpoints</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <BarChart\n data={analytics?.topEndpoints ?? []}\n layout=\"vertical\"\n >\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis type=\"number\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis\n dataKey=\"path\"\n type=\"category\"\n tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 10 }}\n width={120}\n />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n />\n <Bar dataKey=\"count\" fill=\"var(--gw-accent, #64ffda)\" radius={[0, 4, 4, 0]} />\n </BarChart>\n </ResponsiveContainer>\n </div>\n </div>\n\n {/* Recent Logs */}\n <div className=\"gw-logs-section\">\n <div className=\"gw-logs-title\">Recent Requests</div>\n <table className=\"gw-logs-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Method</th>\n <th>Path</th>\n <th>Status</th>\n <th>Duration</th>\n <th>IP</th>\n </tr>\n </thead>\n <tbody>\n {(logsData?.logs ?? []).map((log, i) => (\n <tr key={i}>\n <td>{new Date(log.timestamp).toLocaleTimeString()}</td>\n <td>\n <span className={`gw-method-badge ${getMethodClass(log.method)}`}>\n {log.method}\n </span>\n </td>\n <td>{log.path}</td>\n <td className={getStatusClass(log.statusCode)}>{log.statusCode}</td>\n <td>{log.responseTime}ms</td>\n <td>{log.ip}</td>\n </tr>\n ))}\n {(!logsData?.logs || logsData.logs.length === 0) && (\n <tr>\n <td colSpan={6} style={{ textAlign: 'center', color: 'var(--gw-text-muted, #666)' }}>\n No requests logged yet. Make some API calls to see data here.\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Config Section */}\n {config && (\n <div className=\"gw-config-section\">\n <div className=\"gw-config-card\">\n <h3>Rate Limit Tiers</h3>\n {Object.entries(config.rateLimits.tiers).map(([name, tier]) => (\n <div key={name} className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">{name}</span>\n <span className=\"gw-tier-detail\">\n {tier.algorithm === 'none'\n ? 'unlimited'\n : `${tier.maxRequests} req / ${(tier.windowMs || 60000) / 1000}s`}\n </span>\n </div>\n ))}\n </div>\n\n <div className=\"gw-config-card\">\n <h3>IP Rules</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Mode</span>\n <span className=\"gw-tier-detail\">{config.ipRules.mode}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Allowlist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.allowlist.length === 0 ? 'empty' : config.ipRules.allowlist.length + ' IPs'}\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Blocklist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.blocklist.length === 0 ? 'empty' : config.ipRules.blocklist.length + ' IPs'}\n </span>\n </div>\n </div>\n\n <div className=\"gw-config-card\">\n <h3>Global Limit</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Max Requests</span>\n <span className=\"gw-tier-detail\">\n {config.rateLimits.globalLimit.maxRequests} / {config.rateLimits.globalLimit.windowMs / 1000}s\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Default Tier</span>\n <span className=\"gw-tier-detail\">{config.rateLimits.defaultTier}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Keys</span>\n <span className=\"gw-tier-detail\">{config.activeKeys}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Key Sessions</span>\n <span className=\"gw-tier-detail\">{config.activeKeyUses}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* API Key Management */}\n <div className=\"gw-keys-section\">\n <div className=\"gw-keys-header\">\n <h2>API Keys</h2>\n <p>Create keys to authenticate API requests. Each key is tied to a rate limit tier.</p>\n </div>\n\n <div className=\"gw-keys-create\">\n <div className=\"gw-keys-form\">\n <input\n type=\"text\"\n className=\"gw-keys-input\"\n placeholder=\"Key name (e.g. My App)\"\n value={keyName}\n onChange={e => setKeyName(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleCreateKey()}\n />\n <select\n className=\"gw-keys-select\"\n value={keyTier}\n onChange={e => setKeyTier(e.target.value)}\n >\n {config && Object.keys(config.rateLimits.tiers).map(tier => (\n <option key={tier} value={tier}>{tier}</option>\n ))}\n </select>\n <button\n className=\"gw-keys-btn\"\n onClick={handleCreateKey}\n disabled={keyLoading}\n >\n {keyLoading ? 'Creating...' : 'Create Key'}\n </button>\n </div>\n {keyError && <div className=\"gw-keys-error\">{keyError}</div>}\n </div>\n\n {createdKeys.length > 0 && (\n <div className=\"gw-keys-list\">\n {createdKeys.map(k => (\n <div key={k.id} className={`gw-key-card ${!k.active ? 'gw-key-revoked' : ''}`}>\n <div className=\"gw-key-top\">\n <span className=\"gw-key-name\">{k.name}</span>\n <div className=\"gw-key-badges\">\n <span className=\"gw-key-tier\">{k.tier}</span>\n <span className={`gw-key-status ${k.active ? 'gw-key-active' : 'gw-key-inactive'}`}>\n {k.active ? 'active' : 'revoked'}\n </span>\n </div>\n </div>\n <div className=\"gw-key-value\">\n <code>{k.key}</code>\n {k.active && (\n <button\n className=\"gw-key-copy\"\n onClick={() => handleCopyKey(k.key, k.id)}\n >\n {copiedKeyId === k.id ? 'Copied!' : 'Copy'}\n </button>\n )}\n </div>\n <div className=\"gw-key-bottom\">\n <span className=\"gw-key-id\">{k.id}</span>\n {k.active && (\n <button\n className=\"gw-key-revoke\"\n onClick={() => handleRevokeKey(k.id)}\n >\n Revoke\n </button>\n )}\n </div>\n {k.justCreated && (\n <div className=\"gw-key-usage\">\n <span className=\"gw-key-usage-label\">Usage:</span>\n <code>curl -H \"X-API-Key: {k.key}\" {apiBaseUrl}/health</code>\n </div>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["resetMs","import_express","crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/backend/middleware/gateway.ts","../src/backend/services/RateLimiterService.ts","../src/backend/services/AnalyticsService.ts","../src/config/defaults.ts","../src/backend/middleware/apiKeyAuth.ts","../src/backend/middleware/ipFilter.ts","../src/backend/middleware/rateLimiter.ts","../src/backend/middleware/requestLogger.ts","../src/backend/routes/gateway.ts","../src/frontend/GatewayDashboard.tsx"],"sourcesContent":["// Backend exports\nexport { createGatewayMiddleware } from './backend/middleware/gateway';\nexport type { GatewayInstances } from './backend/middleware/gateway';\nexport { createGatewayRoutes } from './backend/routes/gateway';\nexport type { GatewayRoutesOptions } from './backend/routes/gateway';\nexport { RateLimiterService } from './backend/services/RateLimiterService';\nexport { AnalyticsService } from './backend/services/AnalyticsService';\nexport { createApiKeyAuth } from './backend/middleware/apiKeyAuth';\nexport { createIpFilter } from './backend/middleware/ipFilter';\nexport { createRateLimiter } from './backend/middleware/rateLimiter';\nexport { createRequestLogger } from './backend/middleware/requestLogger';\n\n// Frontend exports\nexport { GatewayDashboard } from './frontend/GatewayDashboard';\nexport type { GatewayDashboardProps } from './frontend/GatewayDashboard';\n\n// Type exports\nexport type {\n ApiKey,\n ApiKeysConfig,\n TierConfig,\n RateLimitConfig,\n IpRules,\n BucketState,\n SlidingWindowState,\n FixedWindowState,\n RequestLog,\n GatewayAnalytics,\n GatewayConfig,\n GatewayMiddlewareConfig,\n} from './types';\n\n// Config exports\nexport {\n DEFAULT_RATE_LIMIT_CONFIG,\n DEFAULT_IP_RULES,\n DEFAULT_API_KEYS,\n} from './config/defaults';\n","import { Router } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport { DEFAULT_RATE_LIMIT_CONFIG, DEFAULT_IP_RULES, DEFAULT_API_KEYS } from '../../config/defaults';\nimport { createApiKeyAuth } from './apiKeyAuth';\nimport { createIpFilter } from './ipFilter';\nimport { createRateLimiter } from './rateLimiter';\nimport { createRequestLogger } from './requestLogger';\n\nexport interface GatewayInstances {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n middleware: Router;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances {\n const config: Required<GatewayMiddlewareConfig> = {\n rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,\n ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,\n apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,\n };\n\n const rateLimiterService = new RateLimiterService(config.rateLimits);\n const analyticsService = new AnalyticsService();\n\n const router = Router();\n router.use(createRequestLogger(analyticsService));\n router.use(createApiKeyAuth(() => config.apiKeys));\n router.use(createIpFilter(() => config.ipRules));\n router.use(createRateLimiter(rateLimiterService));\n\n return {\n rateLimiterService,\n analyticsService,\n middleware: router,\n config,\n };\n}\n","import type { BucketState, SlidingWindowState, FixedWindowState, TierConfig, RateLimitConfig } from '../../types';\n\nclass TokenBucket {\n private buckets = new Map<string, BucketState>();\n\n tryConsume(ip: string, maxTokens: number, refillRate: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let bucket = this.buckets.get(ip);\n\n if (!bucket) {\n bucket = { tokens: maxTokens, lastRefill: now };\n this.buckets.set(ip, bucket);\n }\n\n const elapsed = (now - bucket.lastRefill) / 1000;\n bucket.tokens = Math.min(maxTokens, bucket.tokens + elapsed * refillRate);\n bucket.lastRefill = now;\n\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n const resetMs = bucket.tokens <= 0 ? Math.ceil((1 / refillRate) * 1000) : 0;\n return { allowed: true, remaining: Math.floor(bucket.tokens), resetMs };\n }\n\n const resetMs = Math.ceil(((1 - bucket.tokens) / refillRate) * 1000);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass SlidingWindowLog {\n private windows = new Map<string, SlidingWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state) {\n state = { timestamps: [] };\n this.windows.set(ip, state);\n }\n\n state.timestamps = state.timestamps.filter(t => now - t < windowMs);\n\n if (state.timestamps.length < maxRequests) {\n state.timestamps.push(now);\n return {\n allowed: true,\n remaining: maxRequests - state.timestamps.length,\n resetMs: state.timestamps.length > 0 ? windowMs - (now - state.timestamps[0]) : windowMs,\n };\n }\n\n const oldestInWindow = state.timestamps[0];\n const resetMs = windowMs - (now - oldestInWindow);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass FixedWindowCounter {\n private windows = new Map<string, FixedWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state || now - state.windowStart >= windowMs) {\n state = { count: 0, windowStart: now };\n this.windows.set(ip, state);\n }\n\n const resetMs = windowMs - (now - state.windowStart);\n\n if (state.count < maxRequests) {\n state.count++;\n return { allowed: true, remaining: maxRequests - state.count, resetMs };\n }\n\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nexport class RateLimiterService {\n private tokenBucket = new TokenBucket();\n private slidingWindow = new SlidingWindowLog();\n private fixedWindow = new FixedWindowCounter();\n private globalWindow = new FixedWindowCounter();\n private config: RateLimitConfig;\n private _rateLimitHits = 0;\n\n constructor(config: RateLimitConfig) {\n this.config = config;\n }\n\n get rateLimitHits(): number {\n return this._rateLimitHits;\n }\n\n checkLimit(ip: string, tier: string): { allowed: boolean; remaining: number; resetMs: number; limit: number } {\n const globalResult = this.globalWindow.tryConsume(\n '__global__',\n this.config.globalLimit.maxRequests,\n this.config.globalLimit.windowMs\n );\n\n if (!globalResult.allowed) {\n this._rateLimitHits++;\n return { allowed: false, remaining: 0, resetMs: globalResult.resetMs, limit: this.config.globalLimit.maxRequests };\n }\n\n const tierConfig = this.config.tiers[tier] || this.config.tiers[this.config.defaultTier];\n\n if (!tierConfig || tierConfig.algorithm === 'none') {\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n let result: { allowed: boolean; remaining: number; resetMs: number };\n\n switch (tierConfig.algorithm) {\n case 'tokenBucket':\n result = this.tokenBucket.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.refillRate || 1\n );\n break;\n case 'slidingWindow':\n result = this.slidingWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n case 'fixedWindow':\n result = this.fixedWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n default:\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n if (!result.allowed) {\n this._rateLimitHits++;\n }\n\n return { ...result, limit: tierConfig.maxRequests! };\n }\n\n getConfig(): RateLimitConfig {\n return this.config;\n }\n}\n","import type { RequestLog, GatewayAnalytics } from '../../types';\n\nconst MAX_LOG_SIZE = 10000;\nconst ACTIVE_WINDOW_MS = 300000; // 5 minutes\n\nexport class AnalyticsService {\n private logs: RequestLog[] = [];\n private head = 0;\n private count = 0;\n\n addLog(log: RequestLog): void {\n if (this.count < MAX_LOG_SIZE) {\n this.logs.push(log);\n this.count++;\n } else {\n this.logs[this.head] = log;\n this.head = (this.head + 1) % MAX_LOG_SIZE;\n }\n }\n\n getRecentLogs(limit = 20, offset = 0): RequestLog[] {\n const ordered = this.getOrderedLogs();\n return ordered.slice(offset, offset + limit);\n }\n\n getAnalytics(rateLimitHits: number): GatewayAnalytics {\n const now = Date.now();\n const oneMinuteAgo = now - 60000;\n const activeWindowStart = now - ACTIVE_WINDOW_MS;\n const ordered = this.getOrderedLogs();\n\n const recentLogs = ordered.filter(l => l.timestamp > oneMinuteAgo);\n const requestsPerMinute = recentLogs.length;\n\n // Top endpoints\n const endpointCounts = new Map<string, number>();\n for (const log of ordered) {\n const current = endpointCounts.get(log.path) || 0;\n endpointCounts.set(log.path, current + 1);\n }\n const topEndpoints = Array.from(endpointCounts.entries())\n .map(([path, count]) => ({ path, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 5);\n\n // Error rate\n const errorCount = ordered.filter(l => l.statusCode >= 400).length;\n const errorRate = this.count > 0 ? (errorCount / this.count) * 100 : 0;\n\n // Average response time\n const totalResponseTime = ordered.reduce((sum, l) => sum + l.responseTime, 0);\n const avgResponseTime = this.count > 0 ? totalResponseTime / this.count : 0;\n\n // Active clients: unique IPs in last 5 minutes\n const activeLogs = ordered.filter(l => l.timestamp > activeWindowStart);\n const uniqueIps = new Set(activeLogs.map(l => l.ip));\n\n // Active key uses: unique (IP + apiKey) pairs in last 5 minutes\n const keyUsePairs = new Set<string>();\n for (const log of activeLogs) {\n if (log.apiKey) {\n keyUsePairs.add(`${log.ip}::${log.apiKey}`);\n }\n }\n\n return {\n totalRequests: this.count,\n requestsPerMinute,\n topEndpoints,\n errorRate: Math.round(errorRate * 100) / 100,\n avgResponseTime: Math.round(avgResponseTime * 100) / 100,\n activeClients: uniqueIps.size,\n activeKeyUses: keyUsePairs.size,\n rateLimitHits,\n };\n }\n\n private getOrderedLogs(): RequestLog[] {\n if (this.count < MAX_LOG_SIZE) {\n return [...this.logs].reverse();\n }\n const tail = this.logs.slice(0, this.head);\n const headPart = this.logs.slice(this.head);\n return [...headPart, ...tail].reverse();\n }\n}\n","import type { RateLimitConfig, IpRules, ApiKeysConfig } from '../types';\n\nexport const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig = {\n tiers: {\n free: { algorithm: 'tokenBucket', maxRequests: 100, windowMs: 60000, refillRate: 10 },\n pro: { algorithm: 'slidingWindow', maxRequests: 1000, windowMs: 60000 },\n unlimited: { algorithm: 'none' },\n },\n defaultTier: 'free',\n globalLimit: { maxRequests: 10000, windowMs: 60000 },\n};\n\nexport const DEFAULT_IP_RULES: IpRules = {\n allowlist: [],\n blocklist: [],\n mode: 'blocklist',\n};\n\nexport const DEFAULT_API_KEYS: ApiKeysConfig = {\n keys: [],\n};\n","import { Request, Response, NextFunction } from 'express';\nimport type { ApiKeysConfig } from '../../types';\n\nexport function createApiKeyAuth(getKeys: () => ApiKeysConfig) {\n return function apiKeyAuth(req: Request, res: Response, next: NextFunction): void {\n const apiKey = req.header('X-API-Key') || req.query.apiKey as string;\n\n if (!apiKey) {\n (req as any).clientId = req.ip || 'unknown';\n (req as any).tier = 'free';\n next();\n return;\n }\n\n const config = getKeys();\n const keyEntry = config.keys.find(k => k.key === apiKey && k.active);\n\n if (!keyEntry) {\n res.status(401).json({ error: 'Invalid or revoked API key' });\n return;\n }\n\n (req as any).clientId = keyEntry.id;\n (req as any).tier = keyEntry.tier;\n (req as any).apiKeyValue = keyEntry.key;\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport type { IpRules } from '../../types';\n\nexport function createIpFilter(getRules: () => IpRules) {\n return function ipFilter(req: Request, res: Response, next: NextFunction): void {\n const rules = getRules();\n const clientIp = req.ip || req.socket.remoteAddress || 'unknown';\n\n if (rules.mode === 'allowlist' && rules.allowlist.length > 0) {\n if (!rules.allowlist.includes(clientIp)) {\n res.status(403).json({ error: 'IP not in allowlist' });\n return;\n }\n }\n\n if (rules.mode === 'blocklist' && rules.blocklist.length > 0) {\n if (rules.blocklist.includes(clientIp)) {\n res.status(403).json({ error: 'IP is blocked' });\n return;\n }\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\n\nexport function createRateLimiter(service: RateLimiterService) {\n return function rateLimiter(req: Request, res: Response, next: NextFunction): void {\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const tier = (req as any).tier || 'free';\n\n const result = service.checkLimit(ip, tier);\n\n if (result.limit > 0) {\n res.setHeader('X-RateLimit-Limit', result.limit);\n res.setHeader('X-RateLimit-Remaining', Math.max(0, result.remaining));\n res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetMs / 1000));\n }\n\n if (!result.allowed) {\n res.status(429).json({\n error: 'Rate limit exceeded',\n retryAfter: Math.ceil(result.resetMs / 1000),\n });\n return;\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { AnalyticsService } from '../services/AnalyticsService';\n\nexport function createRequestLogger(analytics: AnalyticsService) {\n return function requestLogger(req: Request, res: Response, next: NextFunction): void {\n const start = Date.now();\n\n res.on('finish', () => {\n const responseTime = Date.now() - start;\n const apiKeyValue = (req as any).apiKeyValue || undefined;\n analytics.addLog({\n timestamp: Date.now(),\n method: req.method,\n path: req.originalUrl,\n statusCode: res.statusCode,\n responseTime,\n clientId: (req as any).clientId || req.ip || 'unknown',\n ip: req.ip || req.socket.remoteAddress || 'unknown',\n apiKey: apiKeyValue,\n authenticated: !!apiKeyValue,\n });\n });\n\n next();\n };\n}\n","import { Router, Request, Response } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport crypto from 'crypto';\n\nexport interface GatewayRoutesOptions {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayRoutes(options: GatewayRoutesOptions): Router {\n const { rateLimiterService, analyticsService, config } = options;\n const router = Router();\n\n // GET /analytics - Returns current analytics snapshot\n router.get('/analytics', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json(analytics);\n });\n\n // GET /analytics/live - SSE stream pushing analytics every 5 seconds\n router.get('/analytics/live', (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders();\n\n const send = (): void => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.write(`data: ${JSON.stringify(analytics)}\\n\\n`);\n };\n\n send();\n const interval = setInterval(send, 5000);\n\n _req.on('close', () => {\n clearInterval(interval);\n });\n });\n\n // GET /config - Returns current gateway config\n router.get('/config', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json({\n rateLimits: config.rateLimits,\n ipRules: config.ipRules,\n activeKeys: config.apiKeys.keys.filter(k => k.active).length,\n activeKeyUses: analytics.activeKeyUses,\n });\n });\n\n // POST /keys - Create a new API key\n router.post('/keys', (req: Request, res: Response) => {\n const { name, tier } = req.body;\n\n if (!name) {\n res.status(400).json({ error: 'Name is required' });\n return;\n }\n\n const newKey = {\n id: `key_${String(config.apiKeys.keys.length + 1).padStart(3, '0')}`,\n key: `gw_live_${crypto.randomBytes(16).toString('hex')}`,\n name,\n tier: tier || 'free',\n createdAt: new Date().toISOString(),\n active: true,\n };\n\n config.apiKeys.keys.push(newKey);\n res.status(201).json(newKey);\n });\n\n // DELETE /keys/:keyId - Revoke an API key\n router.delete('/keys/:keyId', (req: Request, res: Response) => {\n const { keyId } = req.params;\n const key = config.apiKeys.keys.find(k => k.id === keyId);\n\n if (!key) {\n res.status(404).json({ error: 'API key not found' });\n return;\n }\n\n key.active = false;\n res.json({ message: 'API key revoked', id: keyId });\n });\n\n // GET /logs - Returns recent request logs (paginated)\n router.get('/logs', (req: Request, res: Response) => {\n const limit = parseInt(req.query.limit as string) || 20;\n const offset = parseInt(req.query.offset as string) || 0;\n const logs = analyticsService.getRecentLogs(limit, offset);\n res.json({ logs, limit, offset });\n });\n\n return router;\n}\n","import { useState, useEffect } from 'react';\nimport { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';\nimport type { GatewayAnalytics, GatewayConfig, RequestLog, ApiKey } from '../types';\n\nexport interface GatewayDashboardProps {\n apiBaseUrl: string;\n apiKey?: string;\n}\n\ninterface LogsResponse {\n logs: RequestLog[];\n limit: number;\n offset: number;\n}\n\ninterface CreatedKey extends ApiKey {\n justCreated?: boolean;\n}\n\nfunction useGatewayApi<T>(apiBaseUrl: string, path: string, apiKey?: string): { data: T | null } {\n const [data, setData] = useState<T | null>(null);\n useEffect(() => {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n fetch(`${apiBaseUrl}${path}`, { headers })\n .then(r => r.json())\n .then(setData)\n .catch(() => {});\n }, [apiBaseUrl, path, apiKey]);\n return { data };\n}\n\nexport function GatewayDashboard({ apiBaseUrl, apiKey }: GatewayDashboardProps) {\n const [analytics, setAnalytics] = useState<GatewayAnalytics | null>(null);\n const [rpmHistory, setRpmHistory] = useState<{ time: string; rpm: number }[]>([]);\n const { data: config } = useGatewayApi<GatewayConfig>(apiBaseUrl, '/gateway/config', apiKey);\n const { data: logsData } = useGatewayApi<LogsResponse>(apiBaseUrl, '/gateway/logs?limit=20', apiKey);\n const [keyName, setKeyName] = useState('');\n const [keyTier, setKeyTier] = useState('free');\n const [createdKeys, setCreatedKeys] = useState<CreatedKey[]>([]);\n const [keyError, setKeyError] = useState('');\n const [keyLoading, setKeyLoading] = useState(false);\n const [copiedKeyId, setCopiedKeyId] = useState<string | null>(null);\n\n const handleCreateKey = async () => {\n if (!keyName.trim()) {\n setKeyError('Name is required');\n return;\n }\n setKeyError('');\n setKeyLoading(true);\n try {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ name: keyName.trim(), tier: keyTier }),\n });\n if (!res.ok) {\n const err = await res.json();\n setKeyError(err.error || 'Failed to create key');\n return;\n }\n const newKey: ApiKey = await res.json();\n setCreatedKeys(prev => [{ ...newKey, justCreated: true }, ...prev]);\n setKeyName('');\n } catch {\n setKeyError('Network error');\n } finally {\n setKeyLoading(false);\n }\n };\n\n const handleRevokeKey = async (keyId: string) => {\n try {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: 'DELETE', headers });\n if (res.ok) {\n setCreatedKeys(prev => prev.map(k => k.id === keyId ? { ...k, active: false } : k));\n }\n } catch {}\n };\n\n const handleCopyKey = (key: string, keyId: string) => {\n navigator.clipboard.writeText(key).then(() => {\n setCopiedKeyId(keyId);\n setTimeout(() => setCopiedKeyId(null), 2000);\n });\n };\n\n useEffect(() => {\n let cancelled = false;\n\n async function connectSSE() {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n\n try {\n const res = await fetch(`${apiBaseUrl}/gateway/analytics/live`, { headers });\n if (!res.ok || !res.body) return;\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (!cancelled) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const data: GatewayAnalytics = JSON.parse(line.slice(6));\n setAnalytics(data);\n setRpmHistory(prev => {\n const next = [\n ...prev,\n { time: new Date().toLocaleTimeString(), rpm: data.requestsPerMinute },\n ];\n return next.slice(-20);\n });\n }\n }\n\n reader.cancel();\n } catch {\n // connection closed or failed\n }\n }\n\n connectSSE();\n\n return () => {\n cancelled = true;\n };\n }, [apiBaseUrl, apiKey]);\n\n const getMethodClass = (method: string) => {\n switch (method) {\n case 'GET': return 'gw-method-get';\n case 'POST': return 'gw-method-post';\n case 'DELETE': return 'gw-method-delete';\n default: return 'gw-method-get';\n }\n };\n\n const getStatusClass = (code: number) => {\n if (code === 429) return 'gw-status-rate-limit';\n if (code >= 400) return 'gw-status-error';\n return 'gw-status-ok';\n };\n\n return (\n <div className=\"gw-dashboard\">\n <div className=\"gw-header\">\n <h1>API Gateway Dashboard</h1>\n <p>Real-time monitoring for the API gateway and rate limiter</p>\n <div className=\"gw-status-badge\">\n <span className=\"gw-status-dot\" />\n Live\n </div>\n </div>\n\n {/* Stats Grid */}\n <div className=\"gw-stats-grid\">\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Total Requests</div>\n <div className=\"gw-stat-value\">{analytics?.totalRequests ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Requests / Min</div>\n <div className=\"gw-stat-value gw-accent\">\n {analytics?.requestsPerMinute ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Error Rate</div>\n <div className={`gw-stat-value ${analytics && analytics.errorRate > 5 ? 'gw-danger' : ''}`}>\n {analytics?.errorRate ?? 0}%\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Avg Response Time</div>\n <div className=\"gw-stat-value\">{analytics?.avgResponseTime ?? 0}ms</div>\n </div>\n </div>\n\n {/* Second Row Stats */}\n <div className=\"gw-stats-grid\" style={{ marginBottom: 32 }}>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Hits</div>\n <div className=\"gw-stat-value gw-warning\">\n {analytics?.rateLimitHits ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active IPs</div>\n <div className=\"gw-stat-value\">{analytics?.activeClients ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active Key Sessions</div>\n <div className=\"gw-stat-value\">{analytics?.activeKeyUses ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Tiers</div>\n <div className=\"gw-stat-value\">\n {config ? Object.keys(config.rateLimits.tiers).length : 0}\n </div>\n </div>\n </div>\n\n {/* Charts Row */}\n <div className=\"gw-charts-row\">\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Requests Per Minute</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <LineChart data={rpmHistory}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis dataKey=\"time\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n labelStyle={{ color: 'var(--gw-text-muted, #888)' }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rpm\"\n stroke=\"var(--gw-accent, #64ffda)\"\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4, fill: 'var(--gw-accent, #64ffda)' }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Top Endpoints</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <BarChart\n data={analytics?.topEndpoints ?? []}\n layout=\"vertical\"\n >\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis type=\"number\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis\n dataKey=\"path\"\n type=\"category\"\n tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 10 }}\n width={120}\n />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n />\n <Bar dataKey=\"count\" fill=\"var(--gw-accent, #64ffda)\" radius={[0, 4, 4, 0]} />\n </BarChart>\n </ResponsiveContainer>\n </div>\n </div>\n\n {/* Recent Logs */}\n <div className=\"gw-logs-section\">\n <div className=\"gw-logs-title\">Recent Requests</div>\n <table className=\"gw-logs-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Method</th>\n <th>Path</th>\n <th>Status</th>\n <th>Duration</th>\n <th>IP</th>\n <th>Auth</th>\n </tr>\n </thead>\n <tbody>\n {(logsData?.logs ?? []).map((log, i) => (\n <tr key={i}>\n <td>{new Date(log.timestamp).toLocaleTimeString()}</td>\n <td>\n <span className={`gw-method-badge ${getMethodClass(log.method)}`}>\n {log.method}\n </span>\n </td>\n <td>{log.path}</td>\n <td className={getStatusClass(log.statusCode)}>{log.statusCode}</td>\n <td>{log.responseTime}ms</td>\n <td>{log.ip}</td>\n <td>\n <span className={`gw-auth-badge ${log.authenticated ? 'gw-auth-yes' : 'gw-auth-no'}`}>\n {log.authenticated ? 'key' : 'none'}\n </span>\n </td>\n </tr>\n ))}\n {(!logsData?.logs || logsData.logs.length === 0) && (\n <tr>\n <td colSpan={7} style={{ textAlign: 'center', color: 'var(--gw-text-muted, #666)' }}>\n No requests logged yet. Make some API calls to see data here.\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Config Section */}\n {config && (\n <div className=\"gw-config-section\">\n <div className=\"gw-config-card\">\n <h3>Rate Limit Tiers</h3>\n {Object.entries(config.rateLimits.tiers).map(([name, tier]) => (\n <div key={name} className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">{name}</span>\n <span className=\"gw-tier-detail\">\n {tier.algorithm === 'none'\n ? 'unlimited'\n : `${tier.maxRequests} req / ${(tier.windowMs || 60000) / 1000}s`}\n </span>\n </div>\n ))}\n </div>\n\n <div className=\"gw-config-card\">\n <h3>IP Rules</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Mode</span>\n <span className=\"gw-tier-detail\">{config.ipRules.mode}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Allowlist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.allowlist.length === 0 ? 'empty' : config.ipRules.allowlist.length + ' IPs'}\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Blocklist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.blocklist.length === 0 ? 'empty' : config.ipRules.blocklist.length + ' IPs'}\n </span>\n </div>\n </div>\n\n <div className=\"gw-config-card\">\n <h3>Global Limit</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Max Requests</span>\n <span className=\"gw-tier-detail\">\n {config.rateLimits.globalLimit.maxRequests} / {config.rateLimits.globalLimit.windowMs / 1000}s\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Default Tier</span>\n <span className=\"gw-tier-detail\">{config.rateLimits.defaultTier}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Keys</span>\n <span className=\"gw-tier-detail\">{config.activeKeys}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Key Sessions</span>\n <span className=\"gw-tier-detail\">{config.activeKeyUses}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* API Key Management */}\n <div className=\"gw-keys-section\">\n <div className=\"gw-keys-header\">\n <h2>API Keys</h2>\n <p>Create keys to authenticate API requests. Each key is tied to a rate limit tier.</p>\n </div>\n\n <div className=\"gw-keys-create\">\n <div className=\"gw-keys-form\">\n <input\n type=\"text\"\n className=\"gw-keys-input\"\n placeholder=\"Key name (e.g. My App)\"\n value={keyName}\n onChange={e => setKeyName(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleCreateKey()}\n />\n <select\n className=\"gw-keys-select\"\n value={keyTier}\n onChange={e => setKeyTier(e.target.value)}\n >\n {config && Object.keys(config.rateLimits.tiers).map(tier => (\n <option key={tier} value={tier}>{tier}</option>\n ))}\n </select>\n <button\n className=\"gw-keys-btn\"\n onClick={handleCreateKey}\n disabled={keyLoading}\n >\n {keyLoading ? 'Creating...' : 'Create Key'}\n </button>\n </div>\n {keyError && <div className=\"gw-keys-error\">{keyError}</div>}\n </div>\n\n {createdKeys.length > 0 && (\n <div className=\"gw-keys-list\">\n {createdKeys.map(k => (\n <div key={k.id} className={`gw-key-card ${!k.active ? 'gw-key-revoked' : ''}`}>\n <div className=\"gw-key-top\">\n <span className=\"gw-key-name\">{k.name}</span>\n <div className=\"gw-key-badges\">\n <span className=\"gw-key-tier\">{k.tier}</span>\n <span className={`gw-key-status ${k.active ? 'gw-key-active' : 'gw-key-inactive'}`}>\n {k.active ? 'active' : 'revoked'}\n </span>\n </div>\n </div>\n <div className=\"gw-key-value\">\n <code>{k.key}</code>\n {k.active && (\n <button\n className=\"gw-key-copy\"\n onClick={() => handleCopyKey(k.key, k.id)}\n >\n {copiedKeyId === k.id ? 'Copied!' : 'Copy'}\n </button>\n )}\n </div>\n <div className=\"gw-key-bottom\">\n <span className=\"gw-key-id\">{k.id}</span>\n {k.active && (\n <button\n className=\"gw-key-revoke\"\n onClick={() => handleRevokeKey(k.id)}\n >\n Revoke\n </button>\n )}\n </div>\n {k.justCreated && (\n <div className=\"gw-key-usage\">\n <span className=\"gw-key-usage-label\">Usage:</span>\n <code>curl -H \"X-API-Key: {k.key}\" {apiBaseUrl}/health</code>\n </div>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["resetMs","import_express","crypto"]}
|
package/dist/index.mjs
CHANGED
|
@@ -283,6 +283,7 @@ function createRequestLogger(analytics) {
|
|
|
283
283
|
const start = Date.now();
|
|
284
284
|
res.on("finish", () => {
|
|
285
285
|
const responseTime = Date.now() - start;
|
|
286
|
+
const apiKeyValue = req.apiKeyValue || void 0;
|
|
286
287
|
analytics.addLog({
|
|
287
288
|
timestamp: Date.now(),
|
|
288
289
|
method: req.method,
|
|
@@ -291,7 +292,8 @@ function createRequestLogger(analytics) {
|
|
|
291
292
|
responseTime,
|
|
292
293
|
clientId: req.clientId || req.ip || "unknown",
|
|
293
294
|
ip: req.ip || req.socket.remoteAddress || "unknown",
|
|
294
|
-
apiKey:
|
|
295
|
+
apiKey: apiKeyValue,
|
|
296
|
+
authenticated: !!apiKeyValue
|
|
295
297
|
});
|
|
296
298
|
});
|
|
297
299
|
next();
|
|
@@ -335,7 +337,6 @@ function createGatewayRoutes(options) {
|
|
|
335
337
|
res.setHeader("Cache-Control", "no-cache");
|
|
336
338
|
res.setHeader("Connection", "keep-alive");
|
|
337
339
|
res.setHeader("X-Accel-Buffering", "no");
|
|
338
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
339
340
|
res.flushHeaders();
|
|
340
341
|
const send = () => {
|
|
341
342
|
const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);
|
|
@@ -395,23 +396,24 @@ function createGatewayRoutes(options) {
|
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
// src/frontend/GatewayDashboard.tsx
|
|
398
|
-
import { useState, useEffect
|
|
399
|
+
import { useState, useEffect } from "react";
|
|
399
400
|
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
|
|
400
401
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
401
|
-
function useGatewayApi(apiBaseUrl, path) {
|
|
402
|
+
function useGatewayApi(apiBaseUrl, path, apiKey) {
|
|
402
403
|
const [data, setData] = useState(null);
|
|
403
404
|
useEffect(() => {
|
|
404
|
-
|
|
405
|
+
const headers = {};
|
|
406
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
407
|
+
fetch(`${apiBaseUrl}${path}`, { headers }).then((r) => r.json()).then(setData).catch(() => {
|
|
405
408
|
});
|
|
406
|
-
}, [apiBaseUrl, path]);
|
|
409
|
+
}, [apiBaseUrl, path, apiKey]);
|
|
407
410
|
return { data };
|
|
408
411
|
}
|
|
409
|
-
function GatewayDashboard({ apiBaseUrl }) {
|
|
412
|
+
function GatewayDashboard({ apiBaseUrl, apiKey }) {
|
|
410
413
|
const [analytics, setAnalytics] = useState(null);
|
|
411
414
|
const [rpmHistory, setRpmHistory] = useState([]);
|
|
412
|
-
const { data: config } = useGatewayApi(apiBaseUrl, "/gateway/config");
|
|
413
|
-
const { data: logsData } = useGatewayApi(apiBaseUrl, "/gateway/logs?limit=20");
|
|
414
|
-
const eventSourceRef = useRef(null);
|
|
415
|
+
const { data: config } = useGatewayApi(apiBaseUrl, "/gateway/config", apiKey);
|
|
416
|
+
const { data: logsData } = useGatewayApi(apiBaseUrl, "/gateway/logs?limit=20", apiKey);
|
|
415
417
|
const [keyName, setKeyName] = useState("");
|
|
416
418
|
const [keyTier, setKeyTier] = useState("free");
|
|
417
419
|
const [createdKeys, setCreatedKeys] = useState([]);
|
|
@@ -426,9 +428,11 @@ function GatewayDashboard({ apiBaseUrl }) {
|
|
|
426
428
|
setKeyError("");
|
|
427
429
|
setKeyLoading(true);
|
|
428
430
|
try {
|
|
431
|
+
const headers = { "Content-Type": "application/json" };
|
|
432
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
429
433
|
const res = await fetch(`${apiBaseUrl}/gateway/keys`, {
|
|
430
434
|
method: "POST",
|
|
431
|
-
headers
|
|
435
|
+
headers,
|
|
432
436
|
body: JSON.stringify({ name: keyName.trim(), tier: keyTier })
|
|
433
437
|
});
|
|
434
438
|
if (!res.ok) {
|
|
@@ -447,7 +451,9 @@ function GatewayDashboard({ apiBaseUrl }) {
|
|
|
447
451
|
};
|
|
448
452
|
const handleRevokeKey = async (keyId) => {
|
|
449
453
|
try {
|
|
450
|
-
const
|
|
454
|
+
const headers = {};
|
|
455
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
456
|
+
const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: "DELETE", headers });
|
|
451
457
|
if (res.ok) {
|
|
452
458
|
setCreatedKeys((prev) => prev.map((k) => k.id === keyId ? { ...k, active: false } : k));
|
|
453
459
|
}
|
|
@@ -461,23 +467,44 @@ function GatewayDashboard({ apiBaseUrl }) {
|
|
|
461
467
|
});
|
|
462
468
|
};
|
|
463
469
|
useEffect(() => {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
470
|
+
let cancelled = false;
|
|
471
|
+
async function connectSSE() {
|
|
472
|
+
const headers = {};
|
|
473
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
474
|
+
try {
|
|
475
|
+
const res = await fetch(`${apiBaseUrl}/gateway/analytics/live`, { headers });
|
|
476
|
+
if (!res.ok || !res.body) return;
|
|
477
|
+
const reader = res.body.getReader();
|
|
478
|
+
const decoder = new TextDecoder();
|
|
479
|
+
let buffer = "";
|
|
480
|
+
while (!cancelled) {
|
|
481
|
+
const { done, value } = await reader.read();
|
|
482
|
+
if (done) break;
|
|
483
|
+
buffer += decoder.decode(value, { stream: true });
|
|
484
|
+
const lines = buffer.split("\n");
|
|
485
|
+
buffer = lines.pop() || "";
|
|
486
|
+
for (const line of lines) {
|
|
487
|
+
if (!line.startsWith("data: ")) continue;
|
|
488
|
+
const data = JSON.parse(line.slice(6));
|
|
489
|
+
setAnalytics(data);
|
|
490
|
+
setRpmHistory((prev) => {
|
|
491
|
+
const next = [
|
|
492
|
+
...prev,
|
|
493
|
+
{ time: (/* @__PURE__ */ new Date()).toLocaleTimeString(), rpm: data.requestsPerMinute }
|
|
494
|
+
];
|
|
495
|
+
return next.slice(-20);
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
reader.cancel();
|
|
500
|
+
} catch {
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
connectSSE();
|
|
477
504
|
return () => {
|
|
478
|
-
|
|
505
|
+
cancelled = true;
|
|
479
506
|
};
|
|
480
|
-
}, [apiBaseUrl]);
|
|
507
|
+
}, [apiBaseUrl, apiKey]);
|
|
481
508
|
const getMethodClass = (method) => {
|
|
482
509
|
switch (method) {
|
|
483
510
|
case "GET":
|
|
@@ -613,7 +640,8 @@ function GatewayDashboard({ apiBaseUrl }) {
|
|
|
613
640
|
/* @__PURE__ */ jsx("th", { children: "Path" }),
|
|
614
641
|
/* @__PURE__ */ jsx("th", { children: "Status" }),
|
|
615
642
|
/* @__PURE__ */ jsx("th", { children: "Duration" }),
|
|
616
|
-
/* @__PURE__ */ jsx("th", { children: "IP" })
|
|
643
|
+
/* @__PURE__ */ jsx("th", { children: "IP" }),
|
|
644
|
+
/* @__PURE__ */ jsx("th", { children: "Auth" })
|
|
617
645
|
] }) }),
|
|
618
646
|
/* @__PURE__ */ jsxs("tbody", { children: [
|
|
619
647
|
(logsData?.logs ?? []).map((log, i) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
@@ -625,9 +653,10 @@ function GatewayDashboard({ apiBaseUrl }) {
|
|
|
625
653
|
log.responseTime,
|
|
626
654
|
"ms"
|
|
627
655
|
] }),
|
|
628
|
-
/* @__PURE__ */ jsx("td", { children: log.ip })
|
|
656
|
+
/* @__PURE__ */ jsx("td", { children: log.ip }),
|
|
657
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: `gw-auth-badge ${log.authenticated ? "gw-auth-yes" : "gw-auth-no"}`, children: log.authenticated ? "key" : "none" }) })
|
|
629
658
|
] }, i)),
|
|
630
|
-
(!logsData?.logs || logsData.logs.length === 0) && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan:
|
|
659
|
+
(!logsData?.logs || logsData.logs.length === 0) && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 7, style: { textAlign: "center", color: "var(--gw-text-muted, #666)" }, children: "No requests logged yet. Make some API calls to see data here." }) })
|
|
631
660
|
] })
|
|
632
661
|
] })
|
|
633
662
|
] }),
|