feat(infra): add HostGrid 2x2 compact host grid component

This commit is contained in:
OG T
2026-04-01 19:50:27 +08:00
parent 354bf7a6f2
commit 91b42b4bb9

View File

@@ -0,0 +1,111 @@
'use client'
/**
* HostGrid - 2×2 主機緊湊 Grid
* ================================
* 每格hostname + IP + CPU/RAM + 3條關鍵服務 mini 列
* hover 橘紅邊框,異常指標用警示色
* 統帥鐵律:無數據顯示 "--",禁止假數據
*/
export interface HostService {
name: string
healthy: boolean
}
export interface HostInfo {
hostname: string
ip: string
cpuPct: number | null
ramPct: number | null
services: HostService[]
}
export interface HostGridProps {
hosts: HostInfo[]
}
function MetricBar({ value, warn = 80, critical = 90 }: { value: number | null; warn?: number; critical?: number }) {
if (value === null) return <span style={{ color: '#b0ad9f', fontSize: 9 }}>--</span>
const color = value >= critical ? '#cc2200' : value >= warn ? '#F59E0B' : '#22C55E'
return (
<span style={{ color, fontSize: 9, fontWeight: 600 }}>{value}%</span>
)
}
export function HostGrid({ hosts }: HostGridProps) {
// 補足至 4 格(不足時顯示空格)
const slots = [...hosts, ...Array(Math.max(0, 4 - hosts.length)).fill(null)]
return (
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 6,
padding: '0 10px 8px',
}}>
{slots.map((host: HostInfo | null, idx) => (
<div
key={idx}
style={{
border: '0.5px solid #e0ddd4',
borderRadius: 8,
padding: '7px 9px',
background: '#f5f4ed',
cursor: host ? 'pointer' : 'default',
transition: 'border-color 0.15s',
}}
onMouseEnter={e => host && ((e.currentTarget as HTMLDivElement).style.borderColor = '#d97757')}
onMouseLeave={e => host && ((e.currentTarget as HTMLDivElement).style.borderColor = '#e0ddd4')}
>
{host ? (
<>
{/* Hostname + IP */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<div style={{ width: 7, height: 7, borderRadius: '50%', background: '#22C55E', flexShrink: 0 }} />
<span style={{ fontSize: 9, fontWeight: 700, color: '#141413', fontFamily: 'monospace' }}>
{host.hostname}
</span>
</div>
<span style={{ fontSize: 8, color: '#87867f' }}>{host.ip}</span>
</div>
{/* CPU / RAM */}
<div style={{ display: 'flex', gap: 8, marginBottom: 4 }}>
<span style={{ fontSize: 8, color: '#b0ad9f' }}>
CPU <MetricBar value={host.cpuPct} />
</span>
<span style={{ fontSize: 8, color: '#b0ad9f' }}>
RAM <MetricBar value={host.ramPct} />
</span>
</div>
{/* 3 關鍵服務 mini 列 */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{host.services.slice(0, 3).map((svc, si) => (
<div key={si} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<div style={{
width: 4, height: 4, borderRadius: '50%',
background: svc.healthy ? '#22C55E' : '#cc2200',
flexShrink: 0,
}} />
<span style={{ fontSize: 8, color: '#87867f', fontFamily: 'monospace' }}>
{svc.name}
</span>
</div>
))}
</div>
</>
) : (
<div style={{ height: 60, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span style={{ fontSize: 9, color: '#b0ad9f' }}>--</span>
</div>
)}
</div>
))}
</div>
)
}
export default HostGrid