Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f290328dd5 | ||
|
|
065b3de1db | ||
|
|
ea9dd716f0 | ||
|
|
4383da38ef |
No files matched your search
@@ -149,20 +149,16 @@ const createColumns = (t: TranslationFunction): MRT_ColumnDef<DockerContainer>[]
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
interface DockerTableProps {
|
export function DockerTable({ containers, timestamp }: RouterOutputs["docker"]["getContainers"]) {
|
||||||
initialData?: RouterOutputs["docker"]["getContainers"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DockerTable({ initialData }: DockerTableProps) {
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const tDocker = useScopedI18n("docker");
|
const tDocker = useScopedI18n("docker");
|
||||||
const { data, isFetching } = clientApi.docker.getContainers.useQuery(undefined, {
|
const { data } = clientApi.docker.getContainers.useQuery(undefined, {
|
||||||
initialData,
|
initialData: { containers, timestamp },
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnReconnect: false,
|
refetchOnReconnect: false,
|
||||||
});
|
});
|
||||||
const relativeTime = useTimeAgo(data?.timestamp ?? new Date());
|
const relativeTime = useTimeAgo(data.timestamp);
|
||||||
const utils = clientApi.useUtils();
|
const utils = clientApi.useUtils();
|
||||||
const { mutate, isPending } = clientApi.docker.invalidate.useMutation({
|
const { mutate, isPending } = clientApi.docker.invalidate.useMutation({
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
@@ -180,11 +176,8 @@ export function DockerTable({ initialData }: DockerTableProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const containers = data?.containers ?? [];
|
|
||||||
|
|
||||||
const table = useTranslatedMantineReactTable({
|
const table = useTranslatedMantineReactTable({
|
||||||
data: containers,
|
data: data.containers,
|
||||||
state: { isLoading: isFetching },
|
|
||||||
enableDensityToggle: false,
|
enableDensityToggle: false,
|
||||||
enableColumnActions: false,
|
enableColumnActions: false,
|
||||||
enableColumnFilters: false,
|
enableColumnFilters: false,
|
||||||
@@ -197,7 +190,7 @@ export function DockerTable({ initialData }: DockerTableProps) {
|
|||||||
enableBottomToolbar: false,
|
enableBottomToolbar: false,
|
||||||
positionGlobalFilter: "right",
|
positionGlobalFilter: "right",
|
||||||
mantineSearchTextInputProps: {
|
mantineSearchTextInputProps: {
|
||||||
placeholder: tDocker("table.search", { count: String(containers.length) }),
|
placeholder: tDocker("table.search", { count: String(data.containers.length) }),
|
||||||
style: { minWidth: 300 },
|
style: { minWidth: 300 },
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default async function DockerPage() {
|
|||||||
<DynamicBreadcrumb />
|
<DynamicBreadcrumb />
|
||||||
<Stack>
|
<Stack>
|
||||||
<Title order={1}>{tDocker("title")}</Title>
|
<Title order={1}>{tDocker("title")}</Title>
|
||||||
<DockerTable initialData={{ containers, timestamp }} />
|
<DockerTable containers={containers} timestamp={timestamp} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { createModal } from "@homarr/modals";
|
|
||||||
|
|
||||||
import { DockerTable } from "~/app/[locale]/manage/tools/docker/docker-table";
|
|
||||||
|
|
||||||
export const DockerQuickAccessModal = createModal<void>(() => <DockerTable />).withOptions({
|
|
||||||
defaultTitle: (t) => t("docker.title"),
|
|
||||||
size: 1400,
|
|
||||||
});
|
|
||||||
@@ -3,7 +3,6 @@ import { UnstyledButton } from "@mantine/core";
|
|||||||
|
|
||||||
import { api } from "@homarr/api/server";
|
import { api } from "@homarr/api/server";
|
||||||
import { auth } from "@homarr/auth/next";
|
import { auth } from "@homarr/auth/next";
|
||||||
import { env } from "@homarr/docker/env";
|
|
||||||
|
|
||||||
import { CurrentUserAvatar } from "~/components/user-avatar";
|
import { CurrentUserAvatar } from "~/components/user-avatar";
|
||||||
import { UserAvatarMenu } from "~/components/user-avatar-menu";
|
import { UserAvatarMenu } from "~/components/user-avatar-menu";
|
||||||
@@ -12,10 +11,9 @@ import { UpdateIndicator } from "./update";
|
|||||||
export const UserButton = async () => {
|
export const UserButton = async () => {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
const isAdmin = session?.user.permissions.includes("admin");
|
const isAdmin = session?.user.permissions.includes("admin");
|
||||||
const isDockerEnabled = isAdmin && env.ENABLE_DOCKER;
|
|
||||||
const availableUpdatesPromise = isAdmin ? api.updateChecker.getAvailableUpdates() : undefined;
|
const availableUpdatesPromise = isAdmin ? api.updateChecker.getAvailableUpdates() : undefined;
|
||||||
return (
|
return (
|
||||||
<UserAvatarMenu availableUpdatesPromise={availableUpdatesPromise} isDockerEnabled={isDockerEnabled}>
|
<UserAvatarMenu availableUpdatesPromise={availableUpdatesPromise}>
|
||||||
<UnstyledButton>
|
<UnstyledButton>
|
||||||
<Suspense fallback={<CurrentUserAvatar size="md" />}>
|
<Suspense fallback={<CurrentUserAvatar size="md" />}>
|
||||||
<UpdateIndicator availableUpdatesPromise={availableUpdatesPromise} disabled={!isAdmin}>
|
<UpdateIndicator availableUpdatesPromise={availableUpdatesPromise} disabled={!isAdmin}>
|
||||||
|
|||||||
@@ -5,15 +5,7 @@ import { useCallback, useEffect } from "react";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Center, Menu, Stack, Text, useMantineColorScheme } from "@mantine/core";
|
import { Center, Menu, Stack, Text, useMantineColorScheme } from "@mantine/core";
|
||||||
import { useHotkeys, useTimeout } from "@mantine/hooks";
|
import { useHotkeys, useTimeout } from "@mantine/hooks";
|
||||||
import {
|
import { IconCheck, IconHome, IconLogin, IconLogout, IconSettings, IconTool } from "@tabler/icons-react";
|
||||||
IconBrandDocker,
|
|
||||||
IconCheck,
|
|
||||||
IconHome,
|
|
||||||
IconLogin,
|
|
||||||
IconLogout,
|
|
||||||
IconSettings,
|
|
||||||
IconTool,
|
|
||||||
} from "@tabler/icons-react";
|
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { signOut, useSession } from "@homarr/auth/client";
|
import { signOut, useSession } from "@homarr/auth/client";
|
||||||
@@ -25,16 +17,14 @@ import { Link } from "@homarr/ui";
|
|||||||
import { useAuthContext } from "~/app/[locale]/_client-providers/session";
|
import { useAuthContext } from "~/app/[locale]/_client-providers/session";
|
||||||
import { CurrentColorSchemeCombobox } from "./color-scheme/current-color-scheme-combobox";
|
import { CurrentColorSchemeCombobox } from "./color-scheme/current-color-scheme-combobox";
|
||||||
import { CurrentLanguageCombobox } from "./language/current-language-combobox";
|
import { CurrentLanguageCombobox } from "./language/current-language-combobox";
|
||||||
import { DockerQuickAccessModal } from "./layout/header/docker-quick-access-modal";
|
|
||||||
import { AvailableUpdatesMenuItem } from "./layout/header/update";
|
import { AvailableUpdatesMenuItem } from "./layout/header/update";
|
||||||
|
|
||||||
interface UserAvatarMenuProps {
|
interface UserAvatarMenuProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
availableUpdatesPromise?: Promise<RouterOutputs["updateChecker"]["getAvailableUpdates"]>;
|
availableUpdatesPromise?: Promise<RouterOutputs["updateChecker"]["getAvailableUpdates"]>;
|
||||||
isDockerEnabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserAvatarMenu = ({ children, availableUpdatesPromise, isDockerEnabled }: UserAvatarMenuProps) => {
|
export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatarMenuProps) => {
|
||||||
const t = useScopedI18n("common.userAvatar.menu");
|
const t = useScopedI18n("common.userAvatar.menu");
|
||||||
const { toggleColorScheme } = useMantineColorScheme();
|
const { toggleColorScheme } = useMantineColorScheme();
|
||||||
useHotkeys([[hotkeys.toggleColorScheme, toggleColorScheme]]);
|
useHotkeys([[hotkeys.toggleColorScheme, toggleColorScheme]]);
|
||||||
@@ -44,7 +34,6 @@ export const UserAvatarMenu = ({ children, availableUpdatesPromise, isDockerEnab
|
|||||||
|
|
||||||
const { logoutUrl } = useAuthContext();
|
const { logoutUrl } = useAuthContext();
|
||||||
const { openModal } = useModalAction(LogoutModal);
|
const { openModal } = useModalAction(LogoutModal);
|
||||||
const { openModal: openDockerModal } = useModalAction(DockerQuickAccessModal);
|
|
||||||
|
|
||||||
const handleSignout = useCallback(async () => {
|
const handleSignout = useCallback(async () => {
|
||||||
await signOut({
|
await signOut({
|
||||||
@@ -91,11 +80,6 @@ export const UserAvatarMenu = ({ children, availableUpdatesPromise, isDockerEnab
|
|||||||
<Menu.Item component={Link} href="/manage" leftSection={<IconTool size="1rem" />}>
|
<Menu.Item component={Link} href="/manage" leftSection={<IconTool size="1rem" />}>
|
||||||
{t("management")}
|
{t("management")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
{isDockerEnabled && (
|
|
||||||
<Menu.Item leftSection={<IconBrandDocker size="1rem" />} onClick={() => openDockerModal()}>
|
|
||||||
{t("docker")}
|
|
||||||
</Menu.Item>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export const beszelSystemsRequestHandler = createCachedIntegrationRequestHandler
|
|||||||
});
|
});
|
||||||
return enriched;
|
return enriched;
|
||||||
},
|
},
|
||||||
cacheDuration: dayjs.duration(5, "seconds"),
|
cacheDuration: dayjs.duration(1, "second"),
|
||||||
fallbackToStaleOnError: true,
|
fallbackToStaleOnError: true,
|
||||||
retry: { attempts: 2, delayMs: 500 },
|
retry: { attempts: 2, delayMs: 500 },
|
||||||
queryKey: "beszelSystems",
|
queryKey: "beszelSystems",
|
||||||
@@ -104,19 +104,19 @@ export const beszelAlertsRequestHandler = createCachedIntegrationRequestHandler<
|
|||||||
});
|
});
|
||||||
return { alerts, history };
|
return { alerts, history };
|
||||||
},
|
},
|
||||||
cacheDuration: dayjs.duration(15, "seconds"),
|
cacheDuration: dayjs.duration(1, "second"),
|
||||||
fallbackToStaleOnError: true,
|
fallbackToStaleOnError: true,
|
||||||
retry: { attempts: 2, delayMs: 500 },
|
retry: { attempts: 2, delayMs: 500 },
|
||||||
queryKey: "beszelAlerts",
|
queryKey: "beszelAlerts",
|
||||||
});
|
});
|
||||||
|
|
||||||
const timePeriodConfig: Record<string, { type: string; perPage: number; cacheSeconds: number }> = {
|
const timePeriodConfig: Record<string, { type: string; perPage: number }> = {
|
||||||
"1m": { type: "1m", perPage: 60, cacheSeconds: 5 },
|
"1m": { type: "1m", perPage: 60 },
|
||||||
"1h": { type: "1m", perPage: 60, cacheSeconds: 60 },
|
"1h": { type: "1m", perPage: 60 },
|
||||||
"12h": { type: "10m", perPage: 72, cacheSeconds: 300 },
|
"12h": { type: "10m", perPage: 72 },
|
||||||
"24h": { type: "20m", perPage: 72, cacheSeconds: 600 },
|
"24h": { type: "20m", perPage: 72 },
|
||||||
"1w": { type: "1440m", perPage: 9, cacheSeconds: 1800 },
|
"1w": { type: "480m", perPage: 21 },
|
||||||
"30d": { type: "1440m", perPage: 32, cacheSeconds: 3600 },
|
"30d": { type: "480m", perPage: 90 },
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface BeszelStatsData {
|
export interface BeszelStatsData {
|
||||||
@@ -131,7 +131,7 @@ export const beszelStatsRequestHandler = createCachedIntegrationRequestHandler<
|
|||||||
>({
|
>({
|
||||||
async requestAsync(integration, input) {
|
async requestAsync(integration, input) {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
const config = timePeriodConfig[input.timePeriod] ?? { type: "1m", perPage: 60, cacheSeconds: 60 };
|
const config = timePeriodConfig[input.timePeriod] ?? { type: "1m", perPage: 60 };
|
||||||
const instance = await createIntegrationAsync(integration);
|
const instance = await createIntegrationAsync(integration);
|
||||||
const systemStats = await instance.getSystemStatsAsync(input.systemId, config.type, config.perPage);
|
const systemStats = await instance.getSystemStatsAsync(input.systemId, config.type, config.perPage);
|
||||||
const containerStats = input.includeDocker
|
const containerStats = input.includeDocker
|
||||||
@@ -147,13 +147,8 @@ export const beszelStatsRequestHandler = createCachedIntegrationRequestHandler<
|
|||||||
});
|
});
|
||||||
return { systemStats, containerStats };
|
return { systemStats, containerStats };
|
||||||
},
|
},
|
||||||
cacheDuration: dayjs.duration(60, "seconds"),
|
cacheDuration: dayjs.duration(1, "second"),
|
||||||
fallbackToStaleOnError: true,
|
fallbackToStaleOnError: true,
|
||||||
retry: { attempts: 2, delayMs: 500 },
|
retry: { attempts: 2, delayMs: 500 },
|
||||||
queryKey: "beszelStats",
|
queryKey: "beszelStats",
|
||||||
cacheDurationForInput(input) {
|
|
||||||
const config = timePeriodConfig[input.timePeriod];
|
|
||||||
if (!config) return undefined;
|
|
||||||
return dayjs.duration(config.cacheSeconds, "seconds");
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
@@ -1467,7 +1467,6 @@
|
|||||||
"switchToDarkMode": "Switch to dark mode",
|
"switchToDarkMode": "Switch to dark mode",
|
||||||
"switchToLightMode": "Switch to light mode",
|
"switchToLightMode": "Switch to light mode",
|
||||||
"management": "Management",
|
"management": "Management",
|
||||||
"docker": "Docker",
|
|
||||||
"preferences": "Your preferences",
|
"preferences": "Your preferences",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
@@ -4296,7 +4295,7 @@
|
|||||||
"notStarted": "Not started"
|
"notStarted": "Not started"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"management": "Management",
|
"management": "Management",
|
||||||
"dashboard": "Dashboard"
|
"dashboard": "Dashboard"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ export default function BeszelAlertsWidget({
|
|||||||
error: alertsError,
|
error: alertsError,
|
||||||
isPending,
|
isPending,
|
||||||
} = clientApi.widget.beszel.getAlerts.useQuery(alertsInput, {
|
} = clientApi.widget.beszel.getAlerts.useQuery(alertsInput, {
|
||||||
staleTime: 15_000,
|
staleTime: 10_000,
|
||||||
refetchInterval: 15_000,
|
gcTime: 48 * 60 * 60 * 1000,
|
||||||
|
refetchInterval: 10_000,
|
||||||
retry: false,
|
retry: false,
|
||||||
});
|
});
|
||||||
const utils = clientApi.useUtils();
|
const utils = clientApi.useUtils();
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ export default function BeszelSystemGridWidget({
|
|||||||
isPending,
|
isPending,
|
||||||
} = clientApi.widget.beszel.getSystems.useQuery(
|
} = clientApi.widget.beszel.getSystems.useQuery(
|
||||||
{ integrationIds },
|
{ integrationIds },
|
||||||
{ staleTime: 5_000, refetchInterval: 5_000, retry: false },
|
{ staleTime: 10_000, gcTime: 48 * 60 * 60 * 1000, refetchInterval: 10_000, retry: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
useBeszelSystemsSubscription(integrationIds, !isEditMode);
|
useBeszelSystemsSubscription(integrationIds, !isEditMode);
|
||||||
|
|||||||
@@ -67,7 +67,10 @@ export default function BeszelSystemStatsWidget({
|
|||||||
data: systemsResult = [],
|
data: systemsResult = [],
|
||||||
isPending: systemsPending,
|
isPending: systemsPending,
|
||||||
error: systemsError,
|
error: systemsError,
|
||||||
} = clientApi.widget.beszel.getSystems.useQuery({ integrationIds }, { staleTime: 30 * 1000, retry: false });
|
} = clientApi.widget.beszel.getSystems.useQuery(
|
||||||
|
{ integrationIds },
|
||||||
|
{ staleTime: 10_000, gcTime: 48 * 60 * 60 * 1000, retry: false },
|
||||||
|
);
|
||||||
|
|
||||||
const systems = useMemo(
|
const systems = useMemo(
|
||||||
() => systemsResult.flatMap((r) => r.systems.map((s) => ({ value: s.id, label: s.name }))),
|
() => systemsResult.flatMap((r) => r.systems.map((s) => ({ value: s.id, label: s.name }))),
|
||||||
@@ -100,8 +103,9 @@ export default function BeszelSystemStatsWidget({
|
|||||||
includeDocker: showDocker,
|
includeDocker: showDocker,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
staleTime: 30 * 1000,
|
staleTime: 10_000,
|
||||||
refetchInterval: isLive ? false : 5_000,
|
gcTime: 48 * 60 * 60 * 1000,
|
||||||
|
refetchInterval: isLive ? false : 10_000,
|
||||||
enabled: !isLive && systemReady && selectedSystem !== "",
|
enabled: !isLive && systemReady && selectedSystem !== "",
|
||||||
retry: false,
|
retry: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default function BeszelSystemTableWidget({
|
|||||||
isPending,
|
isPending,
|
||||||
} = clientApi.widget.beszel.getSystems.useQuery(
|
} = clientApi.widget.beszel.getSystems.useQuery(
|
||||||
{ integrationIds },
|
{ integrationIds },
|
||||||
{ staleTime: 5_000, refetchInterval: 5_000, retry: false },
|
{ staleTime: 10_000, gcTime: 48 * 60 * 60 * 1000, refetchInterval: 10_000, retry: false },
|
||||||
);
|
);
|
||||||
const size = getSizeConfig(width);
|
const size = getSizeConfig(width);
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const BeszelSystemStatsModal = createModal<BeszelSystemStatsModalProps>((
|
|||||||
timePeriod,
|
timePeriod,
|
||||||
includeDocker: false,
|
includeDocker: false,
|
||||||
},
|
},
|
||||||
{ staleTime: 30_000 },
|
{ staleTime: 10_000, gcTime: 48 * 60 * 60 * 1000, refetchInterval: 10_000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const mappers = useMemo(
|
const mappers = useMemo(
|
||||||
|
|||||||
Reference in new issue
Block a user