* partially solves issue #422 * fixed section * added not before now set / clear alerts
This commit is contained in:
parent
a81164ee2a
commit
3b1d89fedb
8 changed files with 216 additions and 217 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue