Ream-settings-> User profile -> Create attribute (#2265)
* create attribute - wip * create attribute - wip * create attribute - added attributes general settings form template * create attribute - added attributes permission form template * added validations form * added validations table * added empty state for validators table * added attribute annotations form * attribute validations - css improvement * attribute annotations - css improvement * added requiredWhen label * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute - wip * new attribute * fixed deleting attribute * put the validators into the form * feedback fixes * added defaultValues * fixed permissions * removed unused form value Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com> Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com> Co-authored-by: Erik Jan de Wit <edewit@redhat.com>
This commit is contained in:
parent
ba4765fdc5
commit
9242ba6935
15 changed files with 1244 additions and 28 deletions
|
@ -1,23 +1,196 @@
|
|||
import { PageSection } from "@patternfly/react-core";
|
||||
import React from "react";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
Button,
|
||||
Form,
|
||||
PageSection,
|
||||
} from "@patternfly/react-core";
|
||||
import { FormProvider, useForm, useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
import { ScrollForm } from "../components/scroll-form/ScrollForm";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||
import { AttributeGeneralSettings } from "./user-profile/attribute/AttributeGeneralSettings";
|
||||
import { AttributePermission } from "./user-profile/attribute/AttributePermission";
|
||||
import { AttributeValidations } from "./user-profile/attribute/AttributeValidations";
|
||||
import { toUserProfile } from "./routes/UserProfile";
|
||||
import "./realm-settings-section.css";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { AttributeAnnotations } from "./user-profile/attribute/AttributeAnnotations";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { UserProfileProvider } from "./user-profile/UserProfileContext";
|
||||
import type { UserProfileAttribute } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||
import type { KeyValueType } from "../components/attribute-form/attribute-convert";
|
||||
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
||||
|
||||
export default function NewAttributeSettings() {
|
||||
type UserProfileAttributeType = UserProfileAttribute &
|
||||
AttributeRequired &
|
||||
Permission;
|
||||
|
||||
type AttributeRequired = {
|
||||
roles: string[];
|
||||
scopeRequired: string[];
|
||||
enabledWhen: boolean;
|
||||
requiredWhen: boolean;
|
||||
};
|
||||
|
||||
type Permission = {
|
||||
view: PermissionView[];
|
||||
edit: PermissionEdit[];
|
||||
};
|
||||
|
||||
type PermissionView = [
|
||||
{
|
||||
adminView: boolean;
|
||||
userView: boolean;
|
||||
}
|
||||
];
|
||||
|
||||
type PermissionEdit = [
|
||||
{
|
||||
adminEdit: boolean;
|
||||
userEdit: boolean;
|
||||
}
|
||||
];
|
||||
|
||||
const CreateAttributeFormContent = ({
|
||||
save,
|
||||
}: {
|
||||
save: (profileConfig: UserProfileConfig) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const form = useFormContext();
|
||||
const { realm } = useRealm();
|
||||
|
||||
return (
|
||||
<PageSection variant="light">
|
||||
<FormAccess
|
||||
onSubmit={() => console.log("TODO handle submit")}
|
||||
isHorizontal
|
||||
role="view-realm"
|
||||
className="pf-u-mt-lg"
|
||||
<UserProfileProvider>
|
||||
<ScrollForm
|
||||
sections={[
|
||||
t("generalSettings"),
|
||||
t("permission"),
|
||||
t("validations"),
|
||||
t("annotations"),
|
||||
]}
|
||||
>
|
||||
<p>{t("createAttribute")}</p>
|
||||
</FormAccess>
|
||||
<AttributeGeneralSettings />
|
||||
<AttributePermission />
|
||||
<AttributeValidations />
|
||||
<AttributeAnnotations />
|
||||
</ScrollForm>
|
||||
<Form onSubmit={form.handleSubmit(save)}>
|
||||
<ActionGroup className="keycloak__form_actions">
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
data-testid="attribute-create"
|
||||
>
|
||||
{t("common:create")}
|
||||
</Button>
|
||||
<Link
|
||||
to={toUserProfile({ realm, tab: "attributes" })}
|
||||
data-testid="attribute-cancel"
|
||||
className="kc-attributeCancel"
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Link>
|
||||
</ActionGroup>
|
||||
</Form>
|
||||
</UserProfileProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default function NewAttributeSettings() {
|
||||
const { realm: realmName } = useRealm();
|
||||
const adminClient = useAdminClient();
|
||||
const form = useForm<UserProfileConfig>();
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const history = useHistory();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const [config, setConfig] = useState<UserProfileConfig | null>(null);
|
||||
const [clientScopes, setClientScopes] =
|
||||
useState<ClientScopeRepresentation[]>();
|
||||
|
||||
useFetch(
|
||||
() =>
|
||||
Promise.all([
|
||||
adminClient.users.getProfile({ realm: realmName }),
|
||||
adminClient.clientScopes.find(),
|
||||
]),
|
||||
([config, clientScopes]) => {
|
||||
setConfig(config);
|
||||
setClientScopes(clientScopes);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const save = async (profileConfig: UserProfileAttributeType) => {
|
||||
const scopeNames = clientScopes?.map((clientScope) => clientScope.name);
|
||||
|
||||
const selector = {
|
||||
scopes: profileConfig.enabledWhen
|
||||
? scopeNames
|
||||
: profileConfig.selector?.scopes,
|
||||
};
|
||||
|
||||
const required = {
|
||||
roles: profileConfig.roles,
|
||||
scopes: profileConfig.requiredWhen
|
||||
? scopeNames
|
||||
: profileConfig.scopeRequired,
|
||||
};
|
||||
|
||||
const validations = profileConfig.validations;
|
||||
|
||||
const annotations = (profileConfig.annotations! as KeyValueType[]).reduce(
|
||||
(obj, item) => Object.assign(obj, { [item.key]: item.value }),
|
||||
{}
|
||||
);
|
||||
|
||||
const newAttribute = [
|
||||
{
|
||||
name: profileConfig.name,
|
||||
displayName: profileConfig.displayName,
|
||||
required,
|
||||
validations,
|
||||
selector,
|
||||
permissions: profileConfig.permissions,
|
||||
annotations,
|
||||
},
|
||||
];
|
||||
|
||||
const newAttributesList = config?.attributes!.concat(
|
||||
newAttribute as UserProfileAttribute
|
||||
);
|
||||
|
||||
try {
|
||||
await adminClient.users.updateProfile({
|
||||
attributes: newAttributesList,
|
||||
realm: realmName,
|
||||
});
|
||||
|
||||
history.push(toUserProfile({ realm: realmName, tab: "attributes" }));
|
||||
|
||||
addAlert(
|
||||
t("realm-settings:createAttributeSuccess"),
|
||||
AlertVariant.success
|
||||
);
|
||||
} catch (error) {
|
||||
addError("realm-settings:createAttributeError", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<ViewHeader
|
||||
titleKey={t("createAttribute")}
|
||||
subKey={t("createAttributeSubTitle")}
|
||||
/>
|
||||
<PageSection variant="light">
|
||||
<CreateAttributeFormContent save={() => form.handleSubmit(save)()} />
|
||||
</PageSection>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -146,6 +146,20 @@ export default {
|
|||
"The condition is checked during client registration/update requests and it evaluates to true if the entity (usually user), who is creating/updating client is member of the specified role. For reference the realm role, you can use the realm role name like 'my_realm_role' . For reference client role, you can use the client_id.role_name for example 'my_client.my_client_role' will refer to client role 'my_client_role' of client 'my_client'. ",
|
||||
defaultGroups:
|
||||
"Default groups allow you to automatically assign groups membership whenever any new user is created or imported through <1>identity brokering</1>.",
|
||||
attributeGeneralSettingsDescription:
|
||||
"This section contains a few basic settings common to all attributes.",
|
||||
attributeNameHelp: "The name of the attribute.",
|
||||
attributeDisplayNameHelp:
|
||||
"Display name for the attribute. Supports keys for localized values as well. For example: ${profile.attribute.phoneNumber}.",
|
||||
attributeGroupHelp: "user.profile.attribute.group.tooltip",
|
||||
requiredHelp:
|
||||
"Set the attribute as required. If enabled, the attribute must be set by users and administrators. Otherwise, the attribute is optional.",
|
||||
attributePermissionDescription:
|
||||
"This section contains permissions for who can edit and who can view the attribute.",
|
||||
whoCanEditHelp:
|
||||
"If enabled, users or administrators can view and edit the attribute. Otherwise, users or administrators don't have access to write to the attribute.",
|
||||
whoCanViewHelp:
|
||||
"If enabled, users or administrators can view the attribute. Otherwise, users or administrators don't have access to the attribute.",
|
||||
editUsername:
|
||||
"If enabled, the username field is editable, readonly otherwise.",
|
||||
},
|
||||
|
|
|
@ -376,15 +376,51 @@ export default {
|
|||
attributeName: "Name",
|
||||
attributeDisplayName: "Display name",
|
||||
attributeGroup: "Attribute group",
|
||||
enabledWhen: "Enabled when",
|
||||
required: "Required",
|
||||
requiredFor: "Required for",
|
||||
requiredWhen: "Required when",
|
||||
whoCanEdit: "Who can edit?",
|
||||
whoCanView: "Who can view?",
|
||||
user: "User",
|
||||
admin: "Admin",
|
||||
addValidator: "Add validator",
|
||||
addValidatorRole: "Add {{validatorName}} validator",
|
||||
validatorDialogColNames: {
|
||||
colName: "Role name",
|
||||
colDescription: "Description",
|
||||
},
|
||||
validatorColNames: {
|
||||
colName: "Validator name",
|
||||
colConfig: "Config",
|
||||
},
|
||||
deleteValidatorConfirmTitle: "Delete validator?",
|
||||
deleteValidatorConfirmMsg:
|
||||
"Are you sure you want to permanently delete the validator {{validatorName}}?",
|
||||
validatorDeletedSuccess:
|
||||
"Success! User Profile configuration has been saved.",
|
||||
validatorDeletedError: "Error saving User Profile: {{error}}",
|
||||
emptyValidators: "No validators.",
|
||||
updatedUserProfileSuccess: "User Profile configuration has been saved",
|
||||
updatedUserProfileError: "User Profile configuration hasn't been saved",
|
||||
createAttribute: "Create attribute",
|
||||
createAttributeSubTitle: "Create a new attribute",
|
||||
createAttributeSuccess:
|
||||
"Success! User Profile configuration has been saved.",
|
||||
createAttributeError:
|
||||
"Error! User Profile configuration has not been saved {{error}}.",
|
||||
attributesDropdown: "Attributes dropdown",
|
||||
deleteAttributeConfirmTitle: "Delete attribute?",
|
||||
deleteAttributeConfirm:
|
||||
"Are you sure you want to permanently delete the attribute {{attributeName}}?",
|
||||
deleteAttributeSuccess: "Attribute deleted",
|
||||
deleteAttributeError: "",
|
||||
deleteAttributeError: "Attribute not deleted",
|
||||
generalSettings: "General settings",
|
||||
permission: "Permission",
|
||||
validations: "Validations",
|
||||
annotations: "Annotations",
|
||||
addAnnotationText: "Add annotation",
|
||||
validateName: "Attribute configuration without name is not allowed.",
|
||||
eventType: "Event saved type",
|
||||
searchEventType: "Search saved event type",
|
||||
addSavedTypes: "Add saved types",
|
||||
|
|
|
@ -273,3 +273,25 @@ input#kc-scopes {
|
|||
.kc-join-group-modal-check {
|
||||
margin-right: var(--pf-global--spacer--sm);
|
||||
}
|
||||
|
||||
.kc-requiredFor {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.kc-requiredFor-option {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.kc-emptyValidators {
|
||||
color: #8d9195;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.kc-attributes-validations {
|
||||
max-width: 1024px;
|
||||
margin-bottom: 52px;
|
||||
}
|
||||
|
||||
.kc-attributeCancel {
|
||||
align-self: center;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { EditClientPolicyConditionRoute } from "./routes/EditCondition";
|
|||
import { UserProfileRoute } from "./routes/UserProfile";
|
||||
import { AddAttributeRoute } from "./routes/AddAttribute";
|
||||
import { KeysRoute } from "./routes/KeysTab";
|
||||
import { AttributeRoute } from "./routes/Attribute";
|
||||
import { NewAttributesGroupRoute } from "./routes/NewAttributesGroup";
|
||||
import { EditAttributesGroupRoute } from "./routes/EditAttributesGroup";
|
||||
|
||||
|
@ -31,6 +32,7 @@ const routes: RouteDef[] = [
|
|||
EditClientPolicyConditionRoute,
|
||||
UserProfileRoute,
|
||||
AddAttributeRoute,
|
||||
AttributeRoute,
|
||||
NewAttributesGroupRoute,
|
||||
EditAttributesGroupRoute,
|
||||
];
|
||||
|
|
|
@ -10,8 +10,8 @@ export type AddAttributeParams = {
|
|||
export const AddAttributeRoute: RouteDef = {
|
||||
path: "/:realm/realm-settings/userProfile/attributes/add-attribute",
|
||||
component: lazy(() => import("../NewAttributeSettings")),
|
||||
breadcrumb: (t) => t("realmSettings"),
|
||||
access: "view-realm",
|
||||
breadcrumb: (t) => t("realm-settings:createAttribute"),
|
||||
access: "manage-realm",
|
||||
};
|
||||
|
||||
export const toAddAttribute = (
|
||||
|
|
22
src/realm-settings/routes/Attribute.ts
Normal file
22
src/realm-settings/routes/Attribute.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { lazy } from "react";
|
||||
import { generatePath } from "react-router-dom";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
|
||||
export type AttributeParams = {
|
||||
realm: string;
|
||||
attributeName: string;
|
||||
};
|
||||
|
||||
export const AttributeRoute: RouteDef = {
|
||||
path: "/:realm/realm-settings/userProfile/attributes/:attributeName/edit-attribute",
|
||||
component: lazy(() => import("../NewAttributeSettings")),
|
||||
breadcrumb: (t) => t("realm-settings:createAttribute"),
|
||||
access: "manage-realm",
|
||||
};
|
||||
|
||||
export const toAttribute = (
|
||||
params: AttributeParams
|
||||
): LocationDescriptorObject => ({
|
||||
pathname: generatePath(AttributeRoute.path, params),
|
||||
});
|
|
@ -13,7 +13,6 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
import { FilterIcon } from "@patternfly/react-icons";
|
||||
|
||||
import type { UserProfileAttribute } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { DraggableTable } from "../../authentication/components/DraggableTable";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
|
@ -21,6 +20,8 @@ import { toAddAttribute } from "../routes/AddAttribute";
|
|||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { useUserProfile } from "./UserProfileContext";
|
||||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { toAttribute } from "../routes/Attribute";
|
||||
import type { UserProfileAttribute } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
|
||||
type movedAttributeType = UserProfileAttribute;
|
||||
|
@ -34,8 +35,7 @@ export const AttributesTab = () => {
|
|||
const [isFilterTypeDropdownOpen, toggleIsFilterTypeDropdownOpen] =
|
||||
useToggle();
|
||||
const [data, setData] = useState(config?.attributes);
|
||||
const [attributeToDelete, setAttributeToDelete] =
|
||||
useState<{ name: string }>();
|
||||
const [attributeToDelete, setAttributeToDelete] = useState("");
|
||||
|
||||
const executeMove = async (
|
||||
attribute: UserProfileAttribute,
|
||||
|
@ -62,13 +62,13 @@ export const AttributesTab = () => {
|
|||
const goToCreate = () => history.push(toAddAttribute({ realm: realmName }));
|
||||
|
||||
const updatedAttributes = config?.attributes!.filter(
|
||||
(attribute) => attribute.name !== attributeToDelete?.name
|
||||
(attribute) => attribute.name !== attributeToDelete
|
||||
);
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: t("deleteAttributeConfirmTitle"),
|
||||
messageKey: t("deleteAttributeConfirm", {
|
||||
attributeName: attributeToDelete?.name!,
|
||||
attributeName: attributeToDelete,
|
||||
}),
|
||||
continueButtonLabel: t("common:delete"),
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
|
@ -80,12 +80,22 @@ export const AttributesTab = () => {
|
|||
errorMessageKey: "realm-settings:deleteAttributeError",
|
||||
}
|
||||
);
|
||||
setAttributeToDelete({
|
||||
name: "",
|
||||
});
|
||||
setAttributeToDelete("");
|
||||
},
|
||||
});
|
||||
|
||||
const cellFormatter = (row: UserProfileAttribute) => (
|
||||
<Link
|
||||
to={toAttribute({
|
||||
realm: realmName,
|
||||
attributeName: row.name!,
|
||||
})}
|
||||
key={row.name}
|
||||
>
|
||||
{row.name}
|
||||
</Link>
|
||||
);
|
||||
|
||||
if (!config?.attributes) {
|
||||
return <KeycloakSpinner />;
|
||||
}
|
||||
|
@ -177,9 +187,7 @@ export const AttributesTab = () => {
|
|||
{
|
||||
name: "name",
|
||||
displayKey: t("attributeName"),
|
||||
cellRenderer: (row) => (
|
||||
<Link to={toAddAttribute({ realm: realmName })}>{row.name}</Link>
|
||||
),
|
||||
cellRenderer: cellFormatter,
|
||||
},
|
||||
{
|
||||
name: "displayName",
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Modal, ModalVariant } from "@patternfly/react-core";
|
||||
import {
|
||||
TableComposable,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
} from "@patternfly/react-table";
|
||||
import { AddValidatorRoleDialog } from "./AddValidatorRoleDialog";
|
||||
import { Validator, validators } from "./Validators";
|
||||
import useToggle from "../../../utils/useToggle";
|
||||
|
||||
export type AddValidatorDialogProps = {
|
||||
toggleDialog: () => void;
|
||||
onConfirm: (newValidator: Validator) => void;
|
||||
};
|
||||
|
||||
export const AddValidatorDialog = ({
|
||||
toggleDialog,
|
||||
onConfirm,
|
||||
}: AddValidatorDialogProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const [selectedValidator, setSelectedValidator] = useState<Validator>();
|
||||
const [addValidatorRoleModalOpen, toggleModal] = useToggle();
|
||||
|
||||
return (
|
||||
<>
|
||||
{addValidatorRoleModalOpen && (
|
||||
<AddValidatorRoleDialog
|
||||
onConfirm={(newValidator) => onConfirm(newValidator)}
|
||||
open={addValidatorRoleModalOpen}
|
||||
toggleDialog={toggleModal}
|
||||
selected={selectedValidator!}
|
||||
/>
|
||||
)}
|
||||
<Modal
|
||||
variant={ModalVariant.small}
|
||||
title={t("addValidator")}
|
||||
isOpen
|
||||
onClose={toggleDialog}
|
||||
>
|
||||
<TableComposable aria-label="validators-table">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t("validatorDialogColNames.colName")}</Th>
|
||||
<Th>{t("validatorDialogColNames.colDescription")}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{validators.map((validator) => (
|
||||
<Tr
|
||||
key={validator.name}
|
||||
onRowClick={() => {
|
||||
setSelectedValidator(validator);
|
||||
toggleModal();
|
||||
}}
|
||||
isHoverable
|
||||
>
|
||||
<Td dataLabel={t("validatorDialogColNames.colName")}>
|
||||
{validator.name}
|
||||
</Td>
|
||||
<Td dataLabel={t("validatorDialogColNames.colDescription")}>
|
||||
{validator.description}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</TableComposable>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Modal, ModalVariant } from "@patternfly/react-core";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { DynamicComponents } from "../../../components/dynamic/DynamicComponents";
|
||||
import type { Validator } from "./Validators";
|
||||
|
||||
export type AddValidatorRoleDialogProps = {
|
||||
open: boolean;
|
||||
toggleDialog: () => void;
|
||||
onConfirm: (newValidator: Validator) => void;
|
||||
selected: Validator;
|
||||
};
|
||||
|
||||
export const AddValidatorRoleDialog = ({
|
||||
open,
|
||||
toggleDialog,
|
||||
onConfirm,
|
||||
selected,
|
||||
}: AddValidatorRoleDialogProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const form = useForm();
|
||||
const { handleSubmit } = form;
|
||||
const selectedRoleValidator = selected;
|
||||
|
||||
const save = () => {
|
||||
const formValues = form.getValues();
|
||||
formValues.name = selectedRoleValidator.name;
|
||||
|
||||
const newValidator = {
|
||||
name: formValues.name,
|
||||
config: formValues.config ?? [],
|
||||
};
|
||||
|
||||
onConfirm(newValidator);
|
||||
toggleDialog();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
variant={ModalVariant.small}
|
||||
title={t("addValidatorRole", {
|
||||
validatorName: selectedRoleValidator.name,
|
||||
})}
|
||||
description={selectedRoleValidator.description}
|
||||
isOpen={open}
|
||||
onClose={toggleDialog}
|
||||
actions={[
|
||||
<Button
|
||||
key="save"
|
||||
data-testid="save-validator-role-button"
|
||||
variant="primary"
|
||||
onClick={() => handleSubmit(save)()}
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>,
|
||||
<Button key="cancel" variant="link" onClick={toggleDialog}>
|
||||
{t("common:cancel")}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<FormProvider {...form}>
|
||||
<DynamicComponents properties={selectedRoleValidator.config!} />
|
||||
</FormProvider>
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
import React from "react";
|
||||
import { FormGroup, Grid, GridItem } from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormAccess } from "../../../components/form-access/FormAccess";
|
||||
import "../../realm-settings-section.css";
|
||||
import { FormProvider, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
AttributeInput,
|
||||
AttributeType,
|
||||
} from "../../../components/attribute-input/AttributeInput";
|
||||
|
||||
export type AttributeAnnotationsProps = {
|
||||
isKeySelectable?: boolean;
|
||||
selectableValues?: AttributeType[];
|
||||
};
|
||||
|
||||
export const AttributeAnnotations = ({
|
||||
isKeySelectable,
|
||||
selectableValues,
|
||||
}: AttributeAnnotationsProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const form = useFormContext();
|
||||
|
||||
return (
|
||||
<FormAccess role="manage-realm" isHorizontal>
|
||||
<FormGroup
|
||||
hasNoPaddingTop
|
||||
label={t("annotations")}
|
||||
fieldId="kc-annotations"
|
||||
className="kc-annotations-label"
|
||||
>
|
||||
<Grid className="kc-annotations">
|
||||
<GridItem>
|
||||
<FormProvider {...form}>
|
||||
<AttributeInput
|
||||
isKeySelectable={isKeySelectable}
|
||||
selectableValues={selectableValues}
|
||||
name="annotations"
|
||||
/>
|
||||
</FormProvider>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</FormGroup>
|
||||
</FormAccess>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,350 @@
|
|||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable prettier/prettier */
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Divider,
|
||||
FormGroup,
|
||||
Radio,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
Switch,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { HelpItem } from "../../../components/help-enabler/HelpItem";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { FormAccess } from "../../../components/form-access/FormAccess";
|
||||
import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
|
||||
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
||||
import "../../realm-settings-section.css";
|
||||
|
||||
const ENABLED_REQUIRED_WHEN = ["Always", "Scopes are requested"] as const;
|
||||
const REQUIRED_FOR = [
|
||||
{ label: "Both users and admins", value: ["admin", "user"]}, { label: "Only users", value: "user" },{ label: "Only admins", value: "admin" }
|
||||
] as const;
|
||||
|
||||
export const AttributeGeneralSettings = () => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const adminClient = useAdminClient();
|
||||
const form = useFormContext();
|
||||
const [clientScopes, setClientScopes] =
|
||||
useState<ClientScopeRepresentation[]>();
|
||||
const [selectEnabledWhenOpen, setSelectEnabledWhenOpen] = useState(false);
|
||||
const [selectRequiredForOpen, setSelectRequiredForOpen] = useState(false);
|
||||
const [isAttributeGroupDropdownOpen, setIsAttributeGroupDropdownOpen] =
|
||||
useState(false);
|
||||
const [enabledWhenSelection, setEnabledWhenSelection] = useState("Always");
|
||||
const [requiredWhenSelection, setRequiredWhenSelection] = useState("Always");
|
||||
|
||||
useFetch(
|
||||
() => adminClient.clientScopes.find(),
|
||||
(clientScopes) => {
|
||||
setClientScopes(clientScopes);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormAccess role="manage-realm" isHorizontal>
|
||||
<FormGroup
|
||||
label={t("attributeName")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:attributeNameHelp"
|
||||
fieldLabelId="realm-settings:attributeName"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-attribute-name"
|
||||
isRequired
|
||||
>
|
||||
<TextInput
|
||||
isRequired
|
||||
type="text"
|
||||
id="kc-attribute-name"
|
||||
name="name"
|
||||
defaultValue=""
|
||||
ref={form.register({
|
||||
required: {
|
||||
value: true,
|
||||
message: `${t("validateName")}`,
|
||||
},
|
||||
})}
|
||||
data-testid="attribute-name"
|
||||
/>
|
||||
{form.errors.name && (
|
||||
<div className="error">{form.errors.name.message}</div>
|
||||
)}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("attributeDisplayName")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:attributeDisplayNameHelp"
|
||||
fieldLabelId="realm-settings:attributeDisplayName"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-attribute-display-name"
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
id="kc-attribute-display-name"
|
||||
name="displayName"
|
||||
defaultValue=""
|
||||
ref={form.register}
|
||||
data-testid="attribute-display-name"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("attributeGroup")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-setting-help:attributeGroupHelp"
|
||||
fieldLabelId="realm-setting:attributeGroup"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-attribute-group"
|
||||
>
|
||||
<Controller
|
||||
name="attributeGroup"
|
||||
defaultValue=""
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="kc-attributeGroup"
|
||||
onToggle={() =>
|
||||
setIsAttributeGroupDropdownOpen(!isAttributeGroupDropdownOpen)
|
||||
}
|
||||
isOpen={isAttributeGroupDropdownOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
setIsAttributeGroupDropdownOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.single}
|
||||
>
|
||||
<SelectOption key={0} value="" isPlaceholder>
|
||||
Select a group
|
||||
</SelectOption>
|
||||
<SelectOption key={1} value=""></SelectOption>
|
||||
</Select>
|
||||
)}
|
||||
></Controller>
|
||||
</FormGroup>
|
||||
<Divider />
|
||||
<FormGroup label={t("enabledWhen")} fieldId="enabledWhen" hasNoPaddingTop>
|
||||
<Controller
|
||||
name="enabledWhen"
|
||||
data-testid="enabledWhen"
|
||||
control={form.control}
|
||||
defaultValue={ENABLED_REQUIRED_WHEN[0]}
|
||||
render={({ onChange, value }) => (
|
||||
<>
|
||||
{ENABLED_REQUIRED_WHEN.map((option) => (
|
||||
<Radio
|
||||
id={option}
|
||||
key={option}
|
||||
data-testid={option}
|
||||
isChecked={value === option}
|
||||
name="enabledWhen"
|
||||
onChange={() => {
|
||||
onChange(option);
|
||||
setEnabledWhenSelection(option);
|
||||
}}
|
||||
label={option}
|
||||
className="pf-u-mb-md"
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup fieldId="kc-scope-enabled-when">
|
||||
<Controller
|
||||
name="scopes"
|
||||
control={form.control}
|
||||
defaultValue={[]}
|
||||
render={({
|
||||
onChange,
|
||||
value,
|
||||
}: {
|
||||
onChange: (newValue: string[]) => void;
|
||||
value: string[];
|
||||
}) => (
|
||||
<Select
|
||||
name="scopes"
|
||||
data-testid="enabled-when-scope-field"
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
typeAheadAriaLabel="Select"
|
||||
chipGroupProps={{
|
||||
numChips: 3,
|
||||
expandedText: t("common:hide"),
|
||||
collapsedText: t("common:showRemaining"),
|
||||
}}
|
||||
onToggle={(isOpen) => setSelectEnabledWhenOpen(isOpen)}
|
||||
selections={value}
|
||||
onSelect={(_, selectedValue) => {
|
||||
const option = selectedValue.toString();
|
||||
let changedValue = [""];
|
||||
if (value) {
|
||||
changedValue = value.includes(option)
|
||||
? value.filter((item) => item !== option)
|
||||
: [...value, option];
|
||||
} else {
|
||||
changedValue = [option];
|
||||
}
|
||||
|
||||
onChange(changedValue);
|
||||
}}
|
||||
onClear={(selectedValues) => {
|
||||
selectedValues.stopPropagation();
|
||||
onChange([]);
|
||||
}}
|
||||
isOpen={selectEnabledWhenOpen}
|
||||
isDisabled={enabledWhenSelection === "Always"}
|
||||
aria-labelledby={"scope"}
|
||||
>
|
||||
{clientScopes?.map((option) => (
|
||||
<SelectOption key={option.name} value={option.name} />
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<Divider />
|
||||
<FormGroup
|
||||
label={t("required")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:requiredHelp"
|
||||
fieldLabelId="realm-settings:required"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-required"
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name="required"
|
||||
defaultValue={["false"]}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id={"kc-required"}
|
||||
isDisabled={false}
|
||||
onChange={(value) => onChange([`${value}`])}
|
||||
isChecked={value[0] === "true"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
/>
|
||||
)}
|
||||
></Controller>
|
||||
</FormGroup>
|
||||
<FormGroup label={t("requiredFor")} fieldId="requiredFor" hasNoPaddingTop>
|
||||
<Controller
|
||||
name="roles"
|
||||
data-testid="requiredFor"
|
||||
defaultValue={REQUIRED_FOR[0].value}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<div className="kc-requiredFor">
|
||||
{REQUIRED_FOR.map((option) => (
|
||||
<Radio
|
||||
id={option.label}
|
||||
key={option.label}
|
||||
data-testid={option}
|
||||
isChecked={value === option.value}
|
||||
name="roles"
|
||||
onChange={() => onChange(Array.isArray(option.value) ? option.value : [option.value])}
|
||||
label={option.label}
|
||||
className="kc-requiredFor-option"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("requiredWhen")}
|
||||
fieldId="requiredWhen"
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name="requiredWhen"
|
||||
data-testid="requiredWhen"
|
||||
defaultValue={ENABLED_REQUIRED_WHEN[0]}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<>
|
||||
{ENABLED_REQUIRED_WHEN.map((option) => (
|
||||
<Radio
|
||||
id={option}
|
||||
key={option}
|
||||
data-testid={option}
|
||||
isChecked={value === option}
|
||||
name="requiredWhen"
|
||||
onChange={() => {
|
||||
onChange(option);
|
||||
setRequiredWhenSelection(option);
|
||||
}}
|
||||
label={option}
|
||||
className="pf-u-mb-md"
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup fieldId="kc-scope-required-when">
|
||||
<Controller
|
||||
name="scopeRequired"
|
||||
control={form.control}
|
||||
defaultValue={[]}
|
||||
render={({
|
||||
onChange,
|
||||
value,
|
||||
}: {
|
||||
onChange: (newValue: string[]) => void;
|
||||
value: string[];
|
||||
}) => (
|
||||
<Select
|
||||
name="scopeRequired"
|
||||
data-testid="required-when-scope-field"
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
typeAheadAriaLabel="Select"
|
||||
chipGroupProps={{
|
||||
numChips: 3,
|
||||
expandedText: t("common:hide"),
|
||||
collapsedText: t("common:showRemaining"),
|
||||
}}
|
||||
onToggle={(isOpen) => setSelectRequiredForOpen(isOpen)}
|
||||
selections={value}
|
||||
onSelect={(_, selectedValue) => {
|
||||
const option = selectedValue.toString();
|
||||
let changedValue = [""];
|
||||
if (value) {
|
||||
changedValue = value.includes(option)
|
||||
? value.filter((item) => item !== option)
|
||||
: [...value, option];
|
||||
} else {
|
||||
changedValue = [option];
|
||||
}
|
||||
onChange(changedValue);
|
||||
}}
|
||||
onClear={(selectedValues) => {
|
||||
selectedValues.stopPropagation();
|
||||
onChange([]);
|
||||
}}
|
||||
isOpen={selectRequiredForOpen}
|
||||
isDisabled={requiredWhenSelection === "Always"}
|
||||
aria-labelledby={"scope"}
|
||||
>
|
||||
{clientScopes?.map((option) => (
|
||||
<SelectOption key={option.name} value={option.name} />
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormAccess>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,100 @@
|
|||
import React from "react";
|
||||
import { Checkbox, FormGroup, Grid, GridItem } from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { HelpItem } from "../../../components/help-enabler/HelpItem";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { FormAccess } from "../../../components/form-access/FormAccess";
|
||||
import "../../realm-settings-section.css";
|
||||
|
||||
const Permissions = ({ name }: { name: string }) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const { control } = useFormContext();
|
||||
return (
|
||||
<Grid>
|
||||
<GridItem lg={4} sm={6}>
|
||||
<Controller
|
||||
name={`permissions.${name}`}
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
render={({ onChange, value }) => (
|
||||
<Checkbox
|
||||
id={`user-${name}`}
|
||||
label={t("user")}
|
||||
value="user"
|
||||
data-testid={`user-${name}`}
|
||||
isChecked={value.includes("user")}
|
||||
onChange={() => {
|
||||
const option = "user";
|
||||
const changedValue = value.includes(option)
|
||||
? value.filter((item: string) => item !== option)
|
||||
: [option];
|
||||
|
||||
onChange(changedValue);
|
||||
}}
|
||||
isDisabled={value.includes("admin")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem lg={8} sm={6}>
|
||||
<Controller
|
||||
name={`permissions.${name}`}
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
render={({ onChange, value }) => (
|
||||
<Checkbox
|
||||
id={`admin-${name}`}
|
||||
label={t("admin")}
|
||||
value="admin"
|
||||
data-testid={`admin-${name}`}
|
||||
isChecked={value.includes("admin")}
|
||||
onChange={() => {
|
||||
const option = "admin";
|
||||
const changedValue = value.includes(option)
|
||||
? value.filter((item: string) => item !== option)
|
||||
: ["user", option];
|
||||
|
||||
onChange(changedValue);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const AttributePermission = () => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
|
||||
return (
|
||||
<FormAccess role="manage-realm" isHorizontal>
|
||||
<FormGroup
|
||||
hasNoPaddingTop
|
||||
label={t("whoCanEdit")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:whoCanEditHelp"
|
||||
fieldLabelId="realm-settings:whoCanEdit"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-who-can-edit"
|
||||
>
|
||||
<Permissions name="edit" />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
hasNoPaddingTop
|
||||
label={t("whoCanView")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:whoCanViewHelp"
|
||||
fieldLabelId="realm-settings:whoCanView"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-who-can-view"
|
||||
>
|
||||
<Permissions name="view" />
|
||||
</FormGroup>
|
||||
</FormAccess>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,130 @@
|
|||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
ButtonVariant,
|
||||
Divider,
|
||||
Flex,
|
||||
FlexItem,
|
||||
Text,
|
||||
TextVariants,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "../../realm-settings-section.css";
|
||||
import { PlusCircleIcon } from "@patternfly/react-icons";
|
||||
import { AddValidatorDialog } from "../attribute/AddValidatorDialog";
|
||||
import {
|
||||
TableComposable,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
} from "@patternfly/react-table";
|
||||
import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDialog";
|
||||
import type { Validator } from "./Validators";
|
||||
import useToggle from "../../../utils/useToggle";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import "../../realm-settings-section.css";
|
||||
|
||||
export const AttributeValidations = () => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const [addValidatorModalOpen, toggleModal] = useToggle();
|
||||
const [validatorToDelete, setValidatorToDelete] =
|
||||
useState<{ name: string }>();
|
||||
const { setValue, control, register } = useFormContext();
|
||||
const validators = useWatch<Validator[]>({
|
||||
name: "validations",
|
||||
control,
|
||||
defaultValue: [],
|
||||
});
|
||||
|
||||
useEffect(() => register("validations"), []);
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: t("deleteValidatorConfirmTitle"),
|
||||
messageKey: t("deleteValidatorConfirmMsg", {
|
||||
validatorName: validatorToDelete?.name!,
|
||||
}),
|
||||
continueButtonLabel: "common:delete",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
console.log("TODO");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{addValidatorModalOpen && (
|
||||
<AddValidatorDialog
|
||||
onConfirm={(newValidator) => {
|
||||
setValue("validations", [...validators, newValidator]);
|
||||
}}
|
||||
toggleDialog={toggleModal}
|
||||
/>
|
||||
)}
|
||||
<DeleteConfirm />
|
||||
<div className="kc-attributes-validations">
|
||||
<Flex>
|
||||
<FlexItem align={{ default: "alignRight" }}>
|
||||
<Button
|
||||
id="addValidator"
|
||||
onClick={() => toggleModal()}
|
||||
variant="link"
|
||||
className="kc-addValidator"
|
||||
data-testid="addValidator"
|
||||
icon={<PlusCircleIcon />}
|
||||
>
|
||||
{t("realm-settings:addValidator")}
|
||||
</Button>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
<Divider />
|
||||
<TableComposable aria-label="validators-table">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t("validatorColNames.colName")}</Th>
|
||||
<Th>{t("validatorColNames.colConfig")}</Th>
|
||||
<Th />
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{validators.length ? (
|
||||
validators.map((validator: Validator) => (
|
||||
<Tr key={validator.name}>
|
||||
<Td dataLabel={t("validatorColNames.colName")}>
|
||||
{validator.name}
|
||||
</Td>
|
||||
<Td dataLabel={t("validatorColNames.colConfig")}>
|
||||
{JSON.stringify(validator.config)}
|
||||
</Td>
|
||||
<Td>
|
||||
<Button
|
||||
key="validator"
|
||||
variant="link"
|
||||
data-testid="deleteValidator"
|
||||
isDisabled={true}
|
||||
onClick={() => {
|
||||
toggleDeleteDialog();
|
||||
setValidatorToDelete({
|
||||
name: validator.name,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("common:delete")}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))
|
||||
) : (
|
||||
<Text className="kc-emptyValidators" component={TextVariants.h6}>
|
||||
{t("realm-settings:emptyValidators")}
|
||||
</Text>
|
||||
)}
|
||||
</Tbody>
|
||||
</TableComposable>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
171
src/realm-settings/user-profile/attribute/Validators.ts
Normal file
171
src/realm-settings/user-profile/attribute/Validators.ts
Normal file
|
@ -0,0 +1,171 @@
|
|||
export type Validator = {
|
||||
name: string;
|
||||
description?: string;
|
||||
config?: ValidatorConfig[];
|
||||
};
|
||||
|
||||
export type ValidatorConfig = {
|
||||
name?: string;
|
||||
label?: string;
|
||||
helpText?: string;
|
||||
type?: string;
|
||||
defaultValue?: any;
|
||||
options?: string[];
|
||||
secret?: boolean;
|
||||
};
|
||||
|
||||
export const validators: Validator[] = [
|
||||
{
|
||||
name: "double",
|
||||
description:
|
||||
"Check if the value is a double and within a lower and/or upper range. If no range is defined, the validator only checks whether the value is a valid number.",
|
||||
config: [
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText: "The minimal allowed value - this config is optional.",
|
||||
label: "Minimum",
|
||||
name: "min",
|
||||
},
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText: "The maximal allowed value - this config is optional.",
|
||||
label: "Maximum",
|
||||
name: "max",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
description: "Check if the value has a valid e-mail format.",
|
||||
config: [],
|
||||
},
|
||||
{
|
||||
name: "integer",
|
||||
description:
|
||||
"Check if the value is an integer and within a lower and/or upper range. If no range is defined, the validator only checks whether the value is a valid number.",
|
||||
config: [
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText: "The minimal allowed value - this config is optional.",
|
||||
label: "Minimum",
|
||||
name: "min",
|
||||
},
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText: "The maximal allowed value - this config is optional.",
|
||||
label: "Maximum",
|
||||
name: "max",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "length",
|
||||
description:
|
||||
"Check the length of a string value based on a minimum and maximum length.",
|
||||
config: [
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText: "The minimum length",
|
||||
label: "Minimum length",
|
||||
name: "min",
|
||||
},
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText: "The maximum length",
|
||||
label: "Maximum length",
|
||||
name: "max",
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
defaultValue: false,
|
||||
helpText:
|
||||
"Disable trimming of the String value before the length check",
|
||||
label: "Trimming disabled",
|
||||
name: "trim-disabled",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "local-date",
|
||||
description:
|
||||
"Check if the value has a valid format based on the realm and/or user locale.",
|
||||
config: [],
|
||||
},
|
||||
{
|
||||
name: "options",
|
||||
description:
|
||||
"Check if the value is from the defined set of allowed values. Useful to validate values entered through select and multiselect fields.",
|
||||
config: [
|
||||
{
|
||||
type: "MultivaluedString",
|
||||
defaultValue: "",
|
||||
helpText: "List of allowed options",
|
||||
label: "Options",
|
||||
name: "options",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "pattern",
|
||||
description: "Check if the value matches a specific RegEx pattern.",
|
||||
config: [
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText:
|
||||
"RegExp pattern the value must match. Java Pattern syntax is used.",
|
||||
label: "RegExp pattern",
|
||||
name: "pattern",
|
||||
},
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText:
|
||||
"Key of the error message in i18n bundle. Dafault message key is error-pattern-no-match",
|
||||
label: "Error message key",
|
||||
name: "error-message",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "person-name-prohibited-characters",
|
||||
description:
|
||||
"Check if the value is a valid person name as an additional barrier for attacks such as script injection. The validation is based on a default RegEx pattern that blocks characters not common in person names.",
|
||||
config: [
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText:
|
||||
"Key of the error message in i18n bundle. Dafault message key is error-person-name-invalid-character",
|
||||
label: "Error message key",
|
||||
name: "error-message",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "uri",
|
||||
description: "Check if the value is a valid URI.",
|
||||
config: [],
|
||||
},
|
||||
{
|
||||
name: "username-prohibited-characters",
|
||||
description:
|
||||
"Check if the value is a valid username as an additional barrier for attacks such as script injection. The validation is based on a default RegEx pattern that blocks characters not common in usernames.",
|
||||
config: [
|
||||
{
|
||||
type: "String",
|
||||
defaultValue: "",
|
||||
helpText:
|
||||
"Key of the error message in i18n bundle. Dafault message key is error-username-invalid-character",
|
||||
label: "Error message key",
|
||||
name: "error-message",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
Loading…
Reference in a new issue