Files

OpenGFW Web UI

A Vue 3 + Tailwind CSS dashboard for OpenGFW, built with shadcn/ui style components on top of Reka UI primitives. It is embedded into the OpenGFW binary and served by the web package.

  • Dashboard — live counters, traffic chart, verdict/protocol split, top hosts, blocked destinations and triggered rules
  • Events — real-time feed of verdicts, rule logs and errors with filters and a detail view showing raw analyzer properties
  • Rules — a visual condition builder, a raw expression editor and a YAML editor, all validated by the engine itself; saving writes the rule file and hot reloads the running engine
  • Analyzers — which analyzers are compiled in and how much traffic each one saw
  • Settings — theme (light/dark/system), language (English/中文) and instance info

The layout is responsive: a sidebar on desktop, a bottom tab bar and bottom-sheet dialogs on phones.

Enabling it

# config.yaml
web:
  enabled: true
  listen: :8080
  secret: your-password-here
  # cert: /path/to/fullchain.pem
  # key: /path/to/privkey.pem

If secret is empty a random password is generated and printed to the log on startup.

Rule builder

Rules are still plain expr expressions in the rule file; the builder is only a way to write them without memorising the syntax. Conditions are rows of field + operator + values, joined with AND or OR, each row negatable:

Field group Fields
Connection transport protocol, source/destination IP, source/destination port
Domain TLS SNI, QUIC SNI, DNS query name
HTTP Host, path, method, User-Agent
Protocol detected protocol (any analyzer)

Operators cover the things rules usually need:

Operator Generated expression
domain or subdomain of (S == "x.com" || S endsWith ".x.com")
matches wildcard *.x.com → endsWith, x.* → startsWith, *ad* → contains, a.*.c → matches
in CIDR cidr(ip.dst, "10.0.0.0/8"), validated as you type
in GeoIP country geoip(ip.dst, "cn"), picked from the loaded database
in GeoSite category geosite(string(.name), "category-ads-all@cn")
in range (port.dst >= 1000 && port.dst <= 2000)
is / contains / starts / ends / regex the matching expr operator

Multiple values in one row are OR-ed together, so one row can hold a whole domain or country list. The generated expression is shown live and validated by the engine before the rule is accepted.

Opening an existing rule parses its expression back into conditions. Anything the builder cannot represent — hand written expressions, functions like lookup() — opens in the expression editor with a warning instead of being rewritten.

The GeoIP picker lists whatever the configured geoip.dat contains: country codes plus, with the default Loyalsoldier database, provider groups such as cloudflare, google and telegram. Matching by AS number is not something the v2geo data format supports, so use those groups or an explicit CIDR list instead.

web/frontend/src/lib/rule/ holds the whole thing: fields.ts (catalog), compile.ts (builder → expr), parse.ts (expr → builder) and validate.ts. The canonical expressions are pinned in ruleset/expr_test.go, which compiles them with the real engine.

Layout

web/
├── api.go          HTTP handlers (JSON API + SSE)
├── auth.go         password login, session tokens
├── hub.go          statistics collection and the live event fan-out
├── server.go       routes, static file serving, public types
├── embed.go        //go:embed of dist
├── devserver/      standalone server with synthetic data (any OS)
├── dist/           built UI, embedded into the binary (committed)
└── frontend/       Vue sources

Development

The engine only builds on Linux, so for UI work there is a standalone server that feeds the UI synthetic traffic and an in-memory ruleset:

go run ./web/devserver     # http://127.0.0.1:8080, password: opengfw

Then, in another terminal, run Vite with hot reload (it proxies /api to :8080):

cd web/frontend
npm install
npm run dev

To produce the embedded build (this is what make web runs):

cd web/frontend && npm run build   # writes ../dist

web/dist is committed so that go build works without Node installed. Rebuild it whenever you change the frontend.

API

All endpoints live under /api/v1 and return JSON. Except for login, every request must carry Authorization: Bearer <token>; GET endpoints also accept the session cookie set at login, which is what the EventSource connection uses. Mutating endpoints only accept the bearer token, which makes them immune to CSRF.

Method Path Description
POST /login exchange the password for a session token
POST /logout invalidate the current session
GET /info version, platform and engine configuration
GET /meta available analyzers, modifiers, actions, functions
GET /geo GeoIP/GeoSite entries for the rule builder pickers
GET /metrics counters, time series and top N lists
GET /events?limit= recent events from the ring buffer
GET /live server-sent events: event and metrics frames
GET/PUT /rules read / replace the ruleset (raw YAML or rules)
POST /rules/validate compile without applying; also converts YAML ⇄ rules