|
|
import apiClient from './apiClient.js'; |
|
|
|
|
|
class ProvidersView { |
|
|
constructor(section) { |
|
|
this.section = section; |
|
|
this.tableBody = section?.querySelector('[data-providers-table]'); |
|
|
this.searchInput = section?.querySelector('[data-provider-search]'); |
|
|
this.categorySelect = section?.querySelector('[data-provider-category]'); |
|
|
this.summaryNode = section?.querySelector('[data-provider-summary]'); |
|
|
this.refreshButton = section?.querySelector('[data-provider-refresh]'); |
|
|
this.providers = []; |
|
|
this.filtered = []; |
|
|
} |
|
|
|
|
|
init() { |
|
|
if (!this.section) return; |
|
|
this.bindEvents(); |
|
|
this.loadProviders(); |
|
|
} |
|
|
|
|
|
bindEvents() { |
|
|
this.searchInput?.addEventListener('input', () => this.applyFilters()); |
|
|
this.categorySelect?.addEventListener('change', () => this.applyFilters()); |
|
|
this.refreshButton?.addEventListener('click', () => this.loadProviders()); |
|
|
} |
|
|
|
|
|
async loadProviders() { |
|
|
if (this.tableBody) { |
|
|
this.tableBody.innerHTML = '<tr><td colspan="5">Loading providers...</td></tr>'; |
|
|
} |
|
|
const result = await apiClient.getProviders(); |
|
|
if (!result.ok) { |
|
|
this.tableBody.innerHTML = `<tr><td colspan="5"><div class="inline-message inline-error">${result.error}</div></td></tr>`; |
|
|
return; |
|
|
} |
|
|
|
|
|
const data = result.data || {}; |
|
|
this.providers = data.providers || data || []; |
|
|
this.applyFilters(); |
|
|
} |
|
|
|
|
|
applyFilters() { |
|
|
const term = (this.searchInput?.value || '').toLowerCase(); |
|
|
const category = this.categorySelect?.value || 'all'; |
|
|
this.filtered = this.providers.filter((provider) => { |
|
|
const matchesTerm = `${provider.name} ${provider.provider_id}`.toLowerCase().includes(term); |
|
|
const matchesCategory = category === 'all' || (provider.category || 'uncategorized') === category; |
|
|
return matchesTerm && matchesCategory; |
|
|
}); |
|
|
this.renderTable(); |
|
|
this.renderSummary(); |
|
|
} |
|
|
|
|
|
renderTable() { |
|
|
if (!this.tableBody) return; |
|
|
if (!this.filtered.length) { |
|
|
this.tableBody.innerHTML = '<tr><td colspan="5">No providers match the filters.</td></tr>'; |
|
|
return; |
|
|
} |
|
|
this.tableBody.innerHTML = this.filtered |
|
|
.map( |
|
|
(provider) => ` |
|
|
<tr> |
|
|
<td>${provider.name || provider.provider_id}</td> |
|
|
<td>${provider.category || 'general'}</td> |
|
|
<td><span class="badge ${provider.status === 'healthy' ? 'badge-success' : 'badge-danger'}">${ |
|
|
provider.status || 'unknown' |
|
|
}</span></td> |
|
|
<td>${provider.latency_ms ? `${provider.latency_ms}ms` : '—'}</td> |
|
|
<td>${provider.error || provider.status_code || 'OK'}</td> |
|
|
</tr> |
|
|
`, |
|
|
) |
|
|
.join(''); |
|
|
} |
|
|
|
|
|
renderSummary() { |
|
|
if (!this.summaryNode) return; |
|
|
const total = this.providers.length; |
|
|
const healthy = this.providers.filter((provider) => provider.status === 'healthy').length; |
|
|
const degraded = total - healthy; |
|
|
this.summaryNode.innerHTML = ` |
|
|
<div class="stat-card glass-card"> |
|
|
<h3>Total Providers</h3> |
|
|
<p class="stat-value">${total}</p> |
|
|
</div> |
|
|
<div class="stat-card glass-card"> |
|
|
<h3>Healthy</h3> |
|
|
<p class="stat-value text-success">${healthy}</p> |
|
|
</div> |
|
|
<div class="stat-card glass-card"> |
|
|
<h3>Issues</h3> |
|
|
<p class="stat-value text-danger">${degraded}</p> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
export default ProvidersView; |
|
|
|