CI / Build & Test (push) Failing after 2m44s
- Add "Use TLS (secure connection)" checkbox in Add Virtual Host modal - Persist and restore useTls in state; show TLS badge in table - When enabled, HTTPS proxy connects to tunnel backend over TLS (SNI = hostname) for HTTP and WebSocket; supports services on port 443
1308 lines
71 KiB
HTML
1308 lines
71 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Holesail – Dashboard</title>
|
||
<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&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||
<link rel="manifest" href="manifest.webmanifest">
|
||
<link rel="stylesheet" href="../vendor/xterm.css">
|
||
<link rel="stylesheet" href="dashboard.css">
|
||
<script src="../vendor/xterm.js"></script>
|
||
<script src="../vendor/xterm-addon-fit.js"></script>
|
||
<script type="module" src="../vendor/novnc.js"></script>
|
||
</head>
|
||
<body>
|
||
<div class="layout">
|
||
|
||
<!-- ── Sidebar ──────────────────────────────────── -->
|
||
<nav class="sidebar">
|
||
<div class="sidebar-logo">
|
||
<div class="sidebar-logo-icon"><img src="../icons/32.png" alt="Holesail"></div>
|
||
<div class="sidebar-logo-text">
|
||
<div class="sidebar-logo-name">Holesail</div>
|
||
<div class="sidebar-logo-version" id="sidebarVersion">v1.0.0 · hole.sail</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sidebar-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.5"/>
|
||
<rect x="14" y="3" width="7" height="7" rx="1.5"/>
|
||
<rect x="3" y="14" width="7" height="7" rx="1.5"/>
|
||
<rect x="14" y="14" width="7" height="7" rx="1.5"/>
|
||
</svg>
|
||
Overview
|
||
</div>
|
||
<div class="nav-item" data-page="connections">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="12" cy="12" r="2"/>
|
||
<circle cx="4" cy="6" r="2"/><circle cx="20" cy="6" r="2"/>
|
||
<circle cx="4" cy="18" r="2"/><circle cx="20" cy="18" r="2"/>
|
||
<line x1="6" y1="6" x2="10" y2="11"/><line x1="18" y1="6" x2="14" y2="11"/>
|
||
<line x1="6" y1="18" x2="10" y2="13"/><line x1="18" y1="18" x2="14" y2="13"/>
|
||
</svg>
|
||
Virtual Hosts
|
||
<span class="nav-badge" id="connCount">0</span>
|
||
</div>
|
||
<div class="nav-item" data-page="service-tunnels">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
||
<path d="M2 17l10 5 10-5"/>
|
||
<path d="M2 12l10 5 10-5"/>
|
||
</svg>
|
||
Service Tunnels
|
||
<span class="nav-badge" id="serviceTunnelCount">0</span>
|
||
</div>
|
||
<div class="nav-item" data-page="ssh">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="2" y="4" width="20" height="16" rx="2"/>
|
||
<polyline points="8,10 12,14 16,10"/>
|
||
</svg>
|
||
SSH
|
||
<span class="nav-badge" id="sshCount">0</span>
|
||
</div>
|
||
<div class="nav-item" data-page="swarms">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="2" y="3" width="6" height="6" rx="1"/>
|
||
<rect x="16" y="3" width="6" height="6" rx="1"/>
|
||
<rect x="9" y="15" width="6" height="6" rx="1"/>
|
||
<line x1="5" y1="9" x2="12" y2="15"/><line x1="19" y1="9" x2="12" y2="15"/>
|
||
</svg>
|
||
Servers
|
||
<span class="nav-badge" id="swarmCount">0</span>
|
||
</div>
|
||
<div class="nav-item" data-page="tabs">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||
</svg>
|
||
Proxy & CA
|
||
<span class="nav-badge" id="tabCount">—</span>
|
||
</div>
|
||
<div class="nav-item" data-page="rdp">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="2" y="3" width="20" height="14" rx="2"/>
|
||
<line x1="8" y1="21" x2="16" y2="21"/>
|
||
<line x1="12" y1="17" x2="12" y2="21"/>
|
||
</svg>
|
||
Remote Desktop
|
||
<span class="nav-badge" id="rdpCount">0</span>
|
||
</div>
|
||
<div class="nav-item" data-page="backups">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||
<polyline points="17,8 12,3 7,8"/>
|
||
<line x1="12" y1="3" x2="12" y2="15"/>
|
||
</svg>
|
||
Backups
|
||
<span class="nav-badge" id="backupCount">0</span>
|
||
</div>
|
||
<div class="nav-item" data-page="logs">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<polyline points="22,12 18,12 15,21 9,3 6,12 2,12"/>
|
||
</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-2.83 2.83l-.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-4 0v-.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-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.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 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.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 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||
</svg>
|
||
Settings
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sidebar-footer">
|
||
<div class="status-pill">
|
||
<div class="status-dot" id="sidebarDot"></div>
|
||
<span id="sidebarStatus">Connecting…</span>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- ── Main ─────────────────────────────────────── -->
|
||
<main class="main">
|
||
<div class="topbar">
|
||
<span class="topbar-title" id="topbarTitle">Overview</span>
|
||
<div class="topbar-actions" id="topbarActions"></div>
|
||
</div>
|
||
|
||
<div class="content">
|
||
|
||
<!-- ── Overview page ──────────────────────────── -->
|
||
<div class="page active" id="page-dashboard">
|
||
<div style="margin-bottom:16px;flex-shrink:0;display:flex;align-items:center;justify-content:flex-end;">
|
||
<div id="overviewLastUpdated" style="font-size:11px;color:var(--text4);"></div>
|
||
</div>
|
||
|
||
<!-- System status bar -->
|
||
<div id="overviewStatusBar" style="display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius-lg);margin-bottom:14px;flex-wrap:wrap;flex-shrink:0;">
|
||
<div style="display:flex;align-items:center;gap:7px;flex:1;min-width:160px;">
|
||
<div class="status-dot" id="ovHostDot"></div>
|
||
<span style="font-size:12.5px;font-weight:500;color:var(--text2);">Native Host</span>
|
||
<span id="ovHostLabel" style="font-size:11.5px;color:var(--text3);">—</span>
|
||
</div>
|
||
<div style="width:1px;height:18px;background:var(--border);flex-shrink:0;"></div>
|
||
<div style="display:flex;align-items:center;gap:7px;flex:1;min-width:140px;">
|
||
<div id="ovCaDot" style="width:7px;height:7px;border-radius:50%;background:var(--text4);flex-shrink:0;"></div>
|
||
<span style="font-size:12.5px;font-weight:500;color:var(--text2);">Root CA</span>
|
||
<span id="ovCaLabel" style="font-size:11.5px;color:var(--text3);">—</span>
|
||
</div>
|
||
<div style="width:1px;height:18px;background:var(--border);flex-shrink:0;"></div>
|
||
<div style="display:flex;align-items:center;gap:7px;flex:1;min-width:160px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px;color:var(--text4);flex-shrink:0;"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||
<span style="font-size:12.5px;font-weight:500;color:var(--text2);">HTTPS Proxy</span>
|
||
<span id="ovProxyLabel" style="font-size:11.5px;color:var(--text3);">—</span>
|
||
</div>
|
||
<div style="width:1px;height:18px;background:var(--border);flex-shrink:0;"></div>
|
||
<div style="display:flex;align-items:center;gap:7px;flex:1;min-width:160px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px;color:var(--text4);flex-shrink:0;"><polyline points="22,12 18,12 15,21 9,3 6,12 2,12"/></svg>
|
||
<span style="font-size:12.5px;font-weight:500;color:var(--text2);">CONNECT Proxy</span>
|
||
<span id="ovConnectLabel" style="font-size:11.5px;color:var(--text3);">—</span>
|
||
</div>
|
||
<div style="width:1px;height:18px;background:var(--border);flex-shrink:0;"></div>
|
||
<div style="display:flex;align-items:center;gap:7px;flex:1;min-width:180px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px;color:var(--text4);flex-shrink:0;"><polyline points="16,3 21,3 21,8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21,16 21,21 16,21"/><line x1="15" y1="15" x2="21" y2="21"/></svg>
|
||
<span style="font-size:12.5px;font-weight:500;color:var(--text2);">Traffic</span>
|
||
<span id="ovTrafficLabel" style="font-size:11.5px;color:var(--text3);">—</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stat cards — 6 across -->
|
||
<div class="stats-grid" style="grid-template-columns:repeat(6,1fr);margin-bottom:14px;flex-shrink:0;">
|
||
<div class="stat-card">
|
||
<div class="stat-icon cyan">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="2"/><circle cx="4" cy="6" r="2"/><circle cx="20" cy="6" r="2"/><line x1="6" y1="6" x2="10" y2="11"/><line x1="18" y1="6" x2="14" y2="11"/></svg>
|
||
</div>
|
||
<div class="stat-value" id="dashConnections">0</div>
|
||
<div class="stat-label">Virtual Hosts</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon amber">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="6" height="6" rx="1"/><rect x="16" y="3" width="6" height="6" rx="1"/><rect x="9" y="15" width="6" height="6" rx="1"/><line x1="5" y1="9" x2="12" y2="15"/><line x1="19" y1="9" x2="12" y2="15"/></svg>
|
||
</div>
|
||
<div class="stat-value" id="dashSwarms">0</div>
|
||
<div class="stat-label">Server Tunnels</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background:rgba(139,92,246,.12);color:#a78bfa;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||
</div>
|
||
<div class="stat-value" id="dashServiceTunnels">0</div>
|
||
<div class="stat-label">Service Tunnels</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon green">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><polyline points="8,10 12,14 16,10"/></svg>
|
||
</div>
|
||
<div class="stat-value" id="dashSsh">0</div>
|
||
<div class="stat-label">SSH Connections</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background:rgba(251,113,133,.12);color:#fb7185;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
||
</div>
|
||
<div class="stat-value" id="dashRdp">0</div>
|
||
<div class="stat-label">Remote Desktops</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon" id="caStatIcon" style="background:var(--red-dim);color:var(--red);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||
</div>
|
||
<div class="stat-value" id="dashUptime" style="font-size:16px;">—</div>
|
||
<div class="stat-label">CA Status</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Lists: flex column that fills all remaining space between stat cards
|
||
and Quick Actions. Each grid row gets equal share via flex:1. -->
|
||
<div id="ovListsWrapper" style="flex:1;min-height:0;display:flex;flex-direction:column;gap:10px;margin-bottom:10px;">
|
||
|
||
<!-- Row 1: Virtual Hosts + Server Tunnels -->
|
||
<div style="flex:1;min-height:0;display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
||
|
||
<!-- Virtual Hosts -->
|
||
<div class="card ov-list-card">
|
||
<div class="card-header" style="padding-bottom:8px;">
|
||
<div style="min-width:0;">
|
||
<div class="card-title">Virtual Hosts</div>
|
||
<div class="card-subtitle" id="ovVhostSubtitle">—</div>
|
||
</div>
|
||
</div>
|
||
<div style="padding:0 12px 8px;flex-shrink:0;">
|
||
<input class="input ov-search" id="ovVhostSearch" placeholder="Filter…" autocomplete="off" style="height:28px;font-size:12px;">
|
||
</div>
|
||
<div class="ov-scroll-area">
|
||
<table class="table ov-table">
|
||
<thead><tr><th>Hostname</th><th>State</th><th></th></tr></thead>
|
||
<tbody id="recentConnections"></tbody>
|
||
</table>
|
||
<div id="ovVhostSentinel" class="ov-sentinel"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Server Tunnels -->
|
||
<div class="card ov-list-card">
|
||
<div class="card-header" style="padding-bottom:8px;">
|
||
<div style="min-width:0;">
|
||
<div class="card-title">Server Tunnels</div>
|
||
<div class="card-subtitle" id="ovServersSubtitle">—</div>
|
||
</div>
|
||
</div>
|
||
<div style="padding:0 12px 8px;flex-shrink:0;">
|
||
<input class="input ov-search" id="ovServersSearch" placeholder="Filter…" autocomplete="off" style="height:28px;font-size:12px;">
|
||
</div>
|
||
<div class="ov-scroll-area">
|
||
<table class="table ov-table">
|
||
<thead><tr><th>Port</th><th>State</th><th>hs:// Key</th></tr></thead>
|
||
<tbody id="ovServersBody"></tbody>
|
||
</table>
|
||
<div id="ovServersSentinel" class="ov-sentinel"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Row 2: Service Tunnels + SSH + Remote Desktops -->
|
||
<div style="flex:1;min-height:0;display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;">
|
||
|
||
<!-- Service Tunnels -->
|
||
<div class="card ov-list-card">
|
||
<div class="card-header" style="padding-bottom:8px;">
|
||
<div style="min-width:0;">
|
||
<div class="card-title">Service Tunnels</div>
|
||
<div class="card-subtitle" id="ovSvcSubtitle">—</div>
|
||
</div>
|
||
</div>
|
||
<div style="padding:0 12px 8px;flex-shrink:0;">
|
||
<input class="input ov-search" id="ovSvcSearch" placeholder="Filter…" autocomplete="off" style="height:28px;font-size:12px;">
|
||
</div>
|
||
<div class="ov-scroll-area">
|
||
<table class="table ov-table">
|
||
<thead><tr><th>Label</th><th>Port</th><th>State</th></tr></thead>
|
||
<tbody id="ovSvcBody"></tbody>
|
||
</table>
|
||
<div id="ovSvcSentinel" class="ov-sentinel"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SSH Connections -->
|
||
<div class="card ov-list-card">
|
||
<div class="card-header" style="padding-bottom:8px;">
|
||
<div style="min-width:0;">
|
||
<div class="card-title">SSH Connections</div>
|
||
<div class="card-subtitle" id="ovSshSubtitle">—</div>
|
||
</div>
|
||
</div>
|
||
<div style="padding:0 12px 8px;flex-shrink:0;">
|
||
<input class="input ov-search" id="ovSshSearch" placeholder="Filter…" autocomplete="off" style="height:28px;font-size:12px;">
|
||
</div>
|
||
<div class="ov-scroll-area">
|
||
<table class="table ov-table">
|
||
<thead><tr><th>Label</th><th>User</th></tr></thead>
|
||
<tbody id="ovSshBody"></tbody>
|
||
</table>
|
||
<div id="ovSshSentinel" class="ov-sentinel"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Remote Desktops -->
|
||
<div class="card ov-list-card">
|
||
<div class="card-header" style="padding-bottom:8px;">
|
||
<div style="min-width:0;">
|
||
<div class="card-title">Remote Desktops</div>
|
||
<div class="card-subtitle" id="ovRdpSubtitle">—</div>
|
||
</div>
|
||
</div>
|
||
<div style="padding:0 12px 8px;flex-shrink:0;">
|
||
<input class="input ov-search" id="ovRdpSearch" placeholder="Filter…" autocomplete="off" style="height:28px;font-size:12px;">
|
||
</div>
|
||
<div class="ov-scroll-area">
|
||
<table class="table ov-table">
|
||
<thead><tr><th>Label</th><th>Type</th></tr></thead>
|
||
<tbody id="ovRdpBody"></tbody>
|
||
</table>
|
||
<div id="ovRdpSentinel" class="ov-sentinel"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /ovListsWrapper -->
|
||
|
||
<!-- Quick Actions — sticky at the bottom, never pushed off screen -->
|
||
<div class="card" style="flex-shrink:0;">
|
||
<div class="card-header"><div class="card-title">Quick Actions</div></div>
|
||
<div class="card-body" style="display:flex;gap:8px;flex-wrap:wrap;padding-top:10px;padding-bottom:10px;">
|
||
<button class="btn btn-secondary btn-sm" id="qaAddVhost">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Add Virtual Host
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm" id="qaAddServer">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
New Server Tunnel
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm" id="qaAddSvc">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Add Service Tunnel
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm" id="qaAddSsh">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Add SSH Connection
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm" id="qaAddRdp">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Add Remote Desktop
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm" id="qaInstallCA">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||
Install Root CA
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Holesail Lookup -->
|
||
<div class="card" style="flex-shrink:0;">
|
||
<div class="card-header"><div class="card-title">Holesail Lookup</div><div class="card-subtitle">Resolve an hs:// key to see host, port, protocol and privacy details</div></div>
|
||
<div class="card-body" style="display:flex;gap:8px;align-items:center;padding-top:10px;padding-bottom:10px;flex-wrap:wrap;">
|
||
<input type="text" id="peerLookupInput" class="input mono" placeholder="hs://s000…" autocomplete="off" spellcheck="false" style="flex:1;min-width:220px;">
|
||
<button class="btn btn-primary btn-sm" id="btnPeerLookup">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
Lookup
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Virtual Hosts page ──────────────────────── -->
|
||
<div class="page" id="page-connections">
|
||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;">
|
||
<div id="vhostBulkBar" style="display:none;align-items:center;gap:8px;">
|
||
<span id="vhostBulkCount" style="font-size:12.5px;color:var(--text2);"></span>
|
||
<button class="btn btn-danger btn-sm" id="btnVhostBulkRemove">Remove Selected</button>
|
||
</div>
|
||
<div style="flex:1;"></div>
|
||
<button class="btn btn-primary" id="addVhostBtn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Add Host
|
||
</button>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-body flush">
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:32px;"><input type="checkbox" id="vhostSelectAll" title="Select all"></th>
|
||
<th>Hostname</th>
|
||
<th>hs:// URL</th>
|
||
<th>Backend</th>
|
||
<th>State</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="connectionsTable">
|
||
<tr><td colspan="6" class="empty">No virtual hosts. Click "Add Host" to get started.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Servers page ────────────────────────────── -->
|
||
<div class="page" id="page-swarms">
|
||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;">
|
||
<div id="serverBulkBar" style="display:none;align-items:center;gap:8px;">
|
||
<span id="serverBulkCount" style="font-size:12.5px;color:var(--text2);"></span>
|
||
<button class="btn btn-danger btn-sm" id="btnServerBulkStop">Stop Selected</button>
|
||
</div>
|
||
<div style="flex:1;"></div>
|
||
<button class="btn btn-primary" id="startServerBtn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
New Server
|
||
</button>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-body flush">
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:32px;"><input type="checkbox" id="serverSelectAll" title="Select all"></th>
|
||
<th>ID</th>
|
||
<th>Port</th>
|
||
<th>hs:// URL</th>
|
||
<th>Secure</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="swarmsTable">
|
||
<tr><td colspan="6" class="empty">No server tunnels. Click "New Server" to start one.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Proxy & CA page ────────────────────────── -->
|
||
<div class="page" id="page-tabs">
|
||
<div class="info-grid" id="proxyInfoGrid" style="margin-bottom:16px;">
|
||
<div class="info-item">
|
||
<div class="info-item-label">Proxy Port</div>
|
||
<div class="info-item-value" id="proxyInfoPort">—</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-item-label">CA Status</div>
|
||
<div class="info-item-value" id="proxyInfoCA">—</div>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<div>
|
||
<div class="card-title">Root Certificate Authority</div>
|
||
<div class="card-subtitle">Required for HTTPS on virtual host domains</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-body" style="display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;">
|
||
<p style="font-size:13px;color:var(--text2);max-width:420px;line-height:1.6;">
|
||
Install the Holesail root CA into your system keychain so Chrome trusts HTTPS certificates for your virtual hosts. Works with any private domain — <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--cyan);">myapp.hole.sail</code>, <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--cyan);">api.my.internal</code>, or any custom private TLD you choose.
|
||
</p>
|
||
<button class="btn btn-primary" id="installCaBtn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||
Install Root CA
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Certificate Validator — shown only when CA is installed -->
|
||
<div class="card" id="certValidatorCard" style="display:none;">
|
||
<div class="card-header">
|
||
<div>
|
||
<div class="card-title">Certificate Validator</div>
|
||
<div class="card-subtitle">Tests HTTPS reachability and TLS trust for each virtual host</div>
|
||
</div>
|
||
<button class="btn btn-primary btn-sm" id="runAllValidationsBtn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5,3 19,12 5,21"/></svg>
|
||
Test All
|
||
</button>
|
||
</div>
|
||
<div class="card-body flush">
|
||
<table class="table" id="certValidatorTable">
|
||
<thead>
|
||
<tr>
|
||
<th>Hostname</th>
|
||
<th>TLS Trust</th>
|
||
<th>HTTP Status</th>
|
||
<th>Response Time</th>
|
||
<th>Action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="certValidatorBody">
|
||
<tr><td colspan="5" class="empty">No virtual hosts to test</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Remote Desktop page ───────────────────── -->
|
||
<div class="page" id="page-rdp">
|
||
<div style="margin-bottom:16px;display:flex;justify-content:flex-end;">
|
||
<button class="btn btn-primary" id="addRdpBtn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Add Connection
|
||
</button>
|
||
</div>
|
||
<div class="rdp-grid" id="rdpGrid">
|
||
<div class="empty-state" style="grid-column:1/-1">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
||
<div class="empty-state-title">No remote desktop connections</div>
|
||
<div class="empty-state-desc">Add a VNC or RDP connection. You'll need an hs:// key for the remote peer.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Backups page ───────────────────────────── -->
|
||
<div class="page" id="page-backups">
|
||
<div style="margin-bottom:16px;display:flex;justify-content:flex-end;">
|
||
<button class="btn btn-primary" id="btnTakeBackup">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17,8 12,3 7,8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||
Take Backup
|
||
</button>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-body flush">
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th>Filename</th>
|
||
<th>Created</th>
|
||
<th>Size</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="backupsTable">
|
||
<tr><td colspan="4"><div class="empty-state">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:32px;height:32px;color:var(--text4)"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17,8 12,3 7,8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||
<div class="empty-state-title">No backups yet</div>
|
||
<div class="empty-state-desc">Click "Take Backup" to create your first backup</div>
|
||
</div></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Settings page ──────────────────────────── -->
|
||
<div class="page" id="page-settings">
|
||
<div class="card">
|
||
<div class="card-body">
|
||
<div class="section-heading">Appearance</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Light Theme</div>
|
||
<div class="setting-desc">Switch between dark (default) and light color scheme</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<div class="toggle" id="toggleTheme"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-heading">Notifications</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Host Disconnect Alert</div>
|
||
<div class="setting-desc">Show a notification when the native host disconnects</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<div class="toggle" id="toggleNotify" data-setting="notifyOnDisconnect"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Tunnel Error Alert</div>
|
||
<div class="setting-desc">Show a notification when a virtual host or service tunnel enters an error state</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<div class="toggle" id="toggleNotifyTunnelError" data-setting="notifyOnTunnelError"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Tunnel Auto-Reconnect</div>
|
||
<div class="setting-desc">Automatically reconnect virtual hosts and service tunnels on error or disconnect (exponential backoff)</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<div class="toggle" id="toggleTunnelAutoReconnect" data-setting="tunnelAutoReconnect"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Debug Mode</div>
|
||
<div class="setting-desc">Enable verbose logging for troubleshooting</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<div class="toggle" id="toggleDebug" data-setting="debug"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-heading">Latency Ping</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Enable Latency Ping</div>
|
||
<div class="setting-desc">Periodically ping tunnel ports to display live latency badges on virtual hosts and service tunnels</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<div class="toggle" id="toggleLatencyPing" data-setting="latencyPingEnabled"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Ping Interval (seconds)</div>
|
||
<div class="setting-desc">How often to ping tunnel ports for latency. Minimum 1 s, default 5 s.</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<input type="number" id="latencyPingIntervalMs" min="1" max="60" step="0.5" class="input" style="width:90px;" placeholder="5">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-heading">Proxy & API</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Proxy Port</div>
|
||
<div class="setting-desc">HTTPS proxy port for virtual hosts (default 8443)</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<input type="number" id="proxyPort" min="1" max="65535" class="input" style="width:90px;" placeholder="8443">
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Ready Timeout (ms)</div>
|
||
<div class="setting-desc">Timeout for Holesail tunnel ready state</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<input type="number" id="readyTimeoutMs" class="input" style="width:90px;" placeholder="0">
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Disable on File URLs</div>
|
||
<div class="setting-desc">Do not inject extension on file:// pages</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<div class="toggle" id="toggleDisableFileUrls"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-heading">Backups</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Backup Retention</div>
|
||
<div class="setting-desc">Number of backups to keep (oldest are pruned automatically)</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<input type="number" id="backupRetention" min="1" max="100" class="input" style="width:90px;" placeholder="5">
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Auto-Backup Interval (hours)</div>
|
||
<div class="setting-desc">Automatically create a backup every N hours. Set to 0 to disable.</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<input type="number" id="backupIntervalHours" min="0" max="720" class="input" style="width:90px;" placeholder="0">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-heading">Import / Export</div>
|
||
<div class="setting-row" style="align-items:flex-start;">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Export Configuration</div>
|
||
<div class="setting-desc">Download virtual hosts, service tunnels, SSH & RDP connections as JSON. CA private key is never included.</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<button class="btn btn-secondary btn-sm" id="btnExportConfig">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7,10 12,15 17,10"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||
Export
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row" style="align-items:flex-start;">
|
||
<div class="setting-info">
|
||
<div class="setting-label">Import Configuration</div>
|
||
<div class="setting-desc">Merge connections from a previously exported JSON file. Existing entries are not overwritten.</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<label class="btn btn-secondary btn-sm" style="cursor:pointer;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17,8 12,3 7,8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||
Import
|
||
<input type="file" id="importConfigFile" accept=".json" style="display:none;">
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top:20px;display:flex;gap:8px;">
|
||
<button class="btn btn-primary" id="btnSaveSettings">Save Settings</button>
|
||
<button class="btn btn-secondary" id="btnResetSettings">Reset to Defaults</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ── Service Tunnels page ──────────────────────── -->
|
||
<div class="page" id="page-service-tunnels">
|
||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;">
|
||
<div id="svcBulkBar" style="display:none;align-items:center;gap:8px;">
|
||
<span id="svcBulkCount" style="font-size:12.5px;color:var(--text2);"></span>
|
||
<button class="btn btn-danger btn-sm" id="btnSvcBulkRemove">Remove Selected</button>
|
||
</div>
|
||
<div style="flex:1;"></div>
|
||
<button class="btn btn-primary" id="addServiceTunnelBtn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Add Tunnel
|
||
</button>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-body flush">
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:32px;"><input type="checkbox" id="svcSelectAll" title="Select all"></th>
|
||
<th>Label</th>
|
||
<th>hs:// Key</th>
|
||
<th>Local Port</th>
|
||
<th>State</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="serviceTunnelsTable">
|
||
<tr><td colspan="6" class="empty">No service tunnels. Click "Add Tunnel" to connect a remote service.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── SSH page ────────────────────────────────── -->
|
||
<div class="page" id="page-ssh">
|
||
<div style="margin-bottom:16px;display:flex;justify-content:flex-end;">
|
||
<button class="btn btn-primary" id="addSshBtn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Add Connection
|
||
</button>
|
||
</div>
|
||
<div class="ssh-grid" id="sshGrid">
|
||
<div class="empty-state" style="grid-column:1/-1">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="4" width="20" height="16" rx="2"/><polyline points="8,10 12,14 16,10"/></svg>
|
||
<div class="empty-state-title">No SSH connections</div>
|
||
<div class="empty-state-desc">Add a connection to get started. You'll need an hs:// key for the remote peer.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Logs page ───────────────────────────────── -->
|
||
<div class="page" id="page-logs">
|
||
<div class="logs-toolbar">
|
||
<input type="text" class="input logs-filter" id="logsFilter" placeholder="Filter logs…">
|
||
<div style="display:flex;gap:4px;">
|
||
<button class="btn btn-secondary btn-sm log-level-btn active" data-level="all">All</button>
|
||
<button class="btn btn-secondary btn-sm log-level-btn" data-level="info">Info</button>
|
||
<button class="btn btn-secondary btn-sm log-level-btn" data-level="warn" style="color:var(--amber);">Warn</button>
|
||
<button class="btn btn-secondary btn-sm log-level-btn" data-level="error" style="color:var(--red);">Error</button>
|
||
</div>
|
||
<button class="btn btn-secondary" id="btnAutoScroll">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="7,13 12,18 17,13"/><polyline points="7,6 12,11 17,6"/></svg>
|
||
Auto-scroll: ON
|
||
</button>
|
||
<button class="btn btn-secondary" id="btnDownloadLogs">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7,10 12,15 17,10"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||
Download
|
||
</button>
|
||
<button class="btn btn-ghost" id="btnClearLogs">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3,6 5,6 21,6"/><path d="M19,6l-1,14a2,2,0,0,1-2,2H8a2,2,0,0,1-2-2L5,6"/><path d="M10,11v6"/><path d="M14,11v6"/></svg>
|
||
Clear
|
||
</button>
|
||
</div>
|
||
<div class="logs-container" id="logsContainer">
|
||
<div class="empty-state">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polyline points="22,12 18,12 15,21 9,3 6,12 2,12"/></svg>
|
||
<div class="empty-state-title">No logs yet</div>
|
||
<div class="empty-state-desc">Logs will appear here as the extension runs</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /content -->
|
||
</main>
|
||
</div><!-- /layout -->
|
||
|
||
<!-- ── Toast ─────────────────────────────────────────── -->
|
||
<div class="toast" id="toast"></div>
|
||
|
||
<!-- ── Modal: Add Virtual Host ───────────────────────── -->
|
||
<div class="modal-backdrop" id="modal-addVhost">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-addVhost-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-addVhost-title">Add Virtual Host</span>
|
||
<button class="btn-icon" data-close-modal="modal-addVhost" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Assign a hostname to an hs:// tunnel URL. The host will be accessible at <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--cyan);">https://<hostname></code> via the proxy.</p>
|
||
<div class="form-group">
|
||
<label class="form-label" for="addVhostHostname">Hostname <span class="form-hint">(e.g. myapp.hole.sail or i.love.hole.sail — no real TLDs)</span></label>
|
||
<input type="text" id="addVhostHostname" class="input mono" placeholder="myapp.hole.sail" autocomplete="off" spellcheck="false">
|
||
<div style="font-size:11px;color:var(--text4);margin-top:4px;line-height:1.5;">Use any depth: <code style="font-size:10px;">app.hole.sail</code>, <code style="font-size:10px;">i.love.hole.sail</code>, <code style="font-size:10px;">api.v2.my.internal</code> — private TLDs only, real TLDs like <code style="font-size:10px;">.com</code> are not allowed.</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="addVhostHsUrl">hs:// URL</label>
|
||
<input type="text" id="addVhostHsUrl" class="input mono" placeholder="hs://s000..." autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-checkbox">
|
||
<input type="checkbox" id="addVhostUseTls" autocomplete="off">
|
||
<span>Use TLS (secure connection)</span>
|
||
</label>
|
||
<div style="font-size:11px;color:var(--text4);margin-top:4px;">Enable if the service behind the tunnel uses HTTPS (port 443).</div>
|
||
</div>
|
||
<div class="modal-error" id="addVhostError"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-addVhost">Cancel</button>
|
||
<button class="btn btn-primary" id="addVhostSubmit">Add Host</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ── Modal: Start Server ───────────────────────────── -->
|
||
<div class="modal-backdrop" id="modal-startServer">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-startServer-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-startServer-title">Start Server Tunnel</span>
|
||
<button class="btn-icon" data-close-modal="modal-startServer" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Expose a local port as an hs:// tunnel. The generated URL can be shared with others.</p>
|
||
<div class="form-group">
|
||
<label class="form-label" for="startServerLabel">Label <span style="color:var(--text4);font-weight:400;">(optional)</span></label>
|
||
<input type="text" id="startServerLabel" class="input" placeholder="e.g. My Web App" autocomplete="off">
|
||
</div>
|
||
<div class="row">
|
||
<div class="form-group" style="max-width:120px;">
|
||
<label class="form-label" for="startServerPort">Port</label>
|
||
<input type="number" id="startServerPort" class="input" value="3000" min="1" max="65535">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="startServerHost">Host</label>
|
||
<input type="text" id="startServerHost" class="input mono" value="127.0.0.1">
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Protocol</label>
|
||
<div style="display:flex;gap:8px;">
|
||
<label class="radio-row">
|
||
<input type="radio" name="startServerProtocol" id="startServerProtocolTcp" value="tcp" checked>
|
||
<span>TCP</span>
|
||
</label>
|
||
<label class="radio-row">
|
||
<input type="radio" name="startServerProtocol" id="startServerProtocolUdp" value="udp">
|
||
<span>UDP</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<label class="checkbox-row">
|
||
<input type="checkbox" id="startServerSecure" checked>
|
||
<label for="startServerSecure">Secure mode (hs://s000…)</label>
|
||
</label>
|
||
<input type="hidden" id="serverEditId">
|
||
<div class="modal-error" id="startServerError"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-startServer">Cancel</button>
|
||
<button class="btn btn-primary" id="startServerSubmit">Start Server</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Install CA ─────────────────────────────── -->
|
||
<div class="modal-backdrop" id="modal-installCA">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-installCA-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-installCA-title">Install Root CA</span>
|
||
<button class="btn-icon" data-close-modal="modal-installCA" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">
|
||
This installs the Holesail root certificate authority into your system keychain, so Chrome trusts HTTPS for all your virtual host domains — including any private TLD you use (e.g. <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--cyan);">myapp.hole.sail</code>, <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--cyan);">api.my.internal</code>).
|
||
</p>
|
||
<p class="modal-desc" style="color:var(--fg-muted);font-size:11px;">
|
||
After installing, fully quit and reopen Chrome (Cmd+Q) for the trust to take effect.
|
||
</p>
|
||
<div class="modal-error" id="installCaError"></div>
|
||
<div id="installCaSuccess" style="font-size:12px;color:var(--green);display:none;"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-installCA">Cancel</button>
|
||
<button class="btn btn-primary" id="installCaSubmit">Install CA</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Remove Virtual Host confirm ────────────── -->
|
||
<div class="modal-backdrop" id="modal-removeVhost">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-removeVhost-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-removeVhost-title">Remove Virtual Host</span>
|
||
<button class="btn-icon" data-close-modal="modal-removeVhost" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Remove <strong id="removeVhostName" style="color:var(--cyan);font-family:'JetBrains Mono',monospace;font-size:12px;"></strong>? The tunnel will be closed and the hostname will no longer be accessible.</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-removeVhost">Cancel</button>
|
||
<button class="btn btn-danger" id="removeVhostConfirm">Remove</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Stop Server confirm ────────────────────── -->
|
||
<div class="modal-backdrop" id="modal-stopServer">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-stopServer-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-stopServer-title">Stop Server</span>
|
||
<button class="btn-icon" data-close-modal="modal-stopServer" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Stop server <strong id="stopServerName" style="color:var(--cyan);font-family:'JetBrains Mono',monospace;font-size:12px;"></strong>? The tunnel will be closed and the hs:// URL will no longer be active.</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-stopServer">Cancel</button>
|
||
<button class="btn btn-danger" id="stopServerConfirm">Stop Server</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Add Service Tunnel ─────────────────────── -->
|
||
<div class="modal-backdrop" id="modal-addServiceTunnel">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-addServiceTunnel-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-addServiceTunnel-title">Add Service Tunnel</span>
|
||
<button class="btn-icon" data-close-modal="modal-addServiceTunnel" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Connect to a remote service (game server, database, TCP/UDP service, etc.) via a Holesail tunnel. The service will be available on a local port you choose.</p>
|
||
<div class="form-group">
|
||
<label class="form-label" for="serviceTunnelLabel">Label</label>
|
||
<input type="text" id="serviceTunnelLabel" class="input" placeholder="My Game Server" autocomplete="off">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="serviceTunnelHsUrl">Holesail Key (hs://…)</label>
|
||
<input type="text" id="serviceTunnelHsUrl" class="input mono" placeholder="hs://s000…" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="serviceTunnelLocalPort">Local Port</label>
|
||
<input type="number" id="serviceTunnelLocalPort" class="input" placeholder="25565" min="1" max="65535">
|
||
</div>
|
||
<input type="hidden" id="serviceTunnelEditId">
|
||
<div class="modal-error" id="serviceTunnelError"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-addServiceTunnel">Cancel</button>
|
||
<button class="btn btn-primary" id="serviceTunnelSubmit">Connect</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Remove Service Tunnel confirm ───────────── -->
|
||
<div class="modal-backdrop" id="modal-removeServiceTunnel">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-removeServiceTunnel-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-removeServiceTunnel-title">Remove Service Tunnel</span>
|
||
<button class="btn-icon" data-close-modal="modal-removeServiceTunnel" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Remove <strong id="removeServiceTunnelName" style="color:var(--cyan);font-family:'JetBrains Mono',monospace;font-size:12px;"></strong>? The tunnel will be disconnected and the local port will be released.</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-removeServiceTunnel">Cancel</button>
|
||
<button class="btn btn-danger" id="removeServiceTunnelConfirm">Remove</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Add / Edit SSH Connection ──────────────── -->
|
||
<div class="modal-backdrop" id="modal-addSsh">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-addSsh-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-addSsh-title">Add SSH Connection</span>
|
||
<button class="btn-icon" data-close-modal="modal-addSsh" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Save an SSH connection by providing its Holesail key. The key is used to establish a secure tunnel to the remote peer before connecting via SSH.</p>
|
||
<div class="form-group">
|
||
<label class="form-label" for="sshConnLabel">Label</label>
|
||
<input type="text" id="sshConnLabel" class="input" placeholder="My Server" autocomplete="off">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="sshConnHsUrl">Holesail Key (hs://…)</label>
|
||
<input type="text" id="sshConnHsUrl" class="input mono" placeholder="hs://s000…" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="sshConnUsername">Username</label>
|
||
<input type="text" id="sshConnUsername" class="input mono" placeholder="root" autocomplete="off">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="sshConnPassword">Password <span style="color:var(--text4);font-weight:400;">(optional — saved, or leave blank to type in terminal)</span></label>
|
||
<input type="password" id="sshConnPassword" class="input" placeholder="Leave blank to enter manually" autocomplete="new-password">
|
||
</div>
|
||
<label class="checkbox-row" style="margin-top:8px;">
|
||
<input type="checkbox" id="sshConnAutoReconnect">
|
||
<label for="sshConnAutoReconnect">Auto-reconnect on disconnect</label>
|
||
</label>
|
||
<input type="hidden" id="sshConnEditId">
|
||
<div class="modal-error" id="sshConnError"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-addSsh">Cancel</button>
|
||
<button class="btn btn-primary" id="sshConnSubmit">Save Connection</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Remove SSH Connection confirm ───────────── -->
|
||
<div class="modal-backdrop" id="modal-removeSsh">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-removeSsh-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-removeSsh-title">Remove SSH Connection</span>
|
||
<button class="btn-icon" data-close-modal="modal-removeSsh" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Remove <strong id="removeSshName" style="color:var(--cyan);font-family:'JetBrains Mono',monospace;font-size:12px;"></strong>? This only removes the saved connection — no tunnels are affected.</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-removeSsh">Cancel</button>
|
||
<button class="btn btn-danger" id="removeSshConfirm">Remove</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: SSH Terminal ────────────────────────────── -->
|
||
<div class="modal-backdrop" id="modal-sshTerminal">
|
||
<div class="modal modal-terminal" role="dialog" aria-modal="true" aria-labelledby="modal-sshTerminal-title">
|
||
<div class="terminal-toolbar">
|
||
<div class="terminal-status-dot" id="termStatusDot"></div>
|
||
<span class="terminal-conn-label" id="termConnLabel">—</span>
|
||
<span style="font-size:12px;color:var(--text3);font-family:'JetBrains Mono',monospace;" id="termUserHost"></span>
|
||
<div class="terminal-spacer"></div>
|
||
<button class="btn btn-ghost" id="termCopyBtn" title="Copy selection" style="padding:5px 10px;font-size:12px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||
Copy
|
||
</button>
|
||
<button class="btn btn-ghost" id="termFullscreenBtn" title="Toggle fullscreen" style="padding:5px 10px;font-size:12px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><polyline points="15,3 21,3 21,9"/><polyline points="9,21 3,21 3,15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
||
</button>
|
||
<button class="btn btn-danger" id="termDisconnectBtn" style="padding:5px 14px;font-size:12px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
Disconnect
|
||
</button>
|
||
</div>
|
||
<div id="terminalContainer"></div>
|
||
<div class="terminal-statusbar">
|
||
<span id="termSizeDisplay">—</span>
|
||
<span style="color:var(--border2);">|</span>
|
||
<span id="termStateDisplay" style="color:var(--green);">Connected</span>
|
||
<div style="flex:1"></div>
|
||
<span style="color:var(--text4);">xterm-256color</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Add/Edit Remote Desktop Connection ──────── -->
|
||
<div class="modal-backdrop" id="modal-addRdp">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-addRdp-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-addRdp-title">Add Remote Desktop Connection</span>
|
||
<button class="btn-icon" data-close-modal="modal-addRdp" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label class="form-label" for="rdpConnLabel">Label (optional)</label>
|
||
<input type="text" id="rdpConnLabel" class="input" placeholder="My Desktop" autocomplete="off">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="rdpConnHsUrl">hs:// Key</label>
|
||
<input type="text" id="rdpConnHsUrl" class="input mono" placeholder="hs://s000..." autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Protocol</label>
|
||
<div style="display:flex;gap:8px;">
|
||
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;">
|
||
<input type="radio" name="rdpProtocol" id="rdpProtocolVnc" value="vnc" checked> VNC
|
||
</label>
|
||
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;">
|
||
<input type="radio" name="rdpProtocol" id="rdpProtocolRdp" value="rdp"> RDP
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="form-group" style="max-width:110px;">
|
||
<label class="form-label" for="rdpConnPort">Port</label>
|
||
<input type="number" id="rdpConnPort" class="input" value="5900" min="1" max="65535">
|
||
</div>
|
||
<div class="form-group" style="max-width:110px;">
|
||
<label class="form-label" for="rdpConnWidth">Width</label>
|
||
<input type="number" id="rdpConnWidth" class="input" value="1280" min="320" max="4096">
|
||
</div>
|
||
<div class="form-group" style="max-width:110px;">
|
||
<label class="form-label" for="rdpConnHeight">Height</label>
|
||
<input type="number" id="rdpConnHeight" class="input" value="720" min="240" max="4096">
|
||
</div>
|
||
</div>
|
||
<div id="rdpUsernameGroup" class="form-group" style="display:none;">
|
||
<label class="form-label" for="rdpConnUsername">Username</label>
|
||
<input type="text" id="rdpConnUsername" class="input" placeholder="Administrator" autocomplete="off">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="rdpConnPassword">Password (optional)</label>
|
||
<input type="password" id="rdpConnPassword" class="input" placeholder="••••••••" autocomplete="new-password">
|
||
</div>
|
||
<input type="hidden" id="rdpConnEditId">
|
||
<div class="modal-error" id="rdpConnError"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-addRdp">Cancel</button>
|
||
<button class="btn btn-primary" id="rdpConnSubmit">Save Connection</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Remove Remote Desktop Connection ─────────── -->
|
||
<div class="modal-backdrop" id="modal-removeRdp">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-removeRdp-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-removeRdp-title">Remove Connection</span>
|
||
<button class="btn-icon" data-close-modal="modal-removeRdp" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Remove <strong id="removeRdpName" style="color:var(--cyan);font-family:'JetBrains Mono',monospace;font-size:12px;"></strong>? This only removes the saved connection.</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-removeRdp">Cancel</button>
|
||
<button class="btn btn-danger" id="removeRdpConfirm">Remove</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Remote Desktop Viewer ───────────────────── -->
|
||
<div class="modal-backdrop" id="modal-rdpViewer">
|
||
<div class="modal modal-rdp-viewer" role="dialog" aria-modal="true" aria-labelledby="modal-rdpViewer-title">
|
||
<div class="rdp-viewer-toolbar">
|
||
<div class="terminal-status-dot" id="rdpStatusDot"></div>
|
||
<span class="terminal-conn-label" id="rdpViewerLabel">—</span>
|
||
<span class="badge" id="rdpProtocolBadge" style="font-size:10px;padding:2px 7px;"></span>
|
||
<div class="terminal-spacer"></div>
|
||
<button class="btn btn-ghost" id="rdpFullscreenBtn" title="Toggle fullscreen" style="padding:5px 10px;font-size:12px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><polyline points="15,3 21,3 21,9"/><polyline points="9,21 3,21 3,15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
||
</button>
|
||
<button class="btn btn-danger" id="rdpDisconnectBtn" style="padding:5px 14px;font-size:12px;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
Disconnect
|
||
</button>
|
||
</div>
|
||
<div id="rdpViewerContainer"></div>
|
||
<div class="rdp-viewer-statusbar">
|
||
<span id="rdpResDisplay">—</span>
|
||
<span style="color:var(--border2);">|</span>
|
||
<span id="rdpStateDisplay" style="color:var(--green);">Connecting…</span>
|
||
<div style="flex:1"></div>
|
||
<span style="color:var(--text4);" id="rdpViewerInfo"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Restore Backup confirm ──────────────────── -->
|
||
<div class="modal-backdrop" id="modal-restoreBackup">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-restoreBackup-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-restoreBackup-title">Restore Backup</span>
|
||
<button class="btn-icon" data-close-modal="modal-restoreBackup" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Restore from <strong id="restoreBackupName" style="color:var(--cyan);font-family:'JetBrains Mono',monospace;font-size:12px;word-break:break-all;"></strong>?</p>
|
||
<p class="modal-desc" style="margin-top:8px;color:var(--amber);">This will overwrite your current state and certificates with the backup. All running tunnels will need to be restarted. If the CA certificate changes, you may need to reinstall it in your browser.</p>
|
||
<div class="modal-error" id="restoreBackupError"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-restoreBackup">Cancel</button>
|
||
<button class="btn btn-danger" id="restoreBackupConfirm">Restore</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Modal: Delete Backup confirm ───────────────────── -->
|
||
<div class="modal-backdrop" id="modal-deleteBackup">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-deleteBackup-title">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-deleteBackup-title">Delete Backup</span>
|
||
<button class="btn-icon" data-close-modal="modal-deleteBackup" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="modal-desc">Permanently delete <strong id="deleteBackupName" style="color:var(--cyan);font-family:'JetBrains Mono',monospace;font-size:12px;word-break:break-all;"></strong>? This cannot be undone.</p>
|
||
<div class="modal-error" id="deleteBackupError"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-close-modal="modal-deleteBackup">Cancel</button>
|
||
<button class="btn btn-danger" id="deleteBackupConfirm">Delete</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ── Modal: Holesail Lookup Result ───────────────────── -->
|
||
<div class="modal-backdrop" id="modal-holesailLookup">
|
||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-holesailLookup-title" style="max-width:420px;">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modal-holesailLookup-title">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:16px;height:16px;vertical-align:-3px;margin-right:6px;"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
Holesail Lookup Result
|
||
</span>
|
||
<button class="btn-icon" data-close-modal="modal-holesailLookup" aria-label="Close">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body" id="holesailLookupBody" style="padding:20px 24px;">
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-primary" data-close-modal="modal-holesailLookup">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Load order: data → core → ui → pages → refresh → events → init -->
|
||
|
||
<!-- Data layer -->
|
||
<script src="data/tlds.js"></script>
|
||
<script src="data/hostname-validator.js"></script>
|
||
|
||
<!-- Core utilities and state -->
|
||
<script src="core/utils.js"></script>
|
||
<script src="core/state.js"></script>
|
||
<script src="core/messaging.js"></script>
|
||
<script src="core/navigation.js"></script>
|
||
|
||
<!-- UI primitives -->
|
||
<script src="ui/toast.js"></script>
|
||
<script src="ui/modal.js"></script>
|
||
<script src="ui/state-tag.js"></script>
|
||
|
||
<!-- Page modules -->
|
||
<script src="pages/settings.js"></script>
|
||
<script src="pages/overview.js"></script>
|
||
<script src="pages/virtual-hosts.js"></script>
|
||
<script src="pages/servers.js"></script>
|
||
<script src="pages/service-tunnels.js"></script>
|
||
<script src="pages/proxy-ca.js"></script>
|
||
<script src="pages/backups.js"></script>
|
||
<script src="pages/logs.js"></script>
|
||
<script src="pages/ssh.js"></script>
|
||
<script src="pages/rdp.js"></script>
|
||
|
||
<!-- Orchestration -->
|
||
<script src="refresh.js"></script>
|
||
<script src="events.js"></script>
|
||
|
||
<!-- Entry point (runs init()) -->
|
||
<script src="core/init.js"></script>
|
||
|
||
<!-- PWA service worker registration (required for installability) -->
|
||
<script src="register-sw.js"></script>
|
||
</body>
|
||
</html>
|