import { formatDateTime } from './locale'; import { useEffect, useState } from 'react'; import { copy } from './copy'; import { presentArrayRole, presentReason, presentStatus } from './presentation'; type SmartAttribute = { id:string; name:string; rawValue:number; normalizedValue?:number; unit?:string; status:string; critical:boolean; reason?:string }; type Smart = { state:string; overall:string; observedAt?:string; attributes:SmartAttribute[]; selfTest?:{supported:boolean;result:string;completedAt?:string;ageSeconds?:number}; reasons?:string[] }; type Sample = { observedAt:string; readBytesPerSecond?:number; writeBytesPerSecond?:number; readIops?:number; writeIops?:number; readLatencyMs?:number; writeLatencyMs?:number }; type Disk = { id:string; name:string; role:string; state:string; model?:string; serialDisplay?:string; filesystem?:string; sizeBytes:number; usedBytes:number; freeBytes:number; utilizationPercent:number; capacitySeverity?:string; thermalSeverity?:string; inodes?:{total:number;used:number;free:number;utilizationPercent:number}; smart?:Smart; performance?:{state:string;current:Sample;history:Sample[]}; temperature?:{state:string;celsius?:number;status:string;observedAt?:string}; spin?:{state:string} }; type Snapshot = { source:{id:string;state:string;freshness:string;observedAt?:string;reason?:string}; disks:Disk[]; total:number; missingHistory?:Array<{diskId:string;name:string;role:string;observedAt:string;reason:string}> }; type Detail = {source:Snapshot['source'];disk:Disk}; function bytes(value:number):string{if(!Number.isFinite(value)||value<0)return'—';const units=['B','KB','MB','GB','TB'];let scaled=value;let index=0;while(scaled>=1024&&index{label}} function severityLabel(value?:string):string{return value==='normal'?'Normaal':value==='attention'?'Aandacht':value==='critical'?'Kritiek':'Onbekend'} function DiskCard({disk}:{disk:Disk}){return

{presentArrayRole(disk.role)}

{disk.name}

{disk.filesystem||copy.disks.noFilesystem} · {bytes(disk.sizeBytes)} · {disk.utilizationPercent.toLocaleString('nl-BE',{maximumFractionDigits:1})}% {copy.disks.used}

Capaciteit: {severityLabel(disk.capacitySeverity)} · Temperatuur: {severityLabel(disk.thermalSeverity)}

{disk.model||copy.disks.noModel} · {disk.serialDisplay||copy.disks.noSerial}

} export function DiskPage(){const[state,setState]=useState<'loading'|'ready'|'error'>('loading');const[snapshot,setSnapshot]=useState(null);useEffect(()=>{const controller=new AbortController();fetch('/api/v1/disks?limit=100',{signal:controller.signal}).then(response=>{if(!response.ok)throw new Error('disks');return response.json() as Promise}).then(data=>{setSnapshot(data);setState('ready')}).catch(error=>{if(error instanceof DOMException&&error.name==='AbortError')return;setState('error')});return()=>controller.abort()},[]);if(state==='loading')return
;if(state==='error'||!snapshot)return

{copy.disks.errorTitle}

{copy.disks.errorDetail}

;const fresh=snapshot.source?.freshness==='fresh'&&snapshot.source?.state!=='unknown';return <>

{copy.disks.eyebrow}

{copy.disks.title}

{copy.disks.intro}

{copy.disks.source}

{snapshot.source?.id||copy.disks.unknown}

{snapshot.source?.reason?presentReason(snapshot.source.reason):(fresh?copy.disks.fresh:copy.disks.stale)}

{copy.disks.observed}: {date(snapshot.source?.observedAt)} · {snapshot.total} {copy.disks.rows}

{snapshot.disks.length?snapshot.disks.map(disk=>):

{copy.disks.empty}

}

{copy.disks.history}

{copy.disks.missingTitle}

{snapshot.missingHistory?.length?
    {snapshot.missingHistory.map(item=>
  • {item.name}{item.role} · {presentReason(item.reason)} · {date(item.observedAt)}
  • )}
:

{copy.disks.noMissingHistory}

}
} function TelemetrySection({performance,temperature,spin}:{performance?:Disk['performance'];temperature?:Disk['temperature'];spin?:Disk['spin']}){return

{copy.disks.telemetry}

{temperature?.celsius == null ? '—' : temperature.celsius.toLocaleString('nl-BE',{maximumFractionDigits:1})+' °C'}

{copy.disks.spin}: {spin?.state==='unsupported'?copy.disks.unsupported:presentStatus(spin?.state)} · {copy.disks.performance}: {performance?.state==='unsupported'?copy.disks.unsupported:presentStatus(performance?.state)}

{performance?.state==='available'&&

{copy.disks.read}: {Math.round(performance.current.readBytesPerSecond||0).toLocaleString('nl-BE')} B/s · {copy.disks.write}: {Math.round(performance.current.writeBytesPerSecond||0).toLocaleString('nl-BE')} B/s · {performance.history.length} {copy.disks.historyPoints}

}
}function SmartSection({smart}:{smart?:Smart}){if(!smart)return

SMART

{copy.disks.smartUnavailable}

;return

SMART

{presentStatus(smart.overall)}

{smart.reasons?.length?
    {smart.reasons.map(reason=>
  • {reason}
  • )}
:

{copy.disks.smartNoReasons}

}{smart.selfTest&&

{copy.disks.selfTest}: {smart.selfTest.result} · {smart.selfTest.ageSeconds == null ? '—' : Math.floor(smart.selfTest.ageSeconds/3600)+' u'}

}{smart.attributes.length>0&&
{smart.attributes.map(attribute=>)}
{copy.disks.attribute}{copy.disks.value}{copy.disks.state}
{attribute.name}{attribute.id}{attribute.rawValue}{attribute.unit?' '+attribute.unit:''}
}
}export function DiskDetailPage({id}:{id:string}){const[state,setState]=useState<'loading'|'ready'|'error'>('loading');const[detail,setDetail]=useState(null);useEffect(()=>{const controller=new AbortController();fetch('/api/v1/disks/'+encodeURIComponent(id),{signal:controller.signal}).then(response=>{if(!response.ok)throw new Error('disk-detail');return response.json() as Promise}).then(data=>{setDetail(data);setState('ready')}).catch(error=>{if(error instanceof DOMException&&error.name==='AbortError')return;setState('error')});return()=>controller.abort()},[id]);if(state==='loading')return
;if(state==='error'||!detail)return

{copy.disks.errorTitle}

{copy.disks.errorDetail}

{copy.disks.back}
;const disk=detail.disk;return <>

{copy.disks.detailEyebrow}

{disk.name}

{copy.disks.detailIntro}

{copy.disks.source}

{detail.source?.id||copy.disks.unknown}

{detail.source?.reason||copy.disks.readOnly}

{copy.disks.observed}: {date(detail.source?.observedAt)} · {detail.source?.freshness=== 'fresh'?copy.disks.fresh:copy.disks.stale}

{copy.disks.capacity}

{disk.utilizationPercent.toLocaleString('nl-BE',{maximumFractionDigits:1})}%

{bytes(disk.usedBytes)} {copy.disks.usedOf} {bytes(disk.sizeBytes)} · {bytes(disk.freeBytes)} {copy.disks.free}

{copy.disks.identity}

{presentArrayRole(disk.role)}

{disk.model||copy.disks.noModel} · {disk.serialDisplay||copy.disks.noSerial}

{disk.inodes&&

{copy.disks.inodes}

{disk.inodes.utilizationPercent.toLocaleString('nl-BE',{maximumFractionDigits:1})}%

{disk.inodes.used} / {disk.inodes.total}

}
{copy.disks.technical}
ID
{disk.id}
{copy.disks.filesystem}
{disk.filesystem||copy.disks.noFilesystem}
{copy.disks.capacity}
{bytes(disk.sizeBytes)}
{copy.disks.free}
{bytes(disk.freeBytes)}
{copy.disks.serial}
{disk.serialDisplay||copy.disks.noSerial}
}