@halilertekin/claude-code-router-config 2.0.0 → 2.0.2

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.
@@ -1,512 +1,409 @@
1
- // Dashboard JavaScript
2
1
  class Dashboard {
3
2
  constructor() {
4
3
  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
- });
4
+ this.lang = this.detectLanguage();
5
+ this.providers = [];
6
+ this.translations = this.buildTranslations();
7
+ this.bindEvents();
8
+ this.setLanguage(this.lang);
9
+ this.refreshAll();
10
+ this.interval = setInterval(() => this.refreshAll(), 30000);
11
+ }
12
+
13
+ buildTranslations() {
14
+ return {
15
+ tr: {
16
+ appTitle: 'Claude Code Router',
17
+ appSubtitle: 'Birleşik yönlendirici panosu',
18
+ refresh: 'Yenile',
19
+ connected: 'Bağlı',
20
+ disconnected: 'Bağlantı yok',
21
+ overview: 'Genel Bakış',
22
+ lastUpdated: 'Son güncelleme',
23
+ requests: 'İstekler',
24
+ tokens: 'Token',
25
+ cost: 'Maliyet',
26
+ avgLatency: 'Ort. Gecikme',
27
+ providers: 'Sağlayıcılar',
28
+ quickActions: 'Hızlı İşlemler',
29
+ export: 'Dışa aktar',
30
+ refreshHealth: 'Sağlığı yenile',
31
+ analytics: 'Analitik',
32
+ periodLabel: 'Dönem',
33
+ periodToday: 'Bugün',
34
+ periodWeek: 'Son 7 gün',
35
+ periodMonth: 'Son 30 gün',
36
+ totalRequests: 'Toplam İstek',
37
+ totalTokens: 'Toplam Token',
38
+ totalCost: 'Toplam Maliyet',
39
+ topProviders: 'En Çok Kullanılanlar',
40
+ health: 'Sağlık',
41
+ system: 'Sistem',
42
+ uptime: 'Çalışma Süresi',
43
+ memory: 'Bellek',
44
+ cpu: 'CPU',
45
+ node: 'Node Sürümü',
46
+ config: 'Yapılandırma',
47
+ configSummary: 'Özet',
48
+ providerCount: 'Sağlayıcı sayısı',
49
+ defaultRoute: 'Varsayılan rota',
50
+ logging: 'Loglama',
51
+ configJson: 'Konfigürasyon',
52
+ logOn: 'Açık',
53
+ logOff: 'Kapalı',
54
+ statusHealthy: 'Sağlıklı',
55
+ statusDegraded: 'Degrade',
56
+ statusDown: 'Kapalı',
57
+ statusUnknown: 'Bilinmiyor',
58
+ dataUnavailable: 'Veri yok'
59
+ },
60
+ nl: {
61
+ appTitle: 'Claude Code Router',
62
+ appSubtitle: 'Gecombineerde routerconsole',
63
+ refresh: 'Vernieuwen',
64
+ connected: 'Verbonden',
65
+ disconnected: 'Niet verbonden',
66
+ overview: 'Overzicht',
67
+ lastUpdated: 'Laatst bijgewerkt',
68
+ requests: 'Verzoeken',
69
+ tokens: 'Tokens',
70
+ cost: 'Kosten',
71
+ avgLatency: 'Gem. latentie',
72
+ providers: 'Providers',
73
+ quickActions: 'Snelle acties',
74
+ export: 'Exporteren',
75
+ refreshHealth: 'Status vernieuwen',
76
+ analytics: 'Analyse',
77
+ periodLabel: 'Periode',
78
+ periodToday: 'Vandaag',
79
+ periodWeek: 'Laatste 7 dagen',
80
+ periodMonth: 'Laatste 30 dagen',
81
+ totalRequests: 'Totaal verzoeken',
82
+ totalTokens: 'Totaal tokens',
83
+ totalCost: 'Totale kosten',
84
+ topProviders: 'Meest gebruikt',
85
+ health: 'Status',
86
+ system: 'Systeem',
87
+ uptime: 'Uptime',
88
+ memory: 'Geheugen',
89
+ cpu: 'CPU',
90
+ node: 'Node-versie',
91
+ config: 'Configuratie',
92
+ configSummary: 'Overzicht',
93
+ providerCount: 'Aantal providers',
94
+ defaultRoute: 'Standaard route',
95
+ logging: 'Logging',
96
+ configJson: 'Configuratie',
97
+ logOn: 'Aan',
98
+ logOff: 'Uit',
99
+ statusHealthy: 'Gezond',
100
+ statusDegraded: 'Gedegradeerd',
101
+ statusDown: 'Niet beschikbaar',
102
+ statusUnknown: 'Onbekend',
103
+ dataUnavailable: 'Geen gegevens'
104
+ }
105
+ };
106
+ }
107
+
108
+ detectLanguage() {
109
+ const stored = localStorage.getItem('ccr_lang');
110
+ if (stored) return stored;
111
+ const lang = navigator.language || 'tr';
112
+ if (lang.startsWith('nl')) return 'nl';
113
+ if (lang.startsWith('tr')) return 'tr';
114
+ return 'tr';
115
+ }
116
+
117
+ t(key) {
118
+ return this.translations[this.lang]?.[key] || key;
119
+ }
120
+
121
+ setLanguage(lang) {
122
+ this.lang = lang;
123
+ localStorage.setItem('ccr_lang', lang);
124
+ document.documentElement.lang = lang;
125
+
126
+ document.querySelectorAll('[data-i18n]').forEach((el) => {
127
+ const key = el.getAttribute('data-i18n');
128
+ el.textContent = this.t(key);
23
129
  });
24
130
 
25
- // Analytics period change
26
- const periodSelect = document.getElementById('analytics-period');
27
- if (periodSelect) {
28
- periodSelect.addEventListener('change', () => {
29
- this.loadAnalyticsData();
30
- });
31
- }
131
+ document.querySelectorAll('.lang-btn').forEach((btn) => {
132
+ btn.classList.toggle('active', btn.dataset.lang === lang);
133
+ });
32
134
 
33
- // Config template change
34
- const templateSelect = document.getElementById('template-select');
35
- if (templateSelect) {
36
- templateSelect.addEventListener('change', () => {
37
- this.updateTemplatePreview();
38
- });
39
- }
135
+ this.updateLastUpdated();
40
136
  }
