UNPKG

passbolt-styleguide

Version:

Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.

757 lines (715 loc) 34.6 kB
/** * Passbolt ~ Open source password manager for teams * Copyright (c) Passbolt SA (https://www.passbolt.com) * * Licensed under GNU Affero General Public License version 3 of the or any later version. * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com) * @license https://opensource.org/licenses/AGPL-3.0 AGPL License * @link https://www.passbolt.com Passbolt(tm) * @since 4.11.0 */ import PropTypes from "prop-types"; import React, { Component } from "react"; import { Trans, withTranslation } from "react-i18next"; import memoize from "memoize-one"; import { withAppContext } from "../../../../shared/context/AppContext/AppContext"; import MetadataSettingsServiceWorkerService from "../../../../shared/services/serviceWorker/metadata/metadataSettingsServiceWorkerService"; import NotifyError from "../../Common/Error/NotifyError/NotifyError"; import { withDialog } from "../../../contexts/DialogContext"; import { withActionFeedback } from "../../../contexts/ActionFeedbackContext"; import ResourceTypesFormEntity from "../../../../shared/models/entity/resourceType/resourceTypesFormEntity"; import ResourceTypesServiceWorkerService from "../../../../shared/services/serviceWorker/resourceTypes/resourceTypesServiceWorkerService"; import MetadataKeysServiceWorkerService from "../../../../shared/services/serviceWorker/metadata/metadataKeysServiceWorkerService"; import Tooltip from "../../Common/Tooltip/Tooltip"; import { createSafePortal } from "../../../../shared/utils/portals"; import InfoSVG from "../../../../img/svg/info.svg"; import KeySVG from "../../../../img/svg/key.svg"; import TotpSVG from "../../../../img/svg/totp.svg"; import TablePropertiesSVG from "../../../../img/svg/table_properties.svg"; import NoteSVG from "../../../../img/svg/notes.svg"; class DisplayContentTypesAllowedContentTypesAdministration extends Component { /** @type {ResourceTypesFormEntity}*/ originalSettings = null; /** @type {ResourceTypesFormEntity} */ formSettings = null; /** @type {MetadataTypesSettingsEntity} */ metadataTypesSettings = undefined; /** @type {MetadataKeysCollection} */ metadataKeys = undefined; /** * Default constructor */ constructor(props) { super(props); this.resourceTypesServiceWorkerService = props.resourceTypesServiceWorkerService ?? new ResourceTypesServiceWorkerService(props.context.port); this.metadataSettingsServiceWorkerService = props.metadataSettingsServiceWorkerService ?? new MetadataSettingsServiceWorkerService(props.context.port); this.metadataKeysServiceWorkerService = props.metadataKeysServiceWorkerService ?? new MetadataKeysServiceWorkerService(props.context.port); this.state = this.defaultState; this.bindCallbacks(); } /** * Get default state * @returns {Object} */ get defaultState() { return { isProcessing: true, // Is the form processing (loading, submitting). hasAlreadyBeenValidated: false, // True if the form has already been submitted once. settings: { password_v4: false, password_v5: false, totp_v4: false, totp_v5: false, note_v5: false, password_v4_count: 0, password_v5_count: 0, totp_v4_count: 0, totp_v5_count: 0, note_v5_count: 0, has_password_v4: false, has_totp_v4: false, has_password_v5: false, has_totp_v5: false, has_v4_resource_types: false, has_v5_resource_types: false, }, }; } /** * Bind callbacks methods */ bindCallbacks() { this.handleFormSubmit = this.handleFormSubmit.bind(this); this.handleInputChange = this.handleInputChange.bind(this); this.save = this.save.bind(this); } /** * ComponentDidMount * Invoked immediately after component is inserted into the tree * @return {void} */ async componentDidMount() { const resourceTypes = await this.resourceTypesServiceWorkerService.findAllByDeletedAndNonDeleted(); this.metadataTypesSettings = await this.metadataSettingsServiceWorkerService.findTypesSettings(); this.metadataKeys = await this.metadataKeysServiceWorkerService.findAll(); this.originalSettings = ResourceTypesFormEntity.createFormResourcesTypesCollection(resourceTypes); this.formSettings = new ResourceTypesFormEntity(this.originalSettings.toFormDto(), { validate: false }); this.setState({ settings: this.formSettings.toFormDto(), isProcessing: false, }); } /** * Validate form. * @param {object} formSetingsDto The form settings dto store in state, not used but required to ensure the memoized * function is only triggered when the form is updated. * @param {ResourceTypesCollection} resourceTypes The resource types. * @return {EntityValidationError} */ // eslint-disable-next-line no-unused-vars validateForm = memoize((formSettingsDto) => this.formSettings?.validate()); /** * Verify the data health. This intends for administrators, helping them adjust settings to prevent unusual or * problematic situations. By instance enabling a metadata types without active related content types. * @param {object} formSetingsDto The form settings dto store in state, not used but required to ensure the memoized * function is only triggered when the form is updated. * @param {ResourceTypesCollection} resourceTypes The resource types. * @return {EntityValidationError} */ verifyDataHealth = memoize((formSettingsDto, metadataTypeSettings, metadataKeys) => this.formSettings?.verifyHealth(metadataTypeSettings, metadataKeys), ); /** * Check if the data have been changed. * @param {object} formSetingsDto The form settings dto store in state, not used but required to ensure the memoized * function is only triggered when the form is updated. * @param {MetadataTypesSettingsFormEntity} originalSettings The metadata settings as originally provided by the API. * @param {MetadataTypesSettingsFormEntity} formSettings The metadata settings updated by the user. * @param {object} formSetingsDto The form settings dto store in state, not used but required to ensure the memoized * function is only triggered when the form is updated. * @param {ResourceTypesCollection} resourceTypes The resource types. * @return {EntityValidationError} */ // eslint-disable-next-line no-unused-vars hasSettingsChanges = memoize((originalSettings, formSettings, formSettingsDto) => this.originalSettings?.hasDiffProps(this.formSettings), ); /** * Handle form input changes. * @params {ReactEvent} The react event * @returns {void} */ handleInputChange(event) { if (this.hasAllInputDisabled()) { return; } const { type, checked, value, name } = event.target; const parsedValue = type === "checkbox" ? checked : value; this.setFormPropertyValue(name, parsedValue); } /** * Set a form property value. Trigger the validation if the form has already been submitted once. * @param name * @param parsedValue */ setFormPropertyValue(name, parsedValue) { this.formSettings.set(name, parsedValue, { validate: false }); this.setState({ settings: this.formSettings.toFormDto() }); } /** * Should input be disabled? True if state is loading or processing * @returns {boolean} */ hasAllInputDisabled() { return this.state.isProcessing; } /** * Handle form submission that can be trigger when hitting `enter` * @param {Event} event The html event triggering the form submit. */ handleFormSubmit(event) { // Avoid the form to be submitted natively by the browser and avoid a redirect to a broken page. event.preventDefault(); this.save(); } /** * Returns true if the checkbox is disabled. * It is disabled if: * - the form is processing * - or there are resources and the box is unchecked (to allow admins to check it back) * @param {integer} resourceCount * @param {boolean} checkboxState * @returns {boolean} */ isInputDisabled(resourceCount, isCheckboxChecked) { return this.hasAllInputDisabled() || (resourceCount > 0 && isCheckboxChecked); } /** * Save the settings * @returns {Promise<void>} */ async save() { if (this.state.isProcessing) { return; } const validationError = this.validateForm(this.state.settings); if (validationError?.hasErrors()) { this.setState({ hasAlreadyBeenValidated: true }); return; } this.setState({ isProcessing: true }); try { const resourceTypesToUpdate = this.formSettings.toResourceTypesCollection(); await this.resourceTypesServiceWorkerService.updateAllDeletedStatus(resourceTypesToUpdate); const resourceTypes = await this.resourceTypesServiceWorkerService.findAllByDeletedAndNonDeleted(); this.originalSettings = ResourceTypesFormEntity.createFormResourcesTypesCollection(resourceTypes); this.formSettings = new ResourceTypesFormEntity(this.originalSettings.toFormDto()); await this.props.actionFeedbackContext.displaySuccess(this.props.t("The allowed content types were updated.")); } catch (error) { console.error(error); this.props.dialogContext.open(NotifyError, { error }); } this.setState({ hasAlreadyBeenValidated: true, isProcessing: false, settings: this.formSettings.toFormDto(), }); } /** * Display the content surrounded or not with a toolti. * @param {ReactDOM} content content to display * @param {boolean} isDisabled if disabled the tooltip is added * @returns {ReactDOM} */ addTooltipOnDisabledElement(content, isDisabled) { return isDisabled ? ( <Tooltip message="You cannot disable a content type that is in use." direction="right"> {content} </Tooltip> ) : ( <>{content}</> ); } /** * Render the component * @returns {JSX} */ render() { const errors = this.state.hasAlreadyBeenValidated ? this.validateForm(this.state.settings) : null; const warnings = this.verifyDataHealth(this.state.settings, this.metadataTypesSettings, this.metadataKeys); const hasSettingsChanges = this.hasSettingsChanges(this.originalSettings, this.formSettings, this.state.settings); const isFeatureBeta = this.props.context.siteSettings.isFeatureBeta("metadata"); const shouldDisplayAWarningBlock = isFeatureBeta || hasSettingsChanges; return ( <div className="row"> <div id="allow-content-types" className="main-column"> <div className="main-content"> <form onSubmit={this.handleFormSubmit} data-testid="submit-form"> <h3 className="title"> <label> <Trans>Allow content types</Trans> </label> </h3> {this.state.settings.has_v5_resource_types && ( <> <h4 className="no-border"> <Trans>Encrypted metadata</Trans> </h4> <p className="description"> <Trans> Select which content type with encrypted metadata is available for your whole organisation. </Trans> </p> <div className="checkboxlist"> <span className={`input checkbox form-element ${errors?.hasError("password_v5") && "error"} ${!errors?.hasError("password_v5") && warnings?.hasError("password_v5") && "warning"}`} > <input type="checkbox" id="passwordV5Input" className="checkbox" name="password_v5" onChange={this.handleInputChange} checked={this.state.settings.password_v5} disabled={this.isInputDisabled( this.state.settings.password_v5_count, this.state.settings.password_v5, )} /> <label htmlFor="passwordV5Input"> {this.addTooltipOnDisabledElement( <div className="allow-content-type-item"> <KeySVG /> <span className="name"> <Trans>Password</Trans> </span> <span className="info"> {this.props.t(`({{count}} resources)`, { count: this.state.settings.password_v5_count })} </span> </div>, this.isInputDisabled(this.state.settings.password_v5_count, this.state.settings.password_v5), )} {errors?.hasError("password_v5", "has_content") && ( <div className="error-message"> <Trans>Password content type is disabled but there are existing password resources.</Trans> </div> )} {errors?.hasError("password_v5", "minimum_requirement") && ( <div className="error-message"> <Trans>At least one content type should be allowed</Trans> </div> )} {!errors?.hasError("password_v5") && ( <> {warnings?.hasError("password_v5", "is_creation_alowed") && ( <div className="warning-message"> <Trans>V5 resource creation is enabled but password content type is disabled.</Trans> </div> )} {warnings?.hasError("password_v5", "active_metadata_key") && ( <div className="warning-message"> <Trans>No active metadata key defined.</Trans> </div> )} {warnings?.hasError("password_v5", "is_creation_not_alowed") && ( <div className="warning-message"> <Trans>Creation of content type v5 is not allowed.</Trans> </div> )} </> )} </label> </span> <span className={`input checkbox form-element ${errors?.hasError("totp_v5") && "error"} ${!errors?.hasError("totp_v5") && warnings?.hasError("totp_v5") && "warning"}`} > <input type="checkbox" id="totpV5Input" className="checkbox" name="totp_v5" onChange={this.handleInputChange} checked={this.state.settings.totp_v5} disabled={this.isInputDisabled(this.state.settings.totp_v5_count, this.state.settings.totp_v5)} /> <label htmlFor="totpV5Input"> {this.addTooltipOnDisabledElement( <div className="allow-content-type-item"> <TotpSVG /> <span className="name"> <Trans>TOTP</Trans> </span> <span className="info"> {this.props.t(`({{count}} resources)`, { count: this.state.settings.totp_v5_count })} </span> </div>, this.isInputDisabled(this.state.settings.totp_v5_count, this.state.settings.totp_v5), )} {errors?.hasError("totp_v5", "has_content") && ( <div className="error-message"> <Trans>TOTP content type is disabled but there are existing TOTP resources.</Trans> </div> )} {errors?.hasError("totp_v5", "minimum_requirement") && ( <div className="error-message"> <Trans>At least one content type should be allowed</Trans> </div> )} {!errors?.hasError("totp_v5") && ( <> {warnings?.hasError("totp_v5", "is_creation_alowed") && ( <div className="warning-message"> <Trans>V5 resource creation is enabled but TOTP content type is disabled.</Trans> </div> )} {warnings?.hasError("totp_v5", "active_metadata_key") && ( <div className="warning-message"> <Trans>No active metadata key defined.</Trans> </div> )} {warnings?.hasError("totp_v5", "is_creation_not_alowed") && ( <div className="warning-message"> <Trans>Creation of content type v5 is not allowed.</Trans> </div> )} </> )} </label> </span> <span className={`input checkbox form-element ${errors?.hasError("custom_fields_v5") && "error"} ${!errors?.hasError("custom_fields_v5") && warnings?.hasError("custom_fields_v5") && "warning"}`} > <input type="checkbox" id="customFieldsV5Input" className="checkbox" name="custom_fields_v5" onChange={this.handleInputChange} checked={this.state.settings.custom_fields_v5} disabled={this.isInputDisabled( this.state.settings.custom_fields_v5_count, this.state.settings.custom_fields_v5, )} /> <label htmlFor="customFieldsV5Input"> {this.addTooltipOnDisabledElement( <div className="allow-content-type-item"> <TablePropertiesSVG /> <span className="name"> <Trans>Custom fields</Trans> </span> <span className="info"> {this.props.t(`({{count}} resources)`, { count: this.state.settings.custom_fields_v5_count, })} </span> </div>, this.isInputDisabled( this.state.settings.custom_fields_v5_count, this.state.settings.custom_fields_v5, ), )} {errors?.hasError("custom_fields_v5", "has_content") && ( <div className="error-message"> <Trans> Custom fields content type is disabled but there are existing custom fields resources. </Trans> </div> )} {errors?.hasError("custom_fields_v5", "minimum_requirement") && ( <div className="error-message"> <Trans>At least one content type should be allowed</Trans> </div> )} {!errors?.hasError("custom_fields_v5") && ( <> {warnings?.hasError("custom_fields_v5", "is_creation_alowed") && ( <div className="warning-message"> <Trans> V5 resource creation is enabled but custom fields content type is disabled. </Trans> </div> )} {warnings?.hasError("custom_fields_v5", "active_metadata_key") && ( <div className="warning-message"> <Trans>No active metadata key defined.</Trans> </div> )} {warnings?.hasError("custom_fields_v5", "is_creation_not_alowed") && ( <div className="warning-message"> <Trans>Creation of content type v5 is not allowed.</Trans> </div> )} </> )} </label> </span> <span className={`input checkbox form-element ${errors?.hasError("note_v5") && "error"} ${!errors?.hasError("note_v5") && warnings?.hasError("note_v5") && "warning"}`} > <input type="checkbox" id="noteV5Input" className="checkbox" name="note_v5" onChange={this.handleInputChange} checked={this.state.settings.note_v5} disabled={this.isInputDisabled(this.state.settings.note_v5_count, this.state.settings.note_v5)} /> <label htmlFor="noteV5Input"> {this.addTooltipOnDisabledElement( <div className="allow-content-type-item"> <NoteSVG /> <span className="name"> <Trans>Note</Trans> </span> <span className="info"> {this.props.t(`({{count}} resources)`, { count: this.state.settings.note_v5_count })} </span> </div>, this.isInputDisabled(this.state.settings.note_v5_count, this.state.settings.note_v5), )} {errors?.hasError("note_v5", "has_content") && ( <div className="error-message"> <Trans>Note content type is disabled but there are existing note resources.</Trans> </div> )} {errors?.hasError("note_v5", "minimum_requirement") && ( <div className="error-message"> <Trans>At least one content type should be allowed</Trans> </div> )} {!errors?.hasError("note_v5") && ( <> {warnings?.hasError("note_v5", "is_creation_alowed") && ( <div className="warning-message"> <Trans>V5 resource creation is enabled but note content type is disabled.</Trans> </div> )} {warnings?.hasError("note_v5", "active_metadata_key") && ( <div className="warning-message"> <Trans>No active metadata key defined.</Trans> </div> )} {warnings?.hasError("note_v5", "is_creation_not_alowed") && ( <div className="warning-message"> <Trans>Creation of content type v5 is not allowed.</Trans> </div> )} </> )} </label> </span> </div> </> )} {this.state.settings.has_v4_resource_types && ( <> <h4 className={`${!this.state.settings.has_password_v5 && "no-border"}`}> <Trans>Legacy cleartext metadata</Trans> </h4> <p className="description"> <Trans> Select which content type with cleartext metadata is available for your whole organisation. </Trans> </p> <div className="checkboxlist"> <span className={`input checkbox form-element ${errors?.hasError("password_v4") && "error"} ${!errors?.hasError("password_v4") && warnings?.hasError("password_v4") && "warning"}`} > <input type="checkbox" id="passwordV4Input" className="checkbox" name="password_v4" onChange={this.handleInputChange} checked={this.state.settings.password_v4} disabled={this.isInputDisabled( this.state.settings.password_v4_count, this.state.settings.password_v4, )} /> <label htmlFor="passwordV4Input"> {this.addTooltipOnDisabledElement( <div className="allow-content-type-item"> <KeySVG /> <span className="name"> <Trans>Password</Trans> </span> <span className="info"> {this.props.t(`({{count}} resources)`, { count: this.state.settings.password_v4_count })} </span> </div>, this.isInputDisabled(this.state.settings.password_v4_count, this.state.settings.password_v4), )} {errors?.hasError("password_v4", "has_content") && ( <div className="error-message"> <Trans>Password content type is disabled but there are existing password resources.</Trans> </div> )} {errors?.hasError("password_v4", "minimum_requirement") && ( <div className="error-message"> <Trans>At least one content type should be allowed</Trans> </div> )} {!errors?.hasError("password_v4") && ( <> {warnings?.hasError("password_v4", "is_creation_alowed") && ( <div className="warning-message"> <Trans>V4 resource creation is enabled but password content type is disabled.</Trans> </div> )} {warnings?.hasError("password_v4", "is_creation_not_alowed") && ( <div className="warning-message"> <Trans>Creation of content type v4 is not allowed.</Trans> </div> )} </> )} </label> </span> <span className={`input checkbox form-element ${errors?.hasError("totp_v4") && "error"} ${!errors?.hasError("totp_v4") && warnings?.hasError("totp_v4") && "warning"}`} > <input type="checkbox" id="totpV4Input" className="checkbox" name="totp_v4" onChange={this.handleInputChange} checked={this.state.settings.totp_v4} disabled={this.isInputDisabled(this.state.settings.totp_v4_count, this.state.settings.totp_v4)} /> <label htmlFor="totpV4Input"> {this.addTooltipOnDisabledElement( <div className="allow-content-type-item"> <TotpSVG /> <span className="name"> <Trans>TOTP</Trans> </span> <span className="info"> {this.props.t(`({{count}} resources)`, { count: this.state.settings.totp_v4_count })} </span> </div>, this.isInputDisabled(this.state.settings.totp_v4_count, this.state.settings.totp_v4), )} {errors?.hasError("totp_v4", "has_content") && ( <div className="error-message"> <Trans>TOTP content type is disabled but there are existing TOTP resources.</Trans> </div> )} {errors?.hasError("totp_v4", "minimum_requirement") && ( <div className="error-message"> <Trans>At least one content type should be allowed</Trans> </div> )} {!errors?.hasError("totp_v4") && ( <> {warnings?.hasError("totp_v4", "is_creation_alowed") && ( <div className="warning-message"> <Trans>V4 resource creation is enabled but TOTP content type is disabled.</Trans> </div> )} {warnings?.hasError("totp_v4", "is_creation_not_alowed") && ( <div className="warning-message"> <Trans>Creation of content type v4 is not allowed.</Trans> </div> )} </> )} </label> </span> </div> </> )} </form> </div> {shouldDisplayAWarningBlock && ( <div className="warning message"> {isFeatureBeta && ( <div className="form-banner"> <b> <Trans>Warning:</Trans> </b>{" "} <Trans> Your current API version includes beta support for encrypted metadata and new resource types. </Trans>{" "} <Trans> To ensure stability and avoid potential issues, upgrade to the latest version before enabling these features. </Trans> </div> )} {hasSettingsChanges && ( <div> <p> <b> <Trans>Warning:</Trans> </b>{" "} <Trans>Don&apos;t forget to save your settings to apply your modification.</Trans> </p> </div> )} </div> )} </div> <div className="actions-wrapper"> <button type="button" className="button primary" disabled={this.state.isProcessing} onClick={this.handleFormSubmit} > <span> <Trans>Save</Trans> </span> </button> </div> {createSafePortal( <div className="sidebar-help-section"> <h3> <Trans>Need help?</Trans> </h3> <p> <Trans> For more information about the content type support and migration, checkout the dedicated page on the official website. </Trans> </p> <a className="button" target="_blank" rel="noopener noreferrer" href="https://passbolt.com/docs/admin/metadata-encryption/allowed-content-types/" > <InfoSVG /> <span> <Trans>Read the documentation</Trans> </span> </a> </div>, document.getElementById("administration-help-panel"), )} </div> ); } } DisplayContentTypesAllowedContentTypesAdministration.propTypes = { context: PropTypes.object, // Defined the expected type for context dialogContext: PropTypes.object, // The dialog context actionFeedbackContext: PropTypes.object, // The action feedback context createPortal: PropTypes.func, // The mocked create portal react dom primitive if test needed. resourceTypesServiceWorkerService: PropTypes.object, metadataKeysServiceWorkerService: PropTypes.object, metadataSettingsServiceWorkerService: PropTypes.object, // The bext service that handle metadata settings. t: PropTypes.func, // translation function }; export default withAppContext( withActionFeedback(withDialog(withTranslation("common")(DisplayContentTypesAllowedContentTypesAdministration))), );