133 lines
4.5 KiB
TypeScript
133 lines
4.5 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import axios from 'axios';
|
|
import { Sidebar } from './components/Sidebar';
|
|
import { ToastContainer } from './components/ui/Toast';
|
|
import { TelemetryLogsView } from './components/TelemetryLogsView';
|
|
import { InstancesPanel } from './components/InstancesPanel';
|
|
import { SUPERADMIN_API_BASE, useSuperAdminAuth } from './store/auth';
|
|
import { useToasts } from './hooks/useToasts';
|
|
import { useDashboardData } from './hooks/useDashboardData';
|
|
import { Dashboard } from './pages/Dashboard';
|
|
import { TenantManagement } from './pages/TenantManagement';
|
|
import { LicensingEngine } from './pages/LicensingEngine';
|
|
import { SupabaseCloudLink } from './pages/SupabaseCloudLink';
|
|
|
|
const api = axios.create({ baseURL: SUPERADMIN_API_BASE });
|
|
api.interceptors.request.use((config) => {
|
|
let token: string | null = useSuperAdminAuth.getState().token;
|
|
if (!token && typeof window !== 'undefined') {
|
|
try {
|
|
const raw = window.localStorage.getItem('superadmin_auth');
|
|
if (raw) {
|
|
const parsed = JSON.parse(raw);
|
|
token = parsed?.state?.token || null;
|
|
}
|
|
} catch {
|
|
token = null;
|
|
}
|
|
}
|
|
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
return config;
|
|
});
|
|
api.interceptors.response.use(
|
|
(response) => response,
|
|
(error) => {
|
|
const status = error?.response?.status;
|
|
const url = error?.config?.url || '';
|
|
const isPublic = url.includes('/auth/login') || url.includes('/api/health') || url.includes('/v1/auth/jwks');
|
|
if (status === 401 && !isPublic) {
|
|
try {
|
|
const current = useSuperAdminAuth.getState().token;
|
|
if (current) {
|
|
useSuperAdminAuth.getState().logout();
|
|
if (typeof window !== 'undefined' && window.location.pathname !== '/login') {
|
|
window.location.assign('/login');
|
|
}
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
export default function App() {
|
|
const navigate = useNavigate();
|
|
const token = useSuperAdminAuth((state) => state.token);
|
|
const logout = useSuperAdminAuth((state) => state.logout);
|
|
const [currentTab, setCurrentTab] = useState('dashboard');
|
|
|
|
const { toasts, addToast, removeToast } = useToasts();
|
|
const { metrics, tenants, instances, loading, refetch } = useDashboardData(api, {
|
|
onError: (msg) => addToast('error', 'Telemetry Error', msg),
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!token) {
|
|
navigate('/login', { replace: true });
|
|
}
|
|
}, [token, navigate]);
|
|
|
|
const handleLogout = () => {
|
|
logout();
|
|
navigate('/login', { replace: true });
|
|
};
|
|
|
|
if (!token) return null;
|
|
|
|
const navigateToInstances = () => setCurrentTab('instances');
|
|
|
|
return (
|
|
<div className="flex h-screen bg-background text-text overflow-hidden">
|
|
<ToastContainer toasts={toasts} onClose={removeToast} />
|
|
|
|
<Sidebar
|
|
currentTab={currentTab}
|
|
onTabChange={setCurrentTab}
|
|
onLogout={handleLogout}
|
|
/>
|
|
|
|
<main className="flex-1 flex flex-col min-w-0 overflow-y-auto custom-scrollbar p-6 sm:p-10 pt-20 lg:pt-10">
|
|
<div className="h-1.5 w-full ng-gradient rounded-xl mb-6 opacity-90" />
|
|
|
|
{currentTab === 'telemetry' ? (
|
|
<TelemetryLogsView api={api} />
|
|
) : currentTab === 'instances' ? (
|
|
<InstancesPanel
|
|
api={api}
|
|
instances={instances}
|
|
onRefresh={refetch}
|
|
onSpawnSuccess={(msg) => addToast('success', 'School Instance Spawned', msg)}
|
|
onSpawnError={(msg) => addToast('error', 'Instance Spawning Failed', msg)}
|
|
onStopSuccess={(msg) => addToast('info', 'Instance Stopped', msg)}
|
|
onStopError={(msg) => addToast('error', 'Stop Failed', msg)}
|
|
/>
|
|
) : currentTab === 'licenses' ? (
|
|
<LicensingEngine tenants={tenants} metrics={metrics} onRefresh={refetch} />
|
|
) : currentTab === 'supabase' ? (
|
|
<SupabaseCloudLink api={api} tenants={tenants} />
|
|
) : currentTab === 'dashboard' ? (
|
|
<Dashboard
|
|
metrics={metrics}
|
|
tenants={tenants}
|
|
instances={instances}
|
|
loading={loading}
|
|
onRefresh={refetch}
|
|
onNavigateToInstances={navigateToInstances}
|
|
/>
|
|
) : (
|
|
<TenantManagement
|
|
api={api}
|
|
tenants={tenants}
|
|
onRefresh={refetch}
|
|
addToast={addToast}
|
|
onSpawnInstanceFromProfile={() => setCurrentTab('instances')}
|
|
/>
|
|
)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|