41
137
 
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');
138
+ bindEvents() {
139
+ const refreshBtn = document.getElementById('refresh-btn');
140
+ refreshBtn?.addEventListener('click', () => this.refreshAll());
48
141
 
49
- // Update content
50
- document.querySelectorAll('.tab-content').forEach(content => {
51
- content.classList.remove('active');
52
- });
53
- document.getElementById(tabName).classList.add('active');
142
+ const exportBtn = document.getElementById('export-btn');
143
+ exportBtn?.addEventListener('click', () => this.exportAnalytics());
54
144
 
55
- this.currentTab = tabName;
145
+ const refreshHealth = document.getElementById('refresh-health');
146
+ refreshHealth?.addEventListener('click', () => this.loadHealth());
56
147
 
57
- // Load tab-specific data
58
- this.loadTabData(tabName);
148
+ document.querySelectorAll('.lang-btn').forEach((btn) => {
149
+ btn.addEventListener('click', () => this.setLanguage(btn.dataset.lang));
150
+ });
151
+
152
+ const periodSelect = document.getElementById('analytics-period');
153
+ periodSelect?.addEventListener('change', () => this.loadAnalytics());
59
154
  }
60
155
 
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;
156
+ async fetchJson(path) {
157
+ const response = await fetch(path);
158
+ if (!response.ok) {
159
+ throw new Error(`HTTP ${response.status}`);
75
160
  }
161
+ return response.json();
76
162
  }
77
163
 
