Use react-hook-form
v7 for client section (#3919)
This commit is contained in:
parent
8dde92e2fd
commit
997881a7a1
33 changed files with 832 additions and 598 deletions
|
@ -55,7 +55,7 @@ export const ExecutionConfigModal = ({
|
|||
} = form;
|
||||
|
||||
const setupForm = (config?: AuthenticatorConfigRepresentation) => {
|
||||
convertToFormValues(config, setValue);
|
||||
convertToFormValues(config || {}, setValue);
|
||||
};
|
||||
|
||||
useFetch(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { AlertVariant, PageSection, Text } from "@patternfly/react-core";
|
||||
import type { TFunction } from "i18next";
|
||||
import { useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import type GlobalRequestResult from "@keycloak/keycloak-admin-client/lib/defs/globalRequestResult";
|
||||
|
@ -10,13 +11,12 @@ import { ScrollForm } from "../components/scroll-form/ScrollForm";
|
|||
import { convertAttributeNameToForm, toUpperCase } from "../util";
|
||||
import { AdvancedSettings } from "./advanced/AdvancedSettings";
|
||||
import { AuthenticationOverrides } from "./advanced/AuthenticationOverrides";
|
||||
import { ClusteringPanel } from "./advanced/ClusteringPanel";
|
||||
import { FineGrainOpenIdConnect } from "./advanced/FineGrainOpenIdConnect";
|
||||
import { FineGrainSamlEndpointConfig } from "./advanced/FineGrainSamlEndpointConfig";
|
||||
import { OpenIdConnectCompatibilityModes } from "./advanced/OpenIdConnectCompatibilityModes";
|
||||
import type { SaveOptions } from "./ClientDetails";
|
||||
import type { TFunction } from "i18next";
|
||||
import { RevocationPanel } from "./advanced/RevocationPanel";
|
||||
import { ClusteringPanel } from "./advanced/ClusteringPanel";
|
||||
import type { FormFields, SaveOptions } from "./ClientDetails";
|
||||
|
||||
export const parseResult = (
|
||||
result: GlobalRequestResult,
|
||||
|
@ -55,7 +55,7 @@ export const AdvancedTab = ({ save, client }: AdvancedProps) => {
|
|||
const { t } = useTranslation("clients");
|
||||
const openIdConnect = "openid-connect";
|
||||
|
||||
const { setValue, control } = useFormContext();
|
||||
const { setValue } = useFormContext();
|
||||
const {
|
||||
publicClient,
|
||||
attributes,
|
||||
|
@ -66,7 +66,7 @@ export const AdvancedTab = ({ save, client }: AdvancedProps) => {
|
|||
const resetFields = (names: string[]) => {
|
||||
for (const name of names) {
|
||||
setValue(
|
||||
convertAttributeNameToForm(`attributes.${name}`),
|
||||
convertAttributeNameToForm<FormFields>(`attributes.${name}`),
|
||||
attributes?.[name] || ""
|
||||
);
|
||||
}
|
||||
|
@ -128,7 +128,6 @@ export const AdvancedTab = ({ save, client }: AdvancedProps) => {
|
|||
{t("clients-help:openIdConnectCompatibilityModes")}
|
||||
</Text>
|
||||
<OpenIdConnectCompatibilityModes
|
||||
control={control}
|
||||
save={() => save()}
|
||||
reset={() =>
|
||||
resetFields(["exclude.session.state.from.auth.response"])
|
||||
|
@ -146,7 +145,6 @@ export const AdvancedTab = ({ save, client }: AdvancedProps) => {
|
|||
{t("clients-help:fineGrainSamlEndpointConfig")}
|
||||
</Text>
|
||||
<FineGrainSamlEndpointConfig
|
||||
control={control}
|
||||
save={() => save()}
|
||||
reset={() =>
|
||||
resetFields([
|
||||
|
@ -178,7 +176,6 @@ export const AdvancedTab = ({ save, client }: AdvancedProps) => {
|
|||
</Text>
|
||||
<AdvancedSettings
|
||||
protocol={protocol}
|
||||
control={control}
|
||||
save={() => save()}
|
||||
reset={() => {
|
||||
resetFields([
|
||||
|
@ -201,7 +198,6 @@ export const AdvancedTab = ({ save, client }: AdvancedProps) => {
|
|||
</Text>
|
||||
<AuthenticationOverrides
|
||||
protocol={protocol}
|
||||
control={control}
|
||||
save={() => save()}
|
||||
reset={() => {
|
||||
setValue(
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormGroup, Switch, ValidatedOptions } from "@patternfly/react-core";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { KeycloakTextArea } from "../components/keycloak-text-area/KeycloakTextArea";
|
||||
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { FormFields } from "./ClientDetails";
|
||||
|
||||
type ClientDescriptionProps = {
|
||||
protocol?: string;
|
||||
|
@ -21,7 +21,7 @@ export const ClientDescription = ({
|
|||
register,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useFormContext<ClientRepresentation>();
|
||||
} = useFormContext<FormFields>();
|
||||
return (
|
||||
<FormAccess role="manage-clients" fineGrainedAccess={configure} unWrap>
|
||||
<FormGroup
|
||||
|
@ -37,11 +37,9 @@ export const ClientDescription = ({
|
|||
isRequired
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register({ required: true })}
|
||||
type="text"
|
||||
{...register("clientId", { required: true })}
|
||||
id="kc-client-id"
|
||||
data-testid="kc-client-id"
|
||||
name="clientId"
|
||||
validated={
|
||||
errors.clientId ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
|
@ -54,12 +52,7 @@ export const ClientDescription = ({
|
|||
label={t("common:name")}
|
||||
fieldId="kc-name"
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="kc-name"
|
||||
name="name"
|
||||
/>
|
||||
<KeycloakTextInput {...register("name")} id="kc-name" />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
labelIcon={
|
||||
|
@ -76,15 +69,13 @@ export const ClientDescription = ({
|
|||
helperTextInvalid={errors.description?.message}
|
||||
>
|
||||
<KeycloakTextArea
|
||||
ref={register({
|
||||
{...register("description", {
|
||||
maxLength: {
|
||||
value: 255,
|
||||
message: t("common:maxLength", { length: 255 }),
|
||||
},
|
||||
})}
|
||||
type="text"
|
||||
id="kc-description"
|
||||
name="description"
|
||||
validated={
|
||||
errors.description
|
||||
? ValidatedOptions.error
|
||||
|
@ -107,13 +98,13 @@ export const ClientDescription = ({
|
|||
name="alwaysDisplayInConsole"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="kc-always-display-in-console-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
aria-label={t("alwaysDisplayInConsole")}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import {
|
||||
AlertVariant,
|
||||
ButtonVariant,
|
||||
|
@ -10,10 +11,14 @@ import {
|
|||
Tooltip,
|
||||
} from "@patternfly/react-core";
|
||||
import { InfoCircleIcon } from "@patternfly/react-icons";
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import { cloneDeep, sortBy } from "lodash-es";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useForm,
|
||||
useWatch,
|
||||
} from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom-v5-compat";
|
||||
|
@ -23,13 +28,21 @@ import {
|
|||
useConfirmDialog,
|
||||
} from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { DownloadDialog } from "../components/download-dialog/DownloadDialog";
|
||||
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { PermissionsTab } from "../components/permission-tab/PermissionTab";
|
||||
import {
|
||||
routableTab,
|
||||
RoutableTabs,
|
||||
} from "../components/routable-tabs/RoutableTabs";
|
||||
import {
|
||||
ViewHeader,
|
||||
ViewHeaderBadge,
|
||||
} from "../components/view-header/ViewHeader";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { RolesList } from "../realm-roles/RolesList";
|
||||
import {
|
||||
convertAttributeNameToForm,
|
||||
|
@ -39,41 +52,33 @@ import {
|
|||
} from "../util";
|
||||
import useToggle from "../utils/useToggle";
|
||||
import { AdvancedTab } from "./AdvancedTab";
|
||||
import { ClientSettings } from "./ClientSettings";
|
||||
import { ClientSessions } from "./ClientSessions";
|
||||
import { Credentials } from "./credentials/Credentials";
|
||||
import { Keys } from "./keys/Keys";
|
||||
import { ClientParams, ClientTab, toClient } from "./routes/Client";
|
||||
import { toClients } from "./routes/Clients";
|
||||
import { ClientScopes } from "./scopes/ClientScopes";
|
||||
import { EvaluateScopes } from "./scopes/EvaluateScopes";
|
||||
import { ServiceAccount } from "./service-account/ServiceAccount";
|
||||
import { isRealmClient, getProtocolName } from "./utils";
|
||||
import { SamlKeys } from "./keys/SamlKeys";
|
||||
import { AuthorizationSettings } from "./authorization/Settings";
|
||||
import { AuthorizationEvaluate } from "./authorization/AuthorizationEvaluate";
|
||||
import { AuthorizationExport } from "./authorization/AuthorizationExport";
|
||||
import { AuthorizationPermissions } from "./authorization/Permissions";
|
||||
import { AuthorizationPolicies } from "./authorization/Policies";
|
||||
import { AuthorizationResources } from "./authorization/Resources";
|
||||
import { AuthorizationScopes } from "./authorization/Scopes";
|
||||
import { AuthorizationPolicies } from "./authorization/Policies";
|
||||
import { AuthorizationPermissions } from "./authorization/Permissions";
|
||||
import { AuthorizationEvaluate } from "./authorization/AuthorizationEvaluate";
|
||||
import {
|
||||
routableTab,
|
||||
RoutableTabs,
|
||||
} from "../components/routable-tabs/RoutableTabs";
|
||||
import { AuthorizationSettings } from "./authorization/Settings";
|
||||
import { ClientSessions } from "./ClientSessions";
|
||||
import { ClientSettings } from "./ClientSettings";
|
||||
import { Credentials } from "./credentials/Credentials";
|
||||
import { Keys } from "./keys/Keys";
|
||||
import { SamlKeys } from "./keys/SamlKeys";
|
||||
import {
|
||||
AuthorizationTab,
|
||||
toAuthorizationTab,
|
||||
} from "./routes/AuthenticationTab";
|
||||
import { ClientParams, ClientTab, toClient } from "./routes/Client";
|
||||
import { toClients } from "./routes/Clients";
|
||||
import { toClientScopesTab } from "./routes/ClientScopeTab";
|
||||
import { AuthorizationExport } from "./authorization/AuthorizationExport";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { PermissionsTab } from "../components/permission-tab/PermissionTab";
|
||||
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
import { ClientScopes } from "./scopes/ClientScopes";
|
||||
import { EvaluateScopes } from "./scopes/EvaluateScopes";
|
||||
import { ServiceAccount } from "./service-account/ServiceAccount";
|
||||
import { getProtocolName, isRealmClient } from "./utils";
|
||||
|
||||
type ClientDetailHeaderProps = {
|
||||
onChange: (value: boolean) => void;
|
||||
value: boolean;
|
||||
value: boolean | undefined;
|
||||
save: () => void;
|
||||
client: ClientRepresentation;
|
||||
toggleDownloadDialog: () => void;
|
||||
|
@ -178,6 +183,11 @@ export type SaveOptions = {
|
|||
messageKey?: string;
|
||||
};
|
||||
|
||||
export type FormFields = Omit<
|
||||
ClientRepresentation,
|
||||
"authorizationSettings" | "resources"
|
||||
>;
|
||||
|
||||
export default function ClientDetails() {
|
||||
const { t } = useTranslation("clients");
|
||||
const { adminClient } = useAdminClient();
|
||||
|
@ -198,7 +208,7 @@ export default function ClientDetails() {
|
|||
const [downloadDialogOpen, toggleDownloadDialogOpen] = useToggle();
|
||||
const [changeAuthenticatorOpen, toggleChangeAuthenticatorOpen] = useToggle();
|
||||
|
||||
const form = useForm<ClientRepresentation>({ shouldUnregister: false });
|
||||
const form = useForm<FormFields>({ shouldUnregister: false });
|
||||
const { clientId } = useParams<ClientParams>();
|
||||
const [key, setKey] = useState(0);
|
||||
|
||||
|
@ -237,6 +247,7 @@ export default function ClientDetails() {
|
|||
if (client.attributes?.["acr.loa.map"]) {
|
||||
form.setValue(
|
||||
convertAttributeNameToForm("attributes.acr.loa.map"),
|
||||
// @ts-ignore
|
||||
Object.entries(JSON.parse(client.attributes["acr.loa.map"])).flatMap(
|
||||
([key, value]) => ({ key, value })
|
||||
)
|
||||
|
@ -262,46 +273,48 @@ export default function ClientDetails() {
|
|||
messageKey: "clientSaveSuccess",
|
||||
}
|
||||
) => {
|
||||
if (await form.trigger()) {
|
||||
if (
|
||||
!client?.publicClient &&
|
||||
client?.clientAuthenticatorType !== clientAuthenticatorType &&
|
||||
!confirmed
|
||||
) {
|
||||
toggleChangeAuthenticatorOpen();
|
||||
return;
|
||||
}
|
||||
if (!(await form.trigger())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const values = convertFormValuesToObject(form.getValues());
|
||||
if (
|
||||
!client?.publicClient &&
|
||||
client?.clientAuthenticatorType !== clientAuthenticatorType &&
|
||||
!confirmed
|
||||
) {
|
||||
toggleChangeAuthenticatorOpen();
|
||||
return;
|
||||
}
|
||||
|
||||
const submittedClient =
|
||||
convertFormValuesToObject<ClientRepresentation>(values);
|
||||
const values = convertFormValuesToObject(form.getValues());
|
||||
|
||||
if (submittedClient.attributes?.["acr.loa.map"]) {
|
||||
submittedClient.attributes["acr.loa.map"] = JSON.stringify(
|
||||
Object.fromEntries(
|
||||
(submittedClient.attributes["acr.loa.map"] as KeyValueType[])
|
||||
.filter(({ key }) => key !== "")
|
||||
.map(({ key, value }) => [key, value])
|
||||
)
|
||||
);
|
||||
}
|
||||
const submittedClient =
|
||||
convertFormValuesToObject<ClientRepresentation>(values);
|
||||
|
||||
try {
|
||||
const newClient: ClientRepresentation = {
|
||||
...client,
|
||||
...submittedClient,
|
||||
};
|
||||
if (submittedClient.attributes?.["acr.loa.map"]) {
|
||||
submittedClient.attributes["acr.loa.map"] = JSON.stringify(
|
||||
Object.fromEntries(
|
||||
(submittedClient.attributes["acr.loa.map"] as KeyValueType[])
|
||||
.filter(({ key }) => key !== "")
|
||||
.map(({ key, value }) => [key, value])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
newClient.clientId = newClient.clientId?.trim();
|
||||
try {
|
||||
const newClient: ClientRepresentation = {
|
||||
...client,
|
||||
...submittedClient,
|
||||
};
|
||||
|
||||
await adminClient.clients.update({ id: clientId }, newClient);
|
||||
setupForm(newClient);
|
||||
setClient(newClient);
|
||||
addAlert(t(messageKey), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addError("clients:clientSaveError", error);
|
||||
}
|
||||
newClient.clientId = newClient.clientId?.trim();
|
||||
|
||||
await adminClient.clients.update({ id: clientId }, newClient);
|
||||
setupForm(newClient);
|
||||
setClient(newClient);
|
||||
addAlert(t(messageKey), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addError("clients:clientSaveError", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -360,10 +373,10 @@ export default function ClientDetails() {
|
|||
name="enabled"
|
||||
control={form.control}
|
||||
defaultValue={true}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<ClientDetailHeader
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
client={client}
|
||||
save={save}
|
||||
toggleDeleteDialog={toggleDeleteDialog}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { useFormContext } from "react-hook-form-v7";
|
||||
import { Form } from "@patternfly/react-core";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
|
@ -11,6 +11,7 @@ import { SamlSignature } from "./add/SamlSignature";
|
|||
import { AccessSettings } from "./add/AccessSettings";
|
||||
import { LoginSettingsPanel } from "./add/LoginSettingsPanel";
|
||||
import { LogoutPanel } from "./add/LogoutPanel";
|
||||
import { FormFields } from "./ClientDetails";
|
||||
|
||||
export type ClientSettingsProps = {
|
||||
client: ClientRepresentation;
|
||||
|
@ -21,7 +22,7 @@ export type ClientSettingsProps = {
|
|||
export const ClientSettings = (props: ClientSettingsProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
|
||||
const { watch } = useFormContext<ClientRepresentation>();
|
||||
const { watch } = useFormContext<FormFields>();
|
||||
const protocol = watch("protocol");
|
||||
|
||||
const { client } = props;
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { FormGroup } from "@patternfly/react-core";
|
||||
import { useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import type { ClientSettingsProps } from "../ClientSettings";
|
||||
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { SaveReset } from "../advanced/SaveReset";
|
||||
import environment from "../../environment";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { MultiLineInput } from "../../components/multi-line-input/hook-form-v7/MultiLineInput";
|
||||
import { useAccess } from "../../context/access/Access";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import environment from "../../environment";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { SaveReset } from "../advanced/SaveReset";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import type { ClientSettingsProps } from "../ClientSettings";
|
||||
|
||||
export const AccessSettings = ({
|
||||
client,
|
||||
|
@ -20,7 +20,7 @@ export const AccessSettings = ({
|
|||
reset,
|
||||
}: ClientSettingsProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { register, watch } = useFormContext<ClientRepresentation>();
|
||||
const { register, watch } = useFormContext<FormFields>();
|
||||
const { realm } = useRealm();
|
||||
|
||||
const { hasAccess } = useAccess();
|
||||
|
@ -49,12 +49,7 @@ export const AccessSettings = ({
|
|||
/>
|
||||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="kc-root-url"
|
||||
name="rootUrl"
|
||||
ref={register}
|
||||
/>
|
||||
<KeycloakTextInput id="kc-root-url" {...register("rootUrl")} />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("homeURL")}
|
||||
|
@ -66,12 +61,7 @@ export const AccessSettings = ({
|
|||
/>
|
||||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="kc-home-url"
|
||||
name="baseUrl"
|
||||
ref={register}
|
||||
/>
|
||||
<KeycloakTextInput id="kc-home-url" {...register("baseUrl")} />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("validRedirectUri")}
|
||||
|
@ -127,11 +117,9 @@ export const AccessSettings = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="idpInitiatedSsoUrlName"
|
||||
name="attributes.saml_idp_initiated_sso_url_name"
|
||||
data-testid="idpInitiatedSsoUrlName"
|
||||
ref={register}
|
||||
{...register("attributes.saml_idp_initiated_sso_url_name")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -145,11 +133,9 @@ export const AccessSettings = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="idpInitiatedSsoRelayState"
|
||||
name="attributes.saml_idp_initiated_sso_relay_state"
|
||||
data-testid="idpInitiatedSsoRelayState"
|
||||
ref={register}
|
||||
{...register("attributes.saml_idp_initiated_sso_relay_state")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -163,11 +149,9 @@ export const AccessSettings = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="masterSamlProcessingUrl"
|
||||
name="adminUrl"
|
||||
data-testid="masterSamlProcessingUrl"
|
||||
ref={register}
|
||||
{...register("adminUrl")}
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
|
@ -203,12 +187,7 @@ export const AccessSettings = ({
|
|||
/>
|
||||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="kc-admin-url"
|
||||
name="adminUrl"
|
||||
ref={register}
|
||||
/>
|
||||
<KeycloakTextInput id="kc-admin-url" {...register("adminUrl")} />
|
||||
</FormGroup>
|
||||
)}
|
||||
{client.bearerOnly && (
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
FormGroup,
|
||||
Switch,
|
||||
Checkbox,
|
||||
FormGroup,
|
||||
Grid,
|
||||
GridItem,
|
||||
InputGroup,
|
||||
Switch,
|
||||
} from "@patternfly/react-core";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
|
||||
import "./capability-config.css";
|
||||
|
||||
|
@ -26,7 +27,7 @@ export const CapabilityConfig = ({
|
|||
protocol: type,
|
||||
}: CapabilityConfigProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { control, watch, setValue } = useFormContext<ClientRepresentation>();
|
||||
const { control, watch, setValue } = useFormContext<FormFields>();
|
||||
const protocol = type || watch("protocol");
|
||||
const clientAuthentication = watch("publicClient");
|
||||
const authorization = watch("authorizationServicesEnabled");
|
||||
|
@ -56,21 +57,20 @@ export const CapabilityConfig = ({
|
|||
name="publicClient"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
data-testid="authentication"
|
||||
id="kc-authentication-switch"
|
||||
name="publicClient"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={!value}
|
||||
isChecked={!field.value}
|
||||
onChange={(value) => {
|
||||
onChange(!value);
|
||||
field.onChange(!value);
|
||||
if (!value) {
|
||||
setValue("authorizationServicesEnabled", false);
|
||||
setValue("serviceAccountsEnabled", false);
|
||||
setValue(
|
||||
convertAttributeNameToForm(
|
||||
convertAttributeNameToForm<FormFields>(
|
||||
"attributes.oidc.ciba.grant.enabled"
|
||||
),
|
||||
false
|
||||
|
@ -97,16 +97,15 @@ export const CapabilityConfig = ({
|
|||
name="authorizationServicesEnabled"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
data-testid="authorization"
|
||||
id="kc-authorization-switch"
|
||||
name="authorizationServicesEnabled"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value && !clientAuthentication}
|
||||
isChecked={field.value && !clientAuthentication}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
if (value) {
|
||||
setValue("serviceAccountsEnabled", true);
|
||||
}
|
||||
|
@ -128,15 +127,14 @@ export const CapabilityConfig = ({
|
|||
name="standardFlowEnabled"
|
||||
defaultValue={true}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<InputGroup>
|
||||
<Checkbox
|
||||
data-testid="standard"
|
||||
label={t("standardFlow")}
|
||||
id="kc-flow-standard"
|
||||
name="standardFlowEnabled"
|
||||
isChecked={value.toString() === "true"}
|
||||
onChange={onChange}
|
||||
isChecked={field.value?.toString() === "true"}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<HelpItem
|
||||
helpText="clients-help:standardFlow"
|
||||
|
@ -151,15 +149,14 @@ export const CapabilityConfig = ({
|
|||
name="directAccessGrantsEnabled"
|
||||
defaultValue={true}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<InputGroup>
|
||||
<Checkbox
|
||||
data-testid="direct"
|
||||
label={t("directAccess")}
|
||||
id="kc-flow-direct"
|
||||
name="directAccessGrantsEnabled"
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<HelpItem
|
||||
helpText="clients-help:directAccess"
|
||||
|
@ -174,15 +171,14 @@ export const CapabilityConfig = ({
|
|||
name="implicitFlowEnabled"
|
||||
defaultValue={true}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<InputGroup>
|
||||
<Checkbox
|
||||
data-testid="implicit"
|
||||
label={t("implicitFlow")}
|
||||
id="kc-flow-implicit"
|
||||
name="implicitFlowEnabled"
|
||||
isChecked={value.toString() === "true"}
|
||||
onChange={onChange}
|
||||
isChecked={field.value?.toString() === "true"}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<HelpItem
|
||||
helpText="clients-help:implicitFlow"
|
||||
|
@ -197,18 +193,17 @@ export const CapabilityConfig = ({
|
|||
name="serviceAccountsEnabled"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<InputGroup>
|
||||
<Checkbox
|
||||
data-testid="service-account"
|
||||
label={t("serviceAccount")}
|
||||
id="kc-flow-service-account"
|
||||
name="serviceAccountsEnabled"
|
||||
isChecked={
|
||||
value.toString() === "true" ||
|
||||
field.value?.toString() === "true" ||
|
||||
(clientAuthentication && authorization)
|
||||
}
|
||||
onChange={onChange}
|
||||
onChange={field.onChange}
|
||||
isDisabled={
|
||||
(clientAuthentication && !authorization) ||
|
||||
(!clientAuthentication && authorization)
|
||||
|
@ -224,20 +219,20 @@ export const CapabilityConfig = ({
|
|||
</GridItem>
|
||||
<GridItem lg={8} sm={6}>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
"attributes.oauth2.device.authorization.grant.enabled"
|
||||
)}
|
||||
name={convertAttributeNameToForm<
|
||||
Required<ClientRepresentation["attributes"]>
|
||||
>("attributes.oauth2.device.authorization.grant.enabled")}
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<InputGroup>
|
||||
<Checkbox
|
||||
data-testid="oauth-device-authorization-grant"
|
||||
label={t("oauthDeviceAuthorizationGrant")}
|
||||
id="kc-oauth-device-authorization-grant"
|
||||
name="oauth2.device.authorization.grant.enabled"
|
||||
isChecked={value.toString() === "true"}
|
||||
onChange={onChange}
|
||||
isChecked={field.value.toString() === "true"}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<HelpItem
|
||||
helpText="clients-help:oauthDeviceAuthorizationGrant"
|
||||
|
@ -249,20 +244,20 @@ export const CapabilityConfig = ({
|
|||
</GridItem>
|
||||
<GridItem lg={8} sm={6}>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.oidc.ciba.grant.enabled"
|
||||
)}
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<InputGroup>
|
||||
<Checkbox
|
||||
data-testid="oidc-ciba-grant"
|
||||
label={t("oidcCibaGrant")}
|
||||
id="kc-oidc-ciba-grant"
|
||||
name="oidc.ciba.grant.enabled"
|
||||
isChecked={value.toString() === "true"}
|
||||
onChange={onChange}
|
||||
isChecked={field.value.toString() === "true"}
|
||||
onChange={field.onChange}
|
||||
isDisabled={clientAuthentication}
|
||||
/>
|
||||
<HelpItem
|
||||
|
@ -291,17 +286,19 @@ export const CapabilityConfig = ({
|
|||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm("attributes.saml.encrypt")}
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.saml.encrypt"
|
||||
)}
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
data-testid="encrypt"
|
||||
id="kc-encrypt"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
aria-label={t("encryptAssertions")}
|
||||
/>
|
||||
)}
|
||||
|
@ -319,19 +316,19 @@ export const CapabilityConfig = ({
|
|||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.saml.client.signature"
|
||||
)}
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
data-testid="client-signature"
|
||||
id="kc-client-signature"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
aria-label={t("clientSignature")}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { useState } from "react";
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectVariant,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
|
||||
import { ClientDescription } from "../ClientDescription";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
|
||||
import { ClientDescription } from "../ClientDescription";
|
||||
import { getProtocolName } from "../utils";
|
||||
|
||||
export const GeneralSettings = () => {
|
||||
|
@ -41,22 +41,22 @@ export const GeneralSettings = () => {
|
|||
name="protocol"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
id="kc-type"
|
||||
onToggle={isOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
field.onChange(value.toString());
|
||||
isOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("selectEncryptionType")}
|
||||
isOpen={open}
|
||||
>
|
||||
{providers.map((option) => (
|
||||
<SelectOption
|
||||
selected={option === value}
|
||||
selected={option === field.value}
|
||||
key={option}
|
||||
value={option}
|
||||
data-testid={`option-${option}`}
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
|
@ -8,23 +5,28 @@ import {
|
|||
SelectVariant,
|
||||
Switch,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||
import { KeycloakTextArea } from "../../components/keycloak-text-area/KeycloakTextArea";
|
||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
|
||||
export const LoginSettingsPanel = ({ access }: { access?: boolean }) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { register, control, watch } = useFormContext<ClientRepresentation>();
|
||||
const { register, control, watch } = useFormContext<FormFields>();
|
||||
|
||||
const [loginThemeOpen, setLoginThemeOpen] = useState(false);
|
||||
const loginThemes = useServerInfo().themes!["login"];
|
||||
const consentRequired = watch("consentRequired");
|
||||
const displayOnConsentScreen: string = watch(
|
||||
convertAttributeNameToForm("attributes.display.on.consent.screen")
|
||||
convertAttributeNameToForm<FormFields>(
|
||||
"attributes.display.on.consent.screen"
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -43,15 +45,15 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => {
|
|||
name="attributes.login_theme"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="loginTheme"
|
||||
onToggle={setLoginThemeOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
field.onChange(value.toString());
|
||||
setLoginThemeOpen(false);
|
||||
}}
|
||||
selections={value || t("common:choose")}
|
||||
selections={field.value || t("common:choose")}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("loginTheme")}
|
||||
isOpen={loginThemeOpen}
|
||||
|
@ -62,7 +64,7 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => {
|
|||
</SelectOption>,
|
||||
...loginThemes.map((theme) => (
|
||||
<SelectOption
|
||||
selected={theme.name === value}
|
||||
selected={theme.name === field.value}
|
||||
key={theme.name}
|
||||
value={theme.name}
|
||||
/>
|
||||
|
@ -87,13 +89,13 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => {
|
|||
name="consentRequired"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="kc-consent-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
aria-label={t("consentRequired")}
|
||||
/>
|
||||
)}
|
||||
|
@ -111,18 +113,18 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => {
|
|||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.display.on.consent.screen"
|
||||
)}
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="kc-display-on-client-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange("" + value)}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange("" + value)}
|
||||
isDisabled={!consentRequired}
|
||||
aria-label={t("displayOnClient")}
|
||||
/>
|
||||
|
@ -141,8 +143,11 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => {
|
|||
>
|
||||
<KeycloakTextArea
|
||||
id="kc-consent-screen-text"
|
||||
name={convertAttributeNameToForm("attributes.consent.screen.text")}
|
||||
ref={register}
|
||||
{...register(
|
||||
convertAttributeNameToForm<FormFields>(
|
||||
"attributes.consent.screen.text"
|
||||
)
|
||||
)}
|
||||
isDisabled={!(consentRequired && displayOnConsentScreen === "true")}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { FormGroup, Switch, ValidatedOptions } from "@patternfly/react-core";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import type { ClientSettingsProps } from "../ClientSettings";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { useAccess } from "../../context/access/Access";
|
||||
import { beerify, convertAttributeNameToForm } from "../../util";
|
||||
import { SaveReset } from "../advanced/SaveReset";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import type { ClientSettingsProps } from "../ClientSettings";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
|
||||
export const LogoutPanel = ({
|
||||
save,
|
||||
|
@ -22,7 +22,7 @@ export const LogoutPanel = ({
|
|||
control,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useFormContext<ClientRepresentation>();
|
||||
} = useFormContext<FormFields>();
|
||||
|
||||
const { hasAccess } = useAccess();
|
||||
const isManager = hasAccess("manage-clients") || access?.configure;
|
||||
|
@ -51,13 +51,13 @@ export const LogoutPanel = ({
|
|||
name="frontchannelLogout"
|
||||
defaultValue={true}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="kc-frontchannelLogout-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
aria-label={t("frontchannelLogout")}
|
||||
/>
|
||||
)}
|
||||
|
@ -74,29 +74,30 @@ export const LogoutPanel = ({
|
|||
/>
|
||||
}
|
||||
helperTextInvalid={
|
||||
errors.attributes?.frontchannel?.logout?.url?.message
|
||||
errors.attributes?.[beerify("frontchannel.logout.url")]?.message
|
||||
}
|
||||
validated={
|
||||
errors.attributes?.frontchannel?.logout?.url?.message
|
||||
errors.attributes?.[beerify("frontchannel.logout.url")]?.message
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="frontchannelLogoutUrl"
|
||||
name={convertAttributeNameToForm(
|
||||
"attributes.frontchannel.logout.url"
|
||||
{...register(
|
||||
convertAttributeNameToForm<FormFields>(
|
||||
"attributes.frontchannel.logout.url"
|
||||
),
|
||||
{
|
||||
validate: (uri) =>
|
||||
((uri.startsWith("https://") || uri.startsWith("http://")) &&
|
||||
!uri.includes("*")) ||
|
||||
uri === "" ||
|
||||
t("frontchannelUrlInvalid").toString(),
|
||||
}
|
||||
)}
|
||||
ref={register({
|
||||
validate: (uri) =>
|
||||
((uri.startsWith("https://") || uri.startsWith("http://")) &&
|
||||
!uri.includes("*")) ||
|
||||
uri === "" ||
|
||||
t("frontchannelUrlInvalid").toString(),
|
||||
})}
|
||||
validated={
|
||||
errors.attributes?.frontchannel?.logout?.url?.message
|
||||
errors.attributes?.[beerify("frontchannel.logout.url")]?.message
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
|
@ -115,29 +116,31 @@ export const LogoutPanel = ({
|
|||
/>
|
||||
}
|
||||
helperTextInvalid={
|
||||
errors.attributes?.backchannel?.logout?.url?.message
|
||||
errors.attributes?.[beerify("backchannel.logout.url")]?.message
|
||||
}
|
||||
validated={
|
||||
errors.attributes?.backchannel?.logout?.url?.message
|
||||
errors.attributes?.[beerify("backchannel.logout.url")]?.message
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="backchannelLogoutUrl"
|
||||
name={convertAttributeNameToForm(
|
||||
"attributes.backchannel.logout.url"
|
||||
{...register(
|
||||
convertAttributeNameToForm<FormFields>(
|
||||
"attributes.backchannel.logout.url"
|
||||
),
|
||||
{
|
||||
validate: (uri) =>
|
||||
((uri.startsWith("https://") ||
|
||||
uri.startsWith("http://")) &&
|
||||
!uri.includes("*")) ||
|
||||
uri === "" ||
|
||||
t("backchannelUrlInvalid").toString(),
|
||||
}
|
||||
)}
|
||||
ref={register({
|
||||
validate: (uri) =>
|
||||
((uri.startsWith("https://") || uri.startsWith("http://")) &&
|
||||
!uri.includes("*")) ||
|
||||
uri === "" ||
|
||||
t("backchannelUrlInvalid").toString(),
|
||||
})}
|
||||
validated={
|
||||
errors.attributes?.backchannel?.logout?.url?.message
|
||||
errors.attributes?.[beerify("backchannel.logout.url")]?.message
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
|
@ -155,18 +158,18 @@ export const LogoutPanel = ({
|
|||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.backchannel.logout.session.required"
|
||||
)}
|
||||
defaultValue="true"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="backchannelLogoutSessionRequired"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange(value.toString())}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange(value.toString())}
|
||||
aria-label={t("backchannelLogoutSessionRequired")}
|
||||
/>
|
||||
)}
|
||||
|
@ -184,18 +187,18 @@ export const LogoutPanel = ({
|
|||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.backchannel.logout.revoke.offline.tokens"
|
||||
)}
|
||||
defaultValue="false"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="backchannelLogoutRevokeOfflineSessions"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange(value.toString())}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange(value.toString())}
|
||||
aria-label={t("backchannelLogoutRevokeOfflineSessions")}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
|
@ -6,9 +7,8 @@ import {
|
|||
WizardContextConsumer,
|
||||
WizardFooter,
|
||||
} from "@patternfly/react-core";
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import { useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { FormProvider, useForm } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom-v5-compat";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
|
@ -16,6 +16,7 @@ import { ViewHeader } from "../../components/view-header/ViewHeader";
|
|||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { convertFormValuesToObject } from "../../util";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import { toClient } from "../routes/Client";
|
||||
import { toClients } from "../routes/Clients";
|
||||
import { CapabilityConfig } from "./CapabilityConfig";
|
||||
|
@ -42,7 +43,7 @@ export default function NewClientForm() {
|
|||
frontchannelLogout: true,
|
||||
});
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const methods = useForm<ClientRepresentation>({ defaultValues: client });
|
||||
const methods = useForm<FormFields>({ defaultValues: client });
|
||||
const protocol = methods.watch("protocol");
|
||||
|
||||
const save = async () => {
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
|
@ -8,15 +5,27 @@ import {
|
|||
SelectVariant,
|
||||
Switch,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Controller,
|
||||
Path,
|
||||
PathValue,
|
||||
useFormContext,
|
||||
} from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
|
||||
export const Toggle = ({ name, label }: { name: string; label: string }) => {
|
||||
type ToggleProps = {
|
||||
name: PathValue<FormFields, Path<FormFields>>;
|
||||
label: string;
|
||||
};
|
||||
export const Toggle = ({ name, label }: ToggleProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { control } = useFormContext<ClientRepresentation>();
|
||||
const { control } = useFormContext<FormFields>();
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
|
@ -34,14 +43,14 @@ export const Toggle = ({ name, label }: { name: string; label: string }) => {
|
|||
name={name}
|
||||
defaultValue="false"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id={name!}
|
||||
data-testid={label}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange(value.toString())}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange(value.toString())}
|
||||
aria-label={t(label)}
|
||||
/>
|
||||
)}
|
||||
|
@ -52,7 +61,7 @@ export const Toggle = ({ name, label }: { name: string; label: string }) => {
|
|||
|
||||
export const SamlConfig = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { control } = useFormContext<ClientRepresentation>();
|
||||
const { control } = useFormContext<FormFields>();
|
||||
|
||||
const [nameFormatOpen, setNameFormatOpen] = useState(false);
|
||||
return (
|
||||
|
@ -75,22 +84,22 @@ export const SamlConfig = () => {
|
|||
name="attributes.saml_name_id_format"
|
||||
defaultValue="username"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="samlNameIdFormat"
|
||||
onToggle={setNameFormatOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
field.onChange(value.toString());
|
||||
setNameFormatOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("nameIdFormat")}
|
||||
isOpen={nameFormatOpen}
|
||||
>
|
||||
{["username", "email", "transient", "persistent"].map((name) => (
|
||||
<SelectOption
|
||||
selected={name === value}
|
||||
selected={name === field.value}
|
||||
key={name}
|
||||
value={name}
|
||||
/>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import { Toggle } from "./SamlConfig";
|
||||
|
||||
const SIGNATURE_ALGORITHMS = [
|
||||
|
@ -47,13 +47,15 @@ export const SamlSignature = () => {
|
|||
const [keyOpen, setKeyOpen] = useState(false);
|
||||
const [canOpen, setCanOpen] = useState(false);
|
||||
|
||||
const { control, watch } = useFormContext<ClientRepresentation>();
|
||||
const { control, watch } = useFormContext<FormFields>();
|
||||
|
||||
const signDocs = watch(
|
||||
convertAttributeNameToForm("attributes.saml.server.signature")
|
||||
convertAttributeNameToForm<FormFields>("attributes.saml.server.signature")
|
||||
);
|
||||
const signAssertion = watch(
|
||||
convertAttributeNameToForm("attributes.saml.assertion.signature")
|
||||
convertAttributeNameToForm<FormFields>(
|
||||
"attributes.saml.assertion.signature"
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -83,28 +85,27 @@ export const SamlSignature = () => {
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.saml.signature.algorithm"
|
||||
)}
|
||||
defaultValue={SIGNATURE_ALGORITHMS[0]}
|
||||
Key
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="signatureAlgorithm"
|
||||
onToggle={setAlgOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
field.onChange(value.toString());
|
||||
setAlgOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("signatureAlgorithm")}
|
||||
isOpen={algOpen}
|
||||
>
|
||||
{SIGNATURE_ALGORITHMS.map((algorithm) => (
|
||||
<SelectOption
|
||||
selected={algorithm === value}
|
||||
selected={algorithm === field.value}
|
||||
key={algorithm}
|
||||
value={algorithm}
|
||||
/>
|
||||
|
@ -124,27 +125,27 @@ export const SamlSignature = () => {
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
"attributes.saml.server.signature.keyinfo$xmlSigKeyInfoKeyNameTransformer"
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer"
|
||||
)}
|
||||
defaultValue={KEYNAME_TRANSFORMER[0]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="signatureKeyName"
|
||||
onToggle={setKeyOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
field.onChange(value.toString());
|
||||
setKeyOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("signatureKeyName")}
|
||||
isOpen={keyOpen}
|
||||
>
|
||||
{KEYNAME_TRANSFORMER.map((key) => (
|
||||
<SelectOption
|
||||
selected={key === value}
|
||||
selected={key === field.value}
|
||||
key={key}
|
||||
value={key}
|
||||
/>
|
||||
|
@ -167,16 +168,17 @@ export const SamlSignature = () => {
|
|||
name="attributes.saml_signature_canonicalization_method"
|
||||
defaultValue={CANONICALIZATION[0].value}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="canonicalization"
|
||||
onToggle={setCanOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
field.onChange(value.toString());
|
||||
setCanOpen(false);
|
||||
}}
|
||||
selections={
|
||||
CANONICALIZATION.find((can) => can.value === value)?.name
|
||||
CANONICALIZATION.find((can) => can.value === field.value)
|
||||
?.name
|
||||
}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("canonicalization")}
|
||||
|
@ -184,7 +186,7 @@ export const SamlSignature = () => {
|
|||
>
|
||||
{CANONICALIZATION.map((can) => (
|
||||
<SelectOption
|
||||
selected={can.value === value}
|
||||
selected={can.value === field.value}
|
||||
key={can.name}
|
||||
value={can.value}
|
||||
>
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
|
@ -10,17 +7,20 @@ import {
|
|||
SelectVariant,
|
||||
Switch,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { KeyValueInput } from "../../components/key-value-form/hook-form-v7/KeyValueInput";
|
||||
import { MultiLineInput } from "../../components/multi-line-input/hook-form-v7/MultiLineInput";
|
||||
import { TimeSelector } from "../../components/time-selector/TimeSelector";
|
||||
import { TokenLifespan } from "./TokenLifespan";
|
||||
import { KeyValueInput } from "../../components/key-value-form/KeyValueInput";
|
||||
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import { TokenLifespan } from "./TokenLifespan";
|
||||
|
||||
type AdvancedSettingsProps = {
|
||||
control: Control<Record<string, any>>;
|
||||
save: () => void;
|
||||
reset: () => void;
|
||||
protocol?: string;
|
||||
|
@ -28,7 +28,6 @@ type AdvancedSettingsProps = {
|
|||
};
|
||||
|
||||
export const AdvancedSettings = ({
|
||||
control,
|
||||
save,
|
||||
reset,
|
||||
protocol,
|
||||
|
@ -36,6 +35,8 @@ export const AdvancedSettings = ({
|
|||
}: AdvancedSettingsProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { control, register } = useFormContext();
|
||||
return (
|
||||
<FormAccess
|
||||
role="manage-realm"
|
||||
|
@ -54,16 +55,16 @@ export const AdvancedSettings = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.saml.assertion.lifespan"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<TimeSelector
|
||||
units={["minute", "day", "hour"]}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
@ -78,7 +79,6 @@ export const AdvancedSettings = ({
|
|||
)}
|
||||
defaultValue=""
|
||||
units={["minute", "day", "hour"]}
|
||||
control={control}
|
||||
/>
|
||||
|
||||
<TokenLifespan
|
||||
|
@ -88,7 +88,6 @@ export const AdvancedSettings = ({
|
|||
)}
|
||||
defaultValue=""
|
||||
units={["minute", "day", "hour"]}
|
||||
control={control}
|
||||
/>
|
||||
|
||||
<TokenLifespan
|
||||
|
@ -98,7 +97,6 @@ export const AdvancedSettings = ({
|
|||
)}
|
||||
defaultValue=""
|
||||
units={["minute", "day", "hour"]}
|
||||
control={control}
|
||||
/>
|
||||
|
||||
<TokenLifespan
|
||||
|
@ -108,7 +106,6 @@ export const AdvancedSettings = ({
|
|||
)}
|
||||
defaultValue=""
|
||||
units={["minute", "day", "hour"]}
|
||||
control={control}
|
||||
/>
|
||||
|
||||
<TokenLifespan
|
||||
|
@ -118,7 +115,6 @@ export const AdvancedSettings = ({
|
|||
)}
|
||||
defaultValue=""
|
||||
units={["minute", "day", "hour"]}
|
||||
control={control}
|
||||
/>
|
||||
|
||||
<FormGroup
|
||||
|
@ -136,13 +132,13 @@ export const AdvancedSettings = ({
|
|||
name="attributes.tls-client-certificate-bound-access-tokens"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="oAuthMutual-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange("" + value)}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange("" + value)}
|
||||
aria-label={t("oAuthMutual")}
|
||||
/>
|
||||
)}
|
||||
|
@ -160,22 +156,22 @@ export const AdvancedSettings = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.pkce.code.challenge.method"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="keyForCodeExchange"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setOpen}
|
||||
isOpen={open}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setOpen(false);
|
||||
}}
|
||||
selections={[value || t("common:choose")]}
|
||||
selections={[field.value || t("common:choose")]}
|
||||
>
|
||||
{["", "S256", "plain"].map((v) => (
|
||||
<SelectOption key={v} value={v}>
|
||||
|
@ -197,18 +193,18 @@ export const AdvancedSettings = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.require.pushed.authorization.requests"
|
||||
)}
|
||||
defaultValue="false"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="pushedAuthorizationRequestRequired"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange(value.toString())}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange(value.toString())}
|
||||
aria-label={t("pushedAuthorizationRequestRequired")}
|
||||
/>
|
||||
)}
|
||||
|
@ -225,7 +221,9 @@ export const AdvancedSettings = ({
|
|||
}
|
||||
>
|
||||
<KeyValueInput
|
||||
name={convertAttributeNameToForm("attributes.acr.loa.map")}
|
||||
{...register(
|
||||
convertAttributeNameToForm("attributes.acr.loa.map")
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { FormGroup } from "@patternfly/react-core";
|
||||
import { useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
|
||||
export const ApplicationUrls = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
|
@ -23,11 +24,11 @@ export const ApplicationUrls = () => {
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="logoUrl"
|
||||
name={convertAttributeNameToForm("attributes.logoUri")}
|
||||
data-testid="logoUrl"
|
||||
ref={register}
|
||||
{...register(
|
||||
convertAttributeNameToForm<FormFields>("attributes.logoUri")
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -41,11 +42,11 @@ export const ApplicationUrls = () => {
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="policyUrl"
|
||||
name={convertAttributeNameToForm("attributes.policyUri")}
|
||||
data-testid="policyUrl"
|
||||
ref={register}
|
||||
{...register(
|
||||
convertAttributeNameToForm<FormFields>("attributes.policyUri")
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -59,11 +60,11 @@ export const ApplicationUrls = () => {
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="termsOfServiceUrl"
|
||||
name={convertAttributeNameToForm("attributes.tosUri")}
|
||||
data-testid="termsOfServiceUrl"
|
||||
ref={register}
|
||||
{...register(
|
||||
convertAttributeNameToForm<FormFields>("attributes.tosUri")
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
import { useState } from "react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { sortBy } from "lodash-es";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
|
@ -10,13 +6,16 @@ import {
|
|||
SelectOption,
|
||||
SelectVariant,
|
||||
} from "@patternfly/react-core";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
|
||||
type AuthenticationOverridesProps = {
|
||||
control: Control<Record<string, any>>;
|
||||
save: () => void;
|
||||
reset: () => void;
|
||||
protocol?: string;
|
||||
|
@ -25,7 +24,6 @@ type AuthenticationOverridesProps = {
|
|||
|
||||
export const AuthenticationOverrides = ({
|
||||
protocol,
|
||||
control,
|
||||
save,
|
||||
reset,
|
||||
hasConfigureAccess,
|
||||
|
@ -36,6 +34,8 @@ export const AuthenticationOverrides = ({
|
|||
const [browserFlowOpen, setBrowserFlowOpen] = useState(false);
|
||||
const [directGrantOpen, setDirectGrantOpen] = useState(false);
|
||||
|
||||
const { control } = useFormContext();
|
||||
|
||||
useFetch(
|
||||
() => adminClient.authenticationManagement.getFlows(),
|
||||
(flows) => {
|
||||
|
@ -77,17 +77,17 @@ export const AuthenticationOverrides = ({
|
|||
name="authenticationFlowBindingOverrides.browser"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="browserFlow"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setBrowserFlowOpen}
|
||||
isOpen={browserFlowOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setBrowserFlowOpen(false);
|
||||
}}
|
||||
selections={[value]}
|
||||
selections={[field.value]}
|
||||
>
|
||||
{flows}
|
||||
</Select>
|
||||
|
@ -109,17 +109,17 @@ export const AuthenticationOverrides = ({
|
|||
name="authenticationFlowBindingOverrides.direct_grant"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="directGrant"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setDirectGrantOpen}
|
||||
isOpen={directGrantOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setDirectGrantOpen(false);
|
||||
}}
|
||||
selections={[value]}
|
||||
selections={[field.value]}
|
||||
>
|
||||
{flows}
|
||||
</Select>
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
|
@ -11,8 +8,10 @@ import {
|
|||
SplitItem,
|
||||
ToolbarItem,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { AdvancedProps, parseResult } from "../AdvancedTab";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
|
@ -21,8 +20,9 @@ import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState
|
|||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||
import { TimeSelector } from "../../components/time-selector/TimeSelector";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { AddHostDialog } from ".././advanced/AddHostDialog";
|
||||
import useFormatDate, { FORMAT_DATE_AND_TIME } from "../../utils/useFormatDate";
|
||||
import { AddHostDialog } from ".././advanced/AddHostDialog";
|
||||
import { AdvancedProps, parseResult } from "../AdvancedTab";
|
||||
|
||||
export const ClusteringPanel = ({
|
||||
save,
|
||||
|
@ -98,8 +98,8 @@ export const ClusteringPanel = ({
|
|||
name="nodeReRegistrationTimeout"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<TimeSelector value={value} onChange={onChange} />
|
||||
render={({ field }) => (
|
||||
<TimeSelector value={field.value} onChange={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
</SplitItem>
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
|
@ -9,12 +6,16 @@ import {
|
|||
SelectOption,
|
||||
SelectVariant,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { MultiLineInput } from "../../components/multi-line-input/hook-form-v7/MultiLineInput";
|
||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||
import { convertAttributeNameToForm, sortProviders } from "../../util";
|
||||
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import { ApplicationUrls } from "./ApplicationUrls";
|
||||
|
||||
type FineGrainOpenIdConnectProps = {
|
||||
|
@ -160,22 +161,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.access.token.signed.response.alg"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="accessTokenSignatureAlgorithm"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setAccessTokenOpen}
|
||||
isOpen={accessTokenOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setAccessTokenOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{keyOptions}
|
||||
</Select>
|
||||
|
@ -193,22 +194,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.id.token.signed.response.alg"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="idTokenSignatureAlgorithm"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setIdTokenOpen}
|
||||
isOpen={idTokenOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setIdTokenOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{keyOptions}
|
||||
</Select>
|
||||
|
@ -226,22 +227,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.id.token.encrypted.response.alg"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="idTokenEncryptionKeyManagementAlgorithm"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setIdTokenKeyManagementOpen}
|
||||
isOpen={idTokenKeyManagementOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setIdTokenKeyManagementOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{cekManagementOptions}
|
||||
</Select>
|
||||
|
@ -259,22 +260,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.id.token.encrypted.response.enc"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="idTokenEncryptionContentEncryptionAlgorithm"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setIdTokenContentOpen}
|
||||
isOpen={idTokenContentOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setIdTokenContentOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{contentOptions}
|
||||
</Select>
|
||||
|
@ -292,22 +293,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.user.info.response.signature.alg"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="userInfoSignedResponseAlgorithm"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setUserInfoSignedResponseOpen}
|
||||
isOpen={userInfoSignedResponseOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setUserInfoSignedResponseOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{signatureOptions}
|
||||
</Select>
|
||||
|
@ -325,22 +326,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.request.object.signature.alg"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="requestObjectSignatureAlgorithm"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setRequestObjectSignatureOpen}
|
||||
isOpen={requestObjectSignatureOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setRequestObjectSignatureOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{requestObjectOptions}
|
||||
</Select>
|
||||
|
@ -358,22 +359,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.request.object.encryption.alg"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="requestObjectEncryption"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setRequestObjectEncryptionOpen}
|
||||
isOpen={requestObjectEncryptionOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setRequestObjectEncryptionOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{requestObjectEncryptionOptions}
|
||||
</Select>
|
||||
|
@ -391,22 +392,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.request.object.encryption.enc"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="requestObjectEncoding"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setRequestObjectEncodingOpen}
|
||||
isOpen={requestObjectEncodingOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setRequestObjectEncodingOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{requestObjectEncodingOptions}
|
||||
</Select>
|
||||
|
@ -424,22 +425,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.request.object.required"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="requestObjectRequired"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setRequestObjectRequiredOpen}
|
||||
isOpen={requestObjectRequiredOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setRequestObjectRequiredOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{requestObjectRequiredOptions}
|
||||
</Select>
|
||||
|
@ -474,22 +475,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.authorization.signed.response.alg"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="authorizationSignedResponseAlg"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setAuthorizationSignedOpen}
|
||||
isOpen={authorizationSignedOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setAuthorizationSignedOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{authorizationSignedResponseOptions}
|
||||
</Select>
|
||||
|
@ -507,22 +508,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.authorization.encrypted.response.alg"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="authorizationEncryptedResponseAlg"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setAuthorizationEncryptedOpen}
|
||||
isOpen={authorizationEncryptedOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setAuthorizationEncryptedOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{cekManagementOptions}
|
||||
</Select>
|
||||
|
@ -540,22 +541,22 @@ export const FineGrainOpenIdConnect = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.authorization.encrypted.response.enc"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="authorizationEncryptedResponseEnc"
|
||||
variant={SelectVariant.single}
|
||||
onToggle={setAuthorizationEncryptedResponseOpen}
|
||||
isOpen={authorizationEncryptedResponseOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setAuthorizationEncryptedResponseOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
>
|
||||
{contentOptions}
|
||||
</Select>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import type { Control } from "react-hook-form";
|
||||
import { ActionGroup, Button, FormGroup } from "@patternfly/react-core";
|
||||
import { useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
|
@ -8,17 +8,16 @@ import { KeycloakTextInput } from "../../components/keycloak-text-input/Keycloak
|
|||
import { ApplicationUrls } from "./ApplicationUrls";
|
||||
|
||||
type FineGrainSamlEndpointConfigProps = {
|
||||
control: Control<Record<string, any>>;
|
||||
save: () => void;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
export const FineGrainSamlEndpointConfig = ({
|
||||
control: { register },
|
||||
save,
|
||||
reset,
|
||||
}: FineGrainSamlEndpointConfigProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { register } = useFormContext();
|
||||
return (
|
||||
<FormAccess role="manage-realm" isHorizontal>
|
||||
<ApplicationUrls />
|
||||
|
@ -33,10 +32,8 @@ export const FineGrainSamlEndpointConfig = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="assertionConsumerServicePostBindingURL"
|
||||
name="attributes.saml_assertion_consumer_url_post"
|
||||
{...register("attributes.saml_assertion_consumer_url_post")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -50,10 +47,8 @@ export const FineGrainSamlEndpointConfig = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="assertionConsumerServiceRedirectBindingURL"
|
||||
name="attributes.saml_assertion_consumer_url_redirect"
|
||||
{...register("attributes.saml_assertion_consumer_url_redirect")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -67,10 +62,8 @@ export const FineGrainSamlEndpointConfig = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="logoutServicePostBindingURL"
|
||||
name="attributes.saml_single_logout_service_url_post"
|
||||
{...register("attributes.saml_single_logout_service_url_post")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -84,10 +77,8 @@ export const FineGrainSamlEndpointConfig = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="logoutServiceRedirectBindingURL"
|
||||
name="attributes.saml_single_logout_service_url_redirect"
|
||||
{...register("attributes.saml_single_logout_service_url_redirect")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -101,10 +92,8 @@ export const FineGrainSamlEndpointConfig = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="logoutServiceArtifactBindingUrl"
|
||||
name="attributes.saml_single_logout_service_url_artifact"
|
||||
{...register("attributes.saml_single_logout_service_url_artifact")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -118,10 +107,8 @@ export const FineGrainSamlEndpointConfig = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="artifactBindingUrl"
|
||||
name="attributes.saml_artifact_binding_url"
|
||||
{...register("attributes.saml_artifact_binding_url")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -135,10 +122,8 @@ export const FineGrainSamlEndpointConfig = ({
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="artifactResolutionService"
|
||||
name="attributes.saml_artifact_resolution_service_url"
|
||||
{...register("attributes.saml_artifact_resolution_service_url")}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
import { ActionGroup, Button, FormGroup, Switch } from "@patternfly/react-core";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
|
||||
type OpenIdConnectCompatibilityModesProps = {
|
||||
control: Control<Record<string, any>>;
|
||||
save: () => void;
|
||||
reset: () => void;
|
||||
hasConfigureAccess?: boolean;
|
||||
};
|
||||
|
||||
export const OpenIdConnectCompatibilityModes = ({
|
||||
control,
|
||||
save,
|
||||
reset,
|
||||
hasConfigureAccess,
|
||||
}: OpenIdConnectCompatibilityModesProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { control } = useFormContext();
|
||||
return (
|
||||
<FormAccess
|
||||
role="manage-realm"
|
||||
|
@ -38,18 +38,18 @@ export const OpenIdConnectCompatibilityModes = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.exclude.session.state.from.auth.response"
|
||||
)}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="excludeSessionStateFromAuthenticationResponse-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange(value.toString())}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange(value.toString())}
|
||||
aria-label={t("excludeSessionStateFromAuthenticationResponse")}
|
||||
/>
|
||||
)}
|
||||
|
@ -67,16 +67,18 @@ export const OpenIdConnectCompatibilityModes = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm("attributes.use.refresh.tokens")}
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.use.refresh.tokens"
|
||||
)}
|
||||
defaultValue="true"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="useRefreshTokens"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange(value.toString())}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange(value.toString())}
|
||||
aria-label={t("useRefreshTokens")}
|
||||
/>
|
||||
)}
|
||||
|
@ -94,18 +96,18 @@ export const OpenIdConnectCompatibilityModes = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.client_credentials.use_refresh_token"
|
||||
)}
|
||||
defaultValue="false"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="useRefreshTokenForClientCredentialsGrant"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange(value.toString())}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange(value.toString())}
|
||||
aria-label={t("useRefreshTokenForClientCredentialsGrant")}
|
||||
/>
|
||||
)}
|
||||
|
@ -123,18 +125,18 @@ export const OpenIdConnectCompatibilityModes = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm(
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
"attributes.token.response.type.bearer.lower-case"
|
||||
)}
|
||||
defaultValue="false"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="useLowerCaseBearerType"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange(value.toString())}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange(value.toString())}
|
||||
aria-label={t("useLowerCaseBearerType")}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import { Link } from "react-router-dom-v5-compat";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
Button,
|
||||
ActionGroup,
|
||||
Tooltip,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@patternfly/react-core";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useFormContext } from "react-hook-form-v7";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom-v5-compat";
|
||||
|
||||
import { AdvancedProps, parseResult } from "../AdvancedTab";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { toClient } from "../routes/Client";
|
||||
import useFormatDate, { FORMAT_DATE_AND_TIME } from "../../utils/useFormatDate";
|
||||
import { AdvancedProps, parseResult } from "../AdvancedTab";
|
||||
import { toClient } from "../routes/Client";
|
||||
|
||||
export const RevocationPanel = ({
|
||||
save,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from "react";
|
||||
import { Control, Controller, FieldValues } from "react-hook-form";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
FormGroup,
|
||||
|
@ -20,7 +20,6 @@ type TokenLifespanProps = {
|
|||
id: string;
|
||||
name: string;
|
||||
defaultValue: string;
|
||||
control: Control<FieldValues>;
|
||||
units?: Unit[];
|
||||
};
|
||||
|
||||
|
@ -31,7 +30,6 @@ export const TokenLifespan = ({
|
|||
id,
|
||||
name,
|
||||
defaultValue,
|
||||
control,
|
||||
units,
|
||||
}: TokenLifespanProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
|
@ -41,6 +39,7 @@ export const TokenLifespan = ({
|
|||
const onFocus = () => setFocused(true);
|
||||
const onBlur = () => setFocused(false);
|
||||
|
||||
const { control } = useFormContext();
|
||||
const isExpireSet = (value: string | number) =>
|
||||
(typeof value === "number" && value !== -1) ||
|
||||
(typeof value === "string" && value !== "" && value !== "-1") ||
|
||||
|
@ -61,7 +60,7 @@ export const TokenLifespan = ({
|
|||
name={name}
|
||||
defaultValue={defaultValue}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Split hasGutter>
|
||||
<SplitItem>
|
||||
<Select
|
||||
|
@ -69,21 +68,21 @@ export const TokenLifespan = ({
|
|||
onToggle={setOpen}
|
||||
isOpen={open}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
field.onChange(value);
|
||||
setOpen(false);
|
||||
}}
|
||||
selections={[isExpireSet(value) ? t(expires) : t(never)]}
|
||||
selections={[isExpireSet(field.value) ? t(expires) : t(never)]}
|
||||
>
|
||||
<SelectOption value={-1}>{t(never)}</SelectOption>
|
||||
<SelectOption value={60}>{t(expires)}</SelectOption>
|
||||
</Select>
|
||||
</SplitItem>
|
||||
<SplitItem>
|
||||
{isExpireSet(value) && (
|
||||
{isExpireSet(field.value) && (
|
||||
<TimeSelector
|
||||
units={units}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
min={1}
|
||||
|
|
|
@ -1,40 +1,41 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectVariant,
|
||||
SelectOption,
|
||||
PageSection,
|
||||
ActionGroup,
|
||||
Button,
|
||||
Switch,
|
||||
ExpandableSection,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
Switch,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||
import type EvaluationResultRepresentation from "@keycloak/keycloak-admin-client/lib/defs/evaluationResultRepresentation";
|
||||
import type PolicyEvaluationResponse from "@keycloak/keycloak-admin-client/lib/defs/policyEvaluationResponse";
|
||||
import type ResourceEvaluation from "@keycloak/keycloak-admin-client/lib/defs/resourceEvaluation";
|
||||
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
|
||||
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||
import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation";
|
||||
import type PolicyEvaluationResponse from "@keycloak/keycloak-admin-client/lib/defs/policyEvaluationResponse";
|
||||
|
||||
import type { KeyValueType } from "../../components/key-value-form/key-value-convert";
|
||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { ClientSelect } from "../../components/client/ClientSelect";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import type { KeyValueType } from "../../components/key-value-form/key-value-convert";
|
||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { FormPanel } from "../../components/scroll-form/FormPanel";
|
||||
import { UserSelect } from "../../components/users/UserSelect";
|
||||
import { useAccess } from "../../context/access/Access";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { KeyBasedAttributeInput } from "./KeyBasedAttributeInput";
|
||||
import { defaultContextAttributes } from "../utils";
|
||||
import { useAccess } from "../../context/access/Access";
|
||||
import { ForbiddenSection } from "../../ForbiddenSection";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import { defaultContextAttributes } from "../utils";
|
||||
import { Results } from "./evaluate/Results";
|
||||
import { ClientSelect } from "../../components/client/ClientSelect";
|
||||
import { UserSelect } from "../../components/users/UserSelect";
|
||||
import { KeyBasedAttributeInput } from "./KeyBasedAttributeInput";
|
||||
|
||||
import "./auth-evaluate.css";
|
||||
|
||||
|
@ -46,7 +47,7 @@ interface EvaluateFormInputs
|
|||
attributes: Record<string, string>[];
|
||||
};
|
||||
resources?: Record<string, string>[];
|
||||
client: ClientRepresentation;
|
||||
client: FormFields;
|
||||
user: string[];
|
||||
}
|
||||
|
||||
|
@ -82,9 +83,8 @@ export const AuthorizationEvaluate = ({ client }: Props) => {
|
|||
control,
|
||||
register,
|
||||
reset,
|
||||
errors,
|
||||
trigger,
|
||||
formState: { isValid },
|
||||
formState: { isValid, errors },
|
||||
} = form;
|
||||
const { t } = useTranslation("clients");
|
||||
const { adminClient } = useAdminClient();
|
||||
|
@ -210,37 +210,37 @@ export const AuthorizationEvaluate = ({ client }: Props) => {
|
|||
>
|
||||
<Controller
|
||||
name="roleIds"
|
||||
placeholderText={t("selectARole")}
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
rules={{ validate: (value) => value.length > 0 }}
|
||||
render={({ onChange, value }) => (
|
||||
rules={{ validate: (value) => (value || "").length > 0 }}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
placeholderText={t("selectARole")}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
toggleId="role"
|
||||
onToggle={setRoleDropdownOpen}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
onSelect={(_, v) => {
|
||||
const option = v.toString();
|
||||
if (value.includes(option)) {
|
||||
onChange(
|
||||
value.filter((item: string) => item !== option)
|
||||
if (field.value?.includes(option)) {
|
||||
field.onChange(
|
||||
field.value.filter((item: string) => item !== option)
|
||||
);
|
||||
} else {
|
||||
onChange([...value, option]);
|
||||
field.onChange([...(field.value || []), option]);
|
||||
}
|
||||
setRoleDropdownOpen(false);
|
||||
}}
|
||||
onClear={(event) => {
|
||||
event.stopPropagation();
|
||||
onChange([]);
|
||||
field.onChange([]);
|
||||
}}
|
||||
aria-label={t("realmRole")}
|
||||
isOpen={roleDropdownOpen}
|
||||
>
|
||||
{clientRoles.map((role) => (
|
||||
<SelectOption
|
||||
selected={role.name === value}
|
||||
selected={role.name === field.value}
|
||||
key={role.name}
|
||||
value={role.name}
|
||||
/>
|
||||
|
@ -308,15 +308,13 @@ export const AuthorizationEvaluate = ({ client }: Props) => {
|
|||
/>
|
||||
}
|
||||
fieldId="client"
|
||||
validated={form.errors.alias ? "error" : "default"}
|
||||
validated={errors.alias ? "error" : "default"}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="alias"
|
||||
name="alias"
|
||||
data-testid="alias"
|
||||
ref={register({ required: true })}
|
||||
{...register("alias", { required: true })}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -333,29 +331,31 @@ export const AuthorizationEvaluate = ({ client }: Props) => {
|
|||
name="authScopes"
|
||||
defaultValue={[]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="authScopes"
|
||||
onToggle={setScopesDropdownOpen}
|
||||
onSelect={(_, v) => {
|
||||
const option = v.toString();
|
||||
if (value.includes(option)) {
|
||||
onChange(
|
||||
value.filter((item: string) => item !== option)
|
||||
if (field.value.includes(option)) {
|
||||
field.onChange(
|
||||
field.value.filter(
|
||||
(item: string) => item !== option
|
||||
)
|
||||
);
|
||||
} else {
|
||||
onChange([...value, option]);
|
||||
field.onChange([...field.value, option]);
|
||||
}
|
||||
setScopesDropdownOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
aria-label={t("authScopes")}
|
||||
isOpen={scopesDropdownOpen}
|
||||
>
|
||||
{scopes.map((scope) => (
|
||||
<SelectOption
|
||||
selected={scope.name === value}
|
||||
selected={field.value.includes(scope.name!)}
|
||||
key={scope.id}
|
||||
value={scope.name}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { FormGroup, Radio } from "@patternfly/react-core";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
|
||||
|
@ -35,7 +35,7 @@ export const DecisionStrategySelect = ({
|
|||
data-testid="decisionStrategy"
|
||||
defaultValue={DECISION_STRATEGY[0]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={(field) => (
|
||||
<>
|
||||
{(isLimited
|
||||
? DECISION_STRATEGY.slice(0, 2)
|
||||
|
@ -45,9 +45,9 @@ export const DecisionStrategySelect = ({
|
|||
id={strategy}
|
||||
key={strategy}
|
||||
data-testid={strategy}
|
||||
isChecked={value === strategy}
|
||||
isChecked={field.value === strategy}
|
||||
name="decisionStrategy"
|
||||
onChange={() => onChange(strategy)}
|
||||
onChange={() => field.onChange(strategy)}
|
||||
label={t(`decisionStrategies.${strategy}`)}
|
||||
className="pf-u-mb-md"
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import { useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom-v5-compat";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { Language } from "@patternfly/react-code-editor";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
|
@ -9,14 +6,18 @@ import {
|
|||
FormGroup,
|
||||
PageSection,
|
||||
} from "@patternfly/react-core";
|
||||
import { Language } from "@patternfly/react-code-editor";
|
||||
import { useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate } from "react-router-dom-v5-compat";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import { FileUploadForm } from "../../components/json-file-upload/FileUploadForm";
|
||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import {
|
||||
|
@ -24,12 +25,12 @@ import {
|
|||
convertFormValuesToObject,
|
||||
convertToFormValues,
|
||||
} from "../../util";
|
||||
import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders";
|
||||
import { CapabilityConfig } from "../add/CapabilityConfig";
|
||||
import { ClientDescription } from "../ClientDescription";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import { toClient } from "../routes/Client";
|
||||
import { toClients } from "../routes/Clients";
|
||||
import { FileUploadForm } from "../../components/json-file-upload/FileUploadForm";
|
||||
import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders";
|
||||
|
||||
const isXml = (text: string) => text.startsWith("<");
|
||||
|
||||
|
@ -38,7 +39,7 @@ export default function ImportForm() {
|
|||
const navigate = useNavigate();
|
||||
const { adminClient } = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
const form = useForm<ClientRepresentation>({ shouldUnregister: false });
|
||||
const form = useForm<FormFields>({ shouldUnregister: false });
|
||||
const { register, handleSubmit, setValue } = form;
|
||||
const [imported, setImported] = useState<ClientRepresentation>({});
|
||||
|
||||
|
@ -118,11 +119,9 @@ export default function ImportForm() {
|
|||
<ClientDescription hasConfigureAccess />
|
||||
<FormGroup label={t("common:type")} fieldId="kc-type">
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="kc-type"
|
||||
name="protocol"
|
||||
isReadOnly
|
||||
ref={register()}
|
||||
{...register("protocol")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<CapabilityConfig unWrap={true} />
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { saveAs } from "file-saver";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
|
@ -15,21 +12,24 @@ import {
|
|||
Text,
|
||||
TextContent,
|
||||
} from "@patternfly/react-core";
|
||||
import { saveAs } from "file-saver";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import type CertificateRepresentation from "@keycloak/keycloak-admin-client/lib/defs/certificateRepresentation";
|
||||
import type KeyStoreConfig from "@keycloak/keycloak-admin-client/lib/defs/keystoreConfig";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { GenerateKeyDialog, getFileExtension } from "./GenerateKeyDialog";
|
||||
import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form-v7";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { ImportKeyDialog, ImportFile } from "./ImportKeyDialog";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import { Certificate } from "./Certificate";
|
||||
import { GenerateKeyDialog, getFileExtension } from "./GenerateKeyDialog";
|
||||
import { ImportFile, ImportKeyDialog } from "./ImportKeyDialog";
|
||||
|
||||
type KeysProps = {
|
||||
save: () => void;
|
||||
|
@ -46,7 +46,7 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => {
|
|||
register,
|
||||
getValues,
|
||||
formState: { isDirty },
|
||||
} = useFormContext<ClientRepresentation>();
|
||||
} = useFormContext<FormFields>();
|
||||
const { adminClient } = useAdminClient();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
|
||||
|
@ -149,16 +149,15 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => {
|
|||
>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm("attributes.use.jwks.url")}
|
||||
defaultValue="false"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
data-testid="useJwksUrl"
|
||||
id="useJwksUrl-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => onChange(`${value}`)}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => field.onChange(`${value}`)}
|
||||
aria-label={t("useJwksUrl")}
|
||||
/>
|
||||
)}
|
||||
|
@ -182,10 +181,10 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => {
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="jwksUrl"
|
||||
name={convertAttributeNameToForm("attributes.jwks.url")}
|
||||
ref={register}
|
||||
{...register(
|
||||
convertAttributeNameToForm("attributes.jwks.url")
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
import { Fragment, useState } from "react";
|
||||
import { saveAs } from "file-saver";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
CardBody,
|
||||
PageSection,
|
||||
TextContent,
|
||||
Text,
|
||||
FormGroup,
|
||||
Switch,
|
||||
Card,
|
||||
Form,
|
||||
ActionGroup,
|
||||
Button,
|
||||
AlertVariant,
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
Form,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
Switch,
|
||||
Text,
|
||||
TextContent,
|
||||
} from "@patternfly/react-core";
|
||||
import { saveAs } from "file-saver";
|
||||
import { Fragment, useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import type CertificateRepresentation from "@keycloak/keycloak-admin-client/lib/defs/certificateRepresentation";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { SamlKeysDialog } from "./SamlKeysDialog";
|
||||
import { FormPanel } from "../../components/scroll-form/FormPanel";
|
||||
import { Certificate } from "./Certificate";
|
||||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { SamlImportKeyDialog } from "./SamlImportKeyDialog";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import { Certificate } from "./Certificate";
|
||||
import { ExportSamlKeyDialog } from "./ExportSamlKeyDialog";
|
||||
import { SamlImportKeyDialog } from "./SamlImportKeyDialog";
|
||||
import { SamlKeysDialog } from "./SamlKeysDialog";
|
||||
|
||||
type SamlKeysProps = {
|
||||
clientId: string;
|
||||
|
@ -70,14 +70,14 @@ const KeySection = ({
|
|||
onImport,
|
||||
}: KeySectionProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { control, watch } = useFormContext<ClientRepresentation>();
|
||||
const { control, watch } = useFormContext<FormFields>();
|
||||
const title = KEYS_MAPPING[attr].title;
|
||||
const key = KEYS_MAPPING[attr].key;
|
||||
const name = KEYS_MAPPING[attr].name;
|
||||
|
||||
const [showImportDialog, toggleImportDialog] = useToggle();
|
||||
|
||||
const section = watch(name);
|
||||
const section = watch(name as keyof FormFields);
|
||||
return (
|
||||
<>
|
||||
{showImportDialog && (
|
||||
|
@ -100,21 +100,21 @@ const KeySection = ({
|
|||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name={name}
|
||||
name={name as keyof FormFields}
|
||||
control={control}
|
||||
defaultValue="false"
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
data-testid={key}
|
||||
id={key}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
isChecked={field.value === "true"}
|
||||
onChange={(value) => {
|
||||
const v = value.toString();
|
||||
if (v === "true") {
|
||||
onChanged(attr);
|
||||
onChange(v);
|
||||
field.onChange(v);
|
||||
} else {
|
||||
onGenerate(attr, false);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
import {
|
||||
ActionList,
|
||||
ActionListItem,
|
||||
Button,
|
||||
Flex,
|
||||
FlexItem,
|
||||
} from "@patternfly/react-core";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
import { useEffect } from "react";
|
||||
import { useFieldArray, useFormContext, useWatch } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { KeycloakTextInput } from "../../keycloak-text-input/KeycloakTextInput";
|
||||
import { KeyValueType } from "../key-value-convert";
|
||||
|
||||
type KeyValueInputProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const KeyValueInput = ({ name }: KeyValueInputProps) => {
|
||||
const { t } = useTranslation("common");
|
||||
const { control, register } = useFormContext();
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name,
|
||||
});
|
||||
|
||||
const watchFields = useWatch({
|
||||
control,
|
||||
name,
|
||||
defaultValue: [{ key: "", value: "" }],
|
||||
});
|
||||
|
||||
const isValid =
|
||||
Array.isArray(watchFields) &&
|
||||
watchFields.every(
|
||||
({ key, value }: KeyValueType) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
key?.trim().length !== 0 && value?.trim().length !== 0
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fields.length) {
|
||||
append({ key: "", value: "" }, { shouldFocus: false });
|
||||
}
|
||||
}, [fields]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex direction={{ default: "column" }}>
|
||||
<Flex>
|
||||
<FlexItem
|
||||
grow={{ default: "grow" }}
|
||||
spacer={{ default: "spacerNone" }}
|
||||
>
|
||||
<strong>{t("key")}</strong>
|
||||
</FlexItem>
|
||||
<FlexItem grow={{ default: "grow" }}>
|
||||
<strong>{t("value")}</strong>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
{fields.map((attribute, index) => (
|
||||
<Flex key={attribute.id} data-testid="row">
|
||||
<FlexItem grow={{ default: "grow" }}>
|
||||
<KeycloakTextInput
|
||||
placeholder={t("keyPlaceholder")}
|
||||
aria-label={t("key")}
|
||||
defaultValue=""
|
||||
data-testid={`${name}[${index}].key`}
|
||||
{...register(`${name}[${index}].key`)}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem
|
||||
grow={{ default: "grow" }}
|
||||
spacer={{ default: "spacerNone" }}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
placeholder={t("valuePlaceholder")}
|
||||
aria-label={t("value")}
|
||||
defaultValue=""
|
||||
data-testid={`${name}[${index}].value`}
|
||||
{...register(`${name}[${index}].value`)}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<Button
|
||||
variant="link"
|
||||
title={t("removeAttribute")}
|
||||
isDisabled={watchFields.length === 1}
|
||||
onClick={() => remove(index)}
|
||||
data-testid={`${name}[${index}].remove`}
|
||||
>
|
||||
<MinusCircleIcon />
|
||||
</Button>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
<ActionList>
|
||||
<ActionListItem>
|
||||
<Button
|
||||
data-testid={`${name}-add-row`}
|
||||
className="pf-u-px-0 pf-u-mt-sm"
|
||||
variant="link"
|
||||
icon={<PlusCircleIcon />}
|
||||
isDisabled={!isValid}
|
||||
onClick={() => append({ key: "", value: "" })}
|
||||
>
|
||||
{t("addAttribute")}
|
||||
</Button>
|
||||
</ActionListItem>
|
||||
</ActionList>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,3 +1,5 @@
|
|||
import { Path, PathValue } from "react-hook-form-v7";
|
||||
|
||||
export type KeyValueType = { key: string; value: string };
|
||||
|
||||
export function keyValueToArray(attributeArray: KeyValueType[] = []) {
|
||||
|
@ -15,10 +17,10 @@ export function keyValueToArray(attributeArray: KeyValueType[] = []) {
|
|||
return result;
|
||||
}
|
||||
|
||||
export function arrayToKeyValue(attributes: Record<string, string[]> = {}) {
|
||||
export function arrayToKeyValue<T>(attributes: Record<string, string[]> = {}) {
|
||||
const result = Object.entries(attributes).flatMap(([key, value]) =>
|
||||
value.map<KeyValueType>((value) => ({ key, value }))
|
||||
);
|
||||
|
||||
return result.concat({ key: "", value: "" });
|
||||
return result.concat({ key: "", value: "" }) as PathValue<T, Path<T>>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
import {
|
||||
Button,
|
||||
ButtonVariant,
|
||||
InputGroup,
|
||||
TextInput,
|
||||
TextInputProps,
|
||||
} from "@patternfly/react-core";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function stringToMultiline(value?: string): string[] {
|
||||
return value ? value.split("##") : [];
|
||||
}
|
||||
|
||||
function toStringValue(formValue: string[]): string {
|
||||
return formValue.join("##");
|
||||
}
|
||||
|
||||
type IdValue = {
|
||||
id: number;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const generateId = () => Math.floor(Math.random() * 1000);
|
||||
|
||||
export type MultiLineInputProps = Omit<TextInputProps, "form"> & {
|
||||
name: string;
|
||||
addButtonLabel?: string;
|
||||
isDisabled?: boolean;
|
||||
defaultValue?: string[];
|
||||
stringify?: boolean;
|
||||
};
|
||||
|
||||
export const MultiLineInput = ({
|
||||
name,
|
||||
addButtonLabel,
|
||||
isDisabled = false,
|
||||
defaultValue,
|
||||
stringify = false,
|
||||
...rest
|
||||
}: MultiLineInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, setValue, getValues } = useFormContext();
|
||||
|
||||
const [fields, setFields] = useState<IdValue[]>([]);
|
||||
|
||||
const remove = (index: number) => {
|
||||
update([...fields.slice(0, index), ...fields.slice(index + 1)]);
|
||||
};
|
||||
|
||||
const append = () => {
|
||||
update([...fields, { id: generateId(), value: "" }]);
|
||||
};
|
||||
|
||||
const updateValue = (index: number, value: string) => {
|
||||
update([
|
||||
...fields.slice(0, index),
|
||||
{ ...fields[index], value },
|
||||
...fields.slice(index + 1),
|
||||
]);
|
||||
};
|
||||
|
||||
const update = (values: IdValue[]) => {
|
||||
setFields(values);
|
||||
const fieldValue = values.flatMap((field) => field.value);
|
||||
setValue(name, stringify ? toStringValue(fieldValue) : fieldValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
register(name);
|
||||
let values = stringify
|
||||
? stringToMultiline(getValues(name))
|
||||
: getValues(name);
|
||||
|
||||
values =
|
||||
Array.isArray(values) && values.length !== 0
|
||||
? values
|
||||
: defaultValue || [""];
|
||||
|
||||
setFields(values.map((value: string) => ({ value, id: generateId() })));
|
||||
}, [register, getValues]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{fields.map(({ id, value }, index) => (
|
||||
<Fragment key={id}>
|
||||
<InputGroup>
|
||||
<TextInput
|
||||
data-testid={name + index}
|
||||
onChange={(value) => updateValue(index, value)}
|
||||
name={`${name}[${index}].value`}
|
||||
value={value}
|
||||
isDisabled={isDisabled}
|
||||
{...rest}
|
||||
/>
|
||||
<Button
|
||||
variant={ButtonVariant.link}
|
||||
onClick={() => remove(index)}
|
||||
tabIndex={-1}
|
||||
aria-label={t("common:remove")}
|
||||
isDisabled={fields.length === 1}
|
||||
>
|
||||
<MinusCircleIcon />
|
||||
</Button>
|
||||
</InputGroup>
|
||||
{index === fields.length - 1 && (
|
||||
<Button
|
||||
variant={ButtonVariant.link}
|
||||
onClick={append}
|
||||
tabIndex={-1}
|
||||
aria-label={t("common:add")}
|
||||
data-testid="addValue"
|
||||
isDisabled={!value}
|
||||
>
|
||||
<PlusCircleIcon /> {t(addButtonLabel || "common:add")}
|
||||
</Button>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -53,7 +53,7 @@ export const EventsTab = ({ realm }: EventsTabProps) => {
|
|||
|
||||
const setupForm = (eventConfig?: EventsConfigForm) => {
|
||||
setEvents(eventConfig);
|
||||
convertToFormValues(eventConfig, setValue);
|
||||
convertToFormValues(eventConfig || {}, setValue);
|
||||
};
|
||||
|
||||
const clear = async (type: EventsType) => {
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
import {
|
||||
arrayToKeyValue,
|
||||
keyValueToArray,
|
||||
KeyValueType,
|
||||
} from "../components/key-value-form/key-value-convert";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useUserProfile } from "../realm-settings/user-profile/UserProfileContext";
|
||||
|
@ -34,8 +35,9 @@ export const UserAttributes = ({ user: defaultUser }: UserAttributesProps) => {
|
|||
const { config } = useUserProfile();
|
||||
|
||||
const convertAttributes = () => {
|
||||
return arrayToKeyValue(user.attributes!).filter(
|
||||
(a) => !config?.attributes?.some((attribute) => attribute.name === a.key)
|
||||
return arrayToKeyValue<UserRepresentation>(user.attributes!).filter(
|
||||
(a: KeyValueType) =>
|
||||
!config?.attributes?.some((attribute) => attribute.name === a.key)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import { saveAs } from "file-saver";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import {
|
||||
FieldValues,
|
||||
Path,
|
||||
PathValue,
|
||||
UseFormSetValue,
|
||||
} from "react-hook-form-v7";
|
||||
import { flatten } from "flat";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import type { ProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation";
|
||||
import type { IFormatter, IFormatterValueType } from "@patternfly/react-table";
|
||||
import { saveAs } from "file-saver";
|
||||
import { flatten } from "flat";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
|
||||
import {
|
||||
arrayToKeyValue,
|
||||
|
@ -82,24 +89,27 @@ const isAttributeArray = (value: any) => {
|
|||
|
||||
const isEmpty = (obj: any) => Object.keys(obj).length === 0;
|
||||
|
||||
export const convertAttributeNameToForm = <T extends string>(name: T) => {
|
||||
export function convertAttributeNameToForm<T>(
|
||||
name: string
|
||||
): PathValue<T, Path<T>> {
|
||||
const index = name.indexOf(".");
|
||||
return `${name.substring(0, index)}.${beerify(
|
||||
name.substring(index + 1)
|
||||
)}` as ReplaceString<T, ".", "🍺", { skipFirst: true }>;
|
||||
};
|
||||
)}` as PathValue<T, Path<T>>;
|
||||
}
|
||||
|
||||
const beerify = <T extends string>(name: T) =>
|
||||
export const beerify = <T extends string>(name: T) =>
|
||||
name.replaceAll(".", "🍺") as ReplaceString<T, ".", "🍺">;
|
||||
|
||||
const debeerify = <T extends string>(name: T) =>
|
||||
name.replaceAll("🍺", ".") as ReplaceString<T, "🍺", ".">;
|
||||
|
||||
export const convertToFormValues = (
|
||||
obj: any,
|
||||
setValue: (name: string, value: any) => void
|
||||
) => {
|
||||
Object.entries(obj).map(([key, value]) => {
|
||||
export function convertToFormValues<T extends FieldValues>(
|
||||
obj: FieldValues,
|
||||
setValue: UseFormSetValue<T>
|
||||
) {
|
||||
Object.entries(obj).map((entry) => {
|
||||
const [key, value] = entry as [Path<T>, any];
|
||||
if (key === "attributes" && isAttributesObject(value)) {
|
||||
setValue(key, arrayToKeyValue(value as Record<string, string[]>));
|
||||
} else if (key === "config" || key === "attributes") {
|
||||
|
@ -110,16 +120,16 @@ export const convertToFormValues = (
|
|||
);
|
||||
|
||||
convertedValues.forEach(([k, v]) =>
|
||||
setValue(`${key}.${beerify(k)}`, v)
|
||||
setValue(`${key}.${beerify(k)}` as Path<T>, v)
|
||||
);
|
||||
} else {
|
||||
setValue(key, undefined);
|
||||
setValue(key, undefined as PathValue<T, Path<T>>);
|
||||
}
|
||||
} else {
|
||||
setValue(key, value);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function convertFormValuesToObject<T extends Record<string, any>, G = T>(
|
||||
obj: T
|
||||
|
|
Loading…
Reference in a new issue