|
@@ -220,6 +220,87 @@
|
|
|
|
|
|
|
|
/* Inline highlights */
|
|
/* Inline highlights */
|
|
|
.accent { color: var(--accent-strong); font-weight: 600; }
|
|
.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>
|
|
</style>
|
|
|
</head>
|
|
</head>
|
|
|
<body>
|
|
<body>
|
|
@@ -255,7 +336,8 @@
|
|
|
<a class="btn btn-ghost" href="https://git.chiapparini.org/chiappa/irdb">View source on Git</a>
|
|
<a class="btn btn-ghost" href="https://git.chiapparini.org/chiappa/irdb">View source on Git</a>
|
|
|
</div>
|
|
</div>
|
|
|
</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.">
|
|
<img src="/assets/screenshots/Dashboard.png" alt="IRDB dashboard showing real-time activity, top threat categories, and ingest volume.">
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -298,8 +380,210 @@
|
|
|
and machines that <em>block</em> them. Below is the same picture, in plain steps.
|
|
and machines that <em>block</em> them. Below is the same picture, in plain steps.
|
|
|
</p>
|
|
</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>
|
|
|
|
|
|
|
|
<div class="steps">
|
|
<div class="steps">
|
|
@@ -360,7 +644,8 @@
|
|
|
<li>Health indicators for background jobs</li>
|
|
<li>Health indicators for background jobs</li>
|
|
|
</ul>
|
|
</ul>
|
|
|
</div>
|
|
</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">
|
|
<img src="/assets/screenshots/Dashboard.png" alt="IRDB dashboard">
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -375,7 +660,8 @@
|
|
|
<li>Quick links into the full per-IP history</li>
|
|
<li>Quick links into the full per-IP history</li>
|
|
|
</ul>
|
|
</ul>
|
|
|
</div>
|
|
</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">
|
|
<img src="/assets/screenshots/IP_list.png" alt="IP list view with filters and reputation scores">
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -390,7 +676,8 @@
|
|
|
<li>Full audit trail of reports and admin actions</li>
|
|
<li>Full audit trail of reports and admin actions</li>
|
|
|
</ul>
|
|
</ul>
|
|
|
</div>
|
|
</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">
|
|
<img src="/assets/screenshots/IP_details.png" alt="IP details with score breakdown and history">
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -405,7 +692,8 @@
|
|
|
<li>Score distribution chart to set thresholds confidently</li>
|
|
<li>Score distribution chart to set thresholds confidently</li>
|
|
|
</ul>
|
|
</ul>
|
|
|
</div>
|
|
</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">
|
|
<img src="/assets/screenshots/Policies_edit.png" alt="Policy editor with thresholds and preview">
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -420,7 +708,8 @@
|
|
|
<li>Configurable retention to match your policy</li>
|
|
<li>Configurable retention to match your policy</li>
|
|
|
</ul>
|
|
</ul>
|
|
|
</div>
|
|
</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">
|
|
<img src="/assets/screenshots/Audit_log.png" alt="Audit log table">
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -519,5 +808,80 @@ docker compose up -d
|
|
|
</div>
|
|
</div>
|
|
|
</footer>
|
|
</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">×</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>
|
|
</body>
|
|
|
</html>
|
|
</html>
|