feat: Add P2P Domain Conflicts management
Implement comprehensive P2P domain conflict resolution allowing users to choose between local claim hashes and consensus-resolved hashes for domains where they have local claims but another claimant won consensus. Key features: - P2P Domain Conflicts UI tab in Local DNS section - Hash preference toggle (local vs resolved) with automatic client restart - Extended selector_cache.json to store hashPreferences alongside versionPreferences - DNS cache invalidation for immediate preference application - REST API endpoints for conflict detection and preference management - Automatic Holesail client restart when hash preferences change - Complete documentation updates across README, API docs, and glossary Resolves conflicts between local claims and consensus resolution by giving users control over which hash their domain resolves to, with seamless client management ensuring immediate effect.
This commit is contained in:
@@ -315,6 +315,37 @@ window.tabs = {
|
||||
return data.conflicts || [];
|
||||
}
|
||||
},
|
||||
'p2p-domain-conflicts': {
|
||||
api: '/api/p2p-domain-conflicts',
|
||||
searchId: 'search-p2p-domain-conflicts',
|
||||
dataKey: 'p2pDomainConflictsData',
|
||||
filteredKey: 'filteredP2pDomainConflicts',
|
||||
containerId: 'p2pDomainConflictsTable',
|
||||
paginationId: 'p2pDomainConflictsPagination',
|
||||
sort: (a, b) => a.domain.localeCompare(b.domain, undefined, { sensitivity: 'base' }),
|
||||
filter: (item, query) => item.domain.toLowerCase().includes(query) || item.localHash.toLowerCase().includes(query) || item.resolvedHash.toLowerCase().includes(query),
|
||||
renderItem: (item) => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = 'border-b hover:bg-gray-50 dark:hover:bg-gray-700';
|
||||
tr.innerHTML = `
|
||||
<td class="p-3">${item.domain}</td>
|
||||
<td class="p-3 break-all">${item.localHash || 'N/A'}</td>
|
||||
<td class="p-3 break-all">${item.resolvedHash || 'N/A'}</td>
|
||||
<td class="p-3">
|
||||
<label class="inline-flex items-center cursor-pointer">
|
||||
<span class="mr-2">${item.hashPreference === 'local' ? 'Local' : 'Resolved'}</span>
|
||||
<input type="checkbox" ${item.hashPreference === 'local' ? 'checked' : ''} onchange="toggleHashPreference('${item.domain}', this.checked)" class="sr-only peer">
|
||||
<div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-primary rounded-full peer peer-checked:bg-primary">
|
||||
<div class="absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform peer-checked:translate-x-5"></div>
|
||||
</div>
|
||||
</label>
|
||||
</td>`;
|
||||
return tr;
|
||||
},
|
||||
postFetch: (data) => {
|
||||
return data.conflicts || [];
|
||||
}
|
||||
},
|
||||
'host-servers': {
|
||||
api: '/api/holesail-servers',
|
||||
searchId: 'search-holesail',
|
||||
|
||||
@@ -319,6 +319,7 @@ function filterCerts() { genericFilter('certs'); }
|
||||
function filterInterfaces() { genericFilter('interfaces'); }
|
||||
function filterLocalDNS() { genericFilter('local-dns'); }
|
||||
function filterDnsConflicts() { genericFilter('dns-conflicts'); }
|
||||
function filterP2pDomainConflicts() { genericFilter('p2p-domain-conflicts'); }
|
||||
function filterHolesailServers() { genericFilter('host-servers'); }
|
||||
function filterHolesailClients() { genericFilter('host-clients'); }
|
||||
|
||||
@@ -334,6 +335,7 @@ window.filterCerts = filterCerts;
|
||||
window.filterInterfaces = filterInterfaces;
|
||||
window.filterLocalDNS = filterLocalDNS;
|
||||
window.filterDnsConflicts = filterDnsConflicts;
|
||||
window.filterP2pDomainConflicts = filterP2pDomainConflicts;
|
||||
window.filterHolesailServers = filterHolesailServers;
|
||||
window.filterHolesailClients = filterHolesailClients;
|
||||
|
||||
|
||||
@@ -241,6 +241,58 @@ async function toggleVersionPreference(domain, isPublic) {
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleHashPreference(domain, useLocal) {
|
||||
const newPreference = useLocal ? 'local' : 'resolved';
|
||||
|
||||
// Immediately update UI to show pending state
|
||||
if (window.showNotification) {
|
||||
window.showNotification(`Switching hash preference for ${domain}...`, 'info');
|
||||
}
|
||||
|
||||
try {
|
||||
// Step 1: Update the preference
|
||||
const response = await fetch('/api/update-hash-preference', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ domain, preference: newPreference })
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
// Step 2: Clear DNS cache for immediate effect
|
||||
await fetch('/api/clear-dns-cache', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ domain })
|
||||
});
|
||||
|
||||
// Step 3: Wait for Holesail clients to fully restart
|
||||
await fetch('/api/restart-holesail-clients-for-domain', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ domain })
|
||||
});
|
||||
|
||||
// Step 4: Verify the new connections are ready (small delay for startup)
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Success - update UI
|
||||
if (window.showNotification) {
|
||||
window.showNotification(`Hash preference for ${domain} set to ${newPreference}`);
|
||||
}
|
||||
if (window.genericFetch) {
|
||||
window.genericFetch('p2p-domain-conflicts', true);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to update hash preference:', err);
|
||||
if (window.showNotification) {
|
||||
window.showNotification('Failed to update hash preference: ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.openLocalDnsModal = openLocalDnsModal;
|
||||
window.updateLocalForm = updateLocalForm;
|
||||
window.submitLocalDns = submitLocalDns;
|
||||
|
||||
Reference in New Issue
Block a user