···
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
box-sizing: border-box;
+
font-family: 'SF Mono', 'Monaco', monospace;
+
background: linear-gradient(135deg, #14b8a6, #fb923c);
+
-webkit-background-clip: text;
+
-webkit-text-fill-color: transparent;
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+
border: 1px solid #30363d;
+
border: 1px solid #30363d;
+
flex-direction: column;
+
border: 1px solid #30363d;
+
grid-template-columns: 1fr auto;
+
margin-bottom: 0.25rem;
+
text-decoration: underline;
+
padding: 0.25rem 0.75rem;
+
background: rgba(20, 184, 166, 0.2);
+
border: 1px solid #14b8a6;
+
padding: 0.2rem 0.5rem;
+
<div class="container">
+
<p class="subtitle">tunnel dashboard • bore.dunkirk.sh</p>
+
<div class="stat-card">
+
<div class="stat-label">active tunnels</div>
+
<div class="stat-value" id="activeTunnels">—</div>
+
<div class="stat-card">
+
<div class="stat-label">server status</div>
+
<div class="stat-value orange" id="serverStatus">—</div>
+
<div class="stat-card">
+
<div class="stat-label">active connections</div>
+
<div class="stat-value" id="totalConnections">—</div>
+
<h2>~ active tunnels</h2>
+
<div class="tunnel-list" id="tunnelList">
+
<div class="empty-state">
+
<div class="empty-icon">🚇</div>
+
<p>no active tunnels</p>
+
<div class="last-updated">
+
last updated: <span id="lastUpdated">never</span>
+
let fetchFailCount = 0;
+
const MAX_FAIL_COUNT = 3;
+
async function fetchStats() {
+
const serverResponse = await fetch('/api/serverinfo');
+
if (!serverResponse.ok) throw new Error('API unavailable');
+
const serverData = await serverResponse.json();
+
// Fetch HTTP proxies (tunnels)
+
const proxiesResponse = await fetch('/api/proxy/http');
+
const proxiesData = await proxiesResponse.json();
+
// Reset fail count on success
+
document.getElementById('activeTunnels').textContent = serverData.clientCounts || 0;
+
document.getElementById('serverStatus').textContent = 'online';
+
document.getElementById('totalConnections').textContent = serverData.curConns || 0;
+
const tunnelList = document.getElementById('tunnelList');
+
const proxies = proxiesData.proxies || [];
+
if (proxies.length === 0) {
+
tunnelList.innerHTML = `
+
<div class="empty-state">
+
<div class="empty-icon">🚇</div>
+
<p>no active tunnels</p>
+
tunnelList.innerHTML = proxies.map(proxy => {
+
const subdomain = proxy.conf.subdomain;
+
const url = `https://${subdomain}.bore.dunkirk.sh`;
+
const statusClass = proxy.status === 'online' ? 'status-online' : '';
+
<div class="tunnel-icon">→</div>
+
<div class="tunnel-info">
+
<div class="tunnel-name">${proxy.name}</div>
+
<div class="tunnel-url">
+
<a href="${url}" target="_blank">${url}</a>
+
<div style="color: #8b949e; font-size: 0.75rem; margin-top: 0.25rem;">
+
started: ${proxy.lastStartTime} • traffic in: ${formatBytes(proxy.todayTrafficIn)} • out: ${formatBytes(proxy.todayTrafficOut)}
+
<div class="tunnel-status ${statusClass}">${proxy.status}</div>
+
document.getElementById('lastUpdated').textContent = new Date().toLocaleTimeString();
+
document.getElementById('serverStatus').textContent = 'offline';
+
console.error('Failed to fetch stats:', error);
+
// Reload page if failed multiple times (server might have updated)
+
if (fetchFailCount >= MAX_FAIL_COUNT) {
+
console.log('Multiple fetch failures detected, reloading page...');
+
window.location.reload();
+
function formatBytes(bytes) {
+
if (bytes === 0) return '0 B';
+
const sizes = ['B', 'KB', 'MB', 'GB'];
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
+
// Fetch immediately and then every 5 seconds
+
setInterval(fetchStats, 5000);