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:
@@ -134,7 +134,10 @@ const Attackers: React.FC = () => {
|
||||
<div className="attackers-root">
|
||||
<div className="page-header">
|
||||
<div className="page-title-group">
|
||||
<h1>ATTACKERS</h1>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Users size={22} className="violet-accent" />
|
||||
<h1>ATTACKERS</h1>
|
||||
</div>
|
||||
<span className="page-sub">
|
||||
{total.toLocaleString()} UNIQUE SOURCES · {activityCounts.active} ACTIVE · {activityCounts.passive} PASSIVE · {activityCounts.inactive} INACTIVE
|
||||
</span>
|
||||
@@ -160,39 +163,35 @@ const Attackers: React.FC = () => {
|
||||
</form>
|
||||
|
||||
<div className="ak-filter-row">
|
||||
{(['active', 'passive', 'inactive'] as ActivityTier[]).map(tier => (
|
||||
<button
|
||||
key={tier}
|
||||
type="button"
|
||||
className={`chip ${activityFilter === tier ? (tier === 'active' ? 'alert-chip' : tier === 'passive' ? 'violet' : 'matrix') : 'dim-chip'}`}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => setActivity(tier)}
|
||||
>
|
||||
<span className={`dot status-dot ${tier === 'active' ? 'hot' : tier === 'passive' ? 'warn' : ''}`} style={{ marginRight: 4 }} />
|
||||
{tier.toUpperCase()} {activityCounts[tier] > 0 ? activityCounts[tier] : ''}
|
||||
</button>
|
||||
))}
|
||||
{countries.length > 0 && <span className="dim" style={{ fontSize: '0.65rem', letterSpacing: 1, opacity: 0.4, alignSelf: 'center' }}>|</span>}
|
||||
{countries.map(cc => (
|
||||
<button
|
||||
key={cc}
|
||||
type="button"
|
||||
className={`chip ${countryFilter === cc ? 'violet' : 'dim-chip'}`}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => setCountry(cc)}
|
||||
>
|
||||
{cc}
|
||||
</button>
|
||||
))}
|
||||
{(activityFilter || countryFilter || serviceFilter) && (
|
||||
<button
|
||||
type="button"
|
||||
className="chip dim-chip"
|
||||
style={{ cursor: 'pointer', opacity: 0.6 }}
|
||||
onClick={() => setSearchParams(_params({ activity: '', country: '', service: '' }))}
|
||||
>
|
||||
CLEAR ×
|
||||
<div className="seg-group" role="tablist">
|
||||
<button type="button" className={!activityFilter ? 'active' : ''} onClick={() => setActivity('')}>
|
||||
ALL
|
||||
</button>
|
||||
{(['active', 'passive', 'inactive'] as ActivityTier[]).map(tier => (
|
||||
<button
|
||||
key={tier}
|
||||
type="button"
|
||||
className={activityFilter === tier ? 'active' : ''}
|
||||
onClick={() => setActivity(tier)}
|
||||
>
|
||||
<span className={`ak-dot ak-dot-${tier}`} />
|
||||
{tier.toUpperCase()}{activityCounts[tier] > 0 ? ` ${activityCounts[tier]}` : ''}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{countries.length > 0 && (
|
||||
<div className="seg-group" role="tablist">
|
||||
{countries.map(cc => (
|
||||
<button
|
||||
key={cc}
|
||||
type="button"
|
||||
className={countryFilter === cc ? 'active' : ''}
|
||||
onClick={() => setCountry(cc)}
|
||||
>
|
||||
{cc}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user