Dashboard product spec

Copy Markdown View Source

Reference for building operator UIs on top of Kathikon.Dashboard: Phoenix LiveView, mix kathikon.ops, a TUI, or remote RPC consumers.

Layout

  1. Global commands (top bar): pause all, resume all, bulk cancel, bulk retry/rerun, purge
  2. Queue summary — one row per queue with aggregated counts and pause state
  3. Jobs panel — state tabs, paginated job table, per-job drill-down

The UX target is similar to Oban Web: queue summary on top, filterable job list below, bulk actions, row actions, and job detail on ID click.

State model

From Kathikon.Job.StateMachine and docs/job_lifecycle.md:

StateMeaning
:scheduledWaiting until scheduled_at
:availableReady to claim
:claimedAtomically claimed, not yet running
:runningWorker executing
:waiting_for_childrenBatch parent waiting on children
:retryableFailed, waiting for backoff retry
:completedSuccess
:failedTerminal failure (may move to :dead or :discarded)
:deadDead-letter queue
:cancelledCancelled before completion
:discardedPermanently discarded

Legacy :executing normalizes to :running.

Oban mapping

Oban UIKathikon statesDashboard tab
Available:scheduled, :available:available
Executing:claimed, :running, :waiting_for_children:executing
Retryable:retryable:retryable
Completed:completed:completed
Cancelled:cancelled:cancelled
Discarded:discarded only:discarded
(no Oban tab):failed, :dead:dead

Kathikon keeps :dead as an actionable DLQ (rerun, retry, discard) distinct from :discarded (terminal, purge only). Tab mapping is implemented by Kathikon.Dashboard.states_for_tab/1.

Queue summary columns

UI columns use ui_counts on each queue_summary/1 row. Raw per-state counts remain in counts for drill-down.

UI columnStates summed
Available:scheduled + :available
Executing:claimed + :running + :waiting_for_children
Completed:completed
Retryable:retryable
Cancelled:cancelled
Failed:failed + :dead
Discarded:discarded
Totalall states
Pausedqueue_status/1paused: true/false

Global commands

ButtonAPINotes
Pause allDashboard.pause_all/0Pauses configured and storage-seen queues
Resume allDashboard.resume_all/0
Kill allDashboard.cancel_jobs/1Cancellable only; skips :running and :waiting_for_children
Rerun allDashboard.rerun_jobs/1Default states [:dead, :failed]
RetryDashboard.retry_jobs/1Default states [:retryable, :failed, :dead]
PurgeDashboard.purge_jobs/1 or Dashboard.prune_now/0Immediate delete vs retention pruner
PromoteDashboard.promote_now/0Scheduler tick :scheduled:available

cancel stops pending work. discard moves jobs to :discarded. There is no force-kill of a BEAM task mid-perform/1 in v0.2.

Per-job actions

Use Kathikon.Dashboard.actions_for_state/1 to enable or disable buttons.

Statecancelretryrerundiscardpurge
:scheduledyesyes
:availableyesyes
:claimedyesyes
:running
:waiting_for_children
:retryableyesyesyes
:failedyesyesyes
:deadyesyesyes
:completedyesyes
:cancelledyes
:discardedyes

Bulk equivalents: cancel_jobs/1, retry_jobs/1, rerun_jobs/1, discard_jobs/1, purge_jobs/1.

Jobs panel

Tabs: Dashboard.state_tabs/0

Dashboard.list_jobs(queue: :default, tab: :dead, limit: 50, offset: 0)

Footer: Showing at most N jobs out of M — always paginate.

Table columns: id, state, queue, worker, attempts (attempts/max_attempts), timestamp, errors, actions (from actions_for_state/1).

Job detail: Dashboard.fetch_job/1job map + history list; show parent_job_id, batch_id, rerun_of when set.

Library API

Read

{:ok, queues} = Kathikon.Dashboard.queue_summary()
# each row: %{queue, paused, counts, ui_counts, executing, failed, total}

{:ok, %{jobs: jobs, total: total, limit: 50, offset: 0}} =
  Kathikon.Dashboard.list_jobs(queue: :default, tab: :completed)

{:ok, %{job: job, history: events}} = Kathikon.Dashboard.fetch_job(job_id)

Kathikon.Dashboard.actions_for_state(:dead)
#=> [:retry, :rerun, :discard]

queue_summary/1 builds on Kathikon.Report.queue_summary/1 and adds dashboard fields plus queues seen in storage.

Write

Kathikon.Dashboard.pause_all()
Kathikon.Dashboard.resume_all()
Kathikon.Dashboard.pause_queue(:emails)
Kathikon.Dashboard.cancel_job(id)
Kathikon.Dashboard.retry_job(id)
Kathikon.Dashboard.rerun_job(id)
Kathikon.Dashboard.discard_job(id)

Kathikon.Dashboard.cancel_jobs(queue: :default)
Kathikon.Dashboard.retry_jobs(queue: :default)
Kathikon.Dashboard.rerun_jobs(queue: :default, states: [:dead])
Kathikon.Dashboard.discard_jobs(queue: :default)
Kathikon.Dashboard.purge_jobs(queue: :default, states: [:completed])
Kathikon.Dashboard.promote_now()
Kathikon.Dashboard.prune_now()

RPC

# Returns the remote Dashboard result directly (not double-wrapped).
{:ok, queues} = Kathikon.Dashboard.RPC.call(:"kathikon@host", :queue_summary, [[]])
:ok = Kathikon.Dashboard.RPC.call(:"kathikon@host", :pause_all, [])

Whitelist only — see Kathikon.Dashboard.RPC.allowed?/1.

CLI remote (named local node and matching cookie required):

elixir --name ops@127.0.0.1 --cookie SECRET -S mix kathikon.ops --node kathikon@127.0.0.1 summary

Validation

mix kathikon.ops summary
mix kathikon.ops jobs --queue default --tab completed --limit 20
mix kathikon.ops show JOB_ID
mix kathikon.ops pause --all
mix kathikon.ops --node kathikon@host summary

Future work

  • Phoenix LiveView dashboard subscribing to [:kathikon, :job, :*] (debounced)
  • Optional TUI for SSH-only environments
  • Batch status and children links in job detail when batch_id is set

Non-goals (v1)

  • Force-killing BEAM tasks mid-perform/1
  • Editing job args in the UI
  • Multi-cluster aggregated view
  • Authn/z (host app responsibility)