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')}
-
- }
- variant="default"
- >
- GitHub
-
- }
- variant="default"
- >
- Documentation
-
- }
- variant="default"
- >
- Discord
-
-
+
+
+ }
+ variant="default"
+ fullWidth
+ >
+ GitHub
+
+
+
+ }
+ variant="default"
+ fullWidth
+ >
+ Documentation
+
+
+
+
+ }
+ variant="default"
+ fullWidth
+ >
+ Discord
+
+
+
);
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 };