import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { IFormatter, IFormatterValueType, Table, TableBody, TableHeader, TableVariant, } from "@patternfly/react-table"; import { AlertVariant, Button, Dropdown, DropdownItem, DropdownToggle, KebabToggle, Select, Spinner, ToolbarItem, } from "@patternfly/react-core"; import { FilterIcon } from "@patternfly/react-icons"; import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation"; import KeycloakAdminClient from "keycloak-admin"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; import { TableToolbar } from "../../components/table-toolbar/TableToolbar"; import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; import { AddScopeDialog } from "./AddScopeDialog"; import { clientScopeTypesSelectOptions, ClientScopeType, ClientScope, } from "./ClientScopeTypes"; import { useAlerts } from "../../components/alert/Alerts"; export type ClientScopesProps = { clientId: string; protocol: string; }; const firstUpperCase = (name: string) => name.charAt(0).toUpperCase() + name.slice(1); const castAdminClient = (adminClient: KeycloakAdminClient) => (adminClient.clients as unknown) as { [index: string]: Function; }; const changeScope = async ( adminClient: KeycloakAdminClient, clientId: string, clientScope: ClientScopeRepresentation, type: ClientScopeType, changeTo: ClientScopeType ) => { await removeScope(adminClient, clientId, clientScope, type); await addScope(adminClient, clientId, clientScope, changeTo); }; const removeScope = async ( adminClient: KeycloakAdminClient, clientId: string, clientScope: ClientScopeRepresentation, type: ClientScopeType ) => { const typeToName = firstUpperCase(type); await castAdminClient(adminClient)[`del${typeToName}ClientScope`]({ id: clientId, clientScopeId: clientScope.id!, }); }; const addScope = async ( adminClient: KeycloakAdminClient, clientId: string, clientScope: ClientScopeRepresentation, type: ClientScopeType ) => { const typeToName = firstUpperCase(type); await castAdminClient(adminClient)[`add${typeToName}ClientScope`]({ id: clientId, clientScopeId: clientScope.id!, }); }; type CellDropdownProps = { clientScope: ClientScopeRepresentation; type: ClientScopeType; onSelect: (value: ClientScopeType) => void; }; const CellDropdown = ({ clientScope, type, onSelect }: CellDropdownProps) => { const { t } = useTranslation("clients"); const [open, setOpen] = useState(false); return ( ); }; type SearchType = "client" | "assigned"; type TableRow = { selected: boolean; clientScope: ClientScopeRepresentation; type: ClientScopeType; cells: (string | undefined)[]; }; export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => { const { t } = useTranslation("clients"); const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const [searchToggle, setSearchToggle] = useState(false); const [searchType, setSearchType] = useState("client"); const [addToggle, setAddToggle] = useState(false); const [addDialogOpen, setAddDialogOpen] = useState(false); const [kebabOpen, setKebabOpen] = useState(false); const [rows, setRows] = useState(); const [rest, setRest] = useState(); const [key, setKey] = useState(0); const refresh = () => setKey(new Date().getTime()); useEffect(() => { return useFetch( async () => { const defaultClientScopes = await adminClient.clients.listDefaultClientScopes( { id: clientId } ); const optionalClientScopes = await adminClient.clients.listOptionalClientScopes( { id: clientId } ); const clientScopes = await adminClient.clientScopes.find(); const find = (id: string) => clientScopes.find((clientScope) => id === clientScope.id)!; const optional = optionalClientScopes.map((c) => { const scope = find(c.id!); return { selected: false, clientScope: c, type: ClientScope.optional, cells: [c.name, c.id, scope.description], }; }); const defaultScopes = defaultClientScopes.map((c) => { const scope = find(c.id!); return { selected: false, clientScope: c, type: ClientScope.default, cells: [c.name, c.id, scope.description], }; }); const rows = [...optional, ...defaultScopes]; const names = rows.map((row) => row.cells[0]); const rest = clientScopes .filter((scope) => !names.includes(scope.name)) .filter((scope) => scope.protocol === protocol); return { rows, rest }; }, ({ rows, rest }) => { setRows(rows); setRest(rest); } ); }, [key]); const dropdown = (): IFormatter => (data?: IFormatterValueType) => { if (!data) { return <>; } const row = rows?.find((row) => row.clientScope.id === data.toString())!; return ( { try { await changeScope( adminClient, clientId, row.clientScope, row.type, value ); addAlert(t("clientScopeSuccess"), AlertVariant.success); await refresh(); } catch (error) { addAlert(t("clientScopeError", { error }), AlertVariant.danger); } }} /> ); }; const filterData = () => {}; return ( <> {!rows && (
)} {rest && ( setAddDialogOpen(!addDialogOpen)} onAdd={async (scopes) => { try { await Promise.all( scopes.map( async (scope) => await addScope( adminClient, clientId, scope.scope, scope.type ) ) ); addAlert(t("clientScopeSuccess"), AlertVariant.success); refresh(); } catch (error) { addAlert(t("clientScopeError", { error }), AlertVariant.danger); } }} /> )} {rows && rows.length > 0 && ( setSearchToggle(!searchToggle)} > {t(`clientScopeSearch.${searchType}`)} } aria-label="Select Input" isOpen={searchToggle} dropdownItems={[ { setSearchType("client"); setSearchToggle(false); }} > {t("clientScopeSearch.client")} , { setSearchType("assigned"); setSearchToggle(false); }} > {t("clientScopeSearch.assigned")} , ]} /> } inputGroupName="clientsScopeToolbarTextInput" inputGroupPlaceholder={t("searchByName")} inputGroupOnChange={filterData} toolbarItem={ <> {}} toggle={ setKebabOpen(!kebabOpen)} /> } isOpen={kebabOpen} isPlain dropdownItems={[ row.selected).length === 0 } onClick={async () => { try { await Promise.all( rows.map(async (row) => { if (row.selected) { await removeScope( adminClient, clientId, row.clientScope, row.type ); } }) ); setKebabOpen(false); addAlert( t("clientScopeRemoveSuccess"), AlertVariant.success ); refresh(); } catch (error) { addAlert( t("clientScopeRemoveError", { error }), AlertVariant.danger ); } }} > {t("common:remove")} , ]} /> } > { if (rowIndex === -1) { setRows( rows.map((row) => { row.selected = isSelected; return row; }) ); } else { rows[rowIndex].selected = isSelected; setRows([...rows]); } }} variant={TableVariant.compact} cells={[ t("name"), { title: t("assignedType"), cellFormatters: [dropdown()] }, t("description"), ]} rows={rows} actions={[ { title: t("common:remove"), onClick: async (_, rowId) => { try { await removeScope( adminClient, clientId, rows[rowId].clientScope, rows[rowId].type ); addAlert( t("clientScopeRemoveSuccess"), AlertVariant.success ); refresh(); } catch (error) { addAlert( t("clientScopeRemoveError", { error }), AlertVariant.danger ); } }, }, ]} aria-label={t("clientScopeList")} >
)} {rows && rows.length === 0 && ( setAddDialogOpen(true)} /> )} ); };