Added missing fields when dynamic scope is enabled (#19984)
Closes #19865
This commit is contained in:
parent
81580908c3
commit
e8ed1abda7
6 changed files with 72 additions and 8 deletions
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Name of the client scope. Must be unique in the realm. Name should not contain space characters as it is used as value of scope parameter",
|
"name": "Name of the client scope. Must be unique in the realm. Name should not contain space characters as it is used as value of scope parameter",
|
||||||
|
"dynamicScope": "If on, this scope will be considered a Dynamic Scope, which will be comprised of a static and a variable portion.",
|
||||||
|
"dynamicScopeFormat": "This is the regular expression that the system will use to extract the scope name and variable.",
|
||||||
"description": "Description of the client scope",
|
"description": "Description of the client scope",
|
||||||
"protocol": "Which SSO protocol configuration is being supplied by this client scope",
|
"protocol": "Which SSO protocol configuration is being supplied by this client scope",
|
||||||
"type": "Client scopes, which will be added as default scopes to each created client",
|
"type": "Client scopes, which will be added as default scopes to each created client",
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
"clientScopeDetails": "Client scope details",
|
"clientScopeDetails": "Client scope details",
|
||||||
"clientScopeExplain": "Client scopes are a common set of protocol mappers and roles that are shared between multiple clients.",
|
"clientScopeExplain": "Client scopes are a common set of protocol mappers and roles that are shared between multiple clients.",
|
||||||
"searchFor": "Search for client scope",
|
"searchFor": "Search for client scope",
|
||||||
|
"dynamicScope": "Dynamic scope",
|
||||||
|
"dynamicScopeFormat": "Dynamic scope format",
|
||||||
"protocol": "Protocol",
|
"protocol": "Protocol",
|
||||||
"assignedType": "Assigned type",
|
"assignedType": "Assigned type",
|
||||||
"displayOrder": "Display order",
|
"displayOrder": "Display order",
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
ValidatedOptions,
|
ValidatedOptions,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
@ -21,13 +21,15 @@ import {
|
||||||
clientScopeTypesSelectOptions,
|
clientScopeTypesSelectOptions,
|
||||||
} from "../../components/client-scope/ClientScopeTypes";
|
} from "../../components/client-scope/ClientScopeTypes";
|
||||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||||
import { HelpItem } from "ui-shared";
|
import { HelpItem, TextControl } from "ui-shared";
|
||||||
import { KeycloakTextArea } from "../../components/keycloak-text-area/KeycloakTextArea";
|
import { KeycloakTextArea } from "../../components/keycloak-text-area/KeycloakTextArea";
|
||||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
|
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
|
||||||
import { convertAttributeNameToForm, convertToFormValues } from "../../util";
|
import { convertAttributeNameToForm, convertToFormValues } from "../../util";
|
||||||
import { toClientScopes } from "../routes/ClientScopes";
|
import { toClientScopes } from "../routes/ClientScopes";
|
||||||
|
import useIsFeatureEnabled, { Feature } from "../../utils/useIsFeatureEnabled";
|
||||||
|
import { DefaultSwitchControl } from "../../components/SwitchControl";
|
||||||
|
|
||||||
type ScopeFormProps = {
|
type ScopeFormProps = {
|
||||||
clientScope?: ClientScopeRepresentation;
|
clientScope?: ClientScopeRepresentation;
|
||||||
|
@ -37,16 +39,19 @@ type ScopeFormProps = {
|
||||||
export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
|
export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
|
||||||
const { t } = useTranslation("client-scopes");
|
const { t } = useTranslation("client-scopes");
|
||||||
const { t: tc } = useTranslation("clients");
|
const { t: tc } = useTranslation("clients");
|
||||||
|
const form = useForm<ClientScopeDefaultOptionalType>({ mode: "onChange" });
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
formState: { errors, isDirty, isValid },
|
formState: { errors, isDirty, isValid },
|
||||||
} = useForm<ClientScopeDefaultOptionalType>({ mode: "onChange" });
|
} = form;
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
|
|
||||||
const providers = useLoginProviders();
|
const providers = useLoginProviders();
|
||||||
|
const isFeatureEnabled = useIsFeatureEnabled();
|
||||||
|
const isDynamicScopesEnabled = isFeatureEnabled(Feature.DynamicScopes);
|
||||||
const [open, isOpen] = useState(false);
|
const [open, isOpen] = useState(false);
|
||||||
const [openType, setOpenType] = useState(false);
|
const [openType, setOpenType] = useState(false);
|
||||||
|
|
||||||
|
@ -57,6 +62,22 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
|
||||||
clientScope?.attributes?.["display.on.consent.screen"] ?? "true",
|
clientScope?.attributes?.["display.on.consent.screen"] ?? "true",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dynamicScope = useWatch({
|
||||||
|
control,
|
||||||
|
name: convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
|
||||||
|
"attributes.is.dynamic.scope"
|
||||||
|
),
|
||||||
|
defaultValue: "false",
|
||||||
|
});
|
||||||
|
|
||||||
|
const setDynamicRegex = (value: string, append: boolean) =>
|
||||||
|
setValue(
|
||||||
|
convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
|
||||||
|
"attributes.dynamic.scope.regexp"
|
||||||
|
),
|
||||||
|
append ? `${value}:*` : value
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
convertToFormValues(clientScope ?? {}, setValue);
|
convertToFormValues(clientScope ?? {}, setValue);
|
||||||
}, [clientScope]);
|
}, [clientScope]);
|
||||||
|
@ -88,9 +109,41 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
|
||||||
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||||
}
|
}
|
||||||
isRequired
|
isRequired
|
||||||
{...register("name", { required: true })}
|
{...register("name", {
|
||||||
|
required: true,
|
||||||
|
onChange: (e) => {
|
||||||
|
if (isDynamicScopesEnabled) {
|
||||||
|
setDynamicRegex(e.target.value, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
{isDynamicScopesEnabled && (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<DefaultSwitchControl
|
||||||
|
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
|
||||||
|
"attributes.is.dynamic.scope"
|
||||||
|
)}
|
||||||
|
label={t("dynamicScope")}
|
||||||
|
labelIcon={t("client-scopes-help:dynamicScope")}
|
||||||
|
onChange={(value) => {
|
||||||
|
setDynamicRegex(value ? form.getValues("name") || "" : "", value);
|
||||||
|
}}
|
||||||
|
stringify
|
||||||
|
/>
|
||||||
|
{dynamicScope === "true" && (
|
||||||
|
<TextControl
|
||||||
|
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
|
||||||
|
"attributes.dynamic.scope.regexp"
|
||||||
|
)}
|
||||||
|
label={t("dynamicScopeFormat")}
|
||||||
|
labelIcon={t("client-scopes-help:dynamicScopeFormat")}
|
||||||
|
isDisabled
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FormProvider>
|
||||||
|
)}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("common:description")}
|
label={t("common:description")}
|
||||||
labelIcon={
|
labelIcon={
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { FieldPath, FieldValues, UseControllerProps } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { SwitchControl } from "ui-shared";
|
import { SwitchControl } from "ui-shared";
|
||||||
|
|
||||||
type AdminSwitchControlProps<
|
type DefaultSwitchControlProps<
|
||||||
T extends FieldValues,
|
T extends FieldValues,
|
||||||
P extends FieldPath<T> = FieldPath<T>
|
P extends FieldPath<T> = FieldPath<T>
|
||||||
> = SwitchProps &
|
> = SwitchProps &
|
||||||
|
@ -11,13 +11,14 @@ type AdminSwitchControlProps<
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
labelIcon?: string;
|
labelIcon?: string;
|
||||||
|
stringify?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DefaultSwitchControl = <
|
export const DefaultSwitchControl = <
|
||||||
T extends FieldValues,
|
T extends FieldValues,
|
||||||
P extends FieldPath<T> = FieldPath<T>
|
P extends FieldPath<T> = FieldPath<T>
|
||||||
>(
|
>(
|
||||||
props: AdminSwitchControlProps<T, P>
|
props: DefaultSwitchControlProps<T, P>
|
||||||
) => {
|
) => {
|
||||||
const { t } = useTranslation("common");
|
const { t } = useTranslation("common");
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ export enum Feature {
|
||||||
ClientPolicies = "CLIENT_POLICIES",
|
ClientPolicies = "CLIENT_POLICIES",
|
||||||
DeclarativeUserProfile = "DECLARATIVE_USER_PROFILE",
|
DeclarativeUserProfile = "DECLARATIVE_USER_PROFILE",
|
||||||
Kerberos = "KERBEROS",
|
Kerberos = "KERBEROS",
|
||||||
|
DynamicScopes = "DYNAMIC_SCOPES",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useIsFeatureEnabled() {
|
export default function useIsFeatureEnabled() {
|
||||||
|
|
|
@ -19,6 +19,7 @@ export type SwitchControlProps<
|
||||||
labelIcon?: string;
|
labelIcon?: string;
|
||||||
labelOn: string;
|
labelOn: string;
|
||||||
labelOff: string;
|
labelOff: string;
|
||||||
|
stringify?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SwitchControl = <
|
export const SwitchControl = <
|
||||||
|
@ -46,8 +47,12 @@ export const SwitchControl = <
|
||||||
data-testid={props.name}
|
data-testid={props.name}
|
||||||
label={props.labelOn}
|
label={props.labelOn}
|
||||||
labelOff={props.labelOff}
|
labelOff={props.labelOff}
|
||||||
isChecked={value}
|
isChecked={props.stringify ? value === "true" : value}
|
||||||
onChange={(checked) => onChange(checked)}
|
onChange={(checked, e) => {
|
||||||
|
const value = props.stringify ? checked.toString() : checked;
|
||||||
|
props.onChange?.(checked, e);
|
||||||
|
onChange(value);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue