claude-code-router-config 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,512 @@
1
+ // Dashboard JavaScript
2
+ class Dashboard {
3
+ constructor() {
4
+ this.apiBase = '/api';
5
+ this.currentTab = 'overview';
6
+ this.refreshInterval = null;
7
+ this.init();
8
+ }
9
+
10
+ async init() {
11
+ this.setupEventListeners();
12
+ await this.loadInitialData();
13
+ this.startAutoRefresh();
14
+ }
15
+
16
+ setupEventListeners() {
17
+ // Tab navigation
18
+ document.querySelectorAll('.nav-item').forEach(item => {
19
+ item.addEventListener('click', (e) => {
20
+ const tabName = e.target.dataset.tab;
21
+ this.switchTab(tabName);
22
+ });
23
+ });
24
+
25
+ // Analytics period change
26
+ const periodSelect = document.getElementById('analytics-period');
27
+ if (periodSelect) {
28
+ periodSelect.addEventListener('change', () => {
29
+ this.loadAnalyticsData();
30
+ });
31
+ }
32
+
33
+ // Config template change
34
+ const templateSelect = document.getElementById('template-select');
35
+ if (templateSelect) {
36
+ templateSelect.addEventListener('change', () => {
37
+ this.updateTemplatePreview();
38
+ });
39
+ }
40
+ }
41
+
42
+ switchTab(tabName) {
43
+ // Update nav items
44
+ document.querySelectorAll('.nav-item').forEach(item => {
45
+ item.classList.remove('active');
46
+ });
47
+ document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
48
+
49
+ // Update content
50
+ document.querySelectorAll('.tab-content').forEach(content => {
51
+ content.classList.remove('active');
52
+ });
53
+ document.getElementById(tabName).classList.add('active');
54
+
55
+ this.currentTab = tabName;
56
+
57
+ // Load tab-specific data
58
+ this.loadTabData(tabName);
59
+ }
60
+
61
+ async loadTabData(tabName) {
62
+ switch (tabName) {
63
+ case 'overview':
64
+ await this.loadOverviewData();
65
+ break;
66
+ case 'analytics':
67
+ await this.loadAnalyticsData();
68
+ break;
69
+ case 'health':
70
+ await this.loadHealthData();
71
+ break;
72
+ case 'config':
73
+ await this.loadConfigData();
74
+ break;
75
+ }
76
+ }
77
+
78
+ async loadInitialData() {
79
+ try {
80
+ await Promise.all([
81
+ this.loadOverviewData(),
82
+ this.loadConfigData()
83
+ ]);
84
+ } catch (error) {
85
+ this.showError('Failed to load initial data');
86
+ }
87
+ }
88
+
89
+ async loadOverviewData() {
90
+ try {
91
+ const [todayResponse, providersResponse] = await Promise.all([
92
+ fetch(`${this.apiBase}/analytics/today`),
93
+ fetch(`${this.apiBase}/health/providers`)
94
+ ]);
95
+
96
+ const today = await todayResponse.json();
97
+ const providers = await providersResponse.json();
98
+
99
+ this.updateTodayStats(today.data);
100
+ this.updateProviderStatus(providers.data);
101
+ this.updateLastUpdated();
102
+ } catch (error) {
103
+ console.error('Failed to load overview data:', error);
104
+ }
105
+ }
106
+
107
+ async loadAnalyticsData() {
108
+ try {
109
+ const period = document.getElementById('analytics-period')?.value || 'week';
110
+ const response = await fetch(`${this.apiBase}/analytics/summary`);
111
+ const data = await response.json();
112
+
113
+ this.updateAnalyticsDisplay(data.data);
114
+ } catch (error) {
115
+ console.error('Failed to load analytics data:', error);
116
+ }
117
+ }
118
+
119
+ async loadHealthData() {
120
+ try {
121
+ const [providersResponse, systemResponse] = await Promise.all([
122
+ fetch(`${this.apiBase}/health/providers`),
123
+ fetch(`${this.apiBase}/health/system`)
124
+ ]);
125
+
126
+ const providers = await providersResponse.json();
127
+ const system = await systemResponse.json();
128
+
129
+ this.updateHealthProviders(providers.data);
130
+ this.updateSystemHealth(system.data);
131
+ } catch (error) {
132
+ console.error('Failed to load health data:', error);
133
+ }
134
+ }
135
+
136
+ async loadConfigData() {
137
+ try {
138
+ const [configResponse, templatesResponse] = await Promise.all([
139
+ fetch(`${this.apiBase}/config/current`),
140
+ fetch(`${this.apiBase}/config/templates`)
141
+ ]);
142
+
143
+ const config = await configResponse.json();
144
+ const templates = await templatesResponse.json();
145
+
146
+ this.updateConfigDisplay(config.data);
147
+ this.updateTemplateOptions(templates.data);
148
+ } catch (error) {
149
+ console.error('Failed to load config data:', error);
150
+ }
151
+ }
152
+
153
+ updateTodayStats(data) {
154
+ if (!data) return;
155
+
156
+ document.getElementById('today-requests').textContent = data.requests || 0;
157
+ document.getElementById('today-tokens').textContent = this.formatNumber(data.tokens || 0);
158
+ document.getElementById('today-cost').textContent = `$${(data.cost || 0).toFixed(4)}`;
159
+ document.getElementById('today-latency').textContent = `${data.avgLatency || 0}ms`;
160
+ }
161
+
162
+ updateProviderStatus(providers) {
163
+ const container = document.getElementById('provider-status-grid');
164
+ if (!container) return;
165
+
166
+ container.innerHTML = '';
167
+
168
+ providers.forEach(provider => {
169
+ const statusDiv = document.createElement('div');
170
+ statusDiv.className = `provider-status ${provider.status}`;
171
+ statusDiv.innerHTML = `
172
+ <div style="font-weight: 600; margin-bottom: 0.5rem;">${provider.name}</div>
173
+ <div style="font-size: 0.875rem; color: var(--text-secondary);">${provider.status}</div>
174
+ `;
175
+ container.appendChild(statusDiv);
176
+ });
177
+ }
178
+
179
+ updateAnalyticsDisplay(data) {
180
+ if (!data) return;
181
+
182
+ const container = document.getElementById('detailed-stats');
183
+ if (!container) return;
184
+
185
+ const showDetailed = document.getElementById('show-detailed')?.checked;
186
+ const showCosts = document.getElementById('show-costs')?.checked;
187
+
188
+ let html = `
189
+ <div class="metric">
190
+ <span class="label">Total Requests</span>
191
+ <span class="value">${this.formatNumber(data.totalRequests || 0)}</span>
192
+ </div>
193
+ <div class="metric">
194
+ <span class="label">Total Tokens</span>
195
+ <span class="value">${this.formatNumber(data.totalTokens || 0)}</span>
196
+ </div>
197
+ `;
198
+
199
+ if (showCosts) {
200
+ html += `
201
+ <div class="metric">
202
+ <span class="label">Total Cost</span>
203
+ <span class="value">$${(data.totalCost || 0).toFixed(4)}</span>
204
+ </div>
205
+ `;
206
+ }
207
+
208
+ html += `
209
+ <div class="metric">
210
+ <span class="label">Average Latency</span>
211
+ <span class="value">${data.avgLatency || 0}ms</span>
212
+ </div>
213
+ `;
214
+
215
+ if (showDetailed && data.providers) {
216
+ html += '<h4 style="margin-top: 1rem; margin-bottom: 0.5rem;">Provider Breakdown</h4>';
217
+ Object.entries(data.providers).forEach(([provider, count]) => {
218
+ html += `
219
+ <div class="metric">
220
+ <span class="label">${provider}</span>
221
+ <span class="value">${this.formatNumber(count)}</span>
222
+ </div>
223
+ `;
224
+ });
225
+ }
226
+
227
+ container.innerHTML = html;
228
+ }
229
+
230
+ updateHealthProviders(providers) {
231
+ const container = document.getElementById('health-providers');
232
+ if (!container) return;
233
+
234
+ container.innerHTML = '';
235
+
236
+ providers.forEach(provider => {
237
+ const statusClass = provider.status === 'healthy' ? 'status-healthy' : 'status-unhealthy';
238
+ const statusDiv = document.createElement('div');
239
+ statusDiv.className = `metric`;
240
+ statusDiv.innerHTML = `
241
+ <span class="label">${provider.name}</span>
242
+ <span class="status-badge ${statusClass}">${provider.status}</span>
243
+ `;
244
+ container.appendChild(statusDiv);
245
+ });
246
+ }
247
+
248
+ updateSystemHealth(data) {
249
+ if (!data) return;
250
+
251
+ document.getElementById('system-uptime').textContent = this.formatDuration(data.uptime);
252
+ document.getElementById('system-memory').textContent = this.formatBytes(data.memory?.used || 0);
253
+ document.getElementById('system-node').textContent = data.nodeVersion || '-';
254
+ }
255
+
256
+ updateConfigDisplay(config) {
257
+ const container = document.getElementById('config-display');
258
+ if (!container) return;
259
+
260
+ container.innerHTML = `
261
+ <pre style="background: var(--background); padding: 1rem; border-radius: var(--radius); overflow-x: auto;">
262
+ ${JSON.stringify(config, null, 2)}
263
+ </pre>
264
+ `;
265
+
266
+ // Update provider config
267
+ const providerContainer = document.getElementById('provider-config');
268
+ if (providerContainer && config.Providers) {
269
+ providerContainer.innerHTML = '';
270
+ config.Providers.forEach(provider => {
271
+ providerContainer.innerHTML += `
272
+ <div class="metric">
273
+ <span class="label">${provider.name}</span>
274
+ <span class="value">${provider.models.length} models</span>
275
+ </div>
276
+ `;
277
+ });
278
+ }
279
+
280
+ // Update router config
281
+ const routerContainer = document.getElementById('router-config');
282
+ if (routerContainer && config.Router) {
283
+ routerContainer.innerHTML = '';
284
+ Object.entries(config.Router).forEach(([key, value]) => {
285
+ routerContainer.innerHTML += `
286
+ <div class="metric">
287
+ <span class="label">${key}</span>
288
+ <span class="value">${JSON.stringify(value)}</span>
289
+ </div>
290
+ `;
291
+ });
292
+ }
293
+ }
294
+
295
+ updateTemplateOptions(templates) {
296
+ const select = document.getElementById('template-select');
297
+ if (!select) return;
298
+
299
+ // Keep current selection
300
+ const currentValue = select.value;
301
+
302
+ // Clear existing options (except the first one)
303
+ while (select.children.length > 1) {
304
+ select.removeChild(select.lastChild);
305
+ }
306
+
307
+ templates.forEach(template => {
308
+ const option = document.createElement('option');
309
+ option.value = template.name;
310
+ option.textContent = template.description || template.name;
311
+ select.appendChild(option);
312
+ });
313
+
314
+ // Restore previous selection if it still exists
315
+ if (currentValue) {
316
+ select.value = currentValue;
317
+ }
318
+ }
319
+
320
+ async refreshData() {
321
+ this.showLoading();
322
+ try {
323
+ await this.loadTabData(this.currentTab);
324
+ this.showSuccess('Data refreshed');
325
+ } catch (error) {
326
+ this.showError('Failed to refresh data');
327
+ }
328
+ }
329
+
330
+ startAutoRefresh() {
331
+ this.refreshInterval = setInterval(() => {
332
+ this.loadTabData(this.currentTab);
333
+ }, 30000); // Refresh every 30 seconds
334
+ }
335
+
336
+ stopAutoRefresh() {
337
+ if (this.refreshInterval) {
338
+ clearInterval(this.refreshInterval);
339
+ this.refreshInterval = null;
340
+ }
341
+ }
342
+
343
+ // Utility methods
344
+ formatNumber(num) {
345
+ if (num >= 1000000) {
346
+ return (num / 1000000).toFixed(1) + 'M';
347
+ } else if (num >= 1000) {
348
+ return (num / 1000).toFixed(1) + 'K';
349
+ }
350
+ return num.toString();
351
+ }
352
+
353
+ formatBytes(bytes) {
354
+ if (bytes >= 1073741824) {
355
+ return (bytes / 1073741824).toFixed(2) + ' GB';
356
+ } else if (bytes >= 1048576) {
357
+ return (bytes / 1048576).toFixed(2) + ' MB';
358
+ } else if (bytes >= 1024) {
359
+ return (bytes / 1024).toFixed(2) + ' KB';
360
+ }
361
+ return bytes + ' B';
362
+ }
363
+
364
+ formatDuration(seconds) {
365
+ if (seconds >= 3600) {
366
+ return Math.floor(seconds / 3600) + 'h ' + Math.floor((seconds % 3600) / 60) + 'm';
367
+ } else if (seconds >= 60) {
368
+ return Math.floor(seconds / 60) + 'm ' + Math.floor(seconds % 60) + 's';
369
+ }
370
+ return Math.floor(seconds) + 's';
371
+ }
372
+
373
+ updateLastUpdated() {
374
+ const element = document.getElementById('last-updated');
375
+ if (element) {
376
+ element.textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
377
+ }
378
+ }
379
+
380
+ showLoading() {
381
+ document.getElementById('connection-status').textContent = 'Loading...';
382
+ document.getElementById('connection-status').className = 'status-badge status-warning';
383
+ }
384
+
385
+ showSuccess(message) {
386
+ document.getElementById('connection-status').textContent = message;
387
+ document.getElementById('connection-status').className = 'status-badge status-healthy';
388
+ }
389
+
390
+ showError(message) {
391
+ document.getElementById('connection-status').textContent = message;
392
+ document.getElementById('connection-status').className = 'status-badge status-unhealthy';
393
+ }
394
+ }
395
+
396
+ // Global functions for button clicks
397
+ window.dashboard = null;
398
+
399
+ async function refreshData() {
400
+ if (window.dashboard) {
401
+ await window.dashboard.refreshData();
402
+ }
403
+ }
404
+
405
+ async function testAllProviders() {
406
+ try {
407
+ const response = await fetch('/api/health/providers');
408
+ const data = await response.json();
409
+ alert(`Provider tests completed:\n${data.data.map(p => `${p.name}: ${p.status}`).join('\n')}`);
410
+ } catch (error) {
411
+ alert('Failed to test providers');
412
+ }
413
+ }
414
+
415
+ function openConfig() {
416
+ alert('Configuration editor would open here');
417
+ }
418
+
419
+ function viewLogs() {
420
+ alert('Log viewer would open here');
421
+ }
422
+
423
+ async function exportAnalytics() {
424
+ try {
425
+ const format = prompt('Export format (json/csv):', 'json');
426
+ if (format) {
427
+ window.location.href = `/api/analytics/export?format=${format}`;
428
+ }
429
+ } catch (error) {
430
+ alert('Failed to export analytics');
431
+ }
432
+ }
433
+
434
+ async function refreshHealthStatus() {
435
+ try {
436
+ const response = await fetch('/api/health/providers');
437
+ const data = await response.json();
438
+ alert('Health status refreshed');
439
+ } catch (error) {
440
+ alert('Failed to refresh health status');
441
+ }
442
+ }
443
+
444
+ async function runHealthChecks() {
445
+ alert('Running comprehensive health checks...');
446
+ }
447
+
448
+ function applyTemplate() {
449
+ const select = document.getElementById('template-select');
450
+ if (select && select.value) {
451
+ alert(`Applying template: ${select.value}`);
452
+ }
453
+ }
454
+
455
+ function backupConfig() {
456
+ alert('Configuration backup would be created here');
457
+ }
458
+
459
+ function validateConfig() {
460
+ alert('Configuration validation would run here');
461
+ }
462
+
463
+ function editConfig() {
464
+ alert('Configuration editor would open here');
465
+ }
466
+
467
+ function reloadConfig() {
468
+ alert('Configuration would be reloaded here');
469
+ }
470
+
471
+ function updateTemplatePreview() {
472
+ const select = document.getElementById('template-select');
473
+ if (select && select.value) {
474
+ console.log(`Template preview: ${select.value}`);
475
+ }
476
+ }
477
+
478
+ function closeModal() {
479
+ document.getElementById('modal').classList.add('hidden');
480
+ }
481
+
482
+ function showModal(title, content, actionText = null, actionCallback = null) {
483
+ const modal = document.getElementById('modal');
484
+ const modalTitle = document.getElementById('modal-title');
485
+ const modalBody = document.getElementById('modal-body');
486
+ const modalAction = document.getElementById('modal-action');
487
+
488
+ modalTitle.textContent = title;
489
+ modalBody.innerHTML = content;
490
+
491
+ if (actionText && actionCallback) {
492
+ modalAction.textContent = actionText;
493
+ modalAction.style.display = 'block';
494
+ modalAction.onclick = actionCallback;
495
+ } else {
496
+ modalAction.style.display = 'none';
497
+ }
498
+
499
+ modal.classList.remove('hidden');
500
+ }
501
+
502
+ // Initialize dashboard when DOM is loaded
503
+ document.addEventListener('DOMContentLoaded', () => {
504
+ window.dashboard = new Dashboard();
505
+ });
506
+
507
+ // Cleanup on page unload
508
+ window.addEventListener('beforeunload', () => {
509
+ if (window.dashboard) {
510
+ window.dashboard.stopAutoRefresh();
511
+ }
512
+ });