import { bytesToString } from '@/util/helpers' import { CircleStackIcon, CpuChipIcon, ExclamationTriangleIcon, ServerStackIcon, SignalIcon, UsersIcon, } from '@heroicons/react/24/outline' import { Badge, Skeleton } from '@mantine/core' import { ComponentType } from 'react' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' import useOverviewSWR from '@/api/admin/overview/useOverviewSWR' import { DashboardMetric, DashboardNode, } from '@/api/admin/overview/getOverview' import Card from '@/components/elements/Card' import MessageBox from '@/components/elements/MessageBox' import PageContentBlock from '@/components/elements/PageContentBlock' interface IconProps { className?: string } interface StatCardProps { title: string value: number | string detail?: string icon: ComponentType to?: string tone?: 'default' | 'warning' | 'error' } const toneClasses = { default: 'text-accent-600 border-accent-200 bg-accent-100', warning: 'text-warning-dark border-warning bg-warning-lighter', error: 'text-error border-error-light bg-error-lighter', } const StatCard = ({ title, value, detail, icon: Icon, to, tone = 'default', }: StatCardProps) => { const content = (

{title}

{value}

{detail &&

{detail}

}
) if (to) { return ( {content} ) } return ( {content} ) } const UsageBar = ({ label, metric, }: { label: string metric: DashboardMetric }) => { const { t } = useTranslation('admin.overview') return (

{label}

{bytesToString(metric.allocated)} /{' '} {bytesToString(metric.total)}

= 90 ? 'bg-error' : metric.percent >= 75 ? 'bg-warning' : 'bg-success' }`} style={{ width: `${Math.min(metric.percent, 100)}%` }} />

{t('allocated_percent', { percent: metric.percent })}

) } const NodeRow = ({ node }: { node: DashboardNode }) => { const { t } = useTranslation('admin.overview') const { t: tStrings } = useTranslation('strings') return (
{node.name}

{node.cluster} - {node.fqdn}

{t('servers_count', { count: node.servers })}
) } const OverviewSkeleton = () => (
{[1, 2, 3, 4].map(item => ( ))}
) const OverviewContainer = () => { const { t } = useTranslation('admin.overview') const { t: tStrings } = useTranslation('strings') const { data, error } = useOverviewSWR() return (

{tStrings('overview')}

{t('description')}

{error && ( {t('load_error')} )} {!data ? ( ) : (
0 ? 'error' : 'default' } />

{t('capacity')}

{t('capacity_description')}

{t('addresses')}

{data.addresses.assigned} /{' '} {data.addresses.total}

{t('addresses_detail', { available: data.addresses.available, pools: data.addresses.pools, })}

{tStrings('backup', { count: 2 })}

{data.backups.successful} /{' '} {data.backups.total}

{t('backup_status_detail', { pending: data.backups.pending, failed: data.backups.failed, })}

{tStrings('iso', { count: 2 })}

{data.isos.successful} /{' '} {data.isos.total}

{t('iso_status_detail', { pending: data.isos.pending, })}

{t('server_state')}

{t('server_state_description')}

{[ [t('ready'), data.servers.ready], [t('installing'), data.servers.installing], [ tStrings('suspended'), data.servers.suspended, ], [t('restoring'), data.servers.restoring], [t('deleting'), data.servers.deleting], [t('failed'), data.servers.failed], ].map(([label, value]) => (

{label}

{value}

))}

{tStrings('node', { count: 2 })}

{t('nodes_description')}

{data.nodes.length === 0 ? (

{t('no_nodes')}

) : ( data.nodes.map(node => ( )) )}
)}
) } export default OverviewContainer