Live structural view of an Artifacts server. A feature-gated companion binary that talks HTTP(S) to a running artifacts instance and renders what's there with eframe/egui. Useful for operations (is anything happening?), debugging (why does this fork have no parent?), and demos (show someone what Artifacts is without teaching them git).
Copy-paste from an interactive shell (Wayland or X11, either works):
cd /data4/ex-cc/artifacts
# 1. Build both binaries — server + GUI — in release mode.
cargo build --release --bin artifacts
cargo build --release --bin artifacts-gui --features gui
# 2. Start the server in the background. Pick any admin token string;
# the GUI will need to present it. (Below we use a dev default.)
mkdir -p ./data
ARTIFACTS_ADMIN_TOKEN=dyspel-dev-shared \
./target/release/artifacts serve \
--data-dir ./data \
--bind 127.0.0.1:8787 \
--public-base-url http://127.0.0.1:8787 \
> /tmp/artifacts-server.log 2>&1 &
disown
sleep 1
curl -fsS http://127.0.0.1:8787/v1/health && echo
# 3. (Optional) Seed one source + one fork so the GUI has something to show
TOK=dyspel-dev-shared
SRC=$(curl -fsS -X POST -H "Authorization: Bearer $TOK" \
http://127.0.0.1:8787/v1/repos | python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
curl -fsS -X POST -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
-d '{}' http://127.0.0.1:8787/v1/repos/$SRC/forks > /dev/null
# 4. Launch the GUI. IT MUST BE RUN FROM YOUR INTERACTIVE SHELL so it
# inherits your session's WAYLAND_DISPLAY / DISPLAY / XDG_RUNTIME_DIR.
ARTIFACTS_ADMIN_TOKEN=dyspel-dev-shared \
./target/release/artifacts-gui --url http://127.0.0.1:8787The window opens titled artifacts-gui, ~980×680. Use the left
sidebar to switch between Overview / Repos / Forks.
pkill -f 'artifacts-gui' # close the window
pkill -f 'artifacts serve' # stop the server(The server's SQLite db at ./data/tokens.db and the bare repos under
./data/repos/ persist across restarts — that's the point.)
artifacts-gui [OPTIONS]
--url <URL> Base URL of the Artifacts server.
Default: http://127.0.0.1:8787
--admin-token <TOKEN> Required. Env: ARTIFACTS_ADMIN_TOKEN
--poll-interval-secs <SECS> How often to refresh. Default: 2.0
--admin-token reads ARTIFACTS_ADMIN_TOKEN from the environment if
the flag isn't set — keeps the token out of shell history.
Linux with Wayland or X11. eframe picks the backend via winit at startup; on a Wayland session you get Wayland, on an XWayland-only session you get X11. No config needed if you run the GUI from a shell that already has your session's display env vars. See Troubleshooting for when it doesn't.
Not tested on macOS or Windows. eframe supports both in principle; if you try it there, pass the default build through and see what happens.
Overview — the most useful tab. Shows:
- Server version from
artifacts_build_info - Repo count
- Cumulative request / rate-limited / quota-exceeded counters
- Aggregate p50/p95/p99 latency (current value)
- Three time-series charts over the last 5 minutes of polls:
- Requests / sec (derived from counter deltas between samples)
- Latency percentiles (p50, p95, p99 as three lines)
- Rate-limited / quota-exceeded per sec (usually zero; non-zero means something operator-worthy is happening)
The charts use egui_plot. X-axis is seconds ago — 0 on the right,
−300 on the left. Y always starts at 0 so a flat quiet line stays
visible instead of jumping around on an auto-ranged axis. First
couple of polls show "collecting samples…" because at least two
samples are needed to compute a rate.
Repos — four-column scrollable table: id, owner (or <admin>),
relative created-at, and source id (the parent repo in a fork chain,
or — for roots). Click any id to select that repo — it highlights
in the list and the Detail tab populates with its refs, size on disk,
and fork source. Ordering is whatever the server returns, which is
created_at DESC (newest first).
Forks — graphical tree. Roots at the top, forks attached below their parent by a line edge. Each node is a rounded rectangle with the short repo id + owner. Controls:
- Drag to pan
- Scroll to zoom (centered on cursor, clamped 0.25×..4×)
- Click a node to select it → flips to the Detail tab
- "fit" button in the header resets pan/zoom to defaults
Nodes are color-coded:
- green — root (repo created from scratch)
- gray — fork (source_id present and parent is in the list)
- amber — orphan (source_id set but parent not in the list; either the parent was deleted or the admin-list view is partial)
- blue — selected, regardless of the above
A legend stays pinned in the top-left of the canvas independent of pan/zoom.
Layout is deterministic: siblings sort by id before placement, so the tree doesn't jitter across polls even if the server returns rows in a different order.
Detail — populated when you click a repo in Repos or Forks. Shows:
- id, owner, created-at, fork-of (source_id, if any)
- on-disk size (humanized:
1.50 KB,2.34 MB, etc.) - full ref list: name + short SHA prefix (full SHA on hover)
Data source is GET /v1/admin/repos/:id. The fetch is triggered by
the background poller — selection is written to a shared slot, and on
the next poll cycle (up to ~2s later) the detail lands in the view.
This means zero UI-thread blocking on click, but also up to a
poll-interval of latency before the detail appears. If selection
changes mid-fetch, the racing response is discarded.
Intentionally:
- No create / fork / delete / revoke buttons. Mutation stays in curl or a proper client SDK. Eliminates a whole class of accidental- click incidents.
- No commit-log view. The Detail tab shows refs (branches + tags)
but not individual commits or their graph.
git log-style history would need a new server endpoint. - No token browsing / audit log. Tokens are hashed in the DB; the server never exposes them through the admin API and the GUI never asks.
- No long-term historian. The 5-minute ring is for live-watching
activity, not for post-hoc analysis. Scrape
/metricsinto Prometheus / VictoriaMetrics / Grafana if you want a historical record. - No authentication UI. The admin token is a single static value. The server's JWT path exists; the GUI only speaks admin auth today.
The most common cause is a shell that doesn't have your session's
display env vars. SSH'd shells and some dev-tool environments inherit
an empty WAYLAND_DISPLAY, which winit treats as "Wayland configured
but unreachable" and the GUI hangs silently.
Check from the same shell you're launching the GUI in:
env | grep -E 'WAYLAND_DISPLAY|DISPLAY|XDG_RUNTIME_DIR'Good output looks like:
WAYLAND_DISPLAY=wayland-0
DISPLAY=:0
XDG_RUNTIME_DIR=/run/user/1000
If WAYLAND_DISPLAY is empty or unset on a Wayland session, export it
explicitly:
export WAYLAND_DISPLAY=wayland-0
export XDG_RUNTIME_DIR=/run/user/$(id -u)The socket path is $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY. Verify it
exists:
ls -l $XDG_RUNTIME_DIR/wayland-0
# should be: srwxrwxr-x ... wayland-0If you're reaching the machine from a different host (SSH, a remote
editor, etc.) and your compositor is on that other host — the GUI
running on this machine can't reach it. Either run the GUI on your
local machine and point --url at the remote server, or set up
X11-forwarding (ssh -X) and run the GUI over that.
The server isn't reachable at the URL, or the admin token is wrong. The top-right red text shows the underlying ureq/JSON error. Common causes:
- wrong port (
--url http://127.0.0.1:8787if the server is on its default bind) - admin token mismatch — the token the GUI presents must match the
server's
ARTIFACTS_ADMIN_TOKEN - server refused to start because
--bindwasn't loopback and--public-base-urlwasn'thttps://— see the server's log forrefusing to start
No request-duration observations yet. Make a few REST calls:
TOK=dyspel-dev-shared
for _ in $(seq 1 5); do
curl -fsS -o /dev/null http://127.0.0.1:8787/v1/health
curl -fsS -o /dev/null -H "Authorization: Bearer $TOK" \
http://127.0.0.1:8787/v1/admin/repos
doneWithin one poll cycle the percentiles populate.
At least two polls are needed to compute a rate. If you just launched, wait ~4 seconds (two poll cycles) and the lines appear.
Defaults to 2 seconds between polls. Tweak with --poll-interval-secs.
Polls are independent of window repaint — the background thread
updates shared state, egui repaints every 500 ms and reads the
latest. A slow server response doesn't block the UI; the last-poll
label in the top bar shows how stale the view is, and a red error
string surfaces if polling fails.
The time-series window keeps the last ~5 minutes of samples. At the default 2-s interval that's ~150 points — cheap to render, enough resolution to see a spike.
Single binary, single file: src/bin/artifacts-gui.rs. No separate crate, no build config beyond the feature flag. Modifying the GUI means editing that one file.