fix(ui): follow-up polish — icons, dashboard bar, filter redesign, bounty/creds sort

- Dashboard: fix invisible bar at bottom of LIVE FEED by constraining
  max-height on the section instead of the inner container; same fix
  for side panels
- Page icons: add violet-accent icon beside h1 on all 9 missing pages
  (CanaryTokens, RealismConfig, SyntheticFiles, PersonaGeneration,
  Attackers, Webhooks, LiveLogs, Topologies, DecoyFleet)
- Attackers filter chips: replace ad-hoc chip buttons with seg-group
  tabs (ALL / ACTIVE N / PASSIVE N / INACTIVE N) matching Credential
  Vault style; country chips use same seg-group treatment
- Credential Vault: add sortable headers to REUSE tab (LAST SEEN,
  PRINCIPAL, KIND, TARGETS, ATTEMPTS); reuses same SortTh pattern
- Bounty: remove CREDENTIALS and PAYLOADS tabs; keep ALL, ARTIFACTS,
  FINGERPRINTS; add EMAIL (artifact subtype, filtered client-side)
This commit is contained in:
2026-04-30 00:20:25 -04:00
parent 9adee07d21
commit fbc9877ef2
13 changed files with 152 additions and 69 deletions

View File

@@ -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 }) => (
<th
style={{ cursor: 'pointer', userSelect: 'none', whiteSpace: 'nowrap' }}
@@ -382,18 +399,18 @@ const Credentials: React.FC = () => {
<table className="logs-table">
<thead>
<tr>
<th>LAST SEEN</th>
<th>PRINCIPAL</th>
<th>KIND</th>
<th>TARGETS</th>
<th>ATTEMPTS</th>
<SortTh col="seen">LAST SEEN</SortTh>
<SortTh col="principal">PRINCIPAL</SortTh>
<SortTh col="kind">KIND</SortTh>
<SortTh col="targets">TARGETS</SortTh>
<SortTh col="attempts">ATTEMPTS</SortTh>
<th>DECKIES</th>
<th>SERVICES</th>
<th></th>
</tr>
</thead>
<tbody>
{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);