import React, { useState } from "react"; import { Link, useHistory } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Button, DescriptionList, PageSection, ToolbarItem, } from "@patternfly/react-core"; import { ExpandableRowContent, TableComposable, Tbody, Td, Th, Thead, Tr, } from "@patternfly/react-table"; import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation"; import type PolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/policyRepresentation"; import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner"; import { PaginatingTableToolbar } from "../../components/table-toolbar/PaginatingTableToolbar"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; import { useRealm } from "../../context/realm-context/RealmContext"; import { MoreLabel } from "./MoreLabel"; import { toScopeDetails } from "../routes/Scope"; import { toNewScope } from "../routes/NewScope"; import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; import useToggle from "../../utils/useToggle"; import { DeleteScopeDialog } from "./DeleteScopeDialog"; import { DetailDescription } from "./DetailDescription"; import { toNewPermission } from "../routes/NewPermission"; type ScopesProps = { clientId: string; }; export type ExpandableScopeRepresentation = ScopeRepresentation & { permissions?: PolicyRepresentation[]; isExpanded: boolean; }; export const AuthorizationScopes = ({ clientId }: ScopesProps) => { const { t } = useTranslation("clients"); const history = useHistory(); const adminClient = useAdminClient(); const { realm } = useRealm(); const [deleteDialog, toggleDeleteDialog] = useToggle(); const [scopes, setScopes] = useState(); const [selectedScope, setSelectedScope] = useState(); const [key, setKey] = useState(0); const refresh = () => setKey(key + 1); const [max, setMax] = useState(10); const [first, setFirst] = useState(0); const [search, setSearch] = useState(""); useFetch( async () => { const params = { first, max, deep: false, name: search, }; const scopes = await adminClient.clients.listAllScopes({ ...params, id: clientId, }); return await Promise.all( scopes.map(async (scope) => { const options = { id: clientId, scopeId: scope.id! }; const [resources, permissions] = await Promise.all([ adminClient.clients.listAllResourcesByScope(options), adminClient.clients.listAllPermissionsByScope(options), ]); return { ...scope, resources, permissions, isExpanded: false, }; }) ); }, setScopes, [key, search] ); const ResourceRenderer = ({ row, }: { row: ExpandableScopeRepresentation; }) => { return ( <> {row.resources?.[0]?.name} ); }; const PermissionsRenderer = ({ row, }: { row: ExpandableScopeRepresentation; }) => { return ( <> {row.permissions?.[0]?.name} ); }; if (!scopes) { return ; } const noData = scopes.length === 0; const searching = search !== ""; return ( {(!noData || searching) && ( { setFirst(first); setMax(max); }} inputGroupName="search" inputGroupPlaceholder={t("searchByName")} inputGroupOnEnter={setSearch} toolbarItem={ } > {!noData && ( {t("common:name")} {t("resources")} {t("permissions")} {scopes.map((scope, rowIndex) => ( { const rows = scopes.map((resource, index) => index === rowIndex ? { ...resource, isExpanded: !resource.isExpanded, } : resource ); setScopes(rows); }, }} /> {scope.name} { setSelectedScope(scope); toggleDeleteDialog(); }, }, ], }} /> {scope.isExpanded && ( r.name!} /> p.name!} /> )} ))} )} )} {noData && !searching && ( history.push(toNewScope({ id: clientId, realm })) } primaryActionText={t("createAuthorizationScope")} /> )} {noData && searching && ( )} ); };