diff options
| author | ertopogo <erwin.t.pombett@gmail.com> | 2026-02-20 19:36:00 +0100 |
|---|---|---|
| committer | ertopogo <erwin.t.pombett@gmail.com> | 2026-02-20 19:36:00 +0100 |
| commit | b713be161431729305701f80b3b6f53d2f07f62a (patch) | |
| tree | 820d055331f38e6edda2c1ee160a6611560fbf7c /server/public/index.html | |
Initial commit: schemas infrastructure Arauco avec serveur web
Diffstat (limited to 'server/public/index.html')
| -rw-r--r-- | server/public/index.html | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/server/public/index.html b/server/public/index.html new file mode 100644 index 0000000..c903ecf --- /dev/null +++ b/server/public/index.html @@ -0,0 +1,515 @@ +<!DOCTYPE html>
+<html lang="fr" data-theme="dark">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Arauco - Schemas Infrastructure</title>
+ <style>
+ :root {
+ --bg: #1a1b26;
+ --bg-sidebar: #16161e;
+ --bg-card: #1f2029;
+ --text: #c0caf5;
+ --text-muted: #565f89;
+ --accent: #7aa2f7;
+ --accent-hover: #89b4fa;
+ --border: #292e42;
+ --success: #9ece6a;
+ --warning: #e0af68;
+ --diagram-bg: #1a1b26;
+ }
+
+ [data-theme="light"] {
+ --bg: #f5f5f5;
+ --bg-sidebar: #e8e8e8;
+ --bg-card: #ffffff;
+ --text: #1a1b26;
+ --text-muted: #6b7280;
+ --accent: #4a6cf7;
+ --accent-hover: #3b5de7;
+ --border: #d1d5db;
+ --success: #22863a;
+ --warning: #b08800;
+ --diagram-bg: #ffffff;
+ }
+
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+
+ body {
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ display: flex;
+ height: 100vh;
+ overflow: hidden;
+ }
+
+ .sidebar {
+ width: 280px;
+ min-width: 280px;
+ background: var(--bg-sidebar);
+ border-right: 1px solid var(--border);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ .sidebar-header {
+ padding: 20px;
+ border-bottom: 1px solid var(--border);
+ }
+
+ .sidebar-header h1 {
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--accent);
+ letter-spacing: -0.5px;
+ }
+
+ .sidebar-header p {
+ font-size: 12px;
+ color: var(--text-muted);
+ margin-top: 4px;
+ }
+
+ .sidebar-nav {
+ flex: 1;
+ overflow-y: auto;
+ padding: 12px;
+ }
+
+ .category {
+ margin-bottom: 16px;
+ }
+
+ .category-title {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: var(--text-muted);
+ padding: 4px 8px;
+ margin-bottom: 4px;
+ }
+
+ .schema-link {
+ display: block;
+ padding: 8px 12px;
+ border-radius: 6px;
+ color: var(--text);
+ text-decoration: none;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ border: 1px solid transparent;
+ }
+
+ .schema-link:hover {
+ background: var(--bg-card);
+ border-color: var(--border);
+ }
+
+ .schema-link.active {
+ background: var(--accent);
+ color: #fff;
+ border-color: var(--accent);
+ }
+
+ .sidebar-footer {
+ padding: 12px;
+ border-top: 1px solid var(--border);
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ }
+
+ .btn-icon {
+ background: var(--bg-card);
+ border: 1px solid var(--border);
+ color: var(--text);
+ padding: 6px 10px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ transition: all 0.15s ease;
+ }
+
+ .btn-icon:hover {
+ border-color: var(--accent);
+ color: var(--accent);
+ }
+
+ .status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--success);
+ margin-left: auto;
+ animation: pulse 2s infinite;
+ }
+
+ @keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+ }
+
+ .main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ .toolbar {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 20px;
+ border-bottom: 1px solid var(--border);
+ background: var(--bg-sidebar);
+ }
+
+ .toolbar-title {
+ font-size: 14px;
+ font-weight: 600;
+ flex: 1;
+ }
+
+ .toolbar-path {
+ font-size: 12px;
+ color: var(--text-muted);
+ font-family: 'Cascadia Code', 'Fira Code', monospace;
+ }
+
+ .zoom-controls {
+ display: flex;
+ gap: 4px;
+ align-items: center;
+ }
+
+ .zoom-label {
+ font-size: 12px;
+ color: var(--text-muted);
+ min-width: 40px;
+ text-align: center;
+ }
+
+ .diagram-container {
+ flex: 1;
+ overflow: auto;
+ padding: 24px;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ }
+
+ .diagram-wrapper {
+ transform-origin: top left;
+ transition: transform 0.2s ease;
+ background: var(--diagram-bg);
+ border-radius: 8px;
+ padding: 24px;
+ min-width: 200px;
+ }
+
+ .diagram-wrapper svg {
+ width: auto !important;
+ height: auto !important;
+ min-width: 1200px;
+ max-width: none;
+ }
+
+ .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: var(--text-muted);
+ gap: 12px;
+ }
+
+ .empty-state-icon {
+ font-size: 48px;
+ opacity: 0.3;
+ }
+
+ .toast {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ background: var(--accent);
+ color: #fff;
+ padding: 10px 16px;
+ border-radius: 8px;
+ font-size: 13px;
+ opacity: 0;
+ transform: translateY(10px);
+ transition: all 0.3s ease;
+ pointer-events: none;
+ z-index: 100;
+ }
+
+ .toast.visible {
+ opacity: 1;
+ transform: translateY(0);
+ }
+
+ .sidebar-nav::-webkit-scrollbar,
+ .diagram-container::-webkit-scrollbar {
+ width: 6px;
+ }
+ .sidebar-nav::-webkit-scrollbar-track,
+ .diagram-container::-webkit-scrollbar-track {
+ background: transparent;
+ }
+ .sidebar-nav::-webkit-scrollbar-thumb,
+ .diagram-container::-webkit-scrollbar-thumb {
+ background: var(--border);
+ border-radius: 3px;
+ }
+ </style>
+</head>
+<body>
+
+ <aside class="sidebar">
+ <div class="sidebar-header">
+ <h1>Arauco</h1>
+ <p>Schemas Infrastructure</p>
+ </div>
+ <nav class="sidebar-nav" id="nav"></nav>
+ <div class="sidebar-footer">
+ <button class="btn-icon" id="themeToggle" title="Changer de theme">◑</button>
+ <button class="btn-icon" id="refreshBtn" title="Rafraichir la liste">↻</button>
+ <span class="status-dot" id="wsStatus" title="Connecte - Hot reload actif"></span>
+ </div>
+ </aside>
+
+ <main class="main">
+ <div class="toolbar" id="toolbar" style="display:none;">
+ <span class="toolbar-title" id="toolbarTitle"></span>
+ <span class="toolbar-path" id="toolbarPath"></span>
+ <div class="zoom-controls">
+ <button class="btn-icon" id="zoomOut" title="Zoom -">−</button>
+ <span class="zoom-label" id="zoomLabel">100%</span>
+ <button class="btn-icon" id="zoomIn" title="Zoom +">+</button>
+ <button class="btn-icon" id="zoomFit" title="Adapter a l'ecran">▢</button>
+ <button class="btn-icon" id="zoomReset" title="Reset zoom">↺</button>
+ </div>
+ </div>
+ <div class="diagram-container" id="diagramContainer">
+ <div class="empty-state">
+ <div class="empty-state-icon">◫</div>
+ <p>Selectionnez un schema dans la barre laterale</p>
+ </div>
+ </div>
+ </main>
+
+ <div class="toast" id="toast"></div>
+
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
+ <script>
+ const CATEGORY_LABELS = {
+ macro: 'Vue d\'ensemble',
+ reseau: 'Reseau',
+ vms: 'Machines Virtuelles',
+ iam: 'IAM / Identite',
+ applications: 'Applications'
+ };
+
+ const CATEGORY_ORDER = ['macro', 'reseau', 'vms', 'iam', 'applications'];
+
+ let currentSchema = null;
+ let currentZoom = 1;
+ let renderCounter = 0;
+
+ function getTheme() {
+ return document.documentElement.getAttribute('data-theme');
+ }
+
+ function initMermaid() {
+ mermaid.initialize({
+ startOnLoad: false,
+ theme: getTheme() === 'dark' ? 'dark' : 'default',
+ securityLevel: 'loose',
+ flowchart: {
+ htmlLabels: true,
+ curve: 'basis',
+ useMaxWidth: false,
+ nodeSpacing: 50,
+ rankSpacing: 70,
+ padding: 25,
+ wrappingWidth: 200
+ },
+ fontSize: 14,
+ themeVariables: getTheme() === 'dark'
+ ? { primaryColor: '#4a90d9', primaryTextColor: '#c0caf5', lineColor: '#5c6bc0', secondaryColor: '#292e42', tertiaryColor: '#1f2029' }
+ : {}
+ });
+ }
+
+ initMermaid();
+
+ async function loadSchemaList() {
+ const res = await fetch('/api/schemas');
+ const grouped = await res.json();
+ const nav = document.getElementById('nav');
+ nav.innerHTML = '';
+
+ for (const cat of CATEGORY_ORDER) {
+ if (!grouped[cat]) continue;
+ const div = document.createElement('div');
+ div.className = 'category';
+ div.innerHTML = `<div class="category-title">${CATEGORY_LABELS[cat] || cat}</div>`;
+ for (const schema of grouped[cat]) {
+ const link = document.createElement('a');
+ link.className = 'schema-link';
+ link.textContent = schema.name.replace(/_/g, ' ');
+ link.dataset.path = schema.path;
+ link.dataset.name = schema.name;
+ link.addEventListener('click', () => loadSchema(schema.path, schema.name));
+ div.appendChild(link);
+ }
+ nav.appendChild(div);
+ }
+ }
+
+ async function loadSchema(schemaPath, name) {
+ const res = await fetch(`/api/schema/${encodeURIComponent(schemaPath)}`);
+ const data = await res.json();
+ currentSchema = schemaPath;
+
+ document.querySelectorAll('.schema-link').forEach(el => {
+ el.classList.toggle('active', el.dataset.path === schemaPath);
+ });
+
+ document.getElementById('toolbar').style.display = 'flex';
+ document.getElementById('toolbarTitle').textContent = (name || schemaPath).replace(/_/g, ' ');
+ document.getElementById('toolbarPath').textContent = schemaPath;
+
+ await renderDiagram(data.content);
+ }
+
+ async function renderDiagram(mmdContent) {
+ const container = document.getElementById('diagramContainer');
+ const clean = mmdContent.replace(/%%\{init:.*?\}%%\n?/s, '');
+ renderCounter++;
+ const id = `mermaid-${renderCounter}`;
+
+ try {
+ const { svg } = await mermaid.render(id, clean);
+ container.innerHTML = `<div class="diagram-wrapper" id="diagramWrapper">${svg}</div>`;
+ requestAnimationFrame(() => {
+ document.getElementById('zoomFit').click();
+ });
+ } catch (err) {
+ container.innerHTML = `<div class="empty-state">
+ <div class="empty-state-icon">⚠</div>
+ <p>Erreur de rendu Mermaid</p>
+ <pre style="font-size:12px;color:var(--warning);max-width:600px;overflow:auto;">${err.message || err}</pre>
+ <pre style="font-size:11px;color:var(--text-muted);max-width:600px;overflow:auto;margin-top:8px;">${mmdContent.substring(0, 500)}</pre>
+ </div>`;
+ }
+ }
+
+ function applyZoom() {
+ const wrapper = document.getElementById('diagramWrapper');
+ if (wrapper) wrapper.style.transform = `scale(${currentZoom})`;
+ document.getElementById('zoomLabel').textContent = Math.round(currentZoom * 100) + '%';
+ }
+
+ document.getElementById('zoomIn').addEventListener('click', () => {
+ currentZoom = Math.min(currentZoom + 0.25, 10);
+ applyZoom();
+ });
+
+ document.getElementById('zoomOut').addEventListener('click', () => {
+ currentZoom = Math.max(currentZoom - 0.25, 0.2);
+ applyZoom();
+ });
+
+ document.getElementById('zoomFit').addEventListener('click', () => {
+ const wrapper = document.getElementById('diagramWrapper');
+ const container = document.getElementById('diagramContainer');
+ if (!wrapper || !container) return;
+ wrapper.style.transform = 'scale(1)';
+ const svgEl = wrapper.querySelector('svg');
+ if (!svgEl) return;
+ const svgRect = svgEl.getBoundingClientRect();
+ const containerRect = container.getBoundingClientRect();
+ const scaleX = (containerRect.width - 48) / svgRect.width;
+ const scaleY = (containerRect.height - 48) / svgRect.height;
+ currentZoom = Math.max(0.2, Math.min(10, Math.min(scaleX, scaleY)));
+ applyZoom();
+ });
+
+ document.getElementById('zoomReset').addEventListener('click', () => {
+ currentZoom = 1;
+ applyZoom();
+ });
+
+ document.getElementById('themeToggle').addEventListener('click', () => {
+ const html = document.documentElement;
+ const next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
+ html.setAttribute('data-theme', next);
+ initMermaid();
+ if (currentSchema) {
+ const active = document.querySelector('.schema-link.active');
+ if (active) loadSchema(active.dataset.path, active.dataset.name);
+ }
+ });
+
+ document.getElementById('refreshBtn').addEventListener('click', loadSchemaList);
+
+ function showToast(message) {
+ const toast = document.getElementById('toast');
+ toast.textContent = message;
+ toast.classList.add('visible');
+ setTimeout(() => toast.classList.remove('visible'), 3000);
+ }
+
+ function connectWebSocket() {
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
+ const ws = new WebSocket(`${proto}//${location.host}`);
+ const dot = document.getElementById('wsStatus');
+
+ ws.addEventListener('open', () => {
+ dot.style.background = 'var(--success)';
+ dot.title = 'Connecte - Hot reload actif';
+ });
+
+ ws.addEventListener('close', () => {
+ dot.style.background = 'var(--warning)';
+ dot.title = 'Deconnecte - Reconnexion...';
+ setTimeout(connectWebSocket, 2000);
+ });
+
+ ws.addEventListener('message', async (event) => {
+ const data = JSON.parse(event.data);
+ if (data.type === 'reload') {
+ showToast(`Schema mis a jour : ${data.path}`);
+ await loadSchemaList();
+ if (currentSchema === data.path) {
+ const active = document.querySelector('.schema-link.active');
+ if (active) loadSchema(active.dataset.path, active.dataset.name);
+ }
+ }
+ });
+ }
+
+ document.addEventListener('DOMContentLoaded', () => {
+ loadSchemaList();
+ connectWebSocket();
+ });
+
+ document.getElementById('diagramContainer').addEventListener('wheel', (e) => {
+ if (e.ctrlKey) {
+ e.preventDefault();
+ const delta = e.deltaY > 0 ? -0.1 : 0.1;
+ currentZoom = Math.max(0.2, Math.min(10, currentZoom + delta));
+ applyZoom();
+ }
+ }, { passive: false });
+ </script>
+</body>
+</html>
|
