feat: add integration access settings (#725)
* feat: add integration access settings * fix: typecheck and test issues * fix: test timeout * chore: address pull request feedback * chore: add throw if action forbidden for integration permissions * fix: unable to create new migrations because of duplicate prevId in sqlite snapshots * chore: add sqlite migration for integration permissions * test: add unit tests for integration access * test: add permission checks to integration router tests * test: add unit test for integration permissions * chore: add mysql migration * fix: format issues
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { IconPlayerPlay, IconSelector, IconSettings } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { integrationPermissions, integrationPermissionsMap } from "@homarr/definitions";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { AccessSettings } from "~/components/access/access-settings";
|
||||
|
||||
interface Props {
|
||||
integration: RouterOutputs["integration"]["byId"];
|
||||
initialPermissions: RouterOutputs["integration"]["getIntegrationPermissions"];
|
||||
}
|
||||
|
||||
export const IntegrationAccessSettings = ({ integration, initialPermissions }: Props) => {
|
||||
const t = useI18n();
|
||||
const utils = clientApi.useUtils();
|
||||
const { data } = clientApi.integration.getIntegrationPermissions.useQuery(
|
||||
{
|
||||
id: integration.id,
|
||||
},
|
||||
{
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
initialData: initialPermissions,
|
||||
},
|
||||
);
|
||||
const usersMutation = clientApi.integration.saveUserIntegrationPermissions.useMutation();
|
||||
const groupsMutation = clientApi.integration.saveGroupIntegrationPermissions.useMutation();
|
||||
|
||||
return (
|
||||
<AccessSettings
|
||||
entity={{
|
||||
id: integration.id,
|
||||
ownerId: null,
|
||||
owner: null,
|
||||
}}
|
||||
permission={{
|
||||
items: integrationPermissions,
|
||||
default: "use",
|
||||
fullAccessGroupPermission: "integration-full-all",
|
||||
icons: {
|
||||
use: IconSelector,
|
||||
interact: IconPlayerPlay,
|
||||
full: IconSettings,
|
||||
},
|
||||
groupPermissionMapping: integrationPermissionsMap,
|
||||
}}
|
||||
translate={(key) => t(`integration.permission.${key}`)}
|
||||
query={{
|
||||
data,
|
||||
invalidate: () => utils.integration.getIntegrationPermissions.invalidate(),
|
||||
}}
|
||||
groupsMutation={groupsMutation}
|
||||
usersMutation={usersMutation}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Container, Group, Stack, Title } from "@mantine/core";
|
||||
import { Container, Fieldset, Group, Stack, Title } from "@mantine/core";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
import { getIntegrationName } from "@homarr/definitions";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { getI18n, getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { IntegrationAccessSettings } from "../../_components/integration-access-settings";
|
||||
import { IntegrationAvatar } from "../../_integration-avatar";
|
||||
import { EditIntegrationForm } from "./_integration-edit-form";
|
||||
|
||||
@@ -13,8 +14,10 @@ interface EditIntegrationPageProps {
|
||||
}
|
||||
|
||||
export default async function EditIntegrationPage({ params }: EditIntegrationPageProps) {
|
||||
const t = await getScopedI18n("integration.page.edit");
|
||||
const editT = await getScopedI18n("integration.page.edit");
|
||||
const t = await getI18n();
|
||||
const integration = await api.integration.byId({ id: params.id });
|
||||
const integrationPermissions = await api.integration.getIntegrationPermissions({ id: integration.id });
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -23,9 +26,14 @@ export default async function EditIntegrationPage({ params }: EditIntegrationPag
|
||||
<Stack>
|
||||
<Group align="center">
|
||||
<IntegrationAvatar kind={integration.kind} size="md" />
|
||||
<Title>{t("title", { name: getIntegrationName(integration.kind) })}</Title>
|
||||
<Title>{editT("title", { name: getIntegrationName(integration.kind) })}</Title>
|
||||
</Group>
|
||||
<EditIntegrationForm integration={integration} />
|
||||
|
||||
<Title order={2}>{t("permission.title")}</Title>
|
||||
<Fieldset>
|
||||
<IntegrationAccessSettings integration={integration} initialPermissions={integrationPermissions} />
|
||||
</Fieldset>
|
||||
</Stack>
|
||||
</Container>
|
||||
</>
|
||||
|
||||
@@ -24,7 +24,7 @@ export default async function IntegrationsNewPage({ searchParams }: NewIntegrati
|
||||
notFound();
|
||||
}
|
||||
|
||||
const t = await getScopedI18n("integration.page.create");
|
||||
const tCreate = await getScopedI18n("integration.page.create");
|
||||
|
||||
const currentKind = result.data;
|
||||
|
||||
@@ -35,7 +35,7 @@ export default async function IntegrationsNewPage({ searchParams }: NewIntegrati
|
||||
<Stack>
|
||||
<Group align="center">
|
||||
<IntegrationAvatar kind={currentKind} size="md" />
|
||||
<Title>{t("title", { name: getIntegrationName(currentKind) })}</Title>
|
||||
<Title>{tCreate("title", { name: getIntegrationName(currentKind) })}</Title>
|
||||
</Group>
|
||||
<NewIntegrationForm searchParams={searchParams} />
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user