Files
BridgeSwarm/extension/dashboard.html
T
2026-02-21 05:13:06 -05:00

699 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Dashboard</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0f0f14;
color: #e4e4e7;
min-height: 100vh;
}
/* Sidebar */
.sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 240px;
background: #18181b;
border-right: 1px solid #27272a;
padding: 1.5rem 0;
display: flex;
flex-direction: column;
}
.logo {
padding: 0 1.5rem 1.5rem;
border-bottom: 1px solid #27272a;
margin-bottom: 1rem;
}
.logo h1 {
font-size: 1.25rem;
font-weight: 700;
color: #fff;
display: flex;
align-items: center;
gap: 0.5rem;
}
.logo h1 span {
color: #22d3ee;
}
.logo .version {
font-size: 0.75rem;
color: #71717a;
margin-top: 0.25rem;
}
.nav {
flex: 1;
}
.nav-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1.5rem;
color: #a1a1aa;
text-decoration: none;
font-size: 0.9rem;
transition: all 0.15s;
cursor: pointer;
}
.nav-item:hover {
background: #27272a;
color: #e4e4e7;
}
.nav-item.active {
background: #22d3ee15;
color: #22d3ee;
border-right: 2px solid #22d3ee;
}
.nav-item svg {
width: 18px;
height: 18px;
flex-shrink: 0;
}
.nav-item .badge {
margin-left: auto;
background: #27272a;
padding: 0.125rem 0.5rem;
border-radius: 999px;
font-size: 0.75rem;
}
.nav-item.active .badge {
background: #22d3ee25;
color: #22d3ee;
}
/* Main content */
.main {
margin-left: 240px;
padding: 2rem;
}
.page {
display: none;
}
.page.active {
display: block;
}
/* Status banner */
.status-banner {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.25rem;
border-radius: 8px;
margin-bottom: 2rem;
font-size: 0.9rem;
}
.status-banner.connected {
background: #22d3ee15;
border: 1px solid #22d3ee35;
}
.status-banner.disconnected {
background: #f43f5e15;
border: 1px solid #f43f5e35;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
animation: pulse 2s infinite;
}
.connected .status-dot { background: #22d3ee; }
.disconnected .status-dot { background: #f43f5e; }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Cards */
.card {
background: #18181b;
border: 1px solid #27272a;
border-radius: 12px;
overflow: hidden;
margin-bottom: 1.5rem;
}
.card-header {
padding: 1rem 1.25rem;
border-bottom: 1px solid #27272a;
display: flex;
align-items: center;
justify-content: space-between;
}
.card-title {
font-size: 0.95rem;
font-weight: 600;
color: #fafafa;
}
.card-body {
padding: 1rem 1.25rem;
}
/* Stats grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: #18181b;
border: 1px solid #27272a;
border-radius: 12px;
padding: 1.25rem;
}
.stat-label {
font-size: 0.8rem;
color: #71717a;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: #fff;
}
.stat-value.accent { color: #22d3ee; }
.stat-value.warning { color: #fbbf24; }
.stat-value.danger { color: #f43f5e; }
/* Tables */
.table {
width: 100%;
border-collapse: collapse;
}
.table th,
.table td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid #27272a;
}
.table th {
font-size: 0.75rem;
font-weight: 600;
color: #71717a;
text-transform: uppercase;
letter-spacing: 0.05em;
background: #1f1f23;
}
.table td {
font-size: 0.875rem;
color: #a1a1aa;
}
.table tr:hover td {
background: #27272a;
}
.table .mono {
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
font-size: 0.8rem;
color: #22d3ee;
}
.table .empty {
text-align: center;
color: #52525b;
padding: 2rem;
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
border: none;
transition: all 0.15s;
}
.btn-primary {
background: #22d3ee;
color: #0f0f14;
}
.btn-primary:hover {
background: #06b6d4;
}
.btn-danger {
background: #f43f5e20;
color: #f43f5e;
border: 1px solid #f43f5e35;
}
.btn-danger:hover {
background: #f43f5e35;
}
.btn-sm {
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Tags */
.tag {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
}
.tag-success {
background: #22d3ee20;
color: #22d3ee;
}
.tag-warning {
background: #fbbf2420;
color: #fbbf24;
}
.tag-error {
background: #f43f5e20;
color: #f43f5e;
}
/* Time ago */
.time-ago {
color: #52525b;
font-size: 0.8rem;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #18181b;
}
::-webkit-scrollbar-thumb {
background: #3f3f46;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #52525b;
}
/* Refresh button */
.refresh-btn {
position: fixed;
bottom: 2rem;
right: 2rem;
width: 48px;
height: 48px;
border-radius: 50%;
background: #22d3ee;
color: #0f0f14;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(34, 211, 238, 0.3);
transition: all 0.2s;
}
.refresh-btn:hover {
transform: rotate(180deg);
}
.refresh-btn svg {
width: 24px;
height: 24px;
}
/* Settings section */
.settings-group {
margin-bottom: 2rem;
}
.settings-group h3 {
font-size: 1rem;
font-weight: 600;
color: #fafafa;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #27272a;
}
.setting-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px solid #27272a;
}
.setting-row:last-child {
border-bottom: none;
}
.setting-info {
flex: 1;
}
.setting-label {
font-size: 0.9rem;
color: #e4e4e7;
}
.setting-desc {
font-size: 0.8rem;
color: #71717a;
margin-top: 0.25rem;
}
.toggle {
position: relative;
width: 44px;
height: 24px;
background: #3f3f46;
border-radius: 12px;
cursor: pointer;
transition: background 0.2s;
}
.toggle.active {
background: #22d3ee;
}
.toggle::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: #fff;
border-radius: 50%;
transition: transform 0.2s;
}
.toggle.active::after {
transform: translateX(20px);
}
/* Logs console */
.logs-container {
background: #0d0d0f;
border: 1px solid #27272a;
border-radius: 8px;
height: calc(100vh - 200px);
overflow-y: auto;
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
font-size: 0.8rem;
}
.log-entry {
padding: 0.375rem 0.75rem;
border-bottom: 1px solid #1f1f23;
display: flex;
gap: 0.75rem;
}
.log-entry:hover {
background: #18181b;
}
.log-time {
color: #52525b;
flex-shrink: 0;
}
.log-msg {
color: #a1a1aa;
word-break: break-all;
}
.logs-toolbar {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.logs-toolbar .btn {
font-size: 0.75rem;
padding: 0.375rem 0.75rem;
}
.log-level-info { color: #22d3ee; }
.log-level-warn { color: #fbbf24; }
.log-level-error { color: #f43f5e; }
</style>
</head>
<body>
<!-- Sidebar -->
<nav class="sidebar">
<div class="logo">
<h1>🦅 <span>BridgeSwarm</span></h1>
<div class="version">v1.0.0</div>
</div>
<div class="nav">
<div class="nav-item active" data-page="dashboard">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
<rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
</svg>
Dashboard
</div>
<div class="nav-item" data-page="swarms">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/><circle cx="19" cy="5" r="2"/><circle cx="5" cy="19" r="2"/>
<circle cx="5" cy="5" r="2"/><circle cx="19" cy="19" r="2"/>
<line x1="12" y1="9" x2="12" y2="5"/><line x1="9.5" y1="14.5" x2="6" y2="17"/>
<line x1="14.5" y1="14.5" x2="18" y2="17"/>
</svg>
Swarms
<span class="badge" id="swarmCount">0</span>
</div>
<div class="nav-item" data-page="connections">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
<polyline points="15,3 21,3 21,9"/><line x1="10" y1="14" x2="21" y2="3"/>
</svg>
Connections
<span class="badge" id="connCount">0</span>
</div>
<div class="nav-item" data-page="tabs">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/>
</svg>
Tabs
<span class="badge" id="tabCount">0</span>
</div>
<div class="nav-item" data-page="logs">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14,2 14,8 20,8"/><line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/><polyline points="10,9 9,9 8,9"/>
</svg>
Logs
</div>
<div class="nav-item" data-page="settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
Settings
</div>
</div>
</nav>
<!-- Main Content -->
<main class="main">
<!-- Dashboard Page -->
<div class="page active" id="page-dashboard">
<div class="status-banner" id="statusBanner">
<div class="status-dot"></div>
<span id="statusText">Connecting...</span>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">Active Swarms</div>
<div class="stat-value accent" id="dashSwarms">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Active Connections</div>
<div class="stat-value" id="dashConnections">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Active Tabs</div>
<div class="stat-value" id="dashTabs">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Uptime</div>
<div class="stat-value" id="dashUptime">0s</div>
</div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">Recent Connections</span>
</div>
<div class="card-body" style="padding: 0;">
<table class="table">
<thead>
<tr>
<th>Connection ID</th>
<th>Swarm</th>
<th>Peer Key</th>
<th>Connected</th>
</tr>
</thead>
<tbody id="recentConnections">
<tr><td colspan="4" class="empty">No active connections</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Swarms Page -->
<div class="page" id="page-swarms">
<h2 style="margin-bottom: 1.5rem; font-size: 1.25rem;">Active Swarms</h2>
<div class="card">
<div class="card-body" style="padding: 0;">
<table class="table">
<thead>
<tr>
<th>Swarm ID</th>
<th>App Name</th>
<th>Connections</th>
<th>Tab Count</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="swarmsTable">
<tr><td colspan="5" class="empty">No active swarms</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Connections Page -->
<div class="page" id="page-connections">
<h2 style="margin-bottom: 1.5rem; font-size: 1.25rem;">Active Connections</h2>
<div class="card">
<div class="card-body" style="padding: 0;">
<table class="table">
<thead>
<tr>
<th>Connection ID</th>
<th>Swarm</th>
<th>Peer Key</th>
<th>Connected</th>
</tr>
</thead>
<tbody id="connectionsTable">
<tr><td colspan="4" class="empty">No active connections</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Tabs Page -->
<div class="page" id="page-tabs">
<h2 style="margin-bottom: 1.5rem; font-size: 1.25rem;">Active Tabs</h2>
<div class="card">
<div class="card-body" style="padding: 0;">
<table class="table">
<thead>
<tr>
<th>Tab ID</th>
<th>Swarm IDs</th>
</tr>
</thead>
<tbody id="tabsTable">
<tr><td colspan="2" class="empty">No active tabs</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Settings Page -->
<div class="page" id="page-settings">
<h2 style="margin-bottom: 1.5rem; font-size: 1.25rem;">Extension Settings</h2>
<div class="card">
<div class="card-body">
<div class="settings-group">
<h3>Notifications</h3>
<div class="setting-row">
<div class="setting-info">
<div class="setting-label">Host Disconnect</div>
<div class="setting-desc">Show notification when native host disconnects</div>
</div>
<div class="toggle" id="toggleNotify" data-setting="notifyOnDisconnect"></div>
</div>
<div class="setting-row">
<div class="setting-info">
<div class="setting-label">Debug Mode</div>
<div class="setting-desc">Log events to console for troubleshooting</div>
</div>
<div class="toggle" id="toggleDebug" data-setting="debug"></div>
</div>
</div>
<div class="settings-group">
<h3>Defaults</h3>
<div class="setting-row">
<div class="setting-info">
<div class="setting-label">Default App Name</div>
<div class="setting-desc">Used when page doesn't specify appName</div>
</div>
<input type="text" id="defaultAppName" style="width: 200px; padding: 0.5rem; background: #27272a; border: 1px solid #3f3f46; border-radius: 6px; color: #e4e4e7;">
</div>
<div class="setting-row">
<div class="setting-info">
<div class="setting-label">Default Max Peers</div>
<div class="setting-desc">Maximum peer connections per swarm</div>
</div>
<input type="number" id="defaultMaxPeers" style="width: 100px; padding: 0.5rem; background: #27272a; border: 1px solid #3f3f46; border-radius: 6px; color: #e4e4e7;">
</div>
<div class="setting-row">
<div class="setting-info">
<div class="setting-label">Request Timeout (ms)</div>
<div class="setting-desc">Timeout for WebRTC data channel requests</div>
</div>
<input type="number" id="defaultRequestTimeoutMs" style="width: 100px; padding: 0.5rem; background: #27272a; border: 1px solid #3f3f46; border-radius: 6px; color: #e4e4e7;">
</div>
<div class="setting-row">
<div class="setting-info">
<div class="setting-label">Ready Timeout (ms)</div>
<div class="setting-desc">Timeout for swarm ready event</div>
</div>
<input type="number" id="readyTimeoutMs" style="width: 100px; padding: 0.5rem; background: #27272a; border: 1px solid #3f3f46; border-radius: 6px; color: #e4e4e7;">
</div>
<div class="setting-row">
<div class="setting-info">
<div class="setting-label">Disable on File URLs</div>
<div class="setting-desc">Disable extension on file:// URLs</div>
</div>
<div class="toggle" id="toggleDisableFileUrls"></div>
</div>
</div>
<div style="margin-top: 1.5rem;">
<button class="btn btn-primary" id="btnSaveSettings">Save Settings</button>
</div>
</div>
</div>
</div>
<!-- Logs Page -->
<div class="page" id="page-logs">
<h2 style="margin-bottom: 1rem; font-size: 1.25rem;">System Logs</h2>
<div class="logs-toolbar">
<button class="btn btn-primary" id="btnClearLogs">Clear Logs</button>
<button class="btn btn-secondary" id="btnAutoScroll">Auto-scroll: ON</button>
</div>
<div class="logs-container" id="logsContainer">
<div class="empty" style="padding: 2rem; text-align: center; color: #52525b;">No logs yet</div>
</div>
</div>
</main>
<!-- Refresh Button -->
<button class="refresh-btn" id="refreshBtn" title="Refresh">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23,4 23,10 17,10"/><polyline points="1,20 1,14 7,14"/>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
</svg>
</button>
<script src="dashboard.js"></script>
</body>
</html>