Skip to content

Admin Dashboard Guide

The Objectuve Admin Dashboard is a standalone web application for managing users, reviewing moderation queues, and monitoring administrative activity. It runs at admin.objectuve.com (production) or localhost:5174 (local development).

See admin-site-map.md for a visual map of every route, the role required to load it, and how the role guard resolves.

Accessing the Dashboard

Production & Staging

EnvironmentURL
Productionhttps://admin.objectuve.com
Staginghttps://staging.admin.objectuve.com
Localhttp://localhost:5174

Sign-In Methods

Clerk (Standard): Navigate to /sign-in and authenticate with your Clerk account (Google, Apple, or email). Your Clerk email must be in the admin allowlist.

Fallback (Testing & Outages): Navigate to /sign-in?fallback to access the email + secret form. This bypasses Clerk entirely.

FieldValue
Emailjosh@objectuve.com (or any admin email in the database)
Auth SecretThe value of FALLBACK_AUTH_SECRET from the environment

Fallback auth requires FALLBACK_AUTH_SECRET to be set on the backend. For local development, set it in your local, gitignored env file (copy rails_api/.env.example for the full variable template). For staging, set it as a Cloud Run environment variable.

Local Development Setup

bash
# Terminal 1: Start the Rails API
cd rails_api
bundle exec rails s

# Terminal 2: Start the admin dashboard
cd admin_dashboard
npm run dev
# → http://localhost:5174

If this is your first time, seed the database to create the admin user:

bash
cd rails_api
bundle exec rails db:setup   # Creates DB, runs migrations, seeds data

This creates josh@objectuve.com as the admin user.

Dashboard Pages

Activity Log (Dashboard)

URL: /

The landing page shows a chronological feed of administrative actions performed across the platform. Each entry displays:

  • Admin user who performed the action
  • Action type (e.g., UPDATE_ROLES)
  • Target entity affected
  • Timestamp of the action
  • Metadata with before/after state details

When no actions have been logged yet, you'll see "No administrative actions tracked yet."

User Management

URL: /users

Search for any user by name, email, or exact ID. The search uses PostgreSQL trigram matching (pg_trgm) for fuzzy results.

Search: Type at least 2 characters in the search box. Results appear after a 400ms debounce.

Results table columns:

  • Name — First and last name
  • Email — Account email address
  • Roles — Current admin roles (or "None")
  • Joined — Account creation date
  • Edit Roles — Opens the role editor

Role Editor: Click "Edit Roles" on any user to open a slide-over panel with checkboxes for each available role:

RoleAccess Level
super_adminFull platform access, can manage other admins
adminStandard admin access, user management, moderation
moderatorContent moderation, report review
supportRead-only access to user data for customer support

Check/uncheck roles and click "Save" to update. Changes are logged in the Activity Log automatically via the Admin::Logged audit interaction.

AI Workforce (Live Ops Board)

URL: /ai-workforce · Roles: super_admin, admin

The AI Workforce landing (v4.8, OBJ-1562 route cutover) — a single command-center board over every in-flight and recent piece of AI-generated work ("tickets"), replacing the old separate agent-grid + Review Queue split. Auto-polls every 30 seconds (GET_WORKFORCE_BOARD). /ops-board and /review-queue (the pre-cutover URLs) redirect here.

Top bar: a "Live" status chip, a Board/Timeline segmented control (Timeline is disabled — "coming soon"), and five right-aligned stats: Need you, Agents active, Approval 7d, Spend MTD, Shipped 7d. Below ~1100px of board width the stats collapse behind an "Activity" toggle that opens the activity rail as a drawer.

