feat: add Proxmox Uptime View (#2092)

* feat: add Proxmox Uptime View

* fix: Proxmox Uptime

* fix: Proxmox Uptime

* fix: add env.example, make formattedUptime a constant

* fix: Uptime

* fix: Implement dayjs

* fix: removed unused import

* fix: Uptime
This commit is contained in:
Jason B.
2024-08-01 19:58:47 +02:00
committed by GitHub
parent 68ff84c85a
commit c042c245a3
3 changed files with 38 additions and 21 deletions

View File

@@ -86,7 +86,7 @@
}, },
"info": { "info": {
"uptime": "Uptime", "uptime": "Uptime",
"uptimeFormat": "{{days}} days, {{hours}} hours", "uptimeFormat": "{{days}} days, {{hours}} hours, {{minutes}} minutes",
"updates": "Updates Available", "updates": "Updates Available",
"reboot": "Reboot" "reboot": "Reboot"
}, },

View File

@@ -6,6 +6,8 @@ import {
IconInfoSquare, IconInfoSquare,
IconStatusChange, IconStatusChange,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useConfigContext } from '~/config/provider'; import { useConfigContext } from '~/config/provider';
import { api } from '~/utils/api'; import { api } from '~/utils/api';
@@ -18,6 +20,8 @@ import HealthMonitoringFileSystem from './HealthMonitoringFileSystem';
import HealthMonitoringMemory from './HealthMonitoringMemory'; import HealthMonitoringMemory from './HealthMonitoringMemory';
import { ClusterStatusTile } from './cluster/HealthMonitoringClusterTile'; import { ClusterStatusTile } from './cluster/HealthMonitoringClusterTile';
dayjs.extend(duration);
const defaultViewStates = ['none', 'node', 'vm', 'lxc', 'storage'] as const; const defaultViewStates = ['none', 'node', 'vm', 'lxc', 'storage'] as const;
type DefaultViewState = (typeof defaultViewStates)[number]; type DefaultViewState = (typeof defaultViewStates)[number];
@@ -186,12 +190,6 @@ function HealthMonitoringWidgetTile({ widget }: HealthMonitoringWidgetProps) {
const SystemStatusTile = ({ data, properties }: { data: any; properties: any }) => { const SystemStatusTile = ({ data, properties }: { data: any; properties: any }) => {
const { t } = useTranslation('modules/health-monitoring'); const { t } = useTranslation('modules/health-monitoring');
const formatUptime = (uptime: number) => {
const days = Math.floor(uptime / (60 * 60 * 24));
const remainingHours = Math.floor((uptime % (60 * 60 * 24)) / 3600);
return t('info.uptimeFormat', { days: days, hours: remainingHours})
};
return ( return (
<Stack> <Stack>
<Card> <Card>
@@ -268,3 +266,13 @@ const useStatusQuery = (node: string, ignoreCerts: boolean) => {
}; };
export default definition; export default definition;
export const formatUptime = (uptime: number) => {
const { t } = useTranslation('modules/health-monitoring');
const time = dayjs.duration(uptime, 's');
return t('info.uptimeFormat', {
days: Math.floor(time.asDays()),
hours: time.hours(),
minutes: time.minutes(),
});
};

View File

@@ -1,24 +1,20 @@
import { Accordion, Center, Flex, Group, RingProgress, Stack, Text } from '@mantine/core'; import { Accordion, Card, Center, Flex, Group, RingProgress, Stack, Text } from '@mantine/core';
import { import {
IconBrain, IconBrain,
IconCpu, IconCpu,
IconCube, IconCube,
IconDatabase, IconDatabase,
IconDeviceLaptop, IconDeviceLaptop,
IconInfoSquare,
IconServer, IconServer,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ResourceData, ResourceSummary } from '~/widgets/health-monitoring/cluster/types'; import { ResourceData } from '~/widgets/health-monitoring/cluster/types';
import { formatUptime } from '../HealthMonitoringTile';
import { ResourceType } from './HealthMonitoringClusterResourceRow'; import { ResourceType } from './HealthMonitoringClusterResourceRow';
export const ClusterStatusTile = ({ export const ClusterStatusTile = ({ data, properties }: { data: any; properties: any }) => {
data,
properties,
}: {
data: ResourceSummary;
properties: any;
}) => {
const { t } = useTranslation('modules/health-monitoring'); const { t } = useTranslation('modules/health-monitoring');
const running = (total: number, current: ResourceData) => { const running = (total: number, current: ResourceData) => {
@@ -46,12 +42,26 @@ export const ClusterStatusTile = ({
(sum: number, item: ResourceData) => (item.running ? item.cpu * item.maxCpu + sum : sum), (sum: number, item: ResourceData) => (item.running ? item.cpu * item.maxCpu + sum : sum),
0 0
); );
const uptime = data.nodes.reduce(
(sum: number, { uptime }: ResourceData) => (sum > uptime ? sum : uptime),
0
);
const cpuPercent = (usedCpu / maxCpu) * 100; const cpuPercent = (usedCpu / maxCpu) * 100;
const memPercent = (usedMem / maxMem) * 100; const memPercent = (usedMem / maxMem) * 100;
return ( return (
<Stack h="100%"> <Stack h="100%">
<Card>
<Group position="center">
<IconInfoSquare size={40} />
<Text fz="lg" tt="uppercase" fw={700} c="dimmed" align="center">
{t('info.uptime')}:
<br />
{formatUptime(uptime)}
</Text>
</Group>
</Card>
<SummaryHeader cpu={cpuPercent} memory={memPercent} include={properties.summary} /> <SummaryHeader cpu={cpuPercent} memory={memPercent} include={properties.summary} />
<Accordion <Accordion
variant="contained" variant="contained"
@@ -123,9 +133,8 @@ interface SummaryHeaderProps {
const SummaryHeader = ({ cpu, memory, include }: SummaryHeaderProps) => { const SummaryHeader = ({ cpu, memory, include }: SummaryHeaderProps) => {
const { t } = useTranslation('modules/health-monitoring'); const { t } = useTranslation('modules/health-monitoring');
if (!include) { if (!include) return null;
return null;
}
return ( return (
<Center> <Center>
<Group noWrap> <Group noWrap>
@@ -142,7 +151,7 @@ const SummaryHeader = ({ cpu, memory, include }: SummaryHeaderProps) => {
sections={[{ value: cpu, color: cpu > 75 ? 'orange' : 'green' }]} sections={[{ value: cpu, color: cpu > 75 ? 'orange' : 'green' }]}
/> />
<Stack align="center" justify="center" spacing={0}> <Stack align="center" justify="center" spacing={0}>
<Text>{t('cluster.summary.cpu')}</Text> <Text weight={500}>{t('cluster.summary.cpu')}</Text>
<Text>{cpu.toFixed(1)}%</Text> <Text>{cpu.toFixed(1)}%</Text>
</Stack> </Stack>
</Flex> </Flex>
@@ -159,7 +168,7 @@ const SummaryHeader = ({ cpu, memory, include }: SummaryHeaderProps) => {
sections={[{ value: memory, color: memory > 75 ? 'orange' : 'green' }]} sections={[{ value: memory, color: memory > 75 ? 'orange' : 'green' }]}
/> />
<Stack align="center" justify="center" spacing={0}> <Stack align="center" justify="center" spacing={0}>
<Text>{t('cluster.summary.ram')}</Text> <Text weight={500}>{t('cluster.summary.ram')}</Text>
<Text>{memory.toFixed(1)}%</Text> <Text>{memory.toFixed(1)}%</Text>
</Stack> </Stack>
</Flex> </Flex>