UNPKG

passbolt-styleguide

Version:

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

446 lines (407 loc) 14.7 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 2.13.0 */ import React from "react"; import PropTypes from "prop-types"; import { withResourceWorkspace } from "../../../contexts/ResourceWorkspaceContext"; import { withDialog } from "../../../contexts/DialogContext"; import { withAppContext } from "../../../../shared/context/AppContext/AppContext"; import DialogWrapper from "../../Common/Dialog/DialogWrapper/DialogWrapper"; import FormSubmitButton from "../../Common/Inputs/FormSubmitButton/FormSubmitButton"; import FormCancelButton from "../../Common/Inputs/FormSubmitButton/FormCancelButton"; import NotifyError from "../../Common/Error/NotifyError/NotifyError"; import { withActionFeedback } from "../../../contexts/ActionFeedbackContext"; import { withExportPoliciesSettings } from "../../../contexts/ExportPoliciesSettingsContext"; import ExportResourcesCredentials from "./ExportResourcesCredentials"; import { Trans, withTranslation } from "react-i18next"; import Select from "../../Common/Select/Select"; /** * This component allows to export resources to a specified format */ class ExportResources extends React.Component { /** * Default constructor * @param props Component props */ constructor(props) { super(props); this.state = this.defaultState; this.bindHandlers(); } /** * Returns the component default state */ get defaultState() { return { selectedExportFormat: this.exportFormats[0].value, // The selected export format hasCsvWarningAccepted: false, // Whether user accepted the CSV warning csvWarningError: false, // Whether to show error state on CSV warning checkbox actions: { processing: false, // Actions flag about processing }, }; } /** * Bind the component handlers */ bindHandlers() { this.handleExport = this.handleExport.bind(this); this.handleCancel = this.handleCancel.bind(this); this.handleClose = this.handleClose.bind(this); this.handleExportFormatSelected = this.handleExportFormatSelected.bind(this); this.handleCsvWarningAcceptChange = this.handleCsvWarningAcceptChange.bind(this); } /** * ComponentDidMount * Invoked immediately after component is inserted into the tree * @return {void} */ componentDidMount() { this.findContentToExport(); } /** * Find the content to export and update the context. */ findContentToExport() { const foldersIds = this.findFoldersIdsToExport(); const resourcesIds = this.findResourcesIdsToExport(foldersIds); this.props.resourceWorkspaceContext.onResourcesToExport({ resourcesIds, foldersIds }); } /** * Return trus if the export is processing */ get isProcessing() { return this.state.actions.processing; } /** * Returns true if the current format is CSV */ get isCsvFormat() { return this.state.selectedExportFormat.startsWith("csv"); } /** * Returns the export policies settings entity or null if still loading. * @returns {ExportPoliciesSettingsEntity|null} */ get exportPoliciesSettings() { return this.props.exportPoliciesSettingsContext.getSettings(); } /** * Returns true if actions can be performed */ get areActionsAllowed() { return !this.isProcessing && this.exportPoliciesSettings !== null; } /** * Handles the CSV warning checkbox change */ handleCsvWarningAcceptChange() { this.setState({ hasCsvWarningAccepted: !this.state.hasCsvWarningAccepted, csvWarningError: false, }); } /** * Returns true if some folders must be exported */ get hasFoldersToExport() { const foldersIds = this.props.resourceWorkspaceContext.resourcesToExport.foldersIds; return foldersIds && foldersIds.length > 0; } /** * Returns true if some resources must be exported */ get hasResourcesToExport() { const resourcesIds = this.props.resourceWorkspaceContext.resourcesToExport.resourcesIds; return resourcesIds && resourcesIds.length > 0; } /** * Returns the folders identifiers to export */ findFoldersIdsToExport() { if (!this.hasFoldersToExport) { return []; } const foldersIds = this.props.resourceWorkspaceContext.resourcesToExport.foldersIds; const childrenFoldersIds = foldersIds.map((folderId) => this.getChildrenFoldersIds(folderId)).flat(); return [...foldersIds, ...childrenFoldersIds]; } /** * Returns the children folders ids of the given folder * @param folderId A folder identifier */ getChildrenFoldersIds(folderId) { const folders = this.props.context.folders; const childrenFoldersIds = folders .filter((folder) => folder.folder_parent_id === folderId) .map((folder) => folder.id); const hasChildren = childrenFoldersIds.length !== 0; if (!hasChildren) { return []; } else { const grandChildrenFolders = childrenFoldersIds.map((childFolderId) => this.getChildrenFoldersIds(childFolderId)); return [...childrenFoldersIds, ...grandChildrenFolders.flat()]; } } /** * Returns the resources identifiers to export * @param {array} foldersIds The list of folders to crawl for resources */ findResourcesIdsToExport(foldersIds) { const resourcesIdsOfFoldersIds = this.getResourcesIdsOfFoldersToExport(foldersIds); const resourcesIdsToExport = this.props.resourceWorkspaceContext.resourcesToExport.resourcesIds || []; return Array.from(new Set([...resourcesIdsToExport, ...resourcesIdsOfFoldersIds])); } /** * Returns the resources ids of the folders to export * @param foldersIds The folders to look into */ getResourcesIdsOfFoldersToExport(foldersIds) { const belongsToFolder = (resource) => foldersIds.some((folderId) => folderId === resource.folder_parent_id); return this.props.context.resources.filter(belongsToFolder).map((resource) => resource.id); } /** * Returns the export format label based on CSV policy * @returns {string} */ get exportFormatLabel() { return this.translate("Choose the export format"); } /** * Returns the list of available export formats */ get exportFormats() { const allFormats = [ { label: "kdbx (keepass)", value: "kdbx" }, { label: "kdbx (keepassXC & others)", value: "kdbx-others" }, { label: "csv (keepass)", value: "csv-kdbx" }, { label: "csv (lastpass)", value: "csv-lastpass" }, { label: "csv (1password)", value: "csv-1password" }, { label: "csv (chromium based browsers)", value: "csv-chromium" }, { label: "csv (bitwarden)", value: "csv-bitwarden" }, { label: "csv (mozilla)", value: "csv-mozilla" }, { label: "csv (safari)", value: "csv-safari" }, { label: "csv (dashlane)", value: "csv-dashlane" }, { label: "csv (nordpass)", value: "csv-nordpass" }, { label: "csv (logmeonce)", value: "csv-logmeonce" }, ]; if (this.exportPoliciesSettings?.allowCsvFormat === false) { return allFormats.filter((format) => !format.value.startsWith("csv")); } return allFormats; } /** * Whenever the user selects an export format * @param event Select DOM event */ handleExportFormatSelected(event) { this.selectFormat(event.target.value); } /** * Whenever the export is submitted * @param event A dom event */ async handleExport(event) { event.preventDefault(); const isCsv = this.state.selectedExportFormat.startsWith("csv"); if (isCsv) { // CSV case: validate checkbox first if (!this.state.hasCsvWarningAccepted) { this.setState({ csvWarningError: true }); return; } this.setState({ actions: { processing: true } }); this.export().then(this.onExportSuccess.bind(this)).catch(this.onExportFailure.bind(this)); } else { // KDBX case await this.props.dialogContext.open(ExportResourcesCredentials, { format: this.state.selectedExportFormat }); this.close(); } } /** * Whenever the export is cancelled */ async handleCancel() { await this.props.resourceWorkspaceContext.onResourcesToExport({ resourcesIds: null, foldersIds: null }); this.close(); } /** * Whenever the dialog is closed */ async handleClose() { await this.props.resourceWorkspaceContext.onResourcesToExport({ resourcesIds: null, foldersIds: null }); this.close(); } /** * Select the export format * @param selectedExportFormat The selected export format */ selectFormat(selectedExportFormat) { this.setState({ selectedExportFormat, hasCsvWarningAccepted: false, csvWarningError: false, }); } /** * Export the selected resources or folders */ async export() { const foldersIds = this.props.resourceWorkspaceContext.resourcesToExport.foldersIds; const resourcesIds = this.props.resourceWorkspaceContext.resourcesToExport.resourcesIds; const exportDto = { format: this.state.selectedExportFormat, folders_ids: foldersIds, resources_ids: resourcesIds, }; await this.props.context.port.request("passbolt.export-resources.export-to-file", exportDto); } /** * Whenever the export has been performed successfully */ async onExportSuccess() { this.setState({ actions: { processing: false } }); await this.props.actionFeedbackContext.displaySuccess( this.translate("The passwords have been exported successfully"), ); await this.props.resourceWorkspaceContext.onResourcesToExport({ resourcesIds: null, foldersIds: null }); this.close(); } /** * Whenever the export has been performed with failure */ onExportFailure(error) { const isUserAbortsOperation = error.name === "UserAbortsOperationError"; if (isUserAbortsOperation) { this.setState({ actions: { processing: false } }); return; } const errorDialogProps = { error: error, }; this.setState({ actions: { processing: false } }); this.props.dialogContext.open(NotifyError, errorDialogProps); } /** * Close the dialog */ close() { this.props.onClose(); } /** * Get the translate function * @returns {function(...[*]=)} */ get translate() { return this.props.t; } /** * Render the component */ render() { const foldersIdsToExport = this.hasFoldersToExport && this.props.resourceWorkspaceContext.resourcesToExport.foldersIds; const resourcesIdsToExport = this.hasResourcesToExport && this.props.resourceWorkspaceContext.resourcesToExport.resourcesIds; return ( <DialogWrapper className="export-password-dialog" title={this.translate("Export passwords")} onClose={this.handleClose} disabled={!this.areActionsAllowed} > <form onSubmit={this.handleExport} noValidate> <div className="form-content"> <div className={`select-wrapper input required ${this.isProcessing ? "disabled" : ""}`}> <label htmlFor="export-format">{this.exportFormatLabel}</label> <Select id="export-format" value={this.state.selectedExportFormat} items={this.exportFormats} onChange={this.handleExportFormatSelected} disabled={!this.areActionsAllowed} /> </div> {this.hasFoldersToExport && ( <p> <em> {this.translate("{{count}} folder is going to be exported.", { count: foldersIdsToExport.length })} </em> </p> )} {this.isCsvFormat && ( <div className={`input checkbox${this.state.csvWarningError ? " error" : ""}`}> <input id="csv-warning-accept" type="checkbox" name="csv-warning-accept" checked={this.state.hasCsvWarningAccepted} onChange={this.handleCsvWarningAcceptChange} disabled={this.isProcessing} /> <label htmlFor="csv-warning-accept"> <Trans> I understand this file is unencrypted and potentially unsafe to open in a spreadsheet software. </Trans> </label> </div> )} {this.hasResourcesToExport && ( <p> <em> {this.translate("{{count}} password is going to be exported.", { count: resourcesIdsToExport.length, })} </em> </p> )} </div> <div className="submit-wrapper clearfix"> <a className="button" href="https://www.passbolt.com/docs/user/basic-features/browser/export/" target="_blank" rel="noopener noreferrer" > <Trans>Learn more</Trans> </a> <FormCancelButton disabled={!this.areActionsAllowed} processing={this.isProcessing} onClick={this.handleCancel} /> <FormSubmitButton disabled={!this.areActionsAllowed} processing={this.isProcessing} value={this.translate("Export")} /> </div> </form> </DialogWrapper> ); } } ExportResources.propTypes = { context: PropTypes.any, // The application context onClose: PropTypes.func, // Whenever the dialog is closes resourceWorkspaceContext: PropTypes.object, // The resource workspace context dialogContext: PropTypes.object, // The dialog context actionFeedbackContext: PropTypes.object, // The action feedback context exportPoliciesSettingsContext: PropTypes.object, // The export policies settings context t: PropTypes.func, // The translation function }; export default withAppContext( withActionFeedback( withDialog(withResourceWorkspace(withExportPoliciesSettings(withTranslation("common")(ExportResources)))), ), );