forked from snxraven/p2ns
Full Redesign of p2ns.admin
This commit is contained in:
@@ -1,75 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<html lang="en" class="dark" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#0f172a">
|
||||
<meta name="theme-color" content="#0a0e1a">
|
||||
<title>P2NS Admin Panel</title>
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/tailwind.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
body.admin-app {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #0a0e1a 0%, #0f1419 50%, #1a1f2e 100%);
|
||||
background-attachment: fixed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Disable body scrolling for fixed-height tabs */
|
||||
body.no-scroll {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
body.admin-app.no-scroll {
|
||||
overflow: hidden;
|
||||
height: 100dvh;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/css/xterm.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
|
||||
</head>
|
||||
<body style="color: var(--text-primary);">
|
||||
<div class="container mx-auto max-w-7xl">
|
||||
<div class="flex justify-between items-center mb-8 flex-wrap gap-4">
|
||||
<h1 class="text-4xl font-extrabold" style="background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">P2NS Admin Panel</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<div id="status-indicator" class="px-4 py-2 rounded-lg"></div>
|
||||
<button id="refresh-button" onclick="handleRefresh()" class="px-3 py-2 bg-primary hover:bg-primary-hover text-white rounded-lg transition-colors" title="Gracefully stop the process">
|
||||
<i class="fas fa-redo"></i>
|
||||
</button>
|
||||
<body class="admin-app" style="color: var(--text-primary);">
|
||||
<div class="admin-shell">
|
||||
<aside id="admin-sidebar" class="admin-sidebar" aria-label="Main navigation">
|
||||
<div class="admin-sidebar-brand">
|
||||
<span class="admin-sidebar-brand-title">P2NS</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="flex justify-center mb-8 space-x-4 flex-wrap">
|
||||
<!-- Core Management -->
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('domains'); else location.hash = 'domains';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Domains</button>
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('host'); else location.hash = 'host';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Host</button>
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('entries'); else location.hash = 'entries';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Entries</button>
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('peers'); else location.hash = 'peers';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Peers</button>
|
||||
<!-- Network -->
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('local-dns'); else location.hash = 'local-dns';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Local DNS</button>
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('interfaces'); else location.hash = 'interfaces';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Interfaces</button>
|
||||
<!-- Security -->
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('certs'); else location.hash = 'certs';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Certificates</button>
|
||||
<!-- Monitoring -->
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('stats'); else location.hash = 'stats';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Stats</button>
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('logs'); else location.hash = 'logs';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Logs</button>
|
||||
<!-- Configuration -->
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('backups'); else location.hash = 'backups';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Backups</button>
|
||||
<!-- Plugins -->
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('plugins'); else location.hash = 'plugins';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Plugins</button>
|
||||
<button onclick="if(window.navigateToTab) window.navigateToTab('settings'); else location.hash = 'settings';" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover m-1">Settings</button>
|
||||
</nav>
|
||||
|
||||
<div id="domains" class="tab-content hidden flex flex-col" style="height: calc(100vh - 200px); max-height: calc(100vh - 200px);">
|
||||
<nav class="admin-sidebar-nav">
|
||||
<div class="admin-nav-section">
|
||||
<p class="admin-nav-section-label">Core</p>
|
||||
<button type="button" data-tab-id="domains" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('domains'); else location.hash = 'domains';"><i class="fas fa-globe admin-nav-icon" aria-hidden="true"></i>Domains</button>
|
||||
<button type="button" data-tab-id="host" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('host'); else location.hash = 'host';"><i class="fas fa-server admin-nav-icon" aria-hidden="true"></i>Host</button>
|
||||
<button type="button" data-tab-id="entries" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('entries'); else location.hash = 'entries';"><i class="fas fa-list admin-nav-icon" aria-hidden="true"></i>Entries</button>
|
||||
<button type="button" data-tab-id="peers" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('peers'); else location.hash = 'peers';"><i class="fas fa-network-wired admin-nav-icon" aria-hidden="true"></i>Peers</button>
|
||||
</div>
|
||||
<div class="admin-nav-section">
|
||||
<p class="admin-nav-section-label">Network</p>
|
||||
<button type="button" data-tab-id="local-dns" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('local-dns'); else location.hash = 'local-dns';"><i class="fas fa-sitemap admin-nav-icon" aria-hidden="true"></i>Local DNS</button>
|
||||
<button type="button" data-tab-id="interfaces" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('interfaces'); else location.hash = 'interfaces';"><i class="fas fa-ethernet admin-nav-icon" aria-hidden="true"></i>Interfaces</button>
|
||||
</div>
|
||||
<div class="admin-nav-section">
|
||||
<p class="admin-nav-section-label">Security</p>
|
||||
<button type="button" data-tab-id="certs" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('certs'); else location.hash = 'certs';"><i class="fas fa-certificate admin-nav-icon" aria-hidden="true"></i>Certificates</button>
|
||||
</div>
|
||||
<div class="admin-nav-section">
|
||||
<p class="admin-nav-section-label">Monitoring</p>
|
||||
<button type="button" data-tab-id="stats" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('stats'); else location.hash = 'stats';"><i class="fas fa-chart-line admin-nav-icon" aria-hidden="true"></i>Stats</button>
|
||||
<button type="button" data-tab-id="logs" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('logs'); else location.hash = 'logs';"><i class="fas fa-terminal admin-nav-icon" aria-hidden="true"></i>Logs</button>
|
||||
</div>
|
||||
<div class="admin-nav-section">
|
||||
<p class="admin-nav-section-label">System</p>
|
||||
<button type="button" data-tab-id="backups" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('backups'); else location.hash = 'backups';"><i class="fas fa-database admin-nav-icon" aria-hidden="true"></i>Backups</button>
|
||||
<button type="button" data-tab-id="plugins" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('plugins'); else location.hash = 'plugins';"><i class="fas fa-puzzle-piece admin-nav-icon" aria-hidden="true"></i>Plugins</button>
|
||||
<button type="button" data-tab-id="settings" class="admin-nav-link" onclick="if(window.navigateToTab) window.navigateToTab('settings'); else location.hash = 'settings';"><i class="fas fa-cog admin-nav-icon" aria-hidden="true"></i>Settings</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="admin-sidebar-footer">
|
||||
<span class="admin-sidebar-footer-text">P2NS Network</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div id="admin-sidebar-overlay" class="admin-sidebar-overlay" onclick="if(window.closeAdminSidebar) window.closeAdminSidebar();" aria-hidden="true"></div>
|
||||
|
||||
<div class="admin-main">
|
||||
<header class="admin-topbar">
|
||||
<button type="button" id="admin-sidebar-toggle" class="admin-sidebar-toggle" onclick="if(window.toggleAdminSidebar) window.toggleAdminSidebar();" aria-label="Toggle navigation menu" aria-expanded="false">
|
||||
<i class="fas fa-bars" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="admin-topbar-spacer"></div>
|
||||
<div id="status-indicator" class="admin-status-indicator"></div>
|
||||
<div class="admin-alerts-wrap">
|
||||
<button type="button" id="admin-alerts-button" class="admin-topbar-action admin-alerts-bell" title="Alerts" aria-label="Alerts" aria-expanded="false" aria-haspopup="true">
|
||||
<i class="fas fa-bell" aria-hidden="true"></i>
|
||||
<span id="admin-alerts-badge" class="admin-alerts-badge">0</span>
|
||||
</button>
|
||||
<div id="admin-alerts-panel" class="admin-alerts-panel" role="dialog" aria-label="Alerts">
|
||||
<header class="admin-alerts-panel-header">
|
||||
<h3 class="admin-alerts-panel-title">Alerts</h3>
|
||||
<button type="button" class="admin-alerts-panel-close" onclick="if(window.closeAdminAlertsPanel) window.closeAdminAlertsPanel();" aria-label="Close alerts">
|
||||
<i class="fas fa-xmark" aria-hidden="true"></i>
|
||||
</button>
|
||||
</header>
|
||||
<div id="admin-alerts-empty" class="admin-alerts-empty admin-alerts-empty--visible">
|
||||
<i class="fas fa-bell-slash admin-alerts-empty-icon" aria-hidden="true"></i>
|
||||
<p class="admin-alerts-empty-title">All clear</p>
|
||||
<p class="admin-alerts-empty-text theme-text-tertiary">No active warnings or issues.</p>
|
||||
</div>
|
||||
<div id="admin-alerts-list" class="admin-alerts-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="refresh-button" onclick="handleRefresh()" class="admin-topbar-action" title="Gracefully stop the process" aria-label="Refresh">
|
||||
<i class="fas fa-redo" aria-hidden="true"></i>
|
||||
</button>
|
||||
</header>
|
||||
<div id="admin-content" class="admin-content admin-content--fill">
|
||||
<div id="domains" 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" style="color: var(--text-primary);">
|
||||
Domains
|
||||
<button onclick="openInfoModal('domains')" class="text-sm px-3 py-1 rounded transition-colors" style="background: var(--bg-glass); border: 1px solid var(--border-color); color: var(--text-secondary);" onmouseover="this.style.background='var(--bg-glass-hover)'" onmouseout="this.style.background='var(--bg-glass)'">Info</button>
|
||||
<button onclick="openInfoModal('domains')" class="theme-button-info text-sm px-3 py-1 rounded">Info</button>
|
||||
</h2>
|
||||
<div class="mb-6 flex-shrink-0">
|
||||
<input id="search-domains" type="text" placeholder="Search domains..." class="w-full p-3 rounded-lg focus:outline-none transition-all mb-3" style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary);" onfocus="this.style.borderColor='var(--primary)'; this.style.boxShadow='0 0 0 3px rgba(99, 102, 241, 0.1)'" onblur="this.style.borderColor='var(--border-color)'; this.style.boxShadow='none'" oninput="filterDomains()">
|
||||
<input id="search-domains" type="text" placeholder="Search domains..." class="theme-input w-full p-3 rounded-lg focus:outline-none mb-3" oninput="filterDomains()">
|
||||
<div class="flex gap-3 flex-wrap">
|
||||
<select id="filter-consensus-status" onchange="filterDomains(); saveDomainFilterSettings()" class="flex-1 min-w-[150px] p-3 rounded-lg focus:outline-none transition-all" style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary);" onfocus="this.style.borderColor='var(--primary)'; this.style.boxShadow='0 0 0 3px rgba(99, 102, 241, 0.1)'" onblur="this.style.borderColor='var(--border-color)'; this.style.boxShadow='none'">
|
||||
<select id="filter-consensus-status" onchange="filterDomains(); saveDomainFilterSettings()" class="theme-input flex-1 min-w-[150px] p-3 rounded-lg focus:outline-none">
|
||||
<option value="all" selected>All Consensus Status</option>
|
||||
<option value="resolved">Resolved</option>
|
||||
<option value="internal">Internal</option>
|
||||
@@ -80,12 +125,12 @@
|
||||
<option value="error">Error</option>
|
||||
<option value="unknown">Unknown</option>
|
||||
</select>
|
||||
<select id="filter-hash-type" onchange="filterDomains(); saveDomainFilterSettings()" class="flex-1 min-w-[150px] p-3 rounded-lg focus:outline-none transition-all" style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary);" onfocus="this.style.borderColor='var(--primary)'; this.style.boxShadow='0 0 0 3px rgba(99, 102, 241, 0.1)'" onblur="this.style.borderColor='var(--border-color)'; this.style.boxShadow='none'">
|
||||
<select id="filter-hash-type" onchange="filterDomains(); saveDomainFilterSettings()" class="theme-input flex-1 min-w-[150px] p-3 rounded-lg focus:outline-none">
|
||||
<option value="all">All Hash Types</option>
|
||||
<option value="internal">Internal</option>
|
||||
<option value="holesail" selected>Holesail Hash</option>
|
||||
</select>
|
||||
<select id="filter-ownership" onchange="filterDomains(); saveDomainFilterSettings()" class="flex-1 min-w-[150px] p-3 rounded-lg focus:outline-none transition-all" style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary);" onfocus="this.style.borderColor='var(--primary)'; this.style.boxShadow='0 0 0 3px rgba(99, 102, 241, 0.1)'" onblur="this.style.borderColor='var(--border-color)'; this.style.boxShadow='none'">
|
||||
<select id="filter-ownership" onchange="filterDomains(); saveDomainFilterSettings()" class="theme-input flex-1 min-w-[150px] p-3 rounded-lg focus:outline-none">
|
||||
<option value="all" selected>All Ownership</option>
|
||||
<option value="local">Local</option>
|
||||
<option value="remote">Remote</option>
|
||||
@@ -93,8 +138,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto overflow-y-auto flex-1 min-h-0">
|
||||
<table class="w-full rounded-lg" style="background: var(--bg-glass); backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%); border: 1px solid var(--border-color); box-shadow: var(--shadow-lg);">
|
||||
<thead class="sticky top-0 z-10" style="background: var(--bg-secondary); backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%);">
|
||||
<table class="theme-table w-full rounded-lg">
|
||||
<thead class="sticky top-0 z-10">
|
||||
<tr>
|
||||
<th class="p-3 text-left">Domain</th>
|
||||
<th class="p-3 text-left">Hash</th>
|
||||
@@ -112,14 +157,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="local-dns" class="tab-content hidden flex flex-col" style="height: calc(100vh - 200px); max-height: calc(100vh - 200px);">
|
||||
<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="flex gap-2 mb-4 flex-shrink-0">
|
||||
<button onclick="showSubTab('local-dns', 'records')" id="local-dns-subtab-records" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover">DNS Records</button>
|
||||
<button onclick="showSubTab('local-dns', 'conflicts')" id="local-dns-subtab-conflicts" class="px-4 py-2 theme-button-info rounded transition-colors">DNS Conflicts</button>
|
||||
<button onclick="showSubTab('local-dns', 'p2p-conflicts')" id="local-dns-subtab-p2p-conflicts" class="px-4 py-2 theme-button-info rounded transition-colors">P2P Conflicts</button>
|
||||
<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 -->
|
||||
@@ -204,7 +249,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="entries" class="tab-content hidden flex flex-col" style="height: calc(100vh - 250px); max-height: calc(100vh - 250px);">
|
||||
<div id="entries" 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">
|
||||
Autopass Entries (<span id="entries-count">0</span>)
|
||||
<button onclick="openInfoModal('entries')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
|
||||
@@ -227,7 +272,7 @@
|
||||
<div id="entriesPagination" class="hidden"></div>
|
||||
</div>
|
||||
|
||||
<div id="peers" class="tab-content hidden flex flex-col" style="height: calc(100vh - 200px); max-height: calc(100vh - 200px);">
|
||||
<div id="peers" 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">
|
||||
Connected Peers <span id="peers-count" class="text-lg"></span>
|
||||
<button onclick="openInfoModal('peers')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
|
||||
@@ -252,59 +297,180 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="certs" class="tab-content hidden flex flex-col" style="height: calc(100vh - 200px); max-height: calc(100vh - 200px);">
|
||||
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2 flex-shrink-0">
|
||||
Domain Certificates
|
||||
<button onclick="openInfoModal('certs')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
|
||||
</h2>
|
||||
<div class="mb-6 flex-shrink-0">
|
||||
<input id="search-certs" type="text" placeholder="Search certificates..." class="w-full p-3 rounded-lg theme-input focus:outline-none focus:ring-2 focus:ring-primary" oninput="filterCerts()">
|
||||
</div>
|
||||
<div class="overflow-y-auto flex-1 min-h-0">
|
||||
<ul id="certsList" class="space-y-3"></ul>
|
||||
<div id="certsScrollSentinel" class="scroll-sentinel"></div>
|
||||
</div>
|
||||
<div id="certsPagination" class="flex justify-center mt-4 space-x-2 flex-shrink-0"></div>
|
||||
<div class="mt-4 flex-shrink-0">
|
||||
<input id="cert-domain" placeholder="Domain for Cert" class="p-3 theme-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
<button onclick="generateCert()" class="ml-2 px-4 py-3 bg-primary text-white rounded-lg hover:bg-primary-hover">Generate Cert</button>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold mt-8 mb-4 flex items-center gap-2 flex-shrink-0">
|
||||
CA Management
|
||||
<button onclick="openInfoModal('ca-management')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
|
||||
</h2>
|
||||
<div class="flex-shrink-0">
|
||||
<button onclick="regenerateCA()" class="px-6 py-3 bg-primary text-white rounded-lg hover:bg-primary-hover mr-4">Regenerate Root CA</button>
|
||||
<button onclick="installCA()" class="px-6 py-3 bg-primary text-white rounded-lg hover:bg-primary-hover">Install Root CA</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="interfaces" class="tab-content hidden flex flex-col" style="height: calc(100vh - 200px); max-height: calc(100vh - 200px);">
|
||||
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2 flex-shrink-0">
|
||||
Virtual Interfaces
|
||||
<button onclick="openInfoModal('interfaces')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
|
||||
</h2>
|
||||
<div class="mb-6 flex-shrink-0">
|
||||
<input id="search-interfaces" type="text" placeholder="Search interfaces..." class="w-full p-3 rounded-lg theme-input focus:outline-none focus:ring-2 focus:ring-primary" oninput="filterInterfaces()">
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden flex flex-col min-h-0">
|
||||
<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">IP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="interfacesTable"></tbody>
|
||||
</table>
|
||||
<div id="interfacesScrollSentinel" class="scroll-sentinel"></div>
|
||||
<div id="certs" class="tab-content hidden flex flex-col admin-tab-panel--fixed">
|
||||
<header class="admin-tab-header flex-shrink-0">
|
||||
<div class="admin-tab-header-main">
|
||||
<h2 class="admin-tab-title">Certificates</h2>
|
||||
<p class="admin-tab-description">Manage TLS certificates for domains and the local root certificate authority.</p>
|
||||
</div>
|
||||
<div id="interfacesPagination" class="flex justify-center mt-4 space-x-2 flex-shrink-0"></div>
|
||||
<button type="button" onclick="openInfoModal('certs')" class="theme-button-info text-sm px-3 py-1 rounded">Info</button>
|
||||
</header>
|
||||
|
||||
<div class="certs-layout flex-1 min-h-0 grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
<section class="theme-card admin-panel certs-panel lg:col-span-2 flex flex-col min-h-0">
|
||||
<div class="admin-panel-header">
|
||||
<h3 class="admin-panel-title">Domain certificates</h3>
|
||||
<p class="admin-panel-subtitle">Click a domain to view PEM details.</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-certs" type="search" placeholder="Search certificates…" class="theme-input admin-search-input" oninput="filterCerts()" autocomplete="off">
|
||||
</label>
|
||||
</div>
|
||||
<div class="cert-list-scroll flex-1 min-h-0 overflow-y-auto">
|
||||
<ul id="certsList" class="cert-list"></ul>
|
||||
<div id="certsEmpty" class="cert-empty">
|
||||
<i class="fas fa-certificate cert-empty-icon" aria-hidden="true"></i>
|
||||
<p class="cert-empty-title">No certificates yet</p>
|
||||
<p class="cert-empty-text theme-text-tertiary">Generate a certificate for a domain below.</p>
|
||||
</div>
|
||||
<div id="certsEnd" class="cert-list-end" aria-live="polite">You have reached the end</div>
|
||||
<div id="certsScrollSentinel" class="scroll-sentinel"></div>
|
||||
</div>
|
||||
<div id="certsPagination" class="hidden"></div>
|
||||
<div class="admin-panel-footer cert-generate-bar flex-shrink-0">
|
||||
<label class="cert-generate-field">
|
||||
<span class="cert-generate-label">Domain</span>
|
||||
<input id="cert-domain" type="text" placeholder="example.p2p" class="theme-input cert-generate-input" autocomplete="off">
|
||||
</label>
|
||||
<button type="button" onclick="generateCert()" class="admin-btn admin-btn--primary">
|
||||
<i class="fas fa-plus" aria-hidden="true"></i> Generate certificate
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="theme-card admin-panel cert-ca-panel flex flex-col">
|
||||
<div class="admin-panel-header">
|
||||
<h3 class="admin-panel-title">Root CA</h3>
|
||||
<p class="admin-panel-subtitle">Signs all domain certificates on this node.</p>
|
||||
</div>
|
||||
<div class="cert-ca-note">
|
||||
<i class="fas fa-triangle-exclamation" aria-hidden="true"></i>
|
||||
<p>Regenerating the root CA invalidates every existing certificate. You will need to reinstall the CA and regenerate domain certs.</p>
|
||||
</div>
|
||||
<div class="cert-ca-actions">
|
||||
<button type="button" onclick="openInfoModal('ca-management')" class="admin-btn admin-btn--secondary admin-btn--sm w-full">
|
||||
<i class="fas fa-circle-info" aria-hidden="true"></i> About CA management
|
||||
</button>
|
||||
<button type="button" onclick="installCA()" class="admin-btn admin-btn--primary w-full">
|
||||
<i class="fas fa-download" aria-hidden="true"></i> Install root CA
|
||||
</button>
|
||||
<button type="button" onclick="regenerateCA()" class="admin-btn admin-btn--danger w-full">
|
||||
<i class="fas fa-rotate-right" aria-hidden="true"></i> Regenerate root CA
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="logs" class="tab-content hidden" style="height: calc(100vh - 200px); max-height: calc(100vh - 200px);">
|
||||
<div id="interfaces" class="tab-content hidden flex flex-col admin-tab-panel--fixed">
|
||||
<header class="admin-tab-header flex-shrink-0">
|
||||
<div class="admin-tab-header-main">
|
||||
<h2 class="admin-tab-title">Virtual Interfaces</h2>
|
||||
<p class="admin-tab-description">Domain-to-IP mappings for local routing, subnet usage, and tunnel bindings.</p>
|
||||
</div>
|
||||
<button type="button" onclick="openInfoModal('interfaces')" class="theme-button-info text-sm px-3 py-1 rounded">Info</button>
|
||||
</header>
|
||||
|
||||
<div class="interfaces-layout flex-1 min-h-0 grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
<section class="theme-card admin-panel interfaces-panel lg:col-span-2 flex flex-col min-h-0">
|
||||
<div class="admin-panel-header">
|
||||
<h3 class="admin-panel-title">Domain mappings</h3>
|
||||
<p class="admin-panel-subtitle"><span id="interfacesCount">0</span> assigned</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-interfaces" type="search" placeholder="Search by domain, IP, or subnet…" class="theme-input admin-search-input" oninput="filterInterfaces()" autocomplete="off">
|
||||
</label>
|
||||
</div>
|
||||
<div class="interfaces-table-scroll flex-1 min-h-0 overflow-auto">
|
||||
<table class="w-full theme-table interfaces-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">IP address</th>
|
||||
<th class="p-3 text-left">Type</th>
|
||||
<th class="p-3 text-left">Subnet</th>
|
||||
<th class="p-3 text-left">Status</th>
|
||||
<th class="p-3 text-left">Tunnels</th>
|
||||
<th class="p-3 text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="interfacesTable"></tbody>
|
||||
</table>
|
||||
<div id="interfacesEmpty" class="iface-empty">
|
||||
<i class="fas fa-ethernet iface-empty-icon" aria-hidden="true"></i>
|
||||
<p class="iface-empty-title">No interface mappings</p>
|
||||
<p class="iface-empty-text theme-text-tertiary">Domains receive virtual IPs when resolved or when Holesail clients connect.</p>
|
||||
</div>
|
||||
<div id="interfacesFilteredEmpty" class="iface-empty">
|
||||
<i class="fas fa-filter iface-empty-icon" aria-hidden="true"></i>
|
||||
<p class="iface-empty-title">No matching mappings</p>
|
||||
<p class="iface-empty-text theme-text-tertiary">Try a different search term.</p>
|
||||
</div>
|
||||
<div id="interfacesEnd" class="iface-list-end" aria-live="polite">You have reached the end</div>
|
||||
<div id="interfacesScrollSentinel" class="scroll-sentinel"></div>
|
||||
</div>
|
||||
<div id="interfacesPagination" class="hidden"></div>
|
||||
</section>
|
||||
|
||||
<aside class="interfaces-sidebar flex flex-col gap-4 min-h-0">
|
||||
<section class="theme-card admin-panel interfaces-summary-panel">
|
||||
<div class="admin-panel-header">
|
||||
<h3 class="admin-panel-title">Overview</h3>
|
||||
<p class="admin-panel-subtitle" id="interfacesStatusLine">Loading…</p>
|
||||
</div>
|
||||
<dl class="iface-stat-grid" id="interfacesStatGrid">
|
||||
<div class="iface-stat">
|
||||
<dt>Mappings</dt>
|
||||
<dd id="ifaceStatMappings">—</dd>
|
||||
</div>
|
||||
<div class="iface-stat">
|
||||
<dt>On system</dt>
|
||||
<dd id="ifaceStatConfigured">—</dd>
|
||||
</div>
|
||||
<div class="iface-stat">
|
||||
<dt>Tunnel links</dt>
|
||||
<dd id="ifaceStatConnections">—</dd>
|
||||
</div>
|
||||
<div class="iface-stat">
|
||||
<dt>Clients</dt>
|
||||
<dd id="ifaceStatClients">—</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="theme-card admin-panel interfaces-subnets-panel flex flex-col min-h-0">
|
||||
<div class="admin-panel-header">
|
||||
<h3 class="admin-panel-title">Subnets</h3>
|
||||
<p class="admin-panel-subtitle">Capacity across configured ranges</p>
|
||||
</div>
|
||||
<div id="interfacesSubnetsList" class="iface-subnets-list flex-1 min-h-0 overflow-y-auto">
|
||||
<p class="iface-subnets-empty theme-text-tertiary">No subnet configuration</p>
|
||||
</div>
|
||||
<div class="admin-panel-footer">
|
||||
<button type="button" onclick="if(window.navigateToTab) window.navigateToTab('settings'); else location.hash='settings';" class="admin-btn admin-btn--secondary admin-btn--sm w-full">
|
||||
<i class="fas fa-cog" aria-hidden="true"></i> Subnet settings
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="theme-card admin-panel interfaces-actions-panel">
|
||||
<div class="admin-panel-header">
|
||||
<h3 class="admin-panel-title">Maintenance</h3>
|
||||
<p class="admin-panel-subtitle">Stop tunnels and remove virtual IPs from the loopback interface.</p>
|
||||
</div>
|
||||
<button type="button" onclick="cleanupInterfaces()" class="admin-btn admin-btn--danger w-full">
|
||||
<i class="fas fa-broom" aria-hidden="true"></i> Cleanup interfaces
|
||||
</button>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="logs" class="tab-content hidden admin-tab-panel--fixed flex flex-col">
|
||||
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
Logs
|
||||
<button onclick="openInfoModal('logs')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
|
||||
@@ -353,13 +519,13 @@
|
||||
<div id="terminal" class="bg-black rounded-lg overflow-hidden h-96"></div>
|
||||
</div>
|
||||
|
||||
<div id="host" class="tab-content hidden flex flex-col" style="height: calc(100vh - 200px); max-height: calc(100vh - 200px);">
|
||||
<div id="host" 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">
|
||||
Holesail
|
||||
</h2>
|
||||
<div class="flex gap-2 mb-4 flex-shrink-0">
|
||||
<button onclick="showSubTab('host', 'servers')" id="host-subtab-servers" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover">Servers</button>
|
||||
<button onclick="showSubTab('host', 'clients')" id="host-subtab-clients" class="px-4 py-2 theme-button-info rounded transition-colors">Clients</button>
|
||||
<div class="admin-subtabs flex gap-2 mb-4 flex-shrink-0">
|
||||
<button onclick="showSubTab('host', 'servers')" id="host-subtab-servers" class="admin-subtab-btn admin-subtab-btn--active px-4 py-2 rounded transition-colors">Servers</button>
|
||||
<button onclick="showSubTab('host', 'clients')" id="host-subtab-clients" class="admin-subtab-btn px-4 py-2 rounded transition-colors">Clients</button>
|
||||
</div>
|
||||
|
||||
<!-- Servers Sub-tab -->
|
||||
@@ -427,7 +593,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings" class="tab-content hidden">
|
||||
<div id="settings" class="tab-content hidden admin-tab-panel--scroll">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-bold flex items-center gap-2">
|
||||
Settings
|
||||
@@ -441,7 +607,7 @@
|
||||
<div id="settingsContainer" class="space-y-6"></div>
|
||||
</div>
|
||||
|
||||
<div id="plugins" class="tab-content hidden">
|
||||
<div id="plugins" class="tab-content hidden admin-tab-panel--scroll">
|
||||
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
Plugins
|
||||
<button onclick="openInfoModal('plugins')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
|
||||
@@ -452,7 +618,7 @@
|
||||
<div id="pluginsContainer" class="space-y-6"></div>
|
||||
</div>
|
||||
|
||||
<div id="backups" class="tab-content hidden flex flex-col" style="height: calc(100vh - 200px); max-height: calc(100vh - 200px);">
|
||||
<div id="backups" 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">
|
||||
Backup & Restore
|
||||
<button onclick="openInfoModal('backups')" class="text-sm px-3 py-1 theme-button-info rounded transition-colors">Info</button>
|
||||
@@ -479,7 +645,7 @@
|
||||
<button id="create-backup-btn" onclick="createBackup(this)" class="mt-4 px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover flex-shrink-0">Create Backup</button>
|
||||
</div>
|
||||
|
||||
<div id="stats" class="tab-content hidden">
|
||||
<div id="stats" class="tab-content hidden admin-tab-panel--scroll">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-2xl font-bold flex items-center gap-2">
|
||||
Statistics & Metrics
|
||||
@@ -502,7 +668,7 @@
|
||||
<option value="360">Last 6 hours</option>
|
||||
<option value="1440" selected>Last 24 hours</option>
|
||||
</select>
|
||||
<button onclick="exportStats()" class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700">Export Data</button>
|
||||
<button onclick="exportStats()" class="px-4 py-2 theme-button-info rounded">Export Data</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1017,7 +1183,7 @@
|
||||
<p class="text-sm text-gray-400">Diagnose RPC invite flow (invite.request, invite.deliver, invite.ack on p2ns.core-request-rpc), peer connections, pending acks, and master queue state.</p>
|
||||
<div class="flex space-x-2">
|
||||
<button onclick="runInviteDiagnostics()" class="flex-1 px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover">Run Invite Diagnostics</button>
|
||||
<button onclick="cleanDnsPassStorage()" class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700">🗑️ Clean & Restart</button>
|
||||
<button onclick="cleanDnsPassStorage()" class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700">Clean & Restart</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1030,7 +1196,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog id="addDomainModal" class="p-6 theme-glass rounded-lg shadow-xl w-full max-w-md">
|
||||
<h3 class="text-xl font-bold mb-4">Add New Domain</h3>
|
||||
<input id="modal-domain" placeholder="Domain" class="w-full p-3 mb-4 theme-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
@@ -1070,18 +1239,23 @@
|
||||
</dialog>
|
||||
|
||||
<dialog id="certDetailsModal" class="p-6 theme-glass rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-2xl font-bold theme-text-primary">Certificate Details</h3>
|
||||
<button onclick="document.getElementById('certDetailsModal').close()" class="theme-text-tertiary hover:text-gray-700 dark:hover:text-gray-200 text-3xl leading-none font-bold">×</button>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto mb-4">
|
||||
<div class="rounded-lg p-4 theme-input" style="background: rgba(15, 20, 30, 0.6); backdrop-filter: blur(25px) saturate(200%); -webkit-backdrop-filter: blur(25px) saturate(200%);">
|
||||
<pre id="cert-details-content" class="whitespace-pre-wrap break-all text-sm font-mono theme-text-primary leading-relaxed"></pre>
|
||||
<div class="flex justify-between items-start gap-4 mb-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold theme-text-primary">Certificate details</h3>
|
||||
<p id="cert-details-domain" class="text-sm theme-text-tertiary mt-1 font-mono break-all"></p>
|
||||
</div>
|
||||
<button type="button" onclick="document.getElementById('certDetailsModal').close()" class="admin-btn admin-btn--secondary admin-btn--sm cert-modal-close" aria-label="Close">
|
||||
<i class="fas fa-xmark" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-2 pt-4 border-t border-color: var(--border-color);">
|
||||
<button onclick="copyCertDetails()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors">Copy</button>
|
||||
<button onclick="document.getElementById('certDetailsModal').close()" class="px-4 py-2 theme-button-info theme-text-primary rounded theme-glass-hover transition-colors">Close</button>
|
||||
<div class="flex-1 overflow-y-auto mb-4 min-h-0">
|
||||
<pre id="cert-details-content" class="cert-details-pre whitespace-pre-wrap break-all text-sm font-mono theme-text-primary leading-relaxed"></pre>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 pt-4 border-t" style="border-color: var(--border-color);">
|
||||
<button type="button" onclick="copyCertDetails()" class="admin-btn admin-btn--secondary">
|
||||
<i class="fas fa-copy" aria-hidden="true"></i> Copy
|
||||
</button>
|
||||
<button type="button" onclick="document.getElementById('certDetailsModal').close()" class="admin-btn admin-btn--primary">Close</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
@@ -1180,12 +1354,12 @@
|
||||
</dialog>
|
||||
|
||||
<dialog id="resetIdentityModal" class="p-6 theme-glass rounded-lg shadow-xl w-full max-w-md">
|
||||
<h3 class="text-xl font-bold mb-4 text-red-600 dark:text-red-400">⚠️ Reset Identity</h3>
|
||||
<h3 class="text-lg font-semibold mb-4 text-red-400"><i class="fas fa-triangle-exclamation mr-2" aria-hidden="true"></i>Reset Identity</h3>
|
||||
<p class="text-sm theme-text-primary mb-3">
|
||||
<strong>Warning:</strong> This action will permanently delete your current identity keypair (<code class="theme-glass px-1 rounded">cache/keypair.json</code>) and generate a new one.
|
||||
</p>
|
||||
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3 mb-3">
|
||||
<p class="text-sm text-red-800 dark:text-red-300 font-semibold mb-2">⚠️ This action cannot be undone!</p>
|
||||
<p class="text-sm text-red-300 font-semibold mb-2">This action cannot be undone.</p>
|
||||
<ul class="text-xs text-red-700 dark:text-red-400 list-disc list-inside space-y-1">
|
||||
<li>You will lose access to all domains you have claimed</li>
|
||||
<li>Your claims will become unrecoverable unless network consensus allows you to reclaim them</li>
|
||||
@@ -1262,8 +1436,7 @@
|
||||
<button id="dns-server-submit" onclick="submitDnsServer(this)" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover">Add</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="notifications" class="fixed bottom-4 right-4 flex flex-col-reverse space-y-2" style="z-index: 99999;"></div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm.js"></script>
|
||||
@@ -1274,6 +1447,8 @@
|
||||
<script src="utils.js"></script>
|
||||
<script src="ui/core.js"></script>
|
||||
<script src="ui/notifications.js"></script>
|
||||
<script src="ui/alerts.js"></script>
|
||||
<script src="ui/alerts-sync.js"></script>
|
||||
<script src="ui/confirmation-modal.js"></script>
|
||||
<script src="ws-client.js"></script>
|
||||
<script src="ui/domains.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user