feat: add pi hole summary integration (#521)
* feat: add pi hole summary integration * feat: add pi hole summary widget * fix: type issues with integrations and integrationIds * feat: add middleware for integrations and improve cache redis channel * feat: add error boundary for widgets * fix: broken lock file * fix: format format issues * fix: typecheck issue * fix: deepsource issues * fix: widget sandbox without error boundary * chore: address pull request feedback * chore: remove todo comment and created issue * fix: format issues * fix: deepsource issue
This commit is contained in:
36
packages/widgets/src/errors/base-component.tsx
Normal file
36
packages/widgets/src/errors/base-component.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import Link from "next/link";
|
||||
import { Anchor, Button, Stack, Text } from "@mantine/core";
|
||||
|
||||
import type { stringOrTranslation } from "@homarr/translation";
|
||||
import { translateIfNecessary } from "@homarr/translation";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import type { TablerIcon } from "@homarr/ui";
|
||||
|
||||
interface BaseWidgetErrorProps {
|
||||
icon: TablerIcon;
|
||||
message: stringOrTranslation;
|
||||
showLogsLink?: boolean;
|
||||
onRetry: () => void;
|
||||
}
|
||||
|
||||
export const BaseWidgetError = (props: BaseWidgetErrorProps) => {
|
||||
const t = useI18n();
|
||||
|
||||
return (
|
||||
<Stack h="100%" align="center" justify="center" gap="md">
|
||||
<props.icon size={40} />
|
||||
<Stack gap={0}>
|
||||
<Text ta="center">{translateIfNecessary(t, props.message)}</Text>
|
||||
{props.showLogsLink && (
|
||||
<Anchor component={Link} href="/manage/tools/logs" target="_blank" ta="center" size="sm">
|
||||
{t("widget.common.error.action.logs")}
|
||||
</Anchor>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<Button onClick={props.onRetry} size="sm" variant="light">
|
||||
{t("common.action.tryAgain")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
11
packages/widgets/src/errors/base.ts
Normal file
11
packages/widgets/src/errors/base.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { TablerIcon } from "@tabler/icons-react";
|
||||
|
||||
import type { stringOrTranslation } from "@homarr/translation";
|
||||
|
||||
export abstract class ErrorBoundaryError extends Error {
|
||||
public abstract getErrorBoundaryData(): {
|
||||
icon: TablerIcon;
|
||||
message: stringOrTranslation;
|
||||
showLogsLink: boolean;
|
||||
};
|
||||
}
|
||||
42
packages/widgets/src/errors/component.tsx
Normal file
42
packages/widgets/src/errors/component.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useMemo } from "react";
|
||||
import { IconExclamationCircle } from "@tabler/icons-react";
|
||||
import { TRPCClientError } from "@trpc/client";
|
||||
import type { DefaultErrorData } from "@trpc/server/unstable-core-do-not-import";
|
||||
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
|
||||
import { widgetImports } from "..";
|
||||
import { ErrorBoundaryError } from "./base";
|
||||
import { BaseWidgetError } from "./base-component";
|
||||
|
||||
interface WidgetErrorProps {
|
||||
kind: WidgetKind;
|
||||
error: unknown;
|
||||
resetErrorBoundary: () => void;
|
||||
}
|
||||
|
||||
export const WidgetError = ({ error, resetErrorBoundary, kind }: WidgetErrorProps) => {
|
||||
const currentDefinition = useMemo(() => widgetImports[kind].definition, [kind]);
|
||||
|
||||
if (error instanceof ErrorBoundaryError) {
|
||||
return <BaseWidgetError {...error.getErrorBoundaryData()} onRetry={resetErrorBoundary} />;
|
||||
}
|
||||
|
||||
if (error instanceof TRPCClientError && "code" in error.data) {
|
||||
const errorData = error.data as DefaultErrorData;
|
||||
|
||||
if (!("errors" in currentDefinition && errorData.code in currentDefinition.errors)) return null;
|
||||
|
||||
const errorDefinition = currentDefinition.errors[errorData.code as keyof typeof currentDefinition.errors];
|
||||
|
||||
return <BaseWidgetError {...errorDefinition} onRetry={resetErrorBoundary} showLogsLink />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseWidgetError
|
||||
icon={IconExclamationCircle}
|
||||
message={(error as { toString: () => string }).toString()}
|
||||
onRetry={resetErrorBoundary}
|
||||
/>
|
||||
);
|
||||
};
|
||||
2
packages/widgets/src/errors/index.ts
Normal file
2
packages/widgets/src/errors/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./no-integration-selected";
|
||||
export * from "./base";
|
||||
19
packages/widgets/src/errors/no-integration-selected.tsx
Normal file
19
packages/widgets/src/errors/no-integration-selected.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { IconPlugX } from "@tabler/icons-react";
|
||||
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
|
||||
import { ErrorBoundaryError } from "./base";
|
||||
|
||||
export class NoIntegrationSelectedError extends ErrorBoundaryError {
|
||||
constructor() {
|
||||
super("No integration selected");
|
||||
}
|
||||
|
||||
public getErrorBoundaryData() {
|
||||
return {
|
||||
icon: IconPlugX,
|
||||
message: (t: TranslationFunction) => t("widget.common.error.noIntegration"),
|
||||
showLogsLink: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user