@gagandeep023/api-gateway 0.2.0 → 0.3.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.
@@ -2,19 +2,21 @@
2
2
  import { useState, useEffect, useRef } from "react";
3
3
  import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
- function useGatewayApi(apiBaseUrl, path) {
5
+ function useGatewayApi(apiBaseUrl, path, apiKey) {
6
6
  const [data, setData] = useState(null);
7
7
  useEffect(() => {
8
- fetch(`${apiBaseUrl}${path}`).then((r) => r.json()).then(setData).catch(() => {
8
+ const headers = {};
9
+ if (apiKey) headers["X-API-Key"] = apiKey;
10
+ fetch(`${apiBaseUrl}${path}`, { headers }).then((r) => r.json()).then(setData).catch(() => {
9
11
  });
10
- }, [apiBaseUrl, path]);
12
+ }, [apiBaseUrl, path, apiKey]);
11
13
  return { data };
12
14
  }
13
- function GatewayDashboard({ apiBaseUrl }) {
15
+ function GatewayDashboard({ apiBaseUrl, apiKey }) {
14
16
  const [analytics, setAnalytics] = useState(null);
15
17
  const [rpmHistory, setRpmHistory] = useState([]);
16
- const { data: config } = useGatewayApi(apiBaseUrl, "/gateway/config");
17
- const { data: logsData } = useGatewayApi(apiBaseUrl, "/gateway/logs?limit=20");
18
+ const { data: config } = useGatewayApi(apiBaseUrl, "/gateway/config", apiKey);
19
+ const { data: logsData } = useGatewayApi(apiBaseUrl, "/gateway/logs?limit=20", apiKey);
18
20
  const eventSourceRef = useRef(null);
19
21
  const [keyName, setKeyName] = useState("");
20
22
  const [keyTier, setKeyTier] = useState("free");
@@ -30,9 +32,11 @@ function GatewayDashboard({ apiBaseUrl }) {
30
32
  setKeyError("");
31
33
  setKeyLoading(true);
32
34
  try {
35
+ const headers = { "Content-Type": "application/json" };
36
+ if (apiKey) headers["X-API-Key"] = apiKey;
33
37
  const res = await fetch(`${apiBaseUrl}/gateway/keys`, {
34
38
  method: "POST",
35
- headers: { "Content-Type": "application/json" },
39
+ headers,
36
40
  body: JSON.stringify({ name: keyName.trim(), tier: keyTier })
37
41
  });
38
42
  if (!res.ok) {
@@ -51,7 +55,9 @@ function GatewayDashboard({ apiBaseUrl }) {
51
55
  };
52
56
  const handleRevokeKey = async (keyId) => {
53
57
  try {
54
- const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: "DELETE" });
58
+ const headers = {};
59
+ if (apiKey) headers["X-API-Key"] = apiKey;
60
+ const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: "DELETE", headers });
55
61
  if (res.ok) {
56
62
  setCreatedKeys((prev) => prev.map((k) => k.id === keyId ? { ...k, active: false } : k));
57
63
  }
@@ -217,7 +223,8 @@ function GatewayDashboard({ apiBaseUrl }) {
217
223
  /* @__PURE__ */ jsx("th", { children: "Path" }),
218
224
  /* @__PURE__ */ jsx("th", { children: "Status" }),
219
225
  /* @__PURE__ */ jsx("th", { children: "Duration" }),
220
- /* @__PURE__ */ jsx("th", { children: "IP" })
226
+ /* @__PURE__ */ jsx("th", { children: "IP" }),
227
+ /* @__PURE__ */ jsx("th", { children: "Auth" })
221
228
  ] }) }),
222
229
  /* @__PURE__ */ jsxs("tbody", { children: [
223
230
  (logsData?.logs ?? []).map((log, i) => /* @__PURE__ */ jsxs("tr", { children: [
@@ -229,9 +236,10 @@ function GatewayDashboard({ apiBaseUrl }) {
229
236
  log.responseTime,
230
237
  "ms"
231
238
  ] }),
232
- /* @__PURE__ */ jsx("td", { children: log.ip })
239
+ /* @__PURE__ */ jsx("td", { children: log.ip }),
240
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: `gw-auth-badge ${log.authenticated ? "gw-auth-yes" : "gw-auth-no"}`, children: log.authenticated ? "key" : "none" }) })
233
241
  ] }, i)),
234
- (!logsData?.logs || logsData.logs.length === 0) && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ 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." }) })
242
+ (!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." }) })
235
243
  ] })
236
244
  ] })
237
245
  ] }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/frontend/GatewayDashboard.tsx"],"sourcesContent":["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":[]}
