Vitrine publique (accueil, architecture, statut) + recherche plein texte
- Nouvelles pages publiques sans routeur externe (History API minimale, useRoute/RouteLink) : accueil, /architecture (état réel du projet, honnête sur les limites), /statut (télémétrie live de tripoli-edge lue directement sur /healthz et /metrics, aucune donnée simulée). - Recherche façon Spotlight dans TopBar : nouvel endpoint GET /api/search côté tripoli-api, recherche plein texte Postgres (tsvector généré + index GIN) sur le nom ET le contenu des fichiers texte extrait à l'upload (formats texte/code, borné à 5 Mio) ; extraits de résultat via ts_headline. - AuthLayout : logo cliquable vers l'accueil. Vérifié : tsc --noEmit propre, npm run build propre, cargo build -p tripoli-api propre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
ddd0fbc0cb
commit
3172b3e256
15 changed files with 858 additions and 20 deletions
157
app/src/components/marketing/ArchitecturePage.tsx
Normal file
157
app/src/components/marketing/ArchitecturePage.tsx
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import type { ReactNode } from 'react';
|
||||
import { Cpu, GitBranch, Network, Radio, Server, ShieldCheck } from 'lucide-react';
|
||||
import { MarketingHeader } from './MarketingHeader';
|
||||
import { MarketingFooter } from './MarketingFooter';
|
||||
|
||||
const FLOW = [
|
||||
{
|
||||
title: 'Caddy (edge partagé)',
|
||||
detail:
|
||||
"TLS Let's Encrypt automatique, routage par nom d'hôte. Ce Caddy sert aussi six autres domaines sur ce même serveur — tripoli-edge n'a jamais eu les ports 80/443 pour lui seul.",
|
||||
},
|
||||
{
|
||||
title: 'tripoli-edge — Rust (hyper/tokio)',
|
||||
detail:
|
||||
'Reverse-proxy interne (172.17.0.1:9000, passerelle docker0, jamais exposé directement). Expose /healthz et /metrics en clair, sans framework de métriques : cinq compteurs atomiques suffisaient.',
|
||||
},
|
||||
{
|
||||
title: 'tripoli-api — Rust (axum/sqlx)',
|
||||
detail:
|
||||
"Authentification par session opaque (Argon2, pas de JWT — révoquer une session, c'est supprimer une ligne). Upload en streaming vers disque, recherche plein texte Postgres.",
|
||||
},
|
||||
{
|
||||
title: 'PostgreSQL + blobs',
|
||||
detail:
|
||||
"Conteneur Docker dédié à Tripoli Cloud, séparé de celui d'un autre projet sur ce même serveur. Les fichiers sont nommés par UUID sur disque — le nom d'origine ne vit qu'en base.",
|
||||
},
|
||||
];
|
||||
|
||||
const LABS: { icon: ReactNode; title: string; detail: string }[] = [
|
||||
{
|
||||
icon: <Network size={18} />,
|
||||
title: 'Tunnel WireGuard',
|
||||
detail:
|
||||
"10.100.0.0/24 entre le Mac d'administration et le serveur — canal SSH/Ansible, jamais dans le chemin de trafic applicatif.",
|
||||
},
|
||||
{
|
||||
icon: <Server size={18} />,
|
||||
title: 'tripoli-netctl',
|
||||
detail:
|
||||
'Netns + veth créés à la demande via rtnetlink (Rust). Testé (création, ping traversant, destruction propre) — volontairement jamais branché sur le trafic de prod.',
|
||||
},
|
||||
{
|
||||
icon: <Radio size={18} />,
|
||||
title: 'bird-lab — BGP + BFD',
|
||||
detail:
|
||||
'Deux namespaces en session BGP via une racine en transit (AS 65000/65010/65011). Sessions Established, BFD Up et détection de panne vérifiées, puis démonté proprement.',
|
||||
},
|
||||
];
|
||||
|
||||
const STATE: [string, string][] = [
|
||||
['Frontend (app/)', 'Fait — branché sur les vraies données de tripoli-api, aucun mock.'],
|
||||
['WireGuard', 'Fait — 10.100.0.0/24.'],
|
||||
['tripoli-edge', 'Fait, en prod — /healthz, /metrics vérifiés.'],
|
||||
['tripoli-api', 'Fait, en prod — auth, fichiers, notifications, recherche.'],
|
||||
['tripoli-netctl', 'Fait — labo, jamais branché sur le trafic de prod.'],
|
||||
['Observabilité', 'Fait — scrape Prometheus/Grafana partagé, alertes versionnées.'],
|
||||
['bird-lab (BGP/BFD)', 'Fait, bonus — démonté après vérification.'],
|
||||
['CI/CD', 'Fait — Forgejo Actions, compte deploy sans privilège.'],
|
||||
['OpenTofu', 'Code écrit, non appliqué — identifiants OVH/Hetzner non fournis.'],
|
||||
];
|
||||
|
||||
export function ArchitecturePage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-bg text-ink">
|
||||
<MarketingHeader />
|
||||
|
||||
<main className="mx-auto max-w-[880px] px-6 py-16 sm:px-10 sm:py-24">
|
||||
<span className="inline-flex items-center gap-2 rounded-full border border-line bg-field px-3.5 py-1.5 text-xs font-semibold tracking-wide text-ink-soft">
|
||||
<Cpu size={13} />
|
||||
Un seul serveur, pas un cluster
|
||||
</span>
|
||||
|
||||
<h1 className="m-0 my-5 font-display text-[36px] font-semibold leading-[1.08] tracking-tight sm:text-[48px]">
|
||||
Ce qui tourne <span className="italic">réellement</span>.
|
||||
</h1>
|
||||
<p className="m-0 max-w-[62ch] text-[15.5px] leading-relaxed text-ink-soft">
|
||||
Un VPS Hetzner CPX22 (2 vCPU, 3,7 Go de RAM), partagé avec six autres projets personnels.
|
||||
Ce qui suit est honnête sur ce qui est en place — pas de cluster simulé, pas de haute
|
||||
disponibilité qui n'existe pas.
|
||||
</p>
|
||||
|
||||
<section className="mt-14">
|
||||
<h2 className="m-0 mb-6 font-display text-[22px] font-semibold text-ink">
|
||||
Le chemin d'une requête
|
||||
</h2>
|
||||
<div className="flex flex-col gap-3">
|
||||
{FLOW.map((step, i) => (
|
||||
<div key={step.title} className="flex gap-4 rounded-2xl border border-line bg-panel p-5 shadow-tc">
|
||||
<span className="flex h-8 w-8 flex-none items-center justify-center rounded-full bg-field text-[13px] font-bold text-ink">
|
||||
{i + 1}
|
||||
</span>
|
||||
<div>
|
||||
<p className="m-0 mb-1 text-[14.5px] font-bold text-ink">{step.title}</p>
|
||||
<p className="m-0 text-[13.5px] leading-relaxed text-ink-soft">{step.detail}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-14">
|
||||
<h2 className="m-0 mb-2 font-display text-[22px] font-semibold text-ink">
|
||||
Hors du chemin de trafic
|
||||
</h2>
|
||||
<p className="m-0 mb-6 text-[13.5px] text-ink-soft">
|
||||
Labos pédagogiques — jamais branchés sur le trafic de production.
|
||||
</p>
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
{LABS.map((lab) => (
|
||||
<div key={lab.title} className="rounded-2xl border border-line bg-panel p-5 shadow-tc">
|
||||
<div className="mb-2 text-ink-soft">{lab.icon}</div>
|
||||
<p className="m-0 mb-1 text-[13.5px] font-bold text-ink">{lab.title}</p>
|
||||
<p className="m-0 text-[12.5px] leading-relaxed text-ink-soft">{lab.detail}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-14">
|
||||
<h2 className="m-0 mb-6 font-display text-[22px] font-semibold text-ink">État réel</h2>
|
||||
<div className="overflow-hidden rounded-2xl border border-line bg-panel shadow-tc">
|
||||
{STATE.map(([name, status], i) => (
|
||||
<div
|
||||
key={name}
|
||||
className={`flex items-center justify-between gap-4 px-5 py-3.5 text-[13px] ${
|
||||
i !== STATE.length - 1 ? 'border-b border-line/70' : ''
|
||||
}`}
|
||||
>
|
||||
<span className="font-semibold text-ink">{name}</span>
|
||||
<span className="text-right text-ink-soft">{status}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-14 rounded-2xl border border-line bg-field p-6">
|
||||
<div className="mb-2 flex items-center gap-2 text-ink">
|
||||
<ShieldCheck size={16} />
|
||||
<p className="m-0 text-[13.5px] font-bold">Limites assumées</p>
|
||||
</div>
|
||||
<p className="m-0 text-[13px] leading-relaxed text-ink-soft">
|
||||
SSH public encore ouvert (choix explicite, pas encore restreint au VPN). Pas de
|
||||
virtualisation imbriquée sur ce VPS. Alertes Prometheus qui se déclenchent mais ne sont
|
||||
pas encore routées par notification. Détail complet dans le README du dépôt.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="mt-10 flex items-center gap-2 text-[13px] text-ink-soft">
|
||||
<GitBranch size={15} />
|
||||
Code et historique de commits versionnés sur Forgejo.
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<MarketingFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue