Continuum Observer is an optional Phoenix LiveView UI for inspecting workflow
runs. It is not started by Continuum.Application; mount it in your Phoenix
router when your app directly depends on :phoenix_live_view and
:phoenix_html. Continuum declares Phoenix as optional; host applications that
mount the Observer must include the Phoenix dependencies they use.
defmodule MyAppWeb.Router do
use MyAppWeb, :router
import Continuum.Observer.Router
scope "/admin" do
pipe_through [:browser, :authenticate_admin]
continuum_observer "/continuum", instance: :myapp_continuum
end
endDo not mount the Observer publicly. Continuum does not ship authentication or authorization callbacks in v0.2; the host application owns access control.
The Observer provides:
- a runs index at
/continuumwith state, workflow, run id search, and stable cursor pagination - a run detail page at
/continuum/runs/:idwith run metadata and decoded journal events - operator actions for cancelling a run and sending a JSON signal payload
- an operational health panel at
/continuum/healthbacked byContinuum.Health, including partition/version drift, run wait reasons, durable wake and timer lag, lease heartbeats, activity queues, dead-letter candidates, and signal backlog
Health repairs in the panel always perform a dry-run preview first. The
confirmation step executes the same fenced, idempotent repair API exposed by
mix continuum.health; stale lease epochs, owners, and attempts are rejected.
The index subscribes to the per-instance "continuum:runs" PubSub topic. That
topic intentionally receives only coarse state changes and terminal
transitions. Detail pages subscribe to the existing per-run topic,
"continuum:run:<run_id>".
Event payloads are decoded with :erlang.binary_to_term/1 because Continuum
stores its own journal data as bytea. Treat database write access as trusted;
the Observer is not a sandbox for malicious journal rows.
Run and event payloads are bounded before decoding. Configure a unary redactor
globally with config :continuum, observer_redactor: MyApp.Redactor, where the
module exports redact/1, or pass redactor: and max_payload_bytes: to the
Observer query helpers. Oversized payloads are represented by an omission map
that includes their encoded byte size. Event pages use sequence cursors and the
display renderer also applies a hard byte cap.
The underlying Continuum.Query API supports nested, type-preserving attribute
filters without abandoning the generated JSONB GIN index:
Continuum.query(
where: [{:eq, [:attributes, :customer, :profile, :tier], 4}],
cursor: previous_page.next_cursor
)Continuum includes priv/static/observer.css as a small baseline stylesheet.
Copy or serve it from your host app if you want the default styling. One simple
option is to serve it directly from the Continuum dependency:
plug Plug.Static,
at: "/",
from: :continuum,
only: ~w(observer.css)You can also copy it into your app's priv/static directory and allow it from
your existing Plug.Static:
plug Plug.Static,
at: "/",
from: :my_app,
gzip: false,
only: ~w(assets fonts images favicon.ico robots.txt observer.css)