Add the native Go client and finish M3 hardening
The new cmd/tunnel client keeps the API URL and tunnel identity in ~/.config/tunnel/config.json (0600), provisions the key/CSR and certificate, terminates TLS, proxies to local routes, reloads routes without a restart, and runs as a background service. `tunnel import` takes over an existing official CLI profile, preserving its tunnel ID, token, key and certificate. Server hardening: per-IP and per-tunnel rate limits for creates and binds, a CA cool-down after repeated ACME failures, a startup sweep for stale _acme-challenge records, admin metrics, a 60-second half-close timeout, and separate prioritized control / ordered data bridge queues (with `end` ordered after the final bytes). The dashboard shows the new metrics.
This commit is contained in:
+1
-1
@@ -69,7 +69,7 @@
|
||||
{#if !token}
|
||||
<p class="dim">Enter the ADMIN_TOKEN to view the dashboard.</p>
|
||||
{:else if overview}
|
||||
<SummaryCards server={overview.server} ca={overview.ca} totals={overview.totals} />
|
||||
<SummaryCards server={overview.server} ca={overview.ca} totals={overview.totals} metrics={overview.metrics} />
|
||||
<h2>Tunnels</h2>
|
||||
<TunnelsTable tunnels={overview.tunnels} />
|
||||
<h2>Bridges</h2>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { CaInfo, ServerInfo, Totals } from "./api";
|
||||
import type { CaInfo, Metrics, ServerInfo, Totals } from "./api";
|
||||
import { fmtDate, fmtUptime } from "./format";
|
||||
|
||||
let { server, ca, totals }: { server: ServerInfo; ca: CaInfo; totals: Totals } = $props();
|
||||
let { server, ca, totals, metrics }: { server: ServerInfo; ca: CaInfo; totals: Totals; metrics: Metrics } = $props();
|
||||
</script>
|
||||
|
||||
<div class="cards">
|
||||
@@ -10,6 +10,19 @@
|
||||
<div class="card"><div class="label">Online</div><div class="value">{totals.online}</div></div>
|
||||
<div class="card"><div class="label">Bridges</div><div class="value">{totals.bridges}</div></div>
|
||||
<div class="card"><div class="label">Visitors</div><div class="value">{totals.visitors}</div></div>
|
||||
<div class="card">
|
||||
<div class="label">Certificates</div>
|
||||
<div class="value">{metrics.certificates_ready}</div>
|
||||
<div class="detail">
|
||||
{metrics.certificates_issuing} issuing · {metrics.certificates_failed} failed
|
||||
{#if metrics.certificates_retrying > 0}· {metrics.certificates_retrying} retrying{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Issuances</div>
|
||||
<div class="value">{metrics.issuance_ready}</div>
|
||||
<div class="detail">{metrics.issuance_attempts} attempts · {metrics.rate_limited} limited</div>
|
||||
</div>
|
||||
<div class="card"><div class="label">Local CA expires</div><div class="value small">{fmtDate(ca.expires)}</div></div>
|
||||
<div class="card"><div class="label">Uptime</div><div class="value small">{fmtUptime(server.uptime_seconds)}</div></div>
|
||||
</div>
|
||||
@@ -25,4 +38,9 @@
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.detail {
|
||||
margin-top: 6px;
|
||||
color: #8b949e;
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,6 +19,19 @@ export interface Totals {
|
||||
visitors: number;
|
||||
}
|
||||
|
||||
export interface Metrics {
|
||||
create_requests: number;
|
||||
bind_requests: number;
|
||||
rate_limited: number;
|
||||
issuance_attempts: number;
|
||||
issuance_ready: number;
|
||||
issuance_failed: number;
|
||||
certificates_ready: number;
|
||||
certificates_issuing: number;
|
||||
certificates_failed: number;
|
||||
certificates_retrying: number;
|
||||
}
|
||||
|
||||
export interface Tunnel {
|
||||
id: string;
|
||||
hostname: string;
|
||||
@@ -46,6 +59,7 @@ export interface Overview {
|
||||
server: ServerInfo;
|
||||
ca: CaInfo;
|
||||
totals: Totals;
|
||||
metrics: Metrics;
|
||||
tunnels: Tunnel[] | null;
|
||||
bridges: Bridge[] | null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user