feat: audit logs UI (#6803)

* feat: init auditlogs ui

* chore: add api

* fix: action

* chore: add action,username,time

* feat: add pagination support

* chore: format time

* chore: refactor

* chore: refactor auditlogs api response

* chore: update icon

* chore: rubocop fixes

* Fixes the way meta is handled in store

* Fixes meta not appearing issue

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
This commit is contained in:
Vishnu Narayanan
2023-04-17 19:11:05 +05:30
committed by GitHub
parent 80dcd17f6e
commit 9e2f991484
15 changed files with 314 additions and 7 deletions

View File

@@ -0,0 +1,16 @@
/* global axios */
import ApiClient from './ApiClient';
class AuditLogs extends ApiClient {
constructor() {
super('audit_logs', { accountScoped: true });
}
get({ page }) {
const url = page ? `${this.url}?page=${page}` : this.url;
return axios.get(url);
}
}
export default new AuditLogs();

View File

@@ -8,6 +8,7 @@ const settings = accountId => ({
'agent_list',
'attributes_list',
'automation_list',
'auditlogs_list',
'billing_settings_index',
'canned_list',
'general_settings_index',
@@ -150,6 +151,14 @@ const settings = accountId => ({
toStateName: 'billing_settings_index',
showOnlyOnCloud: true,
},
{
icon: 'key',
label: 'AUDIT_LOGS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/audit-log/list`),
toStateName: 'auditlogs_list',
beta: true,
},
],
});

View File

@@ -0,0 +1,24 @@
{
"AUDIT_LOGS": {
"HEADER": "Audit Logs",
"HEADER_BTN_TXT": "Add Audit Logs",
"LOADING": "Fetching Audit Logs",
"SEARCH_404": "There are no items matching this query",
"SIDEBAR_TXT": "<p><b>Audit Logs</b> </p><p> Audit Logs are trails for events and actions in a Chatwoot System. </p>",
"LIST": {
"404": "There are no Audit Logs available in this account.",
"TITLE": "Manage Audit Logs",
"DESC": "Audit Logs are trails for events and actions in a Chatwoot System.",
"TABLE_HEADER": [
"User",
"Action",
"IP Address",
"Time"
]
},
"API": {
"SUCCESS_MESSAGE": "AuditLogs retrieved successfully",
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
}
}
}

View File

@@ -2,6 +2,7 @@ import advancedFilters from './advancedFilters.json';
import agentBots from './agentBots.json';
import agentMgmt from './agentMgmt.json';
import attributesMgmt from './attributesMgmt.json';
import auditLogs from './auditLogs.json';
import automation from './automation.json';
import bulkActions from './bulkActions.json';
import campaign from './campaign.json';
@@ -34,6 +35,7 @@ export default {
...agentBots,
...agentMgmt,
...attributesMgmt,
...auditLogs,
...automation,
...bulkActions,
...campaign,

View File

@@ -199,6 +199,7 @@
"HOME": "Home",
"AGENTS": "Agents",
"AGENT_BOTS": "Bots",
"AUDIT_LOGS": "Audit Logs",
"INBOXES": "Inboxes",
"NOTIFICATIONS": "Notifications",
"CANNED_RESPONSES": "Canned Responses",

View File

@@ -0,0 +1,107 @@
<template>
<div class="column content-box">
<!-- List Audit Logs -->
<div class="row">
<div class="small-8 columns with-right-space ">
<p
v-if="!uiFlags.fetchingList && !records.length"
class="no-items-error-message"
>
{{ $t('AUDIT_LOGS.LIST.404') }}
</p>
<woot-loading-state
v-if="uiFlags.fetchingList"
:message="$t('AUDIT_LOGS.LOADING')"
/>
<table
v-if="!uiFlags.fetchingList && records.length"
class="woot-table"
>
<thead>
<!-- Header -->
<th
v-for="thHeader in $t('AUDIT_LOGS.LIST.TABLE_HEADER')"
:key="thHeader"
>
{{ thHeader }}
</th>
</thead>
<tbody>
<tr v-for="auditLogItem in records" :key="auditLogItem.id">
<td class="wrap-break-words">{{ auditLogItem.username }}</td>
<td class="wrap-break-words">
{{ auditLogItem.auditable_type }}.{{ auditLogItem.action }}
</td>
<td class="remote-address">
{{ auditLogItem.remote_address }}
</td>
<td class="wrap-break-words">
{{ dynamicTime(auditLogItem.created_at) }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<table-footer
:current-page="Number(meta.currentPage)"
:total-count="meta.totalEntries"
:page-size="meta.perPage"
@page-change="onPageChange"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import TableFooter from 'dashboard/components/widgets/TableFooter';
import timeMixin from 'dashboard/mixins/time';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
TableFooter,
},
mixins: [alertMixin, timeMixin],
data() {
return {
loading: {},
auditLogsAPI: {
message: '',
},
};
},
computed: {
...mapGetters({
records: 'auditlogs/getAuditLogs',
uiFlags: 'auditlogs/getUIFlags',
meta: 'auditlogs/getMeta',
}),
},
mounted() {
// Fetch API Call
this.$store.dispatch('auditlogs/fetch', { page: 1 });
},
methods: {
onPageChange(page) {
window.history.pushState({}, null, `${this.$route.path}?page=${page}`);
try {
this.$store.dispatch('auditlogs/fetch', { page });
} catch (error) {
const errorMessage =
error?.message || this.$t('AUDIT_LOGS.API.ERROR_MESSAGE');
this.showAlert(errorMessage);
}
},
},
};
</script>
<style scoped>
.remote-address {
width: 14rem;
}
.wrap-break-words {
word-break: break-all;
white-space: normal;
}
</style>

View File

@@ -0,0 +1,30 @@
import SettingsContent from '../Wrapper';
import AuditLogsHome from './Index';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/audit-log'),
component: SettingsContent,
props: {
headerTitle: 'AUDIT_LOGS.HEADER',
icon: 'key',
showNewButton: false,
},
children: [
{
path: '',
name: 'auditlogs_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'auditlogs_list',
roles: ['administrator'],
component: AuditLogsHome,
},
],
},
],
};

