mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-24 16:05:00 +08:00
Adding the CPA LDAP/SAML Modal (#31195)
* Adding the CPA LDAP/SAML Modal * Fixing lint * updating lint and separating file * Updating lint * updating lint * updating lint * updating css * Updating ldap modal * Updating translation order * Updating modal and other tests * updating test * updating * use hook in values cell * sync chips a11y --------- Co-authored-by: Caleb Roseland <caleb@calebroseland.com> Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
co-authored by
Caleb Roseland
Mattermost Build
parent
257eec43ed
commit
990bdb7ee2
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {GenericModal} from '@mattermost/components';
|
||||
|
||||
import QuickInput, {MaxLengthInput} from 'components/quick_input';
|
||||
|
||||
const MAX_LDAP_LENGTH = 64;
|
||||
|
||||
type Props = {
|
||||
initialValue: string;
|
||||
fieldType: string;
|
||||
onExited: () => void;
|
||||
onSave: (value: string) => Promise<void>;
|
||||
error: string | null;
|
||||
helpText: React.ReactNode;
|
||||
modalHeaderText: JSX.Element;
|
||||
};
|
||||
|
||||
const AttributeModal = ({
|
||||
initialValue,
|
||||
fieldType,
|
||||
onExited,
|
||||
onSave,
|
||||
error,
|
||||
helpText,
|
||||
modalHeaderText,
|
||||
}: Props) => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const handleClear = () => setValue('');
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value);
|
||||
const handleCancel = () => onExited();
|
||||
const handleConfirm = () => onSave(value);
|
||||
const isConfirmDisabled = () => value.length > MAX_LDAP_LENGTH;
|
||||
|
||||
const showTypeWarning = fieldType !== 'text';
|
||||
|
||||
return (
|
||||
<GenericModal
|
||||
id='attributeModal'
|
||||
modalHeaderText={modalHeaderText}
|
||||
confirmButtonText={
|
||||
<FormattedMessage
|
||||
id='save'
|
||||
defaultMessage='Save'
|
||||
/>
|
||||
}
|
||||
compassDesign={true}
|
||||
onExited={onExited}
|
||||
handleEnterKeyPress={handleConfirm}
|
||||
handleConfirm={handleConfirm}
|
||||
handleCancel={handleCancel}
|
||||
isConfirmDisabled={isConfirmDisabled()}
|
||||
>
|
||||
<QuickInput
|
||||
size='lg'
|
||||
inputComponent={MaxLengthInput}
|
||||
autoFocus={true}
|
||||
className='form-control filter-textbox'
|
||||
placeholder={'department'}
|
||||
type='text'
|
||||
value={value}
|
||||
clearable={true}
|
||||
onClear={handleClear}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<span className='help-text'>
|
||||
{helpText}
|
||||
</span>
|
||||
{showTypeWarning && (
|
||||
<div
|
||||
className='alert alert-warning'
|
||||
style={{marginTop: '12px'}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.customProfileAttribWarning'
|
||||
defaultMessage='(Warning) This attribute will be converted to a TEXT attribute, if the field is set to synchronize.'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{error && <div className='error-text'>{error}</div>}
|
||||
</GenericModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttributeModal;
|
||||
+6
-8
@@ -139,7 +139,7 @@ describe('UserPropertyDotMenu', () => {
|
||||
expect(screen.queryByText('Link attribute to SAML')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Edit link with" text when LDAP attribute is linked', async () => {
|
||||
it('shows "Edit LDAP link" text when LDAP attribute is linked', async () => {
|
||||
const linkedField = {
|
||||
...baseField,
|
||||
attrs: {
|
||||
@@ -154,12 +154,11 @@ describe('UserPropertyDotMenu', () => {
|
||||
const menuButton = screen.getByTestId(`user-property-field_dotmenu-${linkedField.id}`);
|
||||
fireEvent.click(menuButton);
|
||||
|
||||
// Verify the LDAP link text shows the linked property
|
||||
expect(screen.getByText('Edit link with:')).toBeInTheDocument();
|
||||
expect(screen.getByText('AD/LDAP: employeeID')).toBeInTheDocument();
|
||||
// Verify the LDAP link text shows the edit option
|
||||
expect(screen.getByText('Edit LDAP link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Edit link with" text when SAML attribute is linked', async () => {
|
||||
it('shows "Edit SAML link" text when SAML attribute is linked', async () => {
|
||||
const linkedField = {
|
||||
...baseField,
|
||||
attrs: {
|
||||
@@ -174,9 +173,8 @@ describe('UserPropertyDotMenu', () => {
|
||||
const menuButton = screen.getByTestId(`user-property-field_dotmenu-${linkedField.id}`);
|
||||
fireEvent.click(menuButton);
|
||||
|
||||
// Verify the SAML link text shows the linked property
|
||||
expect(screen.getByText('Edit link with:')).toBeInTheDocument();
|
||||
expect(screen.getByText('SAML: position')).toBeInTheDocument();
|
||||
// Verify the SAML link text shows the edit option
|
||||
expect(screen.getByText('Edit SAML link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles field duplication', async () => {
|
||||
|
||||
+93
-27
@@ -1,19 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ComponentProps} from 'react';
|
||||
import React from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import {CheckIcon, ChevronRightIcon, DotsHorizontalIcon, EyeOutlineIcon, SyncIcon, TrashCanOutlineIcon, ContentCopyIcon} from '@mattermost/compass-icons/components';
|
||||
import type {FieldVisibility, UserPropertyField} from '@mattermost/types/properties';
|
||||
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
import * as Menu from 'components/menu';
|
||||
|
||||
import './user_properties_dot_menu.scss';
|
||||
import {ModalIdentifiers} from 'utils/constants';
|
||||
|
||||
import AttributeModal from './attribute_modal';
|
||||
import {useUserPropertyFieldDelete} from './user_properties_delete_modal';
|
||||
import {isCreatePending} from './user_properties_utils';
|
||||
|
||||
import './user_properties_dot_menu.scss';
|
||||
|
||||
type Props = {
|
||||
field: UserPropertyField;
|
||||
canCreate: boolean;
|
||||
@@ -22,6 +28,82 @@ type Props = {
|
||||
deleteField: (id: string) => void;
|
||||
}
|
||||
|
||||
export const useAttributeLinkModal = (field: UserPropertyField, updateField: Props['updateField']) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const promptEditLdapLink = () => {
|
||||
dispatch(openModal({
|
||||
modalId: ModalIdentifiers.ATTRIBUTE_MODAL_LDAP,
|
||||
dialogType: AttributeModal,
|
||||
dialogProps: {
|
||||
initialValue: field.attrs.ldap || '',
|
||||
fieldType: field.type,
|
||||
onExited: () => {},
|
||||
onSave: async (newValue: string) => {
|
||||
updateField({
|
||||
...field,
|
||||
type: 'text',
|
||||
attrs: {
|
||||
...field.attrs,
|
||||
ldap: newValue,
|
||||
},
|
||||
});
|
||||
},
|
||||
error: null,
|
||||
helpText: (
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.dotmenu.ad_ldap.modal.helpText'
|
||||
defaultMessage="The attribute in the AD/LDAP server used to sync as a custom attribute in user's profile in Mattermost."
|
||||
/>
|
||||
),
|
||||
modalHeaderText: (
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.dotmenu.ad_ldap.link_property.label'
|
||||
defaultMessage='Link attribute to AD/LDAP'
|
||||
/>
|
||||
),
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const promptEditSamlLink = () => {
|
||||
dispatch(openModal({
|
||||
modalId: ModalIdentifiers.ATTRIBUTE_MODAL_SAML,
|
||||
dialogType: AttributeModal,
|
||||
dialogProps: {
|
||||
initialValue: field.attrs.saml || '',
|
||||
fieldType: field.type,
|
||||
onExited: () => {},
|
||||
onSave: async (newValue: string) => {
|
||||
updateField({
|
||||
...field,
|
||||
type: 'text',
|
||||
attrs: {
|
||||
...field.attrs,
|
||||
saml: newValue,
|
||||
},
|
||||
});
|
||||
},
|
||||
error: null,
|
||||
helpText: (
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.dotmenu.saml.modal.helpText'
|
||||
defaultMessage="The attribute in the SAML server used to sync as a custom attribute in user's profile in Mattermost."
|
||||
/>
|
||||
),
|
||||
modalHeaderText: (
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.dotmenu.saml.modal.title'
|
||||
defaultMessage='Link attribute to SAML'
|
||||
/>
|
||||
),
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return {promptEditLdapLink, promptEditSamlLink};
|
||||
};
|
||||
|
||||
const menuId = 'user-property-field_dotmenu';
|
||||
|
||||
const DotMenu = ({
|
||||
@@ -33,6 +115,7 @@ const DotMenu = ({
|
||||
}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const {promptDelete} = useUserPropertyFieldDelete();
|
||||
const {promptEditLdapLink, promptEditSamlLink} = useAttributeLinkModal(field, updateField);
|
||||
|
||||
const handleDuplicate = () => {
|
||||
const name = formatMessage({
|
||||
@@ -177,45 +260,37 @@ const DotMenu = ({
|
||||
/>
|
||||
</Menu.SubMenu>
|
||||
{field.create_at !== 0 && ([
|
||||
<Menu.LinkItem
|
||||
<Menu.Item
|
||||
key={`${menuId}_link_ad-ldap`}
|
||||
id={`${menuId}_link_ad-ldap`}
|
||||
to={`/admin_console/authentication/ldap#custom_profile_attribute-${field.name}`}
|
||||
leadingElement={<SyncIcon size={18}/>}
|
||||
onClick={() => promptEditLdapLink()}
|
||||
labels={field.attrs.ldap ? (
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.dotmenu.ad_ldap.edit_link.label'
|
||||
defaultMessage={'Edit link with: <Chip>AD/LDAP: {propertyName}</Chip>'}
|
||||
values={{
|
||||
Chip: (chunks: React.ReactNode) => <Chip>{chunks}</Chip>,
|
||||
propertyName: field.attrs.ldap,
|
||||
}}
|
||||
defaultMessage='Edit LDAP link'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.dotmenu.ad_ldap.link_property.label'
|
||||
defaultMessage={'Link attribute to AD/LDAP'}
|
||||
defaultMessage='Link attribute to AD/LDAP'
|
||||
/>
|
||||
)}
|
||||
/>,
|
||||
<Menu.LinkItem
|
||||
<Menu.Item
|
||||
key={`${menuId}_link_saml`}
|
||||
id={`${menuId}_link_saml`}
|
||||
to={`/admin_console/authentication/saml#custom_profile_attribute-${field.name}`}
|
||||
leadingElement={<SyncIcon size={18}/>}
|
||||
onClick={() => promptEditSamlLink()}
|
||||
labels={field.attrs.saml ? (
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.dotmenu.saml.edit_link.label'
|
||||
defaultMessage={'Edit link with: <Chip>SAML: {propertyName}</Chip>'}
|
||||
values={{
|
||||
Chip: (chunks: React.ReactNode) => <Chip>{chunks}</Chip>,
|
||||
propertyName: field.attrs.saml,
|
||||
}}
|
||||
defaultMessage='Edit SAML link'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.dotmenu.saml.link_property.label'
|
||||
defaultMessage={'Link attribute to SAML'}
|
||||
defaultMessage='Link attribute to SAML'
|
||||
/>
|
||||
)}
|
||||
/>,
|
||||
@@ -250,13 +325,4 @@ const DotMenu = ({
|
||||
);
|
||||
};
|
||||
|
||||
const Chip = ({children, ...rest}: ComponentProps<'span'>) => (
|
||||
<span
|
||||
className='user-property-field-dotmenu__chip'
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
export default DotMenu;
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
.user-property-field-values {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.75);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
+4
-6
@@ -160,10 +160,9 @@ describe('UserPropertyValues', () => {
|
||||
const ldapLink = screen.getByText('AD/LDAP: ldapAttribute');
|
||||
expect(ldapLink).toBeInTheDocument();
|
||||
|
||||
// Check that the link points to the correct location
|
||||
// Check that the clickable element is present (no longer checking href)
|
||||
const linkElement = screen.getByTestId(`user-property-field-values__ldap-${ldapField.name}`);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
expect(linkElement).toHaveAttribute('href', `/admin_console/authentication/ldap#custom_profile_attribute-${baseField.name}`);
|
||||
});
|
||||
|
||||
it('shows SAML sync information when field has SAML attribute', () => {
|
||||
@@ -182,10 +181,9 @@ describe('UserPropertyValues', () => {
|
||||
const samlLink = screen.getByText('SAML: samlAttribute');
|
||||
expect(samlLink).toBeInTheDocument();
|
||||
|
||||
// Check that the link points to the correct location
|
||||
// Check that the clickable element is present (no longer checking href)
|
||||
const linkElement = screen.getByTestId(`user-property-field-values__saml-${samlField.name}`);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
expect(linkElement).toHaveAttribute('href', `/admin_console/authentication/saml#custom_profile_attribute-${baseField.name}`);
|
||||
});
|
||||
|
||||
it('shows both LDAP and SAML sync information when field has both attributes', () => {
|
||||
@@ -209,11 +207,11 @@ describe('UserPropertyValues', () => {
|
||||
const samlLink = screen.getByText('SAML: samlAttribute');
|
||||
expect(samlLink).toBeInTheDocument();
|
||||
|
||||
// Check that both links point to the correct locations
|
||||
// Check that both clickable elements are present (no longer checking href)
|
||||
const ldapLinkElement = screen.getByTestId(`user-property-field-values__ldap-${baseField.name}`);
|
||||
expect(ldapLinkElement).toBeInTheDocument();
|
||||
|
||||
const samlLinkElement = screen.getByTestId(`user-property-field-values__ldap-${baseField.name}`);
|
||||
const samlLinkElement = screen.getByTestId(`user-property-field-values__saml-${baseField.name}`);
|
||||
expect(samlLinkElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+36
-18
@@ -13,11 +13,11 @@ import {SyncIcon} from '@mattermost/compass-icons/components';
|
||||
import type {PropertyFieldOption, UserPropertyField} from '@mattermost/types/properties';
|
||||
|
||||
import Constants from 'utils/constants';
|
||||
import {isKeyPressed} from 'utils/keyboard';
|
||||
|
||||
import {DangerText} from './controls';
|
||||
|
||||
import './user_properties_values.scss';
|
||||
import BlockableLink from '../blockable_link';
|
||||
import {useAttributeLinkModal} from './user_properties_dot_menu';
|
||||
|
||||
type Props = {
|
||||
field: UserPropertyField;
|
||||
@@ -34,6 +34,8 @@ const UserPropertyValues = ({
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const [query, setQuery] = React.useState('');
|
||||
const {promptEditLdapLink, promptEditSamlLink} = useAttributeLinkModal(field, updateField);
|
||||
|
||||
const isQueryValid = useMemo(() => !checkForDuplicates(field.attrs.options, query.trim()), [field?.attrs?.options, query]);
|
||||
|
||||
const addOption = (name: string) => {
|
||||
@@ -80,53 +82,69 @@ const UserPropertyValues = ({
|
||||
const syncedProperties = [
|
||||
|
||||
field.attrs.ldap && (
|
||||
<BlockableLink
|
||||
<a
|
||||
className='user-property-field-values__chip-link'
|
||||
to={`/admin_console/authentication/ldap#custom_profile_attribute-${field.name}`}
|
||||
key={`${field.name}-ldap`}
|
||||
data-testid={`user-property-field-values__ldap-${field.name}`}
|
||||
onClick={() => promptEditLdapLink()}
|
||||
onKeyDown={(e) => {
|
||||
if (isKeyPressed(e, Constants.KeyCodes.ENTER) || isKeyPressed(e, Constants.KeyCodes.SPACE)) {
|
||||
promptEditLdapLink();
|
||||
}
|
||||
}}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.table.values.synced_with.ldap'
|
||||
defaultMessage='AD/LDAP: {propertyName}'
|
||||
values={{propertyName: field.attrs.ldap}}
|
||||
/>
|
||||
</BlockableLink>
|
||||
</a>
|
||||
),
|
||||
field.attrs.saml && (
|
||||
<BlockableLink
|
||||
<a
|
||||
className='user-property-field-values__chip-link'
|
||||
to={`/admin_console/authentication/saml#custom_profile_attribute-${field.name}`}
|
||||
key={`${field.name}-saml`}
|
||||
data-testid={`user-property-field-values__saml-${field.name}`}
|
||||
onClick={() => promptEditSamlLink()}
|
||||
onKeyDown={(e) => {
|
||||
if (isKeyPressed(e, Constants.KeyCodes.ENTER) || isKeyPressed(e, Constants.KeyCodes.SPACE)) {
|
||||
promptEditSamlLink();
|
||||
}
|
||||
}}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.table.values.synced_with.saml'
|
||||
defaultMessage='SAML: {propertyName}'
|
||||
values={{propertyName: field.attrs.saml}}
|
||||
/>
|
||||
</BlockableLink>
|
||||
</a>
|
||||
),
|
||||
|
||||
].filter(Boolean);
|
||||
|
||||
return (
|
||||
<span className='user-property-field-values'>
|
||||
<SyncIcon size={18}/>
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.table.values.synced_with'
|
||||
defaultMessage='Synced with: {syncedProperties}'
|
||||
values={{syncedProperties: <FormattedList value={syncedProperties}/>}}
|
||||
/>
|
||||
</span>
|
||||
<>
|
||||
<span className='user-property-field-values'>
|
||||
<SyncIcon size={18}/>
|
||||
<FormattedMessage
|
||||
id='admin.system_properties.user_properties.table.values.synced_with'
|
||||
defaultMessage='Synced with: {syncedProperties}'
|
||||
values={{syncedProperties: <FormattedList value={syncedProperties}/>}}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type !== 'multiselect' && field.type !== 'select') {
|
||||
return (
|
||||
<>
|
||||
<span className='user-property-field-values'>
|
||||
{'-'}
|
||||
</>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ export type Props = {
|
||||
id?: string;
|
||||
onInput?: (e?: React.FormEvent<HTMLInputElement>) => void;
|
||||
tabIndex?: number;
|
||||
size?: 'md' | 'lg';
|
||||
role?: string;
|
||||
}
|
||||
|
||||
@@ -102,6 +103,8 @@ export const QuickInput = React.memo(({
|
||||
clearableWithoutValue,
|
||||
clearableTooltipText,
|
||||
onClear: onClearFromProps,
|
||||
className,
|
||||
size = 'md',
|
||||
...restProps
|
||||
}: Props) => {
|
||||
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
|
||||
@@ -169,6 +172,9 @@ export const QuickInput = React.memo(({
|
||||
...restProps,
|
||||
ref: setInputRef,
|
||||
defaultValue: value, // Only set the defaultValue since the real one will be updated using the 'useEffect' above
|
||||
className: classNames(className, {
|
||||
'form-control--lg': size === 'lg',
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -2833,13 +2833,16 @@
|
||||
"admin.system_properties.details.saving_changes": "Saving configuration…",
|
||||
"admin.system_properties.details.saving_changes_error": "There was an error while saving the configuration",
|
||||
"admin.system_properties.user_properties.add_property": "Add attribute",
|
||||
"admin.system_properties.user_properties.dotmenu.ad_ldap.edit_link.label": "Edit link with: <Chip>AD/LDAP: {propertyName}</Chip>",
|
||||
"admin.system_properties.user_properties.dotmenu.ad_ldap.edit_link.label": "Edit LDAP link",
|
||||
"admin.system_properties.user_properties.dotmenu.ad_ldap.link_property.label": "Link attribute to AD/LDAP",
|
||||
"admin.system_properties.user_properties.dotmenu.ad_ldap.modal.helpText": "The attribute in the AD/LDAP server used to sync as a custom attribute in user's profile in Mattermost.",
|
||||
"admin.system_properties.user_properties.dotmenu.delete.label": "Delete attribute",
|
||||
"admin.system_properties.user_properties.dotmenu.duplicate.label": "Duplicate attribute",
|
||||
"admin.system_properties.user_properties.dotmenu.duplicate.name_copy": "{fieldName} (copy)",
|
||||
"admin.system_properties.user_properties.dotmenu.saml.edit_link.label": "Edit link with: <Chip>SAML: {propertyName}</Chip>",
|
||||
"admin.system_properties.user_properties.dotmenu.saml.link_property.label": "Link property to SAML",
|
||||
"admin.system_properties.user_properties.dotmenu.saml.edit_link.label": "Edit SAML link",
|
||||
"admin.system_properties.user_properties.dotmenu.saml.link_property.label": "Link attribute to SAML",
|
||||
"admin.system_properties.user_properties.dotmenu.saml.modal.helpText": "The attribute in the SAML server used to sync as a custom attribute in user's profile in Mattermost.",
|
||||
"admin.system_properties.user_properties.dotmenu.saml.modal.title": "Link attribute to SAML",
|
||||
"admin.system_properties.user_properties.dotmenu.visibility.always.label": "Always show",
|
||||
"admin.system_properties.user_properties.dotmenu.visibility.hidden.label": "Always hide",
|
||||
"admin.system_properties.user_properties.dotmenu.visibility.label": "Visibility",
|
||||
@@ -5246,6 +5249,7 @@
|
||||
"rhs_root.mobile.unflag": "Remove from Saved",
|
||||
"rhs_thread.rootPostDeletedMessage.body": "Part of this thread has been deleted due to a data retention policy. You can no longer reply to this thread.",
|
||||
"rhs_thread.toast.newReplies": "New Replies",
|
||||
"save": "Save",
|
||||
"save_button.save": "Save",
|
||||
"save_button.saving": "Saving",
|
||||
"saveChangesPanel.cancel": "Undo",
|
||||
|
||||
@@ -479,6 +479,8 @@ export const ModalIdentifiers = {
|
||||
SHARED_CHANNEL_REMOTE_UNINVITE: 'shared_channel_remote_uninvite',
|
||||
CONFIRM_RESET_FAILED_ATTEMPTS_MODAL: 'confirm_reset_failed_attempts_modal',
|
||||
USER_PROPERTY_FIELD_DELETE: 'user_property_field_delete',
|
||||
ATTRIBUTE_MODAL_LDAP: 'attribute_modal_ldap',
|
||||
ATTRIBUTE_MODAL_SAML: 'attribute_modal_saml',
|
||||
};
|
||||
|
||||
export const UserStatuses = {
|
||||
|
||||
@@ -51,8 +51,21 @@
|
||||
border: var(--border-default);
|
||||
border-radius: 4px;
|
||||
|
||||
&.form-control--lg {
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
|
||||
& + .input-clear {
|
||||
top: 17px;
|
||||
right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--button-bg);
|
||||
box-shadow: 0 0 0 1px var(--button-bg);
|
||||
}
|
||||
|
||||
&.has-error {
|
||||
|
||||
Reference in New Issue
Block a user