Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
0aab543
web: fix stale auth_login url name; drop leftover empty packages
mik3y Aug 3, 2026
bfaa79c
api: add list filtering, page sizing, and a current-session endpoint
mik3y Aug 3, 2026
7a2d58a
api: add the /api/users/me boot endpoint; share constants via codegen
mik3y Aug 3, 2026
cbb8921
api: expose per-entity stats; look up users by username
mik3y Aug 3, 2026
d97c5b5
api: add keg and tap lifecycle operations
mik3y Aug 3, 2026
9c5ed5e
api: add drink management and picture uploads
mik3y Aug 3, 2026
7e37427
api: add account self-service and auth flow endpoints
mik3y Aug 3, 2026
393fba1
api: add admin user management and site settings
mik3y Aug 3, 2026
ead55c0
api: add admin ops endpoints
mik3y Aug 3, 2026
83527ff
api: add setup wizard and upgrade endpoints
mik3y Aug 3, 2026
f43c743
api: add plugin listing and settings endpoints
mik3y Aug 3, 2026
87473a4
api: name generic enums; freeze the openapi schema
mik3y Aug 3, 2026
5d613de
web-ui: scaffold the react frontend
mik3y Aug 3, 2026
b022cd4
web-ui: boot flow, core contexts, and auth views
mik3y Aug 3, 2026
6fd320d
web-ui: public browsing views
mik3y Aug 3, 2026
8acae97
web-ui: account area
mik3y Aug 3, 2026
974ad83
web-ui: admin area
mik3y Aug 3, 2026
8f44ef6
web-ui: setup wizard and upgrade flow
mik3y Aug 3, 2026
d5beab5
web: serve the spa from django; dev on localhost:8000
mik3y Aug 3, 2026
c65e132
web: remove the server-rendered ui
mik3y Aug 3, 2026
ae75457
docs: changelog and readme for the new web interface
mik3y Aug 3, 2026
fbb34ff
web-ui: anchor the dev proxy rules to path segments
mik3y Aug 3, 2026
58ddafb
web-ui: first design pass — theme, type system, mode switch
mik3y Aug 3, 2026
4425490
web-ui: recompose the homepage around the taps
mik3y Aug 3, 2026
246d869
web-ui: structure primitives — header anatomy, widths, de-boxing
mik3y Aug 3, 2026
07444a5
web-ui: table conventions
mik3y Aug 3, 2026
6385cd5
web-ui: design the kiosk mode
mik3y Aug 3, 2026
b289b43
web-ui: design feedback round — gauges, drink page, stat cells
mik3y Aug 4, 2026
991f323
web-ui: make the session date hierarchy browsable again
mik3y Aug 4, 2026
ff4a8f4
web-ui: add a quiet site footer
mik3y Aug 4, 2026
8838b00
api: add GET /api/sessions/directory
mik3y Aug 4, 2026
ef81281
web-ui: drive the session archive from the directory endpoint
mik3y Aug 4, 2026
c9d88b9
web-ui: calendar views for the session archive
mik3y Aug 4, 2026
dcb4a68
web-ui: year tiles on the sessions overview
mik3y Aug 4, 2026
1d3ed03
web-ui: day-view session cards; month calendar in a right column
mik3y Aug 4, 2026
031cf26
web-ui: full archive breadcrumb on drink pages
mik3y Aug 4, 2026
7a034fc
web-ui: fix blank volume-by-weekday charts
mik3y Aug 4, 2026
e8204e1
build: run biome and tsc from pre-commit
mik3y Aug 4, 2026
a2bdd90
docs: cover the two-server dev workflow in the developer guide
mik3y Aug 4, 2026
34a891b
ci: smoke-test the upgraded site via api endpoints
mik3y Aug 4, 2026
f377301
web-ui: preserve the host header in the dev proxy
mik3y Aug 4, 2026
3933a80
web-ui: never drop form errors silently
mik3y Aug 4, 2026
9c2c5ec
api: keep session date bucketing out of sql
mik3y Aug 4, 2026
e95c5c6
api: always include declared fields in plugin settings
mik3y Aug 4, 2026
268b13c
web-ui: unit-aware tick labels on volume chart axes
mik3y Aug 4, 2026
0e02243
web-ui: render UserLink as an inline span
mik3y Aug 4, 2026
671b10f
web-ui: structured admin logs table
mik3y Aug 4, 2026
53ba4ca
web-ui: compact log metadata; dense view toggle
mik3y Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ build
dist
test-db.sqlite

### frontend
node_modules/

/tmp

### vite dev cache
.vite/
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ repos:
args: [--fix]
# Run the formatter.
- id: ruff-format