1
+ {"version":3,"sources":["../../src/frontend/GatewayDashboard.tsx"],"sourcesContent":["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 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 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 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 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 <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":[]}
package/dist/index.js CHANGED
@@ -330,6 +330,7 @@ function createRequestLogger(analytics) {
330
330
  const start = Date.now();
331
331
  res.on("finish", () => {
332
332
  const responseTime = Date.now() - start;
333
+ const apiKeyValue = req.apiKeyValue || void 0;
333
334
  analytics.addLog({
334
335
  timestamp: Date.now(),
335
336
  method: req.method,
@@ -338,7 +339,8 @@ function createRequestLogger(analytics) {
338
339
  responseTime,
339
340
  clientId: req.clientId || req.ip || "unknown",
340
341
  ip: req.ip || req.socket.remoteAddress || "unknown",
341
- apiKey: req.apiKeyValue || void 0
342
+ apiKey: apiKeyValue,
343
+ authenticated: !!apiKeyValue
342
344
  });
343
345
  });
344
346
  next();
@@ -445,19 +447,21 @@ function createGatewayRoutes(options) {
445
447
  var import_react = require("react");
446
448
  var import_recharts = require("recharts");
447
449
  var import_jsx_runtime = require("react/jsx-runtime");
448
- function useGatewayApi(apiBaseUrl, path) {
450
+ function useGatewayApi(apiBaseUrl, path, apiKey) {
449
451
  const [data, setData] = (0, import_react.useState)(null);
450
452
  (0, import_react.useEffect)(() => {
451
- fetch(`${apiBaseUrl}${path}`).then((r) => r.json()).then(setData).catch(() => {
453
+ const headers = {};
454
+ if (apiKey) headers["X-API-Key"] = apiKey;
455
+ fetch(`${apiBaseUrl}${path}`, { headers }).then((r) => r.json()).then(setData).catch(() => {
452
456
  });
453
- }, [apiBaseUrl, path]);
457
+ }, [apiBaseUrl, path, apiKey]);
454
458
  return { data };
455
459
  }
456
- function GatewayDashboard({ apiBaseUrl }) {
460
+ function GatewayDashboard({ apiBaseUrl, apiKey }) {
457
461
  const [analytics, setAnalytics] = (0, import_react.useState)(null);
458
462
  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");
463
+ const { data: config } = useGatewayApi(apiBaseUrl, "/gateway/config", apiKey);
464
+ const { data: logsData } = useGatewayApi(apiBaseUrl, "/gateway/logs?limit=20", apiKey);
461
465
  const eventSourceRef = (0, import_react.useRef)(null);
462
466
  const [keyName, setKeyName] = (0, import_react.useState)("");
463
467
  const [keyTier, setKeyTier] = (0, import_react.useState)("free");
@@ -473,9 +477,11 @@ function GatewayDashboard({ apiBaseUrl }) {
473
477
  setKeyError("");
474
478
  setKeyLoading(true);
475
479
  try {
480
+ const headers = { "Content-Type": "application/json" };
481
+ if (apiKey) headers["X-API-Key"] = apiKey;
476
482
  const res = await fetch(`${apiBaseUrl}/gateway/keys`, {
477
483
  method: "POST",
478
- headers: { "Content-Type": "application/json" },
484
+ headers,
479
485
  body: JSON.stringify({ name: keyName.trim(), tier: keyTier })
480
486
  });
481
487
  if (!res.ok) {
@@ -494,7 +500,9 @@ function GatewayDashboard({ apiBaseUrl }) {
494
500
  };
495
501
  const handleRevokeKey = async (keyId) => {
496
502
  try {
497
- const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: "DELETE" });
503
+ const headers = {};
504
+ if (apiKey) headers["X-API-Key"] = apiKey;
505
+ const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: "DELETE", headers });
498
506
  if (res.ok) {
499
507
  setCreatedKeys((prev) => prev.map((k) => k.id === keyId ? { ...k, active: false } : k));
500
508
  }
@@ -660,7 +668,8 @@ function GatewayDashboard({ apiBaseUrl }) {
660
668
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Path" }),
661
669
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Status" }),
662
670
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Duration" }),
663
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "IP" })
671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "IP" }),
672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Auth" })
664
673
  ] }) }),
665
674
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tbody", { children: [
666
675
  (logsData?.logs ?? []).map((log, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
@@ -672,9 +681,10 @@ function GatewayDashboard({ apiBaseUrl }) {
672
681
  log.responseTime,
673
682
  "ms"
674
683
  ] }),
675
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: log.ip })
684
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: log.ip }),
685
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `gw-auth-badge ${log.authenticated ? "gw-auth-yes" : "gw-auth-no"}`, children: log.authenticated ? "key" : "none" }) })
676
686
  ] }, i)),
677
- (!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." }) })
687
+ (!logsData?.logs || logsData.logs.length === 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime.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." }) })
678
688
  ] })
679
689
  ] })
