gora.
GoraGen — AI video generation platform

Admin panel

What the platform admin sees: personas, job queue, GPU provisioning, LoRA dashboard, events feed, Plan-100 control plane.

~5 min read · 915 words

TL;DR

The admin is a separate React 19 SPA, accessible only to the admin role. It manages cross-workspace resources: system personas, prompt recipes, LoRA models, job queue, Vast.ai instances, provisioning rules, billing, audit. The headline new feature — the Plan-100 Control Plane Console with coordinator/runner/reconciler and shadow-tables for cutover.

Admin overview

The GoraGen admin is the operations cockpit. Through it the platform team sees what's happening across the entire product at once: how many GPUs are rented, which jobs hung, which users spend the most, which models perform poorly.

The shell — AdminShell.tsx with a sidebar and keyboard shortcuts for fast navigation.

Dashboard

Main screen — KPIs and live metrics:

  • Active jobs (by type: video, storyboard, kling, lora_training)
  • Queue depth and average lead time
  • Error counter for the last 5/15/60 minutes with severity filter
  • Current Vast.ai instances and their state (booting / ready / running / cancelling / destroyed)

/admin/system/workers-health monitors the heartbeat loop from the workers container — if the last heartbeat is older than 60 seconds, the dashboard fires a "workers down" alert.

Users

/admin/users — user browser. Clicking a user_id opens details: workspace, generation history, credit balance, daily-spend profile, plan, IPs of recent logins.

The admin can:

  • Change the user's plan (Free → Starter → Pro)
  • Add/deduct credits manually
  • Impersonate (cookie with impersonated_by=:admin_id flag) — to triage "X doesn't work for me" complaints

All these actions are written to domain_events and visible in compliance for audit.

Personas / Presets / Workflows

Three separate CRUD sections for the platform's content backbone:

  • /personas — system personas. Each has a lookbook (12+ frames), presets (camera, background, lighting), a list of LoRA models.
  • /presets — library of prompt templates and camera moves.
  • /workflows — ComfyUI graph templates. A template can be tied to a model (WAN, Hunyuan, LTX) and used by the renderer.

Tasks

/admin/tasks — job queue console. Four tabs: queued / live / recent / failed.

Clicking a specific job opens full details: workspace, user, instance, input parameters, execution log, progress WS messages, Gemini evaluation, total cost. On failure — a stack trace with the Sentry event id.

From live you can cancel (graceful — let the current iteration finish, don't start the next). From failed you can re-run (a new job with the same parameters).

Instances

/admin/instances — Vast.ai instance browser. For each instance:

  • offer_id, image, region, $/hour price
  • state (booting / ready / running / cancelling / destroyed)
  • current job (if any)
  • cost since launch
  • buttons terminate / SSH-connect / re-init

All manual instance operations now go through the Control Plane (see below), not via a direct Vast API call.

LoRA dashboard

/admin/lora — cross-workspace view of all LoRA training jobs. For each:

  • status (preparing / captioning / training / validating / done / failed)
  • percent progress, ETA
  • link to validation grid (result preview)
  • cost, duration

The admin can kill a stuck training, restart a failed task, look at the kohya_ss log.

System

/admin/system — the technical part of the admin, split into sub-tabs:

  • audit — admin action log
  • health — extended health page (Phase 9): workers heartbeat, DB latency, disk usage, queue depth
  • provisioning — auto-provisioning rules (when the platform rents a new instance, when it kills one)
  • spend — financial summary by GPU hours and Anthropic / Gemini / Kling API calls
  • events — Phase 9 domain events feed (append-only, last 1000 events) with a JSON modal for each

Plan-100 Control Plane Console

/admin/system/control-plane — a dedicated page for managing the coordinated provisioning system.

This is the move from direct Vast API calls to an orchestration model where three loops sit between the admin and Vast:

  • Coordinator — reads instance_requests (provisioning requests), checks parallel limits and velocity guard, inserts into provision_attempts.
  • Runner — picks up claimed attempts, calls scripts/vast_provision.ensure_instance(...), applies the terminal reducer.
  • Reconciler — every N seconds scans stale leases, force-terminates instances stuck in cancelling, releases destroyed instances.

All three — leader-elected via the provision_leader table. If the container with the leader dies, the second one (if any) picks up leadership within 60 seconds.

On the page:

  • Live status of the three loops (running / lost / draining)
  • Observability metrics: 7 numbers (active requests, queued attempts, success rate, etc.)
  • Pause button — emergency flag that stops all three loops within 30 seconds
  • Resume button — clears the emergency pause

Under the hood — 5 cutover flags with a 60-second TTL cache: workflow_spawn, auto_provisioner, parallel_limits, provisioning_cutover_admin_rule_action, provisioning_emergency_pause. You can flip each one independently — for example, disable the auto-provisioner while keeping the rest.

Settings

/admin/settings — runtime-tunable system settings:

  • maintenance_mode — global 503 for non-admins
  • prices — generation costs (per shot, per second, per LoRA train)
  • retention — TTL for videos, events, idempotency keys

Setting changes propagate to all workers within 60 seconds via polling.

Events feed

Phase 9 introduced append-only domain_events. Every significant platform event (new user, new job, provisioning failure, plan upgrade) is written as a separate row with type, actor, payload JSON.

In the admin, /admin/system/events — a reactive feed with filters by event type, actor, date range. Double-click — a modal with pretty-printed JSON.

This delivers two things: forensic audit (what happened in the last 24 hours) and an event source for integrations (event delivery to external webhooks via webhook_dispatcher).