docmost/apps/client/src/ee/security/components/sso-provider-list.tsx
Philip Okugbe b81c9ee10c
feat: cloud and ee (#805)
* stripe init
git submodules for enterprise modules

* * Cloud billing UI - WIP
* Proxy websockets in dev mode
* Separate workspace login and creation for cloud
* Other fixes

* feat: billing (cloud)

* * add domain service
* prepare links from workspace hostname

* WIP

* Add exchange token generation
* Validate JWT token type during verification

* domain service

* add SkipTransform decorator

* * updates (server)
* add new packages
* new sso migration file

* WIP

* Fix hostname generation

* WIP

* WIP

* Reduce input error font-size
* set max password length

* jwt package

* license page - WIP

* * License management UI
* Move license key store to db

* add reflector

* SSO enforcement

* * Add default plan
* Add usePlan hook

* * Fix auth container margin in mobile
* Redirect login and home to select page in cloud

* update .gitignore

* Default to yearly

* * Trial messaging
* Handle ended trials

* Don't set to readonly on collab disconnect (Cloud)

* Refine trial (UI)
* Fix bug caused by using jotai optics atom in AppHeader component

* configurable database maximum pool

* Close SSO form on save

* wip

* sync

* Only show sign-in in cloud

* exclude base api part from workspaceId check

* close db connection beforeApplicationShutdown

* Add health/live endpoint

* clear cookie on hostname change

* reset currentUser atom

* Change text

* return 401 if workspace does not match

* feat: show user workspace list in cloud login page

* sync

* Add home path

* Prefetch to speed up queries

* * Add robots.txt
* Disallow login and forgot password routes

* wildcard user-agent

* Fix space query cache

* fix

* fix

* use space uuid for recent pages

* prefetch billing plans

* enhance license page

* sync
2025-03-06 13:38:37 +00:00

187 lines
6.4 KiB
TypeScript

import React, { useState } from "react";
import {
useDeleteSsoProviderMutation,
useGetSsoProviders,
} from "@/ee/security/queries/security-query.ts";
import {
ActionIcon,
Badge,
Card,
Group,
Menu,
Table,
Text,
ThemeIcon,
} from "@mantine/core";
import {
IconCheck,
IconDots,
IconLock,
IconPencil,
IconTrash,
IconX,
} from "@tabler/icons-react";
import { useDisclosure } from "@mantine/hooks";
import { modals } from "@mantine/modals";
import { IAuthProvider } from "@/ee/security/types/security.types.ts";
import { useTranslation } from "react-i18next";
import SsoProviderModal from "@/ee/security/components/sso-provider-modal.tsx";
import { SSO_PROVIDER } from "@/ee/security/contants.ts";
import { GoogleIcon } from "@/components/icons/google-icon.tsx";
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
import RoleSelectMenu from "@/components/ui/role-select-menu.tsx";
import { getUserRoleLabel } from "@/features/workspace/types/user-role-data.ts";
export default function SsoProviderList() {
const { t } = useTranslation();
const { data, isLoading } = useGetSsoProviders();
const [opened, { open, close }] = useDisclosure(false);
const deleteSsoProviderMutation = useDeleteSsoProviderMutation();
const [editProvider, setEditProvider] = useState<IAuthProvider | null>(null);
if (isLoading || !data) {
return null;
}
if (data?.length === 0) {
return <Text c="dimmed">{t("No SSO providers found.")}</Text>;
}
const handleEdit = (provider: IAuthProvider) => {
setEditProvider(provider);
open();
};
const openDeleteModal = (providerId: string) =>
modals.openConfirmModal({
title: t("Delete SSO provider"),
centered: true,
children: (
<Text size="sm">
{t("Are you sure you want to delete this SSO provider?")}
</Text>
),
labels: { confirm: t("Delete"), cancel: t("Don't") },
confirmProps: { color: "red" },
onConfirm: () => deleteSsoProviderMutation.mutateAsync(providerId),
});
return (
<>
<Card shadow="sm" radius="sm">
<Table.ScrollContainer minWidth={500}>
<Table verticalSpacing="sm">
<Table.Thead>
<Table.Tr>
<Table.Th>{t("Name")}</Table.Th>
<Table.Th>{t("Type")}</Table.Th>
<Table.Th>{t("Status")}</Table.Th>
<Table.Th>{t("Allow signup")}</Table.Th>
<Table.Th>{t("Action")}</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{data
.sort((a, b) => {
const enabledDiff = Number(b.isEnabled) - Number(a.isEnabled);
if (enabledDiff !== 0) return enabledDiff;
return a.name.localeCompare(b.name);
})
.map((provider: IAuthProvider, index) => (
<Table.Tr key={index}>
<Table.Td>
<Group gap="xs" wrap="nowrap">
{provider.type === SSO_PROVIDER.GOOGLE ? (
<GoogleIcon size={16} />
) : (
<IconLock size={16} />
)}
<div>
<Text fz="sm" fw={500}>
{provider.name}
</Text>
</div>
</Group>
</Table.Td>
<Table.Td>
<Badge color={"gray"} variant="light">
{provider.type.toUpperCase()}
</Badge>
</Table.Td>
<Table.Td>
<Badge
color={provider.isEnabled ? "blue" : "gray"}
variant="light"
>
{provider.isEnabled ? "Active" : "InActive"}
</Badge>
</Table.Td>
<Table.Td>
{provider.allowSignup ? (
<ThemeIcon variant="light" size={24} radius="xl">
<IconCheck size={16} />
</ThemeIcon>
) : (
<ThemeIcon
variant="light"
color="red"
size={24}
radius="xl"
>
<IconX size={16} />
</ThemeIcon>
)}
</Table.Td>
<Table.Td>
<ActionIcon
variant="subtle"
color="gray"
onClick={() => handleEdit(provider)}
>
<IconPencil size={16} />
</ActionIcon>
<Menu
transitionProps={{ transition: "pop" }}
withArrow
position="bottom-end"
withinPortal
>
<Menu.Target>
<ActionIcon variant="subtle" color="gray">
<IconDots size={16} />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
onClick={() => handleEdit(provider)}
leftSection={<IconPencil size={16} />}
>
{t("Edit")}
</Menu.Item>
<Menu.Item
onClick={() => openDeleteModal(provider.id)}
leftSection={<IconTrash size={16} />}
color="red"
disabled={provider.type === SSO_PROVIDER.GOOGLE}
>
{t("Delete")}
</Menu.Item>
</Menu.Dropdown>
</Menu>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
</Card>
<SsoProviderModal
opened={opened}
onClose={close}
provider={editProvider}
/>
</>
);
}