78
- async loadInitialData() {
164
+ async refreshAll() {
79
165
  try {
166
+ this.setConnectionStatus(true);
80
167
  await Promise.all([
81
- this.loadOverviewData(),
82
- this.loadConfigData()
168
+ this.loadOverview(),
169
+ this.loadAnalytics(),
170
+ this.loadHealth(),
171
+ this.loadConfig(),
172
+ this.loadStatus()
83
173
  ]);
174
+ this.updateLastUpdated();
84
175
  } catch (error) {
85
- this.showError('Failed to load initial data');
176
+ this.setConnectionStatus(false);
177
+ console.error('Failed to refresh dashboard', error);
86
178
  }
87
179
  }
88
180
 
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
- ]);
181
+ async loadOverview() {
182
+ const [todayResponse, providersResponse] = await Promise.all([
183
+ this.fetchJson(`${this.apiBase}/analytics/today`),
184
+ this.fetchJson(`${this.apiBase}/health/providers`)
185
+ ]);
95
186
 
96
- const today = await todayResponse.json();
97
- const providers = await providersResponse.json();
187
+ const today = todayResponse.data || {};
188
+ this.providers = providersResponse.data || [];
98
189
 
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
- }
190
+ document.getElementById('today-requests').textContent = this.formatNumber(today.requests || 0);
191
+ document.getElementById('today-tokens').textContent = this.formatNumber(today.tokens || 0);
192
+ document.getElementById('today-cost').textContent = this.formatCurrency(today.cost || 0);
193
+ document.getElementById('today-latency').textContent = `${today.avgLatency || 0}ms`;
106
194
 
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
- }
195
+ this.renderProviderList('provider-status-list', this.providers);
117
196
  }
118
197
 
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
- ]);
198
+ async loadAnalytics() {
199
+ const period = document.getElementById('analytics-period')?.value || 'week';
200
+ const summaryResponse = await this.fetchJson(`${this.apiBase}/analytics/summary?period=${period}`);
201
+ const summary = summaryResponse.data || {};
125
202
 
126
- const providers = await providersResponse.json();
127
- const system = await systemResponse.json();
203
+ document.getElementById('summary-requests').textContent = this.formatNumber(summary.totalRequests || 0);
204
+ document.getElementById('summary-tokens').textContent = this.formatNumber(summary.totalTokens || 0);
205
+ document.getElementById('summary-cost').textContent = this.formatCurrency(summary.totalCost || 0);
206
+ document.getElementById('summary-latency').textContent = `${summary.avgLatency || 0}ms`;
128
207
 
129
- this.updateHealthProviders(providers.data);
130
- this.updateSystemHealth(system.data);
131
- } catch (error) {
132
- console.error('Failed to load health data:', error);
133
- }
208
+ this.renderTopProviders(summary.providers || {});
134
209
  }
135
210
 
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
- ]);
211
+ async loadHealth() {
212
+ const systemResponse = await this.fetchJson(`${this.apiBase}/health/system`);
213
+ const system = systemResponse.data || {};
142
214
 
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);
215
+ if (!this.providers.length) {
216
+ const providersResponse = await this.fetchJson(`${this.apiBase}/health/providers`);
217
+ this.providers = providersResponse.data || [];
150
218
  }
151
- }
152
219
 
