Files
cameleer-saas/ui/src/router.tsx
hsiegeln 1fbafbb16d
All checks were successful
CI / build (push) Successful in 1m24s
CI / docker (push) Successful in 1m0s
feat: add vendor tenant metrics dashboard
Fleet overview page at /vendor/metrics showing per-tenant operational
metrics (agents, CPU, heap, HTTP requests, ingestion drops, uptime).
Queries each tenant's server via the new POST /api/v1/admin/server-metrics/query
REST API instead of direct ClickHouse access, supporting future per-tenant
CH instances.

Backend: TenantMetricsService fires 11 metric queries per tenant
concurrently over a 5-minute window, assembles into a summary snapshot.
ServerApiClient.queryServerMetrics() handles the M2M authenticated POST.

Frontend: VendorMetricsPage with KPI strip (fleet totals) and per-tenant
table with color-coded badges and heap usage bars. Auto-refreshes every 60s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 14:02:57 +02:00

113 lines
4.7 KiB
TypeScript

import { Routes, Route, Navigate } from 'react-router';
import { LoginPage } from './auth/LoginPage';
import { CallbackPage } from './auth/CallbackPage';
import { ProtectedRoute } from './auth/ProtectedRoute';
import { OrgResolver } from './auth/OrgResolver';
import { Layout } from './components/Layout';
import { RequireScope } from './components/RequireScope';
import { useScopes } from './auth/useScopes';
import { useOrgStore } from './auth/useOrganization';
import { VendorTenantsPage } from './pages/vendor/VendorTenantsPage';
import { CreateTenantPage } from './pages/vendor/CreateTenantPage';
import { TenantDetailPage } from './pages/vendor/TenantDetailPage';
import { VendorAuditPage } from './pages/vendor/VendorAuditPage';
import { CertificatesPage } from './pages/vendor/CertificatesPage';
import { InfrastructurePage } from './pages/vendor/InfrastructurePage';
import { VendorMetricsPage } from './pages/vendor/VendorMetricsPage';
import { TenantDashboardPage } from './pages/tenant/TenantDashboardPage';
import { TenantLicensePage } from './pages/tenant/TenantLicensePage';
import { SsoPage } from './pages/tenant/SsoPage';
import { TeamPage } from './pages/tenant/TeamPage';
import { SettingsPage } from './pages/tenant/SettingsPage';
import { TenantAuditPage } from './pages/tenant/TenantAuditPage';
function LandingRedirect() {
const scopes = useScopes();
const { organizations, currentOrgId, scopesReady } = useOrgStore();
const currentOrg = organizations.find((o) => o.id === currentOrgId);
// Wait for scopes to be fully resolved by OrgResolver before redirecting.
if (!scopesReady) {
return null;
}
// Vendor → vendor console
if (scopes.has('platform:admin')) {
return <Navigate to="/vendor/tenants" replace />;
}
// Tenant admin → tenant portal
if (scopes.has('tenant:manage')) {
return <Navigate to="/tenant" replace />;
}
// Regular user (operator/viewer) → server dashboard directly
if (currentOrg?.slug) {
window.location.href = `/t/${currentOrg.slug}/`;
return null;
}
// No org resolved yet — stay on tenant portal
return <Navigate to="/tenant" replace />;
}
export function AppRouter() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/callback" element={<CallbackPage />} />
<Route element={<ProtectedRoute />}>
<Route element={<OrgResolver />}>
<Route element={<Layout />}>
{/* Vendor console */}
<Route path="/vendor/tenants" element={
<RequireScope scope="platform:admin" fallback={<Navigate to="/tenant" replace />}>
<VendorTenantsPage />
</RequireScope>
} />
<Route path="/vendor/tenants/new" element={
<RequireScope scope="platform:admin" fallback={<Navigate to="/tenant" replace />}>
<CreateTenantPage />
</RequireScope>
} />
<Route path="/vendor/tenants/:id" element={
<RequireScope scope="platform:admin" fallback={<Navigate to="/tenant" replace />}>
<TenantDetailPage />
</RequireScope>
} />
<Route path="/vendor/audit" element={
<RequireScope scope="platform:admin" fallback={<Navigate to="/tenant" replace />}>
<VendorAuditPage />
</RequireScope>
} />
<Route path="/vendor/certificates" element={
<RequireScope scope="platform:admin" fallback={<Navigate to="/tenant" replace />}>
<CertificatesPage />
</RequireScope>
} />
<Route path="/vendor/metrics" element={
<RequireScope scope="platform:admin" fallback={<Navigate to="/tenant" replace />}>
<VendorMetricsPage />
</RequireScope>
} />
<Route path="/vendor/infrastructure" element={
<RequireScope scope="platform:admin" fallback={<Navigate to="/tenant" replace />}>
<InfrastructurePage />
</RequireScope>
} />
{/* Tenant portal */}
<Route path="/tenant" element={<TenantDashboardPage />} />
<Route path="/tenant/license" element={<TenantLicensePage />} />
<Route path="/tenant/sso" element={<SsoPage />} />
<Route path="/tenant/team" element={<TeamPage />} />
<Route path="/tenant/audit" element={<TenantAuditPage />} />
<Route path="/tenant/settings" element={<SettingsPage />} />
{/* Default redirect — vendor goes to /vendor/tenants, customer to /tenant */}
<Route index element={<LandingRedirect />} />
</Route>
</Route>
</Route>
</Routes>
);
}