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:
16
app/javascript/dashboard/api/auditLogs.js
Normal file
16
app/javascript/dashboard/api/auditLogs.js
Normal 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();
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
24
app/javascript/dashboard/i18n/locale/en/auditLogs.json
Normal file
24
app/javascript/dashboard/i18n/locale/en/auditLogs.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"HOME": "Home",
|
||||
"AGENTS": "Agents",
|
||||
"AGENT_BOTS": "Bots",
|
||||
"AUDIT_LOGS": "Audit Logs",
|
||||
"INBOXES": "Inboxes",
|
||||
"NOTIFICATIONS": "Notifications",
|
||||
"CANNED_RESPONSES": "Canned Responses",
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
79
app/javascript/dashboard/store/modules/auditlogs.js
Normal file
79
app/javascript/dashboard/store/modules/auditlogs.js
Normal 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,
|
||||
};
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user