diff --git a/package.json b/package.json index 4fbd03992..23e874fde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homarr", - "version": "0.11.3", + "version": "0.11.4", "description": "Homarr - A homepage for your server.", "license": "MIT", "repository": { diff --git a/public/locales/en/modules/dashdot.json b/public/locales/en/modules/dashdot.json index 9268364f7..5d1e3e623 100644 --- a/public/locales/en/modules/dashdot.json +++ b/public/locales/en/modules/dashdot.json @@ -18,6 +18,9 @@ }, "url": { "label": "Dash. URL" + }, + "usePercentages": { + "label": "Display percentages" } } }, diff --git a/src/components/About/AboutModal.tsx b/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx similarity index 85% rename from src/components/About/AboutModal.tsx rename to src/components/Dashboard/Modals/AboutModal/AboutModal.tsx index 7381caf40..13b290a3f 100644 --- a/src/components/About/AboutModal.tsx +++ b/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx @@ -5,6 +5,7 @@ import { Button, createStyles, Divider, + Grid, Group, HoverCard, Modal, @@ -94,35 +95,45 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod {t('layout/modals/about:contact')} - - - - - + + + + + + + + + + + + ); diff --git a/src/components/Dashboard/Tiles/Apps/AppPing.tsx b/src/components/Dashboard/Tiles/Apps/AppPing.tsx index 3f2a8c925..d94cdc676 100644 --- a/src/components/Dashboard/Tiles/Apps/AppPing.tsx +++ b/src/components/Dashboard/Tiles/Apps/AppPing.tsx @@ -16,7 +16,7 @@ export const AppPing = ({ app }: AppPingProps) => { (config?.settings.customization.layout.enabledPing && app.network.enabledStatusChecker) ?? false; const { data, isLoading } = useQuery({ - queryKey: [`ping/${app.id}`], + queryKey: ['ping', { id: app.id, name: app.name }], queryFn: async () => { const response = await fetch(`/api/modules/ping?url=${encodeURI(app.url)}`); const isOk = app.network.okStatus.includes(response.status); diff --git a/src/components/layout/header/Header.tsx b/src/components/layout/header/Header.tsx index ec7a9f33d..f16d61209 100644 --- a/src/components/layout/header/Header.tsx +++ b/src/components/layout/header/Header.tsx @@ -16,17 +16,15 @@ export function Header(props: any) { const { classes: cardClasses } = useCardStyles(false); const { attributes } = usePackageAttributesStore(); - const [newVersionAvailable, setNewVersionAvailable] = useState(''); - useEffect(() => { - // Fetch Data here when component first mounted - fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => { - res.json().then((data) => { - if (data.tag_name > `v${attributes.packageVersion}`) { - setNewVersionAvailable(data.tag_name); - } - }); - }); - }, []); + const { isLoading, error, data } = useQuery({ + queryKey: ['github/latest'], + cacheTime: 1000 * 60 * 60 * 24, + staleTime: 1000 * 60 * 60 * 5, + queryFn: () => + fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => res.json()), + }); + const newVersionAvailable = + data?.tag_name > `v${attributes.packageVersion}` ? data?.tag_name : undefined; return ( @@ -38,7 +36,13 @@ export function Header(props: any) { - + diff --git a/src/components/layout/header/Search.tsx b/src/components/layout/header/Search.tsx index ba8d799d0..add4cb184 100644 --- a/src/components/layout/header/Search.tsx +++ b/src/components/layout/header/Search.tsx @@ -148,13 +148,14 @@ export function Search() { } = useQuery( ['overseerr', debounced], async () => { - if (debounced !== '' && selectedSearchEngine.value === 'overseerr' && debounced.length > 3) { - const res = await axios.get(`/api/modules/overseerr?query=${debounced}`); - return res.data.results ?? []; - } - return []; + const res = await axios.get(`/api/modules/overseerr?query=${debounced}`); + return res.data.results ?? []; }, { + enabled: + isOverseerrEnabled === true && + selectedSearchEngine.value === 'overseerr' && + debounced.length > 3, refetchOnWindowFocus: false, refetchOnMount: false, refetchInterval: false, diff --git a/src/components/layout/header/SettingsMenu.tsx b/src/components/layout/header/SettingsMenu.tsx index c525d1afd..e02fe5571 100644 --- a/src/components/layout/header/SettingsMenu.tsx +++ b/src/components/layout/header/SettingsMenu.tsx @@ -2,7 +2,7 @@ import { Badge, Button, Menu } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { IconInfoCircle, IconMenu2, IconSettings } from '@tabler/icons'; import { useTranslation } from 'next-i18next'; -import { AboutModal } from '../../About/AboutModal'; +import { AboutModal } from '../../Dashboard/Modals/AboutModal/AboutModal'; import { SettingsDrawer } from '../../Settings/SettingsDrawer'; import { useCardStyles } from '../useCardStyles'; import { ColorSchemeSwitch } from './SettingsMenu/ColorSchemeSwitch'; diff --git a/src/modules/Docker/DockerModule.tsx b/src/modules/Docker/DockerModule.tsx index 684178dd9..91de2247b 100644 --- a/src/modules/Docker/DockerModule.tsx +++ b/src/modules/Docker/DockerModule.tsx @@ -68,6 +68,15 @@ export default function DockerMenuButton(props: any) { position="right" size="full" title={} + styles={{ + drawer: { + display: 'flex', + flexDirection: 'column', + }, + body: { + minHeight: 0, + }, + }} > diff --git a/src/modules/Docker/DockerTable.tsx b/src/modules/Docker/DockerTable.tsx index 70f597d23..b2ba3c3c2 100644 --- a/src/modules/Docker/DockerTable.tsx +++ b/src/modules/Docker/DockerTable.tsx @@ -120,7 +120,7 @@ export default function DockerTable({ }); return ( - + {media.type === 'tvshow' && ( - + s{media.seasonNumber}e{media.episodeNumber} - {media.episodetitle} )} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index b372436a0..204db7734 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -68,7 +68,7 @@ function App( return ( <> - + diff --git a/src/tools/queryClient.ts b/src/tools/queryClient.ts index 6d46de591..b4d80818c 100644 --- a/src/tools/queryClient.ts +++ b/src/tools/queryClient.ts @@ -1,3 +1,10 @@ import { QueryClient } from '@tanstack/react-query'; -export const queryClient = new QueryClient(); +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 mins + cacheTime: 10 * 60 * 1000, // 10 mins + }, + }, +}); diff --git a/src/widgets/calendar/CalendarTile.tsx b/src/widgets/calendar/CalendarTile.tsx index fab26359a..f076160e3 100644 --- a/src/widgets/calendar/CalendarTile.tsx +++ b/src/widgets/calendar/CalendarTile.tsx @@ -54,6 +54,7 @@ function CalendarTile({ widget }: CalendarTileProps) { const { data: medias } = useQuery({ queryKey: ['calendar/medias', { month: month.getMonth(), year: month.getFullYear() }], + staleTime: 1000 * 60 * 60 * 5, queryFn: async () => (await ( await fetch( diff --git a/src/widgets/calendar/MediaList.tsx b/src/widgets/calendar/MediaList.tsx index 6fee8be0f..153e8608c 100644 --- a/src/widgets/calendar/MediaList.tsx +++ b/src/widgets/calendar/MediaList.tsx @@ -18,16 +18,10 @@ export const MediaList = ({ medias }: MediaListProps) => { return ( {mapMedias(medias.tvShows, SonarrMediaDisplay, lastMediaType === 'tv-show')} {mapMedias(medias.movies, RadarrMediaDisplay, lastMediaType === 'movie')} diff --git a/src/widgets/dashDot/DashDotGraph.tsx b/src/widgets/dashDot/DashDotGraph.tsx index 571a021c7..f64ba8140 100644 --- a/src/widgets/dashDot/DashDotGraph.tsx +++ b/src/widgets/dashDot/DashDotGraph.tsx @@ -5,9 +5,15 @@ interface DashDotGraphProps { graph: GraphType; isCompact: boolean; dashDotUrl: string; + usePercentages: boolean; } -export const DashDotGraph = ({ graph, isCompact, dashDotUrl }: DashDotGraphProps) => { +export const DashDotGraph = ({ + graph, + isCompact, + dashDotUrl, + usePercentages, +}: DashDotGraphProps) => { const { classes } = useStyles(); return ( ); }; -const useIframeSrc = (dashDotUrl: string, graph: GraphType, isCompact: boolean) => { +const useIframeSrc = ( + dashDotUrl: string, + graph: GraphType, + isCompact: boolean, + usePercentages: boolean +) => { const { colorScheme, colors, radius } = useMantineTheme(); 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}` + `&gap=${isCompact ? 10 : 5}` + `&innerRadius=${radius.lg}` + - `&multiView=${graph.isMultiView}` + `&multiView=${graph.isMultiView}` + + `&showPercentage=${usePercentages ? 'true' : 'false'}` ); }; diff --git a/src/widgets/dashDot/DashDotTile.tsx b/src/widgets/dashDot/DashDotTile.tsx index 89f83d353..07ff508fc 100644 --- a/src/widgets/dashDot/DashDotTile.tsx +++ b/src/widgets/dashDot/DashDotTile.tsx @@ -25,6 +25,10 @@ const definition = defineWidget({ type: 'switch', defaultValue: true, }, + usePercentages: { + type: 'switch', + defaultValue: false, + }, graphs: { type: 'multi-select', defaultValue: ['cpu', 'memory'], @@ -88,6 +92,8 @@ function DashDotTile({ widget }: DashDotTileProps) { const isCompactNetworkVisible = graphs?.some((g) => g.id === 'network' && isCompact); + const usePercentages = widget?.properties.usePercentages ?? false; + const displayedGraphs = graphs?.filter( (g) => !isCompact || !['network', 'storage'].includes(g.id) ); @@ -109,6 +115,7 @@ function DashDotTile({ widget }: DashDotTileProps) { graph={graph} dashDotUrl={dashDotUrl} isCompact={isCompact} + usePercentages={usePercentages} /> ))} diff --git a/src/widgets/weather/useWeatherForCity.ts b/src/widgets/weather/useWeatherForCity.ts index fac773d66..3f21ebad6 100644 --- a/src/widgets/weather/useWeatherForCity.ts +++ b/src/widgets/weather/useWeatherForCity.ts @@ -11,12 +11,18 @@ export const useWeatherForCity = (cityName: string) => { data: city, isLoading, isError, - } = useQuery({ queryKey: ['weatherCity', { cityName }], queryFn: () => fetchCity(cityName) }); + } = useQuery({ + queryKey: ['weatherCity', { cityName }], + queryFn: () => fetchCity(cityName), + cacheTime: 1000 * 60 * 60 * 24, // the city is cached for 24 hours + staleTime: Infinity, // the city is never considered stale + }); const weatherQuery = useQuery({ queryKey: ['weather', { cityName }], queryFn: () => fetchWeather(city?.results[0]), enabled: !!city, - refetchInterval: 1000 * 60 * 5, // requests the weather every 5 minutes + cacheTime: 1000 * 60 * 60 * 6, // the weather is cached for 6 hours + staleTime: 1000 * 60 * 5, // the weather is considered stale after 5 minutes }); return { @@ -41,14 +47,14 @@ const fetchCity = async (cityName: string) => { * @param coordinates of the location the weather should be fetched * @returns weather of specified coordinates */ -const fetchWeather = async (coordinates?: Coordinates) => { - if (!coordinates) return; +async function fetchWeather(coordinates?: Coordinates) { + if (!coordinates) return null; const { longitude, latitude } = coordinates; const res = await fetch( `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min¤t_weather=true&timezone=Europe%2FLondon` ); // eslint-disable-next-line consistent-return return (await res.json()) as WeatherResponse; -}; +} type Coordinates = { latitude: number; longitude: number };