diff --git a/src/App.tsx b/src/App.tsx index 1afadf672d..13e4a3c3cc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 }) => ( diff --git a/src/clients/AdvancedTab.tsx b/src/clients/AdvancedTab.tsx index a1676d6e93..89e3adcfe0 100644 --- a/src/clients/AdvancedTab.tsx +++ b/src/clients/AdvancedTab.tsx @@ -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 ( - - - - - In order to successfully push setup url on - - {t("settings")} - - tab - - - - - - - } - > - + + + In order to successfully push setup url on + {t("settings")} + tab + + + + - - - - - - - - - - - - - - } + } + > + + + + - - - - - - + {t("setToNow")} + + + + + + + <> + + + } + > + + + ( + + )} + /> + + + + + + + + <> - - - + + + <> {protocol === openIdConnect && ( <> - - - {t("clients-help:fineGrainOpenIdConnectConfiguration")} - - - - - convertToFormValues(attributes, "attributes", setValue) - } - /> - + + {t("clients-help:fineGrainOpenIdConnectConfiguration")} + + save()} + reset={() => + convertToFormValues(attributes, "attributes", setValue) + } + /> )} {protocol !== openIdConnect && ( <> - - {t("clients-help:fineGrainSamlEndpointConfig")} - - - - - convertToFormValues(attributes, "attributes", setValue) - } - /> - - - )} - - {protocol === openIdConnect && ( - - - {t("clients-help:openIdConnectCompatibilityModes")} - - - + {t("clients-help:fineGrainSamlEndpointConfig")} + + save()} reset={() => - resetFields(["exclude-session-state-from-auth-response"]) + convertToFormValues(attributes, "attributes", setValue) } /> - - - )} - - - - {t("clients-help:advancedSettings" + toUpperCase(protocol!))} + + )} + + {protocol === openIdConnect && ( + <> + + {t("clients-help:openIdConnectCompatibilityModes")} - - - { - 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"]) + } /> - - - - - {t("clients-help:authenticationOverrides")} - - - { - setValue( - "authenticationFlowBindingOverrides.browser", - authenticationFlowBindingOverrides?.browser - ); - setValue( - "authenticationFlowBindingOverrides.direct_grant", - authenticationFlowBindingOverrides?.direct_grant - ); - }} - /> - - + + )} + <> + + {t("clients-help:advancedSettings" + toUpperCase(protocol!))} + + save()} + reset={() => { + resetFields([ + "saml-assertion-lifespan", + "access-token-lifespan", + "tls-client-certificate-bound-access-tokens", + "pkce-code-challenge-method", + ]); + }} + /> + + <> + + {t("clients-help:authenticationOverrides")} + + save()} + reset={() => { + setValue( + "authenticationFlowBindingOverrides.browser", + authenticationFlowBindingOverrides?.browser + ); + setValue( + "authenticationFlowBindingOverrides.direct_grant", + authenticationFlowBindingOverrides?.direct_grant + ); + }} + /> + ); }; diff --git a/src/clients/ClientDetails.tsx b/src/clients/ClientDetails.tsx index 85ebeecba5..5e18abdd78 100644 --- a/src/clients/ClientDetails.tsx +++ b/src/clients/ClientDetails.tsx @@ -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={{t("common:settings")}} > - + save()} /> {publicClient && ( { eventKey="credentials" title={{t("credentials")}} > - + save()} /> )} ( )} diff --git a/src/clients/messages.json b/src/clients/messages.json index 8ec24abae3..d83aaa5005 100644 --- a/src/clients/messages.json +++ b/src/clients/messages.json @@ -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", diff --git a/src/components/scroll-form/FormPanel.tsx b/src/components/scroll-form/FormPanel.tsx index b9d2e6cb43..d598db7f7e 100644 --- a/src/components/scroll-form/FormPanel.tsx +++ b/src/components/scroll-form/FormPanel.tsx @@ -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 { +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 ( -
- - {title} - - {children} -
+ + + + + {title} + + + + {children} + ); }; diff --git a/src/components/scroll-form/ScrollForm.tsx b/src/components/scroll-form/ScrollForm.tsx index 9ba377c565..6b080c2002 100644 --- a/src/components/scroll-form/ScrollForm.tsx +++ b/src/components/scroll-form/ScrollForm.tsx @@ -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} > diff --git a/src/components/scroll-form/form-panel.css b/src/components/scroll-form/form-panel.css index ec180abaf3..2b5a50106e 100644 --- a/src/components/scroll-form/form-panel.css +++ b/src/components/scroll-form/form-panel.css @@ -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); }