680
690
  ] }),
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAAuB;;;ACEvB,IAAM,cAAN,MAAkB;AAAA,EACR,UAAU,oBAAI,IAAyB;AAAA,EAE/C,WAAW,IAAY,WAAmB,YAA8E;AACtH,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,SAAS,KAAK,QAAQ,IAAI,EAAE;AAEhC,QAAI,CAAC,QAAQ;AACX,eAAS,EAAE,QAAQ,WAAW,YAAY,IAAI;AAC9C,WAAK,QAAQ,IAAI,IAAI,MAAM;AAAA,IAC7B;AAEA,UAAM,WAAW,MAAM,OAAO,cAAc;AAC5C,WAAO,SAAS,KAAK,IAAI,WAAW,OAAO,SAAS,UAAU,UAAU;AACxE,WAAO,aAAa;AAEpB,QAAI,OAAO,UAAU,GAAG;AACtB,aAAO,UAAU;AACjB,YAAMA,WAAU,OAAO,UAAU,IAAI,KAAK,KAAM,IAAI,aAAc,GAAI,IAAI;AAC1E,aAAO,EAAE,SAAS,MAAM,WAAW,KAAK,MAAM,OAAO,MAAM,GAAG,SAAAA,SAAQ;AAAA,IACxE;AAEA,UAAM,UAAU,KAAK,MAAO,IAAI,OAAO,UAAU,aAAc,GAAI;AACnE,WAAO,EAAE,SAAS,OAAO,WAAW,GAAG,QAAQ;AAAA,EACjD;AACF;AAEA,IAAM,mBAAN,MAAuB;AAAA,EACb,UAAU,oBAAI,IAAgC;AAAA,EAEtD,WAAW,IAAY,aAAqB,UAA4E;AACtH,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,QAAQ,KAAK,QAAQ,IAAI,EAAE;AAE/B,QAAI,CAAC,OAAO;AACV,cAAQ,EAAE,YAAY,CAAC,EAAE;AACzB,WAAK,QAAQ,IAAI,IAAI,KAAK;AAAA,IAC5B;AAEA,UAAM,aAAa,MAAM,WAAW,OAAO,OAAK,MAAM,IAAI,QAAQ;AAElE,QAAI,MAAM,WAAW,SAAS,aAAa;AACzC,YAAM,WAAW,KAAK,GAAG;AACzB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,WAAW,cAAc,MAAM,WAAW;AAAA,QAC1C,SAAS,MAAM,WAAW,SAAS,IAAI,YAAY,MAAM,MAAM,WAAW,CAAC,KAAK;AAAA,MAClF;AAAA,IACF;AAEA,UAAM,iBAAiB,MAAM,WAAW,CAAC;AACzC,UAAM,UAAU,YAAY,MAAM;AAClC,WAAO,EAAE,SAAS,OAAO,WAAW,GAAG,QAAQ;AAAA,EACjD;AACF;AAEA,IAAM,qBAAN,MAAyB;AAAA,EACf,UAAU,oBAAI,IAA8B;AAAA,EAEpD,WAAW,IAAY,aAAqB,UAA4E;AACtH,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,QAAQ,KAAK,QAAQ,IAAI,EAAE;AAE/B,QAAI,CAAC,SAAS,MAAM,MAAM,eAAe,UAAU;AACjD,cAAQ,EAAE,OAAO,GAAG,aAAa,IAAI;AACrC,WAAK,QAAQ,IAAI,IAAI,KAAK;AAAA,IAC5B;AAEA,UAAM,UAAU,YAAY,MAAM,MAAM;AAExC,QAAI,MAAM,QAAQ,aAAa;AAC7B,YAAM;AACN,aAAO,EAAE,SAAS,MAAM,WAAW,cAAc,MAAM,OAAO,QAAQ;AAAA,IACxE;AAEA,WAAO,EAAE,SAAS,OAAO,WAAW,GAAG,QAAQ;AAAA,EACjD;AACF;AAEO,IAAM,qBAAN,MAAyB;AAAA,EACtB,cAAc,IAAI,YAAY;AAAA,EAC9B,gBAAgB,IAAI,iBAAiB;AAAA,EACrC,cAAc,IAAI,mBAAmB;AAAA,EACrC,eAAe,IAAI,mBAAmB;AAAA,EACtC;AAAA,EACA,iBAAiB;AAAA,EAEzB,YAAY,QAAyB;AACnC,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAW,IAAY,MAAuF;AAC5G,UAAM,eAAe,KAAK,aAAa;AAAA,MACrC;AAAA,MACA,KAAK,OAAO,YAAY;AAAA,MACxB,KAAK,OAAO,YAAY;AAAA,IAC1B;AAEA,QAAI,CAAC,aAAa,SAAS;AACzB,WAAK;AACL,aAAO,EAAE,SAAS,OAAO,WAAW,GAAG,SAAS,aAAa,SAAS,OAAO,KAAK,OAAO,YAAY,YAAY;AAAA,IACnH;AAEA,UAAM,aAAa,KAAK,OAAO,MAAM,IAAI,KAAK,KAAK,OAAO,MAAM,KAAK,OAAO,WAAW;AAEvF,QAAI,CAAC,cAAc,WAAW,cAAc,QAAQ;AAClD,aAAO,EAAE,SAAS,MAAM,WAAW,IAAI,SAAS,GAAG,OAAO,GAAG;AAAA,IAC/D;AAEA,QAAI;AAEJ,YAAQ,WAAW,WAAW;AAAA,MAC5B,KAAK;AACH,iBAAS,KAAK,YAAY;AAAA,UACxB;AAAA,UACA,WAAW;AAAA,UACX,WAAW,cAAc;AAAA,QAC3B;AACA;AAAA,MACF,KAAK;AACH,iBAAS,KAAK,cAAc;AAAA,UAC1B;AAAA,UACA,WAAW;AAAA,UACX,WAAW;AAAA,QACb;AACA;AAAA,MACF,KAAK;AACH,iBAAS,KAAK,YAAY;AAAA,UACxB;AAAA,UACA,WAAW;AAAA,UACX,WAAW;AAAA,QACb;AACA;AAAA,MACF;AACE,eAAO,EAAE,SAAS,MAAM,WAAW,IAAI,SAAS,GAAG,OAAO,GAAG;AAAA,IACjE;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,WAAK;AAAA,IACP;AAEA,WAAO,EAAE,GAAG,QAAQ,OAAO,WAAW,YAAa;AAAA,EACrD;AAAA,EAEA,YAA6B;AAC3B,WAAO,KAAK;AAAA,EACd;AACF;;;ACvJA,IAAM,eAAe;AACrB,IAAM,mBAAmB;AAElB,IAAM,mBAAN,MAAuB;AAAA,EACpB,OAAqB,CAAC;AAAA,EACtB,OAAO;AAAA,EACP,QAAQ;AAAA,EAEhB,OAAO,KAAuB;AAC5B,QAAI,KAAK,QAAQ,cAAc;AAC7B,WAAK,KAAK,KAAK,GAAG;AAClB,WAAK;AAAA,IACP,OAAO;AACL,WAAK,KAAK,KAAK,IAAI,IAAI;AACvB,WAAK,QAAQ,KAAK,OAAO,KAAK;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,cAAc,QAAQ,IAAI,SAAS,GAAiB;AAClD,UAAM,UAAU,KAAK,eAAe;AACpC,WAAO,QAAQ,MAAM,QAAQ,SAAS,KAAK;AAAA,EAC7C;AAAA,EAEA,aAAa,eAAyC;AACpD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,eAAe,MAAM;AAC3B,UAAM,oBAAoB,MAAM;AAChC,UAAM,UAAU,KAAK,eAAe;AAEpC,UAAM,aAAa,QAAQ,OAAO,OAAK,EAAE,YAAY,YAAY;AACjE,UAAM,oBAAoB,WAAW;AAGrC,UAAM,iBAAiB,oBAAI,IAAoB;AAC/C,eAAW,OAAO,SAAS;AACzB,YAAM,UAAU,eAAe,IAAI,IAAI,IAAI,KAAK;AAChD,qBAAe,IAAI,IAAI,MAAM,UAAU,CAAC;AAAA,IAC1C;AACA,UAAM,eAAe,MAAM,KAAK,eAAe,QAAQ,CAAC,EACrD,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,EAAE,EACxC,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,CAAC;AAGb,UAAM,aAAa,QAAQ,OAAO,OAAK,EAAE,cAAc,GAAG,EAAE;AAC5D,UAAM,YAAY,KAAK,QAAQ,IAAK,aAAa,KAAK,QAAS,MAAM;AAGrE,UAAM,oBAAoB,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,cAAc,CAAC;AAC5E,UAAM,kBAAkB,KAAK,QAAQ,IAAI,oBAAoB,KAAK,QAAQ;AAG1E,UAAM,aAAa,QAAQ,OAAO,OAAK,EAAE,YAAY,iBAAiB;AACtE,UAAM,YAAY,IAAI,IAAI,WAAW,IAAI,OAAK,EAAE,EAAE,CAAC;AAGnD,UAAM,cAAc,oBAAI,IAAY;AACpC,eAAW,OAAO,YAAY;AAC5B,UAAI,IAAI,QAAQ;AACd,oBAAY,IAAI,GAAG,IAAI,EAAE,KAAK,IAAI,MAAM,EAAE;AAAA,MAC5C;AAAA,IACF;AAEA,WAAO;AAAA,MACL,eAAe,KAAK;AAAA,MACpB;AAAA,MACA;AAAA,MACA,WAAW,KAAK,MAAM,YAAY,GAAG,IAAI;AAAA,MACzC,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,MACrD,eAAe,UAAU;AAAA,MACzB,eAAe,YAAY;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,iBAA+B;AACrC,QAAI,KAAK,QAAQ,cAAc;AAC7B,aAAO,CAAC,GAAG,KAAK,IAAI,EAAE,QAAQ;AAAA,IAChC;AACA,UAAM,OAAO,KAAK,KAAK,MAAM,GAAG,KAAK,IAAI;AACzC,UAAM,WAAW,KAAK,KAAK,MAAM,KAAK,IAAI;AAC1C,WAAO,CAAC,GAAG,UAAU,GAAG,IAAI,EAAE,QAAQ;AAAA,EACxC;AACF;;;ACnFO,IAAM,4BAA6C;AAAA,EACxD,OAAO;AAAA,IACL,MAAM,EAAE,WAAW,eAAe,aAAa,KAAK,UAAU,KAAO,YAAY,GAAG;AAAA,IACpF,KAAK,EAAE,WAAW,iBAAiB,aAAa,KAAM,UAAU,IAAM;AAAA,IACtE,WAAW,EAAE,WAAW,OAAO;AAAA,EACjC;AAAA,EACA,aAAa;AAAA,EACb,aAAa,EAAE,aAAa,KAAO,UAAU,IAAM;AACrD;AAEO,IAAM,mBAA4B;AAAA,EACvC,WAAW,CAAC;AAAA,EACZ,WAAW,CAAC;AAAA,EACZ,MAAM;AACR;AAEO,IAAM,mBAAkC;AAAA,EAC7C,MAAM,CAAC;AACT;;;ACjBO,SAAS,iBAAiB,SAA8B;AAC7D,SAAO,SAAS,WAAW,KAAc,KAAe,MAA0B;AAChF,UAAM,SAAS,IAAI,OAAO,WAAW,KAAK,IAAI,MAAM;AAEpD,QAAI,CAAC,QAAQ;AACX,MAAC,IAAY,WAAW,IAAI,MAAM;AAClC,MAAC,IAAY,OAAO;AACpB,WAAK;AACL;AAAA,IACF;AAEA,UAAM,SAAS,QAAQ;AACvB,UAAM,WAAW,OAAO,KAAK,KAAK,OAAK,EAAE,QAAQ,UAAU,EAAE,MAAM;AAEnE,QAAI,CAAC,UAAU;AACb,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,6BAA6B,CAAC;AAC5D;AAAA,IACF;AAEA,IAAC,IAAY,WAAW,SAAS;AACjC,IAAC,IAAY,OAAO,SAAS;AAC7B,IAAC,IAAY,cAAc,SAAS;AACpC,SAAK;AAAA,EACP;AACF;;;ACxBO,SAAS,eAAe,UAAyB;AACtD,SAAO,SAAS,SAAS,KAAc,KAAe,MAA0B;AAC9E,UAAM,QAAQ,SAAS;AACvB,UAAM,WAAW,IAAI,MAAM,IAAI,OAAO,iBAAiB;AAEvD,QAAI,MAAM,SAAS,eAAe,MAAM,UAAU,SAAS,GAAG;AAC5D,UAAI,CAAC,MAAM,UAAU,SAAS,QAAQ,GAAG;AACvC,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,sBAAsB,CAAC;AACrD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,eAAe,MAAM,UAAU,SAAS,GAAG;AAC5D,UAAI,MAAM,UAAU,SAAS,QAAQ,GAAG;AACtC,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,gBAAgB,CAAC;AAC/C;AAAA,MACF;AAAA,IACF;AAEA,SAAK;AAAA,EACP;AACF;;;ACrBO,SAAS,kBAAkB,SAA6B;AAC7D,SAAO,SAAS,YAAY,KAAc,KAAe,MAA0B;AACjF,UAAM,KAAK,IAAI,MAAM,IAAI,OAAO,iBAAiB;AACjD,UAAM,OAAQ,IAAY,QAAQ;AAElC,UAAM,SAAS,QAAQ,WAAW,IAAI,IAAI;AAE1C,QAAI,OAAO,QAAQ,GAAG;AACpB,UAAI,UAAU,qBAAqB,OAAO,KAAK;AAC/C,UAAI,UAAU,yBAAyB,KAAK,IAAI,GAAG,OAAO,SAAS,CAAC;AACpE,UAAI,UAAU,qBAAqB,KAAK,KAAK,OAAO,UAAU,GAAI,CAAC;AAAA,IACrE;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,UAAI,OAAO,GAAG,EAAE,KAAK;AAAA,QACnB,OAAO;AAAA,QACP,YAAY,KAAK,KAAK,OAAO,UAAU,GAAI;AAAA,MAC7C,CAAC;AACD;AAAA,IACF;AAEA,SAAK;AAAA,EACP;AACF;;;ACvBO,SAAS,oBAAoB,WAA6B;AAC/D,SAAO,SAAS,cAAc,KAAc,KAAe,MAA0B;AACnF,UAAM,QAAQ,KAAK,IAAI;AAEvB,QAAI,GAAG,UAAU,MAAM;AACrB,YAAM,eAAe,KAAK,IAAI,IAAI;AAClC,gBAAU,OAAO;AAAA,QACf,WAAW,KAAK,IAAI;AAAA,QACpB,QAAQ,IAAI;AAAA,QACZ,MAAM,IAAI;AAAA,QACV,YAAY,IAAI;AAAA,QAChB;AAAA,QACA,UAAW,IAAY,YAAY,IAAI,MAAM;AAAA,QAC7C,IAAI,IAAI,MAAM,IAAI,OAAO,iBAAiB;AAAA,QAC1C,QAAS,IAAY,eAAe;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAED,SAAK;AAAA,EACP;AACF;;;APNO,SAAS,wBAAwB,YAAwD;AAC9F,QAAM,SAA4C;AAAA,IAChD,YAAY,YAAY,cAAc;AAAA,IACtC,SAAS,YAAY,WAAW;AAAA,IAChC,SAAS,YAAY,WAAW;AAAA,EAClC;AAEA,QAAM,qBAAqB,IAAI,mBAAmB,OAAO,UAAU;AACnE,QAAM,mBAAmB,IAAI,iBAAiB;AAE9C,QAAM,aAAS,uBAAO;AACtB,SAAO,IAAI,oBAAoB,gBAAgB,CAAC;AAChD,SAAO,IAAI,iBAAiB,MAAM,OAAO,OAAO,CAAC;AACjD,SAAO,IAAI,eAAe,MAAM,OAAO,OAAO,CAAC;AAC/C,SAAO,IAAI,kBAAkB,kBAAkB,CAAC;AAEhD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,EACF;AACF;;;AQvCA,IAAAC,kBAA0C;AAI1C,oBAAmB;AAQZ,SAAS,oBAAoB,SAAuC;AACzE,QAAM,EAAE,oBAAoB,kBAAkB,OAAO,IAAI;AACzD,QAAM,aAAS,wBAAO;AAGtB,SAAO,IAAI,cAAc,CAAC,MAAe,QAAkB;AACzD,UAAM,YAAY,iBAAiB,aAAa,mBAAmB,aAAa;AAChF,QAAI,KAAK,SAAS;AAAA,EACpB,CAAC;AAGD,SAAO,IAAI,mBAAmB,CAAC,MAAe,QAAkB;AAC9D,QAAI,UAAU,gBAAgB,mBAAmB;AACjD,QAAI,UAAU,iBAAiB,UAAU;AACzC,QAAI,UAAU,cAAc,YAAY;AACxC,QAAI,UAAU,qBAAqB,IAAI;AACvC,QAAI,UAAU,+BAA+B,GAAG;AAChD,QAAI,aAAa;AAEjB,UAAM,OAAO,MAAY;AACvB,YAAM,YAAY,iBAAiB,aAAa,mBAAmB,aAAa;AAChF,UAAI,MAAM,SAAS,KAAK,UAAU,SAAS,CAAC;AAAA;AAAA,CAAM;AAAA,IACpD;AAEA,SAAK;AACL,UAAM,WAAW,YAAY,MAAM,GAAI;AAEvC,SAAK,GAAG,SAAS,MAAM;AACrB,oBAAc,QAAQ;AAAA,IACxB,CAAC;AAAA,EACH,CAAC;AAGD,SAAO,IAAI,WAAW,CAAC,MAAe,QAAkB;AACtD,UAAM,YAAY,iBAAiB,aAAa,mBAAmB,aAAa;AAChF,QAAI,KAAK;AAAA,MACP,YAAY,OAAO;AAAA,MACnB,SAAS,OAAO;AAAA,MAChB,YAAY,OAAO,QAAQ,KAAK,OAAO,OAAK,EAAE,MAAM,EAAE;AAAA,MACtD,eAAe,UAAU;AAAA,IAC3B,CAAC;AAAA,EACH,CAAC;AAGD,SAAO,KAAK,SAAS,CAAC,KAAc,QAAkB;AACpD,UAAM,EAAE,MAAM,KAAK,IAAI,IAAI;AAE3B,QAAI,CAAC,MAAM;AACT,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAClD;AAAA,IACF;AAEA,UAAM,SAAS;AAAA,MACb,IAAI,OAAO,OAAO,OAAO,QAAQ,KAAK,SAAS,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,MAClE,KAAK,WAAW,cAAAC,QAAO,YAAY,EAAE,EAAE,SAAS,KAAK,CAAC;AAAA,MACtD;AAAA,MACA,MAAM,QAAQ;AAAA,MACd,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,QAAQ;AAAA,IACV;AAEA,WAAO,QAAQ,KAAK,KAAK,MAAM;AAC/B,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM;AAAA,EAC7B,CAAC;AAGD,SAAO,OAAO,gBAAgB,CAAC,KAAc,QAAkB;AAC7D,UAAM,EAAE,MAAM,IAAI,IAAI;AACtB,UAAM,MAAM,OAAO,QAAQ,KAAK,KAAK,OAAK,EAAE,OAAO,KAAK;AAExD,QAAI,CAAC,KAAK;AACR,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,oBAAoB,CAAC;AACnD;AAAA,IACF;AAEA,QAAI,SAAS;AACb,QAAI,KAAK,EAAE,SAAS,mBAAmB,IAAI,MAAM,CAAC;AAAA,EACpD,CAAC;AAGD,SAAO,IAAI,SAAS,CAAC,KAAc,QAAkB;AACnD,UAAM,QAAQ,SAAS,IAAI,MAAM,KAAe,KAAK;AACrD,UAAM,SAAS,SAAS,IAAI,MAAM,MAAgB,KAAK;AACvD,UAAM,OAAO,iBAAiB,cAAc,OAAO,MAAM;AACzD,QAAI,KAAK,EAAE,MAAM,OAAO,OAAO,CAAC;AAAA,EAClC,CAAC;AAED,SAAO;AACT;;;ACpGA,mBAA4C;AAC5C,sBAA0G;AA4HlG;AA3GR,SAAS,cAAiB,YAAoB,MAAkC;AAC9E,QAAM,CAAC,MAAM,OAAO,QAAI,uBAAmB,IAAI;AAC/C,8BAAU,MAAM;AACd,UAAM,GAAG,UAAU,GAAG,IAAI,EAAE,EACzB,KAAK,OAAK,EAAE,KAAK,CAAC,EAClB,KAAK,OAAO,EACZ,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnB,GAAG,CAAC,YAAY,IAAI,CAAC;AACrB,SAAO,EAAE,KAAK;AAChB;AAEO,SAAS,iBAAiB,EAAE,WAAW,GAA0B;AACtE,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAkC,IAAI;AACxE,QAAM,CAAC,YAAY,aAAa,QAAI,uBAA0C,CAAC,CAAC;AAChF,QAAM,EAAE,MAAM,OAAO,IAAI,cAA6B,YAAY,iBAAiB;AACnF,QAAM,EAAE,MAAM,SAAS,IAAI,cAA4B,YAAY,wBAAwB;AAC3F,QAAM,qBAAiB,qBAA2B,IAAI;AACtD,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,EAAE;AACzC,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,MAAM;AAC7C,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAuB,CAAC,CAAC;AAC/D,QAAM,CAAC,UAAU,WAAW,QAAI,uBAAS,EAAE;AAC3C,QAAM,CAAC,YAAY,aAAa,QAAI,uBAAS,KAAK;AAClD,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAwB,IAAI;AAElE,QAAM,kBAAkB,YAAY;AAClC,QAAI,CAAC,QAAQ,KAAK,GAAG;AACnB,kBAAY,kBAAkB;AAC9B;AAAA,IACF;AACA,gBAAY,EAAE;AACd,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,UAAU,iBAAiB;AAAA,QACpD,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,MAAM,QAAQ,KAAK,GAAG,MAAM,QAAQ,CAAC;AAAA,MAC9D,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,oBAAY,IAAI,SAAS,sBAAsB;AAC/C;AAAA,MACF;AACA,YAAM,SAAiB,MAAM,IAAI,KAAK;AACtC,qBAAe,UAAQ,CAAC,EAAE,GAAG,QAAQ,aAAa,KAAK,GAAG,GAAG,IAAI,CAAC;AAClE,iBAAW,EAAE;AAAA,IACf,QAAQ;AACN,kBAAY,eAAe;AAAA,IAC7B,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,kBAAkB,OAAO,UAAkB;AAC/C,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,UAAU,iBAAiB,KAAK,IAAI,EAAE,QAAQ,SAAS,CAAC;AACnF,UAAI,IAAI,IAAI;AACV,uBAAe,UAAQ,KAAK,IAAI,OAAK,EAAE,OAAO,QAAQ,EAAE,GAAG,GAAG,QAAQ,MAAM,IAAI,CAAC,CAAC;AAAA,MACpF;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,QAAM,gBAAgB,CAAC,KAAa,UAAkB;AACpD,cAAU,UAAU,UAAU,GAAG,EAAE,KAAK,MAAM;AAC5C,qBAAe,KAAK;AACpB,iBAAW,MAAM,eAAe,IAAI,GAAG,GAAI;AAAA,IAC7C,CAAC;AAAA,EACH;AAEA,8BAAU,MAAM;AACd,UAAM,KAAK,IAAI,YAAY,GAAG,UAAU,yBAAyB;AACjE,mBAAe,UAAU;AAEzB,OAAG,YAAY,CAAC,UAAU;AACxB,YAAM,OAAyB,KAAK,MAAM,MAAM,IAAI;AACpD,mBAAa,IAAI;AACjB,oBAAc,UAAQ;AACpB,cAAM,OAAO;AAAA,UACX,GAAG;AAAA,UACH,EAAE,OAAM,oBAAI,KAAK,GAAE,mBAAmB,GAAG,KAAK,KAAK,kBAAkB;AAAA,QACvE;AACA,eAAO,KAAK,MAAM,GAAG;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,WAAO,MAAM;AACX,SAAG,MAAM;AAAA,IACX;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,iBAAiB,CAAC,WAAmB;AACzC,YAAQ,QAAQ;AAAA,MACd,KAAK;AAAO,eAAO;AAAA,MACnB,KAAK;AAAQ,eAAO;AAAA,MACpB,KAAK;AAAU,eAAO;AAAA,MACtB;AAAS,eAAO;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,iBAAiB,CAAC,SAAiB;AACvC,QAAI,SAAS,IAAK,QAAO;AACzB,QAAI,QAAQ,IAAK,QAAO;AACxB,WAAO;AAAA,EACT;AAEA,SACE,6CAAC,SAAI,WAAU,gBACb;AAAA,iDAAC,SAAI,WAAU,aACb;AAAA,kDAAC,QAAG,mCAAqB;AAAA,MACzB,4CAAC,OAAE,uEAAyD;AAAA,MAC5D,6CAAC,SAAI,WAAU,mBACb;AAAA,oDAAC,UAAK,WAAU,iBAAgB;AAAA,QAAE;AAAA,SAEpC;AAAA,OACF;AAAA,IAGA,6CAAC,SAAI,WAAU,iBACb;AAAA,mDAAC,SAAI,WAAU,gBACb;AAAA,oDAAC,SAAI,WAAU,iBAAgB,4BAAc;AAAA,QAC7C,4CAAC,SAAI,WAAU,iBAAiB,qBAAW,iBAAiB,GAAE;AAAA,SAChE;AAAA,MACA,6CAAC,SAAI,WAAU,gBACb;AAAA,oDAAC,SAAI,WAAU,iBAAgB,4BAAc;AAAA,QAC7C,4CAAC,SAAI,WAAU,2BACZ,qBAAW,qBAAqB,GACnC;AAAA,SACF;AAAA,MACA,6CAAC,SAAI,WAAU,gBACb;AAAA,oDAAC,SAAI,WAAU,iBAAgB,wBAAU;AAAA,QACzC,6CAAC,SAAI,WAAW,iBAAiB,aAAa,UAAU,YAAY,IAAI,cAAc,EAAE,IACrF;AAAA,qBAAW,aAAa;AAAA,UAAE;AAAA,WAC7B;AAAA,SACF;AAAA,MACA,6CAAC,SAAI,WAAU,gBACb;AAAA,oDAAC,SAAI,WAAU,iBAAgB,+BAAiB;AAAA,QAChD,6CAAC,SAAI,WAAU,iBAAiB;AAAA,qBAAW,mBAAmB;AAAA,UAAE;AAAA,WAAE;AAAA,SACpE;AAAA,OACF;AAAA,IAGA,6CAAC,SAAI,WAAU,iBAAgB,OAAO,EAAE,cAAc,GAAG,GACvD;AAAA,mDAAC,SAAI,WAAU,gBACb;AAAA,oDAAC,SAAI,WAAU,iBAAgB,6BAAe;AAAA,QAC9C,4CAAC,SAAI,WAAU,4BACZ,qBAAW,iBAAiB,GAC/B;AAAA,SACF;AAAA,MACA,6CAAC,SAAI,WAAU,gBACb;AAAA,oDAAC,SAAI,WAAU,iBAAgB,wBAAU;AAAA,QACzC,4CAAC,SAAI,WAAU,iBAAiB,qBAAW,iBAAiB,GAAE;AAAA,SAChE;AAAA,MACA,6CAAC,SAAI,WAAU,gBACb;AAAA,oDAAC,SAAI,WAAU,iBAAgB,iCAAmB;AAAA,QAClD,4CAAC,SAAI,WAAU,iBAAiB,qBAAW,iBAAiB,GAAE;AAAA,SAChE;AAAA,MACA,6CAAC,SAAI,WAAU,gBACb;AAAA,oDAAC,SAAI,WAAU,iBAAgB,8BAAgB;AAAA,QAC/C,4CAAC,SAAI,WAAU,iBACZ,mBAAS,OAAO,KAAK,OAAO,WAAW,KAAK,EAAE,SAAS,GAC1D;AAAA,SACF;AAAA,OACF;AAAA,IAGA,6CAAC,SAAI,WAAU,iBACb;AAAA,mDAAC,SAAI,WAAU,iBACb;AAAA,oDAAC,SAAI,WAAU,kBAAiB,iCAAmB;AAAA,QACnD,4CAAC,uCAAoB,OAAM,QAAO,QAAQ,KACxC,uDAAC,6BAAU,MAAM,YACf;AAAA,sDAAC,iCAAc,iBAAgB,OAAM,QAAO,6BAA4B;AAAA,UACxE,4CAAC,yBAAM,SAAQ,QAAO,MAAM,EAAE,MAAM,8BAA8B,UAAU,GAAG,GAAG;AAAA,UAClF,4CAAC,yBAAM,MAAM,EAAE,MAAM,8BAA8B,UAAU,GAAG,GAAG;AAAA,UACnE;AAAA,YAAC;AAAA;AAAA,cACC,cAAc,EAAE,YAAY,8BAA8B,QAAQ,uCAAuC,cAAc,EAAE;AAAA,cACzH,YAAY,EAAE,OAAO,6BAA6B;AAAA;AAAA,UACpD;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,QAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,WAAW,EAAE,GAAG,GAAG,MAAM,4BAA4B;AAAA;AAAA,UACvD;AAAA,WACF,GACF;AAAA,SACF;AAAA,MAEA,6CAAC,SAAI,WAAU,iBACb;AAAA,oDAAC,SAAI,WAAU,kBAAiB,2BAAa;AAAA,QAC7C,4CAAC,uCAAoB,OAAM,QAAO,QAAQ,KACxC;AAAA,UAAC;AAAA;AAAA,YACC,MAAM,WAAW,gBAAgB,CAAC;AAAA,YAClC,QAAO;AAAA,YAEP;AAAA,0DAAC,iCAAc,iBAAgB,OAAM,QAAO,6BAA4B;AAAA,cACxE,4CAAC,yBAAM,MAAK,UAAS,MAAM,EAAE,MAAM,8BAA8B,UAAU,GAAG,GAAG;AAAA,cACjF;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,MAAM,EAAE,MAAM,8BAA8B,UAAU,GAAG;AAAA,kBACzD,OAAO;AAAA;AAAA,cACT;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,cAAc,EAAE,YAAY,8BAA8B,QAAQ,uCAAuC,cAAc,EAAE;AAAA;AAAA,cAC3H;AAAA,cACA,4CAAC,uBAAI,SAAQ,SAAQ,MAAK,6BAA4B,QAAQ,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG;AAAA;AAAA;AAAA,QAC9E,GACF;AAAA,SACF;AAAA,OACF;AAAA,IAGA,6CAAC,SAAI,WAAU,mBACb;AAAA,kDAAC,SAAI,WAAU,iBAAgB,6BAAe;AAAA,MAC9C,6CAAC,WAAM,WAAU,iBACf;AAAA,oDAAC,WACC,uDAAC,QACC;AAAA,sDAAC,QAAG,kBAAI;AAAA,UACR,4CAAC,QAAG,oBAAM;AAAA,UACV,4CAAC,QAAG,kBAAI;AAAA,UACR,4CAAC,QAAG,oBAAM;AAAA,UACV,4CAAC,QAAG,sBAAQ;AAAA,UACZ,4CAAC,QAAG,gBAAE;AAAA,WACR,GACF;AAAA,QACA,6CAAC,WACG;AAAA,qBAAU,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,MAChC,6CAAC,QACC;AAAA,wDAAC,QAAI,cAAI,KAAK,IAAI,SAAS,EAAE,mBAAmB,GAAE;AAAA,YAClD,4CAAC,QACC,sDAAC,UAAK,WAAW,mBAAmB,eAAe,IAAI,MAAM,CAAC,IAC3D,cAAI,QACP,GACF;AAAA,YACA,4CAAC,QAAI,cAAI,MAAK;AAAA,YACd,4CAAC,QAAG,WAAW,eAAe,IAAI,UAAU,GAAI,cAAI,YAAW;AAAA,YAC/D,6CAAC,QAAI;AAAA,kBAAI;AAAA,cAAa;AAAA,eAAE;AAAA,YACxB,4CAAC,QAAI,cAAI,IAAG;AAAA,eAVL,CAWT,CACD;AAAA,WACC,CAAC,UAAU,QAAQ,SAAS,KAAK,WAAW,MAC5C,4CAAC,QACC,sDAAC,QAAG,SAAS,GAAG,OAAO,EAAE,WAAW,UAAU,OAAO,6BAA6B,GAAG,2EAErF,GACF;AAAA,WAEJ;AAAA,SACF;AAAA,OACF;AAAA,IAGC,UACC,6CAAC,SAAI,WAAU,qBACb;AAAA,mDAAC,SAAI,WAAU,kBACb;AAAA,oDAAC,QAAG,8BAAgB;AAAA,QACnB,OAAO,QAAQ,OAAO,WAAW,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,MACvD,6CAAC,SAAe,WAAU,gBACxB;AAAA,sDAAC,UAAK,WAAU,gBAAgB,gBAAK;AAAA,UACrC,4CAAC,UAAK,WAAU,kBACb,eAAK,cAAc,SAChB,cACA,GAAG,KAAK,WAAW,WAAW,KAAK,YAAY,OAAS,GAAI,KAClE;AAAA,aANQ,IAOV,CACD;AAAA,SACH;AAAA,MAEA,6CAAC,SAAI,WAAU,kBACb;AAAA,oDAAC,QAAG,sBAAQ;AAAA,QACZ,6CAAC,SAAI,WAAU,gBACb;AAAA,sDAAC,UAAK,WAAU,gBAAe,kBAAI;AAAA,UACnC,4CAAC,UAAK,WAAU,kBAAkB,iBAAO,QAAQ,MAAK;AAAA,WACxD;AAAA,QACA,6CAAC,SAAI,WAAU,gBACb;AAAA,sDAAC,UAAK,WAAU,gBAAe,uBAAS;AAAA,UACxC,4CAAC,UAAK,WAAU,kBACb,iBAAO,QAAQ,UAAU,WAAW,IAAI,UAAU,OAAO,QAAQ,UAAU,SAAS,QACvF;AAAA,WACF;AAAA,QACA,6CAAC,SAAI,WAAU,gBACb;AAAA,sDAAC,UAAK,WAAU,gBAAe,uBAAS;AAAA,UACxC,4CAAC,UAAK,WAAU,kBACb,iBAAO,QAAQ,UAAU,WAAW,IAAI,UAAU,OAAO,QAAQ,UAAU,SAAS,QACvF;AAAA,WACF;AAAA,SACF;AAAA,MAEA,6CAAC,SAAI,WAAU,kBACb;AAAA,oDAAC,QAAG,0BAAY;AAAA,QAChB,6CAAC,SAAI,WAAU,gBACb;AAAA,sDAAC,UAAK,WAAU,gBAAe,0BAAY;AAAA,UAC3C,6CAAC,UAAK,WAAU,kBACb;AAAA,mBAAO,WAAW,YAAY;AAAA,YAAY;AAAA,YAAI,OAAO,WAAW,YAAY,WAAW;AAAA,YAAK;AAAA,aAC/F;AAAA,WACF;AAAA,QACA,6CAAC,SAAI,WAAU,gBACb;AAAA,sDAAC,UAAK,WAAU,gBAAe,0BAAY;AAAA,UAC3C,4CAAC,UAAK,WAAU,kBAAkB,iBAAO,WAAW,aAAY;AAAA,WAClE;AAAA,QACA,6CAAC,SAAI,WAAU,gBACb;AAAA,sDAAC,UAAK,WAAU,gBAAe,yBAAW;AAAA,UAC1C,4CAAC,UAAK,WAAU,kBAAkB,iBAAO,YAAW;AAAA,WACtD;AAAA,QACA,6CAAC,SAAI,WAAU,gBACb;AAAA,sDAAC,UAAK,WAAU,gBAAe,iCAAmB;AAAA,UAClD,4CAAC,UAAK,WAAU,kBAAkB,iBAAO,eAAc;AAAA,WACzD;AAAA,SACF;AAAA,OACF;AAAA,IAIF,6CAAC,SAAI,WAAU,mBACb;AAAA,mDAAC,SAAI,WAAU,kBACb;AAAA,oDAAC,QAAG,sBAAQ;AAAA,QACZ,4CAAC,OAAE,8FAAgF;AAAA,SACrF;AAAA,MAEA,6CAAC,SAAI,WAAU,kBACb;AAAA,qDAAC,SAAI,WAAU,gBACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,aAAY;AAAA,cACZ,OAAO;AAAA,cACP,UAAU,OAAK,WAAW,EAAE,OAAO,KAAK;AAAA,cACxC,WAAW,OAAK,EAAE,QAAQ,WAAW,gBAAgB;AAAA;AAAA,UACvD;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO;AAAA,cACP,UAAU,OAAK,WAAW,EAAE,OAAO,KAAK;AAAA,cAEvC,oBAAU,OAAO,KAAK,OAAO,WAAW,KAAK,EAAE,IAAI,UAClD,4CAAC,YAAkB,OAAO,MAAO,kBAApB,IAAyB,CACvC;AAAA;AAAA,UACH;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS;AAAA,cACT,UAAU;AAAA,cAET,uBAAa,gBAAgB;AAAA;AAAA,UAChC;AAAA,WACF;AAAA,QACC,YAAY,4CAAC,SAAI,WAAU,iBAAiB,oBAAS;AAAA,SACxD;AAAA,MAEC,YAAY,SAAS,KACpB,4CAAC,SAAI,WAAU,gBACZ,sBAAY,IAAI,OACf,6CAAC,SAAe,WAAW,eAAe,CAAC,EAAE,SAAS,mBAAmB,EAAE,IACzE;AAAA,qDAAC,SAAI,WAAU,cACb;AAAA,sDAAC,UAAK,WAAU,eAAe,YAAE,MAAK;AAAA,UACtC,6CAAC,SAAI,WAAU,iBACb;AAAA,wDAAC,UAAK,WAAU,eAAe,YAAE,MAAK;AAAA,YACtC,4CAAC,UAAK,WAAW,iBAAiB,EAAE,SAAS,kBAAkB,iBAAiB,IAC7E,YAAE,SAAS,WAAW,WACzB;AAAA,aACF;AAAA,WACF;AAAA,QACA,6CAAC,SAAI,WAAU,gBACb;AAAA,sDAAC,UAAM,YAAE,KAAI;AAAA,UACZ,EAAE,UACD;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,EAAE;AAAA,cAEvC,0BAAgB,EAAE,KAAK,YAAY;AAAA;AAAA,UACtC;AAAA,WAEJ;AAAA,QACA,6CAAC,SAAI,WAAU,iBACb;AAAA,sDAAC,UAAK,WAAU,aAAa,YAAE,IAAG;AAAA,UACjC,EAAE,UACD;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,MAAM,gBAAgB,EAAE,EAAE;AAAA,cACpC;AAAA;AAAA,UAED;AAAA,WAEJ;AAAA,QACC,EAAE,eACD,6CAAC,SAAI,WAAU,gBACb;AAAA,sDAAC,UAAK,WAAU,sBAAqB,oBAAM;AAAA,UAC3C,6CAAC,UAAK;AAAA;AAAA,YAAqB,EAAE;AAAA,YAAI;AAAA,YAAG;AAAA,YAAW;AAAA,aAAO;AAAA,WACxD;AAAA,WApCM,EAAE,EAsCZ,CACD,GACH;AAAA,OAEJ;AAAA,KACF;AAEJ;","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 { 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 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 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 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 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 <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"]}