Add README with usage, proxy mapping and limitations
This commit is contained in:
1 parent
85bad1d0e8
commit
2e78143812
1 file changed
+140
@@ -0,0 +1,140 @@
|
|||||||
|
# UwuDash MK3
|
||||||
|
|
||||||
|
A single Python app that is both the frontend and the backend for a dashboard of
|
||||||
|
internal services. The sidebar has one button per service; clicking it loads the
|
||||||
|
service in an iframe **through the dashboard's built-in reverse proxy**, so the
|
||||||
|
upstream services never need to be public.
|
||||||
|
|
||||||
|
```
|
||||||
|
browser ──► uwudash (host:port) ──► /proxy/<id>/<path> ──► upstream origin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
uv run uwudash-mk3
|
||||||
|
# http://127.0.0.1:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
Development reload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run uwudash-mk3 --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command line flags
|
||||||
|
|
||||||
|
| Flag | Default | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `--host` | `127.0.0.1` | bind address |
|
||||||
|
| `--port` | `8000` | bind port |
|
||||||
|
| `--data-dir` | `data` | directory holding `services.json` |
|
||||||
|
| `--services-file` | `<data-dir>/services.json` | explicit store path |
|
||||||
|
| `--reload` | off | uvicorn reload on source changes |
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
|
||||||
|
| Variable | Default | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `UWUDASH_HOST` | `127.0.0.1` | bind address |
|
||||||
|
| `UWUDASH_PORT` | `8000` | bind port |
|
||||||
|
| `UWUDASH_DATA_DIR` | `data` | data directory |
|
||||||
|
| `UWUDASH_SERVICES_FILE` | `data/services.json` | services store path |
|
||||||
|
| `UWUDASH_PROXY_TIMEOUT` | `30.0` | upstream request timeout (seconds) |
|
||||||
|
| `UWUDASH_STRIP_FRAME_OPTIONS` | `true` | strip `X-Frame-Options` and CSP `frame-ancestors` from proxied responses |
|
||||||
|
|
||||||
|
CLI flags override environment variables.
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
Services live in `data/services.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"id": "grafana",
|
||||||
|
"name": "Grafana",
|
||||||
|
"url": "http://10.6.9.199:3000/dashboards",
|
||||||
|
"description": "Metrics dashboards",
|
||||||
|
"icon": "G",
|
||||||
|
"open_in": "iframe",
|
||||||
|
"enabled": true,
|
||||||
|
"headers": { "Authorization": "Bearer ..." }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `id` is generated from the name (slugified, unique) and stays stable across edits.
|
||||||
|
- `url` may include a path; that path becomes the landing path of the proxy.
|
||||||
|
- `open_in` is `iframe` (default) or `newtab`.
|
||||||
|
- `headers` are injected into every proxied request to that service.
|
||||||
|
- `enabled: false` keeps the entry in the sidebar but the proxy returns `403`.
|
||||||
|
|
||||||
|
Edit them from the **Settings** page in the UI, or via the API.
|
||||||
|
|
||||||
|
## How the proxy works
|
||||||
|
|
||||||
|
Only the upstream **origin** (`scheme://host:port`) is mapped onto the dashboard:
|
||||||
|
|
||||||
|
```
|
||||||
|
/proxy/<id> -> <origin>/
|
||||||
|
/proxy/<id>/<path> -> <origin>/<path>
|
||||||
|
```
|
||||||
|
|
||||||
|
So a service with `url = http://internal-host:8080/sub/page` is served at
|
||||||
|
`/proxy/<id>/sub/page`, and any link the upstream page emits to
|
||||||
|
`http://internal-host:8080/...` or `//internal-host:8080/...` is rewritten to
|
||||||
|
`/proxy/<id>/...`. Relative links need no rewriting.
|
||||||
|
|
||||||
|
Details:
|
||||||
|
|
||||||
|
- `Location` headers on `3xx` responses are rewritten back onto the proxy prefix;
|
||||||
|
redirects are **not** followed, so the browser stays on the proxy.
|
||||||
|
- `text/html` responses are buffered and rewritten; everything else (JSON, CSS,
|
||||||
|
images, downloads) is streamed through unchanged.
|
||||||
|
- `X-Frame-Options` and CSP `frame-ancestors` are stripped from proxied responses
|
||||||
|
so the iframe can render (disable with `UWUDASH_STRIP_FRAME_OPTIONS=false`).
|
||||||
|
- Hop-by-hop request/response headers are dropped; per-service `headers` are merged
|
||||||
|
into the upstream request.
|
||||||
|
- Request bodies are buffered with a 10 MB cap (`413` above it).
|
||||||
|
- WebSocket upgrades are rejected with `400` - the proxy is HTTP only.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| Method | Path | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET` | `/api/services` | list services |
|
||||||
|
| `POST` | `/api/services` | create |
|
||||||
|
| `GET` | `/api/services/{id}` | read |
|
||||||
|
| `PATCH` / `PUT` | `/api/services/{id}` | update |
|
||||||
|
| `DELETE` | `/api/services/{id}` | delete |
|
||||||
|
| `GET` | `/api/services/{id}/health` | upstream reachability check |
|
||||||
|
| `GET` | `/api/config` | effective config |
|
||||||
|
| `GET` | `/health` | dashboard health |
|
||||||
|
| `ANY` | `/proxy/{id}` and `/proxy/{id}/{path}` | reverse proxy |
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
src/uwudash_mk3/
|
||||||
|
config.py env-driven Config dataclass
|
||||||
|
models.py Service / ServiceInput / ServicePatch + URL helpers
|
||||||
|
store.py JSON file store (atomic writes, lock)
|
||||||
|
proxy.py reverse proxy: header filtering, HTML/redirect rewriting
|
||||||
|
api.py /api routes
|
||||||
|
app.py FastAPI factory, static mount, proxy routes
|
||||||
|
main.py CLI entrypoint
|
||||||
|
templates/ index.html
|
||||||
|
static/ styles.css, app.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- No authentication on the dashboard itself - it is meant for a trusted LAN.
|
||||||
|
- No WebSocket proxying.
|
||||||
|
- Upstream cookies work, but they are scoped to the dashboard origin, so
|
||||||
|
cross-service cookie sharing is possible if two services share a proxy host.
|
||||||
Reference in new issue
Block a user