feat: Update the design of the audit logs page (#9901)

This is continuation of the design update, updates the design for audit logs listing page.

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
Pranav
2024-08-06 20:58:04 -07:00
committed by GitHub
parent e393bcf125
commit aea68f1ecf
5 changed files with 118 additions and 133 deletions

View File

@@ -173,11 +173,10 @@ const settings = accountId => ({
meta: { meta: {
permissions: ['administrator'], permissions: ['administrator'],
}, },
toState: frontendURL(`accounts/${accountId}/settings/audit-log/list`), toState: frontendURL(`accounts/${accountId}/settings/audit-logs/list`),
toStateName: 'auditlogs_list', toStateName: 'auditlogs_list',
isEnterpriseOnly: true, isEnterpriseOnly: true,
featureFlag: FEATURE_FLAGS.AUDIT_LOGS, featureFlag: FEATURE_FLAGS.AUDIT_LOGS,
beta: true,
}, },
{ {
icon: 'document-list-clock', icon: 'document-list-clock',

View File

@@ -1,18 +1,19 @@
const FEATURE_HELP_URLS = { const FEATURE_HELP_URLS = {
agent_bots: 'https://chwt.app/hc/agent-bots',
audit_logs: 'https://chwt.app/hc/audit-logs',
campaigns: 'https://chwt.app/hc/campaigns',
canned_responses: 'https://chwt.app/hc/canned',
channel_email: 'https://chwt.app/hc/email', channel_email: 'https://chwt.app/hc/email',
channel_facebook: 'https://chwt.app/hc/fb', channel_facebook: 'https://chwt.app/hc/fb',
help_center: 'https://chwt.app/hc/help-center',
agent_bots: 'https://chwt.app/hc/agent-bots',
team_management: 'https://chwt.app/hc/teams',
labels: 'https://chwt.app/hc/labels',
custom_attributes: 'https://chwt.app/hc/custom-attributes', custom_attributes: 'https://chwt.app/hc/custom-attributes',
canned_responses: 'https://chwt.app/hc/canned',
integrations: 'https://chwt.app/hc/integrations',
campaigns: 'https://chwt.app/hc/campaigns',
reports: 'https://chwt.app/hc/reports',
message_reply_to: 'https://chwt.app/hc/reply-to',
sla: 'https://chwt.app/hc/sla',
dashboard_apps: 'https://chwt.app/hc/dashboard-apps', dashboard_apps: 'https://chwt.app/hc/dashboard-apps',
help_center: 'https://chwt.app/hc/help-center',
integrations: 'https://chwt.app/hc/integrations',
labels: 'https://chwt.app/hc/labels',
message_reply_to: 'https://chwt.app/hc/reply-to',
reports: 'https://chwt.app/hc/reports',
sla: 'https://chwt.app/hc/sla',
team_management: 'https://chwt.app/hc/teams',
}; };
export function getHelpUrlForFeature(featureName) { export function getHelpUrlForFeature(featureName) {

View File

@@ -3,6 +3,8 @@
"HEADER": "Audit Logs", "HEADER": "Audit Logs",
"HEADER_BTN_TXT": "Add Audit Logs", "HEADER_BTN_TXT": "Add Audit Logs",
"LOADING": "Fetching Audit Logs", "LOADING": "Fetching Audit Logs",
"DESCRIPTION": "Audit Logs maintain a record of activities in your account, allowing you to track and audit your account, team, or services.",
"LEARN_MORE": "Learn more about audit logs",
"SEARCH_404": "There are no items matching this query", "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>", "SIDEBAR_TXT": "<p><b>Audit Logs</b> </p><p> Audit Logs are trails for events and actions in a Chatwoot System. </p>",
"LIST": { "LIST": {
@@ -50,22 +52,22 @@
"ADD": "%{agentName} created a new team (#%{id})", "ADD": "%{agentName} created a new team (#%{id})",
"EDIT": "%{agentName} updated a team (#%{id})", "EDIT": "%{agentName} updated a team (#%{id})",
"DELETE": "%{agentName} deleted a team (#%{id})" "DELETE": "%{agentName} deleted a team (#%{id})"
}, },
"MACRO": { "MACRO": {
"ADD": "%{agentName} created a new macro (#%{id})", "ADD": "%{agentName} created a new macro (#%{id})",
"EDIT": "%{agentName} updated a macro (#%{id})", "EDIT": "%{agentName} updated a macro (#%{id})",
"DELETE": "%{agentName} deleted a macro (#%{id})" "DELETE": "%{agentName} deleted a macro (#%{id})"
}, },
"INBOX_MEMBER": { "INBOX_MEMBER": {
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})", "ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})" "REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
}, },
"TEAM_MEMBER": { "TEAM_MEMBER": {
"ADD": "%{agentName} added %{user} to the team(#%{team_id})", "ADD": "%{agentName} added %{user} to the team(#%{team_id})",
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})" "REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
}, },
"ACCOUNT": { "ACCOUNT": {
"EDIT": "%{agentName} updated the account configuration (#%{id})" "EDIT": "%{agentName} updated the account configuration (#%{id})"
}
} }
} }
}

