Staff push notifications, and revalidate embedded assets

Notifications: staff turn them on per device from a new Notifications page,
pick each type (bans, gags, mutes and silences added or lifted, server down
and back up) and quiet hours. A watcher polls SimpleAdmin's tables, so in-game
penalties are sent too. Web Push with VAPID and RFC 8291 encryption in
internal/push, standard library only. Tables in sql/push.sql, created at
startup; the VAPID key is made on first start and kept in saw_settings.
Penalty notifications link to the public list searching for the player
(#bans?q=<steamid64>). The Android app delegates notifications (versionCode 3).

Caching: pages, scripts and styles are served with Cache-Control: no-cache
and an ETag, so a deploy shows up at once instead of after Cloudflare's
default 4-hour browser cache.
This commit is contained in:
Astra 2026-10-03 13:49:40 +01:00
parent 7900bf5b3e
commit 17232bd5a2
23 changed files with 2135 additions and 12 deletions

View file

@ -9,6 +9,8 @@ A SourceBans-style web panel for [CS2-SimpleAdmin](https://github.com/daffyyyy/C
- **Staff panel** (`/admin`): sign in through Steam. Ban, gag, mute, silence, warn and kick; lift
bans and blocks; manage ranks, permissions and staff; change server cvars and SimpleAdmin's
punishment settings.
- **Notifications** (staff): push notifications for new and lifted bans, gags, mutes and silences,
and the game server going down, on phones and computers, with quiet hours (see below).
- **Verification** (optional): who our GroupVerify plugin verifies, with overrides to give and
remove, and its settings. Only appears when GroupVerify is installed (see below).
@ -163,6 +165,28 @@ rsvg-convert -w 512 -h 512 icon-maskable.svg -o icon-maskable-512.png
rsvg-convert -w 180 -h 180 icon-maskable.svg -o apple-touch-icon.png
```
### Notifications
Staff turn notifications on per device from the panel's **Notifications** page, and pick which
types they want and their quiet hours there; those apply to all their devices. It's standard Web
Push: it works in Chrome, Edge, Firefox and Safari (on iPhone only from a home-screen install), and
in the Android app, where they show as the app's own.
- **Types**: ban added or lifted, and the same for gags, mutes and silences, each on its own
switch; and the game server down (no RCON answer for 30 seconds) and back up. "Live score" and
"Admin called" are listed but switched off until they're built (admin calls need a plugin).
- **What's watched**: SimpleAdmin's `sa_bans`, `sa_mutes`, `sa_unbans` and `sa_unmutes`, every 5
seconds, so penalties made in game are sent too, not just ones from the panel. New rows wait 7
seconds first, so panel actions are credited to the staff member rather than "Console".
- **Who gets them**: staff who can use the panel (`@css/generic`), checked on every send, so people
removed from staff stop getting them at once. Nobody is told about their own actions.
- **Links**: tapping a penalty notification, or its **View player** button, opens the public ban or
gag/mute list searching for the player (`/#bans?q=<steamid64>`, `/#comms?q=...`).
- **Setup**: none. The panel creates `saw_push_devices`, `saw_push_prefs` and `saw_settings` at
startup (`sql/push.sql`), and makes its VAPID key on first start, kept in `saw_settings`. Deleting
that key stops every device until it's turned on again. Messages are encrypted for each device
(RFC 8291) and only sent to the browsers' own push services (Google, Mozilla, Apple, Microsoft).
### Android app
`android/` is the site as an Android app: a Trusted Web Activity, which opens the public site in
@ -183,7 +207,9 @@ Back it up: an APK signed with another key won't install over the old one. Then:
- Bump `versionCode` in `android/app/build.gradle` for every APK you hand out.
- The site and start page are at the top of `android/app/build.gradle`; never change `applicationId`.
The app needs Chrome (or another browser with TWA support) on the phone.
The app needs Chrome (or another browser with TWA support) on the phone. Notifications from the
panel are delegated to the app (`DelegationService`), so they show under its name and icon, and
Android 13+ asks for the app's notification permission when they're turned on.
## Limitations and caveats
@ -217,5 +243,9 @@ podman run -d --rm --name sa-test -e MYSQL_ROOT_PASSWORD=test -e MYSQL_DATABASE=
SAW_TEST_DSN='root:test@tcp(127.0.0.1:33061)/sa?parseTime=true&loc=UTC' go test ./internal/web -run VerifyDB
```
`internal/web/notify_db_test.go` does the same for notifications (`-run NotifyDB`): the endpoints,
and the watcher sending to a fake push service. It needs only SimpleAdmin's migrations, and empties
its ban, mute, admin and group tables and the panel's push tables.
The pages are plain HTML, CSS and JS in `internal/web/assets`, embedded into the binary. The
design mockup they came from is in `docs/mockup` at the repo root.