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:
Yossi Hillali
2024-09-08 00:18:16 +03:00
committed by GitHub
parent e88854c0e5
commit 08d4472d8b
15 changed files with 283 additions and 2 deletions

View File

@@ -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;

View 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>
);
}

View 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"));

View 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: [],
};
}
}