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:
@@ -21,7 +21,7 @@ import {
|
||||
Title,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure, useTimeout } from "@mantine/hooks";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import type { IconProps } from "@tabler/icons-react";
|
||||
import {
|
||||
IconAlertTriangle,
|
||||
@@ -39,13 +39,9 @@ import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useIntegrationsWithInteractAccess } from "@homarr/auth/client";
|
||||
import { humanFileSize } from "@homarr/common";
|
||||
import { humanFileSize, useIntegrationConnected } from "@homarr/common";
|
||||
import { getIconUrl, getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
import type {
|
||||
DownloadClientJobsAndStatus,
|
||||
ExtendedClientStatus,
|
||||
ExtendedDownloadClientItem,
|
||||
} from "@homarr/integrations";
|
||||
import type { ExtendedClientStatus, ExtendedDownloadClientItem } from "@homarr/integrations";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
@@ -82,8 +78,6 @@ const standardIconStyle: IconProps["style"] = {
|
||||
width: "var(--icon-size)",
|
||||
};
|
||||
|
||||
const invalidateTime = 30000;
|
||||
|
||||
export default function DownloadClientsWidget({
|
||||
isEditMode,
|
||||
integrationIds,
|
||||
@@ -103,26 +97,10 @@ export default function DownloadClientsWidget({
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
retry: false,
|
||||
select(data) {
|
||||
return data.map((item) =>
|
||||
dayjs().diff(item.timestamp) < invalidateTime ? item : { ...item, timestamp: new Date(0), data: null },
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
const utils = clientApi.useUtils();
|
||||
|
||||
//Invalidate all data after no update for 30 seconds using timer
|
||||
const invalidationTimer = useTimeout(
|
||||
() => {
|
||||
utils.widget.downloads.getJobsAndStatuses.setData({ integrationIds }, (prevData) =>
|
||||
prevData?.map((item) => ({ ...item, timestamp: new Date(0), data: null })),
|
||||
);
|
||||
},
|
||||
invalidateTime,
|
||||
{ autoInvoke: true },
|
||||
);
|
||||
|
||||
//Translations
|
||||
const t = useScopedI18n("widget.downloads");
|
||||
const tCommon = useScopedI18n("common");
|
||||
@@ -143,32 +121,19 @@ export default function DownloadClientsWidget({
|
||||
},
|
||||
{
|
||||
onData: (data) => {
|
||||
//Use cyclical update to invalidate data older than 30 seconds from unresponsive integrations
|
||||
const invalidIndexes = currentItems
|
||||
//Don't update already invalid data (new Date (0))
|
||||
.filter(({ timestamp }) => dayjs().diff(timestamp) > invalidateTime && timestamp > new Date(0))
|
||||
.map(({ integration }) => integration.id);
|
||||
utils.widget.downloads.getJobsAndStatuses.setData({ integrationIds }, (prevData) =>
|
||||
prevData?.map((item) =>
|
||||
invalidIndexes.includes(item.integration.id) ? item : { ...item, timestamp: new Date(0), data: null },
|
||||
),
|
||||
);
|
||||
utils.widget.downloads.getJobsAndStatuses.setData({ integrationIds }, (prevData) => {
|
||||
const updateIndex = currentItems.findIndex((pair) => pair.integration.id === data.integration.id);
|
||||
if (updateIndex >= 0) {
|
||||
//Update found index
|
||||
return prevData?.map((pair, index) => (index === updateIndex ? data : pair));
|
||||
} else if (integrationIds.includes(data.integration.id)) {
|
||||
//Append index not found (new integration)
|
||||
return [...(prevData ?? []), data];
|
||||
}
|
||||
return prevData?.map((item) => {
|
||||
if (item.integration.id !== data.integration.id) return item;
|
||||
|
||||
return undefined;
|
||||
return {
|
||||
data: data.data,
|
||||
integration: {
|
||||
...data.integration,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
//Reset no update timer
|
||||
invalidationTimer.clear();
|
||||
invalidationTimer.start();
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -179,16 +144,6 @@ export default function DownloadClientsWidget({
|
||||
currentItems
|
||||
//Insure it is only using selected integrations
|
||||
.filter(({ integration }) => integrationIds.includes(integration.id))
|
||||
//Removing any integration with no data associated
|
||||
.filter(
|
||||
(
|
||||
pair,
|
||||
): pair is {
|
||||
integration: typeof pair.integration;
|
||||
timestamp: typeof pair.timestamp;
|
||||
data: DownloadClientJobsAndStatus;
|
||||
} => pair.data != null,
|
||||
)
|
||||
//Construct normalized items list
|
||||
.flatMap((pair) =>
|
||||
//Apply user white/black list
|
||||
@@ -255,7 +210,6 @@ export default function DownloadClientsWidget({
|
||||
.filter(({ integration }) => integrationIds.includes(integration.id))
|
||||
.flatMap(({ integration, data }): ExtendedClientStatus => {
|
||||
const interact = integrationsWithInteractions.includes(integration.id);
|
||||
if (!data) return { integration, interact };
|
||||
const isTorrent = getIntegrationKindsByCategory("torrent").some((kind) => kind === integration.kind);
|
||||
/** Derived from current items */
|
||||
const { totalUp, totalDown } = data.items
|
||||
@@ -821,12 +775,7 @@ const ClientsControl = ({ clients, style }: ClientsControlProps) => {
|
||||
<Group gap="var(--space-size)" style={style}>
|
||||
<AvatarGroup spacing="calc(var(--space-size)*2)">
|
||||
{clients.map((client) => (
|
||||
<Avatar
|
||||
key={client.integration.id}
|
||||
src={getIconUrl(client.integration.kind)}
|
||||
size="var(--image-size)"
|
||||
bd={client.status ? 0 : "calc(var(--space-size)*0.5) solid var(--mantine-color-red-filled)"}
|
||||
/>
|
||||
<ClientAvatar key={client.integration.id} client={client} />
|
||||
))}
|
||||
</AvatarGroup>
|
||||
{someInteract && (
|
||||
@@ -939,3 +888,21 @@ const ClientsControl = ({ clients, style }: ClientsControlProps) => {
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
interface ClientAvatarProps {
|
||||
client: ExtendedClientStatus;
|
||||
}
|
||||
|
||||
const ClientAvatar = ({ client }: ClientAvatarProps) => {
|
||||
const isConnected = useIntegrationConnected(client.integration.updatedAt, { timeout: 30000 });
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
key={client.integration.id}
|
||||
src={getIconUrl(client.integration.kind)}
|
||||
style={{ filter: !isConnected ? "grayscale(100%)" : undefined }}
|
||||
size="var(--image-size)"
|
||||
bd={client.status ? 0 : "calc(var(--space-size)*0.5) solid var(--mantine-color-red-filled)"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user