View File

@@ -1,116 +1,104 @@
<script> <script setup>
import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { messageTimestamp } from 'shared/helpers/timeHelper'; import { messageTimestamp } from 'shared/helpers/timeHelper';
import { useStoreGetters, useStore } from 'dashboard/composables/store';
import TableFooter from 'dashboard/components/widgets/TableFooter.vue'; import TableFooter from 'dashboard/components/widgets/TableFooter.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
import { import {
generateTranslationPayload, generateTranslationPayload,
generateLogActionKey, generateLogActionKey,
} from 'dashboard/helper/auditlogHelper'; } from 'dashboard/helper/auditlogHelper';
import { computed, onMounted, watch } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute, useRouter } from 'dashboard/composables/route';
export default { const getters = useStoreGetters();
components: { const store = useStore();
TableFooter, const router = useRouter();
}, const records = computed(() => getters['auditlogs/getAuditLogs'].value);
beforeRouteEnter(to, from, next) { const uiFlags = computed(() => getters['auditlogs/getUIFlags'].value);
// Fetch Audit Logs on page load without manual refresh const meta = computed(() => getters['auditlogs/getMeta'].value);
next(vm => { const agentList = computed(() => getters['agents/getAgents'].value);
vm.fetchAuditLogs();
});
},
data() {
return {
loading: {},
auditLogsAPI: {
message: '',
},
};
},
computed: {
...mapGetters({
records: 'auditlogs/getAuditLogs',
uiFlags: 'auditlogs/getUIFlags',
meta: 'auditlogs/getMeta',
agentList: 'agents/getAgents',
}),
},
mounted() {
// Fetch API Call
this.$store.dispatch('agents/get');
},
methods: {
messageTimestamp,
fetchAuditLogs() {
const page = this.$route.query.page ?? 1;
this.$store.dispatch('auditlogs/fetch', { page }).catch(error => {
const errorMessage =
error?.message || this.$t('AUDIT_LOGS.API.ERROR_MESSAGE');
useAlert(errorMessage);
});
},
generateLogText(auditLogItem) {
const translationPayload = generateTranslationPayload(
auditLogItem,
this.agentList
);
const translationKey = generateLogActionKey(auditLogItem);
return this.$t(translationKey, translationPayload); const { t } = useI18n();
}, const route = useRoute();
onPageChange(page) {
window.history.pushState({}, null, `${this.$route.path}?page=${page}`); const routerPage = computed(() => Number(route.query.page ?? 1));
try {
this.$store.dispatch('auditlogs/fetch', { page }); const fetchAuditLogs = page => {
} catch (error) { try {
const errorMessage = store.dispatch('auditlogs/fetch', { page });
error?.message || this.$t('AUDIT_LOGS.API.ERROR_MESSAGE'); } catch (error) {
useAlert(errorMessage); const errorMessage = error?.message || t('AUDIT_LOGS.API.ERROR_MESSAGE');
} useAlert(errorMessage);
}, }
},
}; };
const generateLogText = auditLogItem => {
const translationPayload = generateTranslationPayload(
auditLogItem,
agentList.value
);
const translationKey = generateLogActionKey(auditLogItem);
return t(translationKey, translationPayload);
};
const onPageChange = page => {
router.push({ name: 'auditlogs_list', query: { page: page } });
};
onMounted(() => {
store.dispatch('agents/get');
fetchAuditLogs(routerPage.value);
});
watch(routerPage, (newPage, oldPage) => {
if (newPage !== oldPage) {
fetchAuditLogs(newPage);
}
});
</script> </script>
<template> <template>
<div class="flex flex-col justify-between flex-1 p-4 overflow-auto"> <div class="flex-1 overflow-auto">
<!-- List Audit Logs --> <BaseSettingsHeader
<div> :title="$t('AUDIT_LOGS.HEADER')"
<div> :description="$t('AUDIT_LOGS.DESCRIPTION')"
<p :link-text="$t('AUDIT_LOGS.LEARN_MORE')"
v-if="!uiFlags.fetchingList && !records.length" feature-name="audit_logs"
class="flex flex-col items-center justify-center h-full" />
>
{{ $t('AUDIT_LOGS.LIST.404') }}
</p>
<woot-loading-state
v-if="uiFlags.fetchingList"
:message="$t('AUDIT_LOGS.LOADING')"
/>
<table <div class="mt-6 flex-1 text-slate-700 dark:text-slate-300">
v-if="!uiFlags.fetchingList && records.length" <woot-loading-state
class="w-full woot-table" v-if="uiFlags.fetchingList"
> :message="$t('AUDIT_LOGS.LOADING')"
<colgroup> />
<col class="w-3/5" /> <p
<col /> v-else-if="!records.length"
<col /> class="flex flex-col items-center justify-center h-full text-base p-8"
</colgroup> >
{{ $t('AUDIT_LOGS.LIST.404') }}
</p>
<div v-else class="min-w-full overflow-x-auto">
<table class="divide-y divide-slate-75 dark:divide-slate-700">
<thead> <thead>
<!-- Header -->
<th <th
v-for="thHeader in $t('AUDIT_LOGS.LIST.TABLE_HEADER')" v-for="thHeader in $t('AUDIT_LOGS.LIST.TABLE_HEADER')"
:key="thHeader" :key="thHeader"
class="py-4 pr-4 text-left font-semibold text-slate-700 dark:text-slate-300"
> >
{{ thHeader }} {{ thHeader }}
</th> </th>
</thead> </thead>
<tbody> <tbody
class="divide-y divide-slate-50 dark:divide-slate-800 text-slate-700 dark:text-slate-300"
>
<tr v-for="auditLogItem in records" :key="auditLogItem.id"> <tr v-for="auditLogItem in records" :key="auditLogItem.id">
<td class="break-all whitespace-nowrap"> <td class="py-4 pr-4 break-all whitespace-nowrap">
{{ generateLogText(auditLogItem) }} {{ generateLogText(auditLogItem) }}
</td> </td>
<td class="break-all whitespace-nowrap"> <td class="py-4 pr-4 break-all whitespace-nowrap">
{{ {{
messageTimestamp( messageTimestamp(
auditLogItem.created_at, auditLogItem.created_at,
@@ -118,20 +106,20 @@ export default {
) )
}} }}
</td> </td>
<td class="w-[8.75rem]"> <td class="py-4 w-[8.75rem]">
{{ auditLogItem.remote_address }} {{ auditLogItem.remote_address }}
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<TableFooter
:current-page="Number(meta.currentPage)"
:total-count="meta.totalEntries"
:page-size="meta.perPage"
class="border-slate-50 dark:border-slate-800 border-t !px-0 py-4"
@pageChange="onPageChange"
/>
</div> </div>
</div> </div>
<TableFooter
:current-page="Number(meta.currentPage)"
:total-count="meta.totalEntries"
:page-size="meta.perPage"
class="!bg-slate-25 dark:!bg-slate-900 border-t border-slate-75 dark:border-slate-700/50"
@pageChange="onPageChange"
/>
</div> </div>
</template> </template>

View File

@@ -1,18 +1,13 @@
import { frontendURL } from '../../../../helper/URLHelper'; import { frontendURL } from '../../../../helper/URLHelper';
const SettingsContent = () => import('../Wrapper.vue'); const SettingsWrapper = () => import('../SettingsWrapper.vue');
const AuditLogsHome = () => import('./Index.vue'); const AuditLogsHome = () => import('./Index.vue');
export default { export default {
routes: [ routes: [
{ {
path: frontendURL('accounts/:accountId/settings/audit-log'), path: frontendURL('accounts/:accountId/settings/audit-logs'),
component: SettingsContent, component: SettingsWrapper,
props: {
headerTitle: 'AUDIT_LOGS.HEADER',
icon: 'key',
showNewButton: false,
},
children: [ children: [
{ {
path: '', path: '',