partially solves issue #422 (#442)

* partially solves issue #422

* fixed section

* added not before now set / clear alerts
This commit is contained in:
Erik Jan de Wit 2021-03-17 14:40:19 +01:00 committed by GitHub
parent a81164ee2a
commit 3b1d89fedb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 216 additions and 217 deletions

View file

@ -24,8 +24,7 @@ import { useRealm } from "./context/realm-context/RealmContext";
import { useAdminClient, asyncStateFetch } from "./context/auth/AdminClient"; import { useAdminClient, asyncStateFetch } from "./context/auth/AdminClient";
import { ErrorRenderer } from "./components/error/ErrorRenderer"; import { ErrorRenderer } from "./components/error/ErrorRenderer";
// This must match the id given as scrollableSelector in scroll-form export const mainPageContentId = "kc-main-content-page-container";
const mainPageContentId = "kc-main-content-page-container";
const AppContexts = ({ children }: { children: ReactNode }) => ( const AppContexts = ({ children }: { children: ReactNode }) => (
<AccessContextProvider> <AccessContextProvider>

View file

@ -8,8 +8,6 @@ import {
AlertVariant, AlertVariant,
Button, Button,
ButtonVariant, ButtonVariant,
Card,
CardBody,
ExpandableSection, ExpandableSection,
FormGroup, FormGroup,
Split, Split,
@ -37,9 +35,10 @@ import { AddHostDialog } from "./advanced/AddHostDialog";
import { FineGrainSamlEndpointConfig } from "./advanced/FineGrainSamlEndpointConfig"; import { FineGrainSamlEndpointConfig } from "./advanced/FineGrainSamlEndpointConfig";
import { AuthenticationOverrides } from "./advanced/AuthenticationOverrides"; import { AuthenticationOverrides } from "./advanced/AuthenticationOverrides";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { SaveOptions } from "./ClientDetails";
type AdvancedProps = { type AdvancedProps = {
save: () => void; save: (options?: SaveOptions) => void;
client: ClientRepresentation; client: ClientRepresentation;
}; };
@ -68,9 +67,9 @@ export const AdvancedTab = ({
const refresh = () => setKey(new Date().getTime()); const refresh = () => setKey(new Date().getTime());
const [nodes, setNodes] = useState(registeredNodes || {}); const [nodes, setNodes] = useState(registeredNodes || {});
const setNotBefore = (time: number) => { const setNotBefore = (time: number, messageKey: string) => {
setValue(revocationFieldName, time); setValue(revocationFieldName, time);
save(); save({ messageKey });
}; };
const parseResult = (result: GlobalRequestResult, prefixKey: string) => { const parseResult = (result: GlobalRequestResult, prefixKey: string) => {
@ -178,96 +177,95 @@ export const AdvancedTab = ({
return ( return (
<ScrollForm sections={sections}> <ScrollForm sections={sections}>
<Card> <>
<CardBody> <Text className="pf-u-py-lg">
<Text> <Trans i18nKey="clients-help:notBeforeIntro">
<Trans i18nKey="clients-help:notBeforeIntro"> In order to successfully push setup url on
In order to successfully push setup url on <Link to={`/${realm}/clients/${id}/settings`}>{t("settings")}</Link>
<Link to={`/${realm}/clients/${id}/settings`}> tab
{t("settings")} </Trans>
</Link> </Text>
tab <FormAccess role="manage-clients" isHorizontal>
</Trans> <FormGroup
</Text> label={t("notBefore")}
</CardBody> fieldId="kc-not-before"
<CardBody> labelIcon={
<FormAccess role="manage-clients" isHorizontal> <HelpItem
<FormGroup helpText="clients-help:notBefore"
label={t("notBefore")} forLabel={t("notBefore")}
fieldId="kc-not-before" forID="kc-not-before"
labelIcon={
<HelpItem
helpText="clients-help:notBefore"
forLabel={t("notBefore")}
forID="kc-not-before"
/>
}
>
<TextInput
type="text"
id="kc-not-before"
name="notBefore"
isReadOnly
value={formatDate()}
/> />
</FormGroup> }
<ActionGroup> >
<Button <TextInput
id="setToNow" type="text"
variant="tertiary" id="kc-not-before"
onClick={() => setNotBefore(moment.now() / 1000)} name="notBefore"
> isReadOnly
{t("setToNow")} value={formatDate()}
</Button> />
<Button </FormGroup>
id="clear" <ActionGroup>
variant="tertiary" <Button
onClick={() => setNotBefore(0)} id="setToNow"
> variant="tertiary"
{t("clear")} onClick={() => {
</Button> setNotBefore(moment.now() / 1000, "notBeforeSetToNow");
<Button id="push" variant="secondary" onClick={push}> }}
{t("push")}
</Button>
</ActionGroup>
</FormAccess>
</CardBody>
</Card>
<Card>
<CardBody>
<FormAccess role="manage-clients" isHorizontal>
<FormGroup
label={t("nodeReRegistrationTimeout")}
fieldId="kc-node-reregistration-timeout"
labelIcon={
<HelpItem
helpText="clients-help:nodeReRegistrationTimeout"
forLabel={t("nodeReRegistrationTimeout")}
forID="nodeReRegistrationTimeout"
/>
}
> >
<Split hasGutter> {t("setToNow")}
<SplitItem> </Button>
<Controller <Button
name="nodeReRegistrationTimeout" id="clear"
defaultValue="" variant="tertiary"
control={control} onClick={() => {
render={({ onChange, value }) => ( setNotBefore(0, "notBeforeNowClear");
<TimeSelector value={value} onChange={onChange} /> }}
)} >
/> {t("clear")}
</SplitItem> </Button>
<SplitItem> <Button id="push" variant="secondary" onClick={push}>
<Button variant={ButtonVariant.secondary} onClick={save}> {t("push")}
{t("common:save")} </Button>
</Button> </ActionGroup>
</SplitItem> </FormAccess>
</Split> </>
</FormGroup> <>
</FormAccess> <FormAccess role="manage-clients" isHorizontal>
</CardBody> <FormGroup
<CardBody> label={t("nodeReRegistrationTimeout")}
fieldId="kc-node-reregistration-timeout"
labelIcon={
<HelpItem
helpText="clients-help:nodeReRegistrationTimeout"
forLabel={t("nodeReRegistrationTimeout")}
forID="nodeReRegistrationTimeout"
/>
}
>
<Split hasGutter>
<SplitItem>
<Controller
name="nodeReRegistrationTimeout"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<TimeSelector value={value} onChange={onChange} />
)}
/>
</SplitItem>
<SplitItem>
<Button
variant={ButtonVariant.secondary}
onClick={() => save()}
>
{t("common:save")}
</Button>
</SplitItem>
</Split>
</FormGroup>
</FormAccess>
<>
<DeleteNodeConfirm /> <DeleteNodeConfirm />
<AddHostDialog <AddHostDialog
clientId={id!} clientId={id!}
@ -345,105 +343,90 @@ export const AdvancedTab = ({
]} ]}
/> />
</ExpandableSection> </ExpandableSection>
</CardBody> </>
</Card> </>
<Card> <>
{protocol === openIdConnect && ( {protocol === openIdConnect && (
<> <>
<CardBody> <Text className="pf-u-py-lg">
<Text> {t("clients-help:fineGrainOpenIdConnectConfiguration")}
{t("clients-help:fineGrainOpenIdConnectConfiguration")} </Text>
</Text> <FineGrainOpenIdConnect
</CardBody> control={control}
<CardBody> save={() => save()}
<FineGrainOpenIdConnect reset={() =>
control={control} convertToFormValues(attributes, "attributes", setValue)
save={save} }
reset={() => />
convertToFormValues(attributes, "attributes", setValue)
}
/>
</CardBody>
</> </>
)} )}
{protocol !== openIdConnect && ( {protocol !== openIdConnect && (
<> <>
<CardBody> <Text className="pf-u-py-lg">
<Text>{t("clients-help:fineGrainSamlEndpointConfig")}</Text> {t("clients-help:fineGrainSamlEndpointConfig")}
</CardBody> </Text>
<FineGrainSamlEndpointConfig
<CardBody>
<FineGrainSamlEndpointConfig
control={control}
save={save}
reset={() =>
convertToFormValues(attributes, "attributes", setValue)
}
/>
</CardBody>
</>
)}
</Card>
{protocol === openIdConnect && (
<Card>
<CardBody>
<Text>{t("clients-help:openIdConnectCompatibilityModes")}</Text>
</CardBody>
<CardBody>
<OpenIdConnectCompatibilityModes
control={control} control={control}
save={save} save={() => save()}
reset={() => reset={() =>
resetFields(["exclude-session-state-from-auth-response"]) convertToFormValues(attributes, "attributes", setValue)
} }
/> />
</CardBody> </>
</Card> )}
)} </>
<Card> {protocol === openIdConnect && (
<CardBody> <>
<Text> <Text className="pf-u-py-lg">
{t("clients-help:advancedSettings" + toUpperCase(protocol!))} {t("clients-help:openIdConnectCompatibilityModes")}
</Text> </Text>
</CardBody> <OpenIdConnectCompatibilityModes
<CardBody>
<AdvancedSettings
protocol={protocol}
control={control} control={control}
save={save} save={() => save()}
reset={() => { reset={() =>
resetFields([ resetFields(["exclude-session-state-from-auth-response"])
"saml-assertion-lifespan", }
"access-token-lifespan",
"tls-client-certificate-bound-access-tokens",
"pkce-code-challenge-method",
]);
}}
/> />
</CardBody> </>
</Card> )}
<Card> <>
<CardBody> <Text className="pf-u-py-lg">
<Text>{t("clients-help:authenticationOverrides")}</Text> {t("clients-help:advancedSettings" + toUpperCase(protocol!))}
</CardBody> </Text>
<CardBody> <AdvancedSettings
<AuthenticationOverrides protocol={protocol}
protocol={protocol} control={control}
control={control} save={() => save()}
save={save} reset={() => {
reset={() => { resetFields([
setValue( "saml-assertion-lifespan",
"authenticationFlowBindingOverrides.browser", "access-token-lifespan",
authenticationFlowBindingOverrides?.browser "tls-client-certificate-bound-access-tokens",
); "pkce-code-challenge-method",
setValue( ]);
"authenticationFlowBindingOverrides.direct_grant", }}
authenticationFlowBindingOverrides?.direct_grant />
); </>
}} <>
/> <Text className="pf-u-py-lg">
</CardBody> {t("clients-help:authenticationOverrides")}
</Card> </Text>
<AuthenticationOverrides
protocol={protocol}
control={control}
save={() => save()}
reset={() => {
setValue(
"authenticationFlowBindingOverrides.browser",
authenticationFlowBindingOverrides?.browser
);
setValue(
"authenticationFlowBindingOverrides.direct_grant",
authenticationFlowBindingOverrides?.direct_grant
);
}}
/>
</>
</ScrollForm> </ScrollForm>
); );
}; };

View file

@ -110,6 +110,11 @@ export type ClientForm = Omit<
webOrigins: MultiLine[]; webOrigins: MultiLine[];
}; };
export type SaveOptions = {
confirmed?: boolean;
messageKey?: string;
};
export const ClientDetails = () => { export const ClientDetails = () => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
@ -180,7 +185,12 @@ export const ClientDetails = () => {
); );
}, [clientId]); }, [clientId]);
const save = async (confirmed: boolean | undefined = false) => { const save = async (
{ confirmed = false, messageKey = "clientSaveSuccess" }: SaveOptions = {
confirmed: false,
messageKey: "clientSaveSuccess",
}
) => {
if (await form.trigger()) { if (await form.trigger()) {
if ( if (
client?.publicClient && client?.publicClient &&
@ -208,7 +218,7 @@ export const ClientDetails = () => {
await adminClient.clients.update({ id: clientId }, newClient); await adminClient.clients.update({ id: clientId }, newClient);
setupForm(newClient); setupForm(newClient);
setClient(newClient); setClient(newClient);
addAlert(t("clientSaveSuccess"), AlertVariant.success); addAlert(t(messageKey), AlertVariant.success);
} catch (error) { } catch (error) {
addAlert(`${t("clientSaveError")} '${error}'`, AlertVariant.danger); addAlert(`${t("clientSaveError")} '${error}'`, AlertVariant.danger);
} }
@ -231,7 +241,7 @@ export const ClientDetails = () => {
})} })}
open={changeAuthenticatorOpen} open={changeAuthenticatorOpen}
toggleDialog={toggleChangeAuthenticator} toggleDialog={toggleChangeAuthenticator}
onConfirm={() => save(true)} onConfirm={() => save({ confirmed: true })}
> >
<> <>
{t("changeAuthenticatorConfirm", { {t("changeAuthenticatorConfirm", {
@ -272,7 +282,7 @@ export const ClientDetails = () => {
eventKey="settings" eventKey="settings"
title={<TabTitleText>{t("common:settings")}</TabTitleText>} title={<TabTitleText>{t("common:settings")}</TabTitleText>}
> >
<ClientSettings save={save} /> <ClientSettings save={() => save()} />
</Tab> </Tab>
{publicClient && ( {publicClient && (
<Tab <Tab
@ -280,7 +290,7 @@ export const ClientDetails = () => {
eventKey="credentials" eventKey="credentials"
title={<TabTitleText>{t("credentials")}</TabTitleText>} title={<TabTitleText>{t("credentials")}</TabTitleText>}
> >
<Credentials clientId={clientId} save={save} /> <Credentials clientId={clientId} save={() => save()} />
</Tab> </Tab>
)} )}
<Tab <Tab

View file

@ -126,7 +126,7 @@ export const AdvancedSettings = ({
> >
<Controller <Controller
name="attributes.pkce-code-challenge-method" name="attributes.pkce-code-challenge-method"
defaultValue={false} defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
@ -138,10 +138,12 @@ export const AdvancedSettings = ({
onChange(value); onChange(value);
setOpen(false); setOpen(false);
}} }}
selections={[value]} selections={[value || t("common:choose")]}
> >
{["", "S256", "plain"].map((v) => ( {["", "S256", "plain"].map((v) => (
<SelectOption key={v} value={v} /> <SelectOption key={v} value={v}>
{v || t("common:choose")}
</SelectOption>
))} ))}
</Select> </Select>
)} )}

View file

@ -134,6 +134,8 @@
"notBefore": "Not before", "notBefore": "Not before",
"setToNow": "Set to now", "setToNow": "Set to now",
"noAdminUrlSet": "No push sent. No admin URI configured or no registered cluster nodes available", "noAdminUrlSet": "No push sent. No admin URI configured or no registered cluster nodes available",
"notBeforeSetToNow": "Not Before set for client",
"notBeforeNowClear": "Not Before cleared for client",
"notBeforePushFail": "Failed to push \"not before\" to: {{failedNodes}}", "notBeforePushFail": "Failed to push \"not before\" to: {{failedNodes}}",
"notBeforePushSuccess": "Successfully push \"not before\" to: {{successNodes}}", "notBeforePushSuccess": "Successfully push \"not before\" to: {{successNodes}}",
"testClusterFail": "Failed verified availability for: {{failedNodes}}. Fix or unregister failed cluster nodes and try again", "testClusterFail": "Failed verified availability for: {{failedNodes}}. Fix or unregister failed cluster nodes and try again",

View file

@ -1,27 +1,37 @@
import React from "react"; import React, { ReactNode } from "react";
import { Title } from "@patternfly/react-core"; import {
Card,
CardBody,
CardHeader,
CardTitle,
Title,
} from "@patternfly/react-core";
import "./form-panel.css"; import "./form-panel.css";
interface FormPanelProps extends React.HTMLProps<HTMLFormElement> { type FormPanelProps = {
title: string; title: string;
scrollId: string; scrollId: string;
} children: ReactNode;
};
export const FormPanel = (props: FormPanelProps) => { export const FormPanel = ({ title, children, scrollId }: FormPanelProps) => {
const { title, children, scrollId, ...rest } = props;
return ( return (
<section {...rest} className="kc-form-panel__panel"> <Card isFlat className="kc-form-panel__panel">
<Title <CardHeader>
headingLevel="h4" <CardTitle tabIndex={0}>
size="xl" <Title
className="kc-form-panel__title" headingLevel="h4"
id={scrollId} size="xl"
tabIndex={0} // so that jumpLink sends focus to the section for a11y className="kc-form-panel__title"
> id={scrollId}
{title} tabIndex={0} // so that jumpLink sends focus to the section for a11y
</Title> >
{children} {title}
</section> </Title>
</CardTitle>
</CardHeader>
<CardBody>{children}</CardBody>
</Card>
); );
}; };

View file

@ -8,6 +8,7 @@ import {
PageSection, PageSection,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { mainPageContentId } from "../../App";
import { FormPanel } from "./FormPanel"; import { FormPanel } from "./FormPanel";
import "./scroll-form.css"; import "./scroll-form.css";
@ -16,9 +17,6 @@ type ScrollFormProps = {
children: React.ReactNode; children: React.ReactNode;
}; };
// This must match the page id created in App.tsx unless another page section has been given hasScrollableContent
const mainPageContentId = "#kc-main-content-page-container";
const spacesToHyphens = (string: string): string => { const spacesToHyphens = (string: string): string => {
return string.replace(/\s+/g, "-"); return string.replace(/\s+/g, "-");
}; };
@ -42,7 +40,7 @@ export const ScrollForm = ({ sections, children }: ScrollFormProps) => {
isVertical isVertical
// scrollableSelector has to point to the id of the element whose scrollTop changes // scrollableSelector has to point to the id of the element whose scrollTop changes
// to scroll the entire main section, it has to be the pf-c-page__main // to scroll the entire main section, it has to be the pf-c-page__main
scrollableSelector={mainPageContentId} scrollableSelector={`#${mainPageContentId}`}
label={t("jumpToSection")} label={t("jumpToSection")}
offset={100} offset={100}
> >

View file

@ -1,9 +1,4 @@
.kc-form-panel__panel { .kc-form-panel__panel {
padding-top: var(--pf-global--spacer--lg); margin-top: var(--pf-global--spacer--lg);
padding-bottom: var(--pf-global--spacer--2xl); padding-bottom: var(--pf-global--spacer--2xl);
max-width: 768px;
}
.kc-form-panel__title {
margin-bottom: var(--pf-global--spacer--lg);
} }