feat: docker widget (#2288)

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: homarr-crowdin[bot] <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
Yossi Hillali
2025-05-23 21:35:04 +03:00
committed by GitHub
parent 09f4e6785b
commit e1eda534da
19 changed files with 521 additions and 111 deletions

View File

@@ -6,7 +6,7 @@ import { translateIfNecessary } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client";
import type { TablerIcon } from "@homarr/ui";
interface BaseWidgetErrorProps {
export interface BaseWidgetErrorProps {
icon: TablerIcon;
message: stringOrTranslation;
showLogsLink?: boolean;

View File

@@ -1,5 +1,5 @@
import { useMemo } from "react";
import { IconExclamationCircle } from "@tabler/icons-react";
import { IconExclamationCircle, IconShield } from "@tabler/icons-react";
import { TRPCClientError } from "@trpc/client";
import type { DefaultErrorData } from "@trpc/server/unstable-core-do-not-import";
@@ -8,6 +8,7 @@ import type { WidgetKind } from "@homarr/definitions";
import type { WidgetDefinition } from "..";
import { widgetImports } from "..";
import { ErrorBoundaryError } from "./base";
import type { BaseWidgetErrorProps } from "./base-component";
import { BaseWidgetError } from "./base-component";
interface WidgetErrorProps {
@@ -23,28 +24,58 @@ export const WidgetError = ({ error, resetErrorBoundary, kind }: WidgetErrorProp
return <BaseWidgetError {...error.getErrorBoundaryData()} onRetry={resetErrorBoundary} />;
}
const commonFallbackError = (
const widgetTrpcErrorData = handleWidgetTrpcError(error, currentDefinition);
if (widgetTrpcErrorData) {
return <BaseWidgetError {...widgetTrpcErrorData} onRetry={resetErrorBoundary} />;
}
const trpcErrorData = handleCommonTrpcError(error);
if (trpcErrorData) {
return <BaseWidgetError {...trpcErrorData} onRetry={resetErrorBoundary} />;
}
return (
<BaseWidgetError
icon={IconExclamationCircle}
message={(error as { toString: () => string }).toString()}
onRetry={resetErrorBoundary}
/>
);
};
if (error instanceof TRPCClientError && "code" in error.data) {
const errorData = error.data as DefaultErrorData;
const handleWidgetTrpcError = (
error: unknown,
currentDefinition: WidgetDefinition,
): Omit<BaseWidgetErrorProps, "onRetry"> | null => {
if (!(error instanceof TRPCClientError && "code" in error.data)) return null;
if (!("errors" in currentDefinition)) return commonFallbackError;
const errorData = error.data as DefaultErrorData;
const errors: Exclude<WidgetDefinition["errors"], undefined> = currentDefinition.errors;
const errorDefinition = errors[errorData.code];
if (!("errors" in currentDefinition) || currentDefinition.errors === undefined) return null;
if (!errorDefinition) return commonFallbackError;
const errors: Exclude<WidgetDefinition["errors"], undefined> = currentDefinition.errors;
const errorDefinition = errors[errorData.code];
return (
<BaseWidgetError {...errorDefinition} onRetry={resetErrorBoundary} showLogsLink={!errorDefinition.hideLogsLink} />
);
if (!errorDefinition) return null;
return {
...errorDefinition,
showLogsLink: !errorDefinition.hideLogsLink,
};
};
const handleCommonTrpcError = (error: unknown): Omit<BaseWidgetErrorProps, "onRetry"> | null => {
if (!(error instanceof TRPCClientError && "code" in error.data)) return null;
const errorData = error.data as DefaultErrorData;
if (errorData.code === "UNAUTHORIZED" || errorData.code === "FORBIDDEN") {
return {
icon: IconShield,
message: "You don't have permission to access this widget",
showLogsLink: false,
};
}
return commonFallbackError;
return null;
};