- repo: local
hooks:
- id: biome-check
name: biome check
entry: bun run biome check --write --files-ignore-unknown=true --no-errors-on-unmatched
language: system
types_or: [javascript, jsx, ts, tsx, json, css]
- id: typecheck
name: tsc typecheck
entry: bun run typecheck
language: system
types_or: [ts, tsx]
pass_filenames: false
15 changes: 13 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Frontend build stage: compiles the web-ui bundle with bun + vite.
FROM oven/bun:1-slim AS frontend
WORKDIR /build
COPY package.json bun.lockb bunfig.toml ./
RUN bun install --frozen-lockfile
COPY vite.config.ts tsconfig.json openapi-ts.config.ts ./
COPY web-ui ./web-ui
RUN bun run build


FROM python:3.14-slim

RUN mkdir /app
Expand Down Expand Up @@ -50,8 +60,9 @@ COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked

# Collect static files. Use fake versions of required env variables
# since they're not relevant at this step.
# Bring in the built frontend, then collect static files. Use fake
# versions of required env variables since they're not relevant here.
COPY --from=frontend /build/web-ui/dist ./web-ui/dist
RUN DATABASE_URL=mysql:// \
REDIS_URL=redis:// \
KEGBOT_SECRET_KEY=changeme \
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,31 @@ $ open http://localhost:8000/
For much more detail, see the complete [Kegbot Server documentation](https://docs.kegbot.org/projects/kegbot-server/en/latest/).


## Development

The web interface is a React single-page app (in `web-ui/`) served by the
Django backend (in `pykeg/`). For development, run both servers and browse
the vite dev server, which proxies API requests to Django:

```
$ uv sync # python dependencies
$ bun install # frontend dependencies
$ kegbot runserver # django, on port 8001
$ bun run dev # vite, on http://localhost:8000 <-- browse here
```

Other useful commands:

```
$ uv run pytest # backend tests
$ bun run test # frontend tests
$ bun run check # frontend lint + typecheck
$ bun run build # production frontend build (web-ui/dist)
$ bun run generate-api # regenerate the API client from the schema
$ bun run generate-constants # regenerate web-ui/lib/shared-constants.ts
```


## Documentation and Help

* Main project page: https://kegbot.org/
Expand Down
12 changes: 9 additions & 3 deletions bin/check-upgraded-site.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Run after `kegbot restore <legacy zip>` + `kegbot upgrade`. Compares the
database against testdata/demo-site.json (the data the legacy backups were
built from) and smoke-tests key pages. Exits nonzero on any failure.
built from) and smoke-tests key API endpoints. Exits nonzero on any failure.
"""

import json
Expand Down Expand Up @@ -45,8 +45,14 @@ def check(label, expected, actual):
check("kegs on tap", 2, models.Keg.objects.filter(status=models.Keg.STATUS_ON_TAP).count())

client = Client()
for url in ["/", "/kegs/", "/stats/", "/sessions/", "/accounts/login/"]:
response = client.get(url, follow=True)
for url in [
"/api/users/me",
"/api/status",
"/api/kegs",
"/api/sessions",
"/api/stats/system",
]:
response = client.get(url)
check(f"GET {url}", 200, response.status_code)

api_key = models.ApiKey.objects.first()
Expand Down
56 changes: 56 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": [
"web-ui/**",
"vite.config.ts",
"openapi-ts.config.ts",
"!web-ui/api-client",
"!web-ui/dist",
"!web-ui/lib/shared-constants.ts"
]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUnusedImports": "error",
"noUnusedVariables": "error",
"useExhaustiveDependencies": "off"
},
"style": {
"noRestrictedImports": {
"level": "error",
"options": {
"patterns": [
{
"group": ["../*", "./../*"],
"message": "Use @/ root-relative imports instead of parent-relative paths."
}
]
}
}
}
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
Binary file added bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[test]
preload = ["./web-ui/test/setup.ts"]
36 changes: 34 additions & 2 deletions docs/source/developers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,37 @@ Run the server, or any other command, through ``uv run``:

$ uv run bin/kegbot version
$ uv run bin/kegbot migrate
$ uv run bin/kegbot run_server

Running the dev servers
-----------------------

The web interface is a React single-page app in ``web-ui/``, built with
`bun <https://bun.sh/>`_ and vite. Install the frontend dependencies once:

.. code-block:: console

$ bun install

For development, run two servers in separate terminals:

.. code-block:: console

$ uv run bin/kegbot runserver # django backend, on port 8001
$ bun run dev # vite dev server, on port 8000

Then browse http://localhost:8000. The vite dev server serves the frontend
with hot reload and proxies ``/api``, ``/media``, and ``/static`` requests
to Django, so everything is same-origin and CSRF just works.

Other useful frontend commands:

.. code-block:: console

$ bun run test # frontend tests
$ bun run check # biome lint + tsc typecheck
$ bun run build # production build (web-ui/dist)
$ bun run generate-api # regenerate the API client from the schema
$ bun run generate-constants # regenerate web-ui/lib/shared-constants.ts

Running tests
-------------
Expand All @@ -50,12 +80,14 @@ no redis server:
Code format and lint
--------------------

We use `ruff` to format and lint all code:
We use `ruff` to format and lint Python, and `biome` plus ``tsc`` for the
frontend:

.. code-block:: console

$ uv run ruff format
$ uv run ruff check
$ bun run check

To run these checks automatically before each commit, install the
`pre-commit` hooks:
Expand Down
31 changes: 31 additions & 0 deletions docs/source/releases/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ brought up to date.

**Highlights**

* **The web interface is completely new.** A React single-page app
(Material UI) replaces the server-rendered Django UI. Every part of the
interface was rebuilt: browsing (home, kegs, drinkers, drinks, session
archives, system stats with charts), the fullscreen/kiosk mode (which now
updates in place instead of reloading), account management, registration
and password flows, the full admin area, and the setup wizard (which now
also works in production, not just ``DEBUG``). Public URLs are preserved,
including short links (``/d/<id>``, ``/s/<id>``) and links in older
notification e-mails.
* **The new API now covers everything the web UI does**, including:
list filtering and page sizing; a ``/api/users/me`` boot endpoint;
per-user/keg/session/system stats; keg and tap lifecycle operations
(attach/start/end kegs, record drinks and spills, connect hardware);
drink management and picture uploads; account self-service and
authentication flows (registration, password reset, e-mail change,
activation); admin user management and site settings; backups, logs,
test e-mail, and bugreport endpoints; an API-driven setup wizard; and
plugin settings.
* Python 3.14 is now required (was 3.10).
* Django 5.2 LTS (was 3.2).
* Web server switched from gunicorn/gevent to waitress.
Expand Down Expand Up @@ -45,6 +63,19 @@ brought up to date.
jobs are handed to the worker only after the surrounding database
transaction commits. Ensure ``run_workers`` is running (unchanged) to
process them.
* **Building from source now requires** `bun <https://bun.sh>`_ **for the
frontend.** Run ``bun install && bun run build`` before
``kegbot collectstatic``; the Docker image does this automatically. For
development, run ``bun run dev`` (vite, http://localhost:8000) alongside
``kegbot runserver`` (Django, now defaulting to port 8001); the dev
server proxies API requests to Django.
* **Site privacy is now enforced by the API** and rendered by the frontend;
the server-side privacy interstitials (and
``KEGBOT_EXTRA_PRIVACY_EXEMPT_PATHS``) are gone.
* Plugins no longer provide Django template views; plugin settings are
managed through the plugin settings API. The webhook plugin is otherwise
unchanged.
* The legacy ``ga.js`` Google Analytics snippet is no longer emitted.
* ``run_gunicorn`` was removed. Use ``kegbot run_server`` (now waitress).
* The Docker image no longer publishes a ``linux/arm/v7`` variant (amd64 and
arm64 only).
Expand Down
9 changes: 9 additions & 0 deletions openapi-ts.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "@hey-api/openapi-ts";

export default defineConfig({
// The ./ prefix matters: a bare "a/b" path parses as a Hey API
// platform shorthand and crashes the generator.
input: "./web-ui/schema.yaml",
output: "web-ui/api-client",
plugins: ["@hey-api/client-fetch"],
});
39 changes: 39 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "kegbot-server-ui",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "bun test web-ui/",
"typecheck": "tsc --noEmit",
"check": "biome check web-ui && tsc --noEmit",
"format": "biome format --write web-ui",
"generate-api": "uv run python bin/kegbot spectacular --file web-ui/schema.yaml && openapi-ts",
"generate-constants": "uv run python bin/kegbot print_constants"
},
"dependencies": {
"@emotion/react": "^11",
"@emotion/styled": "^11",
"@fontsource/ibm-plex-mono": "^5.3.0",
"@fontsource/ibm-plex-sans": "^5.3.0",
"@mui/icons-material": "^9",
"@mui/material": "^9",
"@mui/x-charts": "^9",
"react": "^19",
"react-dom": "^19",
"react-router": "^8"
},
"devDependencies": {
"@biomejs/biome": "^2",
"@happy-dom/global-registrator": "^18",
"@hey-api/openapi-ts": "^0.80",
"@testing-library/react": "^16",
"@types/bun": "^1",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^4",
"typescript": "^5",
"vite": "^7"
}
}
Loading