fetch and save client (#116)

* fetch and save client

* added multi line for redirect uris

* format

* review
This commit is contained in:
Erik Jan de Wit 2020-09-25 19:42:32 +02:00 committed by GitHub
parent 19b458577b
commit 94b26936d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 148 additions and 103 deletions

View file

@ -38,7 +38,7 @@ export const App = () => {
<Route exact path="/clients" component={ClientsSection}></Route>
<Route
exact
path="/client-settings"
path="/client-settings/:id"
component={ClientSettings}
></Route>
<Route exact path="/add-client" component={NewClientForm}></Route>

View file

@ -36,16 +36,13 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
const { realm } = useContext(RealmContext);
const [add, Alerts] = useAlerts();
const convertClientId = (clientId: string) =>
clientId.substring(0, clientId.indexOf("#"));
const enabled = (): IFormatter => (data?: IFormatterValueType) => {
const field = data!.toString();
const value = convertClientId(field);
return field.indexOf("true") !== -1 ? (
<Link to="client-settings">{value}</Link>
) : (
<Link to="client-settings">
{value} <Badge isRead>Disabled</Badge>
const [id, clientId, disabled] = field.split("#");
return (
<Link to={`client-settings/${id}`}>
{clientId}
{disabled !== "true" && <Badge isRead>Disabled</Badge>}
</Link>
);
};
@ -75,13 +72,13 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
};
const data = clients!
.map((r) => {
r.clientId = r.clientId + "#" + r.enabled;
r.baseUrl = replaceBaseUrl(r);
return r;
.map((client) => {
client.clientId = `${client.id}#${client.clientId}#${client.enabled}`;
client.baseUrl = replaceBaseUrl(client);
return client;
})
.map((c) => {
return { cells: columns.map((col) => c[col]), client: c };
.map((column) => {
return { cells: columns.map((col) => column[col]), client: column };
});
return (
<>
@ -103,7 +100,8 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
title: t("common:export"),
onClick: (_, rowId) => {
const clientCopy = JSON.parse(JSON.stringify(data[rowId].client));
clientCopy.clientId = convertClientId(clientCopy.clientId);
const [, orgClientId] = clientCopy.clientId.split("#");
clientCopy.clientId = orgClientId;
delete clientCopy.id;
if (clientCopy.protocolMappers) {

View file

@ -1,45 +1,72 @@
import React, { useState, FormEvent } from "react";
import React, { useContext, useEffect } from "react";
import { useTranslation } from "react-i18next";
import {
FormGroup,
TextInput,
Form,
Dropdown,
DropdownToggle,
DropdownItem,
Switch,
TextArea,
PageSection,
ActionGroup,
Button,
AlertVariant,
} from "@patternfly/react-core";
import { useForm } from "react-hook-form";
import { useParams } from "react-router-dom";
import { Controller, useForm } from "react-hook-form";
import { ScrollForm } from "../components/scroll-form/ScrollForm";
import { ClientDescription } from "./ClientDescription";
import { ClientRepresentation } from "./models/client-model";
import { CapabilityConfig } from "./add/CapabilityConfig";
import { RealmContext } from "../components/realm-context/RealmContext";
import { HttpClientContext } from "../http-service/HttpClientContext";
import { ClientRepresentation } from "../realm/models/Realm";
import {
convertToMultiline,
MultiLineInput,
toValue,
} from "../components/multi-line-input/MultiLineInput";
import { useAlerts } from "../components/alert/Alerts";
type ClientSettingsProps = {
client: ClientRepresentation;
};
export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
export const ClientSettings = () => {
const { t } = useTranslation("clients");
const [client, setClient] = useState({ ...clientInit });
const form = useForm();
const onChange = (
value: string | boolean,
event: FormEvent<HTMLInputElement>
) => {
const target = event.target;
const name = (target as HTMLInputElement).name;
const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext);
const [addAlert, Alerts] = useAlerts();
setClient({
...client,
[name]: value,
});
const { id } = useParams<{ id: string }>();
const form = useForm();
const url = `/admin/realms/${realm}/clients/${id}`;
useEffect(() => {
(async () => {
const fetchedClient = await httpClient.doGet<ClientRepresentation>(url);
if (fetchedClient.data) {
Object.entries(fetchedClient.data).map((entry) => {
if (entry[0] !== "redirectUris") {
form.setValue(entry[0], entry[1]);
} else if (entry[1] && entry[1].length > 0) {
form.setValue(entry[0], convertToMultiline(entry[1]));
}
});
}
})();
}, []);
const save = async () => {
if (await form.trigger()) {
const redirectUris = toValue(form.getValues()["redirectUris"]);
try {
httpClient.doPut(url, { ...form.getValues(), redirectUris });
addAlert(t("clientSaveSuccess"), AlertVariant.success);
} catch (error) {
addAlert(`${t("clientSaveError")} '${error}'`, AlertVariant.danger);
}
}
};
return (
<PageSection>
<Alerts />
<ScrollForm
sections={[
t("capabilityConfig"),
@ -48,9 +75,7 @@ export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
t("loginSettings"),
]}
>
<Form isHorizontal>
<CapabilityConfig form={form} />
</Form>
<CapabilityConfig form={form} />
<Form isHorizontal>
<ClientDescription form={form} />
</Form>
@ -60,64 +85,55 @@ export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
type="text"
id="kc-root-url"
name="rootUrl"
value={client.rootUrl}
onChange={onChange}
ref={form.register}
/>
</FormGroup>
<FormGroup label={t("validRedirectUri")} fieldId="kc-redirect">
<TextInput
type="text"
id="kc-redirect"
name="redirectUris"
onChange={onChange}
/>
<MultiLineInput form={form} name="redirectUris" />
</FormGroup>
<FormGroup label={t("homeURL")} fieldId="kc-home-url">
<TextInput
type="text"
id="kc-home-url"
name="baseUrl"
value={client.baseUrl}
onChange={onChange}
ref={form.register}
/>
</FormGroup>
</Form>
<Form isHorizontal>
<FormGroup label={t("loginTheme")} fieldId="kc-login-theme">
<Dropdown
id="kc-login-theme"
toggle={
<DropdownToggle id="toggle-id" onToggle={() => {}}>
{t("loginTheme")}
</DropdownToggle>
}
dropdownItems={[
<DropdownItem key="link">Link</DropdownItem>,
<DropdownItem key="action" component="button" />,
]}
/>
</FormGroup>
<FormGroup label={t("consentRequired")} fieldId="kc-consent">
<Switch
id="kc-consent"
<Controller
name="consentRequired"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={client.consentRequired}
onChange={onChange}
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-consent"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("displayOnClient")}
fieldId="kc-display-on-client"
>
<Switch
id="kc-display-on-client"
<Controller
name="alwaysDisplayInConsole"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={client.alwaysDisplayInConsole}
onChange={onChange}
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-display-on-client"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
@ -127,9 +143,15 @@ export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
<TextArea
id="kc-consent-screen-text"
name="consentText"
//value={client.protocolMappers![0].consentText}
ref={form.register}
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" onClick={() => save()}>
{t("common:save")}
</Button>
<Button variant="link">{t("common:cancel")}</Button>
</ActionGroup>
</Form>
</ScrollForm>
</PageSection>

View file

@ -71,7 +71,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/767756c2-21f8-431c-9f4b-edf30654d653"
>
account
</a>
@ -170,7 +170,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/337dc87b-e08d-409e-aaac-6ab7df4b925b"
>
account-console
</a>
@ -269,7 +269,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/60d59afe-7926-4c22-b829-798125793ef5"
>
admin-cli
</a>
@ -342,7 +342,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/c2d74093-2b8c-4ecb-870f-c7358ff48237"
>
broker
</a>
@ -415,7 +415,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/66135023-e667-4864-b1f3-f87e805fabc2"
>
master-realm
</a>
@ -486,7 +486,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/324f4182-d302-44f8-ac8a-149eaa29dc90"
>
new
</a>
@ -585,7 +585,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/fb45882b-4d85-4f40-920e-6a68298d36d0"
>
photoz-realm
</a>
@ -656,7 +656,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/9ed60e41-d794-4046-842f-3247bf32f5ce"
>
security-admin-console
</a>
@ -819,7 +819,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/767756c2-21f8-431c-9f4b-edf30654d653"
>
account
</a>
@ -918,7 +918,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/337dc87b-e08d-409e-aaac-6ab7df4b925b"
>
account-console
</a>
@ -1017,7 +1017,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/60d59afe-7926-4c22-b829-798125793ef5"
>
admin-cli
</a>
@ -1090,7 +1090,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/c2d74093-2b8c-4ecb-870f-c7358ff48237"
>
broker
</a>
@ -1163,7 +1163,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/66135023-e667-4864-b1f3-f87e805fabc2"
>
master-realm
</a>
@ -1234,7 +1234,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/324f4182-d302-44f8-ac8a-149eaa29dc90"
>
new
</a>
@ -1333,7 +1333,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/fb45882b-4d85-4f40-920e-6a68298d36d0"
>
photoz-realm
</a>
@ -1404,7 +1404,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/9ed60e41-d794-4046-842f-3247bf32f5ce"
>
security-admin-console
</a>

View file

@ -21,6 +21,7 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
<FormGroup label={t("clientAuthentication")} fieldId="kc-authentication">
<Controller
name="publicClient"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Switch
@ -37,6 +38,7 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
<FormGroup label={t("clientAuthorization")} fieldId="kc-authorization">
<Controller
name="authorizationServicesEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Switch
@ -55,6 +57,7 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
<GridItem lg={4} sm={6}>
<Controller
name="standardFlowEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Checkbox
@ -70,6 +73,7 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
<GridItem lg={8} sm={6}>
<Controller
name="directAccessGrantsEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Checkbox
@ -85,6 +89,7 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
<GridItem lg={4} sm={6}>
<Controller
name="implicitFlowEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Checkbox
@ -100,6 +105,7 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
<GridItem lg={8} sm={6}>
<Controller
name="serviceAccountsEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Checkbox

View file

@ -14,7 +14,9 @@
"capabilityConfig": "Capability config",
"clientsExplain": "Clients are applications and services that can request authentication of a user",
"clientImportError": "Could not import client",
"clientImportSuccess": "Client imported successful",
"clientSaveSuccess": "Client successfully updated",
"clientSaveError": "Client could not be updated:",
"clientImportSuccess": "Client imported successfully",
"clientDeletedSuccess": "The client has been deleted",
"clientDeleteError": "Could not delete client:",
"clientAuthentication": "Client authentication",

View file

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import { useFieldArray, UseFormMethods } from "react-hook-form";
import {
TextInput,
@ -9,6 +9,20 @@ import {
} from "@patternfly/react-core";
import { MinusIcon, PlusIcon } from "@patternfly/react-icons";
type MultiLine = {
value: string;
};
export function convertToMultiline(fields: string[]): MultiLine[] {
return fields.map((field) => {
return { value: field };
});
}
export function toValue(formValue: MultiLine[]): string[] {
return formValue.map((field) => field.value);
}
export type MultiLineInputProps = {
form: UseFormMethods;
name: string;
@ -20,12 +34,18 @@ export const MultiLineInput = ({ name, form }: MultiLineInputProps) => {
name,
control,
});
useEffect(() => {
form.reset({
[name]: [{ value: "" }],
});
}, []);
return (
<>
{fields.map(({ id, value }, index) => (
<Split key={id}>
<SplitItem>
<TextInput
id={id}
ref={register()}
name={`${name}[${index}].value`}
defaultValue={value}

View file

@ -7,6 +7,7 @@ import { Button } from "@patternfly/react-core";
import {
MultiLineInput,
MultiLineInputProps,
toValue,
} from "../components/multi-line-input/MultiLineInput";
export default {
@ -15,15 +16,11 @@ export default {
} as Meta;
const Template: Story<MultiLineInputProps> = (args) => {
const form = useForm({
defaultValues: {
items: [{ value: "" }],
},
});
const form = useForm();
return (
<form
onSubmit={form.handleSubmit((data) => {
action("submit")(data);
action("submit")(toValue(data.items));
})}
>
<MultiLineInput {...args} form={form} />