passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
318 lines (298 loc) • 11 kB
JavaScript
/**
* 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 5.3.0
*/
import PropTypes from "prop-types";
import React, { Component } from "react";
import { Trans, withTranslation } from "react-i18next";
import DeleteSVG from "../../../../img/svg/delete.svg";
import AddSVG from "../../../../img/svg/add.svg";
import SecureTextarea from "../../../../shared/components/SecureTextarea/SecureTextarea";
import CustomFieldEntity from "../../../../shared/models/entity/customField/customFieldEntity";
import Tooltip from "../../Common/Tooltip/Tooltip";
import { CUSTOM_FIELD_COLLECTION_MAX_CONTENT_SIZE } from "../../../../shared/models/entity/customField/customFieldsCollection";
class AddResourceCustomFields extends Component {
constructor(props) {
super(props);
this.state = this.defaultState;
this.bindCallbacks();
}
get defaultState() {
return {
totalCharacters: 0,
};
}
/**
* Bind callbacks methods
* @return {void}
*/
bindCallbacks() {
this.handleInputChange = this.handleInputChange.bind(this);
this.handleAddCustomFieldsClick = this.handleAddCustomFieldsClick.bind(this);
this.handleAddCustomFieldsClick = this.handleAddCustomFieldsClick.bind(this);
}
/**
* Get the translation function
* @returns {function(...[*]=)}
*/
get translate() {
return this.props.t;
}
/**
* Handle form input change.
* @params {ReactEvent} The react event.
*/
handleInputChange(event) {
if (this.props.onChange) {
this.props.onChange(event);
}
}
/**
* Handle the click on the add custom fields button
*/
handleAddCustomFieldsClick() {
const eventCustomField = {
target: {
name: `secret.custom_fields.${this.customFieldsLength}`,
value: CustomFieldEntity.createFromDefault(),
},
};
this.props.onChange?.(eventCustomField);
}
/**
* Handle the click on the delete custom fields button
* @param {number} index The index of the uri to delete
*/
handleDeleteCustomFieldClick(index) {
const eventCustomField = {
target: {
name: `secret.custom_fields.${index}`,
value: null,
},
};
this.props.onChange?.(eventCustomField);
}
/**
* Can add custom field
* @returns {boolean}
*/
get canAddCustomField() {
return this.customFieldsLength < 32 && !this.isCustomFieldsCollectionMaxContentSizeReached;
}
/**
* Get the custom fields length
* @return {*}
*/
get customFieldsLength() {
return this.props.resource?.secret?.custom_fields?.length;
}
/**
* Display the content surrounded or not with a tooltip.
* @param {React.JSX.Element} content content to display
* @param {boolean} isDisabled if disabled the tooltip is added
* @returns {React.JSX.Element}
*/
addTooltipOnDisabledElement(content, isDisabled) {
if (isDisabled) {
return this.customFieldsLength === 32 ? (
<Tooltip message={<Trans>You have reached the row limit.</Trans>} direction="bottom">
{content}
</Tooltip>
) : (
<Tooltip message={<Trans>You have reached the maximum content size limit.</Trans>} direction="bottom">
{content}
</Tooltip>
);
} else {
return <>{content}</>;
}
}
/**
* Checks if there is a max length warning for a specific property.
*
* @param {string} propName - The name of the property to check for warnings.
* @param {string} rule - The rule of the property to check for warnings.
* @returns {boolean} - Returns true if there is a max length warning for the property, false otherwise.
*/
isWarnings(propName, rule) {
return this.props.warnings?.hasError(propName, rule);
}
/**
* Is max length reach for the key and the value
* @param index
* @returns {boolean}
*/
isMaxLengthKeyAndValueWarning(index) {
return (
this.props.warnings?.hasError(`custom_fields.${index}.key`, "maxLength") &&
this.props.warnings?.hasError(`custom_fields.${index}.value`, "maxLength") &&
!this.isCustomFieldsCollectionMaxContentSizeReached
);
}
/**
* Get the max length according to the index of the custom field
* @param {number} index
* @returns {number}
*/
customFieldValueMaxLengthAllowed(index) {
const currentCustomFieldValueMaxLengthAllowed =
CUSTOM_FIELD_COLLECTION_MAX_CONTENT_SIZE -
this.customFieldsValueLength +
this.props.resource?.secret.custom_fields[index].secret_value.length;
return Math.min(20000, currentCustomFieldValueMaxLengthAllowed);
}
/**
* Get the custom fields value total length
* @returns {*}
*/
get customFieldsValueLength() {
return this.props.resource?.secret.custom_fields.reduce(
(total, custom_field) => total + custom_field.secret_value.length,
0,
);
}
/**
* Is custom fields collection max content size is reached
* @returns {boolean}
*/
get isCustomFieldsCollectionMaxContentSizeReached() {
return this.customFieldsValueLength === CUSTOM_FIELD_COLLECTION_MAX_CONTENT_SIZE;
}
/*
* =============================================================
* Render view
* =============================================================
*/
render() {
return (
<>
<div className="title">
<h2>
<Trans>Custom fields</Trans>
</h2>
</div>
<div className="header">
<div className="key">
<span className="label">
<Trans>Key</Trans>
</span>
<span className="subinfo">
<Trans>Searchable Metadata</Trans>
</span>
</div>
<div className="divider-wrapper">
<span className="divider"></span>
</div>
<div className="value">
<span className="label">
<Trans>Value</Trans>
</span>
<span className="subinfo">
<Trans>Non-Searchable Secret</Trans>
</span>
</div>
</div>
<div className="content">
<div className="custom-fields-fields">
{this.props.resource?.secret.custom_fields?.map((custom_field, index) => (
<div key={index} className="input custom-field-row">
<div className="input custom-field">
<input
id={`resource-custom-fields-key-${index}`}
autoFocus={index + 1 === this.customFieldsLength}
disabled={this.props.disabled}
name={`secret.custom_fields.${index}.metadata_key`}
maxLength="255"
type="text"
autoComplete="off"
placeholder={this.translate("Key")}
value={custom_field.metadata_key}
onChange={this.handleInputChange}
/>
<SecureTextarea
id={`resource-custom-fields-value-${index}`}
name={`secret.custom_fields.${index}.secret_value`}
placeholder={this.translate("Value")}
maxLength={this.customFieldValueMaxLengthAllowed(index)}
value={custom_field.secret_value}
onChange={this.handleInputChange}
disabled={this.props.disabled}
/>
{this.customFieldsLength > 1 && (
<button
type="button"
className="button-transparent inline"
id={`resource-delete-custom-field-${index}`}
onClick={() => this.handleDeleteCustomFieldClick(index)}
>
<DeleteSVG />
</button>
)}
</div>
{this.isWarnings(`custom_fields.${index}.key`, "unique") && (
<div className={`resource-custom-fields-key-warning-${index} warning-message`}>
<Trans>The key name is already used.</Trans>
</div>
)}
{this.isMaxLengthKeyAndValueWarning(index) ? (
<div className={`resource-custom-fields-warning-${index} warning-message`}>
<Trans>
The key and the value reach the character limit, make sure your data won’t be truncated.
</Trans>
</div>
) : (
<>
{this.isWarnings(`custom_fields.${index}.key`, "maxLength") && (
<div className={`resource-custom-fields-key-warning-${index} warning-message`}>
<Trans>The key reaches the character limit, make sure your data won’t be truncated.</Trans>
</div>
)}
{this.isWarnings(`custom_fields.${index}.value`, "maxLength") &&
!this.isCustomFieldsCollectionMaxContentSizeReached && (
<div className={`resource-custom-fields-value-warning-${index} warning-message`}>
<Trans>The value reaches the character limit, make sure your data won’t be truncated.</Trans>
</div>
)}
</>
)}
</div>
))}
<div className="custom-field-add">
{this.addTooltipOnDisabledElement(
<button type="button" disabled={!this.canAddCustomField} onClick={this.handleAddCustomFieldsClick}>
<AddSVG />
<span>
<Trans>Add Row</Trans>
</span>
</button>,
!this.canAddCustomField,
)}
</div>
</div>
</div>
{this.isCustomFieldsCollectionMaxContentSizeReached && (
<div className="warning message no-margin">
<Trans>You have reached the maximum content size limit.</Trans>
</div>
)}
</>
);
}
}
AddResourceCustomFields.propTypes = {
resource: PropTypes.object, // The resource to edit or create
onChange: PropTypes.func, //The resource setter
t: PropTypes.func, // The translation function
warnings: PropTypes.object, //The warnings validation
disabled: PropTypes.bool, // The disabled property
};
export default withTranslation("common")(AddResourceCustomFields);