View File

@@ -4,6 +4,7 @@ import agent from './agents/agent.routes';
import agentBot from './agentBots/agentBot.routes';
import attributes from './attributes/attributes.routes';
import automation from './automation/automation.routes';
import auditlogs from './auditlogs/audit.routes';
import billing from './billing/billing.routes';
import campaigns from './campaigns/campaigns.routes';
import canned from './canned/canned.routes';
@@ -35,6 +36,7 @@ export default {
...agentBot.routes,
...attributes.routes,
...automation.routes,
...auditlogs.routes,
...billing.routes,
...campaigns.routes,
...canned.routes,

View File

@@ -7,6 +7,7 @@ import agents from './modules/agents';
import articles from './modules/helpCenterArticles';
import attributes from './modules/attributes';
import auth from './modules/auth';
import auditlogs from './modules/auditlogs';
import automations from './modules/automations';
import bulkActions from './modules/bulkActions';
import campaigns from './modules/campaigns';
@@ -71,6 +72,7 @@ export default new Vuex.Store({
attributes,
auth,
automations,
auditlogs,
bulkActions,
campaigns,
cannedResponse,

View File

@@ -0,0 +1,79 @@
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
import * as types from '../mutation-types';
import AuditLogsAPI from '../../api/auditLogs';
import { throwErrorMessage } from 'dashboard/store/utils/api';
const state = {
records: [],
meta: {
currentPage: 1,
perPage: 15,
totalEntries: 0,
},
uiFlags: {
fetchingList: false,
},
};
const getters = {
getAuditLogs(_state) {
return _state.records;
},
getUIFlags(_state) {
return _state.uiFlags;
},
getMeta(_state) {
return _state.meta;
},
};
const actions = {
async fetch({ commit }, { page } = {}) {
commit(types.default.SET_AUDIT_LOGS_UI_FLAG, { fetchingList: true });
try {
const response = await AuditLogsAPI.get({ page });
const { audit_logs: logs = [] } = response.data;
const {
total_entries: totalEntries,
per_page: perPage,
current_page: currentPage,
} = response.data;
commit(types.default.SET_AUDIT_LOGS, logs);
commit(types.default.SET_AUDIT_LOGS_META, {
totalEntries,
perPage,
currentPage,
});
commit(types.default.SET_AUDIT_LOGS_UI_FLAG, { fetchingList: false });
return logs;
} catch (error) {
commit(types.default.SET_AUDIT_LOGS_UI_FLAG, { fetchingList: false });
return throwErrorMessage(error);
}
},
};
const mutations = {
[types.default.SET_AUDIT_LOGS_UI_FLAG](_state, data) {
_state.uiFlags = {
..._state.uiFlags,
...data,
};
},
[types.default.SET_AUDIT_LOGS]: MutationHelpers.set,
[types.default.SET_AUDIT_LOGS_META](_state, data) {
_state.meta = {
..._state.meta,
...data,
};
},
};
export default {
namespaced: true,
state,
getters,
actions,
mutations,
};

View File

@@ -269,4 +269,9 @@ export default {
SET_CONVERSATION_PARTICIPANTS_UI_FLAG:
'SET_CONVERSATION_PARTICIPANTS_UI_FLAG',
SET_CONVERSATION_PARTICIPANTS: 'SET_CONVERSATION_PARTICIPANTS',
// Audit Logs
SET_AUDIT_LOGS_UI_FLAG: 'SET_AUDIT_LOGS_UI_FLAG',
SET_AUDIT_LOGS: 'SET_AUDIT_LOGS',
SET_AUDIT_LOGS_META: 'SET_AUDIT_LOGS_META',
};