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="/clients" component={ClientsSection}></Route>
<Route <Route
exact exact
path="/client-settings" path="/client-settings/:id"
component={ClientSettings} component={ClientSettings}
></Route> ></Route>
<Route exact path="/add-client" component={NewClientForm}></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 { realm } = useContext(RealmContext);
const [add, Alerts] = useAlerts(); const [add, Alerts] = useAlerts();
const convertClientId = (clientId: string) =>
clientId.substring(0, clientId.indexOf("#"));
const enabled = (): IFormatter => (data?: IFormatterValueType) => { const enabled = (): IFormatter => (data?: IFormatterValueType) => {
const field = data!.toString(); const field = data!.toString();
const value = convertClientId(field); const [id, clientId, disabled] = field.split("#");
return field.indexOf("true") !== -1 ? ( return (
<Link to="client-settings">{value}</Link> <Link to={`client-settings/${id}`}>
) : ( {clientId}
<Link to="client-settings"> {disabled !== "true" && <Badge isRead>Disabled</Badge>}
{value} <Badge isRead>Disabled</Badge>
</Link> </Link>
); );
}; };
@ -75,13 +72,13 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
}; };
const data = clients! const data = clients!
.map((r) => { .map((client) => {
r.clientId = r.clientId + "#" + r.enabled; client.clientId = `${client.id}#${client.clientId}#${client.enabled}`;
r.baseUrl = replaceBaseUrl(r); client.baseUrl = replaceBaseUrl(client);
return r; return client;
}) })
.map((c) => { .map((column) => {
return { cells: columns.map((col) => c[col]), client: c }; return { cells: columns.map((col) => column[col]), client: column };
}); });
return ( return (
<> <>
@ -103,7 +100,8 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
title: t("common:export"), title: t("common:export"),
onClick: (_, rowId) => { onClick: (_, rowId) => {
const clientCopy = JSON.parse(JSON.stringify(data[rowId].client)); 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; delete clientCopy.id;
if (clientCopy.protocolMappers) { 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 { useTranslation } from "react-i18next";
import { import {
FormGroup, FormGroup,
TextInput, TextInput,
Form, Form,
Dropdown,
DropdownToggle,
DropdownItem,
Switch, Switch,
TextArea, TextArea,
PageSection, PageSection,
ActionGroup,
Button,
AlertVariant,
} from "@patternfly/react-core"; } 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 { ScrollForm } from "../components/scroll-form/ScrollForm";
import { ClientDescription } from "./ClientDescription"; import { ClientDescription } from "./ClientDescription";
import { ClientRepresentation } from "./models/client-model";
import { CapabilityConfig } from "./add/CapabilityConfig"; 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 = { export const ClientSettings = () => {
client: ClientRepresentation;
};
export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const [client, setClient] = useState({ ...clientInit }); const httpClient = useContext(HttpClientContext)!;
const form = useForm(); const { realm } = useContext(RealmContext);
const onChange = ( const [addAlert, Alerts] = useAlerts();
value: string | boolean,
event: FormEvent<HTMLInputElement>
) => {
const target = event.target;
const name = (target as HTMLInputElement).name;
setClient({ const { id } = useParams<{ id: string }>();
...client, const form = useForm();
[name]: value, 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 ( return (
<PageSection> <PageSection>
<Alerts />
<ScrollForm <ScrollForm
sections={[ sections={[
t("capabilityConfig"), t("capabilityConfig"),
@ -48,9 +75,7 @@ export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
t("loginSettings"), t("loginSettings"),
]} ]}
> >
<Form isHorizontal> <CapabilityConfig form={form} />
<CapabilityConfig form={form} />
</Form>
<Form isHorizontal> <Form isHorizontal>
<ClientDescription form={form} /> <ClientDescription form={form} />
</Form> </Form>
@ -60,64 +85,55 @@ export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
type="text" type="text"
id="kc-root-url" id="kc-root-url"
name="rootUrl" name="rootUrl"
value={client.rootUrl} ref={form.register}
onChange={onChange}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("validRedirectUri")} fieldId="kc-redirect"> <FormGroup label={t("validRedirectUri")} fieldId="kc-redirect">
<TextInput <MultiLineInput form={form} name="redirectUris" />
type="text"
id="kc-redirect"
name="redirectUris"
onChange={onChange}
/>
</FormGroup> </FormGroup>
<FormGroup label={t("homeURL")} fieldId="kc-home-url"> <FormGroup label={t("homeURL")} fieldId="kc-home-url">
<TextInput <TextInput
type="text" type="text"
id="kc-home-url" id="kc-home-url"
name="baseUrl" name="baseUrl"
value={client.baseUrl} ref={form.register}
onChange={onChange}
/> />
</FormGroup> </FormGroup>
</Form> </Form>
<Form isHorizontal> <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"> <FormGroup label={t("consentRequired")} fieldId="kc-consent">
<Switch <Controller
id="kc-consent"
name="consentRequired" name="consentRequired"
label={t("common:on")} defaultValue={false}
labelOff={t("common:off")} control={form.control}
isChecked={client.consentRequired} render={({ onChange, value }) => (
onChange={onChange} <Switch
id="kc-consent"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("displayOnClient")} label={t("displayOnClient")}
fieldId="kc-display-on-client" fieldId="kc-display-on-client"
> >
<Switch <Controller
id="kc-display-on-client"
name="alwaysDisplayInConsole" name="alwaysDisplayInConsole"
label={t("common:on")} defaultValue={false}
labelOff={t("common:off")} control={form.control}
isChecked={client.alwaysDisplayInConsole} render={({ onChange, value }) => (
onChange={onChange} <Switch
id="kc-display-on-client"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
@ -127,9 +143,15 @@ export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
<TextArea <TextArea
id="kc-consent-screen-text" id="kc-consent-screen-text"
name="consentText" name="consentText"
//value={client.protocolMappers![0].consentText} ref={form.register}
/> />
</FormGroup> </FormGroup>
<ActionGroup>
<Button variant="primary" onClick={() => save()}>
{t("common:save")}
</Button>
<Button variant="link">{t("common:cancel")}</Button>
</ActionGroup>
</Form> </Form>
</ScrollForm> </ScrollForm>
</PageSection> </PageSection>

View file

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

View file

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

View file

@ -14,7 +14,9 @@
"capabilityConfig": "Capability config", "capabilityConfig": "Capability config",
"clientsExplain": "Clients are applications and services that can request authentication of a user", "clientsExplain": "Clients are applications and services that can request authentication of a user",
"clientImportError": "Could not import client", "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", "clientDeletedSuccess": "The client has been deleted",
"clientDeleteError": "Could not delete client:", "clientDeleteError": "Could not delete client:",
"clientAuthentication": "Client authentication", "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 { useFieldArray, UseFormMethods } from "react-hook-form";
import { import {
TextInput, TextInput,
@ -9,6 +9,20 @@ import {
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { MinusIcon, PlusIcon } from "@patternfly/react-icons"; 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 = { export type MultiLineInputProps = {
form: UseFormMethods; form: UseFormMethods;
name: string; name: string;
@ -20,12 +34,18 @@ export const MultiLineInput = ({ name, form }: MultiLineInputProps) => {
name, name,
control, control,
}); });
useEffect(() => {
form.reset({
[name]: [{ value: "" }],
});
}, []);
return ( return (
<> <>
{fields.map(({ id, value }, index) => ( {fields.map(({ id, value }, index) => (
<Split key={id}> <Split key={id}>
<SplitItem> <SplitItem>
<TextInput <TextInput
id={id}
ref={register()} ref={register()}
name={`${name}[${index}].value`} name={`${name}[${index}].value`}
defaultValue={value} defaultValue={value}

View file

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