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:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user