-
- {/*
*/}
+
+ Audit Trail
+ {total}
-
+ } />
+
+
+
+
+
auditStore.updateKey('searchQuery', value) }/>
+
+
+
diff --git a/frontend/app/components/shared/Select/Select.tsx b/frontend/app/components/shared/Select/Select.tsx
index a69fb931a..8d963c16b 100644
--- a/frontend/app/components/shared/Select/Select.tsx
+++ b/frontend/app/components/shared/Select/Select.tsx
@@ -8,9 +8,10 @@ interface Props {
isSearchable?: boolean;
defaultValue?: string;
plain?: boolean;
+ components?: any;
[x:string]: any;
}
-export default function({ plain = false, options, isSearchable = false, defaultValue = '', ...rest }: Props) {
+export default function({ plain = false, options, isSearchable = false, components = {}, defaultValue = '', ...rest }: Props) {
const customStyles = {
option: (provided, state) => ({
...provided,
@@ -19,6 +20,7 @@ export default function({ plain = false, options, isSearchable = false, defaultV
menu: (provided, state) => ({
...provided,
top: 31,
+ minWidth: 'fit-content',
}),
control: (provided) => {
const obj = {
@@ -61,6 +63,7 @@ export default function({ plain = false, options, isSearchable = false, defaultV
components={{
IndicatorSeparator: () => null,
DropdownIndicator,
+ ...components,
}}
styles={customStyles}
theme={(theme) => ({
diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx
index 5fe03120e..0713b2dc4 100644
--- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx
+++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx
@@ -1,22 +1,71 @@
import React from 'react';
-import { DATE_RANGE_OPTIONS } from 'App/dateRange'
+import { DATE_RANGE_OPTIONS, CUSTOM_RANGE } from 'App/dateRange'
import Select from 'Shared/Select';
+import Period, { LAST_7_DAYS } from 'Types/app/period';
+import { components } from 'react-select';
+import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup';
+import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
interface Props {
- startDate: string;
- endDate: string;
- range: string;
- onChange: (startDate: string, endDate: string) => void;
+ period: any,
+ onChange: (data: any) => void;
}
function SelectDateRange(props: Props) {
+ const [isCustom, setIsCustom] = React.useState(false);
+ const { period } = props;
+ const selectedValue = DATE_RANGE_OPTIONS.find(obj => obj.value === period.rangeName)
+
+ const onChange = (value: any) => {
+ if (value === CUSTOM_RANGE) {
+ setIsCustom(true);
+ } else {
+ props.onChange(new Period({ rangeName: value }));
+ }
+ }
+
+ const onApplyDateRange = (value: any) => {
+ props.onChange(new Period({ rangeName: CUSTOM_RANGE, start: value.start, end: value.end }));
+ setIsCustom(false);
+ }
+
return (
-
+
);
}
-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