From 96615b46a72e7902f7ade2619b21649bf41b2b1b Mon Sep 17 00:00:00 2001 From: ertopogo Date: Mon, 6 Apr 2026 13:50:16 +0200 Subject: documentation zero trust --- .../demos/zero-trust/ZeroTrustScenarioViewer.tsx | 282 +++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 src/components/demos/zero-trust/ZeroTrustScenarioViewer.tsx (limited to 'src/components/demos/zero-trust/ZeroTrustScenarioViewer.tsx') diff --git a/src/components/demos/zero-trust/ZeroTrustScenarioViewer.tsx b/src/components/demos/zero-trust/ZeroTrustScenarioViewer.tsx new file mode 100644 index 0000000..7f7e221 --- /dev/null +++ b/src/components/demos/zero-trust/ZeroTrustScenarioViewer.tsx @@ -0,0 +1,282 @@ +"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. +

+
+
+
+ ); +} -- cgit v1.2.3