Add forge pipeline polish, simple forge UX, and fleet management upgrades.
Fusion copies prep icon and version info via go-winres; optional Garble obfuscation, Authenticode signing, and dry-run size estimates. Forge Simple mode with smart defaults; fleet roster gets compact expandable cards, filters, bulk commands, per-agent notes/tags, and typed WebSocket payloads.
This commit is contained in:
92
server/web/src/components/Fleet/FleetToolbar.tsx
Normal file
92
server/web/src/components/Fleet/FleetToolbar.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { FleetFilterState } from '../../help/fleetFilters';
|
||||
import { collectFleetSubnets, collectFleetTags } from '../../help/fleetFilters';
|
||||
import type { Agent } from '../../types';
|
||||
import './FleetToolbar.css';
|
||||
|
||||
interface Props {
|
||||
agents: Agent[];
|
||||
filters: FleetFilterState;
|
||||
onChange: (next: FleetFilterState) => void;
|
||||
selectedCount: number;
|
||||
onBulkAction: (action: string) => void;
|
||||
bulkBusy: boolean;
|
||||
}
|
||||
|
||||
export default function FleetToolbar({
|
||||
agents,
|
||||
filters,
|
||||
onChange,
|
||||
selectedCount,
|
||||
onBulkAction,
|
||||
bulkBusy,
|
||||
}: Props) {
|
||||
const tags = collectFleetTags(agents);
|
||||
const subnets = collectFleetSubnets(agents);
|
||||
|
||||
return (
|
||||
<div className="fleet-toolbar card">
|
||||
<div className="fleet-toolbar-filters">
|
||||
<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>
|
||||
|
||||
{selectedCount > 0 && (
|
||||
<div className="fleet-bulk-bar">
|
||||
<span className="font-tech">{selectedCount} selected</span>
|
||||
<button type="button" className="btn btn-outline btn-sm" disabled={bulkBusy} onClick={() => onBulkAction('pause')}>Pause</button>
|
||||
<button type="button" className="btn btn-outline btn-sm" disabled={bulkBusy} onClick={() => onBulkAction('resume')}>Resume</button>
|
||||
<button type="button" className="btn btn-outline btn-sm" disabled={bulkBusy} onClick={() => onBulkAction('stop')}>Stop</button>
|
||||
<button type="button" className="btn btn-outline btn-sm" disabled={bulkBusy} onClick={() => onBulkAction('restart_idle')}>Restart idle miners</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user