13 Commits
Author SHA1 Message Date
wweir c86937820a fix(admin): bound error alert to recent window, keep UTF-8-safe details
Review fixes for the error visibility feature: the destructive alert now
only shows while a failure occurred within the last 10 minutes, so a past
outage does not keep a red banner up forever (cumulative counts remain as
context). Error details are truncated on a UTF-8 rune boundary and have
line breaks collapsed, so an IDN host cannot produce invalid UTF-8 in the
SSE payload and a multiline error cannot break the alert layout. Adds
tests for both.
2026-08-11 10:29:10 +08:00
wweir cfddf76eb3 feat(admin): surface proxy-side errors and stale data in overview
Proxy dial/DNS/accept failures were logged but invisible to the console:
an unreachable upstream looked identical to no traffic. Stats now count
failures by kind, keep a bounded error event ring, and expose both in the
traffic snapshot (and Prometheus via sower.proxy_errors). The overview
shows a destructive alert with recent events when failures occur, and a
distinct stale-data alert when the SSE stream stays open but traffic
ticks stop parsing (three consecutive failures). Pins the persist-failure
-> 500 contract with a handler test.
2026-08-11 09:24:53 +08:00
wweir 23a51750a7 test(admin): cover per-domain block counting
Adapt RecordRoute call sites to the two-argument signature and assert
the blocked ranking in snapshots. Add a cap test proving batch eviction
of the blocked map.
2026-08-07 15:51:13 +08:00
wweir defea58ac2 perf: batch byte accounting and bound gateway memory
- countingConn batches per-domain attribution (32KiB/500ms) so the relay
  hot path pays two atomics per I/O instead of a global mutex; Close drains
- Precompute OTel byte attributes to drop per-I/O allocations
- suffixtree: indexMap only for nodes with >=8 children; Match stays
  read-only for concurrent queries
- Soft Go memory limit (128MiB default, GOMEMLIMIT/SOWER_MEMORY_LIMIT_MB
  override) cuts resident memory from ~240MB to ~130MB on the default
  ad/china/gfw rule sets
- Split stats.go into snapshot/eviction/counting by responsibility
2026-08-07 12:02:26 +08:00
wweir 9b74f80cfe feat(admin): cumulative traffic totals for domains and clients
The domain map previously doubled as the live window and dropped entries
after staleDomainAge, so from-start totals were lost. It now accumulates
(bounded at maxTotalDomains with least-recent eviction) while Snapshot
only filters staleness, and a new independent client table (bounded at
maxTotalClients) keeps per-client totals exact even when domains are
evicted. record() takes explicit counter deltas so the cumulative layer
sees the same increments as the window.

- GET /api/totals (auth) returns top domains and clients by bytes since
  start; the SSE stream pushes a totals event initially and every 30s
- Traffic page gains a live/totals toggle; totals view keeps byte columns
  regardless of the source breadcrumb, and clicking a totals client chip
  jumps to the live view filtered by it
- per-domain client breakdown capped at maxClientsPerDomain
- tests: totals survive staleness and domain eviction, both caps enforced
2026-08-06 22:34:55 +08:00
wweir 92221c2ac6 feat(admin): show each domain's most recent client IP
Adds lastClientIP to every domain row on the traffic page so operators
can see at a glance who last touched a domain. Under a client filter the
column shows the filter itself; otherwise it reports the client with the
most recent activity on the domain (approximation for source-filtered
views, since the per-client map has no source dimension).
2026-08-06 22:20:56 +08:00
wweir 7211ce9300 fix(admin): keep client chips stable under the client filter
The clients aggregate was built only from domains surviving the client
filter, so selecting a client chip collapsed every other chip's count to
the few shared domains (or made it vanish), and the chips row hid itself
entirely when the filter matched no domains - stranding the user with no
way to clear the filter.

- Snapshot now aggregates chips over the source-filtered view before the
  client filter is applied; chips stay globally counted and stable.
- Traffic page keeps the toolbar and chips visible when the filtered
  domain list is empty; the empty state only replaces the table.
- Regression test pins chips stability, including clients that share no
  domain with the selected one.
2026-08-06 22:10:18 +08:00
wweir fbc0d54893 feat(admin): track and filter traffic by client IP
The console now records the client source IP for every connection and
DNS query, shows the top clients, and can filter the domain list by one:

