diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx index d2c1def97..764f91844 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx @@ -149,16 +149,20 @@ const createColumns = (t: TranslationFunction): MRT_ColumnDef[] }, ]; -export function DockerTable({ containers, timestamp }: RouterOutputs["docker"]["getContainers"]) { +interface DockerTableProps { + initialData?: RouterOutputs["docker"]["getContainers"]; +} + +export function DockerTable({ initialData }: DockerTableProps) { const t = useI18n(); const tDocker = useScopedI18n("docker"); - const { data } = clientApi.docker.getContainers.useQuery(undefined, { - initialData: { containers, timestamp }, + const { data, isFetching } = clientApi.docker.getContainers.useQuery(undefined, { + initialData, refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, }); - const relativeTime = useTimeAgo(data.timestamp); + const relativeTime = useTimeAgo(data?.timestamp ?? new Date()); const utils = clientApi.useUtils(); const { mutate, isPending } = clientApi.docker.invalidate.useMutation({ async onSuccess() { @@ -176,8 +180,11 @@ export function DockerTable({ containers, timestamp }: RouterOutputs["docker"][" }, }); + const containers = data?.containers ?? []; + const table = useTranslatedMantineReactTable({ - data: data.containers, + data: containers, + state: { isLoading: isFetching }, enableDensityToggle: false, enableColumnActions: false, enableColumnFilters: false, @@ -190,7 +197,7 @@ export function DockerTable({ containers, timestamp }: RouterOutputs["docker"][" enableBottomToolbar: false, positionGlobalFilter: "right", mantineSearchTextInputProps: { - placeholder: tDocker("table.search", { count: String(data.containers.length) }), + placeholder: tDocker("table.search", { count: String(containers.length) }), style: { minWidth: 300 }, autoFocus: true, }, diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/page.tsx index 1da3db128..97544314d 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/docker/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/page.tsx @@ -23,7 +23,7 @@ export default async function DockerPage() { {tDocker("title")} - + ); diff --git a/apps/nextjs/src/components/layout/header/docker-quick-access-modal.tsx b/apps/nextjs/src/components/layout/header/docker-quick-access-modal.tsx new file mode 100644 index 000000000..c9826b8a9 --- /dev/null +++ b/apps/nextjs/src/components/layout/header/docker-quick-access-modal.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { createModal } from "@homarr/modals"; + +import { DockerTable } from "~/app/[locale]/manage/tools/docker/docker-table"; + +export const DockerQuickAccessModal = createModal(() => ).withOptions({ + defaultTitle: (t) => t("docker.title"), + size: 1400, +}); diff --git a/apps/nextjs/src/components/layout/header/user.tsx b/apps/nextjs/src/components/layout/header/user.tsx index 6ec2cf306..af3d72121 100644 --- a/apps/nextjs/src/components/layout/header/user.tsx +++ b/apps/nextjs/src/components/layout/header/user.tsx @@ -3,6 +3,7 @@ import { UnstyledButton } from "@mantine/core"; import { api } from "@homarr/api/server"; import { auth } from "@homarr/auth/next"; +import { env } from "@homarr/docker/env"; import { CurrentUserAvatar } from "~/components/user-avatar"; import { UserAvatarMenu } from "~/components/user-avatar-menu"; @@ -11,9 +12,10 @@ import { UpdateIndicator } from "./update"; export const UserButton = async () => { const session = await auth(); const isAdmin = session?.user.permissions.includes("admin"); + const isDockerEnabled = isAdmin && env.ENABLE_DOCKER; const availableUpdatesPromise = isAdmin ? api.updateChecker.getAvailableUpdates() : undefined; return ( - + }> diff --git a/apps/nextjs/src/components/user-avatar-menu.tsx b/apps/nextjs/src/components/user-avatar-menu.tsx index 71ec2b218..cf6d013a2 100644 --- a/apps/nextjs/src/components/user-avatar-menu.tsx +++ b/apps/nextjs/src/components/user-avatar-menu.tsx @@ -5,7 +5,15 @@ import { useCallback, useEffect } from "react"; import { useRouter } from "next/navigation"; import { Center, Menu, Stack, Text, useMantineColorScheme } from "@mantine/core"; import { useHotkeys, useTimeout } from "@mantine/hooks"; -import { IconCheck, IconHome, IconLogin, IconLogout, IconSettings, IconTool } from "@tabler/icons-react"; +import { + IconBrandDocker, + IconCheck, + IconHome, + IconLogin, + IconLogout, + IconSettings, + IconTool, +} from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { signOut, useSession } from "@homarr/auth/client"; @@ -17,14 +25,16 @@ import { Link } from "@homarr/ui"; import { useAuthContext } from "~/app/[locale]/_client-providers/session"; import { CurrentColorSchemeCombobox } from "./color-scheme/current-color-scheme-combobox"; import { CurrentLanguageCombobox } from "./language/current-language-combobox"; +import { DockerQuickAccessModal } from "./layout/header/docker-quick-access-modal"; import { AvailableUpdatesMenuItem } from "./layout/header/update"; interface UserAvatarMenuProps { children: ReactNode; availableUpdatesPromise?: Promise; + isDockerEnabled?: boolean; } -export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatarMenuProps) => { +export const UserAvatarMenu = ({ children, availableUpdatesPromise, isDockerEnabled }: UserAvatarMenuProps) => { const t = useScopedI18n("common.userAvatar.menu"); const { toggleColorScheme } = useMantineColorScheme(); useHotkeys([[hotkeys.toggleColorScheme, toggleColorScheme]]); @@ -34,6 +44,7 @@ export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatar const { logoutUrl } = useAuthContext(); const { openModal } = useModalAction(LogoutModal); + const { openModal: openDockerModal } = useModalAction(DockerQuickAccessModal); const handleSignout = useCallback(async () => { await signOut({ @@ -80,6 +91,11 @@ export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatar }> {t("management")} + {isDockerEnabled && ( + } onClick={() => openDockerModal()}> + {t("docker")} + + )} )} diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 8b73d45b3..190a13733 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1468,6 +1468,7 @@ "switchToDarkMode": "Switch to dark mode", "switchToLightMode": "Switch to light mode", "management": "Management", + "docker": "Docker", "preferences": "Your preferences", "logout": "Logout", "login": "Login", @@ -4307,7 +4308,7 @@ "notStarted": "Not started" }, "name": { - "management": "Management", +"management": "Management", "dashboard": "Dashboard" } }