p2ns.admin updates for local DNS

This commit is contained in:
Raven Scott
2026-05-30 21:20:15 -04:00
parent a37daab971
commit 1c6419f7ce
4 changed files with 798 additions and 153 deletions
+238 -105
View File
@@ -165,94 +165,206 @@
</div>
<div id="local-dns" class="tab-content hidden flex flex-col admin-tab-panel--fixed">
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2 flex-shrink-0">
Local DNS
</h2>
<div class="admin-subtabs flex gap-2 mb-4 flex-shrink-0">
<button onclick="showSubTab('local-dns', 'records')" id="local-dns-subtab-records" class="admin-subtab-btn admin-subtab-btn--active px-4 py-2 rounded transition-colors">DNS Records</button>
<button onclick="showSubTab('local-dns', 'conflicts')" id="local-dns-subtab-conflicts" class="admin-subtab-btn px-4 py-2 rounded transition-colors">DNS Conflicts</button>
<button onclick="showSubTab('local-dns', 'p2p-conflicts')" id="local-dns-subtab-p2p-conflicts" class="admin-subtab-btn px-4 py-2 rounded transition-colors">P2P Conflicts</button>
</div>
<!-- DNS Records Sub-tab -->
<div id="local-dns-records" class="sub-tab-content flex flex-col flex-1 min-h-0">
<div class="mb-4 flex items-center gap-2 flex-shrink-0">
<h3 class="text-xl font-bold">Custom Local DNS Records</h3>
<button onclick="openInfoModal('local-dns')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
<header class="admin-tab-header flex-shrink-0">
<div class="admin-tab-header-main">
<h2 class="admin-tab-title">Local DNS</h2>
<p class="admin-tab-description">Override resolution with custom records, choose public vs P2P when both exist, and set hash preferences for conflicting domains.</p>
</div>
<div class="mb-6 flex-shrink-0">
<input id="search-local-dns" type="text" placeholder="Search records..." class="w-full p-3 rounded-lg theme-input focus:outline-none focus:ring-2 focus:ring-primary" oninput="filterLocalDNS()">
</div>
<div class="overflow-x-auto overflow-y-auto flex-1 min-h-0">
<table class="w-full theme-table rounded-lg">
<thead class="theme-table thead sticky top-0 z-10">
<tr>
<th class="p-3 text-left">Name</th>
<th class="p-3 text-left">Type</th>
<th class="p-3 text-left">Value</th>
<th class="p-3 text-left">TTL</th>
<th class="p-3 text-left">Actions</th>
</tr>
</thead>
<tbody id="localDnsTable"></tbody>
</table>
<div id="localDnsScrollSentinel" class="scroll-sentinel"></div>
</div>
<div id="localDnsPagination" class="flex justify-center mt-4 space-x-2 flex-shrink-0"></div>
<div class="mt-4 flex justify-end flex-shrink-0">
<button onclick="openLocalDnsModal()" class="px-6 py-2.5 bg-primary text-white rounded-lg hover:bg-primary-hover transition-all font-medium shadow-md hover:shadow-lg" style="min-width: auto; width: auto;">Add Record</button>
</div>
</div>
<!-- DNS Conflicts Sub-tab -->
<div id="local-dns-conflicts" class="sub-tab-content hidden flex flex-col flex-1 min-h-0">
<div class="mb-4 flex items-center gap-2 flex-shrink-0">
<h3 class="text-xl font-bold">DNS Conflict Selector</h3>
<button onclick="openInfoModal('dns-conflicts')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
</div>
<div class="mb-6 flex-shrink-0">
<input id="search-dns-conflicts" type="text" placeholder="Search conflicts..." class="w-full p-3 rounded-lg theme-input focus:outline-none focus:ring-2 focus:ring-primary" oninput="filterDnsConflicts()">
</div>
<div class="overflow-x-auto overflow-y-auto flex-1 min-h-0">
<table class="w-full theme-table rounded-lg">
<thead class="theme-table thead sticky top-0 z-10">
<tr>
<th class="p-3 text-left">Domain</th>
<th class="p-3 text-left">Public IP</th>
<th class="p-3 text-left">Mode</th>
</tr>
</thead>
<tbody id="dnsConflictsTable"></tbody>
</table>
<div id="dnsConflictsScrollSentinel" class="scroll-sentinel"></div>
</div>
<div id="dnsConflictsPagination" class="flex justify-center mt-4 space-x-2 flex-shrink-0"></div>
<button type="button" onclick="openInfoModal('local-dns')" class="theme-button-info text-sm px-3 py-1 rounded">Info</button>
</header>
<div class="admin-subtabs flex flex-shrink-0 mb-4">
<button type="button" onclick="showSubTab('local-dns', 'records')" id="local-dns-subtab-records" class="admin-subtab-btn admin-subtab-btn--active">
<i class="fas fa-list-ul admin-subtab-icon" aria-hidden="true"></i>
DNS Records
<span id="localDnsSubtabBadge" class="admin-subtab-badge" hidden>0</span>
</button>
<button type="button" onclick="showSubTab('local-dns', 'conflicts')" id="local-dns-subtab-conflicts" class="admin-subtab-btn">
<i class="fas fa-arrows-left-right admin-subtab-icon" aria-hidden="true"></i>
DNS Conflicts
<span id="dnsConflictsSubtabBadge" class="admin-subtab-badge" hidden>0</span>
</button>
<button type="button" onclick="showSubTab('local-dns', 'p2p-conflicts')" id="local-dns-subtab-p2p-conflicts" class="admin-subtab-btn">
<i class="fas fa-code-branch admin-subtab-icon" aria-hidden="true"></i>
P2P Conflicts
<span id="p2pConflictsSubtabBadge" class="admin-subtab-badge" hidden>0</span>
</button>
</div>
<!-- P2P Domain Conflicts Sub-tab -->
<!-- DNS Records -->
<div id="local-dns-records" class="sub-tab-content flex flex-col flex-1 min-h-0">
<div class="local-dns-layout flex-1 min-h-0 grid grid-cols-1 lg:grid-cols-3 gap-4">
<section class="theme-card admin-panel local-dns-panel lg:col-span-2 flex flex-col min-h-0">
<div class="admin-panel-header">
<h3 class="admin-panel-title">Custom records</h3>
<p class="admin-panel-subtitle" id="localDnsCount">Loading…</p>
</div>
<div class="admin-toolbar flex-shrink-0">
<label class="admin-search-field">
<i class="fas fa-magnifying-glass admin-search-icon" aria-hidden="true"></i>
<input id="search-local-dns" type="search" placeholder="Search by name, type, or value…" class="theme-input admin-search-input" oninput="filterLocalDNS()" autocomplete="off">
</label>
</div>
<div class="local-dns-table-scroll flex-1 min-h-0 overflow-auto">
<table class="w-full theme-table local-dns-table">
<thead class="theme-table thead sticky top-0 z-10">
<tr>
<th class="p-3 text-left">Name</th>
<th class="p-3 text-left w-[5.5rem]">Type</th>
<th class="p-3 text-left">Value</th>
<th class="p-3 text-left w-[4.5rem]">TTL</th>
<th class="p-3 text-right w-[6.5rem]"><span class="sr-only">Actions</span></th>
</tr>
</thead>
<tbody id="localDnsTable"></tbody>
</table>
<div id="localDnsEmpty" class="local-dns-empty">
<i class="fas fa-sitemap local-dns-empty-icon" aria-hidden="true"></i>
<p class="local-dns-empty-title">No local DNS records</p>
<p class="local-dns-empty-text theme-text-tertiary">Add overrides that take precedence over P2NS resolution on this node.</p>
</div>
<div id="localDnsFilteredEmpty" class="local-dns-empty">
<i class="fas fa-filter local-dns-empty-icon" aria-hidden="true"></i>
<p class="local-dns-empty-title">No matching records</p>
<p class="local-dns-empty-text theme-text-tertiary">Try a different search term.</p>
</div>
<div id="localDnsEnd" class="local-dns-list-end" aria-live="polite">You have reached the end</div>
<div id="localDnsScrollSentinel" class="scroll-sentinel"></div>
</div>
<div id="localDnsPagination" class="hidden"></div>
<div class="admin-panel-footer flex justify-end flex-shrink-0">
<button type="button" onclick="openLocalDnsModal()" class="admin-btn admin-btn--primary">
<i class="fas fa-plus" aria-hidden="true"></i> Add record
</button>
</div>
</section>
<aside class="local-dns-sidebar flex flex-col gap-4 min-h-0">
<section class="theme-card admin-panel local-dns-summary-panel">
<div class="admin-panel-header">
<h3 class="admin-panel-title">Overview</h3>
<p class="admin-panel-subtitle" id="localDnsOverviewLine"></p>
</div>
<dl class="local-dns-stat-grid">
<div class="local-dns-stat">
<dt>Total</dt>
<dd id="localDnsStatTotal"></dd>
</div>
<div class="local-dns-stat">
<dt>A / AAAA</dt>
<dd id="localDnsStatAddress"></dd>
</div>
<div class="local-dns-stat">
<dt>CNAME</dt>
<dd id="localDnsStatCname"></dd>
</div>
<div class="local-dns-stat">
<dt>Other</dt>
<dd id="localDnsStatOther"></dd>
</div>
</dl>
</section>
<section class="theme-card admin-panel local-dns-note-panel">
<div class="admin-panel-header">
<h3 class="admin-panel-title">About local DNS</h3>
</div>
<p class="local-dns-note-text theme-text-tertiary">Records in <code class="local-dns-inline-code">cache/local_dns.json</code> override P2NS and public resolution for matching names on this node.</p>
</section>
</aside>
</div>
</div>
<!-- DNS Conflicts -->
<div id="local-dns-conflicts" class="sub-tab-content hidden flex flex-col flex-1 min-h-0">
<section class="theme-card admin-panel local-dns-conflicts-panel flex flex-col flex-1 min-h-0">
<div class="admin-panel-header">
<div class="flex items-start justify-between gap-2">
<div>
<h3 class="admin-panel-title">DNS conflict selector</h3>
<p class="admin-panel-subtitle" id="dnsConflictsCount">Loading…</p>
</div>
<button type="button" onclick="openInfoModal('dns-conflicts')" class="theme-button-info text-sm px-3 py-1 rounded flex-shrink-0">Info</button>
</div>
</div>
<div class="admin-toolbar flex-shrink-0">
<label class="admin-search-field">
<i class="fas fa-magnifying-glass admin-search-icon" aria-hidden="true"></i>
<input id="search-dns-conflicts" type="search" placeholder="Search by domain or IP…" class="theme-input admin-search-input" oninput="filterDnsConflicts()" autocomplete="off">
</label>
</div>
<div class="local-dns-table-scroll flex-1 min-h-0 overflow-auto">
<table class="w-full theme-table local-dns-conflicts-table">
<thead class="theme-table thead sticky top-0 z-10">
<tr>
<th class="p-3 text-left">Domain</th>
<th class="p-3 text-left">Public IP</th>
<th class="p-3 text-left w-[11rem]">Preferred source</th>
</tr>
</thead>
<tbody id="dnsConflictsTable"></tbody>
</table>
<div id="dnsConflictsEmpty" class="local-dns-empty">
<i class="fas fa-circle-check local-dns-empty-icon" aria-hidden="true"></i>
<p class="local-dns-empty-title">No DNS conflicts</p>
<p class="local-dns-empty-text theme-text-tertiary">Domains with both public and P2P resolution will appear here.</p>
</div>
<div id="dnsConflictsFilteredEmpty" class="local-dns-empty">
<i class="fas fa-filter local-dns-empty-icon" aria-hidden="true"></i>
<p class="local-dns-empty-title">No matching conflicts</p>
<p class="local-dns-empty-text theme-text-tertiary">Try a different search term.</p>
</div>
<div id="dnsConflictsEnd" class="local-dns-list-end" aria-live="polite">You have reached the end</div>
<div id="dnsConflictsScrollSentinel" class="scroll-sentinel"></div>
</div>
<div id="dnsConflictsPagination" class="hidden"></div>
</section>
</div>
<!-- P2P Domain Conflicts -->
<div id="local-dns-p2p-conflicts" class="sub-tab-content hidden flex flex-col flex-1 min-h-0">
<div class="mb-4 flex items-center gap-2 flex-shrink-0">
<h3 class="text-xl font-bold">P2P Domain Conflicts</h3>
<button onclick="openInfoModal('p2p-domain-conflicts')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
</div>
<div class="mb-6 flex-shrink-0">
<input id="search-p2p-domain-conflicts" type="text" placeholder="Search conflicts..." class="w-full p-3 rounded-lg theme-input focus:outline-none focus:ring-2 focus:ring-primary" oninput="filterP2pDomainConflicts()">
</div>
<div class="overflow-x-auto overflow-y-auto flex-1 min-h-0">
<table class="w-full theme-table rounded-lg">
<thead class="theme-table thead sticky top-0 z-10">
<tr>
<th class="p-3 text-left">Domain</th>
<th class="p-3 text-left">Local Hash</th>
<th class="p-3 text-left">Resolved Hash</th>
<th class="p-3 text-left">Hash Preference</th>
</tr>
</thead>
<tbody id="p2pDomainConflictsTable"></tbody>
</table>
<div id="p2pDomainConflictsScrollSentinel" class="scroll-sentinel"></div>
</div>
<div id="p2pDomainConflictsPagination" class="flex justify-center mt-4 space-x-2 flex-shrink-0"></div>
<section class="theme-card admin-panel local-dns-p2p-panel flex flex-col flex-1 min-h-0">
<div class="admin-panel-header">
<div class="flex items-start justify-between gap-2">
<div>
<h3 class="admin-panel-title">P2P domain conflicts</h3>
<p class="admin-panel-subtitle" id="p2pConflictsCount">Loading…</p>
</div>
<button type="button" onclick="openInfoModal('p2p-domain-conflicts')" class="theme-button-info text-sm px-3 py-1 rounded flex-shrink-0">Info</button>
</div>
</div>
<div class="admin-toolbar flex-shrink-0">
<label class="admin-search-field">
<i class="fas fa-magnifying-glass admin-search-icon" aria-hidden="true"></i>
<input id="search-p2p-domain-conflicts" type="search" placeholder="Search by domain or hash…" class="theme-input admin-search-input" oninput="filterP2pDomainConflicts()" autocomplete="off">
</label>
</div>
<div class="local-dns-table-scroll flex-1 min-h-0 overflow-auto">
<table class="w-full theme-table local-dns-p2p-table">
<thead class="theme-table thead sticky top-0 z-10">
<tr>
<th class="p-3 text-left">Domain</th>
<th class="p-3 text-left">Local hash</th>
<th class="p-3 text-left">Resolved hash</th>
<th class="p-3 text-left w-[11rem]">Hash preference</th>
</tr>
</thead>
<tbody id="p2pDomainConflictsTable"></tbody>
</table>
<div id="p2pConflictsEmpty" class="local-dns-empty">
<i class="fas fa-circle-check local-dns-empty-icon" aria-hidden="true"></i>
<p class="local-dns-empty-title">No P2P conflicts</p>
<p class="local-dns-empty-text theme-text-tertiary">Domains where your local claim differs from network consensus appear here.</p>
</div>
<div id="p2pConflictsFilteredEmpty" class="local-dns-empty">
<i class="fas fa-filter local-dns-empty-icon" aria-hidden="true"></i>
<p class="local-dns-empty-title">No matching conflicts</p>
<p class="local-dns-empty-text theme-text-tertiary">Try a different search term.</p>
</div>
<div id="p2pConflictsEnd" class="local-dns-list-end" aria-live="polite">You have reached the end</div>
<div id="p2pDomainConflictsScrollSentinel" class="scroll-sentinel"></div>
</div>
<div id="p2pDomainConflictsPagination" class="hidden"></div>
</section>
</div>
</div>
@@ -1145,27 +1257,48 @@
</div>
</dialog>
<dialog id="localDnsModal" class="p-6 theme-glass rounded-lg shadow-xl w-full max-w-md">
<h3 id="local-dns-title" class="text-xl font-bold mb-4">Add Local DNS Record</h3>
<input id="local-name" placeholder="Name (domain)" class="w-full p-3 mb-4 theme-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary">
<select id="local-type" onchange="updateLocalForm()" class="w-full p-3 mb-4 theme-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary">
<option value="A">A</option>
<option value="AAAA">AAAA</option>
<option value="CNAME">CNAME</option>
<option value="MX">MX</option>
<option value="TXT">TXT</option>
<option value="SRV">SRV</option>
<option value="SOA">SOA</option>
<option value="CAA">CAA</option>
<option value="NS">NS</option>
<option value="PTR">PTR</option>
<option value="OTHER">OTHER</option>
</select>
<div id="local-value-fields" class="mb-4"></div>
<input id="local-ttl" type="number" placeholder="TTL" value="3600" class="w-full p-3 mb-4 theme-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary">
<div class="flex justify-end space-x-2">
<button onclick="document.getElementById('localDnsModal').close()" class="px-4 py-2 theme-button-info theme-text-primary rounded theme-glass-hover">Cancel</button>
<button id="local-submit" onclick="submitLocalDns(this)" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover">Add</button>
<dialog id="localDnsModal" class="local-dns-modal theme-glass rounded-lg shadow-xl w-full max-w-md p-0 border-0">
<div class="local-dns-modal-inner">
<div class="local-dns-modal-header">
<div>
<h3 id="local-dns-title" class="local-dns-modal-title">Add Local DNS Record</h3>
<p class="local-dns-modal-subtitle theme-text-tertiary">Overrides apply on this node only.</p>
</div>
<button type="button" onclick="document.getElementById('localDnsModal').close()" class="admin-btn admin-btn--secondary admin-btn--sm" aria-label="Close">
<i class="fas fa-xmark" aria-hidden="true"></i>
</button>
</div>
<div class="local-dns-modal-body">
<label class="local-dns-form-field">
<span class="local-dns-form-label">Name</span>
<input id="local-name" placeholder="example.tld or host.example.tld" class="theme-input w-full">
</label>
<label class="local-dns-form-field">
<span class="local-dns-form-label">Type</span>
<select id="local-type" onchange="updateLocalForm()" class="theme-input w-full">
<option value="A">A</option>
<option value="AAAA">AAAA</option>
<option value="CNAME">CNAME</option>
<option value="MX">MX</option>
<option value="TXT">TXT</option>
<option value="SRV">SRV</option>
<option value="SOA">SOA</option>
<option value="CAA">CAA</option>
<option value="NS">NS</option>
<option value="PTR">PTR</option>
<option value="OTHER">OTHER</option>
</select>
</label>
<div id="local-value-fields" class="local-dns-dynamic-fields"></div>
<label class="local-dns-form-field">
<span class="local-dns-form-label">TTL (seconds)</span>
<input id="local-ttl" type="number" placeholder="3600" value="3600" class="theme-input w-full" min="0">
</label>
</div>
<div class="local-dns-modal-footer">
<button type="button" onclick="document.getElementById('localDnsModal').close()" class="admin-btn admin-btn--secondary">Cancel</button>
<button type="button" id="local-submit" onclick="submitLocalDns(this)" class="admin-btn admin-btn--primary">Add</button>
</div>
</div>
</dialog>