import { useState } from "react"; import { Link, useNavigate } from "react-router-dom-v5-compat"; import { useTranslation } from "react-i18next"; import { Alert, AlertVariant, Button, PageSection, ToolbarItem, } from "@patternfly/react-core"; import { ExpandableRowContent, TableComposable, Tbody, Td, Th, Thead, Tr, } from "@patternfly/react-table"; import type ResourceServerRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceServerRepresentation"; import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation"; import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; import { PaginatingTableToolbar } from "../../components/table-toolbar/PaginatingTableToolbar"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; import { useAlerts } from "../../components/alert/Alerts"; import { DetailCell } from "./DetailCell"; import { toCreateResource } from "../routes/NewResource"; import { useRealm } from "../../context/realm-context/RealmContext"; import { toResourceDetails } from "../routes/Resource"; import { MoreLabel } from "./MoreLabel"; import { toNewPermission } from "../routes/NewPermission"; import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; import { SearchDropdown, SearchForm } from "./SearchDropdown"; type ResourcesProps = { clientId: string; }; type ExpandableResourceRepresentation = ResourceRepresentation & { isExpanded: boolean; }; export const AuthorizationResources = ({ clientId }: ResourcesProps) => { const { t } = useTranslation("clients"); const navigate = useNavigate(); const { adminClient } = useAdminClient(); const { addAlert, addError } = useAlerts(); const { realm } = useRealm(); const [resources, setResources] = useState(); const [selectedResource, setSelectedResource] = useState(); const [permissions, setPermission] = 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( () => { const params = { first, max: max + 1, deep: false, ...search, }; return adminClient.clients.listResources({ ...params, id: clientId, }); }, (resources) => setResources( resources.map((resource) => ({ ...resource, isExpanded: false })) ), [key, search, first, max] ); const UriRenderer = ({ row }: { row: ResourceRepresentation }) => ( <> {row.uris?.[0]} ); const fetchPermissions = async (id: string) => { return adminClient.clients.listPermissionsByResource({ id: clientId, resourceId: id, }); }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "clients:deleteResource", children: ( <> {t("deleteResourceConfirm")} {permissions?.length && (

{permissions.map((permission) => ( {permission.name} ))}

)} ), continueButtonLabel: "clients:confirm", onConfirm: async () => { try { await adminClient.clients.delResource({ id: clientId, resourceId: selectedResource?._id!, }); addAlert(t("resourceDeletedSuccess"), AlertVariant.success); refresh(); } catch (error) { addError("clients:resourceDeletedError", error); } }, }); if (!resources) { return ; } const noData = resources.length === 0; const searching = Object.keys(search).length !== 0; return ( {(!noData || searching) && ( { setFirst(first); setMax(max); }} toolbarItem={ <> } > {!noData && ( {t("common:name")} {t("common:type")} {t("owner")} {t("uris")} {resources.map((resource, rowIndex) => ( { const rows = resources.map((resource, index) => index === rowIndex ? { ...resource, isExpanded: !resource.isExpanded, } : resource ); setResources(rows); }, }} /> {resource.name} {resource.type} {resource.owner?.name} { setSelectedResource(resource); setPermission( await fetchPermissions(resource._id!) ); toggleDeleteDialog(); }, }, ], }} /> {resource.isExpanded && ( )} ))} )} )} {noData && searching && ( )} {noData && !searching && ( navigate(toCreateResource({ realm, id: clientId })) } /> )} ); };