diff --git a/.gitignore b/.gitignore index bf9a98fa5..63d0781ca 100644 --- a/.gitignore +++ b/.gitignore @@ -62,5 +62,8 @@ test/cypress/videos/* /config/*.enc .vscode/settings.json +.vscode + # yalc for local testing .yalc +yalc.lock diff --git a/app/javascript/dashboard/api/auditLogs.js b/app/javascript/dashboard/api/auditLogs.js new file mode 100644 index 000000000..f3e5864fc --- /dev/null +++ b/app/javascript/dashboard/api/auditLogs.js @@ -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(); diff --git a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js index 62133bec9..94997ac4b 100644 --- a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js +++ b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js @@ -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, + }, ], }); diff --git a/app/javascript/dashboard/i18n/locale/en/auditLogs.json b/app/javascript/dashboard/i18n/locale/en/auditLogs.json new file mode 100644 index 000000000..e288e2959 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/en/auditLogs.json @@ -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": "

Audit Logs

Audit Logs are trails for events and actions in a Chatwoot System.

", + "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" + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/en/index.js b/app/javascript/dashboard/i18n/locale/en/index.js index 387241f31..434e50e1d 100644 --- a/app/javascript/dashboard/i18n/locale/en/index.js +++ b/app/javascript/dashboard/i18n/locale/en/index.js @@ -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, diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index 48d93288e..de972eb78 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -199,6 +199,7 @@ "HOME": "Home", "AGENTS": "Agents", "AGENT_BOTS": "Bots", + "AUDIT_LOGS": "Audit Logs", "INBOXES": "Inboxes", "NOTIFICATIONS": "Notifications", "CANNED_RESPONSES": "Canned Responses", diff --git a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/Index.vue new file mode 100644 index 000000000..990e0cb4e --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/Index.vue @@ -0,0 +1,107 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js new file mode 100644 index 000000000..f1fb18ec1 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js @@ -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, + }, + ], + }, + ], +}; diff --git a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js index 794451e79..48ef316ef 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js @@ -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, diff --git a/app/javascript/dashboard/store/index.js b/app/javascript/dashboard/store/index.js index e2d34f50d..004b8e994 100755 --- a/app/javascript/dashboard/store/index.js +++ b/app/javascript/dashboard/store/index.js @@ -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, diff --git a/app/javascript/dashboard/store/modules/auditlogs.js b/app/javascript/dashboard/store/modules/auditlogs.js new file mode 100644 index 000000000..c0970cf18 --- /dev/null +++ b/app/javascript/dashboard/store/modules/auditlogs.js @@ -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, +}; diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index 17b01a453..59b3e45b9 100644 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -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', }; diff --git a/app/javascript/shared/components/FluentIcon/dashboard-icons.json b/app/javascript/shared/components/FluentIcon/dashboard-icons.json index 1c583ac28..c76fbc33b 100644 --- a/app/javascript/shared/components/FluentIcon/dashboard-icons.json +++ b/app/javascript/shared/components/FluentIcon/dashboard-icons.json @@ -107,6 +107,7 @@ "headphones-sound-wave-outline": "M3.5 12a8.5 8.5 0 0 1 17 0v2h-2.25a.75.75 0 0 0-.75.75v6.5c0 .414.336.75.75.75H19a3 3 0 0 0 3-3v-7c0-5.523-4.477-10-10-10S2 6.477 2 12v7a3 3 0 0 0 3 3h.75a.75.75 0 0 0 .75-.75v-6.5a.75.75 0 0 0-.75-.75H3.5v-2Zm17 3.5V19a1.5 1.5 0 0 1-1.5 1.5v-5h1.5ZM3.5 19v-3.5H5v5A1.5 1.5 0 0 1 3.5 19Zm9.25-7.25a.75.75 0 0 0-1.5 0v10.5a.75.75 0 0 0 1.5 0v-10.5Zm-4 2.25a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-1.5 0v-4.5a.75.75 0 0 1 .75-.75Zm7.25.75a.75.75 0 0 0-1.5 0v4.5a.75.75 0 0 0 1.5 0v-4.5Z", "image-outline": "M17.75 3A3.25 3.25 0 0 1 21 6.25v11.5A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h11.5Zm.58 16.401-5.805-5.686a.75.75 0 0 0-.966-.071l-.084.07-5.807 5.687c.182.064.378.099.582.099h11.5c.203 0 .399-.035.58-.099l-5.805-5.686L18.33 19.4ZM17.75 4.5H6.25A1.75 1.75 0 0 0 4.5 6.25v11.5c0 .208.036.408.103.594l5.823-5.701a2.25 2.25 0 0 1 3.02-.116l.128.116 5.822 5.702c.067-.186.104-.386.104-.595V6.25a1.75 1.75 0 0 0-1.75-1.75Zm-2.498 2a2.252 2.252 0 1 1 0 4.504 2.252 2.252 0 0 1 0-4.504Zm0 1.5a.752.752 0 1 0 0 1.504.752.752 0 0 0 0-1.504Z", "info-outline": "M12 1.999c5.524 0 10.002 4.478 10.002 10.002 0 5.523-4.478 10.001-10.002 10.001-5.524 0-10.002-4.478-10.002-10.001C1.998 6.477 6.476 1.999 12 1.999Zm0 1.5a8.502 8.502 0 1 0 0 17.003A8.502 8.502 0 0 0 12 3.5Zm-.004 7a.75.75 0 0 1 .744.648l.007.102.003 5.502a.75.75 0 0 1-1.493.102l-.007-.101-.003-5.502a.75.75 0 0 1 .75-.75ZM12 7.003a.999.999 0 1 1 0 1.997.999.999 0 0 1 0-1.997Z", + "key-outline": "M15 6a1 1 0 1 1-2 0a1 1 0 0 1 2 0Zm-2.5-4C9.424 2 7 4.424 7 7.5c0 .397.04.796.122 1.175c.058.27-.008.504-.142.638l-4.54 4.54A1.5 1.5 0 0 0 2 14.915V16.5A1.5 1.5 0 0 0 3.5 18h2A1.5 1.5 0 0 0 7 16.5V16h1a1 1 0 0 0 1-1v-1h1a1 1 0 0 0 1-1v-.18c.493.134 1.007.18 1.5.18c3.076 0 5.5-2.424 5.5-5.5S15.576 2 12.5 2ZM8 7.5C8 4.976 9.976 3 12.5 3S17 4.976 17 7.5S15.024 12 12.5 12c-.66 0-1.273-.095-1.776-.347A.5.5 0 0 0 10 12.1v.9H9a1 1 0 0 0-1 1v1H7a1 1 0 0 0-1 1v.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-1.586a.5.5 0 0 1 .146-.353l4.541-4.541c.432-.432.522-1.044.412-1.556A4.619 4.619 0 0 1 8 7.5Z", "keyboard-outline": "M19.745 5a2.25 2.25 0 0 1 2.25 2.25v9.505a2.25 2.25 0 0 1-2.25 2.25H4.25A2.25 2.25 0 0 1 2 16.755V7.25A2.25 2.25 0 0 1 4.25 5h15.495Zm0 1.5H4.25a.75.75 0 0 0-.75.75v9.505c0 .414.336.75.75.75h15.495a.75.75 0 0 0 .75-.75V7.25a.75.75 0 0 0-.75-.75Zm-12.995 8h10.5a.75.75 0 0 1 .102 1.493L17.25 16H6.75a.75.75 0 0 1-.102-1.493l.102-.007h10.5-10.5ZM16.5 11a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm-5.995 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm-3 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm6 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2ZM6 8a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm2.995 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm3 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm3 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm3 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z", "library-outline": "M4 3h1c1.054 0 1.918.816 1.995 1.85L7 5v14a2.001 2.001 0 0 1-1.85 1.994L5 21H4a2.001 2.001 0 0 1-1.995-1.85L2 19V5c0-1.054.816-1.918 1.85-1.995L4 3h1-1Zm6 0h1c1.054 0 1.918.816 1.995 1.85L13 5v14a2.001 2.001 0 0 1-1.85 1.994L11 21h-1a2.001 2.001 0 0 1-1.995-1.85L8 19V5c0-1.054.816-1.918 1.85-1.995L10 3h1-1Zm6.974 2c.84 0 1.608.531 1.89 1.346l.047.157 3.015 11.745a2 2 0 0 1-1.296 2.392l-.144.043-.969.248a2.002 2.002 0 0 1-2.387-1.284l-.047-.155-3.016-11.745a2 2 0 0 1 1.298-2.392l.143-.043.968-.248c.166-.043.334-.064.498-.064ZM5 4.5H4a.501.501 0 0 0-.492.41L3.5 5v14c0 .244.177.45.41.492L4 19.5h1c.245 0 .45-.178.492-.41L5.5 19V5a.501.501 0 0 0-.41-.492L5 4.5Zm6 0h-1a.501.501 0 0 0-.492.41L9.5 5v14c0 .244.177.45.41.492l.09.008h1c.245 0 .45-.178.492-.41L11.5 19V5a.501.501 0 0 0-.41-.492L11 4.5Zm5.975 2-.063.004-.063.013-.968.247a.498.498 0 0 0-.376.51l.015.1 3.016 11.745a.5.5 0 0 0 .483.375l.063-.003.062-.012.97-.25a.5.5 0 0 0 .374-.519l-.015-.088-3.015-11.747a.501.501 0 0 0-.483-.375Z", "link-outline": "M9.25 7a.75.75 0 0 1 .11 1.492l-.11.008H7a3.5 3.5 0 0 0-.206 6.994L7 15.5h2.25a.75.75 0 0 1 .11 1.492L9.25 17H7a5 5 0 0 1-.25-9.994L7 7h2.25ZM17 7a5 5 0 0 1 .25 9.994L17 17h-2.25a.75.75 0 0 1-.11-1.492l.11-.008H17a3.5 3.5 0 0 0 .206-6.994L17 8.5h-2.25a.75.75 0 0 1-.11-1.492L14.75 7H17ZM7 11.25h10a.75.75 0 0 1 .102 1.493L17 12.75H7a.75.75 0 0 1-.102-1.493L7 11.25h10H7Z", diff --git a/enterprise/app/controllers/api/v1/accounts/audit_logs_controller.rb b/enterprise/app/controllers/api/v1/accounts/audit_logs_controller.rb index 1c4dc6647..26a0d2c6e 100644 --- a/enterprise/app/controllers/api/v1/accounts/audit_logs_controller.rb +++ b/enterprise/app/controllers/api/v1/accounts/audit_logs_controller.rb @@ -2,22 +2,25 @@ class Api::V1::Accounts::AuditLogsController < Api::V1::Accounts::BaseController before_action :check_admin_authorization? before_action :fetch_audit + before_action :prepend_view_paths RESULTS_PER_PAGE = 15 + # Prepend the view path to the enterprise/app/views won't be available by default + def prepend_view_paths + prepend_view_path 'enterprise/app/views/' + end + def show @audit_logs = @audit_logs.page(params[:page]).per(RESULTS_PER_PAGE) - render json: { - audit_logs: @audit_logs, - current_page: @audit_logs.current_page, - per_page: RESULTS_PER_PAGE, - total_entries: @audit_logs.total_count - } + @current_page = @audit_logs.current_page + @total_entries = @audit_logs.total_count + @per_page = RESULTS_PER_PAGE end private def fetch_audit - @audit_logs = Current.account.associated_audits + @audit_logs = Current.account.associated_audits.order(created_at: :desc) end end diff --git a/enterprise/app/views/api/v1/accounts/audit_logs/show.json.jbuilder b/enterprise/app/views/api/v1/accounts/audit_logs/show.json.jbuilder new file mode 100644 index 000000000..0df659304 --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/audit_logs/show.json.jbuilder @@ -0,0 +1,23 @@ +json.per_page @per_page +json.total_entries @total_entries +json.current_page @current_page + +json.audit_logs do + json.array! @audit_logs do |audit_log| + json.id audit_log.id + json.auditable_id audit_log.auditable_id + json.auditable_type audit_log.auditable_type + json.associated_id audit_log.associated_id + json.associated_type audit_log.associated_type + json.user_id audit_log.user_id + json.user_type audit_log.user_type + json.username audit_log.username + json.action audit_log.action + json.audited_changes audit_log.audited_changes + json.version audit_log.version + json.comment audit_log.comment + json.request_uuid audit_log.request_uuid + json.created_at audit_log.created_at.to_i + json.remote_address audit_log.remote_address + end +end