"use client"; import { Background, Controls, MiniMap, ReactFlow, ReactFlowProvider, useEdgesState, useNodesState, useReactFlow, } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; import { useCallback, useEffect, useMemo, useState } from "react"; import { ChevronLeft, ChevronRight, ShieldCheck } from "lucide-react"; import { ZTFlowNode } from "./ZTFlowNode"; import { ZERO_TRUST_SCENARIOS } from "./scenarios"; import type { ZTScenarioDefinition, ZTScenarioStep } from "./types"; import { PILLAR_LABELS } from "./types"; import { cn } from "@/lib/utils"; const nodeTypes = { ztNode: ZTFlowNode }; const CROSS_CUTTING = [ "Never trust, always verify — aucune confiance implicite au réseau seul.", "Moindre privilège sur les accès, comptes de service et flux.", "Supposer la compromission : limiter le rayon d’explosion (blast radius).", "Vérification continue : identité, contexte et signaux de risque.", "Journalisation, corrélation et observabilité pour détecter les abus.", ]; function applyStepStyle( scenario: ZTScenarioDefinition, step: ZTScenarioStep | undefined, ) { const hn = step?.highlightNodes ?? []; const he = step?.highlightEdges ?? []; const nodeDim = hn.length > 0 ? (id: string) => !hn.includes(id) : () => false; const edgeDim = he.length > 0 ? (id: string) => !he.includes(id) : () => false; const nodes = scenario.nodes.map((n) => ({ ...n, className: cn( "transition-all duration-300", nodeDim(n.id) ? "opacity-[0.38]" : "opacity-100", hn.includes(n.id) && "ring-2 ring-araucaria-400 ring-offset-2 ring-offset-cosmos-950 rounded-xl", ), })); const edges = scenario.edges.map((e) => { const dimE = edgeDim(e.id); const baseStyle = (e.style as Record | undefined) ?? {}; return { ...e, animated: he.length === 0 ? e.animated : he.includes(e.id), style: { ...baseStyle, opacity: dimE ? 0.32 : 1, }, labelStyle: { ...(e.labelStyle as object), opacity: dimE ? 0.45 : 1, }, zIndex: he.includes(e.id) ? 10 : 0, }; }); return { nodes, edges }; } function FitViewSync({ scenarioId, stepIndex, }: { scenarioId: string; stepIndex: number; }) { const { fitView } = useReactFlow(); const run = useCallback(() => { const id = requestAnimationFrame(() => { fitView({ padding: 0.18, duration: 280, maxZoom: 1.35 }); }); return () => cancelAnimationFrame(id); }, [fitView]); useEffect(() => { const t = setTimeout(run, 60); return () => clearTimeout(t); }, [scenarioId, stepIndex, run]); return null; } function FlowCanvas({ scenario, step, }: { scenario: ZTScenarioDefinition; step: ZTScenarioStep | undefined; }) { const { nodes: initialNodes, edges: initialEdges } = useMemo( () => applyStepStyle(scenario, step), [scenario, step], ); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); useEffect(() => { const styled = applyStepStyle(scenario, step); setNodes(styled.nodes); setEdges(styled.edges); }, [scenario, step, setNodes, setEdges]); return (
); } export function ZeroTrustScenarioViewer() { const [scenarioIndex, setScenarioIndex] = useState(0); const [stepIndex, setStepIndex] = useState(0); const scenario = ZERO_TRUST_SCENARIOS[scenarioIndex]!; const step = scenario.steps[stepIndex]; useEffect(() => { setStepIndex(0); }, [scenarioIndex]); const goPrev = () => setStepIndex((i) => Math.max(0, i - 1)); const goNext = () => setStepIndex((i) => Math.min(scenario.steps.length - 1, i + 1)); return (

{scenario.subtitle}

{scenario.intro}

Étape {stepIndex + 1} — {step?.title}

{step?.description}

{step && step.pillars.length > 0 && (

Piliers NIST SP 800-207 (rappel)

    {step.pillars.map((p) => (
  • {PILLAR_LABELS[p]}
  • ))}
)} {step && (
    {step.practices.map((line, i) => (
  • {line}
  • ))}
)}

Principes transverses Zero Trust

    {CROSS_CUTTING.map((line, i) => (
  • {line}
  • ))}

Référence : modèle en piliers décrit dans NIST SP 800-207 (Zero Trust Architecture). Les libellés sont vulgarisés pour l’interface.

); }