Add LOTL Timeline page for live tier progression visualization.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Operators get a dedicated Onion view with 14-tier stepper, fleet progress chips, and AI decision overlay when fleet AI control is enabled.
This commit is contained in:
AetherForge
2026-06-07 02:13:15 -07:00
parent f27cec887a
commit 34afa28f81
14 changed files with 939 additions and 2 deletions

View File

@@ -0,0 +1,58 @@
/**
* @vitest-environment happy-dom
*/
import { afterEach, describe, expect, it } from 'vitest';
import { cleanup, render, screen, fireEvent } from '@testing-library/react';
import LotlTierTimeline from './LotlTierTimeline';
import { buildLotlTimelineModel, resolveLotlTierOrder } from '../../help/lotlTimeline';
import { mockAgent } from '../../test/fixtures';
describe('LotlTierTimeline', () => {
afterEach(() => cleanup());
it('renders 14 tiers from fixture attempts', () => {
const order = resolveLotlTierOrder();
expect(order).toHaveLength(14);
const agent = mockAgent({
status: 'online',
lotl_tier: 'wsl',
lotl_attempts: [
{ tier: 'vuln_recon', ok: true, duration_ms: 800, phase: 'recon' },
{ tier: 'docker', ok: false, error: 'no daemon', duration_ms: 1200, phase: 'deploy' },
{ tier: 'wsl', ok: false, error: 'distro missing', duration_ms: 900, phase: 'deploy' },
],
});
const model = buildLotlTimelineModel(agent, order, agent.lotl_attempts ?? []);
expect(model.tiers).toHaveLength(14);
expect(model.succeeded).toBe(1);
expect(model.tiers[0].state).toBe('success');
expect(model.tiers[1].state).toBe('failed');
expect(model.tiers[2].state).toBe('trying');
render(<LotlTierTimeline model={model} agentName="Test Miner" />);
expect(screen.getByText('ONION TIER CHAIN')).toBeInTheDocument();
expect(screen.getByText('1/14 tiers succeeded')).toBeInTheDocument();
expect(screen.getAllByRole('listitem')).toHaveLength(14);
expect(screen.getByText('Vuln Recon')).toBeInTheDocument();
expect(screen.getByText('GPO')).toBeInTheDocument();
});
it('expands attempt detail on tier click', () => {
const order = resolveLotlTierOrder();
const agent = mockAgent({
lotl_attempts: [
{ tier: 'docker', ok: false, error: 'AV blocked', duration_ms: 500, phase: 'deploy' },
],
});
const model = buildLotlTimelineModel(agent, order, agent.lotl_attempts ?? []);
render(<LotlTierTimeline model={model} agentName="Node A" />);
fireEvent.click(screen.getByText('Docker'));
expect(screen.getByText('AV blocked')).toBeInTheDocument();
expect(screen.getByText('deploy')).toBeInTheDocument();
expect(screen.getByText('500ms')).toBeInTheDocument();
});
});