Four columns, each a queue of tickets:

  • Running — agents currently executing, with a live progress bar
  • Needs review — pending artifacts and human-decision escalations awaiting a call
  • Handed to you — tickets claimed via "Take it myself" or escalated directly to an operator
  • Shipped today — delivered artifacts anchored to the current day (the backend's 7-day recent-artifact window is filtered client-side to "today" so the heading stays honest)

Activity rail (right side, collapses to a drawer on narrow boards): a reverse-chronological feed of board events — runs starting, approvals, rejections, hand-offs — each with a colored dot, actor, and relative time.

Clicking any ticket opens the Focus Detail overlay — see below.

Focus Detail overlay

Opens over the board (not a route of its own) when a ticket is clicked. A centered modal showing:

  • Agent identity — avatar, name, role, ticket age, and a stage chip
  • Queue position — "Reviewing N of M in your queue" with a progress bar, when the ticket is one of your actionable items
  • Artifact body — the draft content; code_patch artifacts render as a side-by-side diff (diff2html, output sanitized with DOMPurify to prevent XSS) instead of raw text
  • Why it's here and Run trace (turns, duration, cost) side panels, with a link to the full Run Detail page
  • Footer actions, depending on the ticket's stage: Approve & deliver / Approve — open PR / Send reply, Edit (inline text edit before approving; disabled for code_patch — a diff isn't safely free-text-editable), Take it myself (claims the artifact via the claimAiArtifact mutation, moving it to "Handed to you"), Reject (requires a reason, minimum 10 characters), and Pause agent for running tickets
  • Keyboard shortcuts: J/K or arrow keys to move through your queue, A approve, E edit, H hand off, R reject, Esc to close

On approve/reject, the overlay auto-advances to the next actionable ticket in your queue and shows a toast; if none remain, it closes.

Agents

URL: /agents · Roles: super_admin, admin

The secondary AI Workforce view — agent configuration, filters, and manual trigger/pause controls that the Ops Board doesn't replicate. This is the former /ai-workforce grid view, moved to its own route in the v4.8 cutover. Auto-polls every 30 seconds.

Employee list columns:

  • Name — Employee display name and role
  • Status — Active (green) or Paused (gray)
  • Autonomy — Current level (shadow, semi_autonomous, autonomous)
  • Budget — Monthly spend vs. cap
  • Last Run — Timestamp of most recent execution
  • Actions — Trigger run, Pause/Resume

Manual runs: Click "Trigger Run" to start an employee run on demand. The run dispatches to the Agent Runner service and results appear on the Ops Board.

Employee Detail

URL: /ai-workforce/:id

Deep dive into a specific AI employee, reached from either the Ops Board or the Agents grid:

  • Approval rate chart — Chart.js bar chart showing weekly approve/reject rates
  • Run history — 20 most recent runs with status, duration, token cost
  • Memory state — Current JSONB memory entries (editable by admin)
  • Promotion controls — Autonomy level management with confirmation dialog
  • Tabs: Overview, Runs, Memory, Settings

Promotion criteria: Employees must meet approval rate thresholds over a minimum number of runs before promotion. The validate :promotion_requirements_met model validation enforces this at the database level.

Editing an employee's configuration: The Edit configuration button opens a modal that updates the employee's runtime config via the updateAiEmployee mutation. Editable fields:

  • Name and Description
  • Task prompt — the employee's standing instructions
  • Model preference and Schedule (cron)
  • Monthly budget (dollars → stored as cents)
  • Skills — a comma-separated list of skill refs (.claude/skills/{name}) baked into the Agent Runner image
  • MCP tools — toggle chips for the known servers (github, posthog, sentry, mailtrap, slack). An employee can only invoke a tool whose server is enabled here (the mcp_servers scoping rule)
  • Post-filter skill — the brand-voice validation skill applied to user-facing artifacts (blank for internal-only output)

Changes apply to the next run; an in-flight run keeps the config it started with. Autonomy level is managed separately via the Promotion controls. This editor is what makes the five seeded employees configurable without a redeploy (the gap closed by the OBJ-811 epic).

Run Detail

URL: /ai-workforce/:employeeId/runs/:runId

Execution trace for a specific AI employee run:

  • Tool calls — Chronological list of MCP/REST tool invocations with inputs and outputs
  • Artifacts — Generated content with approval status badges
  • Token usage — Prompt/completion tokens and estimated cost
  • Error logs — Failure reasons (max turns exceeded, budget exceeded, SDK errors)

Moderation Queue

URL: /moderation

Review pending content reports and community flags. This page is currently a placeholder — content moderation features will be ported from the Ionic admin view in a future phase.

Audit Log

URL: /audit-log · Roles: super_admin, admin

