refactor: add request handlers for centralized cached requests (#1504)

* feat: add object base64 hash method

* chore: add script to add package

* feat: add request-handler package

* wip: add request handlers for all jobs and widget api procedures

* wip: remove errors shown in logs, add missing decryption for secrets in cached-request-job-handler

* wip: highly improve request handler, add request handlers for calendar, media-server, indexer-manager and more, add support for multiple inputs from job handler creator

* refactor: move media-server requests to request-handler, add invalidation logic for dns-hole and media requests

* refactor: remove unused integration item middleware

* feat: add invalidation to switch entity action of smart-home

* fix: lint issues

* chore: use integration-kind-by-category instead of union for request-handlers

* fix: build not working for tasks and websocket

* refactor: add more logs

* refactor: readd timestamp logic for diconnect status

* fix: lint and typecheck issue

* chore: address pull request feedback
This commit is contained in:
Meier Lukas
2024-11-23 17:16:44 +01:00
committed by GitHub
parent cdfb61fb28
commit 32ee9f3dcc
73 changed files with 1114 additions and 665 deletions

View File

@@ -18,16 +18,16 @@ import {
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { IconCircleFilled, IconClockPause, IconPlayerPlay, IconPlayerStop } from "@tabler/icons-react";
import dayjs from "dayjs";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { useIntegrationsWithInteractAccess } from "@homarr/auth/client";
import { useIntegrationConnected } from "@homarr/common";
import { integrationDefs } from "@homarr/definitions";
import type { TranslationFunction } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client";
import { widgetKind } from ".";
import type { widgetKind } from ".";
import type { WidgetComponentProps } from "../../definition";
import { NoIntegrationSelectedError } from "../../errors";
import TimerModal from "./TimerModal";
@@ -47,7 +47,6 @@ export default function DnsHoleControlsWidget({
const [summaries] = clientApi.widget.dnsHole.summary.useSuspenseQuery(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
{
@@ -61,21 +60,27 @@ export default function DnsHoleControlsWidget({
// Subscribe to summary updates
clientApi.widget.dnsHole.subscribeToSummary.useSubscription(
{
widgetKind,
integrationIds,
},
{
onData: (data) => {
utils.widget.dnsHole.summary.setData(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
(prevData) => {
if (!prevData) return undefined;
const newData = prevData.map((summary) =>
summary.integration.id === data.integration.id ? { ...summary, summary: data.summary } : summary,
summary.integration.id === data.integration.id
? {
integration: {
...summary.integration,
updatedAt: new Date(),
},
summary: data.summary,
}
: summary,
);
return newData;
@@ -90,14 +95,13 @@ export default function DnsHoleControlsWidget({
onSettled: (_, error, { integrationId }) => {
utils.widget.dnsHole.summary.setData(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
(prevData) => {
if (!prevData) return [];
return prevData.map((item) =>
item.integration.id === integrationId && item.summary
item.integration.id === integrationId
? {
...item,
summary: {
@@ -115,14 +119,13 @@ export default function DnsHoleControlsWidget({
onSettled: (_, error, { integrationId }) => {
utils.widget.dnsHole.summary.setData(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
(prevData) => {
if (!prevData) return [];
return prevData.map((item) =>
item.integration.id === integrationId && item.summary
item.integration.id === integrationId
? {
...item,
summary: {
@@ -138,17 +141,16 @@ export default function DnsHoleControlsWidget({
});
const toggleDns = (integrationId: string) => {
const integrationStatus = summaries.find(({ integration }) => integration.id === integrationId);
if (!integrationStatus?.summary?.status) return;
if (!integrationStatus?.summary.status) return;
utils.widget.dnsHole.summary.setData(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
(prevData) => {
if (!prevData) return [];
return prevData.map((item) =>
item.integration.id === integrationId && item.summary
item.integration.id === integrationId
? {
...item,
summary: {
@@ -170,7 +172,7 @@ export default function DnsHoleControlsWidget({
// make lists of enabled and disabled interactable integrations (with permissions, not disconnected and not processing)
const integrationsSummaries = summaries.reduce(
(acc, { summary, integration: { id } }) =>
integrationsWithInteractions.includes(id) && summary?.status != null ? (acc[summary.status].push(id), acc) : acc,
integrationsWithInteractions.includes(id) && summary.status != null ? (acc[summary.status].push(id), acc) : acc,
{ enabled: [] as string[], disabled: [] as string[] },
);
@@ -310,9 +312,8 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
open,
t,
}) => {
// Independently determine connection status, current state and permissions
const isConnected = data.summary !== null && Math.abs(dayjs(data.timestamp).diff()) < 30000;
const isEnabled = data.summary?.status ? data.summary.status === "enabled" : undefined;
const isConnected = useIntegrationConnected(data.integration.updatedAt, { timeout: 30000 });
const isEnabled = data.summary.status ? data.summary.status === "enabled" : undefined;
const isInteractPermitted = integrationsWithInteractions.includes(data.integration.id);
// Use all factors to infer the state of the action buttons
const controlEnabled = isInteractPermitted && isEnabled !== undefined && isConnected;
@@ -355,7 +356,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
lts="0.1cqmin"
color="var(--background-color)"
c="var(--mantine-color-text)"
styles={{ section: { marginInlineEnd: "2.5cqmin" } }}
styles={{ section: { marginInlineEnd: "2.5cqmin" }, root: { cursor: "inherit" } }}
leftSection={
isConnected && (
<IconCircleFilled

View File

@@ -5,7 +5,6 @@ import type { BoxProps } from "@mantine/core";
import { Avatar, AvatarGroup, Box, Card, Flex, Stack, Text, Tooltip } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import { IconBarrierBlock, IconPercentage, IconSearch, IconWorldWww } from "@tabler/icons-react";
import dayjs from "dayjs";
import { clientApi } from "@homarr/api/client";
import { formatNumber } from "@homarr/common";
@@ -16,14 +15,13 @@ import { translateIfNecessary } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client";
import type { TablerIcon } from "@homarr/ui";
import { widgetKind } from ".";
import type { widgetKind } from ".";
import type { WidgetComponentProps, WidgetProps } from "../../definition";
import { NoIntegrationSelectedError } from "../../errors";
export default function DnsHoleSummaryWidget({ options, integrationIds }: WidgetComponentProps<typeof widgetKind>) {
const [summaries] = clientApi.widget.dnsHole.summary.useSuspenseQuery(
{
widgetKind,
integrationIds,
},
{
@@ -39,14 +37,12 @@ export default function DnsHoleSummaryWidget({ options, integrationIds }: Widget
clientApi.widget.dnsHole.subscribeToSummary.useSubscription(
{
widgetKind,
integrationIds,
},
{
onData: (data) => {
utils.widget.dnsHole.summary.setData(
{
widgetKind,
integrationIds,
},
(prevData) => {
@@ -64,14 +60,7 @@ export default function DnsHoleSummaryWidget({ options, integrationIds }: Widget
},
);
const data = useMemo(
() =>
summaries
.filter((pair) => Math.abs(dayjs(pair.timestamp).diff()) < 30000)
.flatMap(({ summary }) => summary)
.filter((summary) => summary !== null),
[summaries],
);
const data = useMemo(() => summaries.flatMap(({ summary }) => summary), [summaries]);
if (integrationIds.length === 0) {
throw new NoIntegrationSelectedError();