feat: add error page for docker tools page (#1655)

This commit is contained in:
Meier Lukas
2024-12-15 12:34:42 +01:00
committed by GitHub
parent 2ef36366ec
commit 60311918ce
4 changed files with 84 additions and 37 deletions

View File

@@ -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 (
<Center>
<Stack align="center">
<IconShipOff size={48} stroke={1.5} />
<Stack align="center" gap="xs">
<Text size="lg" fw={500}>
{t("docker.error.internalServerError")}
</Text>
<Anchor size="sm" component={Link} href="/manage/tools/logs">
{t("common.action.checkLogs")}
</Anchor>
</Stack>
</Stack>
</Center>
);
}

View File

@@ -5,6 +5,7 @@ import type { Container } from "dockerode";
import { db, like, or } from "@homarr/db"; import { db, like, or } from "@homarr/db";
import { icons } from "@homarr/db/schema/sqlite"; import { icons } from "@homarr/db/schema/sqlite";
import type { DockerContainerState } from "@homarr/definitions"; import type { DockerContainerState } from "@homarr/definitions";
import { logger } from "@homarr/log";
import { createCacheChannel } from "@homarr/redis"; import { createCacheChannel } from "@homarr/redis";
import { z } from "@homarr/validation"; import { z } from "@homarr/validation";
@@ -17,42 +18,60 @@ const dockerCache = createCacheChannel<{
export const dockerRouter = createTRPCRouter({ export const dockerRouter = createTRPCRouter({
getContainers: permissionRequiredProcedure.requiresPermission("admin").query(async () => { getContainers: permissionRequiredProcedure.requiresPermission("admin").query(async () => {
const { timestamp, data } = await dockerCache.consumeAsync(async () => { const result = await dockerCache
const dockerInstances = DockerSingleton.getInstance(); .consumeAsync(async () => {
const containers = await Promise.all( const dockerInstances = DockerSingleton.getInstance();
// Return all the containers of all the instances into only one item const containers = await Promise.all(
dockerInstances.map(({ instance, host: key }) => // Return all the containers of all the instances into only one item
instance.listContainers({ all: true }).then((containers) => dockerInstances.map(({ instance, host: key }) =>
containers.map((container) => ({ instance.listContainers({ all: true }).then((containers) =>
...container, containers.map((container) => ({
instance: key, ...container,
})), instance: key,
})),
),
), ),
), ).then((containers) => containers.flat());
).then((containers) => containers.flat());
const extractImage = (container: Docker.ContainerInfo) => const extractImage = (container: Docker.ContainerInfo) =>
container.Image.split("/").at(-1)?.split(":").at(0) ?? ""; container.Image.split("/").at(-1)?.split(":").at(0) ?? "";
const likeQueries = containers.map((container) => like(icons.name, `%${extractImage(container)}%`)); const likeQueries = containers.map((container) => like(icons.name, `%${extractImage(container)}%`));
const dbIcons = const dbIcons =
likeQueries.length >= 1 likeQueries.length >= 1
? await db.query.icons.findMany({ ? await db.query.icons.findMany({
where: or(...likeQueries), where: or(...likeQueries),
}) })
: []; : [];
return { return {
containers: containers.map((container) => ({ containers: containers.map((container) => ({
...container, ...container,
iconUrl: iconUrl:
dbIcons.find((icon) => { dbIcons.find((icon) => {
const extractedImage = extractImage(container); const extractedImage = extractImage(container);
if (!extractedImage) return false; if (!extractedImage) return false;
return icon.name.toLowerCase().includes(extractedImage.toLowerCase()); return icon.name.toLowerCase().includes(extractedImage.toLowerCase());
})?.url ?? null, })?.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 { return {
containers: sanitizeContainers(data.containers), containers: sanitizeContainers(data.containers),

View File

@@ -678,6 +678,7 @@
"previous": "Previous", "previous": "Previous",
"next": "Next", "next": "Next",
"checkoutDocs": "Check out the documentation", "checkoutDocs": "Check out the documentation",
"checkLogs": "Check logs for more details",
"tryAgain": "Try again", "tryAgain": "Try again",
"loading": "Loading" "loading": "Loading"
}, },
@@ -1309,9 +1310,6 @@
"description": "Click <here></here> to create a new app" "description": "Click <here></here> to create a new app"
}, },
"error": { "error": {
"action": {
"logs": "Check logs for more details"
},
"noIntegration": "No integration selected", "noIntegration": "No integration selected",
"noData": "No integration data available" "noData": "No integration data available"
}, },
@@ -2305,6 +2303,9 @@
} }
} }
} }
},
"error": {
"internalServerError": "Failed to fetch Docker containers"
} }
}, },
"permission": { "permission": {

View File

@@ -23,7 +23,7 @@ export const BaseWidgetError = (props: BaseWidgetErrorProps) => {
<Text ta="center">{translateIfNecessary(t, props.message)}</Text> <Text ta="center">{translateIfNecessary(t, props.message)}</Text>
{props.showLogsLink && ( {props.showLogsLink && (
<Anchor component={Link} href="/manage/tools/logs" target="_blank" ta="center" size="sm"> <Anchor component={Link} href="/manage/tools/logs" target="_blank" ta="center" size="sm">
{t("widget.common.error.action.logs")} {t("common.action.checkLogs")}
</Anchor> </Anchor>
)} )}
</Stack> </Stack>