Global log of administrative actions (adminActions query). Filterable by acting admin (actorId, a user public_id), action type, and target type. Each row shows the acting admin, the action category, the target (rendered as the target's opaque public_id, never a raw integer DB id), a timestamp, and a JSON metadata blob.

Badge Stats

URL: /badges · Roles: super_admin, admin

Unlock-rate breakdown for every badge (badgeStats query), sorted highest-first. Surfaces which badges are common vs. rare across the user base — useful for tuning gamification difficulty.

Teams Monitoring

URL: /teams/monitoring · Roles: super_admin, admin

Read-only closed-beta monitoring for Teams V1 (adminTeamsMonitoring query, M11/PRIVACY-3). One row per team:

  • Team — team name
  • Seats — seats used vs. seat limit (team.memberships.size vs. subscription.seat_count)
  • Status — subscription status (trialing / active / past_due / grace / canceled, or "no subscription")
  • Last active — most recent updated_at across the team's members, or "No activity yet"

Carries no personal goal, mood, or journal data — team-scoped operational fields only, per the data-boundary contract. See the Teams billing runbook for billing-incident procedures.

Content Reports

URL: /content-reports · Roles: super_admin, admin, moderator

User-submitted content reports awaiting review (contentReports query) — distinct from the auto-detected flags in the Moderation Queue. Each report card shows the reporter, the reportable type, reason, and a content preview; reviewing a report fires the reviewContentReport mutation. This is the only one of the four new views also accessible to the moderator role.

Plans

URL: /plans · Roles: super_admin, admin

Read-only view of configured subscription plans (plans query): name, slug, price (display + cents), billing interval, and active status. Mirrors the Stripe-backed plan catalog used by the Supporter tier.

Communities curation (editorial discovery)

URL: /communities/curation · Roles: super_admin, admin

Assigns communities to the three consumer-facing editorial discovery rails on /communities (behind the editorial-discovery flag — see Communities § Editorial Discovery Rails), and toggles the independent "featured" badge. CommunityCurationView.vue calls curatableCommunities for the list and two mutations, setCommunityEditorialSlot and setCommunityFeatured, to write — see Communities Deep-Dive § GraphQL Schema for the full contract on all three.

Workflow — assigning a community to a rail:

  1. Open /communities/curation. The table lists every discoverable community (5+ members) plus any sub-threshold community that already holds a slot (see below), ordered by slot → position → name.
  2. Find the community by name, or scan the rail overview strip at the top of the page — it shows the 3 named rails (Active now / Just started / Most members) with their 3 numbered positions each, and which community (if any) currently fills each one.
  3. In that community's row, pick a Rail slot from the dropdown (— None —, Active now, Just started, Most members).
  4. Enter a Position (1–3). The position field is disabled until a slot is chosen.
  5. Click Save. The row shows "Saving…", then the table refetches — the community now appears in that rail's position on the live /communities page (subject to the 5-member floor below).

Choosing — None — and saving clears the community's slot and position, freeing that rail position.

Table columns: Community, Category, Members, Rail slot, Position, Featured, Status, row actions (Save/Revert or Retry/Revert once dirty).

Unsaved changes: editing a row's slot, position, or Featured toggle marks it "Unsaved" and reveals Save/Revert buttons — nothing writes until Save is clicked. Revert discards the staged edit and restores the row to its last-saved state. A row you haven't touched always reflects the latest server data, even if another admin's save changes it underneath you.

The 5-member discoverability floor. Communities need members_count >= 5 (Community::DISCOVERY_THRESHOLD, rails_api/app/models/community.rb:24) to render on any consumer discovery surface, including the editorial rails. A community can still be assigned a slot below that floor — the admin list is deliberately widened to include it (Resolvers::AdminQueries#curatable_communities, rails_api/app/graphql/resolvers/admin_queries.rb:229-238) so it never becomes invisible — but it won't render in the rail until it crosses 5 members. The curation screen shows an inline warning on that row ("N of 5 members. Name won't show in the rail until it hits 5 — leave it placed, or clear the slot to free the position.") so the admin can choose to hold the slot or free it up for another community. This also covers the reverse case: a community that shipped a slot and later drops below 5 members keeps its assignment, disappears from the consumer rail, but stays visible and clearable here rather than becoming an orphaned, invisible slot-holder.

Eviction on position collision — intentional. Only one community can occupy a given slot + position at a time. Assigning a slot/position that another community already holds silently evicts that community (clears its editorial_slot, editorial_position, and editorial_updated_at in the same transaction — Admin::SetCommunityEditorialSlot#apply_slot, rails_api/app/interactions/admin/set_community_editorial_slot.rb:45-58) and hands the position to the new one. This is deliberate — there is no "swap" or "insert and shift" affordance. The curation screen surfaces it after the fact with a dismissable toast naming both communities ("New community now holds Rail · Position. Evicted community held that spot and is back to unplaced.") so the admin isn't left wondering why a community they placed earlier disappeared.

is_featured is independent of editorial_slot. The Featured toggle sets a standalone boolean (Admin::SetCommunityFeatured, rails_api/app/interactions/admin/set_community_featured.rb) that drives the "Featured" badge shown on community cards elsewhere in the app (CommunityCardV2.vue) — it has no relationship to rail placement. A community can be featured with no editorial slot, slotted with no featured badge, both, or neither. Saving a row that changed both the slot and the Featured toggle fires both mutations; if one succeeds and the other fails, the row's status shows exactly which one didn't save ("Slot saved, Featured didn't" or "Featured saved, the slot didn't") so you know what to retry.

Row-level errors don't affect other rows — a failed save leaves that row's staged edits in place with a Retry/Revert choice, and every other row (saved or still-unsaved) is unaffected.

Role System (RBAC)

Objectuve uses a dual authorization model during the v1.3 transition:

  1. Legacy admin boolean — The original User.admin flag. Users with admin: true have full admin access.
  2. AdminRole records — The new RBAC system. Users can have multiple roles (super_admin, admin, moderator, support) via the admin_roles table.

During the transition, a user is considered an admin if either admin: true OR they have at least one AdminRole record.

Checking access in code

ruby
# In GraphQL resolvers
require_admin!  # Checks admin boolean + AdminRole records

# On the User model
user.admin?           # Legacy boolean check
user.has_role?('admin')  # RBAC check
user.super_admin?     # admin? OR has_role?('super_admin')

Audit Logging

All administrative actions are automatically logged via the Admin::Logged interaction. Every mutation that modifies user data, roles, or content wraps its logic in a Logged.call block that captures:

  • Before state — Attribute snapshot before the change
  • After state — Attribute snapshot after the change
  • Actor — The admin who performed the action
  • Metadata — Request IP, user agent, and action-specific details

Audit logs are immutable — they cannot be edited or deleted.

Fallback Authentication (Technical Details)

Fallback auth is a secret-based authentication mechanism that bypasses Clerk entirely. It exists for two scenarios:

  1. Testing — When Clerk isn't configured (local dev, CI)
  2. Clerk outages — Emergency access when Clerk is down

How it works

  1. Frontend stores Fallback <secret>:<email> in localStorage
  2. Apollo client sends it as the SessionToken header (instead of a Clerk JWT)
  3. Backend GraphqlController#fallback_auth validates the secret using ActiveSupport::SecurityUtils.secure_compare
  4. If valid, looks up the user by email and sets context[:current_user]

Security considerations

  • Uses timing-safe comparison to prevent side-channel attacks
  • Only active when FALLBACK_AUTH_SECRET environment variable is set
  • Should NOT be set in production unless during a Clerk outage
  • The secret should be rotated after each emergency use
  • Admin dashboard rejects non-admin users even with a valid fallback token

Setting up for staging

bash
# Generate a new secret
openssl rand -hex 32

# Set on Cloud Run
gcloud run services update enkidu-api-staging \
  --set-env-vars="FALLBACK_AUTH_SECRET=<generated-secret>" \
  --region=us-central1 \
  --project=enkidu-488723

Troubleshooting

"Authentication failed" on fallback login

  1. Verify FALLBACK_AUTH_SECRET is set in your local env file (template: rails_api/.env.example)
  2. Verify the Rails server is running on port 3000
  3. Verify the user exists: bundle exec rails runner "puts User.find_by(email: 'josh@objectuve.com')&.admin"
  4. Check the Rails server logs for auth errors

"This account does not have admin access"

The user exists but doesn't have admin privileges. Grant admin access:

bash
cd rails_api
bundle exec rake users:make_admin EMAIL=user@example.com

Dashboard shows "No administrative actions tracked yet"

This is normal for a fresh database. Actions will appear after you perform admin operations (e.g., updating user roles).

Stale gem warnings on Rails startup

If you see "Source locally installed gems is ignoring..." warnings:

bash
cd rails_api
rm -rf vendor/bundle/ruby/4.0.0/specifications/<stale-gem>*.gemspec
bundle install

Last updated: 2026-08-07 — documented the Communities curation screen (/communities/curation): workflow, the 5-member floor and sub-threshold/orphan handling, eviction-on-collision, and the independent is_featured toggle (OBJ-2238).

Loading…