- WrapConn captures conn.RemoteAddr into the counting conn; RecordDNS
  takes the client IP from the DNS ResponseWriter. Domain stats keep a
  per-client breakdown alongside the per-source one.
- /api/traffic?client=<ip> restricts domains to that client and reports
  the client's conns/bytes/lastSeen; it composes with ?source=.
- The snapshot now carries clients: top 50 per-IP aggregates (conns,
  bytes up/down, last seen) for display.
- Traffic page shows a client chip row (全部客户端 + IPs with counts);
  clicking one filters, tooltips show byte totals.

Verified live: ?client=192.168.1.1 lists that device's domains with its
counts; ?source=dns&client=... composes correctly.
2026-08-06 19:54:43 +08:00
wweir 6d52cc14c3 feat(admin): monitor DNS queries and filter traffic by source
Domain stats are now tracked per entry point (http/https/socks5/dns)
instead of one mixed counter, and /api/traffic accepts ?source= so each
can be inspected separately:

- Stats.record attributes events to a source; DNS queries land in the
  dns slot with no bytes, proxy connections in their protocol slot.
- Snapshot(sort, source) filters domains to one source, reporting that
  source's conns/bytes/lastSeen; source=all (default) aggregates.
- The traffic page breadcrumb gains a source switcher (all/http/https/
  socks5/dns), the view reloads on switch even while paused, and the
  DNS view hides the byte columns and labels the list as DNS queries.

Verified live: source=dns lists queried domains with query counts;
source=https lists proxied domains with bytes; all still aggregates.
2026-08-06 19:35:44 +08:00
wweir cd5b857328 feat(admin): switchable domain sorting on the traffic page
Traffic view now sorts domains three ways via ?sort= on /api/traffic:
- bytes (default): total traffic, high to low (the former fixed order)
- recent: most recently seen first
- conns: most connections first
Invalid or missing sort falls back to bytes; the top-100 cap is kept.

Frontend gains a segmented sort switcher next to the filter; the footer
label follows the active mode. api.ts picks up the sort query param and
is committed with the user's prettier reformat (pure formatting) since
the change is entangled with it.
2026-08-06 18:51:28 +08:00
wweir b4cb204e3f feat(admin): OTEL metrics, real-time rates and in-process history
Monitoring overhaul for the admin console:

- Add internal/metrics: OpenTelemetry meter with Prometheus exporter
  (counters for bytes/dns/connections/rule hits, updown counter for
  active connections, histogram for connection duration, process
  gauges). Served unauthenticated at GET /metrics; an OTLP exporter
  can be added later without touching call sites.
- Fix uptime: TrafficSnapshot.Uptime was a time.Duration serialized
  as nanoseconds while the frontend renders seconds; it is now int64
  seconds. Stats is created before rule loading so uptime tracks the
  process start.
- Extend admin.Stats: per-protocol active connections (idempotent
  decrement on Close), 60s sliding-window rates, rule-hit counters,
  process resources, and a bounded in-process history ring (1h at 5s
  sampling, gap-clamped) served at GET /api/history.
- Router route observer: block/direct decisions in DialSmart, proxy
  decisions in DialProxyOnly (covers HTTP/HTTPS paths), exactly once
  per connection.
- Fix socks5 handler closing the raw conn instead of the wrapped one,
  which would have leaked active-connection counts.
- Frontend: Overview gains rate cards, active connections, rule hits,
  system stats, and four hand-rolled SVG area charts (traffic, DNS/
  conns, active per protocol, rule hits) with hover readouts.
2026-08-06 17:17:24 +08:00
wweir d4825deed2 fix(admin): harden console review findings 2026-08-06 10:30:47 +08:00
wweir 1e87298d15 feat(admin): embedded admin console for rule management and traffic stats
- internal/admin: session-cookie auth server, rule CRUD API, bounded
  per-domain traffic stats with pre-parse byte accounting
- cmd/sower: wrap proxied connections and DNS queries into stats, serve the
  admin console on its own listener
- web/: Svelte 5 + Vite admin console with shadcn-svelte UI and an advanced
  breadcrumb header (section/context dropdowns, favorites), embedded via
  go:embed and committed to web/dist
- Makefile: sower build depends on the frontend build (bun); sowerd does not
- docs: ARCHITECTURE, README, and plan updated for the admin surface
2026-08-06 08:56:33 +08:00