···
5
+
<meta charset="UTF-8">
6
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+
<title>🚇 bore</title>
12
+
box-sizing: border-box;
16
+
font-family: 'SF Mono', 'Monaco', monospace;
17
+
background: #0d1117;
29
+
margin-bottom: 3rem;
34
+
background: linear-gradient(135deg, #14b8a6, #fb923c);
35
+
-webkit-background-clip: text;
36
+
-webkit-text-fill-color: transparent;
37
+
margin-bottom: 0.5rem;
47
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
49
+
margin-bottom: 2rem;
53
+
background: #161b22;
54
+
border: 1px solid #30363d;
62
+
margin-bottom: 0.5rem;
71
+
.stat-value.orange {
76
+
background: #161b22;
77
+
border: 1px solid #30363d;
80
+
margin-bottom: 2rem;
86
+
margin-bottom: 1.5rem;
88
+
align-items: center;
94
+
flex-direction: column;
99
+
background: #0d1117;
100
+
border: 1px solid #30363d;
104
+
grid-template-columns: 1fr auto;
106
+
align-items: center;
120
+
margin-bottom: 0.25rem;
125
+
font-size: 0.85rem;
130
+
text-decoration: none;
133
+
.tunnel-url a:hover {
134
+
text-decoration: underline;
138
+
padding: 0.25rem 0.75rem;
145
+
background: rgba(20, 184, 166, 0.2);
147
+
border: 1px solid #14b8a6;
151
+
text-align: center;
152
+
padding: 3rem 1rem;
158
+
margin-bottom: 1rem;
163
+
background: #0d1117;
164
+
padding: 0.2rem 0.5rem;
171
+
background: #0d1117;
184
+
text-align: center;
193
+
<div class="container">
196
+
<p class="subtitle">tunnel dashboard • bore.dunkirk.sh</p>
199
+
<div class="stats">
200
+
<div class="stat-card">
201
+
<div class="stat-label">active tunnels</div>
202
+
<div class="stat-value" id="activeTunnels">—</div>
204
+
<div class="stat-card">
205
+
<div class="stat-label">server status</div>
206
+
<div class="stat-value orange" id="serverStatus">—</div>
208
+
<div class="stat-card">
209
+
<div class="stat-label">active connections</div>
210
+
<div class="stat-value" id="totalConnections">—</div>
214
+
<div class="section">
215
+
<h2>~ active tunnels</h2>
216
+
<div class="tunnel-list" id="tunnelList">
217
+
<div class="empty-state">
218
+
<div class="empty-icon">🚇</div>
219
+
<p>no active tunnels</p>
224
+
<div class="last-updated">
225
+
last updated: <span id="lastUpdated">never</span>
230
+
let fetchFailCount = 0;
231
+
const MAX_FAIL_COUNT = 3;
233
+
async function fetchStats() {
235
+
// Fetch server info
236
+
const serverResponse = await fetch('/api/serverinfo');
237
+
if (!serverResponse.ok) throw new Error('API unavailable');
238
+
const serverData = await serverResponse.json();
240
+
// Fetch HTTP proxies (tunnels)
241
+
const proxiesResponse = await fetch('/api/proxy/http');
242
+
const proxiesData = await proxiesResponse.json();
244
+
// Reset fail count on success
245
+
fetchFailCount = 0;
248
+
document.getElementById('activeTunnels').textContent = serverData.clientCounts || 0;
249
+
document.getElementById('serverStatus').textContent = 'online';
250
+
document.getElementById('totalConnections').textContent = serverData.curConns || 0;
252
+
// Update tunnels list
253
+
const tunnelList = document.getElementById('tunnelList');
254
+
const proxies = proxiesData.proxies || [];
256
+
if (proxies.length === 0) {
257
+
tunnelList.innerHTML = `
258
+
<div class="empty-state">
259
+
<div class="empty-icon">🚇</div>
260
+
<p>no active tunnels</p>
264
+
tunnelList.innerHTML = proxies.map(proxy => {
265
+
const subdomain = proxy.conf.subdomain;
266
+
const url = `https://${subdomain}.bore.dunkirk.sh`;
267
+
const statusClass = proxy.status === 'online' ? 'status-online' : '';
270
+
<div class="tunnel">
271
+
<div class="tunnel-icon">→</div>
272
+
<div class="tunnel-info">
273
+
<div class="tunnel-name">${proxy.name}</div>
274
+
<div class="tunnel-url">
275
+
<a href="${url}" target="_blank">${url}</a>
277
+
<div style="color: #8b949e; font-size: 0.75rem; margin-top: 0.25rem;">
278
+
started: ${proxy.lastStartTime} • traffic in: ${formatBytes(proxy.todayTrafficIn)} • out: ${formatBytes(proxy.todayTrafficOut)}
281
+
<div class="tunnel-status ${statusClass}">${proxy.status}</div>
287
+
document.getElementById('lastUpdated').textContent = new Date().toLocaleTimeString();
290
+
document.getElementById('serverStatus').textContent = 'offline';
291
+
console.error('Failed to fetch stats:', error);
293
+
// Reload page if failed multiple times (server might have updated)
294
+
if (fetchFailCount >= MAX_FAIL_COUNT) {
295
+
console.log('Multiple fetch failures detected, reloading page...');
296
+
window.location.reload();
301
+
function formatBytes(bytes) {
302
+
if (bytes === 0) return '0 B';
304
+
const sizes = ['B', 'KB', 'MB', 'GB'];
305
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
306
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
309
+
// Fetch immediately and then every 5 seconds
311
+
setInterval(fetchStats, 5000);