Admin Dashboard
System-wide operations dashboard with KPI cards, recent activity, tenant table, and quick actions. Uses the full app shell (sidebar + header).
Components used​
Layout · Sidebar · Header · StatsCard · ActivityFeed · DataTable · Badge · Button · ExportMenu
Code​
- HTML · @primus/ui-core
- React
- Angular
<div style="display:flex;height:540px;border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:0.875rem">
<!-- Sidebar -->
<aside style="width:200px;background:var(--card);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0">
<div style="padding:1rem 1rem 0.75rem;border-bottom:1px solid var(--border);font-weight:700;font-size:0.9rem;display:flex;align-items:center;gap:0.5rem">
<div style="width:24px;height:24px;background:var(--primary);border-radius:4px;display:flex;align-items:center;justify-content:center;color:white;font-size:0.7rem;font-weight:700">P</div>
Primus Admin
</div>
<nav style="padding:0.5rem;flex:1">
<div style="padding:0.375rem 0.625rem;border-radius:5px;background:color-mix(in srgb,var(--primary) 10%,transparent);color:var(--primary);font-weight:500;margin-bottom:2px">Dashboard</div>
<div style="padding:0.375rem 0.625rem;border-radius:5px;color:var(--muted-foreground);margin-bottom:2px">Tenants</div>
<div style="padding:0.375rem 0.625rem;border-radius:5px;color:var(--muted-foreground);margin-bottom:2px">Users</div>
<div style="padding:0.375rem 0.625rem;border-radius:5px;color:var(--muted-foreground);margin-bottom:2px">Billing</div>
<div style="padding:0.375rem 0.625rem;border-radius:5px;color:var(--muted-foreground);margin-bottom:2px">Logs</div>
<div style="padding:0.375rem 0.625rem;border-radius:5px;color:var(--muted-foreground)">Settings</div>
</nav>
<div style="padding:0.75rem 1rem;border-top:1px solid var(--border);display:flex;align-items:center;gap:0.5rem">
<div style="width:28px;height:28px;border-radius:50%;background:color-mix(in srgb,var(--primary) 15%,transparent);display:flex;align-items:center;justify-content:center;font-size:0.7rem;font-weight:600;color:var(--primary)">JD</div>
<div style="flex:1;min-width:0">
<div style="font-weight:500;font-size:0.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">Jane Doe</div>
<div style="font-size:0.7rem;color:var(--muted-foreground)">Super Admin</div>
</div>
</div>
</aside>
<!-- Main -->
<div style="flex:1;display:flex;flex-direction:column;overflow:hidden">
<!-- Header -->
<div style="padding:0.75rem 1.25rem;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:1rem;background:var(--card)">
<div style="flex:1">
<div style="font-weight:600;font-size:0.9rem">Dashboard</div>
<div style="font-size:0.75rem;color:var(--muted-foreground)">Last updated: just now</div>
</div>
<button class="outline small">Export</button>
<button class="small">+ New Tenant</button>
</div>
<!-- Content -->
<div style="flex:1;overflow-y:auto;padding:1.25rem;background:var(--background)">
<!-- KPI cards -->
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:0.75rem;margin-bottom:1.25rem">
<div class="card stat" style="padding:1rem">
<span class="card-label">Total Tenants</span>
<span class="card-value" style="font-size:1.5rem">142</span>
<span class="card-delta up">+8 this month</span>
</div>
<div class="card stat" style="padding:1rem">
<span class="card-label">Monthly Revenue</span>
<span class="card-value" style="font-size:1.5rem">$98.2k</span>
<span class="card-delta up">+4.2%</span>
</div>
<div class="card stat" style="padding:1rem">
<span class="card-label">Active Users</span>
<span class="card-value" style="font-size:1.5rem">3,841</span>
<span class="card-delta up">+12%</span>
</div>
<div class="card stat" style="padding:1rem">
<span class="card-label">Churn Rate</span>
<span class="card-value" style="font-size:1.5rem">1.8%</span>
<span class="card-delta down">+0.2%</span>
</div>
</div>
<!-- Tenants table + Activity side by side -->
<div style="display:grid;grid-template-columns:1fr 280px;gap:0.75rem">
<!-- Tenants table -->
<div class="card" style="padding:0">
<div style="padding:0.875rem 1rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between">
<span style="font-weight:600;font-size:0.875rem">Recent Tenants</span>
<a href="#" style="font-size:0.775rem;color:var(--primary)">View all</a>
</div>
<table style="width:100%;font-size:0.8rem;border-collapse:collapse">
<thead>
<tr style="border-bottom:1px solid var(--border)">
<th style="padding:0.625rem 1rem;text-align:left;font-weight:500;color:var(--muted-foreground)">Tenant</th>
<th style="padding:0.625rem 0.5rem;text-align:left;font-weight:500;color:var(--muted-foreground)">Plan</th>
<th style="padding:0.625rem 0.5rem;text-align:left;font-weight:500;color:var(--muted-foreground)">Status</th>
<th style="padding:0.625rem 1rem;text-align:right;font-weight:500;color:var(--muted-foreground)">MRR</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:0.625rem 1rem;font-weight:500">Acme Corp</td>
<td style="padding:0.625rem 0.5rem">Enterprise</td>
<td style="padding:0.625rem 0.5rem"><span class="badge success" style="font-size:0.7rem">Active</span></td>
<td style="padding:0.625rem 1rem;text-align:right">$12,000</td>
</tr>
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:0.625rem 1rem;font-weight:500">Globex Inc</td>
<td style="padding:0.625rem 0.5rem">Pro</td>
<td style="padding:0.625rem 0.5rem"><span class="badge warning" style="font-size:0.7rem">Trial</span></td>
<td style="padding:0.625rem 1rem;text-align:right">$499</td>
</tr>
<tr>
<td style="padding:0.625rem 1rem;font-weight:500">Initech</td>
<td style="padding:0.625rem 0.5rem">Starter</td>
<td style="padding:0.625rem 0.5rem"><span class="badge danger" style="font-size:0.7rem">Suspended</span></td>
<td style="padding:0.625rem 1rem;text-align:right">—</td>
</tr>
</tbody>
</table>
</div>
<!-- Activity feed -->
<div class="card" style="padding:0">
<div style="padding:0.875rem 1rem;border-bottom:1px solid var(--border);font-weight:600;font-size:0.875rem">Activity</div>
<div style="padding:0.75rem">
<div style="display:flex;flex-direction:column;gap:0.875rem">
<div style="display:flex;gap:0.625rem;font-size:0.775rem">
<div style="width:6px;height:6px;border-radius:50%;background:var(--primary);margin-top:5px;flex-shrink:0"></div>
<div><span style="font-weight:500">Acme Corp</span> upgraded to Enterprise<div style="color:var(--muted-foreground)">2 min ago</div></div>
</div>
<div style="display:flex;gap:0.625rem;font-size:0.775rem">
<div style="width:6px;height:6px;border-radius:50%;background:var(--success);margin-top:5px;flex-shrink:0"></div>
<div><span style="font-weight:500">New tenant</span> Globex Inc created<div style="color:var(--muted-foreground)">18 min ago</div></div>
</div>
<div style="display:flex;gap:0.625rem;font-size:0.775rem">
<div style="width:6px;height:6px;border-radius:50%;background:var(--warning);margin-top:5px;flex-shrink:0"></div>
<div><span style="font-weight:500">Initech</span> payment failed<div style="color:var(--muted-foreground)">1 hr ago</div></div>
</div>
<div style="display:flex;gap:0.625rem;font-size:0.775rem">
<div style="width:6px;height:6px;border-radius:50%;background:var(--muted-foreground);margin-top:5px;flex-shrink:0"></div>
<div><span style="font-weight:500">API key</span> rotated for Acme<div style="color:var(--muted-foreground)">3 hr ago</div></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
import {
PrimusLayout, PrimusSidebar, PrimusHeader,
PrimusStatCard, DashboardGrid,
PrimusDataTable, PrimusActivityFeed,
PrimusButton, PrimusExportMenu, Badge,
} from 'primus-react-ui';
const navItems = [
{ id: 'dashboard', label: 'Dashboard', href: '/dashboard' },
{ id: 'tenants', label: 'Tenants', href: '/tenants' },
{ id: 'users', label: 'Users', href: '/users' },
{ id: 'billing', label: 'Billing', href: '/billing' },
{ id: 'logs', label: 'Logs', href: '/logs' },
{ id: 'settings', label: 'Settings', href: '/settings' },
];
const tenantColumns = [
{ key: 'name', header: 'Tenant', sortable: true },
{ key: 'plan', header: 'Plan' },
{ key: 'status', header: 'Status',
render: row => <Badge variant={row.statusVariant}>{row.status}</Badge> },
{ key: 'mrr', header: 'MRR' },
];
export function AdminDashboard() {
return (
<PrimusLayout
sidebar={<PrimusSidebar items={navItems} activeId="dashboard" />}
header={
<PrimusHeader title="Dashboard" subtitle="Last updated: just now">
<PrimusExportMenu formats={['CSV', 'PDF']} onExport={handleExport} />
<PrimusButton onClick={() => navigate('/tenants/new')}>+ New Tenant</PrimusButton>
</PrimusHeader>
}
>
<DashboardGrid columns={4}>
<PrimusStatCard title="Total Tenants" value="142" change={{ value: 8, type: 'increase' }} description="this month" />
<PrimusStatCard title="Monthly Revenue" value="$98.2k" change={{ value: 4.2, type: 'increase' }} />
<PrimusStatCard title="Active Users" value="3,841" change={{ value: 12, type: 'increase' }} />
<PrimusStatCard title="Churn Rate" value="1.8%" change={{ value: 0.2, type: 'decrease' }} />
</DashboardGrid>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 300px', gap: '1rem' }}>
<PrimusDataTable
title="Recent Tenants"
columns={tenantColumns}
data={tenants}
rowKey="id"
paginated
pageSize={10}
onRowClick={row => navigate('/tenants/' + row.id)}
/>
<PrimusActivityFeed items={activityItems} />
</div>
</PrimusLayout>
);
}
<!-- admin-dashboard.component.html -->
<primus-layout>
<primus-sidebar [items]="navItems" activeId="dashboard"></primus-sidebar>
<primus-header title="Dashboard" subtitle="Last updated: just now">
<div header-actions>
<primus-export-menu [formats]="['CSV','PDF']" (exportTriggered)="onExport($event)">
</primus-export-menu>
<primus-button (clicked)="openNewTenant()">+ New Tenant</primus-button>
</div>
</primus-header>
<div dashboard-content>
<primus-dashboard-grid [columns]="4">
<primus-stats-card label="Total Tenants" [value]="'142'" delta="+8" trend="up" description="this month"></primus-stats-card>
<primus-stats-card label="Monthly Revenue" [value]="'$98.2k'" delta="+4.2%" trend="up"></primus-stats-card>
<primus-stats-card label="Active Users" [value]="'3,841'" delta="+12%" trend="up"></primus-stats-card>
<primus-stats-card label="Churn Rate" [value]="'1.8%'" delta="+0.2%" trend="down"></primus-stats-card>
</primus-dashboard-grid>
<div style="display:grid;grid-template-columns:1fr 300px;gap:1rem">
<primus-data-table
[columns]="tenantColumns"
[data]="tenants"
rowKey="id"
[searchable]="true"
[paginated]="true"
[pageSize]="10"
(onRowClick)="openTenant($event)">
</primus-data-table>
<primus-activity-feed [items]="activityItems"></primus-activity-feed>
</div>
</div>
</primus-layout>