diff --git a/app/api/server/lib/rooms.js b/app/api/server/lib/rooms.js
index 93535314e319..12d807f3cf4c 100644
--- a/app/api/server/lib/rooms.js
+++ b/app/api/server/lib/rooms.js
@@ -94,6 +94,8 @@ export async function findChannelAndPrivateAutocomplete({ uid, selector }) {
fields: {
_id: 1,
name: 1,
+ t: 1,
+ avatarETag: 1,
},
limit: 10,
sort: {
diff --git a/ee/app/auditing/client/routes.js b/ee/app/auditing/client/routes.js
index 334de697eb2b..3087462f6aeb 100644
--- a/ee/app/auditing/client/routes.js
+++ b/ee/app/auditing/client/routes.js
@@ -1,16 +1,21 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
+import { createTemplateForComponent } from '../../../../client/reactAdapters';
+
+createTemplateForComponent('auditPage', () => import('../../../client/audit/AuditPage'));
+createTemplateForComponent('auditLogPage', () => import('../../../client/audit/AuditLogPage'));
+
FlowRouter.route('/audit', {
name: 'audit-home',
action() {
- BlazeLayout.render('main', { center: 'audit' });
+ BlazeLayout.render('main', { center: 'auditPage' });
},
});
FlowRouter.route('/audit-log', {
name: 'audit-log',
action() {
- BlazeLayout.render('main', { center: 'auditLog' });
+ BlazeLayout.render('main', { center: 'auditLogPage' });
},
});
diff --git a/ee/app/auditing/client/templates/audit/audit.html b/ee/app/auditing/client/templates/audit/audit.html
index b5247322ce60..4e147a15858b 100644
--- a/ee/app/auditing/client/templates/audit/audit.html
+++ b/ee/app/auditing/client/templates/audit/audit.html
@@ -1,127 +1,17 @@
-
- {{# header sectionName="Message_auditing" hideHelp=true}}{{/header}}
-
-
- {{#if isLoading}}
- {{> loading class="loading-animation--primary"}}
- {{else}}
- {{#if hasResults}}
-
- {{#with messageContext}}
- {{#each msg in messages}}
- {{> nrr nrrargs 'message' shouldCollapseReplies=true msg=msg room=room subscription=subscription settings=settings u=u}}
- {{/each}}
- {{/with}}
-
- {{else}}
- {{> roomSearchEmpty class="rc-audit-empty"}}
- {{/if}}
- {{/if}}
-
-
-
-
-
-
-
-
-
-
+ {{/with}}
+
+ {{else}}
+ {{> roomSearchEmpty class="rc-audit-empty"}}
+ {{/if}}
+ {{/if}}
diff --git a/ee/app/auditing/client/templates/audit/audit.js b/ee/app/auditing/client/templates/audit/audit.js
index 8235c03cd28d..8ff72329cc87 100644
--- a/ee/app/auditing/client/templates/audit/audit.js
+++ b/ee/app/auditing/client/templates/audit/audit.js
@@ -1,12 +1,10 @@
-import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { FlowRouter } from 'meteor/kadira:flow-router';
-import { AutoComplete } from '../../../../../../app/meteor-autocomplete/client';
import { hasAllPermission } from '../../../../../../app/authorization/client';
import { messageContext } from '../../../../../../app/ui-utils/client/lib/messageContext';
-import { call, convertDate, scrollTo } from '../../utils.js';
+import { call } from '../../utils.js';
import './audit.html';
@@ -27,107 +25,8 @@ const loadMessages = async function({ rid, users, startDate, endDate = new Date(
}
};
-Template.audit.events({
- 'submit form'(e) {
- e.preventDefault();
- },
- 'change input[type=date]'(e) {
- e.currentTarget.parentElement.parentElement.parentElement.classList.remove('rc-input--error');
- },
- 'change [name=type]'(e, t) {
- t.type.set(e.currentTarget.value);
- },
- async 'click .js-submit'(e, t) {
- const form = e.currentTarget.parentElement;
-
- const type = t.type.get();
- const result = { type };
-
- e.currentTarget.blur();
-
- if (type === 'd') {
- if (!t.users) {
- return form.querySelector('#autocomplete-users').classList.add('rc-input--error');
- }
- form.querySelector('#autocomplete-users').classList.remove('rc-input--error');
- result.users = t.users.map((user) => user.username);
- } else if (type === 'l') {
- if (!t.visitor && !t.agent) {
- form.querySelector('#autocomplete-agent').classList.add('rc-input--error');
- form.querySelector('#autocomplete-visitor').classList.add('rc-input--error');
- return;
- }
-
- form.querySelector('#autocomplete-agent').classList.remove('rc-input--error');
- form.querySelector('#autocomplete-visitor').classList.remove('rc-input--error');
-
- result.visitor = t.visitor?._id;
- result.agent = t.agent?._id;
- } else {
- if (!t.room || !t.room._id) {
- return form.querySelector('#autocomplete-room').classList.add('rc-input--error');
- }
- form.querySelector('#autocomplete-room').classList.remove('rc-input--error');
- result.rid = t.room._id;
- }
-
- if (!form.startDate.value) {
- return form.startDate.parentElement.parentElement.parentElement.classList.add('rc-input--error');
- }
- form.startDate.parentElement.parentElement.parentElement.classList.remove('rc-input--error');
- result.startDate = form.startDate.type === 'date' ? convertDate(form.startDate.value) : form.startDate.value;
-
-
- result.msg = form.msg.value;
-
- if (!form.endDate.value) {
- return form.endDate.parentElement.parentElement.parentElement.classList.add('rc-input--error');
- }
- form.endDate.parentElement.parentElement.parentElement.classList.remove('rc-input--error');
- result.endDate = form.endDate.type === 'date' ? convertDate(form.endDate.value) : form.endDate.value;
- result.endDate = new Date(result.endDate.getTime() + 86400000);
-
- await t.loadMessages(result);
-
- setTimeout(() => {
- const offset = $(document.querySelector('.rc-audit-container ul')).offset();
- if (!offset) {
- return;
- }
- scrollTo(document.querySelector('.rc-audit-container'), offset.top - 150, 300);
- }, 150);
- },
-});
Template.audit.helpers({
- onChange() {
- const that = Template.instance();
- return function(value, key) {
- that[key] = value;
- };
- },
- prepareRoom: () => function(room) {
- room.username = room.name;
- return room;
- },
- modifierUser: () => function(text, filter) {
- const f = filter.get();
- return `@${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) {
- return `${ part }`;
- }) }`;
- },
- nTypeOthers() {
- return ['d', 'l'].includes(Template.instance().type.get());
- },
- nTypeDM() {
- return Template.instance().type.get() !== 'd';
- },
- nTypeOmni() {
- return Template.instance().type.get() !== 'l';
- },
- type() {
- return Template.instance().type.get();
- },
isLoading() {
return Template.instance().loading.get();
},
@@ -137,14 +36,10 @@ Template.audit.helpers({
hasResults() {
return Template.instance().hasResults.get();
},
- agentConditions() {
- return { role: 'livechat-agent' };
- },
});
Template.audit.onCreated(async function() {
this.messagesContext = new ReactiveVar({});
- this.type = new ReactiveVar();
this.loading = new ReactiveVar(false);
this.hasResults = new ReactiveVar(false);
@@ -159,243 +54,14 @@ Template.audit.onCreated(async function() {
});
this.loadMessages = loadMessages.bind(this);
-});
-
-const acEvents = (key/* , variable, name*/) => ({
- 'click .rc-popup-list__item'(e, t) {
- t[key].onItemClick(this, e);
- },
- 'keydown input'(e, t) {
- if ([8, 46].includes(e.keyCode) && e.target.value === '') {
- const users = t.selected;
- const usersArr = users.get();
- usersArr.pop();
- return users.set(usersArr);
- }
- t[key].onKeyDown(e);
- },
- 'keyup input'(e, t) {
- t[key].onKeyUp(e);
- },
- 'focus input'(e, t) {
- t[key].onFocus(e);
- },
- 'blur input'(e, t) {
- t[key].onBlur(e);
- },
-});
-
-Template.auditAutocompleteDirectMessage.events({
- ...acEvents('ac', 'selected'),
- 'input input'(e, t) {
- t.filter.set(e.target.value);
- },
- 'click .rc-tags__tag-icon'(e, t) {
- t.selected.set();
- },
- 'click .rc-tags__tag'({ target }, t) {
- const { onClickTag } = t;
- return onClickTag & onClickTag(Blaze.getData(target));
- },
-});
-
-Template.auditAutocomplete.events({
- ...acEvents('ac', 'selected'),
- 'input input'(e, t) {
- t.filter.set(e.target.value);
- },
- 'click .rc-tags__tag-icon'(e, t) {
- t.selected.set();
- },
- 'click .rc-tags__tag'({ target }, t) {
- const { onClickTag } = t;
- return onClickTag & onClickTag(Blaze.getData(target));
- },
-});
-
-Template.auditAutocompleteDirectMessage.helpers({
- selected() {
- const instance = Template.instance();
- const selected = instance.selected.get();
- return selected && (instance.data.prepare ? instance.data.prepare(selected) : selected);
- },
- config() {
- const { filter } = Template.instance();
- return {
- template_item: 'popupList_item_channel',
- // noMatchTemplate: Template.roomSearchEmpty,
- filter: filter.get(),
- noMatchTemplate: 'userSearchEmpty',
- modifier: (text) => (Template.parentData(8).modifier || function(text, filter) {
- const f = filter.get();
- return `#${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) {
- return `${ part }`;
- }) }`;
- })(text, filter),
- };
- },
- autocomplete(key) {
- const instance = Template.instance();
- const param = instance.ac[key];
- return typeof param === 'function' ? param.apply(instance.ac) : param;
- },
- items() {
- return Template.instance().ac.filteredList();
- },
-});
-
-Template.auditAutocomplete.helpers({
- selected() {
- const instance = Template.instance();
- const selected = instance.selected.get();
- return selected && (instance.data.prepare ? instance.data.prepare(selected) : selected);
- },
- config() {
- const { filter } = Template.instance();
- return {
- template_item: 'popupList_item_channel',
- // noMatchTemplate: Template.roomSearchEmpty,
- filter: filter.get(),
- noMatchTemplate: 'userSearchEmpty',
- modifier: (text) => (Template.parentData(8).modifier || function(text, filter) {
- const f = filter.get();
- return `#${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) {
- return `${ part }`;
- }) }`;
- })(text, filter),
- };
- },
- autocomplete(key) {
- const instance = Template.instance();
- const param = instance.ac[key];
- return typeof param === 'function' ? param.apply(instance.ac) : param;
- },
- items() {
- return Template.instance().ac.filteredList();
- },
-});
-
-Template.auditAutocomplete.onRendered(async function() {
- const { selected } = this;
-
- this.ac.element = this.firstNode.querySelector('input');
- this.ac.$element = $(this.ac.element);
-
- this.ac.$element.on('autocompleteselect', function(e, { item }) {
- selected.set(item);
- });
-});
-
-Template.auditAutocomplete.helpers({
- selected() {
- const instance = Template.instance();
- const selected = instance.selected.get();
- return selected && (instance.data.prepare ? instance.data.prepare(selected) : selected);
- },
- config() {
- const { filter } = Template.instance();
- const { templateItem } = Template.instance().data;
- return {
- template_item: templateItem || 'popupList_item_channel',
- // noMatchTemplate: Template.roomSearchEmpty,
- filter: filter.get(),
- noMatchTemplate: 'userSearchEmpty',
- modifier: (text) => (Template.parentData(8).modifier || function(text, filter) {
- const f = filter.get();
- return `#${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) {
- return `${ part }`;
- }) }`;
- })(text, filter),
- };
- },
- autocomplete(key) {
- const instance = Template.instance();
- const param = instance.ac[key];
- return typeof param === 'function' ? param.apply(instance.ac) : param;
- },
- items() {
- return Template.instance().ac.filteredList();
- },
-});
-const autocompleteConfig = ({
- collection,
- endpoint,
- field,
- term = 'term',
-}) => ({
- selector: {
- item: '.rc-popup-list__item',
- container: '.rc-popup-list__list',
- },
-
- limit: 10,
- inputDelay: 300,
- rules: [{
- // @TODO maybe change this 'collection' and/or template
- collection,
- endpoint,
- field,
- matchAll: true,
- selector(match) {
- return {
- [term]: match,
- };
- },
- sort: field,
- }],
-});
-
-Template.auditAutocomplete.onCreated(function() {
- this.filter = new ReactiveVar('');
- this.selected = new ReactiveVar('');
-
- this.onClickTag = () => {
- this.selected.set('');
- };
-
- this.autorun(() => {
- const value = this.selected.get();
- this.data.onChange(value, this.data.key);
- });
- this.ac = new AutoComplete(autocompleteConfig({
- field: this.data.field,
- collection: this.data.collection || 'CachedChannelList',
- endpoint: this.data.endpoint || 'rooms.autocomplete.channelAndPrivate',
- term: this.data.term || 'term',
- }));
- this.ac.tmplInst = this;
-});
-
-
-Template.auditAutocompleteDirectMessage.onCreated(function() {
- this.filter = new ReactiveVar('');
- this.selected = new ReactiveVar([]);
-
- this.onClickTag = ({ username }) => {
- this.selected.set(this.selected.get().filter((user) => user.username !== username));
- };
-
- this.autorun(() => {
- const value = this.selected.get();
- this.data.onChange(value, this.data.key);
- });
- this.ac = new AutoComplete(autocompleteConfig({
- field: this.data.field,
- collection: this.data.collection || 'CachedChannelList',
- endpoint: this.data.endpoint || 'rooms.autocomplete.channelAndPrivate',
- term: this.data.term || 'term',
- }));
- this.ac.tmplInst = this;
-});
-
-Template.auditAutocompleteDirectMessage.onRendered(async function() {
- const { selected } = this;
-
- this.ac.element = this.firstNode.querySelector('input');
- this.ac.$element = $(this.ac.element);
-
- this.ac.$element.on('autocompleteselect', function(e, { item }) {
- selected.set([...selected.get(), item]);
- });
+ const {
+ visitor,
+ agent,
+ users,
+ rid,
+ } = this.data;
+ if (rid || users.length || agent || visitor) {
+ await this.loadMessages(this.data);
+ }
});
diff --git a/ee/app/auditing/client/templates/auditLog/auditLog.html b/ee/app/auditing/client/templates/auditLog/auditLog.html
deleted file mode 100644
index 22082570c5cc..000000000000
--- a/ee/app/auditing/client/templates/auditLog/auditLog.html
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
- {{# header sectionName="Message_auditing_log" hideHelp=true}}{{/header}}
-
-
- {{#if isLoading}}
- {{> loading class="loading-animation--primary"}}
- {{else}}
- {{# if logs}}
-
-
-
-
- {{_'Username'}} |
- Looked for |
- When |
- Results |
- Filter applied |
-
-
-
- {{#each logs}}
- {{> auditLogItem}}
- {{/each}}
-
-
- {{else}}
- {{> roomSearchEmpty class="rc-audit-empty"}}
- {{/if}}
- {{/if}}
-
-
-
-
-
-
-
-
- {{> avatar username=u.username}}
-
-
- {{u.username}}
-
- |
- {{msg}} |
- {{ts}} |
- {{results}} |
- {{{fields}}} |
-
-
diff --git a/ee/app/auditing/client/templates/auditLog/auditLog.js b/ee/app/auditing/client/templates/auditLog/auditLog.js
deleted file mode 100644
index 902dfd4228c2..000000000000
--- a/ee/app/auditing/client/templates/auditLog/auditLog.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import moment from 'moment';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Template } from 'meteor/templating';
-import { FlowRouter } from 'meteor/kadira:flow-router';
-
-import { hasAllPermission } from '../../../../../../app/authorization';
-import { call, convertDate } from '../../utils.js';
-
-import './auditLog.html';
-
-const loadLog = async function({ startDate, endDate = new Date() }) {
- this.logs = this.logs || new ReactiveVar([]);
- this.loading = this.loading || new ReactiveVar(false);
- if (this.loading.get() === true) {
- return;
- }
- this.loading.set(true);
- try {
- const logs = await call('auditGetAuditions', { startDate, endDate });
- this.logs.set(logs);
- } catch (e) {
- this.logs.set([]);
- } finally {
- this.loading.set(false);
- }
-};
-
-Template.auditLog.events({
- 'click button'(e, t) {
- const form = e.currentTarget.parentElement;
- t.loadLog({
- startDate: convertDate(form.startDate.value),
- endDate: new Date(convertDate(form.endDate.value).getTime() + 86400000),
- });
- },
-});
-
-Template.auditLog.helpers({
- logs() {
- return Template.instance().logs.get();
- },
-});
-
-Template.auditLog.onRendered(function() {
- this.loadLog = loadLog.bind(this);
-});
-
-Template.auditLog.onCreated(function() {
- this.logs = new ReactiveVar([]);
-
- if (!hasAllPermission('can-audit-log')) {
- return FlowRouter.go('/home');
- }
-});
-
-Template.auditLogItem.helpers({
- msg() {
- return this.fields.msg;
- },
- username() {
- return this.u.username;
- },
- ts() {
- return moment(this.ts).format('lll');
- },
- fields() {
- const { fields } = this;
-
- const from = fields.users ? `@${ fields.users[0] } : @${ fields.users[1] }` : `#${ fields.room }`;
-
- return `${ from }
${ moment(fields.startDate).format('DD/MM/YYYY') } to ${ moment(fields.endDate).format('DD/MM/YYYY') }
`;
- },
-});
diff --git a/ee/app/auditing/client/templates/index.js b/ee/app/auditing/client/templates/index.js
index b6bc546b034e..8e4cbfbcf7e6 100644
--- a/ee/app/auditing/client/templates/index.js
+++ b/ee/app/auditing/client/templates/index.js
@@ -1,2 +1 @@
import './audit/audit.js';
-import './auditLog/auditLog.js';
diff --git a/ee/client/audit/AuditLogPage.js b/ee/client/audit/AuditLogPage.js
new file mode 100644
index 000000000000..0e2e3ea60119
--- /dev/null
+++ b/ee/client/audit/AuditLogPage.js
@@ -0,0 +1,44 @@
+import React, { useMemo, useState } from 'react';
+import { Field } from '@rocket.chat/fuselage';
+
+import Page from '../../../client/components/basic/Page';
+import DateRangePicker from './DateRangePicker';
+import AuditLogTable from './AuditLogTable';
+import { useTranslation } from '../../../client/contexts/TranslationContext';
+import { useMethodData } from '../../../client/contexts/ServerContext';
+
+const AuditLogPage = () => {
+ const t = useTranslation();
+
+ const [dateRange, setDateRange] = useState({
+ start: '',
+ end: '',
+ });
+
+ const {
+ start,
+ end,
+ } = dateRange;
+
+ const params = useMemo(() => [{
+ startDate: new Date(start),
+ endDate: new Date(end),
+ }], [end, start]);
+
+ const [data] = useMethodData('auditGetAuditions', params);
+
+ return
+
+
+
+ {t('Date')}
+
+
+
+
+
+
+ ;
+};
+
+export default AuditLogPage;
diff --git a/ee/client/audit/AuditLogTable.js b/ee/client/audit/AuditLogTable.js
new file mode 100644
index 000000000000..e1b06459b537
--- /dev/null
+++ b/ee/client/audit/AuditLogTable.js
@@ -0,0 +1,92 @@
+import React, { useMemo } from 'react';
+import { Box, Table } from '@rocket.chat/fuselage';
+
+import UserAvatar from '../../../client/components/basic/avatar/UserAvatar';
+import { GenericTable } from '../../../client/components/GenericTable';
+import { useTranslation } from '../../../client/contexts/TranslationContext';
+import { useFormatDateAndTime } from '../../../client/hooks/useFormatDateAndTime';
+import { useFormatDate } from '../../../client/hooks/useFormatDate';
+
+
+const FilterDisplay = ({ users, room, startDate, endDate, t }) =>
+
+ {users ? `@${ users[0] } : @${ users[1] }` : `#${ room }`}
+
+
+ {startDate} {t('to')} {endDate}
+
+;
+
+const UserRow = React.memo(({ u, results, ts, _id, formatDateAndTime, formatDate, fields, mediaQuery }) => {
+ const t = useTranslation();
+
+ const {
+ username,
+ name,
+ avatarETag,
+ } = u;
+
+ const {
+ msg,
+ users,
+ room,
+ startDate,
+ endDate,
+ } = fields;
+
+ const when = useMemo(() => formatDateAndTime(ts), [formatDateAndTime, ts]);
+
+ return
+
+
+
+
+
+ {name || username}
+ {name && {`@${ username }`} }
+
+
+
+
+
+ { msg }
+
+ {when}
+ {results}
+
+
+
+ ;
+});
+
+export function AuditLogTable({ data }) {
+ const t = useTranslation();
+
+ const formatDateAndTime = useFormatDateAndTime();
+ const formatDate = useFormatDate();
+
+ return
+
+ {t('Username')}
+
+
+ {t('Looked_for')}
+
+
+ {t('When')}
+
+
+ {t('Results')}
+
+
+ {t('Filters_applied')}
+
+ >}
+ results={data}
+ >
+ {(props) => }
+ ;
+}
+
+export default AuditLogTable;
diff --git a/ee/client/audit/AuditPage.js b/ee/client/audit/AuditPage.js
new file mode 100644
index 000000000000..660c7debda7b
--- /dev/null
+++ b/ee/client/audit/AuditPage.js
@@ -0,0 +1,191 @@
+import React, { useRef, useState } from 'react';
+import { Box, Field, TextInput, ButtonGroup, Button, Margins, Tabs } from '@rocket.chat/fuselage';
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+
+import Page from '../../../client/components/basic/Page';
+import DateRangePicker from './DateRangePicker';
+import RoomAutoComplete from './RoomAutoComplete';
+import UserAutoCompleteMultiple from './UserAutoCompleteMultiple';
+import VisitorAutoComplete from './VisitorAutoComplete';
+import Result from './Result';
+import { AutoCompleteAgent } from '../../../client/components/basic/AutoCompleteAgent';
+import { useTranslation } from '../../../client/contexts/TranslationContext';
+import { useForm } from '../../../client/hooks/useForm';
+
+const initialValues = {
+ msg: '',
+ type: '',
+ dateRange: {
+ start: '',
+ end: '',
+ },
+ visitor: '',
+ agent: '',
+ rid: '',
+ users: [],
+};
+
+const AuditPage = () => {
+ const t = useTranslation();
+
+ const { values, handlers } = useForm(initialValues);
+ const setData = useRef(() => {});
+
+ const [errors, setErrors] = useState({});
+
+ const {
+ msg,
+ type,
+ dateRange: {
+ start: startDate,
+ end: endDate,
+ },
+ visitor,
+ agent,
+ users,
+ rid,
+ } = values;
+
+ const {
+ handleMsg,
+ handleType,
+ handleVisitor,
+ handleAgent,
+ handleUsers,
+ handleRid,
+ handleDateRange,
+ } = handlers;
+
+ const useHandleType = (type) => useMutableCallback(() => {
+ handleType(type);
+ });
+
+ const onChangeUsers = useMutableCallback((value, action) => {
+ if (!action) {
+ if (users.includes(value)) {
+ return;
+ }
+ return handleUsers([...users, value]);
+ }
+ handleUsers(users.filter((current) => current !== value));
+ });
+
+ const apply = useMutableCallback(() => {
+ if (!rid && type === '') {
+ return setErrors({
+ rid: t('The_field_is_required', t('room_name')),
+ });
+ }
+
+ if (users.length < 2 && type === 'd') {
+ return setErrors({
+ users: t('Select_at_least_two_users'),
+ });
+ }
+
+ if (type === 'l') {
+ const errors = {};
+
+ if (visitor === '') {
+ errors.visitor = t('The_field_is_required', t('Visitor'));
+ }
+
+ if (agent === '') {
+ errors.visitor = t('The_field_is_required', t('Agent'));
+ }
+
+ if (errors.visitor || errors.agent) {
+ return setErrors(errors);
+ }
+ }
+
+ setErrors({});
+ setData.current({
+ msg,
+ type,
+ startDate: new Date(startDate),
+ endDate: new Date(endDate),
+ visitor,
+ agent,
+ users,
+ rid,
+ });
+ });
+
+ return
+
+
+ {t('Others')}
+ {t('Direct_Messages')}
+ {t('Omnichannel')}
+
+
+
+
+
+
+ {t('Message')}
+
+
+
+
+
+ {t('Date')}
+
+
+
+
+
+
+
+ {type === '' &&
+ {t('room_name')}
+
+
+
+ {errors.rid &&
+ {errors.rid}
+ }
+ }
+ {type === 'd' &&
+ {t('Users')}
+
+
+
+ {errors.users &&
+ {errors.users}
+ }
+ }
+ {type === 'l' &&
+
+
+ {t('Visitor')}
+
+
+
+ {errors.visitor &&
+ {errors.visitor}
+ }
+
+
+ {t('Agent')}
+
+
+
+ {errors.agent &&
+ {errors.agent}
+ }
+
+
+ }
+
+
+
+
+
+
+
+ ;
+};
+
+export default AuditPage;
diff --git a/ee/client/audit/AuditPage.stories.js b/ee/client/audit/AuditPage.stories.js
new file mode 100644
index 000000000000..d97a3e618a38
--- /dev/null
+++ b/ee/client/audit/AuditPage.stories.js
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import AuditPage from './AuditPage';
+
+export default {
+ title: 'ee/Audit',
+ component: AuditPage,
+};
+
+
+export const Default = () => ;
diff --git a/ee/client/audit/DateRangePicker.js b/ee/client/audit/DateRangePicker.js
new file mode 100644
index 000000000000..16b64373102e
--- /dev/null
+++ b/ee/client/audit/DateRangePicker.js
@@ -0,0 +1,122 @@
+import React, { useState, useMemo, useEffect } from 'react';
+import { Box, InputBox, Menu, Margins } from '@rocket.chat/fuselage';
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+
+import { useTranslation } from '../../../client/contexts/TranslationContext';
+
+const date = new Date();
+
+const formatToDateInput = (date) => date.toISOString().slice(0, 10);
+
+const todayDate = formatToDateInput(date);
+
+const getMonthRange = (monthsToSubtractFromToday) => {
+ const date = new Date();
+ return {
+ start: formatToDateInput(new Date(
+ date.getFullYear(),
+ date.getMonth() - monthsToSubtractFromToday,
+ 1)),
+ end: formatToDateInput(new Date(
+ date.getFullYear(),
+ date.getMonth() - monthsToSubtractFromToday + 1,
+ 0)),
+ };
+};
+
+const getWeekRange = (daysToSubtractFromStart, daysToSubtractFromEnd) => {
+ const date = new Date();
+ return {
+ start: formatToDateInput(new Date(
+ date.getFullYear(),
+ date.getMonth(),
+ date.getDate() - daysToSubtractFromStart)),
+ end: formatToDateInput(new Date(
+ date.getFullYear(),
+ date.getMonth(),
+ date.getDate() - daysToSubtractFromEnd)),
+ };
+};
+
+const DateRangePicker = ({ onChange = () => {}, ...props }) => {
+ const t = useTranslation();
+ const [range, setRange] = useState({ start: '', end: '' });
+
+ const {
+ start,
+ end,
+ } = range;
+
+ const handleStart = useMutableCallback(({ currentTarget }) => {
+ const rangeObj = {
+ start: currentTarget.value,
+ end: range.end,
+ };
+ setRange(rangeObj);
+ onChange(rangeObj);
+ });
+
+ const handleEnd = useMutableCallback(({ currentTarget }) => {
+ const rangeObj = {
+ end: currentTarget.value,
+ start: range.start,
+ };
+ setRange(rangeObj);
+ onChange(rangeObj);
+ });
+
+ const handleRange = useMutableCallback((range) => {
+ setRange(range);
+ onChange(range);
+ });
+
+ useEffect(() => {
+ handleRange({
+ start: todayDate,
+ end: todayDate,
+ });
+ }, [handleRange]);
+
+ const options = useMemo(() => ({
+ today: {
+ icon: 'history',
+ label: t('Today'),
+ action: () => { handleRange(getWeekRange(0, 0)); },
+ },
+ yesterday: {
+ icon: 'history',
+ label: t('Yesterday'),
+ action: () => { handleRange(getWeekRange(1, 1)); },
+ },
+ thisWeek: {
+ icon: 'history',
+ label: t('This_week'),
+ action: () => { handleRange(getWeekRange(7, 0)); },
+ },
+ previousWeek: {
+ icon: 'history',
+ label: t('Previous_week'),
+ action: () => { handleRange(getWeekRange(14, 7)); },
+ },
+ thisMonth: {
+ icon: 'history',
+ label: t('This_month'),
+ action: () => { handleRange(getMonthRange(0)); },
+ },
+ lastMonth: {
+ icon: 'history',
+ label: t('Previous_month'),
+ action: () => { handleRange(getMonthRange(1)); },
+ },
+ }), [handleRange, t]);
+
+ return
+
+
+
+
+
+ ;
+};
+
+export default DateRangePicker;
diff --git a/ee/client/audit/Result.js b/ee/client/audit/Result.js
new file mode 100644
index 000000000000..86f3ea5b87a8
--- /dev/null
+++ b/ee/client/audit/Result.js
@@ -0,0 +1,48 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Box } from '@rocket.chat/fuselage';
+import { useStableArray } from '@rocket.chat/fuselage-hooks';
+import { Template } from 'meteor/templating';
+import { Blaze } from 'meteor/blaze';
+
+
+import '../../app/auditing/client/templates/audit/audit.html';
+
+const Result = React.memo(({ setDataRef }) => {
+ const ref = useRef();
+
+ const [data, setData] = useState({});
+
+ const {
+ msg,
+ type,
+ startDate,
+ endDate,
+ visitor,
+ agent,
+ users = [],
+ rid,
+ } = data;
+
+ const stableUsers = useStableArray(users);
+
+ setDataRef.current = setData;
+
+ useEffect(() => {
+ const view = Blaze.renderWithData(Template.audit, {
+ msg,
+ type,
+ startDate,
+ endDate,
+ visitor,
+ agent,
+ users: stableUsers,
+ rid,
+ }, ref.current);
+
+ return () => Blaze.remove(view);
+ }, [agent, endDate, msg, rid, startDate, type, stableUsers, visitor]);
+
+ return ;
+});
+
+export default Result;
diff --git a/ee/client/audit/RoomAutoComplete.js b/ee/client/audit/RoomAutoComplete.js
new file mode 100644
index 000000000000..f65b9c673e81
--- /dev/null
+++ b/ee/client/audit/RoomAutoComplete.js
@@ -0,0 +1,32 @@
+import React, { useMemo, useState } from 'react';
+import { AutoComplete, Option, Options } from '@rocket.chat/fuselage';
+
+import { useEndpointDataExperimental } from '../../../client/hooks/useEndpointDataExperimental';
+import RoomAvatar from '../../../client/components/basic/avatar/RoomAvatar';
+
+const query = (term = '') => ({ selector: JSON.stringify({ term }) });
+
+const Avatar = ({ value, type, avatarETag, ...props }) => ;
+
+const RoomAutoComplete = React.memo((props) => {
+ const [filter, setFilter] = useState('');
+ const { data } = useEndpointDataExperimental('rooms.autocomplete.channelAndPrivate', useMemo(() => query(filter), [filter]));
+ const options = useMemo(() => (data && data.items.map(({ name, _id, avatarETag, t }) => ({
+ value: _id,
+ label: { name, avatarETag, type: t },
+ }))) || [], [data]);
+
+ return <> {label.name}>}
+ renderItem={({ value, label, ...props }) => } />}
+ options={ options }
+ />;
+});
+
+export default RoomAutoComplete;
diff --git a/ee/client/audit/UserAutoCompleteMultiple.js b/ee/client/audit/UserAutoCompleteMultiple.js
new file mode 100644
index 000000000000..a0cc526cdad4
--- /dev/null
+++ b/ee/client/audit/UserAutoCompleteMultiple.js
@@ -0,0 +1,31 @@
+import React, { useMemo, useState } from 'react';
+import { AutoComplete, Option, Options, Chip } from '@rocket.chat/fuselage';
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+
+import { useEndpointDataExperimental } from '../../../client/hooks/useEndpointDataExperimental';
+import UserAvatar from '../../../client/components/basic/avatar/UserAvatar';
+
+const query = (term = '') => ({ selector: JSON.stringify({ term }) });
+
+const Avatar = ({ value, ...props }) => ;
+
+const UserAutoCompleteMultiple = React.memo((props) => {
+ const [filter, setFilter] = useState('');
+ const { data } = useEndpointDataExperimental('users.autocomplete', useMemo(() => query(filter), [filter]));
+ const options = useMemo(() => (data && data.items.map((user) => ({ value: user.username, label: user.name }))) || [], [data]);
+ const onClickRemove = useMutableCallback((e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ props.onChange(e.currentTarget.value, 'remove');
+ });
+ return selected?.map((value) => {value})}
+ renderItem={({ value, ...props }) => } />}
+ options={ options }
+ />;
+});
+
+export default UserAutoCompleteMultiple;
diff --git a/ee/client/audit/VisitorAutoComplete.js b/ee/client/audit/VisitorAutoComplete.js
new file mode 100644
index 000000000000..e54c933240a6
--- /dev/null
+++ b/ee/client/audit/VisitorAutoComplete.js
@@ -0,0 +1,22 @@
+import React, { useMemo, useState } from 'react';
+import { AutoComplete, Option } from '@rocket.chat/fuselage';
+
+import { useEndpointDataExperimental } from '../../../client/hooks/useEndpointDataExperimental';
+
+const query = (term = '') => ({ selector: JSON.stringify({ term }) });
+
+const VisitorAutoComplete = React.memo((props) => {
+ const [filter, setFilter] = useState('');
+ const { data } = useEndpointDataExperimental('livechat/visitors.autocomplete', useMemo(() => query(filter), [filter]));
+ const options = useMemo(() => (data && data.items.map((user) => ({ value: user.username, label: user.name }))) || [], [data]);
+ return <>{label}>}
+ renderItem={({ value, ...props }) => }
+ options={ options }
+ />;
+});
+
+export default VisitorAutoComplete;
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index d6e264059b4e..b66b9250e49c 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -95,6 +95,7 @@
"Login_Logs_ForwardedForIp": "Show Forwarded IP on failed login attempts logs",
"Login_Logs_Username": "Show Username on failed login attempts logs",
"Login_Logs_UserAgent": "Show UserAgent on failed login attempts logs",
+ "Looked_for": "Looked for",
"Accounts_ForgetUserSessionOnWindowClose": "Forget User Session on Window Close",
"Accounts_Iframe_api_method": "Api Method",
"Accounts_Iframe_api_url": "API URL",
@@ -1698,6 +1699,7 @@
"FileSize_MB": "__fileSize__ MB",
"FileSize_Bytes": "__fileSize__ Bytes",
"Filter": "Filter",
+ "Filters_applied": "Filters applied",
"Financial_Services": "Financial Services",
"First_Channel_After_Login": "First Channel After Login",
"First_response_time": "First Response Time",
@@ -3187,6 +3189,7 @@
"Security": "Security",
"Select_a_department": "Select a department",
"Select_a_user": "Select a user",
+ "Select_at_least_two_users": "Select at least two users",
"Select_an_avatar": "Select an avatar",
"Select_an_option": "Select an option",
"Select_department": "Select a department",
@@ -3934,6 +3937,7 @@
"Welcome_to": "Welcome to __Site_Name__",
"Welcome_to_the": "Welcome to the",
"Where_are_the_messages_being_sent?": "Where are the messages being sent?",
+ "When": "When",
"When_is_the_chat_busier?": "When is the chat busier?",
"When_a_line_starts_with_one_of_there_words_post_to_the_URLs_below": "When a line starts with one of these words, post to the URL(s) below",
"Why_do_you_want_to_report_question_mark": "Why do you want to report?",