passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
520 lines (482 loc) • 17.1 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 2.13.0
*/
import React from "react";
import PropTypes from "prop-types";
import UserAvatar from "../../Common/Avatar/UserAvatar";
import GroupAvatar from "../../Common/Avatar/GroupAvatar";
import { withAppContext } from "../../../../shared/context/AppContext/AppContext";
import { withResourceWorkspace } from "../../../contexts/ResourceWorkspaceContext";
import SpinnerSVG from "../../../../img/svg/spinner.svg";
import { Trans, withTranslation } from "react-i18next";
import { formatDateTimeAgo } from "../../../../shared/utils/dateUtils";
import { withActionFeedback } from "../../../contexts/ActionFeedbackContext";
import ActivitiesServiceWorkerService from "./ActivitiesServiceWorkerService";
import DisplayAroName from "../../../../shared/components/Aro/DisplayAroName";
/**
* This component display activity section of a resource
*/
class DisplayResourceDetailsActivity extends React.Component {
/**
* Constructor
* @param {Object} props
*/
constructor(props) {
super(props);
this.state = this.defaultState;
this.activitiesServiceWorkerService = new ActivitiesServiceWorkerService(props.context.port);
this.bindCallbacks();
}
/**
* Get default state
* @returns {*}
*/
get defaultState() {
return {
activities: [], // list of activities
activitiesPage: 1, // pagination for activity
loadingMore: false, // processing when the user want to see more activities
loading: true,
};
}
/**
* Whenever the component has mounted
*/
async componentDidMount() {
await this.fetch(this.state.activitiesPage);
this.setState({ loading: false });
}
/**
* Whenever the component has updated in terms of props
* @param prevProps
*/
async componentDidUpdate(prevProps) {
if (prevProps.resourceWorkspaceContext.details) {
await this.handleResourceChange(prevProps.resourceWorkspaceContext.details.resource);
}
if (prevProps.resourceWorkspaceContext.refresh) {
await this.handleResourceActivitiesRefresh(prevProps.resourceWorkspaceContext.refresh.activities);
}
}
/**
* Bind callbacks methods
*/
bindCallbacks() {
this.handleMoreClickEvent = this.handleMoreClickEvent.bind(this);
}
/**
* Check if the resource has changed and fetch
* @param previousResource
*/
async handleResourceChange(previousResource) {
// do nothing if the resource doesn't change.
if (this.resource.id === previousResource.id) {
return;
}
// Reset the component, and fetch activities for the new resource.
this.setState(this.defaultState, async () => await this.fetch(this.state.activitiesPage));
this.setState({ loading: false });
}
/**
* Handle the refresh of the activities
* @param hasPreviouslyRefreshed True if one previously refreshed the activities
*/
async handleResourceActivitiesRefresh(hasPreviouslyRefreshed) {
const mustRefresh = !hasPreviouslyRefreshed && this.props.resourceWorkspaceContext.refresh.activities;
if (mustRefresh) {
await this.fetch(this.state.activitiesPage);
await this.props.resourceWorkspaceContext.onResourceActivitiesRefreshed();
}
}
/**
* handle when the users click on the more button.
* Open/Close it.
*/
async handleMoreClickEvent() {
// If initial load : page 1 is not successful, don't increment to page 2
const activitiesPage =
this.state?.activities?.length > 0 ? this.state.activitiesPage + 1 : this.state.activitiesPage;
this.setState({ activitiesPage, loadingMore: true });
await this.fetch(activitiesPage);
this.setState({ loadingMore: false });
}
/**
* Fetch the resource activitiesPage
* @param {number} page the page to load
* @returns {Promise<void>}
*/
async fetch(page) {
try {
const newActivities =
(await this.activitiesServiceWorkerService.findAllFromResourceId(this.resource.id, { page })) || [];
// For the first page need to reset activities state
this.mergeActivities(newActivities);
} catch (error) {
console.error(error);
this.props.actionFeedbackContext.displayError(error.message);
// If fetch failed for page 2 or more, decrement page count to try the same page next
if (this.state.activitiesPage > 1) {
this.setState((prevState) => ({ activitiesPage: prevState.activitiesPage - 1 }));
}
}
}
/**
* Merge current activities with newly fetched ones.
* It avoids to have duplicated activities especially when a refresh is asked.
* @param {array} fetchActivities
*/
mergeActivities(fetchActivities) {
const newActivities = fetchActivities.filter(
(activity) => !this.state.activities.find((a) => a.id === activity.id),
);
const activities = [...this.state.activities, ...newActivities];
activities.sort((a, b) => new Date(b.created) - new Date(a.created));
this.setState({ activities });
}
/**
* Get an activity creator full name
* @param {object} user The creator
* @return string
*/
getActivityCreatorFullName(user) {
return `${user.profile.first_name} ${user.profile.last_name}`;
}
/**
* Get a permission aro name
* @param {object} permission The permission
*/
getPermissionLabel(permission) {
switch (permission.type) {
case 1:
return this.translate("can read");
case 7:
return this.translate("can update");
case 15:
return this.translate("is owner");
}
}
/**
* Get permission change type label
* @param {string} type The target change type
*/
getPermissionChangeTypeLabel(type) {
switch (type) {
case "created":
return this.translate("new");
case "updated":
return this.translate("updated");
case "removed":
return this.translate("deleted");
}
}
/**
* Get the base url
* @return {string}
*/
get baseUrl() {
return this.props.context.userSettings.getTrustedDomain();
}
/**
* Render a resource created activity.
* @param {object} activity The target activity
* @returns {JSX}
*/
renderResouceCreatedActivity(activity) {
const activityCreatorName = this.getActivityCreatorFullName(activity.creator);
const resourceName = this.resource.metadata.name;
const activityFormattedDate = formatDateTimeAgo(activity.created, this.props.t, this.props.context.locale);
return (
<li key={activity.id} className="usercard-detailed-col-2">
<div className="content-wrapper">
<div className="content">
<div className="name">
<Trans>
<span className="creator">{{ activityCreatorName }}</span> created item{" "}
<span className="item">{{ resourceName }}</span>
</Trans>
</div>
<div className="subinfo third-level light" title={activity.created}>
{activityFormattedDate}
</div>
</div>
</div>
<UserAvatar user={activity.creator} baseUrl={this.baseUrl} />
</li>
);
}
/**
* Render a resource updated activity.
* @param {object} activity The target activity
* @returns {JSX}
*/
renderResourceUpdatedActivity(activity) {
const activityCreatorName = this.getActivityCreatorFullName(activity.creator);
const resourceName = this.resource.metadata.name;
const activityFormattedDate = formatDateTimeAgo(activity.created, this.props.t, this.props.context.locale);
return (
<li key={activity.id} className="usercard-detailed-col-2">
<div className="content-wrapper">
<div className="content">
<div className="name">
<Trans>
<span className="creator">{{ activityCreatorName }}</span> updated item{" "}
<span className="item">{{ resourceName }}</span>
</Trans>
</div>
<div className="subinfo third-level light" title={activity.created}>
{activityFormattedDate}
</div>
</div>
</div>
<UserAvatar user={activity.creator} baseUrl={this.baseUrl} />
</li>
);
}
/**
* Render a secret read activity.
* @param {object} activity The target activity
* @returns {JSX}
*/
renderSecretReadActivity(activity) {
const activityCreatorName = this.getActivityCreatorFullName(activity.creator);
const resourceName = this.resource.metadata.name;
const activityFormattedDate = formatDateTimeAgo(activity.created, this.props.t, this.props.context.locale);
return (
<li key={activity.id} className="usercard-detailed-col-2">
<div className="content-wrapper">
<div className="content">
<div className="name">
<Trans>
<span className="creator">{{ activityCreatorName }}</span> accessed secret of item{" "}
<span className="item">{{ resourceName }}</span>
</Trans>
</div>
<div className="subinfo third-level light" title={activity.created}>
{activityFormattedDate}
</div>
</div>
</div>
<UserAvatar user={activity.creator} baseUrl={this.baseUrl} />
</li>
);
}
/**
* Render a secret updated activity.
* @param {object} activity The target activity
* @returns {JSX}
*/
renderSecretUpdatedActivity(activity) {
const activityCreatorName = this.getActivityCreatorFullName(activity.creator);
const resourceName = this.resource.metadata.name;
const activityFormattedDate = formatDateTimeAgo(activity.created, this.props.t, this.props.context.locale);
return (
<li key={activity.id} className="usercard-detailed-col-2">
<div className="content-wrapper">
<div className="content">
<div className="name">
<Trans>
<span className="creator">{{ activityCreatorName }}</span> updated secret of item{" "}
<span className="item">{{ resourceName }}</span>
</Trans>
</div>
<div className="subinfo third-level light" title={activity.created}>
{activityFormattedDate}
</div>
</div>
</div>
<UserAvatar user={activity.creator} baseUrl={this.baseUrl} />
</li>
);
}
/**
* Render a shared activity permission change item.
* @param {object} permission The target permission
* @param {string} changeType The change type
* @returns {JSX}
*/
renderSharedActivityPermissionChangeItem(permission, changeType) {
const permissionLabel = this.getPermissionLabel(permission);
const changeTypeLabel = this.getPermissionChangeTypeLabel(changeType);
return (
<li key={permission.id}>
{permission.user && <UserAvatar user={permission.user} baseUrl={this.baseUrl} />}
{permission.group && <GroupAvatar group={permission.group} />}
<div className="name">
<span className="creator">
<DisplayAroName displayAs={permission.aro} user={permission.user} group={permission.group} />
</span>
<span className="permission-type"> {permissionLabel}</span>
</div>
<div className="type">
<span className={changeType}>{changeTypeLabel}</span>
</div>
</li>
);
}
/**
* Render a permissions updated activity.
* @param {object} activity The target activity
* @returns {JSX}
*/
renderPermissionsUpdatedActivity(activity) {
const activityCreatorName = this.getActivityCreatorFullName(activity.creator);
const resourceName = this.resource.metadata.name;
const activityFormattedDate = formatDateTimeAgo(activity.created, this.props.t, this.props.context.locale);
return (
<li key={activity.id} className="usercard-detailed-col-2">
<div className="content-wrapper">
<div className="content">
<div className="name">
<Trans>
<span className="creator">{{ activityCreatorName }}</span> changed permissions of item{" "}
<span className="item">{{ resourceName }}</span> with
</Trans>
</div>
<ul className="permissions-list">
{activity.data.permissions.added.map((permission) =>
this.renderSharedActivityPermissionChangeItem(permission, "created"),
)}
{activity.data.permissions.updated.map((permission) =>
this.renderSharedActivityPermissionChangeItem(permission, "updated"),
)}
{activity.data.permissions.removed.map((permission) =>
this.renderSharedActivityPermissionChangeItem(permission, "removed"),
)}
</ul>
<div className="subinfo third-level light" title={activity.created}>
{activityFormattedDate}
</div>
</div>
</div>
<UserAvatar user={activity.creator} baseUrl={this.baseUrl} />
</li>
);
}
/**
* Render an unknown activity.
* @returns {JSX}
*/
renderUnknownActivity(activity) {
return (
<li key={activity.id} className="usercard-detailed-col-2">
<div className="content-wrapper">
<div className="content">
<Trans>Unknown activity, please contact your administrator.</Trans>
</div>
</div>
</li>
);
}
/**
* Render an activity
* @param {object} activity The activity to render
*/
renderActivity(activity) {
let render;
switch (activity.type) {
case "Resources.created": {
render = this.renderResouceCreatedActivity(activity);
break;
}
case "Resources.updated": {
render = this.renderResourceUpdatedActivity(activity);
break;
}
case "Permissions.updated": {
render = this.renderPermissionsUpdatedActivity(activity);
break;
}
case "Resource.Secrets.read": {
render = this.renderSecretReadActivity(activity);
break;
}
case "Resource.Secrets.updated": {
render = this.renderSecretUpdatedActivity(activity);
break;
}
default: {
render = this.renderUnknownActivity(activity);
break;
}
}
return render;
}
/**
* Check if the more button should be displayed.
* @return {boolean}
*/
mustDisplayMoreButton() {
return !this.state.activities.some((activity) => activity.type === "Resources.created");
}
/**
* Returns the current detailed resource
*/
get resource() {
return this.props.resourceWorkspaceContext.details.resource;
}
/**
* Get the translate function
* @returns {function(...[*]=)}
*/
get translate() {
return this.props.t;
}
/**
* Render the component
* @returns {JSX}
*/
render() {
return (
<div className={`activity accordion sidebar-section`}>
<div className="accordion-content">
{this.state.loading && (
<div className="processing-wrapper">
<SpinnerSVG />
<span className="processing-text">
<Trans>Retrieving activities</Trans>
</span>
</div>
)}
{!this.state.loading && (
<React.Fragment>
<ul className="ready">{this.state.activities.map((activity) => this.renderActivity(activity))}</ul>
{this.mustDisplayMoreButton() && (
<div className="actions">
<button
type="button"
onClick={this.handleMoreClickEvent}
disabled={this.state.loadingMore}
className={`link no-border action-logs-load-more ${this.state.loadingMore ? "processing" : ""}`}
>
<span>
<Trans>More</Trans>
</span>
</button>
</div>
)}
</React.Fragment>
)}
</div>
</div>
);
}
}
DisplayResourceDetailsActivity.propTypes = {
context: PropTypes.any, // The application context
resourceWorkspaceContext: PropTypes.any,
actionFeedbackContext: PropTypes.object,
t: PropTypes.func, // The translation function
};
export default withAppContext(
withResourceWorkspace(withActionFeedback(withTranslation("common")(DisplayResourceDetailsActivity))),
);