refactor: Firmware Agent-Liste kompakt und nach Kunde gruppiert

This commit is contained in:
cynfo3000 2026-03-10 01:02:34 +01:00
parent 16a7cd3f87
commit 1923b28fa2

View File

@ -16,6 +16,7 @@ export default function Firmware() {
const [firmwareData, setFirmwareData] = useState(null)
const [installerData, setInstallerData] = useState(null)
const [agents, setAgents] = useState([])
const [customers, setCustomers] = useState([])
const [loading, setLoading] = useState(true)
const [uploading, setUploading] = useState(false)
const [installerUploading, setInstallerUploading] = useState(false)
@ -30,14 +31,16 @@ export default function Firmware() {
const load = async () => {
try {
const [fw, ag, inst] = await Promise.all([
const [fw, ag, inst, cu] = await Promise.all([
api.getFirmwareInfo().catch(() => null),
api.getAgents().catch(() => []),
api.getInstallerInfo().catch(() => null),
api.getCustomers().catch(() => []),
])
setFirmwareData(fw)
setAgents(Array.isArray(ag) ? ag : [])
setInstallerData(inst)
setCustomers(Array.isArray(cu) ? cu : [])
} finally {
setLoading(false)
}
@ -299,14 +302,16 @@ export default function Firmware() {
</div>
</div>
{/* Agent List */}
<div className="bg-gray-800/50 rounded-lg border border-gray-700/50 p-5">
<div className="flex items-center justify-between mb-4">
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider">Agents</h2>
{/* Agent List — nach Kunde gruppiert */}
<div className="bg-gray-900 rounded-lg border border-gray-800 overflow-hidden">
<div className="px-4 py-2.5 border-b border-gray-800 flex items-center justify-between">
<span className="text-xs font-semibold text-gray-400 uppercase tracking-wider">
Agents ({agents.length})
</span>
{hasAnyFirmware && (
<button
onClick={requestAll}
className="flex items-center gap-2 bg-orange-600/20 hover:bg-orange-600/30 text-orange-400 px-3 py-1.5 rounded text-xs transition-colors border border-orange-600/30"
className="flex items-center gap-1.5 text-orange-400 hover:text-orange-300 text-xs transition-colors"
>
<ArrowUpCircle className="w-3.5 h-3.5" />
Alle updaten
@ -314,66 +319,76 @@ export default function Firmware() {
)}
</div>
<div className="space-y-2">
{agents.map((agent) => {
const outdated = needsUpdate(agent)
const pending = agent.update_requested
const agentPlatform = agent.platform || 'freebsd'
const fw = getFwForAgent(agent)
const platformLabel = agentPlatform === 'linux' ? 'Linux' : 'FreeBSD'
const platformColor = agentPlatform === 'linux' ? 'bg-blue-900/50 text-blue-400' : 'bg-orange-900/50 text-orange-400'
{(() => {
// Gruppieren nach customer_id
const groups = {}
agents.forEach(a => {
const gid = a.customer_id ?? '__none__'
if (!groups[gid]) groups[gid] = []
groups[gid].push(a)
})
const sorted = Object.entries(groups).sort(([a], [b]) => {
if (a === '__none__') return 1
if (b === '__none__') return -1
const ca = customers.find(c => c.id === parseInt(a))
const cb = customers.find(c => c.id === parseInt(b))
return (ca?.number || '').localeCompare(cb?.number || '')
})
return sorted.map(([gid, gAgents]) => {
const customer = gid !== '__none__' ? customers.find(c => c.id === parseInt(gid)) : null
return (
<div key={agent.id} className="flex items-center justify-between bg-gray-900/50 rounded-lg px-4 py-3 border border-gray-700/30">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className={`w-2 h-2 rounded-full shrink-0 ${
agent.status === 'online' ? 'bg-emerald-500' :
agent.status === 'stale' ? 'bg-yellow-500' : 'bg-gray-600'
}`} />
<span className="text-white text-sm font-medium truncate">{agent.name}</span>
<span className={`text-[10px] px-1.5 py-0.5 rounded ${platformColor}`}>{platformLabel}</span>
</div>
<div className="flex items-center gap-3 mt-1">
<span className="text-xs text-gray-500">
Version: <span className={outdated ? 'text-yellow-400' : 'text-gray-400'}>{agent.agent_version || '--'}</span>
{fw && <span className="text-gray-600 ml-1">(aktuell: v{fw.version})</span>}
{!fw && <span className="text-gray-600 ml-1">(keine Firmware fuer {platformLabel})</span>}
</span>
{pending && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-orange-500/20 text-orange-400 border border-orange-500/30">
Update ausstehend
</span>
)}
{!outdated && fw && agent.agent_version && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-400 border border-emerald-500/30">
Aktuell
</span>
)}
</div>
</div>
<div className="flex items-center gap-2 shrink-0 ml-3">
{pending ? (
<button
onClick={() => cancelUpdate(agent.id, agent.name)}
className="flex items-center gap-1.5 bg-gray-700 hover:bg-gray-600 text-gray-300 px-3 py-1.5 rounded text-xs transition-colors"
>
<X className="w-3.5 h-3.5" />
Abbrechen
</button>
) : outdated ? (
<button
onClick={() => requestUpdate(agent.id, agent.name)}
className="flex items-center gap-1.5 bg-orange-600 hover:bg-orange-500 text-white px-3 py-1.5 rounded text-xs transition-colors"
>
<ArrowUpCircle className="w-3.5 h-3.5" />
Update
</button>
) : null}
<div key={gid}>
<div className="px-4 py-1 bg-gray-800/40 border-b border-gray-800/60">
<span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wide">
{customer ? `${customer.number}${customer.name}` : 'Kein Kunde'}
</span>
<span className="text-[10px] text-gray-700 ml-1.5">({gAgents.length})</span>
</div>
{gAgents.map(agent => {
const outdated = needsUpdate(agent)
const pending = agent.update_requested
const agentPlatform = agent.platform || 'freebsd'
const fw = getFwForAgent(agent)
const isLinux = agentPlatform === 'linux'
return (
<div key={agent.id} className="px-4 py-1.5 flex items-center gap-2.5 hover:bg-gray-800/30 border-b border-gray-800/30 last:border-0">
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${
agent.status === 'online' ? 'bg-green-400' :
agent.status === 'stale' ? 'bg-yellow-400' : 'bg-gray-600'
}`} />
<span className="text-xs text-gray-300 flex-1 min-w-0 truncate">{agent.name}</span>
<span className={`text-[10px] px-1 py-0.5 rounded flex-shrink-0 ${
isLinux ? 'text-blue-400 bg-blue-900/30' : 'text-orange-400 bg-orange-900/30'
}`}>
{isLinux ? 'Linux' : 'BSD'}
</span>
<span className={`text-[10px] font-mono flex-shrink-0 ${outdated ? 'text-yellow-400' : 'text-gray-600'}`}>
v{agent.agent_version || '—'}
{fw && outdated && <span className="text-gray-600"> v{fw.version}</span>}
</span>
{pending && (
<span className="text-[10px] text-orange-400 flex-shrink-0">ausstehend</span>
)}
{pending ? (
<button onClick={() => cancelUpdate(agent.id, agent.name)}
className="text-gray-600 hover:text-red-400 transition-colors flex-shrink-0" title="Abbrechen">
<X className="w-3.5 h-3.5" />
</button>
) : outdated ? (
<button onClick={() => requestUpdate(agent.id, agent.name)}
className="text-orange-400 hover:text-orange-300 transition-colors flex-shrink-0" title="Update anfordern">
<ArrowUpCircle className="w-3.5 h-3.5" />
</button>
) : fw ? (
<CheckCircle className="w-3.5 h-3.5 text-green-700 flex-shrink-0" />
) : null}
</div>
)
})}
</div>
)
})}
</div>
})
})()}
</div>
</div>
)