diff --git a/decnet_web/src/components/Attackers.css b/decnet_web/src/components/Attackers.css index 1d5e5756..627195c6 100644 --- a/decnet_web/src/components/Attackers.css +++ b/decnet_web/src/components/Attackers.css @@ -7,20 +7,52 @@ .ak-filter-row { display: flex; flex-wrap: wrap; - gap: 6px; + gap: 8px; align-items: center; } -.ak-filter-row .chip { - cursor: pointer; - transition: opacity 0.15s; +.attackers-root .seg-group { + display: flex; + border: 1px solid var(--border); + background: var(--panel); } -.ak-filter-row button { +.attackers-root .seg-group button { + display: flex; + align-items: center; + gap: 6px; + padding: 7px 12px; + font-size: 0.68rem; + letter-spacing: 1.5px; + border: none; + border-right: 1px solid var(--border); + background: transparent; + color: rgba(0, 255, 65, 0.5); + cursor: pointer; font-family: inherit; - font-size: inherit; } +.attackers-root .seg-group button:last-child { border-right: none; } + +.attackers-root .seg-group button.active { + background: var(--violet-tint-10); + color: var(--violet); +} + +.attackers-root .seg-group button:hover:not(.active) { color: var(--matrix); } + +.ak-dot { + display: inline-block; + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; +} + +.ak-dot-active { background: var(--alert); box-shadow: 0 0 5px var(--alert); } +.ak-dot-passive { background: #ffaa00; } +.ak-dot-inactive { background: rgba(0, 255, 65, 0.3); } + .attackers-root .controls-row { display: flex; gap: 12px; diff --git a/decnet_web/src/components/Attackers.tsx b/decnet_web/src/components/Attackers.tsx index 926009d5..4fd9813d 100644 --- a/decnet_web/src/components/Attackers.tsx +++ b/decnet_web/src/components/Attackers.tsx @@ -134,7 +134,10 @@ const Attackers: React.FC = () => {
-

ATTACKERS

+
+ +

ATTACKERS

+
{total.toLocaleString()} UNIQUE SOURCES · {activityCounts.active} ACTIVE · {activityCounts.passive} PASSIVE · {activityCounts.inactive} INACTIVE @@ -160,39 +163,35 @@ const Attackers: React.FC = () => {
- {(['active', 'passive', 'inactive'] as ActivityTier[]).map(tier => ( - - ))} - {countries.length > 0 && |} - {countries.map(cc => ( - - ))} - {(activityFilter || countryFilter || serviceFilter) && ( - + {(['active', 'passive', 'inactive'] as ActivityTier[]).map(tier => ( + + ))} +
+ {countries.length > 0 && ( +
+ {countries.map(cc => ( + + ))} +
)}
diff --git a/decnet_web/src/components/Bounty.tsx b/decnet_web/src/components/Bounty.tsx index 3877ca6c..d3fa1cb0 100644 --- a/decnet_web/src/components/Bounty.tsx +++ b/decnet_web/src/components/Bounty.tsx @@ -72,7 +72,8 @@ const Bounty: React.FC = () => { const offset = (page - 1) * limit; let url = `/bounty?limit=${limit}&offset=${offset}`; if (query) url += `&search=${encodeURIComponent(query)}`; - if (typeFilter) url += `&bounty_type=${typeFilter}`; + const apiType = typeFilter === 'mail' ? 'artifact' : typeFilter; + if (apiType) url += `&bounty_type=${apiType}`; const res = await api.get(url); setBounties(res.data.data); setTotal(res.data.total); @@ -94,10 +95,9 @@ const Bounty: React.FC = () => { const totalPages = Math.max(1, Math.ceil(total / limit)); - const credCount = bounties.filter(b => b.bounty_type === 'credential').length; - const payCount = bounties.filter(b => b.bounty_type === 'payload').length; const fpCount = bounties.filter(b => b.bounty_type === 'fingerprint').length; - const artCount = bounties.filter(b => b.bounty_type === 'artifact').length; + const artCount = bounties.filter(b => b.bounty_type === 'artifact' && b.payload?.kind !== 'mail').length; + const mailCount = bounties.filter(b => b.bounty_type === 'artifact' && b.payload?.kind === 'mail').length; const handleSortCol = (col: string) => { if (sortCol === col) { @@ -109,9 +109,14 @@ const Bounty: React.FC = () => { } }; + const filteredBounties = useMemo(() => { + if (typeFilter !== 'mail') return bounties; + return bounties.filter(b => b.bounty_type === 'artifact' && b.payload?.kind === 'mail'); + }, [bounties, typeFilter]); + const sortedBounties = useMemo(() => { - if (!sortCol) return bounties; - return [...bounties].sort((a, b) => { + if (!sortCol) return filteredBounties; + return [...filteredBounties].sort((a, b) => { let av: string | number = ''; let bv: string | number = ''; if (sortCol === 'time') { av = a.timestamp; bv = b.timestamp; } @@ -122,7 +127,7 @@ const Bounty: React.FC = () => { const cmp = String(av).localeCompare(String(bv)); return sortDir === 'asc' ? cmp : -cmp; }); - }, [bounties, sortCol, sortDir]); + }, [filteredBounties, sortCol, sortDir]); const SortTh: React.FC<{ col: string; children: React.ReactNode }> = ({ col, children }) => ( { const SEGMENTS: [string, string][] = [ ['', 'ALL'], - ['credential', 'CREDENTIALS'], - ['payload', 'PAYLOADS'], ['artifact', 'ARTIFACTS'], ['fingerprint', 'FINGERPRINTS'], + ['mail', 'EMAIL'], ]; const formatBytes = (n: any): string => { @@ -159,7 +163,7 @@ const Bounty: React.FC = () => {

BOUNTY VAULT

- {total.toLocaleString()} BOUNTIES · {credCount} CREDENTIALS · {payCount} PAYLOADS · {artCount} ARTIFACTS · {fpCount} FINGERPRINTS + {total.toLocaleString()} BOUNTIES · {artCount} ARTIFACTS · {fpCount} FINGERPRINTS · {mailCount} EMAIL
diff --git a/decnet_web/src/components/CanaryTokens.tsx b/decnet_web/src/components/CanaryTokens.tsx index 28233d82..93528a3f 100644 --- a/decnet_web/src/components/CanaryTokens.tsx +++ b/decnet_web/src/components/CanaryTokens.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { - Plus, Upload, X, AlertTriangle, Search, + Plus, Upload, X, AlertTriangle, Search, Target, } from '../icons'; import api from '../utils/api'; import { useEscapeKey } from '../hooks/useEscapeKey'; @@ -948,7 +948,10 @@ const CanaryTokens: React.FC = () => {
-

CANARY TOKENS

+
+ +

CANARY TOKENS

+
{tokens.length} TOKEN{tokens.length === 1 ? '' : 'S'} · {counts.planted} PLANTED · {counts.hits} TOTAL HIT{counts.hits === 1 ? '' : 'S'} · {blobs.length} UPLOADED BLOB{blobs.length === 1 ? '' : 'S'} diff --git a/decnet_web/src/components/Credentials.tsx b/decnet_web/src/components/Credentials.tsx index ea451ee6..a1286a9b 100644 --- a/decnet_web/src/components/Credentials.tsx +++ b/decnet_web/src/components/Credentials.tsx @@ -170,6 +170,23 @@ const Credentials: React.FC = () => { }); }, [creds, sortCol, sortDir]); + const sortedReuseRows = useMemo(() => { + if (!sortCol) return reuseRows; + return [...reuseRows].sort((a, b) => { + let av: string | number = ''; + let bv: string | number = ''; + if (sortCol === 'seen') { av = a.last_seen; bv = b.last_seen; } + else if (sortCol === 'principal') { av = a.principal ?? ''; bv = b.principal ?? ''; } + else if (sortCol === 'kind') { av = a.secret_kind; bv = b.secret_kind; } + else if (sortCol === 'targets') { av = a.target_count; bv = b.target_count; } + else if (sortCol === 'attempts') { av = a.attempt_count; bv = b.attempt_count; } + const cmp = typeof av === 'number' && typeof bv === 'number' + ? av - bv + : String(av).localeCompare(String(bv)); + return sortDir === 'asc' ? cmp : -cmp; + }); + }, [reuseRows, sortCol, sortDir]); + const SortTh: React.FC<{ col: string; children: React.ReactNode }> = ({ col, children }) => ( { - - - - - + LAST SEEN + PRINCIPAL + KIND + TARGETS + ATTEMPTS - {reuseRows.length > 0 ? reuseRows.map(r => { + {sortedReuseRows.length > 0 ? sortedReuseRows.map(r => { const isPlain = r.secret_kind === 'plaintext'; const moreDeckies = Math.max(0, r.deckies.length - 3); const moreServices = Math.max(0, r.services.length - 3); diff --git a/decnet_web/src/components/Dashboard.css b/decnet_web/src/components/Dashboard.css index fdb5f015..12896b6a 100644 --- a/decnet_web/src/components/Dashboard.css +++ b/decnet_web/src/components/Dashboard.css @@ -264,10 +264,15 @@ min-height: 0; } +.dash-grid > .logs-section { + max-height: 480px; + overflow: hidden; +} + .dash-grid > .logs-section .logs-table-container { flex: 1; - max-height: 420px; overflow-y: auto; + min-height: 0; } .dash-side { @@ -281,11 +286,13 @@ flex: 1; min-height: 0; overflow: hidden; + max-height: 300px; } .dash-side > .logs-section .panel-body { - max-height: 260px; overflow-y: auto; + flex: 1; + min-height: 0; } /* Attacker/siege rows */ diff --git a/decnet_web/src/components/DeckyFleet.tsx b/decnet_web/src/components/DeckyFleet.tsx index f9d168d0..081e8b2b 100644 --- a/decnet_web/src/components/DeckyFleet.tsx +++ b/decnet_web/src/components/DeckyFleet.tsx @@ -1553,7 +1553,10 @@ const DeckyFleet: React.FC = ({ searchQuery = '' }) => {
-

DECOY FLEET

+
+ +

DECOY FLEET

+
{deckies.length} DECKIES DEPLOYED · {counts.active + counts.hot} ACTIVE · {counts.hot} UNDER SIEGE {deployMode && ( diff --git a/decnet_web/src/components/LiveLogs.tsx b/decnet_web/src/components/LiveLogs.tsx index 57f10544..18614d26 100644 --- a/decnet_web/src/components/LiveLogs.tsx +++ b/decnet_web/src/components/LiveLogs.tsx @@ -182,7 +182,10 @@ const LiveLogs: React.FC = () => {
-

LOGS

+
+ +

LOGS

+
{filteredLogs.length.toLocaleString()} SHOWN · {totalLogs.toLocaleString()} MATCHES · STREAM {streaming ? 'LIVE' : 'PAUSED'} diff --git a/decnet_web/src/components/PersonaGeneration.tsx b/decnet_web/src/components/PersonaGeneration.tsx index cc3b6a06..59e5a371 100644 --- a/decnet_web/src/components/PersonaGeneration.tsx +++ b/decnet_web/src/components/PersonaGeneration.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; import { - Mail, Plus, Pencil, Trash2, Check, AlertTriangle, Upload, Download, + Mail, Plus, Pencil, Trash2, Check, AlertTriangle, Upload, Download, Sparkles, } from '../icons'; import api from '../utils/api'; import { useToast } from './Toasts/useToast'; @@ -736,7 +736,10 @@ const PersonaGeneration: React.FC = ({ topologyId }) =>
-

{isTopology ? 'TOPOLOGY PERSONAS' : 'PERSONA GENERATION'}

+
+ +

{isTopology ? 'TOPOLOGY PERSONAS' : 'PERSONA GENERATION'}

+
{personas.length} PERSONA{personas.length === 1 ? '' : 'S'} · {llmHeavyCount} LLM-HEAVY {isTopology diff --git a/decnet_web/src/components/RealismConfig/RealismConfig.tsx b/decnet_web/src/components/RealismConfig/RealismConfig.tsx index 918796b5..25a9d946 100644 --- a/decnet_web/src/components/RealismConfig/RealismConfig.tsx +++ b/decnet_web/src/components/RealismConfig/RealismConfig.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import api from '../../utils/api'; import { useToast } from '../Toasts/useToast'; -import { Save, RotateCcw, AlertTriangle } from '../../icons'; +import { Save, RotateCcw, AlertTriangle, Sliders } from '../../icons'; import { contentClassLabel, isCanaryClass } from '../../realism/labels'; // Reuse the DeckyFleet shell (page-header / btn / fleet-* / dim / mono) and // the persona-page tweaks (info-banner, .input) so the realism config panel @@ -168,7 +168,10 @@ const RealismConfig: React.FC = () => {
-

REALISM CONFIG

+
+ +

REALISM CONFIG

+
USER {totals.user} · SYSTEM {totals.system} · CANARY {totals.canary} · {' '}CANARY PROB {(config.canary_probability * 100).toFixed(1)}% diff --git a/decnet_web/src/components/SyntheticFiles/SyntheticFiles.tsx b/decnet_web/src/components/SyntheticFiles/SyntheticFiles.tsx index 277c0f32..16b40485 100644 --- a/decnet_web/src/components/SyntheticFiles/SyntheticFiles.tsx +++ b/decnet_web/src/components/SyntheticFiles/SyntheticFiles.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import api from '../../utils/api'; import { useEscapeKey } from '../../hooks/useEscapeKey'; import { useFocusTrap } from '../../hooks/useFocusTrap'; -import { X } from '../../icons'; +import { X, FileText } from '../../icons'; import { contentClassLabel, isCanaryClass } from '../../realism/labels'; // Reuse the DeckyFleet shell + the persona-page tweaks so this page reads // the same as the rest of the realism nav group. @@ -241,7 +241,10 @@ const SyntheticFiles: React.FC = () => {
-

SYNTHETIC FILES

+
+ +

SYNTHETIC FILES

+
{total} TOTAL · PAGE {page + 1} / {totalPages} {filtersActive ? ' · FILTERED' : ''} diff --git a/decnet_web/src/components/TopologyList/TopologyList.tsx b/decnet_web/src/components/TopologyList/TopologyList.tsx index 23ae45c9..e252039a 100644 --- a/decnet_web/src/components/TopologyList/TopologyList.tsx +++ b/decnet_web/src/components/TopologyList/TopologyList.tsx @@ -154,7 +154,10 @@ const TopologyList: React.FC = () => {
-

TOPOLOGIES

+
+ +

TOPOLOGIES

+
{loading ? 'LOADING…' : `${rows.length} ${rows.length === 1 ? 'TOPOLOGY' : 'TOPOLOGIES'}`} {err && · {err}} diff --git a/decnet_web/src/components/Webhooks.tsx b/decnet_web/src/components/Webhooks.tsx index 417d59cc..2f648e7f 100644 --- a/decnet_web/src/components/Webhooks.tsx +++ b/decnet_web/src/components/Webhooks.tsx @@ -292,7 +292,10 @@ const Webhooks: React.FC = () => {
-

WEBHOOKS

+
+ +

WEBHOOKS

+
{webhooks.length} CONFIGURED · {enabledCount} ENABLED {trippedCount > 0 && ` · ${trippedCount} TRIPPED`}
LAST SEENPRINCIPALKINDTARGETSATTEMPTS DECKIES SERVICES