import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { AlertVariant, Button, Dropdown, DropdownItem, DropdownToggle, KebabToggle, Select, 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 } from "../../context/auth/AdminClient"; import { toUpperCase } from "../../util"; import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; import { AddScopeDialog } from "./AddScopeDialog"; import { clientScopeTypesSelectOptions, ClientScopeType, ClientScope, CellDropdown, } from "../../components/client-scope/ClientScopeTypes"; import { useAlerts } from "../../components/alert/Alerts"; import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable"; export type ClientScopesProps = { clientId: string; protocol: string; }; type Row = ClientScopeRepresentation & { type: ClientScopeType; description: string; }; 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 = toUpperCase(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 = toUpperCase(type); await castAdminClient(adminClient)[`add${typeToName}ClientScope`]({ id: clientId, clientScopeId: clientScope.id!, }); }; type SearchType = "client" | "assigned"; 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 [rest, setRest] = useState(); const [selectedRows, setSelectedRows] = useState([]); const [key, setKey] = useState(0); const refresh = () => setKey(new Date().getTime()); const loader = 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 { ...c, type: ClientScope.optional, description: scope.description, } as Row; }); const defaultScopes = defaultClientScopes.map((c) => { const scope = find(c.id!); return { ...c, type: ClientScope.default, description: scope.description, } as Row; }); const rows = [...optional, ...defaultScopes]; const names = rows.map((row) => row.name); setRest( clientScopes .filter((scope) => !names.includes(scope.name)) .filter((scope) => scope.protocol === protocol) ); return rows; }; const TypeSelector = (scope: Row) => ( <> { try { await changeScope( adminClient, clientId, scope, scope.type, value as ClientScope ); addAlert(t("clientScopeSuccess"), AlertVariant.success); refresh(); } catch (error) { addAlert(t("clientScopeError", { error }), AlertVariant.danger); } }} /> ); return ( <> {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); } }} /> )} setSelectedRows([...rows])} searchTypeComponent={ 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")} , ]} /> } toolbarItem={ <> {}} toggle={ setKebabOpen(!kebabOpen)} /> } isOpen={kebabOpen} isPlain dropdownItems={[ { try { await Promise.all( selectedRows.map(async (row) => { await removeScope( adminClient, clientId, { ...row }, row.type ); }) ); setKebabOpen(false); addAlert( t("clientScopeRemoveSuccess"), AlertVariant.success ); refresh(); } catch (error) { addAlert( t("clientScopeRemoveError", { error }), AlertVariant.danger ); } }} > {t("common:remove")} , ]} /> } columns={[ { name: "name", }, { name: "type", displayKey: "clients:assignedType", cellRenderer: TypeSelector, }, { name: "description" }, ]} emptyState={ setAddDialogOpen(true)} /> } /> ); };