mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-07 05:55:48 +01:00
feat: add dash. integration
This commit is contained in:
@@ -1,27 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
Modal,
|
ActionIcon, Anchor, Button, Center,
|
||||||
Center,
|
Group, Image, LoadingOverlay, Modal, MultiSelect,
|
||||||
Group,
|
ScrollArea, Select, Switch, Tabs, Text, TextInput, Title, Tooltip
|
||||||
TextInput,
|
|
||||||
Image,
|
|
||||||
Button,
|
|
||||||
Select,
|
|
||||||
LoadingOverlay,
|
|
||||||
ActionIcon,
|
|
||||||
Tooltip,
|
|
||||||
Title,
|
|
||||||
Anchor,
|
|
||||||
Text,
|
|
||||||
Tabs,
|
|
||||||
MultiSelect,
|
|
||||||
ScrollArea,
|
|
||||||
Switch,
|
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { IconApps as Apps } from '@tabler/icons';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { useDebouncedValue } from '@mantine/hooks';
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
|
import { IconApps as Apps } from '@tabler/icons';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { ServiceTypeList, StatusCodes } from '../../tools/types';
|
import { ServiceTypeList, StatusCodes } from '../../tools/types';
|
||||||
import Tip from '../layout/Tip';
|
import Tip from '../layout/Tip';
|
||||||
@@ -85,6 +71,7 @@ function MatchPort(name: string, form: any) {
|
|||||||
{ name: 'readarr', value: '8686' },
|
{ name: 'readarr', value: '8686' },
|
||||||
{ name: 'deluge', value: '8112' },
|
{ name: 'deluge', value: '8112' },
|
||||||
{ name: 'transmission', value: '9091' },
|
{ name: 'transmission', value: '9091' },
|
||||||
|
{ name: 'dash.', value: '3001' },
|
||||||
];
|
];
|
||||||
// Match name with portmap key
|
// Match name with portmap key
|
||||||
const port = portmap.find((p) => p.name === name.toLowerCase());
|
const port = portmap.find((p) => p.name === name.toLowerCase());
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
import {
|
||||||
createStyles,
|
ActionIcon,
|
||||||
Header as Head,
|
|
||||||
Group,
|
|
||||||
Box,
|
Box,
|
||||||
Burger,
|
Burger,
|
||||||
|
createStyles,
|
||||||
Drawer,
|
Drawer,
|
||||||
Title,
|
Group,
|
||||||
|
Header as Head,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
ActionIcon,
|
Title,
|
||||||
Transition,
|
Transition,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useBooleanToggle } from '@mantine/hooks';
|
import { useBooleanToggle } from '@mantine/hooks';
|
||||||
import { Logo } from './Logo';
|
|
||||||
import SearchBar from '../modules/search/SearchModule';
|
|
||||||
import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem';
|
import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem';
|
||||||
import { SettingsMenuButton } from '../Settings/SettingsMenu';
|
import { CalendarModule, DateModule, TotalDownloadsModule, WeatherModule } from '../modules';
|
||||||
|
import { DashdotModule } from '../modules/dash.';
|
||||||
import { ModuleWrapper } from '../modules/moduleWrapper';
|
import { ModuleWrapper } from '../modules/moduleWrapper';
|
||||||
import { CalendarModule, TotalDownloadsModule, WeatherModule, DateModule } from '../modules';
|
import SearchBar from '../modules/search/SearchModule';
|
||||||
|
import { SettingsMenuButton } from '../Settings/SettingsMenu';
|
||||||
|
import { Logo } from './Logo';
|
||||||
|
|
||||||
const HEADER_HEIGHT = 60;
|
const HEADER_HEIGHT = 60;
|
||||||
|
|
||||||
@@ -84,6 +84,7 @@ export function Header(props: any) {
|
|||||||
<ModuleWrapper module={TotalDownloadsModule} />
|
<ModuleWrapper module={TotalDownloadsModule} />
|
||||||
<ModuleWrapper module={WeatherModule} />
|
<ModuleWrapper module={WeatherModule} />
|
||||||
<ModuleWrapper module={DateModule} />
|
<ModuleWrapper module={DateModule} />
|
||||||
|
<ModuleWrapper module={DashdotModule} />
|
||||||
</Group>
|
</Group>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Group } from '@mantine/core';
|
import { Group } from '@mantine/core';
|
||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
import { CalendarModule, DateModule, TotalDownloadsModule, WeatherModule } from '../modules';
|
import { CalendarModule, DateModule, TotalDownloadsModule, WeatherModule } from '../modules';
|
||||||
|
import { DashdotModule } from '../modules/dash.';
|
||||||
import { ModuleWrapper } from '../modules/moduleWrapper';
|
import { ModuleWrapper } from '../modules/moduleWrapper';
|
||||||
|
|
||||||
export default function Widgets(props: any) {
|
export default function Widgets(props: any) {
|
||||||
@@ -14,6 +15,7 @@ export default function Widgets(props: any) {
|
|||||||
<ModuleWrapper module={TotalDownloadsModule} />
|
<ModuleWrapper module={TotalDownloadsModule} />
|
||||||
<ModuleWrapper module={WeatherModule} />
|
<ModuleWrapper module={WeatherModule} />
|
||||||
<ModuleWrapper module={DateModule} />
|
<ModuleWrapper module={DateModule} />
|
||||||
|
<ModuleWrapper module={DashdotModule} />
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
246
src/components/modules/dash./DashdotModule.tsx
Normal file
246
src/components/modules/dash./DashdotModule.tsx
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import { createStyles, useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
||||||
|
import { IconCalendar as CalendarIcon } from '@tabler/icons';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useConfig } from '../../../tools/state';
|
||||||
|
import { serviceItem } from '../../../tools/types';
|
||||||
|
import { IModule } from '../modules';
|
||||||
|
|
||||||
|
const asModule = <T extends IModule>(t: T) => t;
|
||||||
|
export const DashdotModule = asModule({
|
||||||
|
title: 'Dash.',
|
||||||
|
description: 'A module for displaying the graphs of your running Dash. instance.',
|
||||||
|
icon: CalendarIcon,
|
||||||
|
component: DashdotComponent,
|
||||||
|
options: {
|
||||||
|
cpuMultiView: {
|
||||||
|
name: 'CPU Multi-Core View',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
storageMultiView: {
|
||||||
|
name: 'Storage Multi-Drive View',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
useCompactView: {
|
||||||
|
name: 'Use Compact View',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
showCpu: {
|
||||||
|
name: 'Show CPU Graph',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
showStorage: {
|
||||||
|
name: 'Show Storage Graph',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
showRam: {
|
||||||
|
name: 'Show RAM Graph',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
showNetwork: {
|
||||||
|
name: 'Show Network Graphs',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
showGpu: {
|
||||||
|
name: 'Show GPU Graph',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const useStyles = createStyles((theme, _params) => ({
|
||||||
|
heading: {
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
display: 'table',
|
||||||
|
},
|
||||||
|
tableRow: {
|
||||||
|
display: 'table-row',
|
||||||
|
},
|
||||||
|
tableLabel: {
|
||||||
|
display: 'table-cell',
|
||||||
|
paddingRight: 10,
|
||||||
|
},
|
||||||
|
tableValue: {
|
||||||
|
display: 'table-cell',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
paddingBottom: 5,
|
||||||
|
},
|
||||||
|
graphsContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
rowGap: 15,
|
||||||
|
columnGap: 10,
|
||||||
|
},
|
||||||
|
iframe: {
|
||||||
|
flex: '1 0 auto',
|
||||||
|
maxWidth: '100%',
|
||||||
|
height: '140px',
|
||||||
|
borderRadius: theme.radius.lg,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const bpsPrettyPrint = (bits?: number) =>
|
||||||
|
!bits
|
||||||
|
? '-'
|
||||||
|
: bits > 1000 * 1000 * 1000
|
||||||
|
? `${(bits / 1000 / 1000 / 1000).toFixed(1)} Gb/s`
|
||||||
|
: bits > 1000 * 1000
|
||||||
|
? `${(bits / 1000 / 1000).toFixed(1)} Mb/s`
|
||||||
|
: bits > 1000
|
||||||
|
? `${(bits / 1000).toFixed(1)} Kb/s`
|
||||||
|
: `${bits.toFixed(1)} b/s`;
|
||||||
|
|
||||||
|
const bytePrettyPrint = (byte: number): string =>
|
||||||
|
byte > 1024 * 1024 * 1024
|
||||||
|
? `${(byte / 1024 / 1024 / 1024).toFixed(1)} GiB`
|
||||||
|
: byte > 1024 * 1024
|
||||||
|
? `${(byte / 1024 / 1024).toFixed(1)} MiB`
|
||||||
|
: byte > 1024
|
||||||
|
? `${(byte / 1024).toFixed(1)} KiB`
|
||||||
|
: `${byte.toFixed(1)} B`;
|
||||||
|
|
||||||
|
const useJson = (service: serviceItem | undefined, url: string) => {
|
||||||
|
const [data, setData] = useState<any | undefined>();
|
||||||
|
|
||||||
|
const doRequest = async () => {
|
||||||
|
try {
|
||||||
|
const resp = await axios.get(url, { baseURL: service?.url });
|
||||||
|
|
||||||
|
setData(resp.data);
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (service?.url) {
|
||||||
|
doRequest();
|
||||||
|
}
|
||||||
|
}, [service?.url]);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DashdotComponent() {
|
||||||
|
const { config } = useConfig();
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
const { classes } = useStyles();
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
|
const dashConfig = config.modules?.[DashdotModule.title]
|
||||||
|
.options as typeof DashdotModule['options'];
|
||||||
|
const isCompact = dashConfig?.useCompactView?.value ?? false;
|
||||||
|
const dashdotService = config.services.filter((service) => service.type === 'Dash.')[0];
|
||||||
|
|
||||||
|
const cpuEnabled = dashConfig?.showCpu?.value ?? true;
|
||||||
|
const storageEnabled = dashConfig?.showStorage?.value ?? true;
|
||||||
|
const ramEnabled = dashConfig?.showRam?.value ?? true;
|
||||||
|
const networkEnabled = dashConfig?.showNetwork?.value ?? true;
|
||||||
|
const gpuEnabled = dashConfig?.showGpu?.value ?? false;
|
||||||
|
|
||||||
|
const info = useJson(dashdotService, '/info');
|
||||||
|
const storageLoad = useJson(dashdotService, '/load/storage');
|
||||||
|
|
||||||
|
const totalUsed =
|
||||||
|
(storageLoad?.layout as any[])?.reduce((acc, curr) => (curr.load ?? 0) + acc, 0) ?? 0;
|
||||||
|
const totalSize =
|
||||||
|
(info?.storage?.layout as any[])?.reduce((acc, curr) => (curr.size ?? 0) + acc, 0) ?? 0;
|
||||||
|
|
||||||
|
const graphs = [
|
||||||
|
{
|
||||||
|
name: 'CPU',
|
||||||
|
enabled: cpuEnabled,
|
||||||
|
params: {
|
||||||
|
multiView: dashConfig?.cpuMultiView?.value ?? false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Storage',
|
||||||
|
enabled: storageEnabled && !isCompact,
|
||||||
|
params: {
|
||||||
|
multiView: dashConfig?.storageMultiView?.value ?? false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RAM',
|
||||||
|
enabled: ramEnabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Network',
|
||||||
|
enabled: networkEnabled,
|
||||||
|
spanTwo: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'GPU',
|
||||||
|
enabled: gpuEnabled,
|
||||||
|
spanTwo: true,
|
||||||
|
},
|
||||||
|
].filter((g) => g.enabled);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className={classes.heading}>Dash.</h2>
|
||||||
|
|
||||||
|
{!dashdotService ? (
|
||||||
|
<p>No dash. service found. Please add one to your Homarr dashboard.</p>
|
||||||
|
) : !info ? (
|
||||||
|
<p>Cannot acquire information from dash. - are you running the latest version?</p>
|
||||||
|
) : (
|
||||||
|
<div className={classes.graphsContainer}>
|
||||||
|
<div className={classes.table}>
|
||||||
|
{storageEnabled && isCompact && (
|
||||||
|
<div className={classes.tableRow}>
|
||||||
|
<p className={classes.tableLabel}>Storage:</p>
|
||||||
|
<p className={classes.tableValue}>
|
||||||
|
{(totalUsed / (totalSize || 1)).toFixed(1)}%{'\n'}
|
||||||
|
{bytePrettyPrint(totalUsed)} / {bytePrettyPrint(totalSize)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={classes.tableRow}>
|
||||||
|
<p className={classes.tableLabel}>Network:</p>
|
||||||
|
<p className={classes.tableValue}>
|
||||||
|
{bpsPrettyPrint(info?.network?.speedUp)} Up{'\n'}
|
||||||
|
{bpsPrettyPrint(info?.network?.speedDown)} Down
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{graphs.map((graph) => (
|
||||||
|
<iframe
|
||||||
|
className={classes.iframe}
|
||||||
|
style={
|
||||||
|
isCompact
|
||||||
|
? {
|
||||||
|
width: graph.spanTwo ? '100%' : 'calc(50% - 5px)',
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
key={graph.name}
|
||||||
|
title={graph.name}
|
||||||
|
src={`${
|
||||||
|
dashdotService.url
|
||||||
|
}?singleGraphMode=true&graph=${graph.name.toLowerCase()}&theme=${colorScheme}&surface=${(colorScheme ===
|
||||||
|
'dark'
|
||||||
|
? theme.colors.dark[7]
|
||||||
|
: theme.colors.gray[0]
|
||||||
|
).substring(1)}${isCompact ? '&gap=1' : `&gap=5&innerRadius=${theme.radius.md}`}${
|
||||||
|
graph.params
|
||||||
|
? `&${Object.entries(graph.params)
|
||||||
|
.map(([key, value]) => `${key}=${value.toString()}`)
|
||||||
|
.join('&')}`
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
frameBorder="0"
|
||||||
|
allowTransparency
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
src/components/modules/dash./index.ts
Normal file
1
src/components/modules/dash./index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { DashdotModule } from './DashdotModule';
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './date';
|
|
||||||
export * from './calendar';
|
export * from './calendar';
|
||||||
export * from './search';
|
export * from './dash.';
|
||||||
export * from './ping';
|
export * from './date';
|
||||||
export * from './weather';
|
|
||||||
export * from './downloads';
|
export * from './downloads';
|
||||||
|
export * from './ping';
|
||||||
|
export * from './search';
|
||||||
|
export * from './weather';
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export const Targets = [
|
|||||||
export const ServiceTypeList = [
|
export const ServiceTypeList = [
|
||||||
'Other',
|
'Other',
|
||||||
'Emby',
|
'Emby',
|
||||||
|
'Dash.',
|
||||||
'Deluge',
|
'Deluge',
|
||||||
'Lidarr',
|
'Lidarr',
|
||||||
'Plex',
|
'Plex',
|
||||||
@@ -69,18 +70,8 @@ export const ServiceTypeList = [
|
|||||||
'Sonarr',
|
'Sonarr',
|
||||||
'qBittorrent',
|
'qBittorrent',
|
||||||
'Transmission',
|
'Transmission',
|
||||||
];
|
] as const;
|
||||||
export type ServiceType =
|
export type ServiceType = typeof ServiceTypeList[number];
|
||||||
| 'Other'
|
|
||||||
| 'Emby'
|
|
||||||
| 'Deluge'
|
|
||||||
| 'Lidarr'
|
|
||||||
| 'Plex'
|
|
||||||
| 'Radarr'
|
|
||||||
| 'Readarr'
|
|
||||||
| 'Sonarr'
|
|
||||||
| 'qBittorrent'
|
|
||||||
| 'Transmission';
|
|
||||||
|
|
||||||
export interface serviceItem {
|
export interface serviceItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user