import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { AlertVariant, Button, ButtonVariant, Dropdown, DropdownItem, KebabToggle, PageSection, ToolbarItem, } from "@patternfly/react-core"; import { cellWidth } from "@patternfly/react-table"; import { useAdminClient } from "../context/auth/AdminClient"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useAlerts } from "../components/alert/Alerts"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useRealm } from "../context/realm-context/RealmContext"; import { emptyFormatter } from "../util"; import { CellDropdown, ClientScope, AllClientScopes, ClientScopeDefaultOptionalType, changeScope, removeScope, AllClientScopeType, } from "../components/client-scope/ClientScopeTypes"; import { ChangeTypeDropdown } from "./ChangeTypeDropdown"; import { toNewClientScope } from "./routes/NewClientScope"; import "./client-scope.css"; import { toClientScope } from "./routes/ClientScope"; import { useWhoAmI } from "../context/whoami/WhoAmI"; import { nameFilter, protocolFilter, ProtocolType, SearchDropdown, SearchToolbar, SearchType, typeFilter, } from "./details/SearchFilter"; import type { Row } from "../clients/scopes/ClientScopes"; import { getProtocolName } from "../clients/utils"; import helpUrls from "../help-urls"; export default function ClientScopesSection() { const { realm } = useRealm(); const { whoAmI } = useWhoAmI(); const { t } = useTranslation("client-scopes"); const adminClient = useAdminClient(); const { addAlert, addError } = useAlerts(); const [kebabOpen, setKebabOpen] = useState(false); const [selectedScopes, setSelectedScopes] = useState< ClientScopeDefaultOptionalType[] >([]); const [searchType, setSearchType] = useState("name"); const [searchTypeType, setSearchTypeType] = useState( AllClientScopes.none ); const [searchProtocol, setSearchProtocol] = useState("all"); const [key, setKey] = useState(0); const refresh = () => { setSelectedScopes([]); setKey(key + 1); }; const loader = async (first?: number, max?: number, search?: string) => { const defaultScopes = await adminClient.clientScopes.listDefaultClientScopes(); const optionalScopes = await adminClient.clientScopes.listDefaultOptionalClientScopes(); const clientScopes = await adminClient.clientScopes.find(); const filter = searchType === "name" ? nameFilter(search) : searchType === "type" ? typeFilter(searchTypeType) : protocolFilter(searchProtocol); return clientScopes .map((scope) => { const row: Row = { ...scope, type: defaultScopes.find( (defaultScope) => defaultScope.name === scope.name ) ? ClientScope.default : optionalScopes.find( (optionalScope) => optionalScope.name === scope.name ) ? ClientScope.optional : AllClientScopes.none, }; return row; }) .filter(filter) .sort((a, b) => a.name!.localeCompare(b.name!, whoAmI.getLocale())) .slice(first, Number(first) + Number(max)); }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: t("deleteClientScope", { count: selectedScopes.length, name: selectedScopes[0]?.name, }), messageKey: "client-scopes:deleteConfirm", continueButtonLabel: "common:delete", continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { for (const scope of selectedScopes) { try { await removeScope(adminClient, scope); } catch (error: any) { console.warn( "could not remove scope", error.response?.data?.errorMessage || error ); } await adminClient.clientScopes.del({ id: scope.id! }); } addAlert(t("deletedSuccess"), AlertVariant.success); refresh(); } catch (error) { addError("client-scopes:deleteError", error); } }, }); const TypeSelector = (scope: ClientScopeDefaultOptionalType) => ( { try { await changeScope(adminClient, scope, value as AllClientScopeType); addAlert(t("clientScopeSuccess"), AlertVariant.success); refresh(); } catch (error) { addError("client-scopes:clientScopeError", error); } }} /> ); const ClientScopeDetailLink = ({ id, type, name, }: ClientScopeDefaultOptionalType) => ( {name} ); return ( <> setSearchType(searchType)} withProtocol /> } isPaginated onSelect={(clientScopes) => setSelectedScopes([...clientScopes])} canSelectAll toolbarItem={ <> setSearchType(searchType)} onType={(value) => { setSearchTypeType(value); refresh(); }} protocol={searchProtocol} onProtocol={(protocol) => { setSearchProtocol(protocol); refresh(); }} /> } isOpen={kebabOpen} isPlain dropdownItems={[ { toggleDeleteDialog(); setKebabOpen(false); }} > {t("common:delete")} , ]} /> } actions={[ { title: t("common:delete"), onRowClick: (clientScope) => { setSelectedScopes([clientScope]); toggleDeleteDialog(); }, }, ]} columns={[ { name: "name", cellRenderer: ClientScopeDetailLink, }, { name: "type", displayKey: "client-scopes:assignedType", cellRenderer: TypeSelector, }, { name: "protocol", displayKey: "client-scopes:protocol", cellRenderer: (client) => getProtocolName(t, client.protocol ?? "openid-connect"), transforms: [cellWidth(15)], }, { name: "attributes['gui.order']", displayKey: "client-scopes:displayOrder", cellFormatters: [emptyFormatter()], transforms: [cellWidth(15)], }, { name: "description", cellFormatters: [emptyFormatter()] }, ]} /> ); }