diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/error.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/error.tsx
new file mode 100644
index 000000000..4fe1ae7b0
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/error.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import Link from "next/link";
+import { Anchor, Center, Stack, Text } from "@mantine/core";
+import { IconShipOff } from "@tabler/icons-react";
+
+import { useI18n } from "@homarr/translation/client";
+
+export default function DockerErrorPage() {
+ const t = useI18n();
+
+ return (
+
+
+
+
+
+ {t("docker.error.internalServerError")}
+
+
+ {t("common.action.checkLogs")}
+
+
+
+
+ );
+}
diff --git a/packages/api/src/router/docker/docker-router.ts b/packages/api/src/router/docker/docker-router.ts
index 127573173..0559e6211 100644
--- a/packages/api/src/router/docker/docker-router.ts
+++ b/packages/api/src/router/docker/docker-router.ts
@@ -5,6 +5,7 @@ import type { Container } from "dockerode";
import { db, like, or } from "@homarr/db";
import { icons } from "@homarr/db/schema/sqlite";
import type { DockerContainerState } from "@homarr/definitions";
+import { logger } from "@homarr/log";
import { createCacheChannel } from "@homarr/redis";
import { z } from "@homarr/validation";
@@ -17,42 +18,60 @@ const dockerCache = createCacheChannel<{
export const dockerRouter = createTRPCRouter({
getContainers: permissionRequiredProcedure.requiresPermission("admin").query(async () => {
- const { timestamp, data } = await dockerCache.consumeAsync(async () => {
- const dockerInstances = DockerSingleton.getInstance();
- const containers = await Promise.all(
- // Return all the containers of all the instances into only one item
- dockerInstances.map(({ instance, host: key }) =>
- instance.listContainers({ all: true }).then((containers) =>
- containers.map((container) => ({
- ...container,
- instance: key,
- })),
+ const result = await dockerCache
+ .consumeAsync(async () => {
+ const dockerInstances = DockerSingleton.getInstance();
+ const containers = await Promise.all(
+ // Return all the containers of all the instances into only one item
+ dockerInstances.map(({ instance, host: key }) =>
+ instance.listContainers({ all: true }).then((containers) =>
+ containers.map((container) => ({
+ ...container,
+ instance: key,
+ })),
+ ),
),
- ),
- ).then((containers) => containers.flat());
-
- const extractImage = (container: Docker.ContainerInfo) =>
- container.Image.split("/").at(-1)?.split(":").at(0) ?? "";
- const likeQueries = containers.map((container) => like(icons.name, `%${extractImage(container)}%`));
- const dbIcons =
- likeQueries.length >= 1
- ? await db.query.icons.findMany({
- where: or(...likeQueries),
- })
- : [];
-
- return {
- containers: containers.map((container) => ({
- ...container,
- iconUrl:
- dbIcons.find((icon) => {
- const extractedImage = extractImage(container);
- if (!extractedImage) return false;
- return icon.name.toLowerCase().includes(extractedImage.toLowerCase());
- })?.url ?? null,
- })),
- };
- });
+ ).then((containers) => containers.flat());
+
+ const extractImage = (container: Docker.ContainerInfo) =>
+ container.Image.split("/").at(-1)?.split(":").at(0) ?? "";
+ const likeQueries = containers.map((container) => like(icons.name, `%${extractImage(container)}%`));
+ const dbIcons =
+ likeQueries.length >= 1
+ ? await db.query.icons.findMany({
+ where: or(...likeQueries),
+ })
+ : [];
+
+ return {
+ containers: containers.map((container) => ({
+ ...container,
+ iconUrl:
+ dbIcons.find((icon) => {
+ const extractedImage = extractImage(container);
+ if (!extractedImage) return false;
+ return icon.name.toLowerCase().includes(extractedImage.toLowerCase());
+ })?.url ?? null,
+ })),
+ };
+ })
+ .catch((error) => {
+ logger.error(error);
+ return {
+ isError: true,
+ error: error as unknown,
+ };
+ });
+
+ if ("isError" in result) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "An error occurred while fetching the containers",
+ cause: result.error,
+ });
+ }
+
+ const { data, timestamp } = result;
return {
containers: sanitizeContainers(data.containers),
diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json
index 3ad0c7e85..f47b15da7 100644
--- a/packages/translation/src/lang/en.json
+++ b/packages/translation/src/lang/en.json
@@ -678,6 +678,7 @@
"previous": "Previous",
"next": "Next",
"checkoutDocs": "Check out the documentation",
+ "checkLogs": "Check logs for more details",
"tryAgain": "Try again",
"loading": "Loading"
},
@@ -1308,9 +1309,6 @@
"description": "Click to create a new app"
},
"error": {
- "action": {
- "logs": "Check logs for more details"
- },
"noIntegration": "No integration selected",
"noData": "No integration data available"
},
@@ -2301,6 +2299,9 @@
}
}
}
+ },
+ "error": {
+ "internalServerError": "Failed to fetch Docker containers"
}
},
"permission": {
diff --git a/packages/widgets/src/errors/base-component.tsx b/packages/widgets/src/errors/base-component.tsx
index 61f50389a..8c4e12b40 100644
--- a/packages/widgets/src/errors/base-component.tsx
+++ b/packages/widgets/src/errors/base-component.tsx
@@ -23,7 +23,7 @@ export const BaseWidgetError = (props: BaseWidgetErrorProps) => {
{translateIfNecessary(t, props.message)}
{props.showLogsLink && (
- {t("widget.common.error.action.logs")}
+ {t("common.action.checkLogs")}
)}