diff --git a/public/locales/en/layout/manage.json b/public/locales/en/layout/manage.json
index bcb6b8928..1af1c5bf1 100644
--- a/public/locales/en/layout/manage.json
+++ b/public/locales/en/layout/manage.json
@@ -21,6 +21,12 @@
"discord": "Community Discord",
"contribute": "Contribute"
}
+ },
+ "tools": {
+ "title": "Tools",
+ "items": {
+ "docker": "Docker"
+ }
}
}
}
\ No newline at end of file
diff --git a/public/locales/en/tools/docker.json b/public/locales/en/tools/docker.json
new file mode 100644
index 000000000..548af472e
--- /dev/null
+++ b/public/locales/en/tools/docker.json
@@ -0,0 +1,8 @@
+{
+ "title": "Docker",
+ "alerts": {
+ "notConfigured": {
+ "text": "Your Homarr instance does not have Docker configured. Please check the documentation on how to set up the integration."
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/layout/Templates/ManageLayout.tsx b/src/components/layout/Templates/ManageLayout.tsx
index 5634ee10a..5cdf063d4 100644
--- a/src/components/layout/Templates/ManageLayout.tsx
+++ b/src/components/layout/Templates/ManageLayout.tsx
@@ -16,12 +16,14 @@ import { useDisclosure } from '@mantine/hooks';
import {
IconBook2,
IconBrandDiscord,
+ IconBrandDocker,
IconBrandGithub,
IconGitFork,
IconHome,
IconLayoutDashboard,
IconMailForward,
IconQuestionMark,
+ IconTool,
IconUser,
IconUsers,
TablerIconsProps,
@@ -173,7 +175,7 @@ const CustomNavigationLink = forwardRef<
return (
}>
- {Object.entries(navigationLink.items).map(([itemName, item]) => {
+ {Object.entries(navigationLink.items).map(([itemName, item], index) => {
const commonItemProps = {
label: t(`navigation.${name}.items.${itemName}`),
icon: ,
@@ -183,10 +185,18 @@ const CustomNavigationLink = forwardRef<
const matchesActive = router.pathname.endsWith(item.href);
if (item.href.startsWith('http')) {
- return ;
+ return (
+
+ );
}
- return ;
+ return ;
})}
);
@@ -223,28 +233,38 @@ const navigationLinks: NavigationLinks = {
},
},
},
+ tools: {
+ icon: IconTool,
+ onlyAdmin: true,
+ items: {
+ docker: {
+ icon: IconBrandDocker,
+ href: '/manage/tools/docker',
+ },
+ },
+ },
help: {
icon: IconQuestionMark,
items: {
documentation: {
icon: IconBook2,
href: 'https://homarr.dev/docs/about',
- target: '_blank'
+ target: '_blank',
},
report: {
icon: IconBrandGithub,
href: 'https://github.com/ajnart/homarr/issues/new/choose',
- target: '_blank'
+ target: '_blank',
},
discord: {
icon: IconBrandDiscord,
href: 'https://discord.com/invite/aCsmEV5RgA',
- target: '_blank'
+ target: '_blank',
},
contribute: {
icon: IconGitFork,
href: 'https://github.com/ajnart/homarr',
- target: '_blank'
+ target: '_blank',
},
},
},
diff --git a/src/pages/docker.tsx b/src/pages/manage/tools/docker.tsx
similarity index 52%
rename from src/pages/docker.tsx
rename to src/pages/manage/tools/docker.tsx
index 2dd0700db..6399d06e6 100644
--- a/src/pages/docker.tsx
+++ b/src/pages/manage/tools/docker.tsx
@@ -1,8 +1,10 @@
-import { Stack } from '@mantine/core';
+import { Alert, Stack, Title } from '@mantine/core';
+import { IconInfoCircle } from '@tabler/icons-react';
import { ContainerInfo } from 'dockerode';
-import { GetServerSideProps } from 'next';
+import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import { useState } from 'react';
-import { MainLayout } from '~/components/layout/Templates/MainLayout';
+import { useTranslation } from 'next-i18next';
+import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
import { env } from '~/env';
import ContainerActionBar from '~/modules/Docker/ContainerActionBar';
import DockerTable from '~/modules/Docker/DockerTable';
@@ -11,27 +13,45 @@ import { getServerSideTranslations } from '~/tools/server/getServerSideTranslati
import { boardNamespaces } from '~/tools/server/translation-namespaces';
import { api } from '~/utils/api';
-export default function DockerPage() {
+export default function DockerPage({
+ dockerIsConfigured,
+}: InferGetServerSidePropsType) {
const [selection, setSelection] = useState([]);
- const { data, refetch, isRefetching } = api.docker.containers.useQuery();
+ const { data, refetch, isRefetching } = api.docker.containers.useQuery(undefined, {
+ enabled: dockerIsConfigured,
+ });
+
+ const { t } = useTranslation('tools/docker');
const reload = () => {
refetch();
setSelection([]);
};
+ if (!dockerIsConfigured) {
+ return (
+
+ {t('title')}
+ } color="blue">
+ {t('alerts.notConfigured.text')}
+
+
+ );
+ }
+
return (
-
+
-
+
);
}
export const getServerSideProps: GetServerSideProps = async ({ locale, req, res }) => {
- if (!env.DOCKER_HOST || !env.DOCKER_PORT) return { notFound: true };
+ const dockerIsConfigured = env.DOCKER_HOST !== undefined;
+
const session = await getServerAuthSession({ req, res });
if (!session?.user.isAdmin) {
return {
@@ -39,9 +59,15 @@ export const getServerSideProps: GetServerSideProps = async ({ locale, req, res
};
}
- const translations = await getServerSideTranslations(boardNamespaces, locale, req, res);
+ const translations = await getServerSideTranslations(
+ [...boardNamespaces, 'layout/manage', 'tools/docker'],
+ locale,
+ req,
+ res
+ );
return {
props: {
+ dockerIsConfigured: dockerIsConfigured,
...translations,
},
};