diff --git a/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx b/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx index b7c257e3e..934604dbb 100644 --- a/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx +++ b/frontend/app/components/Client/Audit/AuditDetailModal/AuditDetailModal.tsx @@ -1,11 +1,50 @@ import React from 'react'; +import { JSONTree } from 'UI'; +import { checkForRecent } from 'App/date'; + +interface Props { + audit: any; +} +function AuditDetailModal(props: Props) { + const { audit } = props; + // const jsonResponse = typeof audit.payload === 'string' ? JSON.parse(audit.payload) : audit.payload; + // console.log('jsonResponse', jsonResponse) -function AuditDetailModal(props) { return ( -
+

Audit Details

+
{ 'URL'}
+
{ audit.endPoint }
+
+
+
Username
+
{audit.username}
+
+
+
Created At
+
{audit.createdAt && checkForRecent(audit.createdAt, 'LLL dd, yyyy, hh:mm a')}
+
+
+ +
+
+
Action
+
{audit.action}
+
+
+
Method
+
{audit.method}
+
+
+ + { audit.payload && ( +
+
Payload
+ +
+ )}
); diff --git a/frontend/app/components/Client/Audit/AuditList/AuditList.tsx b/frontend/app/components/Client/Audit/AuditList/AuditList.tsx index 549b25b6d..5ab4c0d3a 100644 --- a/frontend/app/components/Client/Audit/AuditList/AuditList.tsx +++ b/frontend/app/components/Client/Audit/AuditList/AuditList.tsx @@ -16,16 +16,21 @@ function AuditList(props: Props) { const searchQuery = useObserver(() => auditStore.searchQuery); const page = useObserver(() => auditStore.page); const order = useObserver(() => auditStore.order); + const period = useObserver(() => auditStore.period); const { showModal } = useModal(); + console.log('AuditList', period.toTimestamps()); useEffect(() => { + const { startTimestamp, endTimestamp } = period.toTimestamps(); auditStore.fetchAudits({ page: auditStore.page, limit: auditStore.pageSize, query: auditStore.searchQuery, order: auditStore.order, + startDate: startTimestamp, + endDate: endTimestamp, }); - }, [page, searchQuery, order]); + }, [page, searchQuery, order, period]); return useObserver(() => ( @@ -40,7 +45,7 @@ function AuditList(props: Props) {
showModal(, { right: true })} + onShowDetails={() => showModal(, { right: true })} />
))} diff --git a/frontend/app/components/Client/Audit/AuditSearchField/AuditSearchField.tsx b/frontend/app/components/Client/Audit/AuditSearchField/AuditSearchField.tsx index 8c0d32d5b..5574da847 100644 --- a/frontend/app/components/Client/Audit/AuditSearchField/AuditSearchField.tsx +++ b/frontend/app/components/Client/Audit/AuditSearchField/AuditSearchField.tsx @@ -18,7 +18,7 @@ function AuditSearchField(props: Props) { } return ( -
+
auditStore.order); + const total = useObserver(() => auditStore.total); + + const exportToCsv = () => { + auditStore.exportToCsv(); + } + + const onChange = (data) => { + auditStore.setDateRange(data); + } return useObserver(() => (
- -
-
- {/* */} + + Audit Trail + {total}
-
+ } /> +
+
+ +
+
onChange(value)} + components={{ SingleValue: ({ children, ...props} : any) => { + return ( + + {period.rangeName === CUSTOM_RANGE ? period.rangeFormatted() : children} + + ) + } }} + period={period} /> + { + isCustom && + setIsCustom(false)} + > +
+ setIsCustom(false) } + selectedDateRange={ period.range } + /> +
+
+ }
); } -export default SelectDateRange; \ No newline at end of file +export default SelectDateRange; + + \ No newline at end of file diff --git a/frontend/app/mstore/auditStore.ts b/frontend/app/mstore/auditStore.ts index b3df0aa6d..de7bd4433 100644 --- a/frontend/app/mstore/auditStore.ts +++ b/frontend/app/mstore/auditStore.ts @@ -2,6 +2,8 @@ import { makeAutoObservable, runInAction, observable, action, reaction } from "m import { auditService } from "App/services" import Audit from './types/audit' import Period, { LAST_7_DAYS } from 'Types/app/period'; +import { toast } from 'react-toastify'; +import { exportCSVFile } from 'App/utils'; export default class AuditStore { list: any[] = []; @@ -11,18 +13,20 @@ export default class AuditStore { searchQuery: string = ''; isLoading: boolean = false; order: string = 'desc'; - period: Period = Period({ rangeName: LAST_7_DAYS }) + period: Period|null = Period({ rangeName: LAST_7_DAYS }) constructor() { makeAutoObservable(this, { searchQuery: observable, + period: observable, updateKey: action, fetchAudits: action, + setDateRange: action, }) } setDateRange(data: any) { - this.period = new Period(data); + this['period'] = data; } updateKey(key: string, value: any) { @@ -45,4 +49,34 @@ export default class AuditStore { }) }) } + + fetchAllAudits = async (data: any): Promise => { + return new Promise((resolve, reject) => { + auditService.all(data).then((data) => { + const headers = [ + { label: 'User', key: 'username' }, + { label: 'Email', key: 'email' }, + { label: 'UserID', key: 'userId' }, + { label: 'Method', key: 'method' }, + { label: 'Action', key: 'action' }, + { label: 'Endpoint', key: 'endpoint' }, + // { label: 'Status', key: 'status' }, + { label: 'Created At', key: 'createdAt' }, + ] + // console.log('data', data) + exportCSVFile(headers, data.sessions, `audit-${new Date().toLocaleDateString()}.csv`); + resolve(data) + }).catch(error => { + reject(error) + }) + }) + } + + exportToCsv = async (): Promise => { + const promise = this.fetchAllAudits({ limit: this.total }) + toast.promise(promise, { + pending: 'Exporting...', + success: 'Export successful', + }) + } } \ No newline at end of file diff --git a/frontend/app/mstore/types/audit.ts b/frontend/app/mstore/types/audit.ts index 104ff447e..dd8f7c095 100644 --- a/frontend/app/mstore/types/audit.ts +++ b/frontend/app/mstore/types/audit.ts @@ -4,12 +4,14 @@ import { unserscoreToSpaceAndCapitalize } from 'App/utils'; export default class Audit { id: string = ''; username: string = ''; + email: string = ''; action: string = ''; createdAt: any = null; endPoint: string = ''; parameters: any = {}; method: string = ''; status: string = ''; + payload: any = {} constructor() { } @@ -20,10 +22,12 @@ export default class Audit { audit.username = json.username; audit.action = unserscoreToSpaceAndCapitalize(json.action); audit.createdAt = json.createdAt && DateTime.fromMillis(json.createdAt || 0); - audit.endPoint = json.endPoint; + audit.endPoint = json.endpoint; audit.parameters = json.parameters; audit.method = json.method; - audit.status = json.status; + audit.status = json.status + audit.email = json.email + audit.payload = typeof json.payload === 'string' ? JSON.parse(json.payload) : json.payload return audit; } diff --git a/frontend/app/svg/icons/grid-3x3.svg b/frontend/app/svg/icons/grid-3x3.svg new file mode 100644 index 000000000..c40d98c96 --- /dev/null +++ b/frontend/app/svg/icons/grid-3x3.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/types/app/period.js b/frontend/app/types/app/period.js index deebf9cad..b4f8798c5 100644 --- a/frontend/app/types/app/period.js +++ b/frontend/app/types/app/period.js @@ -103,6 +103,10 @@ export default Record({ endTimestamp: this.end, }; }, + rangeFormatted(format = 'MMM Do YY, hh:mm A') { + console.log('period', this) + return this.range.start.format(format) + ' - ' + this.range.end.format(format); + }, toTimestampstwo() { return { startTimestamp: this.start / 1000, diff --git a/frontend/app/utils.js b/frontend/app/utils.js index 571c18561..be25b585e 100644 --- a/frontend/app/utils.js +++ b/frontend/app/utils.js @@ -270,4 +270,57 @@ export const unserscoreToSpaceAndCapitalize = (str) => { return str.replace(/_/g, ' ').replace(/\w\S*/g, (txt) => { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); +} + +export const convertToCSV = (headers, objArray) => { + var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray; + var str = ''; + const headersMap = headers.reduce((acc, curr) => { + acc[curr.key] = curr; + return acc; + }, {}); + console.log('headersMap', headersMap) + + // csv header line + // comma seprated header line from array + str += headers.map(h => h.label).join(',') + '\r\n'; + + for (var i = 0; i < array.length; i++) { + var line = ''; + for (var index in headersMap) { + if (line !== '') line += ','; + line += array[i][index]; + } + str += line + '\r\n'; + } + + + return str; +} + +export const exportCSVFile = (headers, items, fileTitle) => { + // if (headers) { + // items.unshift(headers); + // } + + var jsonObject = JSON.stringify(items); + var csv = convertToCSV(headers, jsonObject); + var exportedFilenmae = fileTitle + '.csv' || 'export.csv'; + + var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + if (navigator.msSaveBlob) { // IE 10+ + navigator.msSaveBlob(blob, exportedFilenmae); + } else { + var link = document.createElement("a"); + if (link.download !== undefined) { // feature detection + // Browsers that support HTML5 download attribute + var url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", exportedFilenmae); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } } \ No newline at end of file