Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Operators can remove machines from Crucible and dashboard rosters with honest messaging that deletion is registry-only; bulk select, oath ledger entries, and Go/Vitest coverage included.
146 lines
5.5 KiB
TypeScript
146 lines
5.5 KiB
TypeScript
import type { FleetFilterState } from '../../help/fleetFilters';
|
|
import { collectFleetSubnets, collectFleetTags } from '../../help/fleetFilters';
|
|
import type { Agent } from '../../types';
|
|
import { HelpTip } from '../HelpTip';
|
|
import './FleetToolbar.css';
|
|
|
|
interface Props {
|
|
agents: Agent[];
|
|
filters: FleetFilterState;
|
|
onChange: (next: FleetFilterState) => void;
|
|
selectedCount: number;
|
|
selectedAgents?: Agent[];
|
|
onBulkAction: (action: string) => void;
|
|
onSelectAllFiltered?: () => void;
|
|
onCreateGroup?: () => void;
|
|
filteredCount?: number;
|
|
bulkBusy: boolean;
|
|
}
|
|
|
|
export default function FleetToolbar({
|
|
agents,
|
|
filters,
|
|
onChange,
|
|
selectedCount,
|
|
selectedAgents = [],
|
|
onBulkAction,
|
|
onSelectAllFiltered,
|
|
onCreateGroup,
|
|
filteredCount,
|
|
bulkBusy,
|
|
}: Props) {
|
|
const tags = collectFleetTags(agents);
|
|
const subnets = collectFleetSubnets(agents);
|
|
|
|
return (
|
|
<div className="fleet-toolbar card">
|
|
<div className="fleet-toolbar-filters">
|
|
<HelpTip field="fl_filter_chips" />
|
|
<input
|
|
type="search"
|
|
className="input fleet-filter-search"
|
|
placeholder="Search name, IP, notes, tags…"
|
|
value={filters.search}
|
|
onChange={(e) => onChange({ ...filters, search: e.target.value })}
|
|
/>
|
|
<select
|
|
className="select fleet-filter-select"
|
|
value={filters.tag}
|
|
onChange={(e) => onChange({ ...filters, tag: e.target.value })}
|
|
title="Filter by tag"
|
|
>
|
|
<option value="">All tags</option>
|
|
{tags.map((t) => (
|
|
<option key={t} value={t}>{t}</option>
|
|
))}
|
|
</select>
|
|
<select
|
|
className="select fleet-filter-select"
|
|
value={filters.subnet}
|
|
onChange={(e) => onChange({ ...filters, subnet: e.target.value })}
|
|
title="Filter by subnet"
|
|
>
|
|
<option value="">All subnets</option>
|
|
{subnets.map((s) => (
|
|
<option key={s} value={s}>{s}</option>
|
|
))}
|
|
</select>
|
|
<select
|
|
className="select fleet-filter-select"
|
|
value={String(filters.hashrateMin)}
|
|
onChange={(e) => onChange({ ...filters, hashrateMin: Number(e.target.value) })}
|
|
title="Minimum 15m hashrate"
|
|
>
|
|
<option value="0">Any hashrate</option>
|
|
<option value="1000">≥ 1 KH/s</option>
|
|
<option value="10000">≥ 10 KH/s</option>
|
|
<option value="100000">≥ 100 KH/s</option>
|
|
<option value="1000000">≥ 1 MH/s</option>
|
|
</select>
|
|
<label className="checkbox-label fleet-filter-attn">
|
|
<input
|
|
type="checkbox"
|
|
className="checkbox"
|
|
checked={filters.needsAttention}
|
|
onChange={(e) => onChange({ ...filters, needsAttention: e.target.checked })}
|
|
/>
|
|
<span>Needs attention</span>
|
|
</label>
|
|
</div>
|
|
|
|
{onSelectAllFiltered && (filteredCount ?? 0) > 0 && selectedCount === 0 && (
|
|
<div className="fleet-bulk-bar">
|
|
<button type="button" className="btn btn-outline btn-sm" onClick={onSelectAllFiltered}>
|
|
Select all filtered ({filteredCount})
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{selectedCount > 0 && (
|
|
<div className="fleet-bulk-bar">
|
|
<span className="font-tech">{selectedCount} selected</span>
|
|
<HelpTip field="fl_bulk_actions" />
|
|
{onCreateGroup && (
|
|
<button
|
|
type="button"
|
|
className="btn btn-primary btn-sm"
|
|
disabled={bulkBusy}
|
|
onClick={onCreateGroup}
|
|
>
|
|
Create group…
|
|
</button>
|
|
)}
|
|
{selectedCount === 1 && (
|
|
<button
|
|
type="button"
|
|
className="btn btn-outline btn-sm"
|
|
disabled={bulkBusy || !selectedAgents.some((a) => a.status === 'online')}
|
|
onClick={() => onBulkAction('screenshot')}
|
|
title="Capture desktop on the selected machine and download JPEG here"
|
|
>
|
|
Screenshot
|
|
</button>
|
|
)}
|
|
<button type="button" className="btn btn-outline btn-sm" disabled={bulkBusy || !selectedAgents.some((a) => a.status === 'online')} onClick={() => onBulkAction('pause')} title="Fleet health: power down hashing">Pause</button>
|
|
<button type="button" className="btn btn-outline btn-sm" disabled={bulkBusy || !selectedAgents.some((a) => a.status === 'online')} onClick={() => onBulkAction('resume')} title="Fleet health: restore hashing">Resume</button>
|
|
<button type="button" className="btn btn-outline btn-sm" disabled={bulkBusy || !selectedAgents.some((a) => a.status === 'online')} onClick={() => onBulkAction('stop')}>Stop</button>
|
|
<button type="button" className="btn btn-outline btn-sm" disabled={bulkBusy || !selectedAgents.some((a) => a.status === 'online')} onClick={() => onBulkAction('restart_idle')}>Restart idle</button>
|
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }}>
|
|
<button
|
|
type="button"
|
|
className="btn btn-sm"
|
|
disabled={bulkBusy}
|
|
style={{ background: 'rgba(255,40,40,0.15)', border: '1px solid #ff4444', color: '#ff6666' }}
|
|
onClick={() => onBulkAction('delete')}
|
|
title="Remove selected machines from fleet registry — does not uninstall on host"
|
|
>
|
|
Remove from fleet
|
|
</button>
|
|
<HelpTip field="fl_delete_roster" />
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|