5949fd43d0
* removed deprecated select Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * some more deprecation removal working towards fixing: #28197 Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * changed to use new api Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * more deprecation removal Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed merge error Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fix tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * small fix Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed merge error Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * no more default text for SelectOption Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * changed to use id Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed dropdown in keycloakCard and test fixes Signed-off-by: mfrances <mfrances@redhat.com> Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed lint error Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fix dropdown/select related test failures Signed-off-by: mfrances <mfrances@redhat.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * i18n label Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fix test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * removed Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed merge error Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> --------- Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> Signed-off-by: mfrances <mfrances@redhat.com> Co-authored-by: mfrances <mfrances@redhat.com>
621 lines
20 KiB
TypeScript
621 lines
20 KiB
TypeScript
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
|
import {
|
|
ActionGroup,
|
|
Button,
|
|
FormGroup,
|
|
FormHelperText,
|
|
HelperText,
|
|
HelperTextItem,
|
|
NumberInput,
|
|
PageSection,
|
|
SelectOption,
|
|
Switch,
|
|
Text,
|
|
TextInput,
|
|
TextVariants,
|
|
} from "@patternfly/react-core";
|
|
import { useEffect, useState } from "react";
|
|
import { Controller, useForm, useWatch } from "react-hook-form";
|
|
import { useTranslation } from "react-i18next";
|
|
import { FormPanel, HelpItem } from "@keycloak/keycloak-ui-shared";
|
|
|
|
import { FormAccess } from "../components/form/FormAccess";
|
|
import {
|
|
TimeSelector,
|
|
toHumanFormat,
|
|
} from "../components/time-selector/TimeSelector";
|
|
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
|
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
|
import { beerify, convertToFormValues, sortProviders } from "../util";
|
|
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
|
|
|
|
import "./realm-settings-section.css";
|
|
import {
|
|
KeycloakSelect,
|
|
SelectVariant,
|
|
} from "../components/select/KeycloakSelect";
|
|
|
|
type RealmSettingsSessionsTabProps = {
|
|
realm: RealmRepresentation;
|
|
save: (realm: RealmRepresentation) => void;
|
|
reset?: () => void;
|
|
};
|
|
|
|
export const RealmSettingsTokensTab = ({
|
|
realm,
|
|
reset,
|
|
save,
|
|
}: RealmSettingsSessionsTabProps) => {
|
|
const { t } = useTranslation();
|
|
const serverInfo = useServerInfo();
|
|
const isFeatureEnabled = useIsFeatureEnabled();
|
|
const { whoAmI } = useWhoAmI();
|
|
|
|
const [defaultSigAlgDrpdwnIsOpen, setDefaultSigAlgDrpdwnOpen] =
|
|
useState(false);
|
|
|
|
const defaultSigAlgOptions = sortProviders(
|
|
serverInfo.providers!["signature"].providers,
|
|
);
|
|
|
|
const form = useForm<RealmRepresentation>();
|
|
const { setValue, control } = form;
|
|
|
|
const offlineSessionMaxEnabled = useWatch({
|
|
control,
|
|
name: "offlineSessionMaxLifespanEnabled",
|
|
defaultValue: realm.offlineSessionMaxLifespanEnabled,
|
|
});
|
|
|
|
const ssoSessionIdleTimeout = useWatch({
|
|
control,
|
|
name: "ssoSessionIdleTimeout",
|
|
defaultValue: 36000,
|
|
});
|
|
|
|
const revokeRefreshToken = useWatch({
|
|
control,
|
|
name: "revokeRefreshToken",
|
|
defaultValue: false,
|
|
});
|
|
|
|
useEffect(() => {
|
|
convertToFormValues(realm, setValue);
|
|
}, []);
|
|
|
|
return (
|
|
<PageSection variant="light">
|
|
<FormPanel title={t("general")} className="kc-sso-session-template">
|
|
<FormAccess
|
|
isHorizontal
|
|
role="manage-realm"
|
|
onSubmit={form.handleSubmit(save)}
|
|
>
|
|
<FormGroup
|
|
label={t("defaultSigAlg")}
|
|
fieldId="kc-default-signature-algorithm"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("defaultSigAlgHelp")}
|
|
fieldLabelId="algorithm"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="defaultSignatureAlgorithm"
|
|
defaultValue={"RS256"}
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<KeycloakSelect
|
|
toggleId="kc-default-sig-alg"
|
|
onToggle={() =>
|
|
setDefaultSigAlgDrpdwnOpen(!defaultSigAlgDrpdwnIsOpen)
|
|
}
|
|
onSelect={(value) => {
|
|
field.onChange(value.toString());
|
|
setDefaultSigAlgDrpdwnOpen(false);
|
|
}}
|
|
selections={field.value?.toString()}
|
|
variant={SelectVariant.single}
|
|
aria-label={t("defaultSigAlg")}
|
|
isOpen={defaultSigAlgDrpdwnIsOpen}
|
|
data-testid="select-default-sig-alg"
|
|
>
|
|
{defaultSigAlgOptions!.map((p, idx) => (
|
|
<SelectOption
|
|
selected={p === field.value}
|
|
key={`default-sig-alg-${idx}`}
|
|
value={p}
|
|
></SelectOption>
|
|
))}
|
|
</KeycloakSelect>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
|
|
{isFeatureEnabled(Feature.DeviceFlow) && (
|
|
<>
|
|
<FormGroup
|
|
label={t("oAuthDeviceCodeLifespan")}
|
|
fieldId="oAuthDeviceCodeLifespan"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("oAuthDeviceCodeLifespanHelp")}
|
|
fieldLabelId="oAuthDeviceCodeLifespan"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="oauth2DeviceCodeLifespan"
|
|
defaultValue={0}
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
id="oAuthDeviceCodeLifespan"
|
|
data-testid="oAuthDeviceCodeLifespan"
|
|
value={field.value || 0}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
<FormGroup
|
|
label={t("oAuthDevicePollingInterval")}
|
|
fieldId="oAuthDevicePollingInterval"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("oAuthDevicePollingIntervalHelp")}
|
|
fieldLabelId="oAuthDevicePollingInterval"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="oauth2DevicePollingInterval"
|
|
defaultValue={0}
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<NumberInput
|
|
id="oAuthDevicePollingInterval"
|
|
value={field.value}
|
|
min={0}
|
|
onPlus={() => field.onChange(Number(field?.value) + 1)}
|
|
onMinus={() =>
|
|
field.onChange(
|
|
Number(field?.value) > 0
|
|
? Number(field?.value) - 1
|
|
: 0,
|
|
)
|
|
}
|
|
onChange={(event) => {
|
|
const newValue = Number(event.currentTarget.value);
|
|
field.onChange(!isNaN(newValue) ? newValue : 0);
|
|
}}
|
|
placeholder={t("oAuthDevicePollingInterval")}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
<FormGroup
|
|
label={t("shortVerificationUri")}
|
|
fieldId="shortVerificationUri"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("shortVerificationUriTooltipHelp")}
|
|
fieldLabelId="shortVerificationUri"
|
|
/>
|
|
}
|
|
>
|
|
<TextInput
|
|
id="shortVerificationUri"
|
|
placeholder={t("shortVerificationUri")}
|
|
{...form.register("attributes.shortVerificationUri")}
|
|
/>
|
|
</FormGroup>
|
|
<FormGroup
|
|
label={t("parRequestUriLifespan")}
|
|
fieldId="parRequestUriLifespan"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("parRequestUriLifespanHelp")}
|
|
fieldLabelId="parRequestUriLifespan"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="attributes.parRequestUriLifespan"
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
id="parRequestUriLifespan"
|
|
className="par-request-uri-lifespan"
|
|
data-testid="par-request-uri-lifespan-input"
|
|
aria-label="par-request-uri-lifespan"
|
|
value={field.value}
|
|
onChange={field.onChange}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
</>
|
|
)}
|
|
</FormAccess>
|
|
</FormPanel>
|
|
<FormPanel
|
|
title={t("refreshTokens")}
|
|
className="kc-client-session-template"
|
|
>
|
|
<FormAccess
|
|
isHorizontal
|
|
role="manage-realm"
|
|
className="pf-v5-u-mt-lg"
|
|
onSubmit={form.handleSubmit(save)}
|
|
>
|
|
<FormGroup
|
|
hasNoPaddingTop
|
|
label={t("revokeRefreshToken")}
|
|
fieldId="kc-revoke-refresh-token"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("revokeRefreshTokenHelp")}
|
|
fieldLabelId="revokeRefreshToken"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="revokeRefreshToken"
|
|
control={form.control}
|
|
defaultValue={false}
|
|
render={({ field }) => (
|
|
<Switch
|
|
id="kc-revoke-refresh-token"
|
|
data-testid="revoke-refresh-token-switch"
|
|
aria-label={t("revokeRefreshToken")}
|
|
label={t("enabled")}
|
|
labelOff={t("disabled")}
|
|
isChecked={field.value}
|
|
onChange={field.onChange}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
{revokeRefreshToken && (
|
|
<FormGroup
|
|
label={t("refreshTokenMaxReuse")}
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("refreshTokenMaxReuseHelp")}
|
|
fieldLabelId="refreshTokenMaxReuse"
|
|
/>
|
|
}
|
|
fieldId="refreshTokenMaxReuse"
|
|
>
|
|
<Controller
|
|
name="refreshTokenMaxReuse"
|
|
defaultValue={0}
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<NumberInput
|
|
type="text"
|
|
id="refreshTokenMaxReuseMs"
|
|
value={field.value}
|
|
onPlus={() => field.onChange(field.value! + 1)}
|
|
onMinus={() => field.onChange(field.value! - 1)}
|
|
onChange={(event) =>
|
|
field.onChange(
|
|
Number((event.target as HTMLInputElement).value),
|
|
)
|
|
}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
)}
|
|
</FormAccess>
|
|
</FormPanel>
|
|
<FormPanel
|
|
title={t("accessTokens")}
|
|
className="kc-offline-session-template"
|
|
>
|
|
<FormAccess
|
|
isHorizontal
|
|
role="manage-realm"
|
|
className="pf-v5-u-mt-lg"
|
|
onSubmit={form.handleSubmit(save)}
|
|
>
|
|
<FormGroup
|
|
label={t("accessTokenLifespan")}
|
|
fieldId="accessTokenLifespan"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("accessTokenLifespanHelp")}
|
|
fieldLabelId="accessTokenLifespan"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="accessTokenLifespan"
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
validated={
|
|
field.value! > ssoSessionIdleTimeout!
|
|
? "warning"
|
|
: "default"
|
|
}
|
|
className="kc-access-token-lifespan"
|
|
data-testid="access-token-lifespan-input"
|
|
aria-label="access-token-lifespan"
|
|
value={field.value!}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
<FormHelperText>
|
|
<HelperText>
|
|
<HelperTextItem>
|
|
{t("recommendedSsoTimeout", {
|
|
time: toHumanFormat(
|
|
ssoSessionIdleTimeout!,
|
|
whoAmI.getLocale(),
|
|
),
|
|
})}
|
|
</HelperTextItem>
|
|
</HelperText>
|
|
</FormHelperText>
|
|
</FormGroup>
|
|
|
|
<FormGroup
|
|
label={t("accessTokenLifespanImplicitFlow")}
|
|
fieldId="accessTokenLifespanImplicitFlow"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("accessTokenLifespanImplicitFlow")}
|
|
fieldLabelId="accessTokenLifespanImplicitFlow"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="accessTokenLifespanForImplicitFlow"
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
className="kc-access-token-lifespan-implicit"
|
|
data-testid="access-token-lifespan-implicit-input"
|
|
value={field.value!}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
<FormGroup
|
|
label={t("clientLoginTimeout")}
|
|
fieldId="clientLoginTimeout"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("clientLoginTimeoutHelp")}
|
|
fieldLabelId="clientLoginTimeout"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="accessCodeLifespan"
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
className="kc-client-login-timeout"
|
|
data-testid="client-login-timeout-input"
|
|
aria-label="client-login-timeout"
|
|
value={field.value!}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
|
|
{offlineSessionMaxEnabled && (
|
|
<FormGroup
|
|
label={t("offlineSessionMax")}
|
|
fieldId="offlineSessionMax"
|
|
id="offline-session-max-label"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("offlineSessionMaxHelp")}
|
|
fieldLabelId="offlineSessionMax"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="offlineSessionMaxLifespan"
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
className="kc-offline-session-max"
|
|
data-testid="offline-session-max-input"
|
|
value={field.value!}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
)}
|
|
</FormAccess>
|
|
</FormPanel>
|
|
<FormPanel
|
|
className="kc-login-settings-template"
|
|
title={t("actionTokens")}
|
|
>
|
|
<FormAccess
|
|
isHorizontal
|
|
role="manage-realm"
|
|
className="pf-v5-u-mt-lg"
|
|
onSubmit={form.handleSubmit(save)}
|
|
>
|
|
<FormGroup
|
|
label={t("userInitiatedActionLifespan")}
|
|
id="kc-user-initiated-action-lifespan"
|
|
fieldId="userInitiatedActionLifespan"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("userInitiatedActionLifespanHelp")}
|
|
fieldLabelId="userInitiatedActionLifespan"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="actionTokenGeneratedByUserLifespan"
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
className="kc-user-initiated-action-lifespan"
|
|
data-testid="user-initiated-action-lifespan"
|
|
aria-label="user-initiated-action-lifespan"
|
|
value={field.value!}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
<FormGroup
|
|
label={t("defaultAdminInitiated")}
|
|
fieldId="defaultAdminInitiated"
|
|
id="default-admin-initiated-label"
|
|
labelIcon={
|
|
<HelpItem
|
|
helpText={t("defaultAdminInitiatedActionLifespanHelp")}
|
|
fieldLabelId="defaultAdminInitiated"
|
|
/>
|
|
}
|
|
>
|
|
<Controller
|
|
name="actionTokenGeneratedByAdminLifespan"
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
className="kc-default-admin-initiated"
|
|
data-testid="default-admin-initated-input"
|
|
aria-label="default-admin-initated-input"
|
|
value={field.value!}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
<Text
|
|
className="kc-override-action-tokens-subtitle"
|
|
component={TextVariants.h1}
|
|
>
|
|
{t("overrideActionTokens")}
|
|
</Text>
|
|
<FormGroup
|
|
label={t("emailVerification")}
|
|
fieldId="emailVerification"
|
|
id="email-verification"
|
|
>
|
|
<Controller
|
|
name={`attributes.${beerify(
|
|
"actionTokenGeneratedByUserLifespan.verify-email",
|
|
)}`}
|
|
defaultValue=""
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
className="kc-email-verification"
|
|
data-testid="email-verification-input"
|
|
value={field.value}
|
|
onChange={(value) => field.onChange(value.toString())}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
<FormGroup
|
|
label={t("idpAccountEmailVerification")}
|
|
fieldId="idpAccountEmailVerification"
|
|
id="idp-acct-label"
|
|
>
|
|
<Controller
|
|
name={`attributes.${beerify(
|
|
"actionTokenGeneratedByUserLifespan.idp-verify-account-via-email",
|
|
)}`}
|
|
defaultValue={""}
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
className="kc-idp-email-verification"
|
|
data-testid="idp-email-verification-input"
|
|
value={field.value}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
<FormGroup
|
|
label={t("forgotPassword")}
|
|
fieldId="forgotPassword"
|
|
id="forgot-password-label"
|
|
>
|
|
<Controller
|
|
name={`attributes.${beerify(
|
|
"actionTokenGeneratedByUserLifespan.reset-credentials",
|
|
)}`}
|
|
defaultValue={""}
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
className="kc-forgot-pw"
|
|
data-testid="forgot-pw-input"
|
|
value={field.value}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
<FormGroup
|
|
label={t("executeActions")}
|
|
fieldId="executeActions"
|
|
id="execute-actions"
|
|
>
|
|
<Controller
|
|
name={`attributes.${beerify(
|
|
"actionTokenGeneratedByUserLifespan.execute-actions",
|
|
)}`}
|
|
defaultValue={""}
|
|
control={form.control}
|
|
render={({ field }) => (
|
|
<TimeSelector
|
|
className="kc-execute-actions"
|
|
data-testid="execute-actions-input"
|
|
value={field.value}
|
|
onChange={field.onChange}
|
|
units={["minute", "hour", "day"]}
|
|
/>
|
|
)}
|
|
/>
|
|
</FormGroup>
|
|
<ActionGroup>
|
|
<Button
|
|
variant="primary"
|
|
type="submit"
|
|
data-testid="tokens-tab-save"
|
|
isDisabled={!form.formState.isDirty}
|
|
>
|
|
{t("save")}
|
|
</Button>
|
|
<Button variant="link" onClick={reset}>
|
|
{t("revert")}
|
|
</Button>
|
|
</ActionGroup>
|
|
</FormAccess>
|
|
</FormPanel>
|
|
</PageSection>
|
|
);
|
|
};
|