/** * Domain list view */ class DomainListView { constructor() { this.domains = []; this.filteredDomains = []; this.sortColumn = null; this.sortDirection = 'asc'; this.filterStatus = 'all'; this.searchQuery = ''; } /** * Render the domain list view */ async render() { const container = document.getElementById('domainListContent'); if (!container) return; try { // Show loading state container.innerHTML = `

Loading domains...

`; // Fetch data const data = await window.apiClient.getDomains(); this.domains = data.domains || []; this.filteredDomains = [...this.domains]; // Update sidebar stats await window.utils.updateSidebarStats(); // Render the view this.renderContent(container); } catch (error) { console.error('Error loading domains:', error); container.innerHTML = `

Error loading domains: ${error.message}

`; } } /** * Render the content */ renderContent(container) { container.innerHTML = `
${this.renderTableRows()}
Domain Status Resolved Claimant Votes Quorum Active Peers Actions
${this.filteredDomains.length === 0 ? `
No domains found
` : ''}
`; // Setup event listeners this.setupEventListeners(); } /** * Render table rows */ renderTableRows() { if (this.filteredDomains.length === 0) { return 'No domains found'; } return this.filteredDomains.map(domain => { const consensus = domain.consensus || {}; const status = consensus.status || 'unknown'; const statusText = window.utils.getStatusText(status); const statusClass = window.utils.getStatusBadgeClass(status); const totalVotes = consensus.totalVotes || 0; const minVotes = consensus.minVotes || 0; const quorumMet = consensus.quorumMet || false; const quorumPercentage = window.utils.calculateQuorumPercentage(totalVotes, minVotes); const claimant = domain.consensus?.resolvedClaimant ? window.utils.formatPeerId(domain.consensus.resolvedClaimant) : 'N/A'; const activePeers = domain.consensus?.activePeers || 0; return ` ${window.utils.escapeHtml(domain.domain)} ${domain.isLocal ? '(local)' : ''} ${statusText} ${claimant} ${totalVotes} / ${minVotes}
${quorumPercentage}%
${activePeers} `; }).join(''); } /** * Setup event listeners */ setupEventListeners() { // Search input const searchInput = document.getElementById('domainSearchInput'); if (searchInput) { const debouncedSearch = window.utils.debounce(() => { this.searchQuery = searchInput.value.toLowerCase(); this.applyFilters(); }, 300); searchInput.addEventListener('input', debouncedSearch); } // Status filter const statusFilter = document.getElementById('domainStatusFilter'); if (statusFilter) { statusFilter.addEventListener('change', (e) => { this.filterStatus = e.target.value; this.applyFilters(); }); } // Sortable columns document.querySelectorAll('[data-column]').forEach(header => { header.addEventListener('click', () => { const column = header.dataset.column; if (this.sortColumn === column) { this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc'; } else { this.sortColumn = column; this.sortDirection = 'asc'; } this.applySort(); this.updateSortIndicators(); const tbody = document.getElementById('domainTableBody'); if (tbody) { tbody.innerHTML = this.renderTableRows(); } }); }); } /** * Apply filters */ applyFilters() { this.filteredDomains = this.domains.filter(domain => { // Search filter if (this.searchQuery && !domain.domain.toLowerCase().includes(this.searchQuery)) { return false; } // Status filter if (this.filterStatus !== 'all') { const status = domain.consensus?.status || 'unknown'; if (status !== this.filterStatus) { return false; } } return true; }); // Apply sort this.applySort(); // Re-render table const tbody = document.getElementById('domainTableBody'); if (tbody) { tbody.innerHTML = this.renderTableRows(); } } /** * Apply sort */ applySort() { if (!this.sortColumn) return; this.filteredDomains.sort((a, b) => { let aVal, bVal; switch (this.sortColumn) { case 'domain': aVal = a.domain.toLowerCase(); bVal = b.domain.toLowerCase(); break; case 'status': aVal = a.consensus?.status || 'unknown'; bVal = b.consensus?.status || 'unknown'; break; case 'claimant': aVal = a.consensus?.resolvedClaimant || ''; bVal = b.consensus?.resolvedClaimant || ''; break; case 'votes': aVal = a.consensus?.totalVotes || 0; bVal = b.consensus?.totalVotes || 0; break; case 'quorum': const aQuorum = window.utils.calculateQuorumPercentage(a.consensus?.totalVotes || 0, a.consensus?.minVotes || 1); const bQuorum = window.utils.calculateQuorumPercentage(b.consensus?.totalVotes || 0, b.consensus?.minVotes || 1); aVal = aQuorum; bVal = bQuorum; break; case 'peers': aVal = a.consensus?.activePeers || 0; bVal = b.consensus?.activePeers || 0; break; default: return 0; } if (aVal < bVal) return this.sortDirection === 'asc' ? -1 : 1; if (aVal > bVal) return this.sortDirection === 'asc' ? 1 : -1; return 0; }); } /** * Update sort indicators */ updateSortIndicators() { document.querySelectorAll('[data-column] .sort-indicator').forEach(indicator => { indicator.textContent = ''; }); if (this.sortColumn) { const header = document.querySelector(`[data-column="${this.sortColumn}"]`); if (header) { const indicator = header.querySelector('.sort-indicator'); if (indicator) { indicator.textContent = this.sortDirection === 'asc' ? ' ▲' : ' ▼'; } } } } /** * View domain details */ viewDomain(domain) { // Switch to domain detail view window.app.showDomainDetail(domain); } /** * Handle updates from WebSocket */ async handleUpdate(data) { // Always update on any consensus-related change if (data.domains || data.consensus || data.changedDomains) { try { // Fetch fresh data window.apiClient.invalidateCache('domains'); const response = await window.apiClient.getDomains(); this.domains = response.domains || []; // Update sidebar stats await window.utils.updateSidebarStats(); // Apply current filters and sort (this already updates the table body) this.applyFilters(); } catch (error) { console.error('Error updating domain list:', error); // Fall back to full render on error this.render(); } } } } // Export window.domainListView = new DomainListView();