Domains tab updates

This commit is contained in:
Raven Scott
2026-05-30 21:25:24 -04:00
parent 9fd3d6753c
commit 2b2b32f044
5 changed files with 430 additions and 102 deletions
+53 -44
View File
@@ -29,17 +29,19 @@ window.paginationState = {
// Helper function to get consensus status badge
function getConsensusStatusBadge(status) {
const badges = {
'resolved': '<span class="px-2 py-1 bg-green-500 text-white rounded text-xs">Resolved</span>',
'tie': '<span class="px-2 py-1 bg-yellow-500 text-white rounded text-xs">Tie</span>',
'conflict': '<span class="px-2 py-1 bg-red-500 text-white rounded text-xs">Conflict</span>',
'insufficient_quorum': '<span class="px-2 py-1 bg-orange-500 text-white rounded text-xs">No Quorum</span>',
'no_claims': '<span class="px-2 py-1 bg-gray-500 text-white rounded text-xs">No Claims</span>',
'error': '<span class="px-2 py-1 bg-red-500 text-white rounded text-xs">Error</span>',
'internal': '<span class="px-2 py-1 bg-blue-500 text-white rounded text-xs">Internal</span>',
'unknown': '<span class="px-2 py-1 bg-gray-400 text-white rounded text-xs">Unknown</span>'
const labels = {
resolved: 'Resolved',
tie: 'Tie',
conflict: 'Conflict',
insufficient_quorum: 'No quorum',
no_claims: 'No claims',
error: 'Error',
internal: 'Internal',
unknown: 'Unknown'
};
return badges[status] || badges['unknown'];
const key = labels[status] ? status : 'unknown';
const esc = window.escapeHtml || ((value) => String(value));
return `<span class="domain-consensus-badge domain-consensus-badge--${key}">${esc(labels[key])}</span>`;
}
// Make function globally available
@@ -142,53 +144,60 @@ window.tabs = {
filteredKey: 'filteredDomains',
containerId: 'domainsTable',
paginationId: 'domainsPagination',
sentinelId: 'domainsScrollSentinel',
sort: (a, b) => a.domain.localeCompare(b.domain, undefined, { sensitivity: 'base' }),
filter: (item, query) => item.domain.toLowerCase().includes(query) || item.hash.toLowerCase().includes(query) || (item.consensusStatus || '').toLowerCase().includes(query),
renderItem: (item) => {
const esc = window.escapeHtml || ((value) => String(value));
const domainAttr = esc(item.domain).replace(/"/g, '&quot;');
const tr = document.createElement('tr');
tr.className = 'border-b hover:bg-gray-50 dark:hover:bg-gray-700';
// Use consensus status from postFetch (which may include 'conflict')
let consensusInfo = '';
tr.className = 'domain-row';
const localBadge = item.isLocal
? ' <span class="domain-local-badge" title="Local claim"><i class="fas fa-house-user" aria-hidden="true"></i></span>'
: '';
let consensusCell = '';
if (item.consensusState) {
const consensusState = item.consensusState;
const statusBadge = getConsensusStatusBadge(item.consensusStatus);
const voteInfo = Object.keys(consensusState.voteCounts || {}).length > 0
? ` (${Object.values(consensusState.voteCounts).reduce((a, b) => a + b, 0)} votes)`
: '';
const quorumInfo = consensusState.quorumMet ? '✓' : '✗';
consensusInfo = `<td class="p-3">
<div class="flex items-center gap-2">
<span>${statusBadge}</span>
<span class="text-xs" style="color: var(--text-primary);">${voteInfo}</span>
<span class="text-xs" style="color: var(--text-primary);" title="Quorum: ${consensusState.quorumMet ? 'Met' : 'Not Met'}">${quorumInfo}</span>
const voteTotal = Object.values(item.consensusState.voteCounts || {}).reduce((a, b) => a + b, 0);
const quorumMet = item.consensusState.quorumMet;
consensusCell = `<td class="p-3">
<div class="domain-consensus-cell">
${statusBadge}
${voteTotal > 0 ? `<span class="domain-vote-count theme-text-tertiary">${voteTotal} vote${voteTotal === 1 ? '' : 's'}</span>` : ''}
<span class="domain-quorum ${quorumMet ? 'domain-quorum--met' : 'domain-quorum--miss'}" title="Quorum ${quorumMet ? 'met' : 'not met'}">${quorumMet ? '✓' : '✗'}</span>
</div>
</td>`;
} else if (item.consensusStatus === 'internal') {
consensusInfo = `<td class="p-3">${getConsensusStatusBadge('internal')}</td>`;
consensusCell = `<td class="p-3">${getConsensusStatusBadge('internal')}</td>`;
} else {
consensusInfo = `<td class="p-3">${getConsensusStatusBadge('unknown')}</td>`;
consensusCell = `<td class="p-3">${getConsensusStatusBadge(item.consensusStatus || 'unknown')}</td>`;
}
tr.innerHTML = `<td class="p-3">${item.domain}${item.isLocal ? ' <span class="domain-local-badge" title="Local claim"><i class="fas fa-house-user" aria-hidden="true"></i></span>' : ''}</td>
<td class="p-3 break-all">${item.hash}</td>
${consensusInfo}
<td class="p-3">${item.isLocal && item.hash !== 'internal' ? `<button onclick="removeDomain('${item.domain}')" class="px-2 py-1 bg-red-500 text-white rounded hover:bg-red-600">Remove</button>` : ''}</td>`;
const canRemove = item.isLocal && item.hash !== 'internal';
const removeBtn = canRemove
? `<button type="button" class="admin-btn admin-btn--danger admin-btn--sm" data-domain="${domainAttr}" onclick="removeDomain(this.getAttribute('data-domain'))" title="Remove local claim">
<i class="fas fa-trash" aria-hidden="true"></i>
</button>`
: '';
tr.innerHTML = `
<td class="p-3"><code class="domain-name">${esc(item.domain)}</code>${localBadge}</td>
<td class="p-3"><code class="domain-hash">${esc(item.hash)}</code></td>
${consensusCell}
<td class="p-3 text-right">
<div class="domain-row-actions">${removeBtn}</div>
</td>`;
return tr;
},
postFetch: (data) => {
// Data already contains consensusState from backend
return data.map(item => {
if (item.consensusStatus === 'internal' || item.hash === 'internal' || item.hash === 'none') {
return { ...item, consensusStatus: 'internal' };
}
// Use consensusStatus set by backend (including 'conflict'), fallback to consensusState status
return {
...item,
consensusStatus: item.consensusStatus || item.consensusState?.status || 'unknown'
};
});
}
preRender: (visibleCount) => {
if (window.updateDomainsChrome) window.updateDomainsChrome(visibleCount);
},
onAllItemsLoaded: () => {
if (window.showDomainsListEnd) window.showDomainsListEnd();
},
postFetch: (data) => (window.normalizeDomainsData ? window.normalizeDomainsData(data) : data)
},
entries: {
api: '/api/entries',
+7 -1
View File
@@ -361,7 +361,13 @@ function filterDomains() {
const data = window[config.dataKey];
if (!data || !Array.isArray(data)) return;
const filtered = data.filter(item => {
if (window.normalizeDomainsData) {
window[config.dataKey] = window.normalizeDomainsData(data);
}
const normalizedData = window[config.dataKey];
const filtered = normalizedData.filter(item => {
// Text search filter (existing functionality)
const matchesText = !query || config.filter(item, query);
if (!matchesText) return false;
@@ -1,4 +1,75 @@
// Domains UI functions
function normalizeDomainsData(data) {
return (Array.isArray(data) ? data : []).map((item) => {
if (item.consensusStatus === 'internal' || item.hash === 'internal' || item.hash === 'none') {
return { ...item, consensusStatus: 'internal' };
}
return {
...item,
consensusStatus: item.consensusStatus || item.consensusState?.status || 'unknown'
};
});
}
function hasActiveDomainFilters() {
const searchEl = document.getElementById('search-domains');
const consensusEl = document.getElementById('filter-consensus-status');
const hashTypeEl = document.getElementById('filter-hash-type');
const ownershipEl = document.getElementById('filter-ownership');
return Boolean(
searchEl?.value?.trim() ||
(consensusEl && consensusEl.value !== 'all') ||
(hashTypeEl && hashTypeEl.value !== 'all') ||
(ownershipEl && ownershipEl.value !== 'all')
);
}
function updateDomainsChrome(filteredCount) {
const totalCount = (window.domainsData || []).length;
const visibleCount = filteredCount != null
? filteredCount
: (window.filteredDomains || window.domainsData || []).length;
const disconnected = !window.wsConnected;
const disconnectedEl = document.getElementById('domainsDisconnected');
const emptyEl = document.getElementById('domainsEmpty');
const filteredEmptyEl = document.getElementById('domainsFilteredEmpty');
const endEl = document.getElementById('domainsEnd');
const tableEl = document.querySelector('.domains-table');
const countEl = document.getElementById('domainsCount');
const trulyEmpty = !disconnected && totalCount === 0;
const filteredToZero = !disconnected && !trulyEmpty && visibleCount === 0 && hasActiveDomainFilters();
if (disconnectedEl) disconnectedEl.classList.toggle('local-dns-empty--visible', disconnected);
if (emptyEl) emptyEl.classList.toggle('local-dns-empty--visible', trulyEmpty);
if (filteredEmptyEl) filteredEmptyEl.classList.toggle('local-dns-empty--visible', filteredToZero);
if (tableEl) tableEl.classList.toggle('domains-table--hidden', disconnected || trulyEmpty || filteredToZero);
if (endEl) endEl.classList.remove('local-dns-list-end--visible');
if (countEl) {
if (disconnected) {
countEl.textContent = 'Disconnected';
} else if (trulyEmpty) {
countEl.textContent = 'No domains';
} else if (hasActiveDomainFilters() && visibleCount !== totalCount) {
countEl.textContent = `${visibleCount.toLocaleString()} of ${totalCount.toLocaleString()} shown`;
} else {
countEl.textContent = `${totalCount.toLocaleString()} domain${totalCount === 1 ? '' : 's'}`;
}
}
}
function showDomainsListEnd() {
const endEl = document.getElementById('domainsEnd');
const totalCount = (window.filteredDomains || window.domainsData || []).length;
if (endEl && totalCount > 0 && window.wsConnected) {
endEl.classList.add('local-dns-list-end--visible');
}
}
function renderDnsConflicts() {
if (window.genericFilter) window.genericFilter('dns-conflicts');
}
@@ -132,4 +203,7 @@ window.submitAddDomain = submitAddDomain;
window.removeDomain = removeDomain;
window.loadDomainFilterSettings = loadDomainFilterSettings;
window.saveDomainFilterSettings = saveDomainFilterSettings;
window.updateDomainsChrome = updateDomainsChrome;
window.showDomainsListEnd = showDomainsListEnd;
window.normalizeDomainsData = normalizeDomainsData;