153
- updateTodayStats(data) {
154
- if (!data) return;
220
+ this.renderProviderList('health-providers', this.providers);
155
221
 
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`;
222
+ document.getElementById('system-uptime').textContent = this.formatDuration(system.uptime || 0);
223
+ document.getElementById('system-memory').textContent = this.formatBytes(system.memory?.heapUsed || 0);
224
+ document.getElementById('system-cpu').textContent = system.cpu ? `${system.cpu.usage}%` : '-';
225
+ document.getElementById('system-node').textContent = system.nodeVersion || '-';
160
226
  }
161
227
 
162
- updateProviderStatus(providers) {
163
- const container = document.getElementById('provider-status-grid');
164
- if (!container) return;
228
+ async loadConfig() {
229
+ const configResponse = await this.fetchJson(`${this.apiBase}/config`);
230
+ const config = configResponse.data || {};
165
231
 
166
- container.innerHTML = '';
232
+ document.getElementById('config-providers').textContent = (config.Providers || []).length;
233
+ document.getElementById('config-default').textContent = config.Router?.default || '-';
234
+ document.getElementById('config-logging').textContent = config.LOG ? this.t('logOn') : this.t('logOff');
167
235
 
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
- });
236
+ const configDisplay = document.getElementById('config-display');
237
+ if (configDisplay) {
238
+ configDisplay.textContent = JSON.stringify(config, null, 2);
239
+ }
177
240
  }
178
241
 
179
- updateAnalyticsDisplay(data) {
180
- if (!data) return;
242
+ async loadStatus() {
243
+ const statusResponse = await this.fetchJson(`${this.apiBase}/status`);
244
+ const status = statusResponse.data || {};
245
+ const version = status.version || 'v2';
246
+ const versionEl = document.getElementById('version');
247
+ if (versionEl) {
248
+ versionEl.textContent = `v${version}`.replace(/^vv/, 'v');
249
+ }
250
+ }
181
251
 
182
- const container = document.getElementById('detailed-stats');
252
+ renderProviderList(targetId, providers) {
253
+ const container = document.getElementById(targetId);
183
254
  if (!container) return;
184
255
 
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
- `;
256
+ container.innerHTML = '';
257
+ if (!providers.length) {
258
+ container.innerHTML = `<div class="muted">${this.t('dataUnavailable')}</div>`;
259
+ return;
206
260
  }
207
261
 
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
- }
262
+ providers.forEach((provider) => {
263
+ const statusKey = this.resolveStatus(provider.status || 'unknown');
264
+ const item = document.createElement('div');
265
+ item.className = 'list-item';
226
266
 
227
- container.innerHTML = html;
228
- }
267
+ const left = document.createElement('div');
268
+ left.className = 'list-left';
229
269
 
230
- updateHealthProviders(providers) {
231
- const container = document.getElementById('health-providers');
232
- if (!container) return;
270
+ const dot = document.createElement('span');
271
+ dot.className = `dot ${statusKey}`;
233
272
 
234
- container.innerHTML = '';
273
+ const name = document.createElement('span');
274
+ name.textContent = provider.name;
235
275
 
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
- }
276
+ left.appendChild(dot);
277
+ left.appendChild(name);
247
278
 
248
- updateSystemHealth(data) {
249
- if (!data) return;
279
+ const badge = document.createElement('span');
280
+ badge.textContent = this.statusLabel(statusKey);
250
281
 
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 || '-';
282
+ item.appendChild(left);
283
+ item.appendChild(badge);
284
+ container.appendChild(item);
285
+ });
254
286
  }
255
287
 
256
- updateConfigDisplay(config) {
257
- const container = document.getElementById('config-display');
288
+ renderTopProviders(providerStats) {
289
+ const container = document.getElementById('top-providers');
258
290
  if (!container) return;
259
291
 
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
- }
292
+ const entries = Object.entries(providerStats)
293
+ .sort((a, b) => b[1] - a[1])
294
+ .slice(0, 5);
279
295
 
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
- });
296
+ container.innerHTML = '';
297
+ if (!entries.length) {
298
+ container.innerHTML = `<div class="muted">${this.t('dataUnavailable')}</div>`;
299
+ return;
292
300
  }
293
- }
294
301
 
295
- updateTemplateOptions(templates) {
296
- const select = document.getElementById('template-select');
297
- if (!select) return;
302
+ entries.forEach(([provider, count]) => {
303
+ const item = document.createElement('div');
304
+ item.className = 'list-item';
298
305
 
299
- // Keep current selection
300
- const currentValue = select.value;
306
+ const left = document.createElement('div');
307
+ left.className = 'list-left';
301
308
 
302
- // Clear existing options (except the first one)
303
- while (select.children.length > 1) {
304
- select.removeChild(select.lastChild);
305
- }
309
+ const dot = document.createElement('span');
310
+ dot.className = 'dot ok';
306
311
 
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
- });
312
+ const name = document.createElement('span');
313
+ name.textContent = provider;
313
314
 
314
- // Restore previous selection if it still exists
315
- if (currentValue) {
316
- select.value = currentValue;
317
- }
318
- }
315
+ left.appendChild(dot);
316
+ left.appendChild(name);
319
317
 
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
- }
318
+ const value = document.createElement('span');
319
+ value.textContent = this.formatNumber(count);
329
320
 
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
- }
321
+ item.appendChild(left);
322
+ item.appendChild(value);
323
+ container.appendChild(item);
324
+ });
341
325
  }
342
326
 
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();
327
+ resolveStatus(status) {
328
+ if (['healthy', 'ok'].includes(status)) return 'ok';
329
+ if (['degraded', 'warn', 'warning'].includes(status)) return 'warn';
330
+ if (['down', 'unhealthy', 'error'].includes(status)) return 'down';
331
+ return 'unknown';
351
332
  }
