MotorSignal-App/website/app/admin/page.tsx

164 lines
5.4 KiB
TypeScript

import { cookies } from 'next/headers';
import { OperationsShell } from '../../components/ops/operations-shell';
import { OperationsErrorState } from '../../components/ops/operations-error-state';
import { normalizeHealthResponse, sanitizeAuditEvent } from '../../lib/ops-data';
import {
OperatorSession,
OperationsHealth,
SafeAuditEvent,
OperationsNavId,
} from '../../components/ops/types';
interface AdminPageProps {
searchParams?: Promise<{
nav?: string;
}>;
}
function parseNavParam(nav?: string): OperationsNavId {
if (nav === 'audit' || nav === 'operators' || nav === 'session') {
return nav;
}
return 'overview';
}
export default async function AdminPage({ searchParams }: AdminPageProps) {
const resolvedParams = searchParams ? await searchParams : {};
const initialNav = parseNavParam(resolvedParams.nav);
const cookieStore = await cookies();
const isProduction = process.env.NODE_ENV === 'production';
const opsToken = isProduction
? cookieStore.get('__Host-motorsignal_ops')?.value
: cookieStore.get('__Host-motorsignal_ops')?.value ??
cookieStore.get('motorsignal_ops')?.value;
// Unauthenticated - prompt for login (Zero query parameter preview/bypass)
if (!opsToken) {
return (
<section className="min-h-screen bg-surface flex items-center justify-center p-4">
<OperationsErrorState
status={401}
message="Your Operations session has expired or is unauthenticated."
/>
</section>
);
}
const baseUrl = process.env.MOTORSIGNAL_API_BASE_URL ?? 'https://api.motorsignal.app';
const headers = {
authorization: `Bearer ${opsToken}`,
accept: 'application/json',
};
try {
// STEP 1: Verify Operations identity & role first
const meResponse = await fetch(`${baseUrl}/api/v1/ops/me`, { headers, cache: 'no-store' });
if (meResponse.status === 401) {
return (
<section className="min-h-screen bg-surface flex items-center justify-center p-4">
<OperationsErrorState
status={401}
message="Your Operations session has expired. Please sign in again."
/>
</section>
);
}
if (meResponse.status === 403) {
return (
<section className="min-h-screen bg-surface flex items-center justify-center p-4">
<OperationsErrorState
status={403}
message="You do not have sufficient authority to access Operations."
/>
</section>
);
}
if (!meResponse.ok) {
return (
<section className="min-h-screen bg-surface flex items-center justify-center p-4">
<OperationsErrorState
status={503}
message="Operations platform is temporarily unavailable."
/>
</section>
);
}
const meData = await meResponse.json();
const session: OperatorSession = {
identity: meData.identity ?? meData.email ?? 'operator',
role: meData.role === 'owner' ? 'owner' : 'operator',
status: meData.status === 'expired' || meData.status === 'revoked' ? meData.status : 'active',
createdAt: meData.created_at ?? meData.createdAt,
lastActivityAt: meData.last_activity_at ?? meData.lastActivityAt,
expiresAt: meData.expires_at ?? meData.expiresAt,
environment: process.env.NODE_ENV === 'production' ? 'production' : 'development',
};
// STEP 2: After successful verified session, fetch health, and ONLY fetch audit if role == 'owner'
const isOwner = session.role === 'owner';
const [healthResponse, auditResponse] = await Promise.all([
fetch(`${baseUrl}/api/v1/ops/health`, { headers, cache: 'no-store' }),
isOwner
? fetch(`${baseUrl}/api/v1/ops/audit-events`, { headers, cache: 'no-store' }).catch(() => null)
: Promise.resolve(null),
]);
let health: OperationsHealth;
if (healthResponse.ok) {
const healthData = await healthResponse.json();
health = normalizeHealthResponse(healthData);
} else {
health = normalizeHealthResponse({ status: 'unavailable', checked_at: new Date().toISOString() });
}
let initialAuditEvents: SafeAuditEvent[] = [];
let nextCursor: string | null = null;
let auditTotal: number | undefined = undefined;
if (auditResponse && auditResponse.ok) {
const auditData = await auditResponse.json();
const rawEvents = Array.isArray(auditData)
? auditData
: Array.isArray(auditData?.events)
? auditData.events
: Array.isArray(auditData?.items)
? auditData.items
: [];
initialAuditEvents = rawEvents
.map(sanitizeAuditEvent)
.filter((e: SafeAuditEvent | null): e is SafeAuditEvent => e !== null);
nextCursor = auditData?.next_cursor ?? auditData?.cursor ?? null;
if (typeof auditData?.total === 'number') {
auditTotal = auditData.total;
}
}
return (
<OperationsShell
initialSession={session}
initialHealth={health}
initialAudit={{
events: initialAuditEvents,
next_cursor: nextCursor,
total: auditTotal,
}}
initialNav={initialNav}
/>
);
} catch {
return (
<section className="min-h-screen bg-surface flex items-center justify-center p-4">
<OperationsErrorState
status={503}
message="Unable to connect to the Operations platform service."
/>
</section>
);
}
}