UNPKG

passbolt-styleguide

Version:

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

429 lines (394 loc) 15.1 kB
/** * Passbolt ~ Open source password manager for teams * Copyright (c) 2020 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) 2020 Passbolt SA (https://www.passbolt.com) * @license https://opensource.org/licenses/AGPL-3.0 AGPL License * @link https://www.passbolt.com Passbolt(tm) * @since 2.14.0 */ import React, {Component} from "react"; import PropTypes from "prop-types"; import {withAppContext} from "../../../contexts/AppContext"; import DialogWrapper from "../../Common/Dialog/DialogWrapper/DialogWrapper"; import {withActionFeedback} from "../../../contexts/ActionFeedbackContext"; import NotifyError from "../../Common/Error/NotifyError/NotifyError"; import {withDialog} from "../../../contexts/DialogContext"; import FormSubmitButton from "../../Common/Inputs/FormSubmitButton/FormSubmitButton"; import FormCancelButton from "../../Common/Inputs/FormSubmitButton/FormCancelButton"; import {withLoading} from "../../../contexts/LoadingContext"; import {Trans, withTranslation} from "react-i18next"; import Select from "../../Common/Select/Select"; /** * This component allows user to delete a group with conflict to reassign ownership of folders, resources */ class DeleteUserGroupWithConflicts extends Component { constructor(props, context) { super(props, context); this.initializeProperties(); this.state = this.defaultState; this.initEventHandlers(); } /** * Initialize properties */ initializeProperties() { this.foldersErrors = this.getFoldersErrors(); this.resourcesErrors = this.getResourcesErrors(); this.acosPermissionsOptions = this.getAcosPermissionsOptionsMap(); } get defaultState() { return { processing: false, owners: this.populateDefaultOwners(), }; } initEventHandlers() { this.handleCloseClick = this.handleCloseClick.bind(this); this.handleFormSubmit = this.handleFormSubmit.bind(this); this.handleOnChangeOwner = this.handleOnChangeOwner.bind(this); } /** * Get the folders errors if any * @returns {array} The list of folders errors * - The list is sorted alphabetically by folder name */ getFoldersErrors() { const errors = this.props.context.deleteGroupWithConflictsDialogProps.errors; const foldersErrors = errors.folders && errors.folders.sole_owner || []; const foldersSorterByName = (folderA, folderB) => folderA.name.localeCompare(folderB.name); return foldersErrors.sort(foldersSorterByName); } /** * Get the resources errors if any * @returns {array} The list of resources errors * - The list is sorted alphabetically by resource name */ getResourcesErrors() { const errors = this.props.context.deleteGroupWithConflictsDialogProps.errors; const resourcesErrors = errors.resources && errors.resources.sole_owner || []; const resourcesSorterByName = (resourcesA, resourcesB) => resourcesA.name.localeCompare(resourcesB.name); return resourcesErrors.sort(resourcesSorterByName); } /** * Get a map of permissions options that can potentially be elevated as owner for each aco (resources/folders). * @returns {object} */ getAcosPermissionsOptionsMap() { const acoPermissionsOptionsMap = {}; this.resourcesErrors.forEach(resourceError => { acoPermissionsOptionsMap[resourceError.id] = this.getAcoPermissionsOptions(resourceError); }); this.foldersErrors.forEach(folderError => { acoPermissionsOptionsMap[folderError.id] = this.getAcoPermissionsOptions(folderError); }); return acoPermissionsOptionsMap; } /** * Get the permissions options that can potentially be elevated as owner for the given aco error (resource/group). * @param {object} acoError * @returns {array} An array of permissions * - The permissions options are sorted alphabetically by associated user full name or group. * - The permission associated to the group we want to delete is removed from this list. * - The permission associated user or group is attached to each permission. */ getAcoPermissionsOptions(acoError) { let acoPermissionsOptions = acoError.permissions; acoPermissionsOptions = this.filterOutGroupToDeleteFromPermissions(acoPermissionsOptions); acoPermissionsOptions = this.decoratePermissionWithAcoEntity(acoPermissionsOptions); acoPermissionsOptions = this.sortPermissionsAlphabeticallyByAcoName(acoPermissionsOptions); return acoPermissionsOptions; } /** * Filter out the user to delete from the list of permissions. * @param {array} permissions * @returns {array} */ filterOutGroupToDeleteFromPermissions(permissions) { const filterOutGroupToDeleteFromPermissions = permission => permission.aro_foreign_key !== this.groupToDelete.id; return permissions.filter(filterOutGroupToDeleteFromPermissions); } /** * Decorate a list of permissions with their associated user or group. * @param {array} permissions * @returns {array} */ decoratePermissionWithAcoEntity(permissions) { permissions.forEach(permission => { if (permission.aro === "Group") { permission.group = this.getGroup(permission.aro_foreign_key); } else { permission.user = this.getUser(permission.aro_foreign_key); } }); return permissions; } /** * Sort a list of permissions by their aco name (fullname for user, name for group) * @param {array} permissions * @returns {array} */ sortPermissionsAlphabeticallyByAcoName(permissions) { return permissions.sort((permissionA, permissionB) => { const permissionAAcoName = permissionA.aro === "Group" ? permissionA.group.name : this.getUserFullName(permissionA.user); const permissionBAcoName = permissionB.aro === "Group" ? permissionB.group.name : this.getUserFullName(permissionB.user); return permissionAAcoName.localeCompare(permissionBAcoName); }); } /** * Get a user by id * @param {string} id * @returns {object} */ getUser(id) { return this.props.context.users.find(user => user.id === id); } /** * Get a group by id * @param {string} id * @returns {object} */ getGroup(id) { return this.props.context.groups.find(group => group.id === id); } /** * Get a user full name * @param {object} user * @returns {string} */ getUserFullName(user) { return `${user.profile.first_name} ${user.profile.last_name}`; } /** * Handle form submit event. * @params {ReactEvent} The react event * @return {Promise} */ async handleFormSubmit(event) { event.preventDefault(); if (!this.state.processing) { await this.delete(); } } /** * Handle close button click. */ handleCloseClick() { this.props.onClose(); this.props.context.setContext({deleteUserWithConflictsDialogProps: null}); } /** * Handle onChange owner select event * @param event * @param id the id of the folder or resource */ handleOnChangeOwner(event, id) { const target = event.target; const permissionId = target.value; const owners = this.state.owners; // assign the new folderId or resourceId with the permissionId Object.assign(owners, {[id]: permissionId}); this.setState({owners}); } /** * create the permission owners for folder and resource transfer to send it as format expected * @returns {[{id: id of the permission, aco_foreign_key: id of the folder or resource}]} */ createPermissionOwnersTransfer() { if (this.state.owners != null) { const owners = []; for (const [key, value] of Object.entries(this.state.owners)) { owners.push({id: value, aco_foreign_key: key}); } return owners; } return null; } /** * create the user delete transfer permission * @returns {{owners: {id: id, of, the, permission, aco_foreign_key: id, folder, or, resource}[]}} */ createUserDeleteTransfer() { const owners = this.createPermissionOwnersTransfer(); return {owners}; } /** * Save the changes. */ async delete() { this.setState({processing: true}); try { const groupDeleteTransfer = this.createUserDeleteTransfer(); this.props.loadingContext.add(); await this.props.context.port.request("passbolt.groups.delete", this.groupToDelete.id, groupDeleteTransfer); this.props.loadingContext.remove(); await this.props.actionFeedbackContext.displaySuccess(this.translate("The group has been deleted successfully")); this.props.onClose(); this.props.context.setContext({deleteUserWithConflictsDialogProps: null}); } catch (error) { this.props.loadingContext.remove(); // It can happen when the user has closed the passphrase entry dialog by instance. if (error.name === "UserAbortsOperationError") { this.setState({processing: false}); } else { // Unexpected error occurred. console.error(error); this.handleError(error); this.setState({processing: false}); } } } handleError(error) { const errorDialogProps = { error: error }; this.props.dialogContext.open(NotifyError, errorDialogProps); } /** * Should input be disabled? True if state is processing * @returns {boolean} */ hasAllInputDisabled() { return this.state.processing; } /** * populate default owner of folders and resources * @returns { folderId: permissionId,... , resourceId: permissionId,... } */ populateDefaultOwners() { const owners = {}; if (this.hasResourcesConflict()) { this.resourcesErrors.forEach(resourceError => { const resourceDefaultOwner = this.acosPermissionsOptions[resourceError.id][0]; if (resourceDefaultOwner) { owners[resourceError.id] = resourceDefaultOwner.id; } }); } if (this.hasFolderConflict()) { this.foldersErrors.forEach(folderError => { const folderDefaultOwner = this.acosPermissionsOptions[folderError.id][0]; if (folderDefaultOwner) { owners[folderError.id] = folderDefaultOwner.id; } }); } return owners; } /** * Get the group to delete * @returns {null} */ get groupToDelete() { return this.props.context.deleteGroupWithConflictsDialogProps.group; } /** * has folders conflict * @returns {boolean} */ hasFolderConflict() { return this.foldersErrors && this.foldersErrors.length > 0; } /** * has resources conflict * @returns {boolean} */ hasResourcesConflict() { return this.resourcesErrors && this.resourcesErrors.length > 0; } /** * Get aco permission * @param id * @returns {*} */ getAcoPermissionsList(id) { const getLabel = permission => (permission.aro === "User" && this.getUserOptionLabel(permission.user)) || (permission.aro === "Group" && permission.group.name); return this.acosPermissionsOptions[id]?.map(permission => ({value: permission.id, label: getLabel(permission)})) || []; } /** * Get the user label displayed as option * @param {object} user */ getUserOptionLabel(user) { const first_name = user.profile.first_name; const last_name = user.profile.last_name; const username = user.username; return `${first_name} ${last_name} (${username})`; } /** * Get the translate function * @returns {function(...[*]=)} */ get translate() { return this.props.t; } render() { return ( <DialogWrapper title={this.translate("You cannot delete this group!")} onClose={this.handleCloseClick} disabled={this.state.processing} className="delete-group-dialog"> <form onSubmit={this.handleFormSubmit} noValidate> <div className="form-content intro"> <p> <Trans> You are about to delete the group <strong className="dialog-variable">{{groupName: this.groupToDelete.name}}</strong>. </Trans> </p> <p><Trans>This group is the sole owner of some content. You need to transfer the ownership to others to continue.</Trans></p> </div> <div className="ownership-transfer"> {this.hasFolderConflict() && <div> <h3><Trans>Folders</Trans></h3> <ul className="ownership-transfer-items"> {this.foldersErrors.map(folderError => <li key={folderError.id}> <div className={`select-wrapper input required ${this.state.processing ? 'disabled' : ''}`}> <label htmlFor="transfer_folder_owner">{folderError.name} <Trans>(Folder) new owner:</Trans></label> <Select className="form-element" value={this.state.owners[folderError.id]} items={this.getAcoPermissionsList(folderError.id)} onChange={event => this.handleOnChangeOwner(event, folderError.id)}/> </div> </li> )} </ul> </div> } {this.hasResourcesConflict() && <div> <h3><Trans>Passwords</Trans></h3> <ul className="ownership-transfer-items"> {this.resourcesErrors.map(resourceError => <li key={resourceError.id}> <div className={`select-wrapper input required ${this.state.processing ? 'disabled' : ''}`}> <label htmlFor="transfer_resource_owner">{resourceError.name} (<Trans>Password</Trans>) <Trans>new owner</Trans>:</label> <Select className="form-element" value={this.state.owners[resourceError.id]} items={this.getAcoPermissionsList(resourceError.id)} onChange={event => this.handleOnChangeOwner(event, resourceError.id)}/> </div> </li> )} </ul> </div> } </div> <div className="submit-wrapper clearfix"> <FormCancelButton disabled={this.hasAllInputDisabled()} onClick={this.handleCloseClick}/> <FormSubmitButton disabled={this.hasAllInputDisabled()} processing={this.state.processing} value={this.translate("Delete")} warning={true}/> </div> </form> </DialogWrapper> ); } } DeleteUserGroupWithConflicts.propTypes = { context: PropTypes.any, // The app context onClose: PropTypes.func, actionFeedbackContext: PropTypes.any, // The action feedback context dialogContext: PropTypes.any, // The dialog context loadingContext: PropTypes.any, // The loading context t: PropTypes.func, // The translation function }; export default withAppContext(withLoading(withActionFeedback(withDialog(withTranslation('common')(DeleteUserGroupWithConflicts)))));