Skip to content

Tags: zechtz/vertex

Tags

v2.6.0

Toggle v2.6.0's commit message
v2.6.0: recover a forgotten password, and Java in the Docker image

v2.5.0

Toggle v2.5.0's commit message
v2.5.0: attach a debugger to services Vertex runs

v2.4.3

Toggle v2.4.3's commit message
v2.4.3: bounded database and log, no env values in logs

v2.4.2

Toggle v2.4.2's commit message
v2.4.2 — A dashboard that admits when it is stale

The service grid is fed entirely by the /ws socket, and nothing ever reopened
it. A sleep, a server restart or a brief network drop ended the feed for the
life of the tab, and the dashboard carried on rendering the last states it
heard. A stopped service still showed green, indistinguishable from a running
one, and the only way to find out was to reload — the same reload the session
work in v2.4.0 existed to remove.

The realtime feed
  The socket now reconnects itself, backing off from a second to a cap of
  thirty so a server that is down is not hammered while a machine waking from
  sleep is back almost at once.

  Reconnecting is only half of it. The server sends no snapshot on connect —
  websocketHandler adds the client to its broadcast list and nothing more — so
  everything that happened while the socket was down was missed and is never
  resent. Services are refetched on any reopen after the first, which is what
  actually makes the view current again.

  While the feed is down the toolbar says so. A permanent "connected" light
  would be noise; silence while disconnected is misinformation.

  Three more faults sat in the same effect. The socket was rebuilt every time
  a service was selected, because selectedService was read inside onmessage
  and so had to be a dependency; those reads are now functional state updates
  and the socket mounts once. Log entries accumulated without limit for as
  long as the tab stayed open, and are capped at a thousand per service.
  JSON.parse ran unguarded on every frame, so one malformed message threw
  inside the handler.

Dialogs
  Two things were already solved in components/ui and not used.

  The Modal primitive handled Escape, backdrop clicks and scroll lock, but was
  invisible to assistive technology and leaked focus: the page behind a modal
  was inert to the eye and not to the keyboard, so tabbing walked out of the
  dialog into controls nobody could see. It now identifies itself as a dialog,
  is labelled by its own title, keeps Tab inside itself, and hands focus back
  to whatever opened it.

  Six call sites still used window.alert and window.confirm while the app had
  toast and confirm-dialog for exactly this. Native dialogs block the page,
  ignore dark mode and look nothing like the rest of the app. Failures are now
  error toasts, the two destructive actions go through the styled confirm, and
  the two "coming soon" notices are info toasts.

  Six dialogs moved off their own overlays onto the primitive: system metrics,
  service actions, topology, uptime detail, user profile and wrapper
  management. The primitive grew 3xl through 7xl widths and a node title
  first, because the dialogs that must follow need both.

Still outstanding
  Eighteen dialogs continue to build their own overlay, and so still miss
  Escape, the scroll lock, the dialog role and the focus trap. The six that
  moved are verified by typecheck and build, not by eye.

v2.4.1

Toggle v2.4.1's commit message
v2.4.1 — Services you can reach offline

Turning the internet off left the client unable to reach any service. The
per-service Eureka overrides that made offline work possible were still read
at startup, but nothing could set them.

Eureka registration overrides
  The feature had landed in two halves. 52e117c added it whole on
  feat/local-dev — the database columns, the load and save paths, the API
  field, and the panel in the service config modal — but that branch was never
  merged. What reached main was 811d12c, a cherry-pick of an unrelated Maven
  wrapper fix that happened to carry the environment variable injection out of
  operations.go and nothing else. main gained the reader without the writer.

  The gap surfaced only as a build failure. 81764bd added the two
  models.Service fields so the package would compile again, and noted at the
  time that the feature was inert. It stayed inert: EurekaPreferIPAddress was
  always nil and EurekaHostname always "", so both injection branches were
  unreachable and the config server's prefer-ip-address won unopposed. Offline,
  that resolves to a hostname nothing can reach.

  The missing half is now in place — the columns and their migration, the
  scans and writes in every load and save path, the field on
  ServiceConfigRequest and its mapping in UpdateService, and the override panel
  in the modal. Set prefer-ip-address on a service and it registers by IP,
  which survives losing the network.

Session renewal across a sleep
  The renewal prompt could appear for a token that had already expired, and
  Continue then failed with no way forward but a reload — the behaviour the
  feature exists to remove. The warning had been scheduled with a single
  setTimeout up to a day ahead, which does not survive the machine sleeping.
  The phase is now polled, so a sleep is noticed on wake, and a token past its
  expiry hands over to the sign-in prompt rather than offering a renewal that
  cannot succeed.

Upgrading
  Both Eureka fields are unset by default, so no service changes behaviour
  until prefer-ip-address is set on it. Existing rows migrate to NULL, which
  reads back as no override, and the migration re-runs as a no-op.

