feat: indexer manager widget (#1057)
* fix(deps): update tanstack-query monorepo to ^5.53.2 (#1055) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> <br/> <div align="center"> <img src="https://homarr.dev/img/logo.png" height="80" alt="" /> <h3>Homarr</h3> </div> **Thank you for your contribution. Please ensure that your pull request meets the following pull request:** - [ ] Builds without warnings or errors (``pnpm buid``, autofix with ``pnpm format:fix``) - [ ] Pull request targets ``dev`` branch - [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/) - [ ] No shorthand variable names are used (eg. ``x``, ``y``, ``i`` or any abbrevation) * fix: requested changes * fix: requested changes * feat: add cron job * fix: review changes * fix: add missing oldmarr import mappings --------- Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import * as dnsHoleControls from "./dns-hole/controls";
|
||||
import * as dnsHoleSummary from "./dns-hole/summary";
|
||||
import * as iframe from "./iframe";
|
||||
import type { WidgetImportRecord } from "./import";
|
||||
import * as indexerManager from "./indexer-manager";
|
||||
import * as mediaRequestsList from "./media-requests/list";
|
||||
import * as mediaRequestsStats from "./media-requests/stats";
|
||||
import * as mediaServer from "./media-server";
|
||||
@@ -47,6 +48,7 @@ export const widgetImports = {
|
||||
"mediaRequests-requestList": mediaRequestsList,
|
||||
"mediaRequests-requestStats": mediaRequestsStats,
|
||||
rssFeed,
|
||||
indexerManager,
|
||||
} satisfies WidgetImportRecord;
|
||||
|
||||
export type WidgetImports = typeof widgetImports;
|
||||
|
||||
85
packages/widgets/src/indexer-manager/component.tsx
Normal file
85
packages/widgets/src/indexer-manager/component.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Anchor, Button, Card, Container, Flex, Group, ScrollArea, Text } from "@mantine/core";
|
||||
import { IconCircleCheck, IconCircleX, IconReportSearch, IconTestPipe } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import type { Indexer } from "@homarr/integrations/types";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { NoIntegrationSelectedError } from "../errors";
|
||||
|
||||
export default function IndexerManagerWidget({
|
||||
options,
|
||||
integrationIds,
|
||||
serverData,
|
||||
}: WidgetComponentProps<"indexerManager">) {
|
||||
if (integrationIds.length === 0) {
|
||||
throw new NoIntegrationSelectedError();
|
||||
}
|
||||
const t = useI18n();
|
||||
const [indexersData, setIndexersData] = useState<{ integrationId: string; indexers: Indexer[] }[]>(
|
||||
serverData?.initialData ?? [],
|
||||
);
|
||||
|
||||
const { mutate: testAll, isPending } = clientApi.widget.indexerManager.testAllIndexers.useMutation();
|
||||
|
||||
clientApi.widget.indexerManager.subscribeIndexersStatus.useSubscription(
|
||||
{ integrationIds },
|
||||
{
|
||||
onData(newData) {
|
||||
setIndexersData((prevData) => {
|
||||
return prevData.map((item) =>
|
||||
item.integrationId === newData.integrationId ? { ...item, indexers: newData.indexers } : item,
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex h="100%" direction="column">
|
||||
<Text size="6.5cqmin" mt="1.5cqmin" pl="20cqmin">
|
||||
<IconReportSearch size="7cqmin" /> {t("widget.indexerManager.title")}
|
||||
</Text>
|
||||
<Card m="2.5cqmin" p="2.5cqmin" radius="md" withBorder>
|
||||
<ScrollArea h="100%">
|
||||
{indexersData.map(({ integrationId, indexers }) => (
|
||||
<Container key={integrationId}>
|
||||
{indexers.map((indexer) => (
|
||||
<Group key={indexer.id} justify="space-between">
|
||||
<Anchor href={indexer.url} target={options.openIndexerSiteInNewTab ? "_blank" : "_self"}>
|
||||
<Text c="dimmed" size="xs">
|
||||
{indexer.name}
|
||||
</Text>
|
||||
</Anchor>
|
||||
{indexer.status === false || indexer.enabled === false ? (
|
||||
<IconCircleX color="#d9534f" />
|
||||
) : (
|
||||
<IconCircleCheck color="#2ecc71" />
|
||||
)}
|
||||
</Group>
|
||||
))}
|
||||
</Container>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</Card>
|
||||
<Button
|
||||
m="2.5cqmin"
|
||||
p="2.5cqmin"
|
||||
radius="md"
|
||||
variant="light"
|
||||
leftSection={<IconTestPipe size={20} />}
|
||||
loading={isPending}
|
||||
loaderProps={{ type: "dots" }}
|
||||
onClick={() => {
|
||||
testAll({ integrationIds });
|
||||
}}
|
||||
>
|
||||
{t("widget.indexerManager.testAll")}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
22
packages/widgets/src/indexer-manager/index.ts
Normal file
22
packages/widgets/src/indexer-manager/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { IconReportSearch, IconServerOff } from "@tabler/icons-react";
|
||||
|
||||
import { createWidgetDefinition } from "../definition";
|
||||
import { optionsBuilder } from "../options";
|
||||
|
||||
export const { definition, componentLoader, serverDataLoader } = createWidgetDefinition("indexerManager", {
|
||||
icon: IconReportSearch,
|
||||
options: optionsBuilder.from((factory) => ({
|
||||
openIndexerSiteInNewTab: factory.switch({
|
||||
defaultValue: true,
|
||||
}),
|
||||
})),
|
||||
supportedIntegrations: ["prowlarr"],
|
||||
errors: {
|
||||
INTERNAL_SERVER_ERROR: {
|
||||
icon: IconServerOff,
|
||||
message: (t) => t("widget.indexerManager.error.internalServerError"),
|
||||
},
|
||||
},
|
||||
})
|
||||
.withServerData(() => import("./serverData"))
|
||||
.withDynamicImport(() => import("./component"));
|
||||
27
packages/widgets/src/indexer-manager/serverData.ts
Normal file
27
packages/widgets/src/indexer-manager/serverData.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
"use server";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
|
||||
import type { WidgetProps } from "../definition";
|
||||
|
||||
export default async function getServerDataAsync({ integrationIds }: WidgetProps<"indexerManager">) {
|
||||
if (integrationIds.length === 0) {
|
||||
return {
|
||||
initialData: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const currentIndexers = await api.widget.indexerManager.getIndexersStatus({
|
||||
integrationIds,
|
||||
});
|
||||
|
||||
return {
|
||||
initialData: currentIndexers,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
initialData: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user