feat: Phase 1c — Platform Admin Dashboard
Full super-admin dashboard for SaaS platform management: Backend (10 files): - Migration 003: Add is_super_admin column to users table - JWT Claims: Carry is_super_admin through access tokens - SuperAdmin extractor: Axum FromRequestParts that rejects non-admins (403) - Admin API module: 10 endpoints behind /api/admin/* - GET /stats (KPIs: licenses, users, MRR, servers, signups) - GET/POST /licenses (paginated, filterable, manual generation) - GET/PATCH /licenses/:id (detail view, revoke/activate) - GET /subscriptions (module sub list with MRR breakdown) - GET/PATCH /users (paginated, toggle admin, disable accounts) - GET /servers (fleet overview across all licenses) - GET /health (DB pool, NATS status, table row counts) - Bootstrap updated: first user gets is_super_admin = true Frontend (8 files): - 5 admin views in src/views/platform-admin/ - DashboardLayout: "Platform" nav section (gated on isSuperAdmin) - Router: /admin/* routes with superAdmin meta guard - Auth store: isSuperAdmin computed property - Types: is_super_admin on User interface Build: 80 chunks, zero TS errors, clean production build. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,10 @@ import {
|
||||
Package,
|
||||
Settings,
|
||||
LogOut,
|
||||
Shield,
|
||||
Key,
|
||||
CreditCard,
|
||||
Network,
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -42,6 +46,14 @@ const navItems = [
|
||||
{ name: 'Settings', path: '/settings', icon: Settings },
|
||||
]
|
||||
|
||||
const adminNavItems = [
|
||||
{ name: 'Admin Home', path: '/admin', icon: Shield },
|
||||
{ name: 'Licenses', path: '/admin/licenses', icon: Key },
|
||||
{ name: 'Subscriptions', path: '/admin/subscriptions', icon: CreditCard },
|
||||
{ name: 'Users', path: '/admin/users', icon: Users },
|
||||
{ name: 'Server Fleet', path: '/admin/servers', icon: Network },
|
||||
]
|
||||
|
||||
function isActive(path: string): boolean {
|
||||
if (path === '/') return route.path === '/'
|
||||
return route.path.startsWith(path)
|
||||
@@ -99,6 +111,29 @@ function handleLogout() {
|
||||
<component :is="item.icon" class="w-4 h-4" />
|
||||
{{ item.name }}
|
||||
</RouterLink>
|
||||
|
||||
<!-- Platform Admin Section (super-admin only) -->
|
||||
<template v-if="auth.isSuperAdmin">
|
||||
<div class="mt-4 mb-2 px-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 border-t border-neutral-700" />
|
||||
<span class="text-[10px] font-semibold uppercase tracking-widest text-oxide-500">Platform</span>
|
||||
<div class="flex-1 border-t border-neutral-700" />
|
||||
</div>
|
||||
</div>
|
||||
<RouterLink
|
||||
v-for="item in adminNavItems"
|
||||
:key="item.path"
|
||||
:to="item.path"
|
||||
class="flex items-center gap-3 px-4 py-2 mx-2 rounded-lg text-sm transition-colors"
|
||||
:class="isActive(item.path)
|
||||
? 'bg-oxide-500/10 text-oxide-400'
|
||||
: 'text-neutral-400 hover:bg-neutral-800 hover:text-neutral-200'"
|
||||
>
|
||||
<component :is="item.icon" class="w-4 h-4" />
|
||||
{{ item.name }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
</nav>
|
||||
|
||||
<!-- User -->
|
||||
|
||||
Reference in New Issue
Block a user