Add public Ecosystem Atlas route

This commit is contained in:
defiQUG
2026-07-28 11:48:17 -07:00
parent d2a5d7684e
commit c79e07739e
5 changed files with 866 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
import type { Metadata } from 'next';
import AtlasDashboard from '@/components/atlas/AtlasDashboard';
import { loadAtlasSnapshot } from '@/lib/atlas';
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export const metadata: Metadata = {
title: 'Ecosystem Atlas',
description:
'Live Sankofa route matrix, hidden inventory, and revision notes with server-side IT inventory awareness.',
alternates: {
canonical: 'https://sankofa.nexus/atlas',
},
};
export default async function AtlasPage() {
const snapshot = await loadAtlasSnapshot();
return (
<AtlasDashboard
routeRows={snapshot.routeRows}
guestRows={snapshot.guestRows}
hiddenRows={snapshot.hiddenRows}
revisions={snapshot.revisions}
recommendations={snapshot.recommendations}
liveSummary={snapshot.liveSummary}
/>
);
}
@@ -0,0 +1,435 @@
'use client';
import { ArrowUpDown, Filter, Globe2, HardDrive, History, Search, ShieldCheck, Sparkles } from 'lucide-react';
import Link from 'next/link';
import { useMemo, useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import type { AtlasGuestRow, AtlasHiddenRow, AtlasRecommendation, AtlasRevision, AtlasRouteRow } from '@/lib/atlas';
type Props = {
routeRows: AtlasRouteRow[];
guestRows: AtlasGuestRow[];
hiddenRows: AtlasHiddenRow[];
revisions: AtlasRevision[];
recommendations: AtlasRecommendation[];
liveSummary: {
collectedAt?: string;
guestCount?: number | null;
runningCount?: number | null;
stoppedCount?: number | null;
portMapState?: string;
portMapNote?: string;
inventoryState?: string;
summaryState?: string;
readApiConfigured: boolean;
};
};
type SortKey = 'name' | 'status' | 'host' | 'serviceFamily';
function includesQuery(value: string, query: string): boolean {
return value.toLowerCase().includes(query.toLowerCase());
}
function sortRouteRows(rows: AtlasRouteRow[], sortKey: SortKey): AtlasRouteRow[] {
return [...rows].sort((a, b) => {
if (sortKey === 'status') return a.status.localeCompare(b.status) || a.hostnames[0].localeCompare(b.hostnames[0]);
if (sortKey === 'host') return a.hostnames[0].localeCompare(b.hostnames[0]);
return a.hostnames.join(', ').localeCompare(b.hostnames.join(', '));
});
}
function sortGuestRows(rows: AtlasGuestRow[], sortKey: SortKey): AtlasGuestRow[] {
return [...rows].sort((a, b) => {
if (sortKey === 'status') return a.status.localeCompare(b.status) || a.vmid.localeCompare(b.vmid);
if (sortKey === 'host') return a.node.localeCompare(b.node) || a.vmid.localeCompare(b.vmid);
if (sortKey === 'serviceFamily') return a.serviceFamily.localeCompare(b.serviceFamily) || a.vmid.localeCompare(b.vmid);
return a.name.localeCompare(b.name) || a.vmid.localeCompare(b.vmid);
});
}
export default function AtlasDashboard({
routeRows,
guestRows,
hiddenRows,
revisions,
recommendations,
liveSummary,
}: Props) {
const [query, setQuery] = useState('');
const [scope, setScope] = useState<'all' | 'routes' | 'guests' | 'hidden'>('all');
const [sortKey, setSortKey] = useState<SortKey>('name');
const filteredRouteRows = useMemo(() => {
const rows = routeRows.filter((row) => {
if (!query) return true;
return (
row.hostnames.some((host) => includesQuery(host, query)) ||
includesQuery(row.edge, query) ||
includesQuery(row.backend, query) ||
includesQuery(row.source, query) ||
includesQuery(row.notes ?? '', query)
);
});
return sortRouteRows(rows, sortKey);
}, [query, routeRows, sortKey]);
const filteredGuestRows = useMemo(() => {
const rows = guestRows.filter((row) => {
if (!query) return true;
return (
includesQuery(row.vmid, query) ||
includesQuery(row.name, query) ||
includesQuery(row.node, query) ||
includesQuery(row.ip, query) ||
includesQuery(row.status, query) ||
includesQuery(row.serviceFamily, query)
);
});
return sortGuestRows(rows, sortKey);
}, [guestRows, query, sortKey]);
const filteredHiddenRows = useMemo(() => {
const rows = hiddenRows.filter((row) => {
if (!query) return true;
return includesQuery(row.label, query) || includesQuery(row.detail, query) || includesQuery(row.source, query);
});
return [...rows].sort((a, b) => a.label.localeCompare(b.label));
}, [hiddenRows, query]);
return (
<div className="bg-sovereign-obsidian text-sovereign-ivory">
<main className="mx-auto flex w-full max-w-7xl flex-col gap-8 px-4 py-8 sm:px-6 lg:px-8">
<section className="overflow-hidden rounded-3xl border border-sovereign-bronze/25 bg-gradient-to-br from-sovereign-midnight via-sovereign-obsidian to-sovereign-midnight p-6 shadow-2xl shadow-black/30">
<div className="grid gap-6 lg:grid-cols-[1.6fr_1fr] lg:items-start">
<div>
<Badge className="border border-sovereign-gold/30 bg-sovereign-gold/10 text-sovereign-gold">
Live atlas
</Badge>
<h1 className="mt-4 text-4xl font-bold tracking-tight sm:text-5xl">Ecosystem Atlas</h1>
<p className="mt-4 max-w-3xl text-base leading-7 text-sovereign-ivory/70 sm:text-lg">
Public Sankofa routing, live inventory, hidden operator surfaces, and revision notes in one
place. The live counters come from the portal server&apos;s IT read API, while the route matrix
is normalized from the repo&apos;s canonical hostname and tunnel docs.
</p>
<div className="mt-6 flex flex-wrap gap-3">
<Link
href="#routes"
className="inline-flex h-9 items-center justify-center rounded-md bg-sovereign-gold px-4 text-sm font-medium text-sovereign-obsidian no-underline transition hover:bg-sovereign-ivory"
>
Review routes
</Link>
<Link
href="#inventory"
className="inline-flex h-9 items-center justify-center rounded-md border border-sovereign-bronze/35 bg-transparent px-4 text-sm font-medium text-sovereign-ivory no-underline transition hover:bg-sovereign-midnight"
>
Inspect inventory
</Link>
<Link
href="https://sankofa.nexus"
className="inline-flex h-9 items-center justify-center rounded-md px-4 text-sm font-medium text-sovereign-gold no-underline transition hover:text-sovereign-ivory"
>
Back to Sankofa
</Link>
</div>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<Card className="border-sovereign-bronze/20 bg-sovereign-obsidian/70">
<CardHeader className="pb-2">
<CardTitle className="text-sm text-sovereign-ivory/60">Live guests</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-semibold tabular-nums">
{liveSummary.guestCount ?? guestRows.length}
</div>
<p className="mt-1 text-xs text-sovereign-ivory/50">
{liveSummary.readApiConfigured ? 'Server-side inventory feed configured' : 'Live inventory feed unavailable'}
</p>
</CardContent>
</Card>
<Card className="border-sovereign-bronze/20 bg-sovereign-obsidian/70">
<CardHeader className="pb-2">
<CardTitle className="text-sm text-sovereign-ivory/60">Running / stopped</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-semibold tabular-nums">
{liveSummary.runningCount ?? '—'} / {liveSummary.stoppedCount ?? '—'}
</div>
<p className="mt-1 text-xs text-sovereign-ivory/50">
{liveSummary.collectedAt ? `Collected ${liveSummary.collectedAt}` : 'No collection timestamp yet'}
</p>
</CardContent>
</Card>
<Card className="border-sovereign-bronze/20 bg-sovereign-obsidian/70 sm:col-span-2">
<CardHeader className="pb-2">
<CardTitle className="text-sm text-sovereign-ivory/60">Port-map state</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-sovereign-ivory/70">
<div className="flex items-center gap-2">
<Globe2 className="h-4 w-4 text-sovereign-gold" />
<span>{liveSummary.portMapState ?? 'unknown'}</span>
</div>
{liveSummary.portMapNote ? <p className="text-sovereign-ivory/55">{liveSummary.portMapNote}</p> : null}
</CardContent>
</Card>
</div>
</div>
</section>
<section className="grid gap-4 md:grid-cols-3">
<Card className="border-sovereign-bronze/20 bg-sovereign-midnight/50">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm text-sovereign-ivory/60">
<ShieldCheck className="h-4 w-4 text-sovereign-gold" />
Public routes
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-semibold tabular-nums">{routeRows.length}</div>
</CardContent>
</Card>
<Card className="border-sovereign-bronze/20 bg-sovereign-midnight/50">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm text-sovereign-ivory/60">
<HardDrive className="h-4 w-4 text-sovereign-gold" />
Hidden inventory
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-semibold tabular-nums">{hiddenRows.length}</div>
</CardContent>
</Card>
<Card className="border-sovereign-bronze/20 bg-sovereign-midnight/50">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm text-sovereign-ivory/60">
<History className="h-4 w-4 text-sovereign-gold" />
Revision notes
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-semibold tabular-nums">{revisions.length}</div>
</CardContent>
</Card>
</section>
<section className="grid gap-4 rounded-2xl border border-sovereign-bronze/20 bg-sovereign-midnight/40 p-4 lg:grid-cols-[1fr_auto_auto] lg:items-center">
<div className="relative">
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-sovereign-ivory/35" />
<Input
type="search"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search hostnames, VMIDs, IPs, or notes"
className="border-sovereign-bronze/25 bg-sovereign-obsidian pl-10 text-sovereign-ivory placeholder:text-sovereign-ivory/35"
/>
</div>
<label className="flex items-center gap-2 text-sm text-sovereign-ivory/70">
<Filter className="h-4 w-4 text-sovereign-gold" />
<select
value={scope}
onChange={(event) => setScope(event.target.value as typeof scope)}
className="h-10 rounded-md border border-sovereign-bronze/25 bg-sovereign-obsidian px-3 text-sm text-sovereign-ivory"
>
<option value="all">All sections</option>
<option value="routes">Routes</option>
<option value="guests">Guests</option>
<option value="hidden">Hidden</option>
</select>
</label>
<label className="flex items-center gap-2 text-sm text-sovereign-ivory/70">
<ArrowUpDown className="h-4 w-4 text-sovereign-gold" />
<select
value={sortKey}
onChange={(event) => setSortKey(event.target.value as SortKey)}
className="h-10 rounded-md border border-sovereign-bronze/25 bg-sovereign-obsidian px-3 text-sm text-sovereign-ivory"
>
<option value="name">Name</option>
<option value="host">Host</option>
<option value="status">Status</option>
<option value="serviceFamily">Service family</option>
</select>
</label>
</section>
{(scope === 'all' || scope === 'routes') && (
<section id="routes" className="scroll-mt-24">
<Card className="border-sovereign-bronze/20 bg-sovereign-midnight/40">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-sovereign-ivory">
<Globe2 className="h-4 w-4 text-sovereign-gold" />
Route matrix
</CardTitle>
</CardHeader>
<CardContent className="overflow-x-auto">
<table className="min-w-full border-collapse text-left text-sm">
<thead className="text-sovereign-ivory/45">
<tr className="border-b border-sovereign-bronze/20">
<th className="py-3 pr-4 font-medium">Hostname</th>
<th className="py-3 pr-4 font-medium">Edge</th>
<th className="py-3 pr-4 font-medium">Backend</th>
<th className="py-3 pr-4 font-medium">Visibility</th>
<th className="py-3 pr-4 font-medium">Status</th>
</tr>
</thead>
<tbody>
{filteredRouteRows.map((row) => (
<tr key={row.hostnames.join('|')} className="border-b border-sovereign-bronze/10 align-top">
<td className="py-4 pr-4">
<div className="font-medium text-sovereign-ivory">{row.hostnames.join(', ')}</div>
<div className="mt-1 text-xs text-sovereign-ivory/45">{row.source}</div>
</td>
<td className="py-4 pr-4 text-sovereign-ivory/70">{row.edge}</td>
<td className="py-4 pr-4 text-sovereign-ivory/70">{row.backend}</td>
<td className="py-4 pr-4">
<Badge className="border-sovereign-bronze/20 bg-sovereign-obsidian text-sovereign-ivory/75">
{row.visibility}
</Badge>
</td>
<td className="py-4 pr-4">
<Badge
className={
row.status === 'live'
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-300'
: row.status === 'documented'
? 'border-cyan-500/30 bg-cyan-500/10 text-cyan-300'
: 'border-amber-500/30 bg-amber-500/10 text-amber-300'
}
>
{row.status}
</Badge>
{row.notes ? <p className="mt-1 max-w-md text-xs text-sovereign-ivory/45">{row.notes}</p> : null}
</td>
</tr>
))}
</tbody>
</table>
</CardContent>
</Card>
</section>
)}
{(scope === 'all' || scope === 'guests') && (
<section id="inventory" className="scroll-mt-24">
<Card className="border-sovereign-bronze/20 bg-sovereign-midnight/40">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-sovereign-ivory">
<HardDrive className="h-4 w-4 text-sovereign-gold" />
Live guest inventory
</CardTitle>
</CardHeader>
<CardContent className="overflow-x-auto">
<table className="min-w-full border-collapse text-left text-sm">
<thead className="text-sovereign-ivory/45">
<tr className="border-b border-sovereign-bronze/20">
<th className="py-3 pr-4 font-medium">VMID</th>
<th className="py-3 pr-4 font-medium">Name</th>
<th className="py-3 pr-4 font-medium">IP</th>
<th className="py-3 pr-4 font-medium">Node</th>
<th className="py-3 pr-4 font-medium">Status</th>
<th className="py-3 pr-4 font-medium">Family</th>
</tr>
</thead>
<tbody>
{filteredGuestRows.map((row) => (
<tr key={`${row.node}-${row.vmid}-${row.name}`} className="border-b border-sovereign-bronze/10 align-top">
<td className="py-4 pr-4 font-mono text-sovereign-ivory/80">{row.vmid}</td>
<td className="py-4 pr-4">
<div className="font-medium text-sovereign-ivory">{row.name}</div>
<div className="mt-1 text-xs text-sovereign-ivory/45">{row.type}</div>
</td>
<td className="py-4 pr-4 text-sovereign-ivory/70">{row.ip}</td>
<td className="py-4 pr-4 text-sovereign-ivory/70">{row.node}</td>
<td className="py-4 pr-4">
<Badge
className={
row.status.toLowerCase() === 'running'
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-300'
: 'border-amber-500/30 bg-amber-500/10 text-amber-300'
}
>
{row.status}
</Badge>
</td>
<td className="py-4 pr-4 text-sovereign-ivory/70">{row.serviceFamily}</td>
</tr>
))}
</tbody>
</table>
</CardContent>
</Card>
</section>
)}
{(scope === 'all' || scope === 'hidden') && (
<section id="hidden" className="scroll-mt-24">
<Card className="border-sovereign-bronze/20 bg-sovereign-midnight/40">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-sovereign-ivory">
<Sparkles className="h-4 w-4 text-sovereign-gold" />
Capture required
</CardTitle>
</CardHeader>
<CardContent className="grid gap-4 md:grid-cols-2">
{filteredHiddenRows.map((row) => (
<article key={row.label} className="rounded-2xl border border-sovereign-bronze/15 bg-sovereign-obsidian/50 p-4">
<h3 className="font-medium text-sovereign-ivory">{row.label}</h3>
<p className="mt-2 text-sm leading-6 text-sovereign-ivory/65">{row.detail}</p>
<p className="mt-3 text-xs text-sovereign-ivory/40">{row.source}</p>
</article>
))}
</CardContent>
</Card>
</section>
)}
<section className="grid gap-4 lg:grid-cols-[1.1fr_0.9fr]">
<Card className="border-sovereign-bronze/20 bg-sovereign-midnight/40">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-sovereign-ivory">
<History className="h-4 w-4 text-sovereign-gold" />
Revision history
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{revisions.map((revision) => (
<div key={`${revision.date}-${revision.title}`} className="border-l border-sovereign-bronze/25 pl-4">
<p className="text-xs uppercase tracking-wide text-sovereign-gold/80">{revision.date}</p>
<h3 className="mt-1 font-medium text-sovereign-ivory">{revision.title}</h3>
<p className="mt-2 text-sm leading-6 text-sovereign-ivory/65">{revision.detail}</p>
</div>
))}
</CardContent>
</Card>
<Card className="border-sovereign-bronze/20 bg-sovereign-midnight/40">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-sovereign-ivory">
<Sparkles className="h-4 w-4 text-sovereign-gold" />
Recommendations
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{recommendations.map((recommendation) => (
<div key={recommendation.title} className="rounded-2xl border border-sovereign-bronze/15 bg-sovereign-obsidian/50 p-4">
<div className="flex items-center justify-between gap-3">
<h3 className="font-medium text-sovereign-ivory">{recommendation.title}</h3>
<Badge className="border-sovereign-gold/20 bg-sovereign-gold/10 text-sovereign-gold">
{recommendation.priority}
</Badge>
</div>
<p className="mt-2 text-sm leading-6 text-sovereign-ivory/65">{recommendation.detail}</p>
</div>
))}
</CardContent>
</Card>
</section>
</main>
</div>
);
}
@@ -30,6 +30,7 @@ const footerColumns = [
title: 'Company',
links: [
{ label: 'About Sankofa', href: '#resources' },
{ label: 'Ecosystem Atlas', href: '/atlas' },
{ label: 'Institutional registry', href: 'https://d-bis.org/cb/dbis' },
{ label: 'Partner program', href: '/partner' },
{ label: 'Contact', href: 'https://portal.sankofa.nexus' },
+398
View File
@@ -0,0 +1,398 @@
import { itReadApiBaseUrl, itReadApiKey } from '@/app/api/it/_auth';
export type AtlasRouteRow = {
hostnames: string[];
edge: string;
backend: string;
visibility: 'public' | 'internal' | 'hidden';
status: 'live' | 'documented' | 'pending';
source: string;
notes?: string;
};
export type AtlasGuestRow = {
vmid: string;
type: string;
name: string;
node: string;
ip: string;
status: string;
visibility: 'live' | 'hidden';
serviceFamily: string;
};
export type AtlasHiddenRow = {
label: string;
detail: string;
source: string;
};
export type AtlasRevision = {
date: string;
title: string;
detail: string;
};
export type AtlasRecommendation = {
priority: 'P1' | 'P2' | 'P3';
title: string;
detail: string;
};
export type AtlasSnapshot = {
routeRows: AtlasRouteRow[];
guestRows: AtlasGuestRow[];
hiddenRows: AtlasHiddenRow[];
revisions: AtlasRevision[];
recommendations: AtlasRecommendation[];
liveSummary: {
collectedAt?: string;
guestCount?: number | null;
runningCount?: number | null;
stoppedCount?: number | null;
portMapState?: string;
portMapNote?: string;
inventoryState?: string;
summaryState?: string;
readApiConfigured: boolean;
};
};
type RecordLike = Record<string, unknown>;
async function fetchJson(url: string, apiKey?: string): Promise<unknown | null> {
try {
const res = await fetch(url, {
cache: 'no-store',
headers: {
Accept: 'application/json',
...(apiKey ? { 'X-API-Key': apiKey } : {}),
},
});
if (!res.ok) return null;
return (await res.json()) as unknown;
} catch {
return null;
}
}
function asRecord(value: unknown): RecordLike | null {
return value && typeof value === 'object' && !Array.isArray(value) ? (value as RecordLike) : null;
}
function firstRecordArray(value: unknown): RecordLike[] {
if (!value || typeof value !== 'object') return [];
if (Array.isArray(value)) {
return value.filter((entry): entry is RecordLike => Boolean(entry && typeof entry === 'object'));
}
const record = value as RecordLike;
for (const key of ['guests', 'rows', 'items', 'nodes', 'vms', 'inventory', 'data']) {
const candidate = record[key];
if (Array.isArray(candidate)) {
return candidate.filter((entry): entry is RecordLike => Boolean(entry && typeof entry === 'object'));
}
}
for (const candidate of Object.values(record)) {
const nested = firstRecordArray(candidate);
if (nested.length > 0) return nested;
}
return [];
}
function toText(value: unknown, fallback = '—'): string {
if (typeof value === 'string') return value;
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
return fallback;
}
function buildGuestRows(liveInventory: unknown): AtlasGuestRow[] {
const rows = firstRecordArray(liveInventory);
const vmRows = rows.filter((row) =>
['vmid', 'name', 'ip', 'status', 'node', 'type'].some((key) => key in row)
);
return vmRows.map((row) => ({
vmid: toText(row.vmid ?? row.id ?? row.vmid_number ?? row.vmidId),
type: toText(row.type ?? row.kind ?? row.resourceType ?? 'guest'),
name: toText(row.name ?? row.hostname ?? row.label),
node: toText(row.node ?? row.host ?? row.site ?? row.cluster ?? '—'),
ip: toText(row.ip ?? row.ipAddress ?? row.address ?? row.primary_ip ?? '—'),
status: toText(row.status ?? row.state ?? row.phase ?? 'unknown'),
visibility: 'live',
serviceFamily: toText(row.serviceFamily ?? row.family ?? row.role ?? row.group ?? 'general'),
}));
}
function buildRoutes(): AtlasRouteRow[] {
return [
{
hostnames: ['sankofa.nexus', 'www.sankofa.nexus'],
edge: 'Cloudflare DNS -> 76.53.10.36 -> NPMplus',
backend: '192.168.11.51:3000 Sankofa public site',
visibility: 'public',
status: 'live',
source: 'docs/INSTITUTIONAL_REGISTRY.md',
notes: 'Public corporate apex. www canonicalizes to apex.',
},
{
hostnames: ['phoenix.sankofa.nexus', 'www.phoenix.sankofa.nexus'],
edge: 'Cloudflare DNS -> 76.53.10.36 -> NPMplus',
backend: '192.168.11.50:4000 Phoenix surface',
visibility: 'public',
status: 'live',
source: 'docs/INSTITUTIONAL_REGISTRY.md',
},
{
hostnames: ['the-order.sankofa.nexus', 'www.the-order.sankofa.nexus'],
edge: 'Cloudflare DNS -> 76.53.10.36 -> 192.168.11.39:80 HAProxy -> portal',
backend: '192.168.11.51:3000 Sankofa portal stack',
visibility: 'public',
status: 'live',
source: 'docs/04-configuration/ALL_VMIDS_ENDPOINTS.md',
notes: 'www redirects to apex.',
},
{
hostnames: ['studio.sankofa.nexus'],
edge: 'Cloudflare DNS -> 76.53.10.36 -> NPMplus',
backend: '192.168.11.72:8000 Sankofa Studio',
visibility: 'public',
status: 'live',
source: 'docs/04-configuration/ALL_VMIDS_ENDPOINTS.md',
notes: 'Public tooling surface under /studio.',
},
{
hostnames: ['portal.sankofa.nexus'],
edge: 'Cloudflare Tunnel -> control-plane ingress',
backend: 'portal.portal.svc.cluster.local:80',
visibility: 'internal',
status: 'live',
source: 'cloudflare/tunnel-configs/control-plane.yaml',
notes: 'Client workspace and SSO shell.',
},
{
hostnames: ['keycloak.sankofa.nexus'],
edge: 'Cloudflare Tunnel -> control-plane ingress',
backend: 'keycloak.keycloak.svc.cluster.local:8080',
visibility: 'internal',
status: 'live',
source: 'cloudflare/tunnel-configs/control-plane.yaml',
notes: 'Shared identity provider.',
},
{
hostnames: ['admin.sankofa.nexus'],
edge: 'Cloudflare DNS -> NPMplus / protected app',
backend: 'SSO administration boundary',
visibility: 'internal',
status: 'documented',
source: 'docs/02-architecture/EXPECTED_WEB_CONTENT.md',
},
{
hostnames: ['dash.sankofa.nexus'],
edge: 'Cloudflare DNS -> NPMplus / operator access',
backend: 'IP allowlist + MFA operator dashboard',
visibility: 'hidden',
status: 'documented',
source: 'docs/02-architecture/EXPECTED_WEB_CONTENT.md',
},
{
hostnames: ['pve.sankofa.nexus', 'pve1.sankofa.nexus'],
edge: 'Cloudflare DNS -> NPMplus / tunnel',
backend: 'Proxmox management surfaces',
visibility: 'hidden',
status: 'documented',
source: 'docs/INSTITUTIONAL_REGISTRY.md',
},
{
hostnames: ['cc.sankofa.nexus', 'auth.cc.sankofa.nexus', 'admin.cc.sankofa.nexus', 'entity.cc.sankofa.nexus'],
edge: 'Cloudflare DNS -> Complete Credential stack',
backend: 'Credential issuance and admin portals',
visibility: 'hidden',
status: 'documented',
source: 'docs/INSTITUTIONAL_REGISTRY.md',
},
{
hostnames: ['ml110-01.sankofa.nexus', 'ml110-01-api.sankofa.nexus', 'ml110-01-metrics.sankofa.nexus'],
edge: 'Cloudflare DNS -> internal instance routing',
backend: '192.168.11.10 / instance services',
visibility: 'hidden',
status: 'documented',
source: 'docs/proxmox/DNS_CONFIGURATION.md',
},
{
hostnames: ['r630-01.sankofa.nexus', 'r630-01-api.sankofa.nexus', 'r630-01-metrics.sankofa.nexus'],
edge: 'Cloudflare DNS -> internal instance routing',
backend: '192.168.11.11 / instance services',
visibility: 'hidden',
status: 'documented',
source: 'docs/proxmox/DNS_CONFIGURATION.md',
},
{
hostnames: ['rancher.sankofa.nexus', 'argocd.sankofa.nexus', 'grafana.sankofa.nexus', 'vault.sankofa.nexus', 'k8s-api.sankofa.nexus'],
edge: 'Cloudflare Tunnel -> control-plane ingress',
backend: 'Kubernetes control-plane services',
visibility: 'hidden',
status: 'documented',
source: 'cloudflare/tunnel-configs/control-plane.yaml',
},
];
}
function buildHiddenRows(): AtlasHiddenRow[] {
return [
{
label: 'Atlas hidden inventory',
detail: 'Public page can surface routes and hosts that are documented in repo but not exposed in the main nav.',
source: 'portal/src/lib/atlas.ts',
},
{
label: 'IT read API',
detail: 'Server-side inventory and drift fetches are backed by IT_READ_API_URL and IT_READ_API_KEY.',
source: 'portal/src/app/api/it/_auth.ts',
},
{
label: 'Control-plane tunnel',
detail: 'portal, keycloak, rancher, argocd, grafana, vault, and k8s-api share the Cloudflare tunnel ingress.',
source: 'cloudflare/tunnel-configs/control-plane.yaml',
},
{
label: 'Proxmox instance DNS',
detail: 'ml110-01 and r630-01 hostnames are documented internal inventory rather than public marketing surfaces.',
source: 'docs/proxmox/DNS_CONFIGURATION.md',
},
];
}
function buildRevisions(): AtlasRevision[] {
return [
{
date: '2026-07-28',
title: 'Atlas route published',
detail: 'Added a public /atlas page on the Sankofa apex so the route can be linked from the corporate shell.',
},
{
date: '2026-06-04',
title: 'Institutional registry published',
detail: 'Documented the primary Sankofa and Phoenix hostnames, including public portal and identity boundaries.',
},
{
date: '2026-03-27',
title: 'NPM routing authority updated',
detail: 'Route ownership for sankofa.nexus, phoenix.sankofa.nexus, and the-order.sankofa.nexus was consolidated in the NPM routing docs.',
},
{
date: '2025-12-15',
title: 'Cluster status baseline',
detail: 'The sfvalley-01 Proxmox / Ceph status report captured the original cluster baseline and quorum state.',
},
];
}
function buildRecommendations(): AtlasRecommendation[] {
return [
{
priority: 'P1',
title: 'Publish an atlas snapshot feed',
detail: 'Generate a JSON snapshot of routes, hosts, and revision history so public rendering and incident review use the same payload.',
},
{
priority: 'P1',
title: 'Keep live inventory server-side',
detail: 'Continue fetching the IT inventory from the server-side read API so the public page stays current without exposing credentials.',
},
{
priority: 'P2',
title: 'Export hidden inventory separately',
detail: 'Add a distinct capture-required section for hidden or operator-only hosts so the public page stays readable while still exhaustive.',
},
{
priority: 'P2',
title: 'Add CSV / JSON export buttons',
detail: 'Allow the atlas to feed handoff, audit, and escalation workflows without copy/paste.',
},
{
priority: 'P3',
title: 'Add route health badges',
detail: 'Annotate route rows with health and 404/5xx checks once the checkers are wired to the public edge.',
},
];
}
async function loadLiveSummary() {
const base = itReadApiBaseUrl();
const apiKey = itReadApiKey();
const readApiConfigured = Boolean(base);
if (!base) {
return {
collectedAt: undefined,
guestCount: null,
runningCount: null,
stoppedCount: null,
portMapState: 'unconfigured',
portMapNote: 'IT_READ_API_URL is not configured on the portal server.',
inventoryState: 'unconfigured',
summaryState: 'unconfigured',
readApiConfigured,
};
}
const root = base.replace(/\/$/, '');
const [summary, inventory, portMap] = await Promise.all([
fetchJson(`${root}/v1/summary`, apiKey),
fetchJson(`${root}/v1/inventory/live`, apiKey),
fetchJson(`${root}/v1/portmap/joined`, apiKey),
]);
const summaryRecord = asRecord(summary);
const inventoryRecord = asRecord(inventory);
const portMapRecord = asRecord(portMap);
const guestRows = buildGuestRows(inventoryRecord ?? inventory);
const liveSummary = summaryRecord && 'artifacts' in summaryRecord ? (summaryRecord.artifacts as RecordLike) : null;
return {
collectedAt:
toText(summaryRecord?.envelope_at ?? summaryRecord?.live_collected_at ?? summaryRecord?.drift_collected_at, undefined),
guestCount: typeof summaryRecord?.guest_count === 'number' ? summaryRecord.guest_count : guestRows.length,
runningCount:
typeof inventoryRecord?.running === 'number'
? inventoryRecord.running
: typeof summaryRecord?.guest_count === 'number'
? summaryRecord.guest_count
: null,
stoppedCount:
typeof inventoryRecord?.stopped === 'number' ? inventoryRecord.stopped : null,
portMapState: portMapRecord?.stale ? 'stale' : 'live',
portMapNote: toText(portMapRecord?.note, undefined),
inventoryState: inventory ? 'live' : 'unavailable',
summaryState: summary ? 'live' : 'unavailable',
readApiConfigured,
// keep the snapshot summary available if the UI wants to expose it later
// without another fetch path.
...(liveSummary ? { liveArtifacts: liveSummary } : {}),
} as AtlasSnapshot['liveSummary'] & { liveArtifacts?: RecordLike };
}
export async function loadAtlasSnapshot(): Promise<AtlasSnapshot> {
const liveSummary = await loadLiveSummary();
const routes = buildRoutes();
const hiddenRows = buildHiddenRows();
const revisions = buildRevisions();
const recommendations = buildRecommendations();
const liveInventory = await (async () => {
const base = itReadApiBaseUrl();
const apiKey = itReadApiKey();
if (!base) return null;
return fetchJson(`${base.replace(/\/$/, '')}/v1/inventory/live`, apiKey);
})();
return {
routeRows: routes,
guestRows: buildGuestRows(liveInventory),
hiddenRows,
revisions,
recommendations,
liveSummary,
};
}
+1
View File
@@ -68,6 +68,7 @@ export const corporateNav: CorporateNavItem[] = [
{ label: 'Institutional grade', href: '#institutional-grade' },
{ label: 'Compliance', href: '#institutional-compliance' },
{ label: 'Resources', href: '#resources' },
{ label: 'Atlas', href: '/atlas' },
];
export const productDivisions: ProductDivision[] = [