added by configuration dialog into the flow (#221)
* updated the detail screen to support new mapping * fix merge error * added by configuration dialog
This commit is contained in:
parent
8e33519df1
commit
ba761c0526
4 changed files with 138 additions and 65 deletions
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, useHistory } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
|
@ -43,6 +43,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||||
const { t } = useTranslation("client-scopes");
|
const { t } = useTranslation("client-scopes");
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
const [filteredData, setFilteredData] = useState<
|
const [filteredData, setFilteredData] = useState<
|
||||||
{ mapper: ProtocolMapperRepresentation; cells: Row }[]
|
{ mapper: ProtocolMapperRepresentation; cells: Row }[]
|
||||||
|
@ -53,12 +54,24 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||||
clientScope.protocol!
|
clientScope.protocol!
|
||||||
];
|
];
|
||||||
|
|
||||||
const [builtInDialogOpen, setBuiltInDialogOpen] = useState(false);
|
const [addMapperDialogOpen, setAddMapperDialogOpen] = useState(false);
|
||||||
const toggleBuiltInMapperDialog = () =>
|
const [filter, setFilter] = useState(clientScope.protocolMappers);
|
||||||
setBuiltInDialogOpen(!builtInDialogOpen);
|
const toggleAddMapperDialog = (buildIn: boolean) => {
|
||||||
|
if (buildIn) {
|
||||||
|
setFilter(mapperList);
|
||||||
|
} else {
|
||||||
|
setFilter(undefined);
|
||||||
|
}
|
||||||
|
setAddMapperDialogOpen(!addMapperDialogOpen);
|
||||||
|
};
|
||||||
|
|
||||||
const addMappers = async (
|
const addMappers = async (
|
||||||
mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[]
|
mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[]
|
||||||
) => {
|
): Promise<void> => {
|
||||||
|
if (filter === undefined) {
|
||||||
|
const mapper = mappers as ProtocolMapperTypeRepresentation;
|
||||||
|
history.push(`/client-scopes/${clientScope.id}/${mapper.id}`);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
await adminClient.clientScopes.addMultipleProtocolMappers(
|
await adminClient.clientScopes.addMultipleProtocolMappers(
|
||||||
{ id: clientScope.id! },
|
{ id: clientScope.id! },
|
||||||
|
@ -69,6 +82,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addAlert(t("mappingCreatedError", { error }), AlertVariant.danger);
|
addAlert(t("mappingCreatedError", { error }), AlertVariant.danger);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!mapperList) {
|
if (!mapperList) {
|
||||||
|
@ -76,20 +90,20 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||||
<>
|
<>
|
||||||
<AddMapperDialog
|
<AddMapperDialog
|
||||||
protocol={clientScope.protocol!}
|
protocol={clientScope.protocol!}
|
||||||
filter={mapperList || []}
|
filter={filter}
|
||||||
onConfirm={addMappers}
|
onConfirm={addMappers}
|
||||||
open={builtInDialogOpen}
|
open={addMapperDialogOpen}
|
||||||
toggleDialog={toggleBuiltInMapperDialog}
|
toggleDialog={() => setAddMapperDialogOpen(!addMapperDialogOpen)}
|
||||||
/>
|
/>
|
||||||
<ListEmptyState
|
<ListEmptyState
|
||||||
message={t("emptyMappers")}
|
message={t("emptyMappers")}
|
||||||
instructions={t("emptyMappersInstructions")}
|
instructions={t("emptyMappersInstructions")}
|
||||||
primaryActionText={t("emptyPrimaryAction")}
|
primaryActionText={t("emptyPrimaryAction")}
|
||||||
onPrimaryAction={toggleBuiltInMapperDialog}
|
onPrimaryAction={() => toggleAddMapperDialog(true)}
|
||||||
secondaryActions={[
|
secondaryActions={[
|
||||||
{
|
{
|
||||||
text: t("emptySecondaryAction"),
|
text: t("emptySecondaryAction"),
|
||||||
onClick: () => {},
|
onClick: () => toggleAddMapperDialog(false),
|
||||||
type: ButtonVariant.secondary,
|
type: ButtonVariant.secondary,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -149,10 +163,16 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||||
}
|
}
|
||||||
isOpen={mapperAction}
|
isOpen={mapperAction}
|
||||||
dropdownItems={[
|
dropdownItems={[
|
||||||
<DropdownItem key="predefined" onClick={toggleBuiltInMapperDialog}>
|
<DropdownItem
|
||||||
|
key="predefined"
|
||||||
|
onClick={() => toggleAddMapperDialog(true)}
|
||||||
|
>
|
||||||
{t("fromPredefinedMapper")}
|
{t("fromPredefinedMapper")}
|
||||||
</DropdownItem>,
|
</DropdownItem>,
|
||||||
<DropdownItem key="byConfiguration">
|
<DropdownItem
|
||||||
|
key="byConfiguration"
|
||||||
|
onClick={() => toggleAddMapperDialog(false)}
|
||||||
|
>
|
||||||
{t("byConfiguration")}
|
{t("byConfiguration")}
|
||||||
</DropdownItem>,
|
</DropdownItem>,
|
||||||
]}
|
]}
|
||||||
|
@ -161,10 +181,10 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
|
||||||
>
|
>
|
||||||
<AddMapperDialog
|
<AddMapperDialog
|
||||||
protocol={clientScope.protocol!}
|
protocol={clientScope.protocol!}
|
||||||
filter={mapperList || []}
|
filter={filter}
|
||||||
onConfirm={addMappers}
|
onConfirm={addMappers}
|
||||||
open={builtInDialogOpen}
|
open={addMapperDialogOpen}
|
||||||
toggleDialog={toggleBuiltInMapperDialog}
|
toggleDialog={() => setAddMapperDialogOpen(!addMapperDialogOpen)}
|
||||||
/>
|
/>
|
||||||
<Table
|
<Table
|
||||||
variant={TableVariant.compact}
|
variant={TableVariant.compact}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
SelectVariant,
|
SelectVariant,
|
||||||
Switch,
|
Switch,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
ValidatedOptions,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configPropertyRepresentation";
|
import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configPropertyRepresentation";
|
||||||
|
|
||||||
|
@ -31,23 +32,30 @@ import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||||
import { convertFormValuesToObject, convertToFormValues } from "../../util";
|
import { convertFormValuesToObject, convertToFormValues } from "../../util";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
scopeId: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const MappingDetails = () => {
|
export const MappingDetails = () => {
|
||||||
const { t } = useTranslation("client-scopes");
|
const { t } = useTranslation("client-scopes");
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
|
|
||||||
const { scopeId, id } = useParams<{ scopeId: string; id: string }>();
|
const { scopeId, id } = useParams<Params>();
|
||||||
const { register, setValue, control, handleSubmit } = useForm();
|
const { register, errors, setValue, control, handleSubmit } = useForm();
|
||||||
const [mapping, setMapping] = useState<ProtocolMapperRepresentation>();
|
const [mapping, setMapping] = useState<ProtocolMapperRepresentation>();
|
||||||
const [typeOpen, setTypeOpen] = useState(false);
|
const [typeOpen, setTypeOpen] = useState(false);
|
||||||
const [configProperties, setConfigProperties] = useState<
|
const [configProperties, setConfigProperties] = useState<
|
||||||
ConfigPropertyRepresentation[]
|
ConfigPropertyRepresentation[]
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const serverInfo = useServerInfo();
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const serverInfo = useServerInfo();
|
||||||
|
const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (id.match(isGuid)) {
|
||||||
(async () => {
|
(async () => {
|
||||||
const data = await adminClient.clientScopes.findProtocolMapper({
|
const data = await adminClient.clientScopes.findProtocolMapper({
|
||||||
id: scopeId,
|
id: scopeId,
|
||||||
|
@ -55,19 +63,23 @@ export const MappingDetails = () => {
|
||||||
});
|
});
|
||||||
if (data) {
|
if (data) {
|
||||||
Object.entries(data).map((entry) => {
|
Object.entries(data).map((entry) => {
|
||||||
if (entry[0] === "config") {
|
|
||||||
convertToFormValues(entry[1], "config", setValue);
|
convertToFormValues(entry[1], "config", setValue);
|
||||||
}
|
|
||||||
setValue(entry[0], entry[1]);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setMapping(data);
|
|
||||||
const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!];
|
const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!];
|
||||||
const properties = mapperTypes.find(
|
const properties = mapperTypes.find(
|
||||||
(type) => type.id === data.protocolMapper
|
(type) => type.id === data!.protocolMapper
|
||||||
)?.properties!;
|
)?.properties!;
|
||||||
setConfigProperties(properties);
|
setConfigProperties(properties);
|
||||||
|
|
||||||
|
setMapping(data);
|
||||||
})();
|
})();
|
||||||
|
} else {
|
||||||
|
(async () => {
|
||||||
|
const scope = await adminClient.clientScopes.findOne({ id: scopeId });
|
||||||
|
setMapping({ protocol: scope.protocol, protocolMapper: id });
|
||||||
|
})();
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
|
@ -91,15 +103,20 @@ export const MappingDetails = () => {
|
||||||
|
|
||||||
const save = async (formMapping: ProtocolMapperRepresentation) => {
|
const save = async (formMapping: ProtocolMapperRepresentation) => {
|
||||||
const config = convertFormValuesToObject(formMapping.config);
|
const config = convertFormValuesToObject(formMapping.config);
|
||||||
const map = { ...mapping, config };
|
const map = { ...mapping, ...formMapping, config };
|
||||||
|
const key = id.match(isGuid) ? "Updated" : "Created";
|
||||||
try {
|
try {
|
||||||
|
if (id.match(isGuid)) {
|
||||||
await adminClient.clientScopes.updateProtocolMapper(
|
await adminClient.clientScopes.updateProtocolMapper(
|
||||||
{ id: scopeId, mapperId: id },
|
{ id: scopeId, mapperId: id },
|
||||||
map
|
map
|
||||||
);
|
);
|
||||||
addAlert(t("mappingUpdatedSuccess"), AlertVariant.success);
|
} else {
|
||||||
|
await adminClient.clientScopes.addProtocolMapper({ id: scopeId }, map);
|
||||||
|
}
|
||||||
|
addAlert(t(`mapping${key}Success`), AlertVariant.success);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addAlert(t("mappingUpdatedError", { error }), AlertVariant.danger);
|
addAlert(t(`mapping${key}Error`, { error }), AlertVariant.danger);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,10 +124,12 @@ export const MappingDetails = () => {
|
||||||
<>
|
<>
|
||||||
<DeleteConfirm />
|
<DeleteConfirm />
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
titleKey={mapping ? mapping.name! : ""}
|
titleKey={mapping ? mapping.name! : t("addMapper")}
|
||||||
subKey={id}
|
subKey={id.match(isGuid) ? id : ""}
|
||||||
badge={mapping?.protocol}
|
badge={mapping?.protocol}
|
||||||
dropdownItems={[
|
dropdownItems={
|
||||||
|
id.match(isGuid)
|
||||||
|
? [
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="delete"
|
key="delete"
|
||||||
value="delete"
|
value="delete"
|
||||||
|
@ -118,10 +137,42 @@ export const MappingDetails = () => {
|
||||||
>
|
>
|
||||||
{t("common:delete")}
|
{t("common:delete")}
|
||||||
</DropdownItem>,
|
</DropdownItem>,
|
||||||
]}
|
]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
<Form isHorizontal onSubmit={handleSubmit(save)}>
|
<Form isHorizontal onSubmit={handleSubmit(save)}>
|
||||||
|
{!id.match(isGuid) && (
|
||||||
|
<FormGroup
|
||||||
|
label={t("name")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="client-scopes-help:mapperName"
|
||||||
|
forLabel={t("name")}
|
||||||
|
forID="name"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="name"
|
||||||
|
isRequired
|
||||||
|
validated={
|
||||||
|
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
ref={register({ required: true })}
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
validated={
|
||||||
|
errors.name
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("realmRolePrefix")}
|
label={t("realmRolePrefix")}
|
||||||
labelIcon={
|
labelIcon={
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
"deleteMappingConfirm": "Are you sure you want to delete this mapping?",
|
"deleteMappingConfirm": "Are you sure you want to delete this mapping?",
|
||||||
"mappingUpdatedSuccess": "Mapping successfully updated",
|
"mappingUpdatedSuccess": "Mapping successfully updated",
|
||||||
"mappingUpdatedError": "Could not update mapping: '{{error}}'",
|
"mappingUpdatedError": "Could not update mapping: '{{error}}'",
|
||||||
|
"mappingCreatedSuccess": "Mapping successfully created",
|
||||||
|
"mappingCreatedError": "Could not create mapping: '{{error}}'",
|
||||||
"realmRolePrefix": "Realm role prefix",
|
"realmRolePrefix": "Realm role prefix",
|
||||||
"multiValued": "Multivalued",
|
"multiValued": "Multivalued",
|
||||||
"tokenClaimName": "Token claim name",
|
"tokenClaimName": "Token claim name",
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { routes } from "../../route-config";
|
||||||
|
|
||||||
export const PageBreadCrumbs = () => {
|
export const PageBreadCrumbs = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const crumbs = useBreadcrumbs(routes(t)).slice(1);
|
const crumbs = useBreadcrumbs(routes(t), { excludePaths: ["/"] });
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{crumbs.length > 1 && (
|
{crumbs.length > 1 && (
|
||||||
|
|
Loading…
Reference in a new issue