Merge branch 'dev' into useQuery-stale-time-cache

This commit is contained in:
Thomas Camlong
2023-02-02 18:59:09 +09:00
committed by GitHub
23 changed files with 121 additions and 39 deletions

View File

@@ -1,3 +1,2 @@
export const REPO_URL = 'ajnart/homarr'; export const REPO_URL = 'ajnart/homarr';
export const CURRENT_VERSION = 'v0.11.3';
export const ICON_PICKER_SLICE_LIMIT = 36; export const ICON_PICKER_SLICE_LIMIT = 36;

View File

@@ -1,6 +1,6 @@
{ {
"name": "homarr", "name": "homarr",
"version": "0.11.3", "version": "0.11.4",
"description": "Homarr - A homepage for your server.", "description": "Homarr - A homepage for your server.",
"license": "MIT", "license": "MIT",
"repository": { "repository": {

View File

@@ -18,6 +18,9 @@
}, },
"url": { "url": {
"label": "Dash. URL" "label": "Dash. URL"
},
"usePercentages": {
"label": "Display percentages"
} }
} }
}, },

View File

@@ -13,6 +13,7 @@ import {
Title, Title,
} from '@mantine/core'; } from '@mantine/core';
import { import {
IconAnchor,
IconBrandDiscord, IconBrandDiscord,
IconBrandGithub, IconBrandGithub,
IconFile, IconFile,
@@ -27,9 +28,9 @@ import { InitOptions } from 'i18next';
import { i18n, Trans, useTranslation } from 'next-i18next'; import { i18n, Trans, useTranslation } from 'next-i18next';
import Image from 'next/image'; import Image from 'next/image';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { CURRENT_VERSION } from '../../../data/constants';
import { useConfigContext } from '../../config/provider'; import { useConfigContext } from '../../config/provider';
import { useConfigStore } from '../../config/store'; import { useConfigStore } from '../../config/store';
import { usePackageAttributesStore } from '../../tools/client/zustands/usePackageAttributesStore';
import { usePrimaryGradient } from '../layout/useGradient'; import { usePrimaryGradient } from '../layout/useGradient';
import Credits from '../Settings/Common/Credits'; import Credits from '../Settings/Common/Credits';
@@ -132,6 +133,7 @@ interface ExtendedInitOptions extends InitOptions {
const useInformationTableItems = (newVersionAvailable?: string): InformationTableItem[] => { const useInformationTableItems = (newVersionAvailable?: string): InformationTableItem[] => {
// TODO: Fix this to not request. Pass it as a prop. // TODO: Fix this to not request. Pass it as a prop.
const colorGradiant = usePrimaryGradient(); const colorGradiant = usePrimaryGradient();
const { attributes } = usePackageAttributesStore();
const { configVersion } = useConfigContext(); const { configVersion } = useConfigContext();
const { configs } = useConfigStore(); const { configs } = useConfigStore();
@@ -190,7 +192,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
content: ( content: (
<Group position="right"> <Group position="right">
<Badge variant="gradient" gradient={colorGradiant}> <Badge variant="gradient" gradient={colorGradiant}>
{CURRENT_VERSION} {attributes.packageVersion ?? 'Unknown'}
</Badge> </Badge>
{newVersionAvailable && ( {newVersionAvailable && (
<HoverCard shadow="md" position="top" withArrow> <HoverCard shadow="md" position="top" withArrow>
@@ -218,13 +220,22 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
{newVersionAvailable} {newVersionAvailable}
</Anchor> </Anchor>
</b>{' '} </b>{' '}
is available ! Current version: {CURRENT_VERSION} is available ! Current version: {attributes.packageVersion}
</HoverCard.Dropdown> </HoverCard.Dropdown>
</HoverCard> </HoverCard>
)} )}
</Group> </Group>
), ),
}, },
{
icon: <IconAnchor size={20} />,
label: 'Node environment',
content: (
<Badge variant="gradient" gradient={colorGradiant}>
{attributes.environment}
</Badge>
),
},
...items, ...items,
]; ];

View File

@@ -3,7 +3,6 @@ import { useConfigStore } from '../../../../config/store';
import { openContextModalGeneric } from '../../../../tools/mantineModalManagerExtensions'; import { openContextModalGeneric } from '../../../../tools/mantineModalManagerExtensions';
import { AppType } from '../../../../types/app'; import { AppType } from '../../../../types/app';
import { CategoryType } from '../../../../types/category'; import { CategoryType } from '../../../../types/category';
import { ConfigType } from '../../../../types/config';
import { WrapperType } from '../../../../types/wrapper'; import { WrapperType } from '../../../../types/wrapper';
import { IWidget } from '../../../../widgets/widgets'; import { IWidget } from '../../../../widgets/widgets';
import { CategoryEditModalInnerProps } from './CategoryEditModal'; import { CategoryEditModalInnerProps } from './CategoryEditModal';

View File

@@ -1,9 +1,10 @@
import { Box, createStyles, Group, Header as MantineHeader, Indicator } from '@mantine/core'; import { Box, createStyles, Group, Header as MantineHeader, Indicator } from '@mantine/core';
import { useQuery } from '@tanstack/react-query'; import { useEffect, useState } from 'react';
import { CURRENT_VERSION, REPO_URL } from '../../../../data/constants'; import { REPO_URL } from '../../../../data/constants';
import DockerMenuButton from '../../../modules/Docker/DockerModule';
import { usePackageAttributesStore } from '../../../tools/client/zustands/usePackageAttributesStore';
import { Logo } from '../Logo'; import { Logo } from '../Logo';
import { useCardStyles } from '../useCardStyles'; import { useCardStyles } from '../useCardStyles';
import DockerMenuButton from '../../../modules/Docker/DockerModule';
import { ToggleEditModeAction } from './Actions/ToggleEditMode/ToggleEditMode'; import { ToggleEditModeAction } from './Actions/ToggleEditMode/ToggleEditMode';
import { Search } from './Search'; import { Search } from './Search';
import { SettingsMenu } from './SettingsMenu'; import { SettingsMenu } from './SettingsMenu';
@@ -13,6 +14,8 @@ export const HeaderHeight = 64;
export function Header(props: any) { export function Header(props: any) {
const { classes } = useStyles(); const { classes } = useStyles();
const { classes: cardClasses } = useCardStyles(false); const { classes: cardClasses } = useCardStyles(false);
const { attributes } = usePackageAttributesStore();
const { isLoading, error, data } = useQuery({ const { isLoading, error, data } = useQuery({
queryKey: ['github/latest'], queryKey: ['github/latest'],
cacheTime: 1000 * 60 * 60 * 24, cacheTime: 1000 * 60 * 60 * 24,
@@ -20,10 +23,11 @@ export function Header(props: any) {
queryFn: () => queryFn: () =>
fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => res.json()), fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => res.json()),
}); });
const newVersionAvailable = data?.tag_name !== CURRENT_VERSION ? data?.tag_name : undefined; const newVersionAvailable =
data?.tag_name > `v${attributes.packageVersion}` ? data?.tag_name : undefined;
return ( return (
<MantineHeader height={HeaderHeight} className={cardClasses.card}> <MantineHeader height="auto" className={cardClasses.card}>
<Group p="xs" noWrap grow> <Group p="xs" noWrap grow>
<Box className={classes.hide}> <Box className={classes.hide}>
<Logo /> <Logo />

View File

@@ -8,8 +8,8 @@ import Layout from '../components/layout/Layout';
import { useInitConfig } from '../config/init'; import { useInitConfig } from '../config/init';
import { getFallbackConfig } from '../tools/config/getFallbackConfig'; import { getFallbackConfig } from '../tools/config/getFallbackConfig';
import { getFrontendConfig } from '../tools/config/getFrontendConfig'; import { getFrontendConfig } from '../tools/config/getFrontendConfig';
import { getServerSideTranslations } from '../tools/getServerSideTranslations'; import { getServerSideTranslations } from '../tools/server/getServerSideTranslations';
import { dashboardNamespaces } from '../tools/translation-namespaces'; import { dashboardNamespaces } from '../tools/server/translation-namespaces';
import { ConfigType } from '../types/config'; import { ConfigType } from '../types/config';
import { DashboardServerSideProps } from '../types/dashboardPageType'; import { DashboardServerSideProps } from '../types/dashboardPageType';

View File

@@ -8,7 +8,7 @@ import { GetServerSidePropsContext } from 'next';
import { appWithTranslation } from 'next-i18next'; import { appWithTranslation } from 'next-i18next';
import { AppProps } from 'next/app'; import { AppProps } from 'next/app';
import Head from 'next/head'; import Head from 'next/head';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { ChangeAppPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal'; import { ChangeAppPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal';
import { ChangeWidgetPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal'; import { ChangeWidgetPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal';
@@ -22,8 +22,16 @@ import '../styles/global.scss';
import { ColorTheme } from '../tools/color'; import { ColorTheme } from '../tools/color';
import { queryClient } from '../tools/queryClient'; import { queryClient } from '../tools/queryClient';
import { theme } from '../tools/theme'; import { theme } from '../tools/theme';
import {
getServiceSidePackageAttributes,
ServerSidePackageAttributesType,
} from '../tools/server/getPackageVersion';
import { usePackageAttributesStore } from '../tools/client/zustands/usePackageAttributesStore';
function App(this: any, props: AppProps & { colorScheme: ColorScheme }) { function App(
this: any,
props: AppProps & { colorScheme: ColorScheme; packageAttributes: ServerSidePackageAttributesType }
) {
const { Component, pageProps } = props; const { Component, pageProps } = props;
const [primaryColor, setPrimaryColor] = useState<MantineTheme['primaryColor']>('red'); const [primaryColor, setPrimaryColor] = useState<MantineTheme['primaryColor']>('red');
const [secondaryColor, setSecondaryColor] = useState<MantineTheme['primaryColor']>('orange'); const [secondaryColor, setSecondaryColor] = useState<MantineTheme['primaryColor']>('orange');
@@ -46,6 +54,12 @@ function App(this: any, props: AppProps & { colorScheme: ColorScheme }) {
getInitialValueInEffect: true, getInitialValueInEffect: true,
}); });
const { setInitialPackageAttributes } = usePackageAttributesStore();
useEffect(() => {
setInitialPackageAttributes(props.packageAttributes);
}, []);
const toggleColorScheme = (value?: ColorScheme) => const toggleColorScheme = (value?: ColorScheme) =>
setColorScheme(value || (colorScheme === 'dark' ? 'light' : 'dark')); setColorScheme(value || (colorScheme === 'dark' ? 'light' : 'dark'));
@@ -111,6 +125,7 @@ function App(this: any, props: AppProps & { colorScheme: ColorScheme }) {
App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => ({ App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => ({
colorScheme: getCookie('color-scheme', ctx) || 'light', colorScheme: getCookie('color-scheme', ctx) || 'light',
packageAttributes: getServiceSidePackageAttributes(),
}); });
export default appWithTranslation(App); export default appWithTranslation(App);

View File

@@ -1,15 +1,15 @@
import { getCookie, setCookie } from 'cookies-next'; import { getCookie, setCookie } from 'cookies-next';
import fs from 'fs';
import { GetServerSidePropsContext } from 'next'; import { GetServerSidePropsContext } from 'next';
import fs from 'fs'; import { LoadConfigComponent } from '../components/Config/LoadConfig';
import { Dashboard } from '../components/Dashboard/Dashboard'; import { Dashboard } from '../components/Dashboard/Dashboard';
import Layout from '../components/layout/Layout'; import Layout from '../components/layout/Layout';
import { useInitConfig } from '../config/init'; import { useInitConfig } from '../config/init';
import { getFrontendConfig } from '../tools/config/getFrontendConfig'; import { getFrontendConfig } from '../tools/config/getFrontendConfig';
import { getServerSideTranslations } from '../tools/getServerSideTranslations'; import { getServerSideTranslations } from '../tools/server/getServerSideTranslations';
import { dashboardNamespaces } from '../tools/translation-namespaces'; import { dashboardNamespaces } from '../tools/server/translation-namespaces';
import { DashboardServerSideProps } from '../types/dashboardPageType'; import { DashboardServerSideProps } from '../types/dashboardPageType';
import { LoadConfigComponent } from '../components/Config/LoadConfig';
export async function getServerSideProps({ export async function getServerSideProps({
req, req,
@@ -47,11 +47,14 @@ export async function getServerSideProps({
} }
const translations = await getServerSideTranslations(req, res, dashboardNamespaces, locale); const translations = await getServerSideTranslations(req, res, dashboardNamespaces, locale);
const config = getFrontendConfig(configName as string); const config = getFrontendConfig(configName as string);
return { return {
props: { configName: configName as string, config, ...translations }, props: {
configName: configName as string,
config,
...translations,
},
}; };
} }

View File

@@ -8,7 +8,7 @@ import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { loginNamespaces } from '../tools/translation-namespaces'; import { loginNamespaces } from '../tools/server/translation-namespaces';
// TODO: Add links to the wiki articles about the login process. // TODO: Add links to the wiki articles about the login process.
export default function AuthenticationTitle() { export default function AuthenticationTitle() {

View File

@@ -1,14 +1,5 @@
import Dockerode from 'dockerode'; import Dockerode from 'dockerode';
import { Config, MatchingImages, ServiceType, tryMatchPort } from './types'; import { MatchingImages, ServiceType, tryMatchPort } from './types';
async function MatchIcon(name: string) {
const res = await fetch(
`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${name
.replace(/\s+/g, '-')
.toLowerCase()}.png`
);
return res.ok ? res.url : '/imgs/favicon/favicon.png';
}
function tryMatchType(imageName: string): ServiceType { function tryMatchType(imageName: string): ServiceType {
// Try to find imageName inside MatchingImages // Try to find imageName inside MatchingImages

View File

@@ -0,0 +1,15 @@
import create from 'zustand';
import { ServerSidePackageAttributesType } from '../../server/getPackageVersion';
interface PackageAttributesState {
attributes: ServerSidePackageAttributesType;
setInitialPackageAttributes: (attributes: ServerSidePackageAttributesType) => void;
}
export const usePackageAttributesStore = create<PackageAttributesState>((set) => ({
attributes: { packageVersion: undefined, environment: 'test' },
setInitialPackageAttributes(attributes) {
set((state) => ({ ...state, attributes }));
},
}));

View File

@@ -0,0 +1,14 @@
const getServerPackageVersion = (): string | undefined => process.env.npm_package_version;
const getServerNodeEnvironment = (): 'development' | 'production' | 'test' =>
process.env.NODE_ENV;
export const getServiceSidePackageAttributes = (): ServerSidePackageAttributesType => ({
packageVersion: getServerPackageVersion(),
environment: getServerNodeEnvironment(),
});
export type ServerSidePackageAttributesType = {
packageVersion: string | undefined;
environment: 'development' | 'production' | 'test';
};

View File

@@ -1,4 +1,5 @@
import { SSRConfig } from 'next-i18next'; import { SSRConfig } from 'next-i18next';
import { ConfigType } from './config'; import { ConfigType } from './config';
export type DashboardServerSideProps = { export type DashboardServerSideProps = {

View File

@@ -5,9 +5,15 @@ interface DashDotGraphProps {
graph: GraphType; graph: GraphType;
isCompact: boolean; isCompact: boolean;
dashDotUrl: string; dashDotUrl: string;
usePercentages: boolean;
} }
export const DashDotGraph = ({ graph, isCompact, dashDotUrl }: DashDotGraphProps) => { export const DashDotGraph = ({
graph,
isCompact,
dashDotUrl,
usePercentages,
}: DashDotGraphProps) => {
const { classes } = useStyles(); const { classes } = useStyles();
return ( return (
<Stack <Stack
@@ -25,13 +31,18 @@ export const DashDotGraph = ({ graph, isCompact, dashDotUrl }: DashDotGraphProps
className={classes.iframe} className={classes.iframe}
key={graph.name} key={graph.name}
title={graph.name} title={graph.name}
src={useIframeSrc(dashDotUrl, graph, isCompact)} src={useIframeSrc(dashDotUrl, graph, isCompact, usePercentages)}
/> />
</Stack> </Stack>
); );
}; };
const useIframeSrc = (dashDotUrl: string, graph: GraphType, isCompact: boolean) => { const useIframeSrc = (
dashDotUrl: string,
graph: GraphType,
isCompact: boolean,
usePercentages: boolean
) => {
const { colorScheme, colors, radius } = useMantineTheme(); const { colorScheme, colors, radius } = useMantineTheme();
const surface = (colorScheme === 'dark' ? colors.dark[7] : colors.gray[0]).substring(1); // removes # from hex value const surface = (colorScheme === 'dark' ? colors.dark[7] : colors.gray[0]).substring(1); // removes # from hex value
@@ -45,7 +56,8 @@ const useIframeSrc = (dashDotUrl: string, graph: GraphType, isCompact: boolean)
`&surface=${surface}` + `&surface=${surface}` +
`&gap=${isCompact ? 10 : 5}` + `&gap=${isCompact ? 10 : 5}` +
`&innerRadius=${radius.lg}` + `&innerRadius=${radius.lg}` +
`&multiView=${graph.isMultiView}` `&multiView=${graph.isMultiView}` +
`&showPercentage=${usePercentages ? 'true' : 'false'}`
); );
}; };

View File

@@ -25,6 +25,10 @@ const definition = defineWidget({
type: 'switch', type: 'switch',
defaultValue: true, defaultValue: true,
}, },
usePercentages: {
type: 'switch',
defaultValue: false,
},
graphs: { graphs: {
type: 'multi-select', type: 'multi-select',
defaultValue: ['cpu', 'memory'], defaultValue: ['cpu', 'memory'],
@@ -88,6 +92,8 @@ function DashDotTile({ widget }: DashDotTileProps) {
const isCompactNetworkVisible = graphs?.some((g) => g.id === 'network' && isCompact); const isCompactNetworkVisible = graphs?.some((g) => g.id === 'network' && isCompact);
const usePercentages = widget?.properties.usePercentages ?? false;
const displayedGraphs = graphs?.filter( const displayedGraphs = graphs?.filter(
(g) => !isCompact || !['network', 'storage'].includes(g.id) (g) => !isCompact || !['network', 'storage'].includes(g.id)
); );
@@ -109,6 +115,7 @@ function DashDotTile({ widget }: DashDotTileProps) {
graph={graph} graph={graph}
dashDotUrl={dashDotUrl} dashDotUrl={dashDotUrl}
isCompact={isCompact} isCompact={isCompact}
usePercentages={usePercentages}
/> />
))} ))}
</Group> </Group>

View File

@@ -24,7 +24,7 @@ import {
IconUpload, IconUpload,
} from '@tabler/icons'; } from '@tabler/icons';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { calculateETA } from '../../tools/calculateEta'; import { calculateETA } from '../../tools/client/calculateEta';
import { humanFileSize } from '../../tools/humanFileSize'; import { humanFileSize } from '../../tools/humanFileSize';
import { AppType } from '../../types/app'; import { AppType } from '../../types/app';

View File

@@ -1,3 +1,4 @@
import { TorrentState } from '@ctrl/shared-torrent';
import { import {
Badge, Badge,
Center, Center,
@@ -122,7 +123,14 @@ function TorrentTile({ widget }: TorrentTileProps) {
); );
} }
const torrents = data.apps.flatMap((app) => (app.type === 'torrent' ? app.torrents : [])); const torrents = data.apps
.flatMap((app) => (app.type === 'torrent' ? app.torrents : []))
.filter((torrent) => (widget.properties.displayCompletedTorrents ? true : !torrent.isCompleted))
.filter((torrent) =>
widget.properties.displayStaleTorrents
? true
: torrent.isCompleted || torrent.downloadSpeed > 0
);
const difference = new Date().getTime() - dataUpdatedAt; const difference = new Date().getTime() - dataUpdatedAt;
const duration = dayjs.duration(difference, 'ms'); const duration = dayjs.duration(difference, 'ms');

View File

@@ -20,7 +20,7 @@ import { useTranslation } from 'next-i18next';
import { FunctionComponent, useState } from 'react'; import { FunctionComponent, useState } from 'react';
import { useGetUsenetHistory } from '../../hooks/widgets/dashDot/api'; import { useGetUsenetHistory } from '../../hooks/widgets/dashDot/api';
import { humanFileSize } from '../../tools/humanFileSize'; import { humanFileSize } from '../../tools/humanFileSize';
import { parseDuration } from '../../tools/parseDuration'; import { parseDuration } from '../../tools/client/parseDuration';
dayjs.extend(duration); dayjs.extend(duration);