From 7feaa376e6d83139e7d16b92d23aca3e66df7516 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 6 May 2022 17:31:35 +0200 Subject: [PATCH] feat(ui) - audit - list and search --- frontend/app/api_client.js | 3 +- .../AuditDetailModal/AuditDetailModal.tsx | 7 ++- .../Client/Audit/AuditList/AuditList.tsx | 58 +++++++++++++++++-- .../Audit/AuditListItem/AuditListItem.tsx | 11 +++- .../AuditSearchField/AuditSearchField.tsx | 33 +++++++++++ .../Client/Audit/AuditSearchField/index.ts | 1 + .../Client/Audit/AuditView/AuditView.tsx | 19 +++++- frontend/app/components/Client/Client.js | 2 + .../Client/ManageUsers/ManageUsers.js | 2 +- .../Client/PreferencesMenu/PreferencesMenu.js | 13 ++++- frontend/app/mstore/auditStore.ts | 43 ++++++++++++++ frontend/app/mstore/index.tsx | 5 +- frontend/app/mstore/types/audit.ts | 36 ++++++++++++ frontend/app/mstore/types/user.ts | 2 +- frontend/app/routes.js | 3 +- frontend/app/services/AuditService.ts | 25 ++++++++ frontend/app/services/index.ts | 4 +- frontend/app/svg/icons/list-ul.svg | 3 + frontend/app/utils.js | 6 ++ 19 files changed, 255 insertions(+), 21 deletions(-) create mode 100644 frontend/app/components/Client/Audit/AuditSearchField/AuditSearchField.tsx create mode 100644 frontend/app/components/Client/Audit/AuditSearchField/index.ts create mode 100644 frontend/app/mstore/auditStore.ts create mode 100644 frontend/app/mstore/types/audit.ts create mode 100644 frontend/app/services/AuditService.ts create mode 100644 frontend/app/svg/icons/list-ul.svg diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 98a1f4dfd..a5fae45e7 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -24,7 +24,8 @@ const siteIdRequiredPaths = [ '/heatmaps', '/custom_metrics', '/dashboards', - '/metrics' + '/metrics', + '/trails', // '/custom_metrics/sessions', ]; diff --git a/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx b/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx index 03a5eee0d..b7c257e3e 100644 --- a/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx +++ b/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx @@ -2,8 +2,11 @@ import React from 'react'; function AuditDetailModal(props) { return ( -
- +
+

Audit Details

+
+ +
); } diff --git a/frontend/app/components/Client/Audit/AuditList/AuditList.tsx b/frontend/app/components/Client/Audit/AuditList/AuditList.tsx index fc135f102..2b4249155 100644 --- a/frontend/app/components/Client/Audit/AuditList/AuditList.tsx +++ b/frontend/app/components/Client/Audit/AuditList/AuditList.tsx @@ -1,14 +1,60 @@ -import React from 'react'; +import { useModal } from 'App/components/Modal'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import React, { useEffect } from 'react'; +import { Loader, Pagination, NoContent } from 'UI'; +import AuditDetailModal from '../AuditDetailModal'; +import AuditListItem from '../AuditListItem'; interface Props { } function AuditList(props: Props) { - return ( -
- List -
- ); + const { auditStore } = useStore(); + const loading = useObserver(() => auditStore.isLoading); + const list = useObserver(() => auditStore.list); + const searchQuery = useObserver(() => auditStore.searchQuery); + const page = useObserver(() => auditStore.page); + const { showModal } = useModal(); + + useEffect(() => { + auditStore.fetchAudits({ + page: auditStore.page, + limit: auditStore.pageSize, + query: auditStore.searchQuery, + }); + }, [page, searchQuery]); + + return useObserver(() => ( + + +
+
Name
+
Status
+
Time
+
+ + {list.map((item, index) => ( +
+ showModal(, { right: true })} + /> +
+ ))} + +
+ auditStore.updateKey('page', page)} + limit={auditStore.pageSize} + debounceRequest={200} + /> +
+
+
+ )); } export default AuditList; \ No newline at end of file diff --git a/frontend/app/components/Client/Audit/AuditListItem/AuditListItem.tsx b/frontend/app/components/Client/Audit/AuditListItem/AuditListItem.tsx index b587f08e7..492d5c140 100644 --- a/frontend/app/components/Client/Audit/AuditListItem/AuditListItem.tsx +++ b/frontend/app/components/Client/Audit/AuditListItem/AuditListItem.tsx @@ -1,12 +1,17 @@ import React from 'react'; +import { checkForRecent } from 'App/date'; interface Props { - + audit: any; + onShowDetails: () => void; } function AuditListItem(props: Props) { + const { audit, onShowDetails } = props; return ( -
- +
+
asd
+
{audit.action}
+
{audit.createdAt && checkForRecent(audit.createdAt, 'LLL dd, yyyy, hh:mm a')}
); } diff --git a/frontend/app/components/Client/Audit/AuditSearchField/AuditSearchField.tsx b/frontend/app/components/Client/Audit/AuditSearchField/AuditSearchField.tsx new file mode 100644 index 000000000..8c0d32d5b --- /dev/null +++ b/frontend/app/components/Client/Audit/AuditSearchField/AuditSearchField.tsx @@ -0,0 +1,33 @@ +import React, { useEffect } from 'react'; +import { Icon } from 'UI'; +import { debounce } from 'App/utils'; + +let debounceUpdate: any = () => {} +interface Props { + onChange: (value: string) => void; +} +function AuditSearchField(props: Props) { + const { onChange } = props; + + useEffect(() => { + debounceUpdate = debounce((value) => onChange(value), 500); + }, []) + + const write = ({ target: { name, value } }) => { + debounceUpdate(value); + } + + return ( +
+ + +
+ ); +} + +export default AuditSearchField; \ No newline at end of file diff --git a/frontend/app/components/Client/Audit/AuditSearchField/index.ts b/frontend/app/components/Client/Audit/AuditSearchField/index.ts new file mode 100644 index 000000000..646947095 --- /dev/null +++ b/frontend/app/components/Client/Audit/AuditSearchField/index.ts @@ -0,0 +1 @@ +export { default } from './AuditSearchField'; \ No newline at end of file diff --git a/frontend/app/components/Client/Audit/AuditView/AuditView.tsx b/frontend/app/components/Client/Audit/AuditView/AuditView.tsx index 02554fe1d..ec9904567 100644 --- a/frontend/app/components/Client/Audit/AuditView/AuditView.tsx +++ b/frontend/app/components/Client/Audit/AuditView/AuditView.tsx @@ -1,11 +1,24 @@ import React from 'react'; +import { PageTitle } from 'UI'; +import AuditList from '../AuditList'; +import AuditSearchField from '../AuditSearchField'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; function AuditView(props) { - return ( + const { auditStore } = useStore(); + return useObserver(() => (
- View +
+ +
+ auditStore.updateKey('searchQuery', value) }/> +
+
+ +
- ); + )); } export default AuditView; \ No newline at end of file diff --git a/frontend/app/components/Client/Client.js b/frontend/app/components/Client/Client.js index 518148a5c..c58056b5f 100644 --- a/frontend/app/components/Client/Client.js +++ b/frontend/app/components/Client/Client.js @@ -8,6 +8,7 @@ import ProfileSettings from './ProfileSettings'; import Integrations from './Integrations'; import ManageUsers from './ManageUsers'; import UserView from './Users/UsersView'; +import AuditView from './Audit/AuditView'; import Sites from './Sites'; import CustomFields from './CustomFields'; import Webhooks from './Webhooks'; @@ -43,6 +44,7 @@ export default class Client extends React.PureComponent { + ) diff --git a/frontend/app/components/Client/ManageUsers/ManageUsers.js b/frontend/app/components/Client/ManageUsers/ManageUsers.js index 9071ee46b..1504a0631 100644 --- a/frontend/app/components/Client/ManageUsers/ManageUsers.js +++ b/frontend/app/components/Client/ManageUsers/ManageUsers.js @@ -236,7 +236,7 @@ class ManageUsers extends React.PureComponent { title="No users are available." size="small" show={ members.size === 0 } - icon + animatedIcon="empty-state" >
{ diff --git a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js index 0c57c1ab7..682d1519a 100644 --- a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js +++ b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js @@ -78,6 +78,17 @@ function PreferencesMenu({ activeTab, appearance, history, isEnterprise }) { />
)} + + { isEnterprise && ( +
+ setTab(CLIENT_TABS.AUDIT) } + /> +
+ )}
setTab(CLIENT_TABS.NOTIFICATIONS) } /> -
+
) } diff --git a/frontend/app/mstore/auditStore.ts b/frontend/app/mstore/auditStore.ts new file mode 100644 index 000000000..11b0ba9f4 --- /dev/null +++ b/frontend/app/mstore/auditStore.ts @@ -0,0 +1,43 @@ +import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { auditService } from "App/services" +import Audit from './types/audit' + +export default class AuditStore { + list: any[] = []; + total: number = 0; + page: number = 1; + pageSize: number = 20; + searchQuery: string = ''; + isLoading: boolean = false; + + constructor() { + makeAutoObservable(this, { + searchQuery: observable, + updateKey: action, + fetchAudits: action, + }) + } + + updateKey(key: string, value: any) { + console.log(key, value) + this[key] = value; + } + + fetchAudits = (data: any): Promise => { + this.isLoading = true; + return new Promise((resolve, reject) => { + auditService.all(data).then(response => { + console.log('response', response); + runInAction(() => { + this.list = response.sessions.map(item => Audit.fromJson(item)) + this.total = response.count + }) + resolve() + }).catch(error => { + reject(error) + }).finally(() => { + this.isLoading = false; + }) + }) + } +} \ No newline at end of file diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index 251b70f9e..2a1897b42 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -4,8 +4,9 @@ import MetricStore, { IMetricStore } from './metricStore'; import UserStore from './userStore'; import RoleStore from './roleStore'; import APIClient from 'App/api_client'; -import { dashboardService, metricService, sessionService, userService } from 'App/services'; +import { dashboardService, metricService, sessionService, userService, auditService } from 'App/services'; import SettingsStore from './settingsStore'; +import AuditStore from './auditStore'; export class RootStore { dashboardStore: IDashboardSotre; @@ -13,6 +14,7 @@ export class RootStore { settingsStore: SettingsStore; userStore: UserStore; roleStore: RoleStore; + auditStore: AuditStore; constructor() { this.dashboardStore = new DashboardStore(); @@ -20,6 +22,7 @@ export class RootStore { this.settingsStore = new SettingsStore(); this.userStore = new UserStore(); this.roleStore = new RoleStore(); + this.auditStore = new AuditStore(); } initClient() { diff --git a/frontend/app/mstore/types/audit.ts b/frontend/app/mstore/types/audit.ts new file mode 100644 index 000000000..b9c30343f --- /dev/null +++ b/frontend/app/mstore/types/audit.ts @@ -0,0 +1,36 @@ +import { DateTime } from 'luxon'; +import { unserscoreToSpaceAndCapitalize } from 'App/utils'; + +export default class Audit { + id: string = ''; + userName: string = ''; + action: string = ''; + createdAt: any = null; + endPoint: string = ''; + parameters: any = {}; + method: string = ''; + status: string = ''; + + constructor() { + } + + static fromJson(json: any): Audit { + const audit = new Audit(); + audit.id = json.rn; + audit.userName = json.userName; + audit.action = unserscoreToSpaceAndCapitalize(json.action); + audit.createdAt = json.createdAt && DateTime.fromMillis(json.createdAt || 0); + audit.endPoint = json.endPoint; + audit.parameters = json.parameters; + audit.method = json.method; + audit.status = json.status; + return audit; + } + + toJson(): any { + return { + id: this.id, + userName: this.userName + }; + } +} \ No newline at end of file diff --git a/frontend/app/mstore/types/user.ts b/frontend/app/mstore/types/user.ts index c69b31067..53ae5dbf0 100644 --- a/frontend/app/mstore/types/user.ts +++ b/frontend/app/mstore/types/user.ts @@ -60,7 +60,7 @@ export default class User implements IUser { this.userId = json.userId || json.id; // TODO api returning id this.name = json.name; this.email = json.email; - this.createdAt = json.createdAt && DateTime.fromISO(json.createdAt || 0) + this.createdAt = json.createdAt && DateTime.fromMillis(json.createdAt || 0) this.isAdmin = json.admin this.isSuperAdmin = json.superAdmin this.isJoined = json.joined diff --git a/frontend/app/routes.js b/frontend/app/routes.js index 7a6c5379e..4492a4b63 100644 --- a/frontend/app/routes.js +++ b/frontend/app/routes.js @@ -61,10 +61,11 @@ export const CLIENT_TABS = { PROFILE: 'account', MANAGE_USERS: 'team', MANAGE_ROLES: 'roles', - SITES: 'projects', + SITES: 'projects', CUSTOM_FIELDS: 'metadata', WEBHOOKS: 'webhooks', NOTIFICATIONS: 'notifications', + AUDIT: 'audit', }; export const CLIENT_DEFAULT_TAB = CLIENT_TABS.PROFILE; const routerClientTabString = `:activeTab(${ Object.values(CLIENT_TABS).join('|') })`; diff --git a/frontend/app/services/AuditService.ts b/frontend/app/services/AuditService.ts new file mode 100644 index 000000000..0a93d6bb6 --- /dev/null +++ b/frontend/app/services/AuditService.ts @@ -0,0 +1,25 @@ +import APIClient from 'App/api_client'; + +export default class AuditService { + private client: APIClient; + + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } + + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } + + all(data: any): Promise { + return this.client.post('/trails', data) + .then(response => response.json()) + .then(response => response.data[0] || []); + } + + one(id: string): Promise { + return this.client.get('/trails/' + id) + .then(response => response.json()) + .then(response => response.data || {}); + } +} \ No newline at end of file diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index 3cb64a7ad..5b4d59651 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -2,8 +2,10 @@ import DashboardService, { IDashboardService } from "./DashboardService"; import MetricService, { IMetricService } from "./MetricService"; import SessionSerivce from "./SessionService"; import UserService from "./UserService"; +import AuditService from './AuditService'; export const dashboardService: IDashboardService = new DashboardService(); export const metricService: IMetricService = new MetricService(); export const sessionService: SessionSerivce = new SessionSerivce(); -export const userService: UserService = new UserService(); \ No newline at end of file +export const userService: UserService = new UserService(); +export const auditService: AuditService = new AuditService(); \ No newline at end of file diff --git a/frontend/app/svg/icons/list-ul.svg b/frontend/app/svg/icons/list-ul.svg new file mode 100644 index 000000000..a07021b20 --- /dev/null +++ b/frontend/app/svg/icons/list-ul.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/utils.js b/frontend/app/utils.js index bdece4058..571c18561 100644 --- a/frontend/app/utils.js +++ b/frontend/app/utils.js @@ -264,4 +264,10 @@ export const convertElementToImage = async (el) => { }, }); return image; +} + +export const unserscoreToSpaceAndCapitalize = (str) => { + return str.replace(/_/g, ' ').replace(/\w\S*/g, (txt) => { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); } \ No newline at end of file