v2.4.0

Toggle v2.4.0's commit message
v2.4.0 — Sessions that do not interrupt you

An expired session used to make actions quietly stop working. Nothing said
why, and the redirect to the login page only happened on the next full page
load, so the way to discover it was to reload — which is not something a
user should have to think to do.

Two layers now cover it.

Before expiry
  The token states its own expiry, so the app schedules a warning locally
  rather than discovering the problem by having a request fail. Five minutes
  out, a prompt offers to continue; one click renews the session in the
  background, with no password and no interruption. POST /api/auth/refresh
  issues the new token, and requires a still-valid one — an expired session
  goes back through login, so this extends an active session rather than
  reviving a dead one.

After expiry
  If nobody was watching the tab, the session still ends. A 401 no longer
  fails the request: it is parked, the app is told once however many
  requests failed together, and each is replayed once the user signs back
  in — so the restart they clicked still happens. The prompt appears over
  the page rather than redirecting, because a redirect discards scroll
  position, open dialogs, filters and half-typed input, and leaves the user
  to remember what they had been doing.

Underneath, every authenticated request now goes through one place. Six
modules previously read the token from localStorage and called fetch
themselves, and almost none recognised a 401 — one had the handling written
with the logout call commented out.

Upgrading

  No action required. Existing tokens keep working and pick up the warning
  at their next expiry.

v2.3.0

Toggle v2.3.0's commit message
fix(ui): show the version actually running in the sidebar

The sidebar read "v2.0.0", hardcoded. The binary reports its version from a
git tag linked in at build time, so the two were never going to agree - the
running build is v2.2.7-1-gcc75c75.

Serve the linked-in build information from GET /api/version and read it in
the sidebar, so there is one source of truth and nothing to remember to
update at release time. Build info is passed to NewHandler rather than set
afterwards: there is a single call site, and a constructor argument cannot
be silently forgotten the way a setter can.

The version string needs normalising for display. The Makefile strips the
"v" prefix when building an exact tag but git describe keeps it otherwise,
so a tagged build yields "2.2.7" and an untagged one "v2.2.7-1-gcc75c75";
rendering either directly gives "2.2.7" or "vv2.2.7". formatVersion strips
any prefix and adds exactly one back.

If the request fails the line is omitted rather than falling back to a
constant, since showing a wrong version is what this replaces. Commit and
build date are available as a tooltip.

(cherry picked from commit e74c4c8fe5c2067335fdd5834627ec5111b6195f)

v2.2.7

Toggle v2.2.7's commit message
v2.2.7 — Configuration access no longer freezes

Fixes the intermittent freeze where Vertex stayed reachable — login kept
working — but configuration and service views hung for minutes, sometimes
until a restart. Login survived because authentication talks straight to
the database; everything routed through the service manager did not.

Four independent causes, each serialising unrelated work behind a single
blocking operation.

Database
  SQLite ran in rollback-journal mode, where every write takes an
  EXCLUSIVE lock on the entire file and blocks every reader. It now opens
  in WAL with an explicit busy timeout and a bounded connection pool.
  Log lines are persisted in batches instead of one INSERT per line, and
  the daily cleanup runs as small batched transactions rather than one
  long transaction that held the write lock from start to finish — and no
  longer takes application locks while that transaction is open.

Manager locking
  GetServices held the manager lock while waiting on individual service
  locks, and service locks were held across HTTP health checks. Because
  sync.RWMutex is writer-preferring, one slow service plus one queued
  writer parked every subsequent reader — including configuration reads,
  which touch no database at all. A service lock is now never taken while
  the manager lock is held, and no blocking I/O happens under one.

Websockets
  Broadcasts wrote to clients while holding the global client lock with no
  write deadline, so a browser tab that stopped draining its socket could
  pin that lock — and the service lock above it — until restart. Writes
  now happen outside that lock, bounded by a deadline, behind a
  per-connection write lock.

Also fixes a data race where the log broadcaster read the service map
under the wrong mutex.

Build
  The release image moved off golang:1.23-bullseye, whose security
  Release file expired when Debian 11 left LTS on 2026-08-31 and broke
  every build with apt exit code 100. It now builds on
  golang:1.23.10-bookworm, and the docker job gained the QEMU setup its
  linux/arm64 leg requires.

Upgrading
  Existing databases migrate to WAL automatically on first start.

  Log churn leaves behind free pages that SQLite never returns to the
  filesystem. If your database has grown, reclaim it while Vertex is
  stopped:

    sqlite3 vertex.db "VACUUM INTO 'vertex.new.db'" && mv vertex.new.db vertex.db

  A 3.6 GB database compacted to 73 MB in testing — 98% of it was
  freelist left over from log deletion.

v2.2.6

Toggle v2.2.6's commit message
fix: fix error generating maven wapper

v2.2.5

Toggle v2.2.5's commit message
Release version 2.2.5