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 { ErrorRenderer } from "./components/error/ErrorRenderer";
// This must match the id given as scrollableSelector in scroll-form
const mainPageContentId = "kc-main-content-page-container";
export const mainPageContentId = "kc-main-content-page-container";
const AppContexts = ({ children }: { children: ReactNode }) => (
<AccessContextProvider>

View file

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

View file

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

View file

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

View file

@ -134,6 +134,8 @@
"notBefore": "Not before",
"setToNow": "Set to now",
"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}}",
"notBeforePushSuccess": "Successfully push \"not before\" to: {{successNodes}}",
"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 { Title } from "@patternfly/react-core";
import React, { ReactNode } from "react";
import {
Card,
CardBody,
CardHeader,
CardTitle,
Title,
} from "@patternfly/react-core";
import "./form-panel.css";
interface FormPanelProps extends React.HTMLProps<HTMLFormElement> {
type FormPanelProps = {
title: string;
scrollId: string;
}
children: ReactNode;
};
export const FormPanel = (props: FormPanelProps) => {
const { title, children, scrollId, ...rest } = props;
export const FormPanel = ({ title, children, scrollId }: FormPanelProps) => {
return (
<section {...rest} className="kc-form-panel__panel">
<Title
headingLevel="h4"
size="xl"
className="kc-form-panel__title"
id={scrollId}
tabIndex={0} // so that jumpLink sends focus to the section for a11y
>
{title}
</Title>
{children}
</section>
<Card isFlat className="kc-form-panel__panel">
<CardHeader>
<CardTitle tabIndex={0}>
<Title
headingLevel="h4"
size="xl"
className="kc-form-panel__title"
id={scrollId}
tabIndex={0} // so that jumpLink sends focus to the section for a11y
>
{title}
</Title>
</CardTitle>
</CardHeader>
<CardBody>{children}</CardBody>
</Card>
);
};

View file

@ -8,6 +8,7 @@ import {
PageSection,
} from "@patternfly/react-core";
import { mainPageContentId } from "../../App";
import { FormPanel } from "./FormPanel";
import "./scroll-form.css";
@ -16,9 +17,6 @@ type ScrollFormProps = {
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 => {
return string.replace(/\s+/g, "-");
};
@ -42,7 +40,7 @@ export const ScrollForm = ({ sections, children }: ScrollFormProps) => {
isVertical
// 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
scrollableSelector={mainPageContentId}
scrollableSelector={`#${mainPageContentId}`}
label={t("jumpToSection")}
offset={100}
>

View file

@ -1,9 +1,4 @@
.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);
max-width: 768px;
}
.kc-form-panel__title {
margin-bottom: var(--pf-global--spacer--lg);
}