Admin Panel Features
Full operational control over users, jobs, API keys, billing, audit logs, and system health.
The admin panel lives at admin.acesense.io (Firebase Hosting site acesense-admin). All pages are gated by the email allowlist in the Firestore isAdmin() rule + Firebase custom claim admin: true.
๐ Dashboardโ
Real-time overview of the platform:
- Stats cards: total users, total jobs, completed/running/pending/failed counts
- Recent jobs (latest 5)
- Recent users (latest 5)
- Jobs-per-day chart (last 7 days)
- Status distribution pie
- Last-updated timestamp + manual refresh
๐ฅ Users (/users)โ
Full user lifecycle management with a detail drawer per user.
List viewโ
- Checkbox-selectable rows
- Filter by plan (free / trial / pro / team), status (active / suspended), joined date (7d / 30d / all)
- Search persists in
?q=โฆURL param - Plan pill (color-coded), last-activity column, total-jobs and completed-jobs counts (computed live from the jobs collection)
Detail drawer (click any row)โ
- Gravatar (SHA-256 hash, identicon fallback) + profile metadata
- Job stats grid: total / completed / last job at
- Plan dropdown โ free / trial / pro / team. Calls
adminSetUserPlanCloud Function. Updatesusers/{uid}.planAND a Firebase custom claim, audited inadmin_audit/{autoid}. - Suspend / Unsuspend toggle โ calls
adminSetUserSuspended. Suspending also callsadmin.auth().updateUser({disabled: true})andrevokeRefreshTokens()to kick existing sessions immediately. - Send password reset โ calls
sendPasswordResetCustom. - Export user data โ calls
exportUserData, opens the returned ZIP URL in a new tab. - API keys section โ read-only embedded list of every key the user owns (name, plan, balance, spent, last used, status). Deeper management on
/admin-api-keys. - Subscription metadata โ renders
trialEndsAt,welcomeEmailSent,emailVerifiedand any othersubscription.*fields when present.
Bulk actionsโ
When one or more rows are checked:
- Export selected to CSV (id, email, displayName, plan, isActive, totalJobs, completedJobs, joinedAt, lastJobAt)
- Send password reset (bulk) โ sequential calls with success/fail counts
Existing flows preservedโ
Add User ยท Edit User ยท Delete User ยท View Jobs modal โ all unchanged.
๐ฌ Jobs (/jobs)โ
Real-time job control with a Firestore onSnapshot listener โ the page updates live as jobs change status.
List viewโ
- Status filter tabs auto-include any status seen in the dataset (defensive against new statuses)
- Filters: status, date range (24h / 7d / 30d / all), has-result (with / without / all), userId autocomplete (top-5 most-frequent userIds)
- Status pill colors: done/completed (green), running/processing/analyzing/merging (blue), pending (amber), failed/error (red), unknown (gray)
- "Live / Reconnecting" badge in the header โ indicates whether the listener is connected
- Checkbox-selectable rows
Detail drawerโ
- Job ID, userId (links to
/users?q={userId}), status pill, fileName, createdAt/updatedAt/processedAt, retryCount - Show raw โ toggle to dump the full Firestore document as JSON
- Storage paths โ videoPath + resultPath, each with a "Download" link that mints a temporary signed URL via
getDownloadURL - RunPod console link โ when
runpodJobIdis present, opens the queue page in the RunPod console - Result viewer โ fetches the result JSON via signed URL, renders summary cards (total shots, completed sessions, max speed) + collapsible JSON tree
Action buttonsโ
- Retry โ calls
adminRetryJob(re-dispatches to RunPod, increments retryCount, resets status) - Mark as done / pending / failed โ manual status flip via Firestore
updateDoc - Delete โ uses the existing
deleteJobWithFileshelper (Storage + Firestore in one go)
Bulk actionsโ
- Export selected to CSV (id, userId, status, fileName, createdAt, processedAt, retryCount, videoPath, resultPath)
- Retry selected โ sequential
adminRetryJobcalls with progress counter - Delete selected โ confirmation modal listing all selected rows
๐ Operations (/operations)โ
System-health + per-user-quota + recent-rejection dashboard.
Health checks (auto-refresh every 30s)โ
| Service | Endpoint | What "healthy" means |
|---|---|---|
| Cloud Functions | https://europe-west1-acesense-prod.cloudfunctions.net/health | 200 with {status, region, timestamp} |
| REST API | https://acesense-prod-api.web.app/v1/healthz | 200 with {status: "ok"} |
| MCP server | https://acesense-prod-api.web.app/mcp POST tools/list | response includes invalid api key (proves reachable) or 200 |
Each card shows green/red dot + last-checked timestamp.
Per-user quota tableโ
Reads users/{uid}.usage for every user. Displays today's uploadCount, concurrent, plan tier, and computed daily/concurrency limits:
- free: 3/day + 1 concurrent
- pro: 50/day + 3 concurrent
- team: 200/day + 5 concurrent
Toggle "Approaching limit" filters to users at โฅ 80% utilization on either axis.
Recent quota_exceeded rejectionsโ
Lists jobs with errorCode: 'quota_exceeded' from the last 7 days. Useful for spotting users hitting tier caps and candidates for plan upgrades.
RunPod fleetโ
Placeholder card with a button linking to https://www.runpod.io/console/serverless. Live RunPod metrics require RunPod's API key โ see Settings.
๐ Audit log (/audit-log)โ
Three tabs over the audit Firestore collections.
Signed URL auditโ
signed_url_audit/ โ every signed download URL minted for RunPod (or any consumer).
Columns: issuedAt, expiresAt, jobId (link), userId (link), consumer, urlHash (truncated SHA-256), status (active / revoked).
Per-row Revoke button โ calls adminRevokeSignedUrl({ auditId }) to flag the row revoked. (The URL itself can't be physically revoked once minted; the audit row tracks intent + supports future bucket-level rotation.)
Moderation auditโ
moderation_audit/ โ every Cloud Vision SafeSearch decision on a job's first frame.
Columns: decidedAt, jobId, userId, verdict (allow / skipped / csam_suspected / adult / violence / racy_high), reason, reviewedBy.
Click any row โ modal with the raw safeSearchAnnotations SafeSearch scores.
Admin actionsโ
admin_audit/ โ every admin-callable invocation (plan changes, suspensions, balance adjustments, retries, revocations).
Columns: at, adminEmail, action, target, payload (truncated; click to expand to JSON modal).
Common featuresโ
- Per-tab date-range filter (24h / 7d / 30d / all)
- Free-text search
- CSV export
๐ API Keys (Admin) (/admin-api-keys)โ
Cross-user view of every api_keys/ document โ different from /developer which is per-user.
Aggregate stat cardsโ
- Total active keys
- Total balance across all keys (EUR)
- Total spent ever (EUR)
- Top-spending key (name + amount)
Tableโ
Columns: keyId (masked), userId (link), name, plan, balance (EUR), spentTotal (EUR), rateLimit (req/min), lastUsedAt, status (active / revoked).
Per-row actionsโ
- Adjust balance โ modal with EUR delta + reason text โ calls
adminAdjustApiKeyBalance({ keyId, deltaCents: Math.round(eur*100), reason }). Transactional, clamped to โฅ 0, audit row inapi_keys/{keyId}/balance_adjustments/{autoid}. - Revoke โ confirmation, then sets
revokedAt: serverTimestamp()directly via FirestoreupdateDoc.
Filtersโ
- Status (active / revoked / all)
- Plan (all / free / pro / team)
- Search
Bulkโ
CSV export of selected/all rows.
๐ Developer (/developer)โ
End-user-facing API key management for the signed-in admin's own account.
- Stat cards: active keys, total balance, total spent
- Create API key (one-time
fullKeyreveal with copy-to-clipboard + warning) - Balance changes are admin-managed through
adminAdjustApiKeyBalance - Revoke
๐พ Storage (/storage)โ
File browser over Firebase Storage. List, navigate, delete.
๐ Analytics (/analytics)โ
Charts: jobs over time, processing-time trend, status distribution, peak hours, top users, file size + resolution breakdown.
โ๏ธ Settings (/settings)โ
System info, admin email allowlist, platform configuration. Read-only summary of:
- Firestore rules
isAdmin()allowlist - Firebase project + region
- Function deploy state (recent timestamps)
To add/remove admins: update isAdmin() in acesense-frontend/firestore.rules and redeploy rules.
๐ฌ Research Decisions (/research-decisions)โ
Internal log of compliance-driven product decisions. See decisions/.
๐น Keyboardโ
Cmd+K (Mac) / Ctrl+K opens the command palette with quick-nav to every page above.
๐ก๏ธ Backend admin callablesโ
Every "admin action" button in the panel routes through one of these Cloud Functions in acesense-auth-function/admin/index.ts:
| Callable | Purpose | Audit |
|---|---|---|
adminRetryJob | Re-dispatch a failed/stalled job to RunPod | admin_audit/{autoid} |
adminSetUserPlan | Change users/{uid}.plan + custom claim | admin_audit/{autoid} |
adminSetUserSuspended | Flip isActive + Firebase Auth disabled + revoke sessions | admin_audit/{autoid} |
adminAdjustApiKeyBalance | Credit / debit prepaid balance, transactional, clamped to โฅ 0 | admin_audit/{autoid} + api_keys/{keyId}/balance_adjustments/{autoid} |
adminRevokeSignedUrl | Flag a signed-URL audit row revoked | admin_audit/{autoid} |
sendPasswordResetCustom | Send branded password-reset email via Resend | (existing) |
exportUserData | GDPR data export โ generates ZIP and returns signed URL | (existing) |
Each is gated by requireAdmin(request) in shared/admin-auth.ts โ same allowlist as the Firestore rule.
๐ Related docsโ
- Deployment โ how to ship admin panel changes
- Setup โ first-time admin setup
- Agent API + MCP โ the API key surface this panel manages
- Threat model โ what the audit log + moderation pages defend against
- ADR-0007: Agent commerce