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:
97
server/web/src/components/Fleet/AgentListItem.tsx
Normal file
97
server/web/src/components/Fleet/AgentListItem.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user