2020-11-24 20:11:13 +00:00
|
|
|
import React, { useState, useEffect } from "react";
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import {
|
|
|
|
IFormatter,
|
|
|
|
IFormatterValueType,
|
|
|
|
Table,
|
|
|
|
TableBody,
|
|
|
|
TableHeader,
|
|
|
|
TableVariant,
|
|
|
|
} from "@patternfly/react-table";
|
|
|
|
import {
|
2020-12-04 21:08:11 +00:00
|
|
|
AlertVariant,
|
2020-11-24 20:11:13 +00:00
|
|
|
Button,
|
|
|
|
Dropdown,
|
|
|
|
DropdownItem,
|
|
|
|
DropdownToggle,
|
2020-12-07 13:42:08 +00:00
|
|
|
KebabToggle,
|
2020-11-24 20:11:13 +00:00
|
|
|
Select,
|
|
|
|
Spinner,
|
2020-12-07 13:42:08 +00:00
|
|
|
ToolbarItem,
|
2020-11-24 20:11:13 +00:00
|
|
|
} 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 { TableToolbar } from "../../components/table-toolbar/TableToolbar";
|
|
|
|
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
|
|
|
import { AddScopeDialog } from "./AddScopeDialog";
|
|
|
|
import {
|
|
|
|
clientScopeTypesSelectOptions,
|
|
|
|
ClientScopeType,
|
|
|
|
ClientScope,
|
|
|
|
} from "./ClientScopeTypes";
|
2020-12-04 21:08:11 +00:00
|
|
|
import { useAlerts } from "../../components/alert/Alerts";
|
2020-11-24 20:11:13 +00:00
|
|
|
|
|
|
|
export type ClientScopesProps = {
|
|
|
|
clientId: string;
|
|
|
|
protocol: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
const firstUpperCase = (name: string) =>
|
|
|
|
name.charAt(0).toUpperCase() + name.slice(1);
|
|
|
|
|
2020-12-04 21:08:11 +00:00
|
|
|
const castAdminClient = (adminClient: KeycloakAdminClient) =>
|
|
|
|
(adminClient.clients as unknown) as {
|
|
|
|
[index: string]: Function;
|
|
|
|
};
|
|
|
|
|
2020-11-24 20:11:13 +00:00
|
|
|
const changeScope = async (
|
|
|
|
adminClient: KeycloakAdminClient,
|
|
|
|
clientId: string,
|
|
|
|
clientScope: ClientScopeRepresentation,
|
|
|
|
type: ClientScopeType,
|
|
|
|
changeTo: ClientScopeType
|
|
|
|
) => {
|
2020-12-04 21:08:11 +00:00
|
|
|
await removeScope(adminClient, clientId, clientScope, type);
|
|
|
|
await addScope(adminClient, clientId, clientScope, changeTo);
|
|
|
|
};
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2020-12-04 21:08:11 +00:00
|
|
|
const removeScope = async (
|
|
|
|
adminClient: KeycloakAdminClient,
|
|
|
|
clientId: string,
|
|
|
|
clientScope: ClientScopeRepresentation,
|
|
|
|
type: ClientScopeType
|
|
|
|
) => {
|
|
|
|
const typeToName = firstUpperCase(type);
|
|
|
|
await castAdminClient(adminClient)[`del${typeToName}ClientScope`]({
|
2020-11-24 20:11:13 +00:00
|
|
|
id: clientId,
|
|
|
|
clientScopeId: clientScope.id!,
|
|
|
|
});
|
2020-12-04 21:08:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const addScope = async (
|
|
|
|
adminClient: KeycloakAdminClient,
|
|
|
|
clientId: string,
|
|
|
|
clientScope: ClientScopeRepresentation,
|
|
|
|
type: ClientScopeType
|
|
|
|
) => {
|
|
|
|
const typeToName = firstUpperCase(type);
|
|
|
|
await castAdminClient(adminClient)[`add${typeToName}ClientScope`]({
|
2020-11-24 20:11:13 +00:00
|
|
|
id: clientId,
|
|
|
|
clientScopeId: clientScope.id!,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
type CellDropdownProps = {
|
|
|
|
clientScope: ClientScopeRepresentation;
|
|
|
|
type: ClientScopeType;
|
2020-12-04 21:08:11 +00:00
|
|
|
onSelect: (value: ClientScopeType) => void;
|
2020-11-24 20:11:13 +00:00
|
|
|
};
|
|
|
|
|
2020-12-04 21:08:11 +00:00
|
|
|
const CellDropdown = ({ clientScope, type, onSelect }: CellDropdownProps) => {
|
2020-11-24 20:11:13 +00:00
|
|
|
const { t } = useTranslation("clients");
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Select
|
|
|
|
key={clientScope.id}
|
|
|
|
onToggle={() => setOpen(!open)}
|
|
|
|
isOpen={open}
|
|
|
|
selections={[type]}
|
|
|
|
onSelect={(_, value) => {
|
2020-12-04 21:08:11 +00:00
|
|
|
onSelect(value as ClientScopeType);
|
2020-11-24 20:11:13 +00:00
|
|
|
setOpen(false);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{clientScopeTypesSelectOptions(t)}
|
|
|
|
</Select>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
type SearchType = "client" | "assigned";
|
|
|
|
|
|
|
|
type TableRow = {
|
2020-12-04 21:08:11 +00:00
|
|
|
selected: boolean;
|
2020-11-24 20:11:13 +00:00
|
|
|
clientScope: ClientScopeRepresentation;
|
|
|
|
type: ClientScopeType;
|
|
|
|
cells: (string | undefined)[];
|
|
|
|
};
|
|
|
|
|
|
|
|
export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|
|
|
const { t } = useTranslation("clients");
|
|
|
|
const adminClient = useAdminClient();
|
2020-12-04 21:08:11 +00:00
|
|
|
const { addAlert } = useAlerts();
|
|
|
|
|
2020-11-24 20:11:13 +00:00
|
|
|
const [searchToggle, setSearchToggle] = useState(false);
|
|
|
|
const [searchType, setSearchType] = useState<SearchType>("client");
|
|
|
|
const [addToggle, setAddToggle] = useState(false);
|
|
|
|
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
2020-12-07 13:42:08 +00:00
|
|
|
const [kebabOpen, setKebabOpen] = useState(false);
|
2020-11-24 20:11:13 +00:00
|
|
|
|
|
|
|
const [rows, setRows] = useState<TableRow[]>();
|
|
|
|
const [rest, setRest] = useState<ClientScopeRepresentation[]>();
|
|
|
|
|
|
|
|
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 {
|
2020-12-04 21:08:11 +00:00
|
|
|
selected: false,
|
2020-11-24 20:11:13 +00:00
|
|
|
clientScope: c,
|
|
|
|
type: ClientScope.optional,
|
|
|
|
cells: [c.name, c.id, scope.description],
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const defaultScopes = defaultClientScopes.map((c) => {
|
|
|
|
const scope = find(c.id!);
|
|
|
|
return {
|
2020-12-04 21:08:11 +00:00
|
|
|
selected: false,
|
2020-11-24 20:11:13 +00:00
|
|
|
clientScope: c,
|
|
|
|
type: ClientScope.default,
|
|
|
|
cells: [c.name, c.id, scope.description],
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2020-12-04 21:08:11 +00:00
|
|
|
const data = [...optional, ...defaultScopes];
|
|
|
|
setRows(data);
|
|
|
|
const names = data.map((row) => row.cells[0]);
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2020-12-04 21:08:11 +00:00
|
|
|
console.log("set rest");
|
2020-11-24 20:11:13 +00:00
|
|
|
setRest(
|
|
|
|
clientScopes
|
|
|
|
.filter((scope) => !names.includes(scope.name))
|
|
|
|
.filter((scope) => scope.protocol === protocol)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-12-04 21:08:11 +00:00
|
|
|
useEffect(() => {
|
|
|
|
loader();
|
|
|
|
}, []);
|
|
|
|
|
2020-11-24 20:11:13 +00:00
|
|
|
const dropdown = (): IFormatter => (data?: IFormatterValueType) => {
|
|
|
|
if (!data) {
|
|
|
|
return <></>;
|
|
|
|
}
|
|
|
|
const row = rows?.find((row) => row.clientScope.id === data.toString())!;
|
|
|
|
return (
|
|
|
|
<CellDropdown
|
|
|
|
clientScope={row.clientScope}
|
|
|
|
type={row.type}
|
2020-12-04 21:08:11 +00:00
|
|
|
onSelect={async (value) => {
|
|
|
|
try {
|
|
|
|
await changeScope(
|
|
|
|
adminClient,
|
|
|
|
clientId,
|
|
|
|
row.clientScope,
|
|
|
|
row.type,
|
|
|
|
value
|
|
|
|
);
|
|
|
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
|
|
|
await loader();
|
|
|
|
} catch (error) {
|
|
|
|
addAlert(t("clientScopeError", { error }), AlertVariant.danger);
|
|
|
|
}
|
|
|
|
}}
|
2020-11-24 20:11:13 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const filterData = () => {};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{!rows && (
|
|
|
|
<div className="pf-u-text-align-center">
|
|
|
|
<Spinner />
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
2020-12-04 21:08:11 +00:00
|
|
|
{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);
|
|
|
|
loader();
|
|
|
|
} catch (error) {
|
|
|
|
addAlert(t("clientScopeError", { error }), AlertVariant.danger);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
|
2020-11-24 20:11:13 +00:00
|
|
|
{rows && rows.length > 0 && (
|
2020-12-04 21:08:11 +00:00
|
|
|
<TableToolbar
|
|
|
|
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>,
|
|
|
|
]}
|
2020-11-24 20:11:13 +00:00
|
|
|
/>
|
2020-12-04 21:08:11 +00:00
|
|
|
}
|
|
|
|
inputGroupName="clientsScopeToolbarTextInput"
|
|
|
|
inputGroupPlaceholder={t("searchByName")}
|
|
|
|
inputGroupOnChange={filterData}
|
|
|
|
toolbarItem={
|
2020-12-07 13:42:08 +00:00
|
|
|
<>
|
|
|
|
<ToolbarItem>
|
2020-12-04 21:08:11 +00:00
|
|
|
<Button onClick={() => setAddDialogOpen(true)}>
|
|
|
|
{t("addClientScope")}
|
|
|
|
</Button>
|
2020-12-07 13:42:08 +00:00
|
|
|
</ToolbarItem>
|
|
|
|
<ToolbarItem>
|
2020-12-04 21:08:11 +00:00
|
|
|
<Select
|
|
|
|
id="add-dropdown"
|
|
|
|
key="add-dropdown"
|
|
|
|
isOpen={addToggle}
|
|
|
|
selections={[]}
|
|
|
|
placeholderText={t("changeTypeTo")}
|
|
|
|
onToggle={() => setAddToggle(!addToggle)}
|
|
|
|
onSelect={async (_, value) => {
|
|
|
|
try {
|
|
|
|
await Promise.all(
|
|
|
|
rows.map((row) => {
|
|
|
|
if (row.selected) {
|
|
|
|
return changeScope(
|
|
|
|
adminClient,
|
|
|
|
clientId,
|
|
|
|
row.clientScope,
|
|
|
|
row.type,
|
|
|
|
value as ClientScope
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return Promise.resolve();
|
|
|
|
})
|
|
|
|
);
|
2020-11-24 20:11:13 +00:00
|
|
|
setAddToggle(false);
|
2020-12-04 21:08:11 +00:00
|
|
|
await loader();
|
|
|
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
|
|
|
} catch (error) {
|
|
|
|
addAlert(
|
|
|
|
t("clientScopeError", { error }),
|
|
|
|
AlertVariant.danger
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{clientScopeTypesSelectOptions(t)}
|
|
|
|
</Select>
|
2020-12-07 13:42:08 +00:00
|
|
|
</ToolbarItem>
|
|
|
|
<ToolbarItem>
|
|
|
|
<Dropdown
|
|
|
|
onSelect={() => {}}
|
|
|
|
toggle={
|
|
|
|
<KebabToggle onToggle={() => setKebabOpen(!kebabOpen)} />
|
|
|
|
}
|
|
|
|
isOpen={kebabOpen}
|
|
|
|
isPlain
|
|
|
|
dropdownItems={[
|
|
|
|
<DropdownItem
|
|
|
|
key="deleteAll"
|
|
|
|
onClick={async () => {
|
|
|
|
try {
|
|
|
|
await Promise.all(
|
|
|
|
rows.map(async (row) => {
|
|
|
|
if (row.selected) {
|
|
|
|
await removeScope(
|
|
|
|
adminClient,
|
|
|
|
clientId,
|
|
|
|
row.clientScope,
|
|
|
|
row.type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
setKebabOpen(false);
|
|
|
|
addAlert(
|
|
|
|
t("clientScopeRemoveSuccess"),
|
|
|
|
AlertVariant.success
|
|
|
|
);
|
|
|
|
loader();
|
|
|
|
} catch (error) {
|
|
|
|
addAlert(
|
|
|
|
t("clientScopeRemoveError", { error }),
|
|
|
|
AlertVariant.danger
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t("common:remove")}
|
|
|
|
</DropdownItem>,
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
</ToolbarItem>
|
|
|
|
</>
|
2020-12-04 21:08:11 +00:00
|
|
|
}
|
|
|
|
>
|
|
|
|
<Table
|
|
|
|
onSelect={(_, isSelected, rowIndex) => {
|
|
|
|
if (rowIndex === -1) {
|
|
|
|
setRows(
|
|
|
|
rows.map((row) => {
|
|
|
|
row.selected = isSelected;
|
|
|
|
return row;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
rows[rowIndex].selected = isSelected;
|
|
|
|
setRows([...rows]);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
variant={TableVariant.compact}
|
|
|
|
cells={[
|
|
|
|
t("name"),
|
|
|
|
{ title: t("assignedType"), cellFormatters: [dropdown()] },
|
|
|
|
t("description"),
|
|
|
|
]}
|
|
|
|
rows={rows}
|
|
|
|
actions={[
|
|
|
|
{
|
|
|
|
title: t("common:remove"),
|
|
|
|
onClick: async (_, rowId) => {
|
|
|
|
try {
|
|
|
|
await removeScope(
|
|
|
|
adminClient,
|
|
|
|
clientId,
|
|
|
|
rows[rowId].clientScope,
|
|
|
|
rows[rowId].type
|
|
|
|
);
|
|
|
|
addAlert(
|
|
|
|
t("clientScopeRemoveSuccess"),
|
|
|
|
AlertVariant.success
|
|
|
|
);
|
|
|
|
loader();
|
|
|
|
} catch (error) {
|
|
|
|
addAlert(
|
|
|
|
t("clientScopeRemoveError", { error }),
|
|
|
|
AlertVariant.danger
|
|
|
|
);
|
|
|
|
}
|
2020-11-24 20:11:13 +00:00
|
|
|
},
|
2020-12-04 21:08:11 +00:00
|
|
|
},
|
|
|
|
]}
|
|
|
|
aria-label={t("clientScopeList")}
|
|
|
|
>
|
|
|
|
<TableHeader />
|
|
|
|
<TableBody />
|
|
|
|
</Table>
|
|
|
|
</TableToolbar>
|
2020-11-24 20:11:13 +00:00
|
|
|
)}
|
|
|
|
{rows && rows.length === 0 && (
|
|
|
|
<ListEmptyState
|
|
|
|
message={t("clients:emptyClientScopes")}
|
|
|
|
instructions={t("clients:emptyClientScopesInstructions")}
|
|
|
|
primaryActionText={t("clients:emptyClientScopesPrimaryAction")}
|
2020-12-04 21:08:11 +00:00
|
|
|
onPrimaryAction={() => setAddDialogOpen(true)}
|
2020-11-24 20:11:13 +00:00
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|