207 lines
7.0 KiB
TypeScript
207 lines
7.0 KiB
TypeScript
import { Fragment } from "react";
|
|
import type { PropsWithChildren } from "react";
|
|
import Link from "next/link";
|
|
import {
|
|
AccordionControl,
|
|
AccordionItem,
|
|
AccordionPanel,
|
|
ActionIcon,
|
|
ActionIconGroup,
|
|
Affix,
|
|
Anchor,
|
|
Box,
|
|
Button,
|
|
Divider,
|
|
Group,
|
|
Menu,
|
|
MenuDropdown,
|
|
MenuTarget,
|
|
Stack,
|
|
Table,
|
|
TableTbody,
|
|
TableTd,
|
|
TableTh,
|
|
TableThead,
|
|
TableTr,
|
|
Text,
|
|
Title,
|
|
} from "@mantine/core";
|
|
import { IconChevronDown, IconChevronUp, IconPencil } from "@tabler/icons-react";
|
|
|
|
import type { RouterOutputs } from "@homarr/api";
|
|
import { api } from "@homarr/api/server";
|
|
import { objectEntries } from "@homarr/common";
|
|
import type { IntegrationKind } from "@homarr/definitions";
|
|
import { getIntegrationName } from "@homarr/definitions";
|
|
import { getScopedI18n } from "@homarr/translation/server";
|
|
import { CountBadge } from "@homarr/ui";
|
|
|
|
import { ManageContainer } from "~/components/manage/manage-container";
|
|
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
|
import { ActiveTabAccordion } from "../../../../components/active-tab-accordion";
|
|
import { IntegrationAvatar } from "./_integration-avatar";
|
|
import { DeleteIntegrationActionButton } from "./_integration-buttons";
|
|
import { IntegrationCreateDropdownContent } from "./new/_integration-new-dropdown";
|
|
|
|
interface IntegrationsPageProps {
|
|
searchParams: {
|
|
tab?: IntegrationKind;
|
|
};
|
|
}
|
|
|
|
export default async function IntegrationsPage({ searchParams }: IntegrationsPageProps) {
|
|
const integrations = await api.integration.all();
|
|
const t = await getScopedI18n("integration");
|
|
|
|
return (
|
|
<ManageContainer>
|
|
<DynamicBreadcrumb />
|
|
<Stack>
|
|
<Group justify="space-between" align="center">
|
|
<Title>{t("page.list.title")}</Title>
|
|
|
|
<Box>
|
|
<IntegrationSelectMenu>
|
|
<Affix hiddenFrom="md" position={{ bottom: 20, right: 20 }}>
|
|
<MenuTarget>
|
|
<Button rightSection={<IconChevronUp size={16} stroke={1.5} />}>{t("action.create")}</Button>
|
|
</MenuTarget>
|
|
</Affix>
|
|
</IntegrationSelectMenu>
|
|
</Box>
|
|
|
|
<Box visibleFrom="md">
|
|
<IntegrationSelectMenu>
|
|
<MenuTarget>
|
|
<Button rightSection={<IconChevronDown size={16} stroke={1.5} />}>{t("action.create")}</Button>
|
|
</MenuTarget>
|
|
</IntegrationSelectMenu>
|
|
</Box>
|
|
</Group>
|
|
|
|
<IntegrationList integrations={integrations} activeTab={searchParams.tab} />
|
|
</Stack>
|
|
</ManageContainer>
|
|
);
|
|
}
|
|
|
|
const IntegrationSelectMenu = ({ children }: PropsWithChildren) => {
|
|
return (
|
|
<Menu width={256} trapFocus position="bottom-end" withinPortal shadow="md" keepMounted={false}>
|
|
{children}
|
|
<MenuDropdown>
|
|
<IntegrationCreateDropdownContent />
|
|
</MenuDropdown>
|
|
</Menu>
|
|
);
|
|
};
|
|
|
|
interface IntegrationListProps {
|
|
integrations: RouterOutputs["integration"]["all"];
|
|
activeTab?: IntegrationKind;
|
|
}
|
|
|
|
const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps) => {
|
|
const t = await getScopedI18n("integration");
|
|
|
|
if (integrations.length === 0) {
|
|
return <div>{t("page.list.empty")}</div>;
|
|
}
|
|
|
|
const grouppedIntegrations = integrations.reduce(
|
|
(acc, integration) => {
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
if (!acc[integration.kind]) {
|
|
acc[integration.kind] = [];
|
|
}
|
|
|
|
acc[integration.kind].push(integration);
|
|
|
|
return acc;
|
|
},
|
|
{} as Record<IntegrationKind, RouterOutputs["integration"]["all"]>,
|
|
);
|
|
|
|
return (
|
|
<ActiveTabAccordion defaultValue={activeTab} variant="separated">
|
|
{objectEntries(grouppedIntegrations).map(([kind, integrations]) => (
|
|
<AccordionItem key={kind} value={kind}>
|
|
<AccordionControl icon={<IntegrationAvatar size="sm" kind={kind} />}>
|
|
<Group>
|
|
<Text>{getIntegrationName(kind)}</Text>
|
|
<CountBadge count={integrations.length} />
|
|
</Group>
|
|
</AccordionControl>
|
|
<AccordionPanel>
|
|
<Table visibleFrom="md">
|
|
<TableThead>
|
|
<TableTr>
|
|
<TableTh>{t("field.name.label")}</TableTh>
|
|
<TableTh>{t("field.url.label")}</TableTh>
|
|
<TableTh />
|
|
</TableTr>
|
|
</TableThead>
|
|
<TableTbody>
|
|
{integrations.map((integration) => (
|
|
<TableTr key={integration.id}>
|
|
<TableTd>{integration.name}</TableTd>
|
|
<TableTd>
|
|
<Anchor href={integration.url} target="_blank" rel="noreferrer" size="sm">
|
|
{integration.url}
|
|
</Anchor>
|
|
</TableTd>
|
|
<TableTd>
|
|
<Group justify="end">
|
|
<ActionIconGroup>
|
|
<ActionIcon
|
|
component={Link}
|
|
href={`/manage/integrations/edit/${integration.id}`}
|
|
variant="subtle"
|
|
color="gray"
|
|
aria-label={t("page.edit.title", { name: getIntegrationName(integration.kind) })}
|
|
>
|
|
<IconPencil size={16} stroke={1.5} />
|
|
</ActionIcon>
|
|
<DeleteIntegrationActionButton integration={integration} count={integrations.length} />
|
|
</ActionIconGroup>
|
|
</Group>
|
|
</TableTd>
|
|
</TableTr>
|
|
))}
|
|
</TableTbody>
|
|
</Table>
|
|
|
|
<Stack gap="xs" hiddenFrom="md">
|
|
{integrations.map((integration, index) => (
|
|
<Fragment key={integration.id}>
|
|
{index !== 0 && <Divider />}
|
|
<Stack gap={0}>
|
|
<Group justify="space-between" align="center" wrap="nowrap">
|
|
<Text>{integration.name}</Text>
|
|
<ActionIconGroup>
|
|
<ActionIcon
|
|
component={Link}
|
|
href={`/manage/integrations/edit/${integration.id}`}
|
|
variant="subtle"
|
|
color="gray"
|
|
aria-label={t("page.edit.title", { name: getIntegrationName(integration.kind) })}
|
|
>
|
|
<IconPencil size={16} stroke={1.5} />
|
|
</ActionIcon>
|
|
<DeleteIntegrationActionButton integration={integration} count={integrations.length} />
|
|
</ActionIconGroup>
|
|
</Group>
|
|
<Anchor href={integration.url} target="_blank" rel="noreferrer" size="sm">
|
|
{integration.url}
|
|
</Anchor>
|
|
</Stack>
|
|
</Fragment>
|
|
))}
|
|
</Stack>
|
|
</AccordionPanel>
|
|
</AccordionItem>
|
|
))}
|
|
</ActiveTabAccordion>
|
|
);
|
|
};
|