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:
@@ -2,7 +2,7 @@ import { useCallback } from "react";
|
||||
|
||||
import { createId } from "@homarr/db/client";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import type { BoardItemAdvancedOptions, BoardItemIntegration } from "@homarr/validation";
|
||||
import type { BoardItemAdvancedOptions } from "@homarr/validation";
|
||||
|
||||
import type { EmptySection, Item } from "~/app/[locale]/boards/_types";
|
||||
import { useUpdateBoard } from "~/app/[locale]/boards/(content)/_client";
|
||||
@@ -38,7 +38,7 @@ interface UpdateItemAdvancedOptions {
|
||||
|
||||
interface UpdateItemIntegrations {
|
||||
itemId: string;
|
||||
newIntegrations: BoardItemIntegration[];
|
||||
newIntegrations: string[];
|
||||
}
|
||||
|
||||
interface CreateItem {
|
||||
@@ -63,7 +63,7 @@ export const useItemActions = () => {
|
||||
options: {},
|
||||
width: 1,
|
||||
height: 1,
|
||||
integrations: [],
|
||||
integrationIds: [],
|
||||
advancedOptions: {
|
||||
customCssClasses: [],
|
||||
},
|
||||
@@ -157,7 +157,7 @@ export const useItemActions = () => {
|
||||
if (item.id !== itemId) return item;
|
||||
return {
|
||||
...item,
|
||||
...("integrations" in item ? { integrations: newIntegrations } : {}),
|
||||
...("integrationIds" in item ? { integrationIds: newIntegrations } : {}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
// Ignored because of gridstack attributes
|
||||
|
||||
import type { RefObject } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { ActionIcon, Card, Menu } from "@mantine/core";
|
||||
import { useElementSize } from "@mantine/hooks";
|
||||
import { IconDotsVertical, IconLayoutKanban, IconPencil, IconTrash } from "@tabler/icons-react";
|
||||
import { QueryErrorResetBoundary } from "@tanstack/react-query";
|
||||
import combineClasses from "clsx";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
||||
@@ -18,6 +20,7 @@ import {
|
||||
WidgetEditModal,
|
||||
widgetImports,
|
||||
} from "@homarr/widgets";
|
||||
import { WidgetError } from "@homarr/widgets/errors";
|
||||
|
||||
import type { Item } from "~/app/[locale]/boards/_types";
|
||||
import { useEditMode, useRequiredBoard } from "~/app/[locale]/boards/(content)/_context";
|
||||
@@ -104,22 +107,43 @@ const BoardItemContent = ({ item, ...dimensions }: ItemContentProps) => {
|
||||
if (!serverData?.isReady) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ItemMenu offset={4} item={newItem} />
|
||||
<Comp
|
||||
options={options as never}
|
||||
integrations={item.integrations}
|
||||
serverData={serverData?.data as never}
|
||||
isEditMode={isEditMode}
|
||||
boardId={board.id}
|
||||
itemId={item.id}
|
||||
{...dimensions}
|
||||
/>
|
||||
</>
|
||||
<QueryErrorResetBoundary>
|
||||
{({ reset }) => (
|
||||
<ErrorBoundary
|
||||
onReset={reset}
|
||||
fallbackRender={({ resetErrorBoundary, error }) => (
|
||||
<>
|
||||
<ItemMenu offset={4} item={newItem} resetErrorBoundary={resetErrorBoundary} />
|
||||
<WidgetError kind={item.kind} error={error as unknown} resetErrorBoundary={resetErrorBoundary} />
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<ItemMenu offset={4} item={newItem} />
|
||||
<Comp
|
||||
options={options as never}
|
||||
integrationIds={item.integrationIds}
|
||||
serverData={serverData?.data as never}
|
||||
isEditMode={isEditMode}
|
||||
boardId={board.id}
|
||||
itemId={item.id}
|
||||
{...dimensions}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
</QueryErrorResetBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
|
||||
const ItemMenu = ({
|
||||
offset,
|
||||
item,
|
||||
resetErrorBoundary,
|
||||
}: {
|
||||
offset: number;
|
||||
item: Item;
|
||||
resetErrorBoundary?: () => void;
|
||||
}) => {
|
||||
const refResetErrorBoundaryOnNextRender = useRef(false);
|
||||
const tItem = useScopedI18n("item");
|
||||
const t = useI18n();
|
||||
const { openModal } = useModalAction(WidgetEditModal);
|
||||
@@ -129,6 +153,14 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
|
||||
const { data: integrationData, isPending } = clientApi.integration.all.useQuery();
|
||||
const currentDefinition = useMemo(() => widgetImports[item.kind].definition, [item.kind]);
|
||||
|
||||
// Reset error boundary on next render if item has been edited
|
||||
useEffect(() => {
|
||||
if (refResetErrorBoundaryOnNextRender.current) {
|
||||
resetErrorBoundary?.();
|
||||
refResetErrorBoundaryOnNextRender.current = false;
|
||||
}
|
||||
}, [item, resetErrorBoundary]);
|
||||
|
||||
if (!isEditMode || isPending) return null;
|
||||
|
||||
const openEditModal = () => {
|
||||
@@ -137,9 +169,9 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
|
||||
value: {
|
||||
advancedOptions: item.advancedOptions,
|
||||
options: item.options,
|
||||
integrations: item.integrations,
|
||||
integrationIds: item.integrationIds,
|
||||
},
|
||||
onSuccessfulEdit: ({ options, integrations, advancedOptions }) => {
|
||||
onSuccessfulEdit: ({ options, integrationIds, advancedOptions }) => {
|
||||
updateItemOptions({
|
||||
itemId: item.id,
|
||||
newOptions: options,
|
||||
@@ -150,8 +182,9 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
|
||||
});
|
||||
updateItemIntegrations({
|
||||
itemId: item.id,
|
||||
newIntegrations: integrations,
|
||||
newIntegrations: integrationIds,
|
||||
});
|
||||
refResetErrorBoundaryOnNextRender.current = true;
|
||||
},
|
||||
integrationData: (integrationData ?? []).filter(
|
||||
(integration) =>
|
||||
|
||||
Reference in New Issue
Block a user