dfc4beced4
* changed to use the keycloak datatable so it will benifid from a general way tables work in the admin console * null check * changed to use any active tab seems sometimes the tab doesn't get set properly so instead of "mappers" it's still "settings" this change makes it work with both
371 lines
11 KiB
TypeScript
371 lines
11 KiB
TypeScript
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,
|
|
} from "./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 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 (
|
|
<Select
|
|
key={clientScope.id}
|
|
onToggle={() => setOpen(!open)}
|
|
isOpen={open}
|
|
selections={[type]}
|
|
onSelect={(_, value) => {
|
|
onSelect(value as ClientScopeType);
|
|
setOpen(false);
|
|
}}
|
|
>
|
|
{clientScopeTypesSelectOptions(t)}
|
|
</Select>
|
|
);
|
|
};
|
|
|
|
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<SearchType>("client");
|
|
const [addToggle, setAddToggle] = useState(false);
|
|
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
|
const [kebabOpen, setKebabOpen] = useState(false);
|
|
|
|
const [rest, setRest] = useState<ClientScopeRepresentation[]>();
|
|
const [selectedRows, setSelectedRows] = useState<Row[]>([]);
|
|
|
|
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) => (
|
|
<>
|
|
<CellDropdown
|
|
clientScope={scope}
|
|
type={scope.type}
|
|
onSelect={async (value) => {
|
|
try {
|
|
await changeScope(adminClient, clientId, scope, scope.type, value);
|
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
|
refresh();
|
|
} catch (error) {
|
|
addAlert(t("clientScopeError", { error }), AlertVariant.danger);
|
|
}
|
|
}}
|
|
/>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{rest && (
|
|
<AddScopeDialog
|
|
clientScopes={rest}
|
|
open={addDialogOpen}
|
|
toggleDialog={() => 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);
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
<KeycloakDataTable
|
|
key={key}
|
|
loader={loader}
|
|
ariaLabelKey="clients:clientScopeList"
|
|
searchPlaceholderKey="clients:searchByName"
|
|
onSelect={(rows) => setSelectedRows([...rows])}
|
|
searchTypeComponent={
|
|
<Dropdown
|
|
toggle={
|
|
<DropdownToggle
|
|
id="toggle-id"
|
|
onToggle={() => setSearchToggle(!searchToggle)}
|
|
>
|
|
<FilterIcon /> {t(`clientScopeSearch.${searchType}`)}
|
|
</DropdownToggle>
|
|
}
|
|
aria-label="Select Input"
|
|
isOpen={searchToggle}
|
|
dropdownItems={[
|
|
<DropdownItem
|
|
key="client"
|
|
onClick={() => {
|
|
setSearchType("client");
|
|
setSearchToggle(false);
|
|
}}
|
|
>
|
|
{t("clientScopeSearch.client")}
|
|
</DropdownItem>,
|
|
<DropdownItem
|
|
key="assigned"
|
|
onClick={() => {
|
|
setSearchType("assigned");
|
|
setSearchToggle(false);
|
|
}}
|
|
>
|
|
{t("clientScopeSearch.assigned")}
|
|
</DropdownItem>,
|
|
]}
|
|
/>
|
|
}
|
|
toolbarItem={
|
|
<>
|
|
<ToolbarItem>
|
|
<Button onClick={() => setAddDialogOpen(true)}>
|
|
{t("addClientScope")}
|
|
</Button>
|
|
</ToolbarItem>
|
|
<ToolbarItem>
|
|
<Select
|
|
id="add-dropdown"
|
|
key="add-dropdown"
|
|
isOpen={addToggle}
|
|
selections={[]}
|
|
placeholderText={t("changeTypeTo")}
|
|
onToggle={() => setAddToggle(!addToggle)}
|
|
onSelect={async (_, value) => {
|
|
try {
|
|
await Promise.all(
|
|
selectedRows.map((row) => {
|
|
return changeScope(
|
|
adminClient,
|
|
clientId,
|
|
{ ...row },
|
|
row.type,
|
|
value as ClientScope
|
|
);
|
|
})
|
|
);
|
|
setAddToggle(false);
|
|
refresh();
|
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
|
} catch (error) {
|
|
addAlert(
|
|
t("clientScopeError", { error }),
|
|
AlertVariant.danger
|
|
);
|
|
}
|
|
}}
|
|
>
|
|
{clientScopeTypesSelectOptions(t)}
|
|
</Select>
|
|
</ToolbarItem>
|
|
<ToolbarItem>
|
|
<Dropdown
|
|
onSelect={() => {}}
|
|
toggle={
|
|
<KebabToggle onToggle={() => setKebabOpen(!kebabOpen)} />
|
|
}
|
|
isOpen={kebabOpen}
|
|
isPlain
|
|
dropdownItems={[
|
|
<DropdownItem
|
|
key="deleteAll"
|
|
isDisabled={selectedRows.length === 0}
|
|
onClick={async () => {
|
|
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")}
|
|
</DropdownItem>,
|
|
]}
|
|
/>
|
|
</ToolbarItem>
|
|
</>
|
|
}
|
|
columns={[
|
|
{
|
|
name: "name",
|
|
},
|
|
{
|
|
name: "type",
|
|
displayKey: "clients:assignedType",
|
|
cellRenderer: TypeSelector,
|
|
},
|
|
{ name: "description" },
|
|
]}
|
|
emptyState={
|
|
<ListEmptyState
|
|
message={t("clients:emptyClientScopes")}
|
|
instructions={t("clients:emptyClientScopesInstructions")}
|
|
primaryActionText={t("clients:emptyClientScopesPrimaryAction")}
|
|
onPrimaryAction={() => setAddDialogOpen(true)}
|
|
/>
|
|
}
|
|
/>
|
|
</>
|
|
);
|
|
};
|