add TLS, service persistence, blocking panel, query types

- Local TLS: auto-generated CA + per-service certs (explicit SANs, not
  wildcards — browsers reject *.numa under single-label TLDs). HTTPS
  proxy on :443 via rustls/tokio-rustls. `numa install` trusts CA in
  macOS Keychain / Linux ca-certificates.
- Service persistence: user-added services saved to
  ~/.config/numa/services.json, survive restarts.
- Blocking panel: renamed "Check Domain" to "Blocking" with sources
  display, allowlist management UI, unpause button.
- Query types: recognize SOA, PTR, TXT, SRV, HTTPS (type 65) instead
  of logging as UNKNOWN.
- Blocklist gzip: reqwest now decompresses gzip responses from CDNs.
- Unified config_dir() in lib.rs for consistent path resolution under
  sudo and launchd. TLS certs use /usr/local/var/numa/ (writable as
  root daemon).
- Dashboard UX: panel subtitles differentiating overrides vs services,
  better placeholders, proxy route display, 600px query log height.
- Deploy: make deploy handles build+copy+codesign+restart cycle.
- Demo: scripts/record-demo.sh for recording hero GIF with CDP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Razvan Dimescu
2026-03-21 01:15:07 +02:00
parent 10502f2db2
commit 3bfcd827ac
17 changed files with 1377 additions and 68 deletions

View File

@@ -35,6 +35,7 @@ pub fn router(ctx: Arc<ServerCtx>) -> Router {
.route("/blocking/stats", get(blocking_stats))
.route("/blocking/toggle", put(blocking_toggle))
.route("/blocking/pause", post(blocking_pause))
.route("/blocking/unpause", post(blocking_unpause))
.route("/blocking/allowlist", get(blocking_allowlist))
.route("/blocking/allowlist", post(blocking_allowlist_add))
.route("/blocking/check/{domain}", get(blocking_check))
@@ -536,6 +537,11 @@ async fn blocking_pause(
Json(serde_json::json!({ "paused_minutes": req.minutes }))
}
async fn blocking_unpause(State(ctx): State<Arc<ServerCtx>>) -> Json<serde_json::Value> {
ctx.blocklist.lock().unwrap().unpause();
Json(serde_json::json!({ "paused": false }))
}
async fn blocking_check(
State(ctx): State<Arc<ServerCtx>>,
Path(domain): Path<String>,