Forráskód Böngészése

feat(ui): redesign /about flow diagram and add zoom modal

Replace the static flow_scematic.png with an inline, theme-aware SVG that
matches the about-page indigo/emerald palette and inherits light/dark mode.
Step-number badges (1–5) are placed on the diagram nodes that correspond
to the workflow list directly below it, so non-technical readers can
trace each step visually.

Add a click-to-enlarge lightbox covering the new diagram, the hero shot,
and all five tour screenshots; supports keyboard activation, backdrop /
button close, and Escape.

On the login page, lift the "New here?" teaser out of the login card
into its own visually-separated panel beneath it, so the call to /about
reads as a distinct CTA rather than a footnote.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ClaudePriv@chiappa.zhdk.ch 9 órája
szülő
commit
3f628837d0
2 módosított fájl, 384 hozzáadás és 13 törlés
  1. 372 8
      ui/public/about.html
  2. 12 5
      ui/resources/views/pages/login.twig

+ 372 - 8
ui/public/about.html

@@ -220,6 +220,87 @@
 
         /* Inline highlights */
         .accent { color: var(--accent-strong); font-weight: 600; }
+
+        /* Zoomable images / diagrams */
+        .zoomable {
+            cursor: zoom-in;
+            transition: transform 0.15s ease, box-shadow 0.15s ease;
+            position: relative;
+        }
+        .zoomable:hover { transform: translateY(-2px); }
+        .zoomable:hover .zoom-hint { opacity: 1; }
+        .zoomable:focus-visible { outline: 3px solid var(--indigo); outline-offset: 4px; }
+        .zoom-hint {
+            position: absolute; top: 0.6rem; right: 0.6rem;
+            background: rgba(15, 23, 42, 0.78); color: #fff;
+            font-size: 0.72rem; font-weight: 600; letter-spacing: 0.04em;
+            padding: 0.3rem 0.55rem; border-radius: 999px;
+            opacity: 0; transition: opacity 0.15s ease;
+            pointer-events: none; backdrop-filter: blur(4px);
+            display: inline-flex; align-items: center; gap: 0.3rem;
+        }
+
+        /* Flow diagram container */
+        .flow-diagram-card {
+            border-radius: 0.85rem; border: 1px solid var(--border);
+            box-shadow: var(--shadow); background: var(--bg-card);
+            padding: 1rem 1rem 0.5rem; margin: 0 auto 2.5rem;
+            max-width: 100%;
+        }
+        .flow-diagram-svg { display: block; width: 100%; height: auto; }
+        .flow-diagram-legend {
+            display: flex; flex-wrap: wrap; gap: 1.25rem; justify-content: center;
+            padding: 0.5rem 0 0.75rem; font-size: 0.85rem; color: var(--text-muted);
+        }
+        .flow-diagram-legend .lg-item { display: inline-flex; align-items: center; gap: 0.45rem; }
+        .flow-diagram-legend .lg-dot {
+            width: 14px; height: 3px; border-radius: 2px; display: inline-block;
+        }
+        .lg-dot.dot-emerald { background: #10b981; }
+        .lg-dot.dot-indigo  { background: #6366f1; }
+
+        /* Zoom modal */
+        .zoom-modal {
+            position: fixed; inset: 0; z-index: 100;
+            background: rgba(2, 6, 23, 0.9);
+            display: none;
+            align-items: center; justify-content: center;
+            padding: 2rem;
+            backdrop-filter: blur(6px);
+        }
+        .zoom-modal[aria-hidden="false"] { display: flex; }
+        .zoom-modal-content {
+            position: relative;
+            max-width: 96vw; max-height: 92vh;
+            display: flex; align-items: center; justify-content: center;
+            background: var(--bg-card); border-radius: 0.75rem;
+            box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.6);
+            padding: 1rem;
+            overflow: auto;
+        }
+        .zoom-modal-content > img,
+        .zoom-modal-content > svg {
+            display: block;
+            max-width: calc(96vw - 2rem);
+            max-height: calc(92vh - 2rem);
+            width: auto; height: auto;
+            border-radius: 0.5rem;
+        }
+        .zoom-close {
+            position: absolute; top: -3rem; right: 0;
+            background: rgba(255, 255, 255, 0.12);
+            color: #fff;
+            border: 1px solid rgba(255, 255, 255, 0.25);
+            width: 40px; height: 40px; border-radius: 50%;
+            font-size: 1.4rem; line-height: 1; cursor: pointer;
+            display: flex; align-items: center; justify-content: center;
+            transition: background 0.15s ease;
+        }
+        .zoom-close:hover { background: rgba(255, 255, 255, 0.22); }
+        @media (max-width: 600px) {
+            .zoom-modal { padding: 1rem; }
+            .zoom-close { top: 0.4rem; right: 0.4rem; }
+        }
     </style>
 </head>
 <body>
@@ -255,7 +336,8 @@
                 <a class="btn btn-ghost" href="https://git.chiapparini.org/chiappa/irdb">View source on Git</a>
             </div>
         </div>
-        <div class="hero-shot">
+        <div class="hero-shot zoomable" data-zoom-label="Dashboard">
+            <span class="zoom-hint" aria-hidden="true">↗ Click to enlarge</span>
             <img src="/assets/screenshots/Dashboard.png" alt="IRDB dashboard showing real-time activity, top threat categories, and ingest volume.">
         </div>
     </div>
@@ -298,8 +380,210 @@
             and machines that <em>block</em> them. Below is the same picture, in plain steps.
         </p>
 
-        <div style="text-align:center; margin: 0 0 2.5rem;">
-            <img src="/assets/screenshots/flow_scematic.png" alt="Diagram showing reporters sending events to IRDB and consumers pulling tailored block lists." style="max-width: 100%; height: auto; border-radius: 0.75rem; border: 1px solid var(--border); box-shadow: var(--shadow); background: var(--bg-card); padding: 0.5rem;">
+        <div class="flow-diagram-card zoomable" data-zoom-label="How IRDB connects reporters and consumers" role="button" tabindex="0" aria-label="Open enlarged flow diagram">
+            <span class="zoom-hint" aria-hidden="true">↗ Click to enlarge</span>
+            <svg viewBox="0 0 1120 560" xmlns="http://www.w3.org/2000/svg"
+                 class="flow-diagram-svg" role="img"
+                 aria-labelledby="flowDiagramTitle flowDiagramDesc">
+                <title id="flowDiagramTitle">How IRDB connects reporters and consumers</title>
+                <desc id="flowDiagramDesc">Reporters send abuse reports to IRDB. IRDB scores reports and applies policies. Consumers pull tailored block lists. Operators monitor and refine.</desc>
+
+                <style>
+                    .fd-node {
+                        fill: var(--bg-card, #ffffff);
+                        stroke: var(--border, #e2e8f0);
+                        stroke-width: 1.5;
+                    }
+                    .fd-card {
+                        fill: var(--bg-soft, #f1f5f9);
+                        stroke: var(--border, #e2e8f0);
+                        stroke-width: 1;
+                    }
+                    .fd-cluster-title {
+                        font: 600 17px ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
+                        fill: var(--text, #0f172a);
+                    }
+                    .fd-cluster-sub {
+                        font: 13px ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
+                        fill: var(--text-muted, #64748b);
+                    }
+                    .fd-card-text {
+                        font: 500 14px ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
+                        fill: var(--text, #0f172a);
+                    }
+                    .fd-arrow-label {
+                        font: 600 13px ui-sans-serif, system-ui, sans-serif;
+                        fill: var(--text-muted, #64748b);
+                    }
+                    .fd-arrow { fill: none; stroke-width: 2.5; }
+                    .fd-arrow.emerald { stroke: #10b981; marker-end: url(#fd-arrow-emerald); }
+                    .fd-arrow.indigo  { stroke: #6366f1; marker-end: url(#fd-arrow-indigo); }
+                    .fd-link {
+                        fill: none; stroke: #94a3b8; stroke-width: 2;
+                        stroke-dasharray: 5 4;
+                        marker-start: url(#fd-arrow-muted-rev);
+                        marker-end: url(#fd-arrow-muted);
+                    }
+                    .fd-badge { fill: #6366f1; }
+                    .fd-badge.emerald { fill: #10b981; }
+                    .fd-badge.invert  { fill: #ffffff; stroke: #6366f1; stroke-width: 2; }
+                    .fd-badge-stroke { stroke: var(--bg-card, #fff); stroke-width: 2; }
+                    .fd-badge-num {
+                        font: 700 14px ui-sans-serif, system-ui, sans-serif;
+                        fill: #ffffff;
+                        text-anchor: middle;
+                        dominant-baseline: central;
+                    }
+                    .fd-badge-num.indigo-num { fill: #4f46e5; }
+                    .fd-irdb-bg { fill: #6366f1; }
+                    .fd-irdb-title {
+                        font: 700 26px ui-monospace, SFMono-Regular, Menlo, monospace;
+                        fill: #ffffff;
+                        text-anchor: middle;
+                        letter-spacing: -0.02em;
+                    }
+                    .fd-irdb-sub {
+                        font: 13px ui-sans-serif, system-ui, sans-serif;
+                        fill: rgba(255,255,255,0.85);
+                        text-anchor: middle;
+                    }
+                    .fd-chip {
+                        fill: rgba(255,255,255,0.18);
+                        stroke: rgba(255,255,255,0.4);
+                        stroke-width: 1;
+                    }
+                    .fd-chip-text {
+                        font: 600 12px ui-sans-serif, system-ui, sans-serif;
+                        fill: #ffffff;
+                        text-anchor: middle;
+                        dominant-baseline: central;
+                    }
+                    .fd-icon { fill: #94a3b8; }
+                </style>
+
+                <defs>
+                    <marker id="fd-arrow-emerald" viewBox="0 0 12 12" refX="10" refY="6" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
+                        <path d="M 0 0 L 12 6 L 0 12 z" fill="#10b981"/>
+                    </marker>
+                    <marker id="fd-arrow-indigo" viewBox="0 0 12 12" refX="10" refY="6" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
+                        <path d="M 0 0 L 12 6 L 0 12 z" fill="#6366f1"/>
+                    </marker>
+                    <marker id="fd-arrow-muted" viewBox="0 0 12 12" refX="10" refY="6" markerWidth="6" markerHeight="6" orient="auto">
+                        <path d="M 0 0 L 12 6 L 0 12 z" fill="#94a3b8"/>
+                    </marker>
+                    <marker id="fd-arrow-muted-rev" viewBox="0 0 12 12" refX="2" refY="6" markerWidth="6" markerHeight="6" orient="auto">
+                        <path d="M 12 0 L 0 6 L 12 12 z" fill="#94a3b8"/>
+                    </marker>
+                    <symbol id="fd-server-icon" viewBox="0 0 30 30">
+                        <rect x="0" y="2" width="30" height="9" rx="2" fill="#94a3b8" fill-opacity="0.25"/>
+                        <rect x="0" y="14" width="30" height="9" rx="2" fill="#94a3b8" fill-opacity="0.25"/>
+                        <circle cx="5" cy="6.5" r="1.4" fill="#94a3b8"/>
+                        <circle cx="5" cy="18.5" r="1.4" fill="#94a3b8"/>
+                        <rect x="11" y="5" width="14" height="1.5" rx="0.5" fill="#94a3b8"/>
+                        <rect x="11" y="17" width="14" height="1.5" rx="0.5" fill="#94a3b8"/>
+                    </symbol>
+                </defs>
+
+                <!-- Reporters cluster (1) -->
+                <g transform="translate(20,80)">
+                    <rect class="fd-node" x="0" y="0" width="240" height="300" rx="16"/>
+                    <text class="fd-cluster-title" x="120" y="34" text-anchor="middle">Reporters</text>
+                    <text class="fd-cluster-sub"   x="120" y="54" text-anchor="middle">things that see attacks</text>
+
+                    <g transform="translate(20,80)">
+                        <rect class="fd-card" width="200" height="44" rx="10"/>
+                        <use href="#fd-server-icon" x="10" y="7" width="28" height="28"/>
+                        <text class="fd-card-text" x="50" y="28">Web servers</text>
+                    </g>
+                    <g transform="translate(20,140)">
+                        <rect class="fd-card" width="200" height="44" rx="10"/>
+                        <use href="#fd-server-icon" x="10" y="7" width="28" height="28"/>
+                        <text class="fd-card-text" x="50" y="28">Mail relays</text>
+                    </g>
+                    <g transform="translate(20,200)">
+                        <rect class="fd-card" width="200" height="44" rx="10"/>
+                        <use href="#fd-server-icon" x="10" y="7" width="28" height="28"/>
+                        <text class="fd-card-text" x="50" y="28">Fail2ban / IDS</text>
+                    </g>
+
+                    <g transform="translate(220,-12)">
+                        <circle class="fd-badge fd-badge-stroke" r="18"/>
+                        <text class="fd-badge-num">1</text>
+                    </g>
+                </g>
+
+                <!-- Reports arrow (2) -->
+                <text class="fd-arrow-label" x="365" y="218" text-anchor="middle">reports</text>
+                <path class="fd-arrow emerald" d="M 270 230 L 458 230"/>
+                <g transform="translate(365,258)">
+                    <circle class="fd-badge emerald fd-badge-stroke" r="14"/>
+                    <text class="fd-badge-num">2</text>
+                </g>
+
+                <!-- IRDB center (3) -->
+                <g transform="translate(470,140)">
+                    <rect class="fd-irdb-bg" width="180" height="180" rx="18"/>
+                    <text class="fd-irdb-title" x="90" y="62">IRDB</text>
+                    <text class="fd-irdb-sub" x="90" y="84">score engine</text>
+                    <rect class="fd-chip" x="22" y="118" width="136" height="36" rx="10"/>
+                    <text class="fd-chip-text" x="90" y="136">policies</text>
+                    <g transform="translate(155,118)">
+                        <circle class="fd-badge invert" r="14"/>
+                        <text class="fd-badge-num indigo-num">3</text>
+                    </g>
+                </g>
+
+                <!-- Block list arrow -->
+                <text class="fd-arrow-label" x="755" y="218" text-anchor="middle">tailored block list</text>
+                <path class="fd-arrow indigo" d="M 660 230 L 848 230"/>
+
+                <!-- Consumers cluster (4) -->
+                <g transform="translate(860,80)">
+                    <rect class="fd-node" x="0" y="0" width="240" height="300" rx="16"/>
+                    <text class="fd-cluster-title" x="120" y="34" text-anchor="middle">Consumers</text>
+                    <text class="fd-cluster-sub"   x="120" y="54" text-anchor="middle">things that block</text>
+
+                    <g transform="translate(20,80)">
+                        <rect class="fd-card" width="200" height="44" rx="10"/>
+                        <use href="#fd-server-icon" x="10" y="7" width="28" height="28"/>
+                        <text class="fd-card-text" x="50" y="28">Firewalls</text>
+                    </g>
+                    <g transform="translate(20,140)">
+                        <rect class="fd-card" width="200" height="44" rx="10"/>
+                        <use href="#fd-server-icon" x="10" y="7" width="28" height="28"/>
+                        <text class="fd-card-text" x="50" y="28">Proxies</text>
+                    </g>
+                    <g transform="translate(20,200)">
+                        <rect class="fd-card" width="200" height="44" rx="10"/>
+                        <use href="#fd-server-icon" x="10" y="7" width="28" height="28"/>
+                        <text class="fd-card-text" x="50" y="28">Load balancers</text>
+                    </g>
+
+                    <g transform="translate(220,-12)">
+                        <circle class="fd-badge fd-badge-stroke" r="18"/>
+                        <text class="fd-badge-num">4</text>
+                    </g>
+                </g>
+
+                <!-- Operators (5) -->
+                <g transform="translate(440,420)">
+                    <rect class="fd-node" width="240" height="80" rx="16"/>
+                    <text class="fd-cluster-title" x="120" y="36" text-anchor="middle">Operators</text>
+                    <text class="fd-cluster-sub"   x="120" y="58" text-anchor="middle">monitor · refine · override</text>
+                    <g transform="translate(220,-12)">
+                        <circle class="fd-badge fd-badge-stroke" r="18"/>
+                        <text class="fd-badge-num">5</text>
+                    </g>
+                </g>
+
+                <!-- IRDB ↔ operator dashed connector -->
+                <path class="fd-link" d="M 560 332 L 560 410"/>
+            </svg>
+            <div class="flow-diagram-legend">
+                <span class="lg-item"><span class="lg-dot dot-emerald"></span> reports flow in</span>
+                <span class="lg-item"><span class="lg-dot dot-indigo"></span> tailored block list goes out</span>
+                <span class="lg-item">numbered dots match the steps below</span>
+            </div>
         </div>
 
         <div class="steps">
@@ -360,7 +644,8 @@
                     <li>Health indicators for background jobs</li>
                 </ul>
             </div>
-            <div class="show-img">
+            <div class="show-img zoomable" data-zoom-label="Dashboard">
+                <span class="zoom-hint" aria-hidden="true">↗ Click to enlarge</span>
                 <img src="/assets/screenshots/Dashboard.png" alt="IRDB dashboard">
             </div>
         </div>
@@ -375,7 +660,8 @@
                     <li>Quick links into the full per-IP history</li>
                 </ul>
             </div>
-            <div class="show-img">
+            <div class="show-img zoomable" data-zoom-label="IP list">
+                <span class="zoom-hint" aria-hidden="true">↗ Click to enlarge</span>
                 <img src="/assets/screenshots/IP_list.png" alt="IP list view with filters and reputation scores">
             </div>
         </div>
@@ -390,7 +676,8 @@
                     <li>Full audit trail of reports and admin actions</li>
                 </ul>
             </div>
-            <div class="show-img">
+            <div class="show-img zoomable" data-zoom-label="IP details">
+                <span class="zoom-hint" aria-hidden="true">↗ Click to enlarge</span>
                 <img src="/assets/screenshots/IP_details.png" alt="IP details with score breakdown and history">
             </div>
         </div>
@@ -405,7 +692,8 @@
                     <li>Score distribution chart to set thresholds confidently</li>
                 </ul>
             </div>
-            <div class="show-img">
+            <div class="show-img zoomable" data-zoom-label="Policy editor">
+                <span class="zoom-hint" aria-hidden="true">↗ Click to enlarge</span>
                 <img src="/assets/screenshots/Policies_edit.png" alt="Policy editor with thresholds and preview">
             </div>
         </div>
@@ -420,7 +708,8 @@
                     <li>Configurable retention to match your policy</li>
                 </ul>
             </div>
-            <div class="show-img">
+            <div class="show-img zoomable" data-zoom-label="Audit log">
+                <span class="zoom-hint" aria-hidden="true">↗ Click to enlarge</span>
                 <img src="/assets/screenshots/Audit_log.png" alt="Audit log table">
             </div>
         </div>
@@ -519,5 +808,80 @@ docker compose up -d
     </div>
 </footer>
 
+<div id="zoom-modal" class="zoom-modal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="zoom-modal-label">
+    <div class="zoom-modal-content" role="document">
+        <button type="button" class="zoom-close" aria-label="Close enlarged view">&times;</button>
+        <span id="zoom-modal-label" class="sr-only" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)"></span>
+    </div>
+</div>
+
+<script>
+(function () {
+    const modal = document.getElementById('zoom-modal');
+    if (!modal) return;
+    const content = modal.querySelector('.zoom-modal-content');
+    const closeBtn = modal.querySelector('.zoom-close');
+    const label = modal.querySelector('#zoom-modal-label');
+    let lastFocused = null;
+
+    function openZoom(source) {
+        // Drop any previously cloned media (keep close button + label).
+        Array.from(content.children).forEach((child) => {
+            if (child !== closeBtn && child !== label) child.remove();
+        });
+        const media = source.querySelector('svg, img') || source;
+        if (!media) return;
+        const clone = media.cloneNode(true);
+        if (clone.tagName === 'IMG') {
+            clone.removeAttribute('style');
+            clone.removeAttribute('class');
+        }
+        content.appendChild(clone);
+        label.textContent = source.getAttribute('data-zoom-label') || 'Enlarged view';
+        modal.setAttribute('aria-hidden', 'false');
+        document.documentElement.style.overflow = 'hidden';
+        lastFocused = document.activeElement;
+        closeBtn.focus();
+    }
+
+    function closeZoom() {
+        modal.setAttribute('aria-hidden', 'true');
+        document.documentElement.style.overflow = '';
+        Array.from(content.children).forEach((child) => {
+            if (child !== closeBtn && child !== label) child.remove();
+        });
+        if (lastFocused && typeof lastFocused.focus === 'function') {
+            lastFocused.focus();
+        }
+    }
+
+    modal.addEventListener('click', (e) => {
+        if (e.target === modal) closeZoom();
+    });
+    closeBtn.addEventListener('click', closeZoom);
+    document.addEventListener('keydown', (e) => {
+        if (e.key === 'Escape' && modal.getAttribute('aria-hidden') === 'false') {
+            closeZoom();
+        }
+    });
+
+    document.querySelectorAll('.zoomable').forEach((el) => {
+        if (!el.hasAttribute('role')) el.setAttribute('role', 'button');
+        if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0');
+        el.addEventListener('click', (e) => {
+            // Don't trigger when clicking nested links.
+            if (e.target.closest('a, button:not(.zoom-close)')) return;
+            openZoom(el);
+        });
+        el.addEventListener('keydown', (e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+                e.preventDefault();
+                openZoom(el);
+            }
+        });
+    });
+})();
+</script>
+
 </body>
 </html>

+ 12 - 5
ui/resources/views/pages/login.twig

@@ -3,7 +3,7 @@
 {% block title %}Sign in — IRDB{% endblock %}
 
 {% block guest_content %}
-<div class="flex min-h-screen items-center justify-center bg-slate-50 px-4 dark:bg-slate-950">
+<div class="flex min-h-screen flex-col items-center justify-center bg-slate-50 px-4 py-12 dark:bg-slate-950">
     <div class="w-full max-w-md rounded-2xl border border-slate-200 bg-white p-8 shadow-sm dark:border-slate-800 dark:bg-slate-900">
         <div class="mb-6 text-center">
             <img src="/assets/logo.svg" alt="IRDB" class="mx-auto h-24 w-24" />
@@ -61,9 +61,16 @@
             <p class="text-sm text-red-600 dark:text-red-400">No sign-in method is enabled. Set <code>OIDC_ENABLED=true</code> or <code>LOCAL_ADMIN_ENABLED=true</code> in the configuration.</p>
         {% endif %}
     </div>
-    <p class="mt-6 max-w-md text-center text-xs text-slate-500 dark:text-slate-400">
-        New here? IRDB collects abuse reports from your servers and hands every firewall a tailored block list.
-        <a href="/about" class="font-medium text-indigo-600 underline-offset-2 hover:underline dark:text-indigo-400">Learn what IRDB is &rarr;</a>
-    </p>
+
+    <div class="mt-6 w-full max-w-md rounded-2xl border border-slate-200 bg-white px-6 py-5 text-center shadow-sm dark:border-slate-800 dark:bg-slate-900">
+        <p class="text-xs font-semibold uppercase tracking-wider text-emerald-600 dark:text-emerald-400">New here?</p>
+        <p class="mt-2 text-sm text-slate-600 dark:text-slate-300">
+            IRDB collects abuse reports from your servers and hands every firewall a tailored block list.
+        </p>
+        <a href="/about" class="mt-3 inline-flex items-center gap-1 text-sm font-semibold text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300">
+            Learn what IRDB is
+            <span aria-hidden="true">&rarr;</span>
+        </a>
+    </div>
 </div>
 {% endblock %}