352
333
 
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';
334
+ statusLabel(statusKey) {
335
+ switch (statusKey) {
336
+ case 'ok':
337
+ return this.t('statusHealthy');
338
+ case 'warn':
339
+ return this.t('statusDegraded');
340
+ case 'down':
341
+ return this.t('statusDown');
342
+ default:
343
+ return this.t('statusUnknown');
360
344
  }
361
- return bytes + ' B';
362
345
  }
363
346
 
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';
347
+ setConnectionStatus(connected) {
348
+ const badge = document.getElementById('connection-status');
349
+ if (!badge) return;
350
+ badge.textContent = connected ? this.t('connected') : this.t('disconnected');
351
+ badge.classList.toggle('status-ok', connected);
352
+ badge.classList.toggle('status-down', !connected);
371
353
  }
372
354
 
373
355
  updateLastUpdated() {
374
356
  const element = document.getElementById('last-updated');
375
357
  if (element) {
376
- element.textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
358
+ const now = new Date();
359
+ element.textContent = `${this.t('lastUpdated')}: ${now.toLocaleTimeString(this.locale())}`;
377
360
  }
378
361
  }
379
362
 
380
- showLoading() {
381
- document.getElementById('connection-status').textContent = 'Loading...';
382
- document.getElementById('connection-status').className = 'status-badge status-warning';
363
+ exportAnalytics() {
364
+ const period = document.getElementById('analytics-period')?.value || 'week';
365
+ window.location.href = `${this.apiBase}/analytics/export?format=json&period=${period}`;
383
366
  }
384
367
 
385
- showSuccess(message) {
386
- document.getElementById('connection-status').textContent = message;
387
- document.getElementById('connection-status').className = 'status-badge status-healthy';
368
+ locale() {
369
+ return this.lang === 'nl' ? 'nl-NL' : 'tr-TR';
388
370
  }
389
371
 
390
- showError(message) {
391
- document.getElementById('connection-status').textContent = message;
392
- document.getElementById('connection-status').className = 'status-badge status-unhealthy';
372
+ formatNumber(value) {
373
+ return new Intl.NumberFormat(this.locale()).format(value || 0);
393
374
  }
394
- }
395
375
 
396
- // Global functions for button clicks
397
- window.dashboard = null;
398
-
399
- async function refreshData() {
400
- if (window.dashboard) {
401
- await window.dashboard.refreshData();
376
+ formatCurrency(value) {
377
+ return new Intl.NumberFormat(this.locale(), {
378
+ style: 'currency',
379
+ currency: 'USD',
380
+ maximumFractionDigits: 2
381
+ }).format(value || 0);
402
382
  }
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
383
 
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}`;
384
+ formatBytes(bytes) {
385
+ if (!bytes) return '0 B';
386
+ const units = ['B', 'KB', 'MB', 'GB'];
387
+ let size = bytes;
388
+ let unitIndex = 0;
389
+ while (size >= 1024 && unitIndex < units.length - 1) {
390
+ size /= 1024;
391
+ unitIndex += 1;
428
392
  }
429
- } catch (error) {
430
- alert('Failed to export analytics');
393
+ return `${size.toFixed(2)} ${units[unitIndex]}`;
431
394
  }
432
- }
433
395
 
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';
396
+ formatDuration(seconds) {
397
+ if (!seconds) return '0s';
398
+ const hours = Math.floor(seconds / 3600);
399
+ const minutes = Math.floor((seconds % 3600) / 60);
400
+ const secs = Math.floor(seconds % 60);
401
+ if (hours > 0) return `${hours}h ${minutes}m`;
402
+ if (minutes > 0) return `${minutes}m ${secs}s`;
403
+ return `${secs}s`;
497
404
  }
498
-
499
- modal.classList.remove('hidden');
500
405
  }
501
406
 
502
- // Initialize dashboard when DOM is loaded
503
407
  document.addEventListener('DOMContentLoaded', () => {
504
408
  window.dashboard = new Dashboard();
505
409
  });
506
-
507
- // Cleanup on page unload
508
- window.addEventListener('beforeunload', () => {
509
- if (window.dashboard) {
510
- window.dashboard.stopAutoRefresh();
511
- }
512
- });