merge testing->tomerge/main #7
@@ -216,6 +216,7 @@ const AttackerDetail: React.FC = () => {
|
|||||||
const [attacker, setAttacker] = useState<AttackerData | null>(null);
|
const [attacker, setAttacker] = useState<AttackerData | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [serviceFilter, setServiceFilter] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAttacker = async () => {
|
const fetchAttacker = async () => {
|
||||||
@@ -330,17 +331,27 @@ const AttackerDetail: React.FC = () => {
|
|||||||
<h2>SERVICES TARGETED</h2>
|
<h2>SERVICES TARGETED</h2>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: '16px', display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
<div style={{ padding: '16px', display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||||
{attacker.services.length > 0 ? attacker.services.map((svc) => (
|
{attacker.services.length > 0 ? attacker.services.map((svc) => {
|
||||||
<span
|
const isActive = serviceFilter === svc;
|
||||||
key={svc}
|
return (
|
||||||
className="service-badge"
|
<span
|
||||||
style={{ fontSize: '0.85rem', padding: '4px 12px', cursor: 'pointer' }}
|
key={svc}
|
||||||
onClick={() => navigate(`/attackers?service=${encodeURIComponent(svc)}`)}
|
className="service-badge"
|
||||||
title={`Filter attackers by ${svc.toUpperCase()}`}
|
style={{
|
||||||
>
|
fontSize: '0.85rem', padding: '4px 12px', cursor: 'pointer',
|
||||||
{svc.toUpperCase()}
|
...(isActive ? {
|
||||||
</span>
|
backgroundColor: 'var(--text-color)',
|
||||||
)) : (
|
color: 'var(--bg-color)',
|
||||||
|
borderColor: 'var(--text-color)',
|
||||||
|
} : {}),
|
||||||
|
}}
|
||||||
|
onClick={() => setServiceFilter(isActive ? null : svc)}
|
||||||
|
title={isActive ? 'Clear filter' : `Filter by ${svc.toUpperCase()}`}
|
||||||
|
>
|
||||||
|
{svc.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}) : (
|
||||||
<span className="dim">No services recorded</span>
|
<span className="dim">No services recorded</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -371,59 +382,76 @@ const AttackerDetail: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Commands */}
|
{/* Commands */}
|
||||||
<div className="logs-section">
|
{(() => {
|
||||||
<div className="section-header">
|
const filteredCmds = serviceFilter
|
||||||
<h2>COMMANDS ({attacker.commands.length})</h2>
|
? attacker.commands.filter((cmd) => cmd.service === serviceFilter)
|
||||||
</div>
|
: attacker.commands;
|
||||||
{attacker.commands.length > 0 ? (
|
return (
|
||||||
<div className="logs-table-container">
|
<div className="logs-section">
|
||||||
<table className="logs-table">
|
<div className="section-header">
|
||||||
<thead>
|
<h2>COMMANDS ({filteredCmds.length}{serviceFilter ? ` / ${attacker.commands.length}` : ''})</h2>
|
||||||
<tr>
|
</div>
|
||||||
<th>TIMESTAMP</th>
|
{filteredCmds.length > 0 ? (
|
||||||
<th>SERVICE</th>
|
<div className="logs-table-container">
|
||||||
<th>DECKY</th>
|
<table className="logs-table">
|
||||||
<th>COMMAND</th>
|
<thead>
|
||||||
</tr>
|
<tr>
|
||||||
</thead>
|
<th>TIMESTAMP</th>
|
||||||
<tbody>
|
<th>SERVICE</th>
|
||||||
{attacker.commands.map((cmd, i) => (
|
<th>DECKY</th>
|
||||||
<tr key={i}>
|
<th>COMMAND</th>
|
||||||
<td className="dim" style={{ fontSize: '0.75rem', whiteSpace: 'nowrap' }}>
|
</tr>
|
||||||
{cmd.timestamp ? new Date(cmd.timestamp).toLocaleString() : '-'}
|
</thead>
|
||||||
</td>
|
<tbody>
|
||||||
<td>{cmd.service}</td>
|
{filteredCmds.map((cmd, i) => (
|
||||||
<td className="violet-accent">{cmd.decky}</td>
|
<tr key={i}>
|
||||||
<td className="matrix-text" style={{ fontFamily: 'monospace' }}>{cmd.command}</td>
|
<td className="dim" style={{ fontSize: '0.75rem', whiteSpace: 'nowrap' }}>
|
||||||
</tr>
|
{cmd.timestamp ? new Date(cmd.timestamp).toLocaleString() : '-'}
|
||||||
))}
|
</td>
|
||||||
</tbody>
|
<td>{cmd.service}</td>
|
||||||
</table>
|
<td className="violet-accent">{cmd.decky}</td>
|
||||||
|
<td className="matrix-text" style={{ fontFamily: 'monospace' }}>{cmd.command}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ padding: '24px', textAlign: 'center', opacity: 0.5 }}>
|
||||||
|
{serviceFilter ? `NO ${serviceFilter.toUpperCase()} COMMANDS CAPTURED` : 'NO COMMANDS CAPTURED'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
);
|
||||||
<div style={{ padding: '24px', textAlign: 'center', opacity: 0.5 }}>
|
})()}
|
||||||
NO COMMANDS CAPTURED
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Fingerprints */}
|
{/* Fingerprints */}
|
||||||
<div className="logs-section">
|
{(() => {
|
||||||
<div className="section-header">
|
const filteredFps = serviceFilter
|
||||||
<h2>FINGERPRINTS ({attacker.fingerprints.length})</h2>
|
? attacker.fingerprints.filter((fp) => {
|
||||||
</div>
|
const p = getPayload(fp);
|
||||||
{attacker.fingerprints.length > 0 ? (
|
return p.service === serviceFilter;
|
||||||
<div style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
})
|
||||||
{attacker.fingerprints.map((fp, i) => (
|
: attacker.fingerprints;
|
||||||
<FingerprintCard key={i} bounty={fp} />
|
return (
|
||||||
))}
|
<div className="logs-section">
|
||||||
|
<div className="section-header">
|
||||||
|
<h2>FINGERPRINTS ({filteredFps.length}{serviceFilter ? ` / ${attacker.fingerprints.length}` : ''})</h2>
|
||||||
|
</div>
|
||||||
|
{filteredFps.length > 0 ? (
|
||||||
|
<div style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
|
{filteredFps.map((fp, i) => (
|
||||||
|
<FingerprintCard key={i} bounty={fp} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ padding: '24px', textAlign: 'center', opacity: 0.5 }}>
|
||||||
|
{serviceFilter ? `NO ${serviceFilter.toUpperCase()} FINGERPRINTS CAPTURED` : 'NO FINGERPRINTS CAPTURED'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
);
|
||||||
<div style={{ padding: '24px', textAlign: 'center', opacity: 0.5 }}>
|
})()}
|
||||||
NO FINGERPRINTS CAPTURED
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* UUID footer */}
|
{/* UUID footer */}
|
||||||
<div style={{ textAlign: 'right', fontSize: '0.65rem', opacity: 0.3, marginTop: '8px' }}>
|
<div style={{ textAlign: 'right', fontSize: '0.65rem', opacity: 0.3, marginTop: '8px' }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user