put shared logic into common component + fixes (#1415)

* put shared logic into common component + fixes

fixes: #1398

* removed conflicted name

* code review

* pr review comments

* update admin-client for better retrun types import
* use image file instead of encoded data:uri

* fix package lock

* is typechecked to be string
This commit is contained in:
Erik Jan de Wit 2021-11-01 08:49:23 +01:00 committed by GitHub
parent 79f8452ab5
commit 48a006e1fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 411 additions and 435 deletions

View file

@ -28,8 +28,8 @@ describe("Identity provider test", () => {
const keycloakServer = Cypress.env("KEYCLOAK_SERVER");
const discoveryUrl = `${keycloakServer}/auth/realms/master/.well-known/openid-configuration`;
const samlDiscoveryUrl = `${keycloakServer}/auth/realms/master/protocol/saml/descriptor`;
const authorizationUrl = `${keycloakServer}/auth/realms/master/protocol/openid-connect/auth`;
const ssoServiceUrl = `${keycloakServer}/auth/realms/sso`;
describe("Identity provider creation", () => {
const identityProviderName = "github";
@ -112,8 +112,8 @@ describe("Identity provider test", () => {
createProviderPage
.clickCreateDropdown()
.clickItem(samlProviderName)
.toggleEntityDescriptor()
.fillSsoServiceUrl(ssoServiceUrl)
.fillDiscoveryUrl(samlDiscoveryUrl)
.shouldBeSuccessful()
.clickAdd();
masthead.checkNotificationMessage(createSuccessMsg);
});

View file

@ -6,7 +6,6 @@ export default class CreateProviderPage {
private clientSecretField = "clientSecret";
private discoveryEndpoint = "discoveryEndpoint";
private authorizationUrl = "authorizationUrl";
private useEntityDescriptorSwitch = "useEntityDescriptor";
private addButton = "createProvider";
private ssoServiceUrl = "sso-service-url";
@ -96,9 +95,4 @@ export default class CreateProviderPage {
cy.findByTestId(this.authorizationUrl).should("have.value", value);
return this;
}
toggleEntityDescriptor() {
cy.findByTestId(this.useEntityDescriptorSwitch).click({ force: true });
return this;
}
}

2
package-lock.json generated
View file

@ -7,7 +7,7 @@
"name": "keycloak-admin-ui",
"license": "Apache",
"dependencies": {
"@keycloak/keycloak-admin-client": "^16.0.0-dev.34",
"@keycloak/keycloak-admin-client": "^16.0.0-dev.37",
"@patternfly/patternfly": "^4.144.5",
"@patternfly/react-code-editor": "^4.3.85",
"@patternfly/react-core": "^4.162.3",

View file

@ -23,7 +23,7 @@
"prepare": "husky install"
},
"dependencies": {
"@keycloak/keycloak-admin-client": "^16.0.0-dev.34",
"@keycloak/keycloak-admin-client": "^16.0.0-dev.37",
"@patternfly/patternfly": "^4.144.5",
"@patternfly/react-code-editor": "^4.3.85",
"@patternfly/react-core": "^4.162.3",

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><circle cx="50" cy="50" fill="none" stroke="#0066cc" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138"><animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform></circle></svg>

After

Width:  |  Height:  |  Size: 438 B

View file

@ -0,0 +1,159 @@
import React, { useState } from "react";
import {
FormGroup,
FileUpload,
Modal,
ModalVariant,
Button,
FileUploadProps,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { CodeEditor, Language } from "@patternfly/react-code-editor";
type FileUploadType = {
value: string;
filename: string;
isLoading: boolean;
modal: boolean;
};
export type FileUploadEvent =
| React.DragEvent<HTMLElement> // User dragged/dropped a file
| React.ChangeEvent<HTMLTextAreaElement> // User typed in the TextArea
| React.MouseEvent<HTMLButtonElement, MouseEvent>; // User clicked Clear button
export type FileUploadFormProps = Omit<FileUploadProps, "onChange"> & {
id: string;
extension: string;
onChange: (value: string) => void;
helpText?: string;
unWrap?: boolean;
language?: Language;
};
export const FileUploadForm = ({
id,
onChange,
helpText = "common-help:helpFileUpload",
unWrap = false,
language,
extension,
...rest
}: FileUploadFormProps) => {
const { t } = useTranslation();
const defaultUpload: FileUploadType = {
value: "",
filename: "",
isLoading: false,
modal: false,
};
const [fileUpload, setFileUpload] = useState<FileUploadType>(defaultUpload);
const removeDialog = () => setFileUpload({ ...fileUpload, modal: false });
const handleChange = (
value: string | File,
filename: string,
event:
| React.DragEvent<HTMLElement>
| React.ChangeEvent<HTMLTextAreaElement>
| React.MouseEvent<HTMLButtonElement, MouseEvent>
): void => {
if (
event.nativeEvent instanceof MouseEvent &&
!(event.nativeEvent instanceof DragEvent)
) {
setFileUpload({ ...fileUpload, modal: true });
} else {
setFileUpload({
...fileUpload,
value: value.toString(),
filename,
});
onChange(value.toString());
}
};
return (
<>
{fileUpload.modal && (
<Modal
variant={ModalVariant.small}
title={t("clearFile")}
isOpen
onClose={removeDialog}
actions={[
<Button
key="confirm"
variant="primary"
onClick={() => {
setFileUpload(defaultUpload);
onChange("");
}}
>
{t("clear")}
</Button>,
<Button key="cancel" variant="link" onClick={removeDialog}>
{t("cancel")}
</Button>,
]}
>
{t("clearFileExplain")}
</Modal>
)}
{unWrap && (
<FileUpload
id={id}
{...rest}
type="text"
value={fileUpload.value}
filename={fileUpload.filename}
onChange={handleChange}
onReadStarted={() =>
setFileUpload({ ...fileUpload, isLoading: true })
}
onReadFinished={() =>
setFileUpload({ ...fileUpload, isLoading: false })
}
isLoading={fileUpload.isLoading}
dropzoneProps={{
accept: extension,
}}
/>
)}
{!unWrap && (
<FormGroup
label={t("resourceFile")}
fieldId={id}
helperText={t(helpText)}
>
<FileUpload
id={id}
{...rest}
type="text"
value={fileUpload.value}
filename={fileUpload.filename}
onChange={handleChange}
onReadStarted={() =>
setFileUpload({ ...fileUpload, isLoading: true })
}
onReadFinished={() =>
setFileUpload({ ...fileUpload, isLoading: false })
}
isLoading={fileUpload.isLoading}
hideDefaultPreview
>
<CodeEditor
isLineNumbersVisible
code={fileUpload.value}
language={language}
height="128px"
onChange={(value, event) =>
handleChange(value ?? "", fileUpload.filename, event)
}
/>
</FileUpload>
</FormGroup>
)}
</>
);
};

View file

@ -1,167 +1,31 @@
import React, { useState } from "react";
import {
FormGroup,
FileUpload,
Modal,
ModalVariant,
Button,
FileUploadProps,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { CodeEditor, Language } from "@patternfly/react-code-editor";
import React from "react";
import { Language } from "@patternfly/react-code-editor";
type FileUpload = {
value: string;
filename: string;
isLoading: boolean;
modal: boolean;
};
import { FileUploadForm, FileUploadFormProps } from "./FileUploadForm";
export type JsonFileUploadEvent =
| React.DragEvent<HTMLElement> // User dragged/dropped a file
| React.ChangeEvent<HTMLTextAreaElement> // User typed in the TextArea
| React.MouseEvent<HTMLButtonElement, MouseEvent>; // User clicked Clear button
export type JsonFileUploadProps = Omit<FileUploadProps, "onChange"> & {
id: string;
export type JsonFileUploadProps = Omit<
FileUploadFormProps,
"onChange" | "language" | "extension"
> & {
onChange: (obj: object) => void;
helpText?: string;
unWrap?: boolean;
};
export const JsonFileUpload = ({
id,
onChange,
helpText = "common-help:helpFileUpload",
unWrap = false,
...rest
}: JsonFileUploadProps) => {
const { t } = useTranslation();
const defaultUpload = {
value: "",
filename: "",
isLoading: false,
modal: false,
};
const [fileUpload, setFileUpload] = useState<FileUpload>(defaultUpload);
const removeDialog = () => setFileUpload({ ...fileUpload, modal: false });
const handleChange = (
value: string | File,
filename: string,
event:
| React.DragEvent<HTMLElement>
| React.ChangeEvent<HTMLTextAreaElement>
| React.MouseEvent<HTMLButtonElement, MouseEvent>
): void => {
if (
event.nativeEvent instanceof MouseEvent &&
!(event.nativeEvent instanceof DragEvent)
) {
setFileUpload({ ...fileUpload, modal: true });
} else {
setFileUpload({
...fileUpload,
value: value as string,
filename,
});
if (value) {
let obj = {};
try {
obj = JSON.parse(value as string);
} catch (error) {
console.warn("Invalid json, ignoring value using {}");
}
onChange(obj);
}
export const JsonFileUpload = ({ onChange, ...props }: JsonFileUploadProps) => {
const handleChange = (value: string) => {
try {
onChange(JSON.parse(value));
} catch (error) {
onChange({});
console.warn("Invalid json, ignoring value using {}");
}
};
return (
<>
{fileUpload.modal && (
<Modal
variant={ModalVariant.small}
title={t("clearFile")}
isOpen
onClose={removeDialog}
actions={[
<Button
key="confirm"
variant="primary"
onClick={() => {
setFileUpload(defaultUpload);
onChange({});
}}
>
{t("clear")}
</Button>,
<Button key="cancel" variant="link" onClick={removeDialog}>
{t("cancel")}
</Button>,
]}
>
{t("clearFileExplain")}
</Modal>
)}
{unWrap && (
<FileUpload
id={id}
{...rest}
type="text"
value={fileUpload.value}
filename={fileUpload.filename}
onChange={handleChange}
onReadStarted={() =>
setFileUpload({ ...fileUpload, isLoading: true })
}
onReadFinished={() =>
setFileUpload({ ...fileUpload, isLoading: false })
}
isLoading={fileUpload.isLoading}
dropzoneProps={{
accept: ".json",
}}
/>
)}
{!unWrap && (
<FormGroup
label={t("resourceFile")}
fieldId={id}
helperText={t(helpText)}
>
<FileUpload
id={id}
{...rest}
type="text"
value={fileUpload.value}
filename={fileUpload.filename}
onChange={handleChange}
onReadStarted={() =>
setFileUpload({ ...fileUpload, isLoading: true })
}
onReadFinished={() =>
setFileUpload({ ...fileUpload, isLoading: false })
}
isLoading={fileUpload.isLoading}
dropzoneProps={{
accept: ".json",
}}
hideDefaultPreview
>
<CodeEditor
isLineNumbersVisible
code={fileUpload.value}
language={Language.json}
height="128px"
onChange={(value, event) =>
handleChange(value ?? "", fileUpload.filename, event)
}
/>
</FileUpload>
</FormGroup>
)}
</>
<FileUploadForm
{...props}
language={Language.json}
extension=".json"
onChange={handleChange}
/>
);
};

View file

@ -18,12 +18,16 @@ import { SamlConnectSettings } from "./SamlConnectSettings";
import { useRealm } from "../../context/realm-context/RealmContext";
import { useAlerts } from "../../components/alert/Alerts";
type DiscoveryIdentityProvider = IdentityProviderRepresentation & {
discoveryEndpoint?: string;
};
export default function AddSamlConnect() {
const { t } = useTranslation("identity-providers");
const history = useHistory();
const id = "saml";
const form = useForm<IdentityProviderRepresentation>({
const form = useForm<DiscoveryIdentityProvider>({
defaultValues: { alias: id },
});
const {
@ -35,7 +39,8 @@ export default function AddSamlConnect() {
const { addAlert } = useAlerts();
const { realm } = useRealm();
const save = async (provider: IdentityProviderRepresentation) => {
const save = async (provider: DiscoveryIdentityProvider) => {
delete provider.discoveryEndpoint;
try {
await adminClient.identityProviders.create({
...provider,

View file

@ -1,19 +1,15 @@
import React, { useEffect, useState } from "react";
import React from "react";
import { useFormContext } from "react-hook-form";
import { FormGroup, Switch, TextInput, Title } from "@patternfly/react-core";
import { FormGroup, Title } from "@patternfly/react-core";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../context/auth/AdminClient";
import type { OIDCConfigurationRepresentation } from "../OIDCConfigurationRepresentation";
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
import { useRealm } from "../../context/realm-context/RealmContext";
import { DiscoverySettings } from "./DiscoverySettings";
import { getBaseUrl } from "../../util";
type Result = OIDCConfigurationRepresentation & {
error: string;
};
import { DiscoveryEndpointField } from "../component/DiscoveryEndpointField";
export const OpenIdConnectSettings = () => {
const { t } = useTranslation("identity-providers");
@ -21,40 +17,13 @@ export const OpenIdConnectSettings = () => {
const adminClient = useAdminClient();
const { realm } = useRealm();
const { setValue, register, errors } = useFormContext();
const [discovery, setDiscovery] = useState(true);
const [discoveryUrl, setDiscoveryUrl] = useState("");
const [discovering, setDiscovering] = useState(false);
const [discoveryResult, setDiscoveryResult] = useState<Result>();
const { setValue, errors, setError } = useFormContext();
const setupForm = (result: any) => {
Object.keys(result).map((k) => setValue(`config.${k}`, result[k]));
};
useEffect(() => {
if (discovering) {
setDiscovering(!!discoveryUrl);
if (discoveryUrl)
(async () => {
let result;
try {
result = await adminClient.identityProviders.importFromUrl({
providerId: id,
fromUrl: discoveryUrl,
});
} catch (error) {
result = { error };
}
setDiscoveryResult(result as Result);
setupForm(result);
setDiscovering(false);
})();
}
}, [discovering]);
const fileUpload = async (obj: object) => {
const fileUpload = async (obj?: object) => {
if (obj) {
const formData = new FormData();
formData.append("providerId", id);
@ -75,8 +44,11 @@ export const OpenIdConnectSettings = () => {
);
const result = await response.json();
setupForm(result);
} catch (error: any) {
setDiscoveryResult({ error });
} catch (error) {
setError("discoveryError", {
type: "manual",
message: (error as Error).message,
});
}
}
};
@ -86,103 +58,36 @@ export const OpenIdConnectSettings = () => {
<Title headingLevel="h4" size="xl" className="kc-form-panel__title">
{t("oidcSettings")}
</Title>
<FormGroup
label={t("useDiscoveryEndpoint")}
fieldId="kc-discovery-endpoint-switch"
labelIcon={
<HelpItem
helpText="identity-providers-help:useDiscoveryEndpoint"
forLabel={t("useDiscoveryEndpoint")}
forID="kc-discovery-endpoint-switch"
/>
<DiscoveryEndpointField
id="oidc"
fileUpload={
<FormGroup
label={t("importConfig")}
fieldId="kc-import-config"
labelIcon={
<HelpItem
helpText="identity-providers-help:importConfig"
forLabel={t("importConfig")}
forID="kc-import-config"
/>
}
validated={errors.discoveryError ? "error" : "default"}
helperTextInvalid={errors.discoveryError}
>
<JsonFileUpload
id="kc-import-config"
helpText="identity=providers-help:jsonFileUpload"
hideDefaultPreview
unWrap
validated={errors.discoveryError ? "error" : "default"}
onChange={(value) => fileUpload(value)}
/>
</FormGroup>
}
>
<Switch
id="kc-discovery-endpoint-switch"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={discovery}
onChange={setDiscovery}
/>
</FormGroup>
{discovery && (
<FormGroup
label={t("discoveryEndpoint")}
fieldId="kc-discovery-endpoint"
labelIcon={
<HelpItem
helpText="identity-providers-help:discoveryEndpoint"
forLabel={t("discoveryEndpoint")}
forID="kc-discovery-endpoint"
/>
}
validated={
discoveryResult?.error || errors.discoveryEndpoint
? "error"
: !discoveryResult
? "default"
: "success"
}
helperTextInvalid={
errors.discoveryEndpoint
? t("common:required")
: t("noValidMetaDataFound")
}
isRequired
>
<TextInput
type="text"
name="discoveryEndpoint"
data-testid="discoveryEndpoint"
id="kc-discovery-endpoint"
placeholder="https://hostname/auth/realms/master/.well-known/openid-configuration"
value={discoveryUrl}
onChange={setDiscoveryUrl}
onBlur={() => setDiscovering(!discovering)}
validated={
discoveryResult?.error || errors.discoveryEndpoint
? "error"
: !discoveryResult
? "default"
: "success"
}
customIconUrl={
discovering
? 'data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"%3E%3Ccircle cx="50" cy="50" fill="none" stroke="%230066cc" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138"%3E%3CanimateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"%3E%3C/animateTransform%3E%3C/circle%3E%3C/svg%3E'
: ""
}
ref={register({ required: true })}
/>
</FormGroup>
)}
{!discovery && (
<FormGroup
label={t("importConfig")}
fieldId="kc-import-config"
labelIcon={
<HelpItem
helpText="identity-providers-help:importConfig"
forLabel={t("importConfig")}
forID="kc-import-config"
/>
}
validated={discoveryResult?.error ? "error" : "default"}
helperTextInvalid={discoveryResult?.error?.toString()}
>
<JsonFileUpload
id="kc-import-config"
helpText="identity=providers-help:jsonFileUpload"
hideDefaultPreview
unWrap
validated={discoveryResult?.error ? "error" : "default"}
onChange={(value) => fileUpload(value)}
/>
</FormGroup>
)}
{discovery && discoveryResult && !discoveryResult.error && (
<DiscoverySettings readOnly={true} />
)}
{!discovery && <DiscoverySettings readOnly={false} />}
{(readonly) => <DiscoverySettings readOnly={readonly} />}
</DiscoveryEndpointField>
</>
);
};

View file

@ -1,26 +1,17 @@
import React, { useEffect, useState } from "react";
import React from "react";
import { useFormContext } from "react-hook-form";
import {
FormGroup,
Switch,
TextInput,
Title,
ValidatedOptions,
} from "@patternfly/react-core";
import { FormGroup, TextInput, Title } from "@patternfly/react-core";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../context/auth/AdminClient";
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
import { FileUploadForm } from "../../components/json-file-upload/FileUploadForm";
import { useRealm } from "../../context/realm-context/RealmContext";
import { DescriptorSettings } from "./DescriptorSettings";
import { getBaseUrl } from "../../util";
type Result = IdentityProviderRepresentation & {
error: string;
};
import { DiscoveryEndpointField } from "../component/DiscoveryEndpointField";
export const SamlConnectSettings = () => {
const { t } = useTranslation("identity-providers");
@ -28,16 +19,7 @@ export const SamlConnectSettings = () => {
const adminClient = useAdminClient();
const { realm } = useRealm();
const { setValue, register, errors } = useFormContext();
const [descriptor, setDescriptor] = useState(true);
const [entityUrl, setEntityUrl] = useState("");
const [descriptorUrl, setDescriptorUrl] = useState("");
const [discovering, setDiscovering] = useState(false);
const [discoveryResult, setDiscoveryResult] = useState<Result>();
const defaultEntityUrl = `${getBaseUrl(adminClient)}realms/${realm}`;
const { setValue, register, errors, setError } = useFormContext();
const setupForm = (result: IdentityProviderRepresentation) => {
Object.entries(result).map(([key, value]) =>
@ -45,38 +27,10 @@ export const SamlConnectSettings = () => {
);
};
useEffect(() => {
if (!discovering) {
return;
}
setDiscovering(!!entityUrl);
if (!entityUrl) {
return;
}
(async () => {
let result;
try {
result = await adminClient.identityProviders.importFromUrl({
providerId: id,
fromUrl: entityUrl,
});
} catch (error) {
result = { error };
}
setDiscoveryResult(result as Result);
setupForm(result);
setDiscovering(false);
})();
}, [discovering]);
const fileUpload = async (obj: object) => {
const fileUpload = async (xml: string) => {
const formData = new FormData();
formData.append("providerId", id);
formData.append("file", new Blob([JSON.stringify(obj)]));
formData.append("file", new Blob([xml]));
try {
const response = await fetch(
@ -93,8 +47,11 @@ export const SamlConnectSettings = () => {
);
const result = await response.json();
setupForm(result);
} catch (error: any) {
setDiscoveryResult({ error });
} catch (error) {
setError("discoveryError", {
type: "manual",
message: (error as Error).message,
});
}
};
@ -120,90 +77,39 @@ export const SamlConnectSettings = () => {
name="config.entityId"
data-testid="serviceProviderEntityId"
id="kc-service-provider-entity-id"
value={entityUrl || defaultEntityUrl}
onChange={setEntityUrl}
ref={register()}
/>
</FormGroup>
<FormGroup
label={t("useEntityDescriptor")}
fieldId="kc-use-entity-descriptor"
labelIcon={
<HelpItem
helpText="identity-providers-help:useEntityDescriptor"
forLabel={t("useEntityDescriptor")}
forID="kc-use-entity-descriptor-switch"
/>
<DiscoveryEndpointField
id="saml"
fileUpload={
<FormGroup
label={t("importConfig")}
fieldId="kc-import-config"
labelIcon={
<HelpItem
helpText="identity-providers-help:importConfig"
forLabel={t("importConfig")}
forID="kc-import-config"
/>
}
validated={errors.discoveryError ? "error" : "default"}
helperTextInvalid={errors.discoveryError}
>
<FileUploadForm
id="kc-import-config"
extension=".xml"
hideDefaultPreview
unWrap
validated={errors.discoveryError ? "error" : "default"}
onChange={(value) => fileUpload(value)}
/>
</FormGroup>
}
>
<Switch
id="kc-use-entity-descriptor-switch"
label={t("common:on")}
data-testid="useEntityDescriptor"
labelOff={t("common:off")}
isChecked={descriptor}
onChange={setDescriptor}
/>
</FormGroup>
{descriptor && (
<FormGroup
label={t("samlEntityDescriptor")}
fieldId="kc-saml-entity-descriptor"
labelIcon={
<HelpItem
helpText="identity-providers-help:samlEntityDescriptor"
forLabel={t("samlEntityDescriptor")}
forID="kc-saml-entity-descriptor"
/>
}
>
<TextInput
type="text"
name="samlEntityDescriptor"
data-testid="samlEntityDescriptor"
id="kc-saml-entity-descriptor"
value={descriptorUrl}
onChange={setDescriptorUrl}
ref={register()}
validated={
errors.samlEntityDescriptor
? ValidatedOptions.error
: ValidatedOptions.default
}
/>
</FormGroup>
)}
{!descriptor && (
<FormGroup
label={t("importConfig")}
fieldId="kc-import-config"
labelIcon={
<HelpItem
helpText="identity-providers-help:importConfig"
forLabel={t("importConfig")}
forID="kc-import-config"
/>
}
validated={discoveryResult?.error ? "error" : "default"}
helperTextInvalid={discoveryResult?.error.toString()}
>
<JsonFileUpload
id="kc-import-config"
helpText="identity-providers-help:jsonFileUpload"
hideDefaultPreview
unWrap
validated={discoveryResult?.error ? "error" : "default"}
onChange={(value) => fileUpload(value)}
/>
</FormGroup>
)}
{descriptor && discoveryResult && !discoveryResult.error && (
<DescriptorSettings readOnly={true} />
)}
{!descriptor && <DescriptorSettings readOnly={false} />}
{(readonly) => <DescriptorSettings readOnly={readonly} />}
</DiscoveryEndpointField>
</>
);
};

View file

@ -0,0 +1,142 @@
import React, { ReactNode, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useFormContext } from "react-hook-form";
import { FormGroup, TextInput, Switch } from "@patternfly/react-core";
import environment from "../../environment";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useAdminClient } from "../../context/auth/AdminClient";
type DiscoveryEndpointFieldProps = {
id: string;
fileUpload: ReactNode;
children: (readOnly: boolean) => ReactNode;
};
export const DiscoveryEndpointField = ({
id,
fileUpload,
children,
}: DiscoveryEndpointFieldProps) => {
const { t } = useTranslation("identity-providers");
const adminClient = useAdminClient();
const { setValue, register, errors, setError, watch, clearErrors } =
useFormContext();
const discoveryUrl = watch("discoveryEndpoint");
const [discovery, setDiscovery] = useState(true);
const [discovering, setDiscovering] = useState(false);
const [discoveryResult, setDiscoveryResult] =
useState<Record<string, string>>();
const setupForm = (result: Record<string, string>) => {
Object.keys(result).map((k) => setValue(`config.${k}`, result[k]));
};
useEffect(() => {
if (!discoveryUrl) {
setDiscovering(false);
return;
}
(async () => {
clearErrors("discoveryError");
try {
const result = await adminClient.identityProviders.importFromUrl({
providerId: id,
fromUrl: discoveryUrl,
});
setupForm(result);
setDiscoveryResult(result);
} catch (error) {
setError("discoveryError", {
type: "manual",
message: (error as Error).message,
});
}
setDiscovering(false);
})();
}, [discovering]);
return (
<>
<FormGroup
label={t("useDiscoveryEndpoint")}
fieldId="kc-discovery-endpoint-switch"
labelIcon={
<HelpItem
helpText="identity-providers-help:useDiscoveryEndpoint"
forLabel={t("useDiscoveryEndpoint")}
forID="kc-discovery-endpoint-switch"
/>
}
>
<Switch
id="kc-discovery-endpoint-switch"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={discovery}
onChange={setDiscovery}
/>
</FormGroup>
{discovery && (
<FormGroup
label={t("discoveryEndpoint")}
fieldId="kc-discovery-endpoint"
labelIcon={
<HelpItem
helpText="identity-providers-help:discoveryEndpoint"
forLabel={t("discoveryEndpoint")}
forID="kc-discovery-endpoint"
/>
}
validated={
errors.discoveryError || errors.discoveryEndpoint
? "error"
: !discoveryResult
? "default"
: "success"
}
helperTextInvalid={
errors.discoveryEndpoint
? t("common:required")
: t("noValidMetaDataFound")
}
isRequired
>
<TextInput
type="text"
name="discoveryEndpoint"
data-testid="discoveryEndpoint"
id="kc-discovery-endpoint"
placeholder={
id === "oidc"
? "https://hostname/auth/realms/master/.well-known/openid-configuration"
: "https://hostname/context/saml/discovery"
}
onBlur={() => setDiscovering(true)}
validated={
errors.discoveryError || errors.discoveryEndpoint
? "error"
: !discoveryResult
? "default"
: "success"
}
customIconUrl={
discovering
? environment.resourceUrl + "./discovery-load-indicator.svg"
: ""
}
ref={register({ required: true })}
/>
</FormGroup>
)}
{!discovery && fileUpload}
{discovery && !errors.discoveryError && children(true)}
{!discovery && children(false)}
</>
);
};