diff --git a/package.json b/package.json index 6307f33e6..0cc6099e9 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "eslint-plugin-unused-imports": "^2.0.0", "jest": "^28.1.3", "prettier": "^2.7.1", + "sass": "^1.56.1", "typescript": "^4.7.4" }, "resolutions": { diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 64232fcf5..fc6ea645e 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1,6 +1,8 @@ { "actions": { - "save": "Save" + "save": "Save", + "cancel": "Cancel", + "ok": "Okay" }, "tip": "Tip: ", "time": { diff --git a/public/locales/en/modules/dashdot.json b/public/locales/en/modules/dashdot.json index c44e04ca2..93bacf733 100644 --- a/public/locales/en/modules/dashdot.json +++ b/public/locales/en/modules/dashdot.json @@ -3,6 +3,7 @@ "name": "Dash.", "description": "A module for displaying the graphs of your running Dash. instance.", "settings": { + "title": "Settings for Dash. integration", "cpuMultiView": { "label": "CPU Multi-Core View" }, @@ -18,6 +19,10 @@ "url": { "label": "Dash. URL" } + }, + "remove": { + "title": "Remove Dash. integration", + "confirm": "Are you sure, that you want to remove the Dash. integration?" } }, "card": { diff --git a/public/locales/en/modules/date.json b/public/locales/en/modules/date.json index 521e220a4..e55f397ef 100644 --- a/public/locales/en/modules/date.json +++ b/public/locales/en/modules/date.json @@ -3,6 +3,7 @@ "name": "Date", "description": "Show the current time and date in a card", "settings": { + "title": "Settings for date integration", "display24HourFormat": { "label": "Display full time (24-hour)" } diff --git a/src/components/Dashboard/Tiles/Clock/ClockTile.tsx b/src/components/Dashboard/Tiles/Clock/ClockTile.tsx new file mode 100644 index 000000000..daea226c0 --- /dev/null +++ b/src/components/Dashboard/Tiles/Clock/ClockTile.tsx @@ -0,0 +1,70 @@ +import { Card, Center, Stack, Text, Title } from '@mantine/core'; +import dayjs from 'dayjs'; +import { useEffect, useRef, useState } from 'react'; +import { useSetSafeInterval } from '../../../../tools/hooks/useSetSafeInterval'; +import { ClockIntegrationType } from '../../../../types/integration'; +import { HomarrCardWrapper } from '../HomarrCardWrapper'; +import { IntegrationsMenu } from '../Integrations/IntegrationsMenu'; +import { BaseTileProps } from '../type'; + +interface ClockTileProps extends BaseTileProps { + module: ClockIntegrationType | undefined; +} + +export const ClockTile = ({ className, module }: ClockTileProps) => { + const date = useDateState(); + const formatString = module?.properties.is24HoursFormat ? 'HH:mm' : 'h:mm A'; + + return ( + + + integration="clock" + module={module} + options={module?.properties} + labels={{ is24HoursFormat: 'descriptor.settings.display24HourFormat.label' }} + /> +
+ + {dayjs(date).format(formatString)} + {dayjs(date).format('dddd, MMMM D')} + +
+
+ ); +}; + +/** + * State which updates when the minute is changing + * @returns current date updated every new minute + */ +const useDateState = () => { + const [date, setDate] = useState(new Date()); + const setSafeInterval = useSetSafeInterval(); + const timeoutRef = useRef(); // reference for initial timeout until first minute change + useEffect(() => { + timeoutRef.current = setTimeout(() => { + setDate(new Date()); + // Starts intervall which update the date every minute + setSafeInterval(() => { + setDate(new Date()); + }, 1000 * 60); + }, getMsUntilNextMinute()); + + return () => timeoutRef.current && clearTimeout(timeoutRef.current); + }, []); + + return date; +}; + +// calculates the amount of milliseconds until next minute starts. +const getMsUntilNextMinute = () => { + const now = new Date(); + const nextMinute = new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + now.getHours(), + now.getMinutes() + 1 + ); + return nextMinute.getTime() - now.getTime(); +}; diff --git a/src/components/Dashboard/Tiles/DashDot/DashDotCompactNetwork.tsx b/src/components/Dashboard/Tiles/DashDot/DashDotCompactNetwork.tsx new file mode 100644 index 000000000..1acb64909 --- /dev/null +++ b/src/components/Dashboard/Tiles/DashDot/DashDotCompactNetwork.tsx @@ -0,0 +1,36 @@ +import { Group, Stack, Text } from '@mantine/core'; +import { IconArrowNarrowDown, IconArrowNarrowUp } from '@tabler/icons'; +import { useTranslation } from 'next-i18next'; +import { bytes } from '../../../../tools/bytesHelper'; +import { DashDotInfo } from './DashDotTile'; + +interface DashDotCompactNetworkProps { + info: DashDotInfo; +} + +export const DashDotCompactNetwork = ({ info }: DashDotCompactNetworkProps) => { + const { t } = useTranslation('modules/dashdot'); + + const upSpeed = bytes.toPerSecondString(info?.network?.speedUp); + const downSpeed = bytes.toPerSecondString(info?.network?.speedDown); + + return ( + + {t('card.graphs.network.label')} + + + + {upSpeed} + + + + + + {downSpeed} + + + + + + ); +}; diff --git a/src/components/Dashboard/Tiles/DashDot/DashDotCompactStorage.tsx b/src/components/Dashboard/Tiles/DashDot/DashDotCompactStorage.tsx new file mode 100644 index 000000000..1bf40c048 --- /dev/null +++ b/src/components/Dashboard/Tiles/DashDot/DashDotCompactStorage.tsx @@ -0,0 +1,72 @@ +import { Group, Stack, Text } from '@mantine/core'; +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; +import { useTranslation } from 'next-i18next'; +import { bytes } from '../../../../tools/bytesHelper'; +import { percentage } from '../../../../tools/percentage'; +import { DashDotInfo } from './DashDotTile'; + +interface DashDotCompactStorageProps { + info: DashDotInfo; + dashDotUrl: string; +} + +export const DashDotCompactStorage = ({ info, dashDotUrl }: DashDotCompactStorageProps) => { + const { t } = useTranslation('modules/dashdot'); + const { data: storageLoad } = useQuery({ + queryKey: [ + 'dashdot/storageLoad', + { + dashDotUrl, + }, + ], + queryFn: () => fetchDashDotStorageLoad(dashDotUrl), + }); + + const totalUsed = calculateTotalLayoutSize({ + layout: storageLoad?.layout ?? [], + key: 'load', + }); + const totalSize = calculateTotalLayoutSize({ + layout: info?.storage.layout ?? [], + key: 'size', + }); + + return ( + + {t('card.graphs.storage.label')} + + + {percentage(totalUsed, totalSize)}% + + + {bytes.toString(totalUsed)} / {bytes.toString(totalSize)} + + + + ); +}; + +const calculateTotalLayoutSize = ({ + layout, + key, +}: CalculateTotalLayoutSizeProps) => { + return layout.reduce((total, current) => { + return total + (current[key] as number); + }, 0); +}; + +interface CalculateTotalLayoutSizeProps { + layout: TLayoutItem[]; + key: keyof TLayoutItem; +} + +const fetchDashDotStorageLoad = async (targetUrl: string) => { + return (await ( + await axios.get('/api/modules/dashdot', { params: { url: '/load/storage', base: targetUrl } }) + ).data) as DashDotStorageLoad; +}; + +interface DashDotStorageLoad { + layout: { load: number }[]; +} diff --git a/src/components/Dashboard/Tiles/DashDot/DashDotGraph.tsx b/src/components/Dashboard/Tiles/DashDot/DashDotGraph.tsx new file mode 100644 index 000000000..70280bbc7 --- /dev/null +++ b/src/components/Dashboard/Tiles/DashDot/DashDotGraph.tsx @@ -0,0 +1,73 @@ +import { createStyles, Stack, Title, useMantineTheme } from '@mantine/core'; +import { DashDotGraph as GraphType } from './types'; + +interface DashDotGraphProps { + graph: GraphType; + isCompact: boolean; + dashDotUrl: string; +} + +export const DashDotGraph = ({ graph, isCompact, dashDotUrl }: DashDotGraphProps) => { + const { classes } = useStyles(); + return ( + + + {graph.name} + +