mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-02 19:45:54 +01:00
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:
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user