Files
tacticalrmm-web/src/components/AuditManager.vue

445 lines
13 KiB
Vue

<template>
<q-card>
<q-bar>
<q-btn @click="search" class="q-mr-sm" dense flat push icon="refresh" />
<q-space />Audit Manager
<q-space />
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
</q-btn>
</q-bar>
<div class="text-h6 q-pl-sm q-pt-sm">Filter</div>
<div class="row">
<div class="q-pa-sm col-1">
<q-option-group v-model="filterType" :options="filterTypeOptions" color="primary" @input="clear" />
</div>
<div class="q-pa-sm col-2" v-if="filterType === 'agents'">
<q-select
new-value-mode="add"
multiple
filled
dense
v-model="agentFilter"
use-input
use-chips
fill-input
input-debounce="3"
label="Agent"
emit-value
:options="agentOptions"
@filter="getAgentOptions"
hint="Start typing the agents name"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">No results</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="q-pa-sm col-2" v-if="filterType === 'clients'">
<q-select
clearable
multiple
filled
dense
v-model="clientFilter"
fill-input
label="Clients"
map-options
emit-value
:options="clientsOptions"
/>
</div>
<div class="q-pa-sm col-2">
<q-select
new-value-mode="add"
multiple
filled
dense
v-model="userFilter"
use-input
use-chips
fill-input
input-debounce="3"
label="User"
emit-value
:options="userOptions"
@filter="getUserOptions"
hint="Start typing the username"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">No results</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="q-pa-sm col-2">
<q-select
clearable
filled
multiple
use-chips
dense
v-model="actionFilter"
label="Action"
emit-value
map-options
:options="actionOptions"
/>
</div>
<div class="q-pa-sm col-2">
<q-select
clearable
filled
multiple
use-chips
dense
v-model="objectFilter"
label="Object"
emit-value
map-options
:options="objectOptions"
/>
</div>
<div class="q-pa-sm col-2">
<q-select filled dense v-model="timeFilter" label="Time" emit-value map-options :options="timeOptions">
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">No results</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="q-pa-sm col-1">
<q-btn color="primary" label="Search" @click="search" />
</div>
</div>
<q-separator />
<q-card-section>
<q-table
@request="onRequest"
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
class="audit-mgr-tbl-sticky"
binary-state-sort
title="Audit Logs"
:rows="auditLogs"
:columns="columns"
row-key="id"
v-model:pagination="pagination"
:rows-per-page-options="[25, 50, 100, 500, 1000]"
:no-data-label="noDataText"
@row-click="showDetails"
virtual-scroll
>
<template v-slot:top-right>
<q-btn color="primary" icon-right="archive" label="Export to csv" no-caps @click="exportLog" />
</template>
<template v-slot:body-cell-action="props">
<q-td :props="props">
<div>
<q-badge :color="actionColor(props.value)" :label="actionText(props.value)" />
</div>
</q-td>
</template>
</q-table>
</q-card-section>
<div class="q-pa-md q-gutter-sm">
<q-dialog v-model="showLogDetails" @hide="closeDetails">
<AuditLogDetail :log="logDetails" />
</q-dialog>
</div>
</q-card>
</template>
<script>
import AuditLogDetail from "@/components/modals/logs/AuditLogDetail";
import mixins from "@/mixins/mixins";
import { exportFile } from "quasar";
function wrapCsvValue(val, formatFn) {
let formatted = formatFn !== void 0 ? formatFn(val) : val;
formatted = formatted === void 0 || formatted === null ? "" : String(formatted);
formatted = formatted.split('"').join('""');
/**
* Excel accepts \n and \r in strings, but some other CSV parsers do not
* Uncomment the next two lines to escape new lines
*/
// .split('\n').join('\\n')
// .split('\r').join('\\r')
return `"${formatted}"`;
}
export default {
name: "AuditManager",
mixins: [mixins],
components: { AuditLogDetail },
data() {
return {
showLogDetails: false,
logDetails: null,
searched: false,
auditLogs: [],
userOptions: [],
agentOptions: [],
agentFilter: null,
userFilter: [],
actionFilter: null,
clientsOptions: [],
clientFilter: null,
objectFilter: null,
timeFilter: 7,
filterType: "clients",
filterTypeOptions: [
{
label: "Clients",
value: "clients",
},
{
label: "Agents",
value: "agents",
},
],
columns: [
{
name: "entry_time",
label: "Time",
field: "entry_time",
align: "left",
sortable: true,
format: (val, row) => this.formatDate(val, true),
},
{ name: "username", label: "Username", field: "username", align: "left", sortable: true },
{ name: "agent", label: "Agent", field: "agent", align: "left", sortable: true },
{ name: "action", label: "Action", field: "action", align: "left", sortable: true },
{
name: "object_type",
label: "Object",
field: "object_type",
align: "left",
sortable: true,
format: (val, row) => this.formatObject(val),
},
{ name: "message", label: "Message", field: "message", align: "left", sortable: true },
],
actionOptions: [
{ value: "agent_install", label: "Agent Installs" },
{ value: "add", label: "Add Object" },
{ value: "bulk_action", label: "Bulk Actions" },
{ value: "check_run", label: "Check Run Results" },
{ value: "execute_command", label: "Execute Command" },
{ value: "execute_script", label: "Execute Script" },
{ value: "delete", label: "Delete Object" },
{ value: "failed_login", label: "Failed User login" },
{ value: "login", label: "User Login" },
{ value: "modify", label: "Modify Object" },
{ value: "remote_session", label: "Remote Session" },
{ value: "task_run", label: "Task Run Results" },
],
timeOptions: [
{ value: 1, label: "1 Day Ago" },
{ value: 7, label: "1 Week Ago" },
{ value: 30, label: "30 Days Ago" },
{ value: 90, label: "3 Months Ago" },
{ value: 180, label: "6 Months Ago" },
{ value: 365, label: "1 Year Ago" },
{ value: 0, label: "Everything" },
],
objectOptions: [
{ value: "agent", label: "Agent" },
{ value: "automatedtask", label: "Automated Task" },
{ value: "bulk", label: "Bulk Actions" },
{ value: "coresettings", label: "Core Settings" },
{ value: "check", label: "Check" },
{ value: "client", label: "Client" },
{ value: "policy", label: "Policy" },
{ value: "site", label: "Site" },
{ value: "script", label: "Script" },
{ value: "user", label: "User" },
{ value: "winupdatepolicy", label: "Patch Policy" },
],
pagination: {
rowsPerPage: 25,
rowsNumber: null,
sortBy: "entry_time",
descending: true,
page: 1,
},
};
},
methods: {
getClients() {
this.$axios
.get("/clients/clients/")
.then(r => {
this.clientsOptions = Object.freeze(r.data.map(client => ({ label: client.name, value: client.id })));
})
.catch(e => {});
},
getUserOptions(val, update, abort) {
if (val.length < 2) {
abort();
return;
}
update(() => {
this.$q.loading.show();
const needle = val.toLowerCase();
let data = {
type: "user",
pattern: needle,
};
this.$axios
.post(`logs/auditlogs/optionsfilter/`, data)
.then(r => {
this.userOptions = Object.freeze(r.data.map(user => user.username));
this.$q.loading.hide();
})
.catch(e => {
this.$q.loading.hide();
});
});
},
getAgentOptions(val, update, abort) {
if (val.length < 2) {
abort();
return;
}
update(() => {
this.$q.loading.show();
const needle = val.toLowerCase();
let data = {
type: "agent",
pattern: needle,
};
this.$axios
.post(`logs/auditlogs/optionsfilter/`, data)
.then(r => {
this.getAgentOptions().then(options => (this.agentOptions = Object.freeze(options)));
this.$q.loading.hide();
})
.catch(e => {
this.$q.loading.hide();
});
});
},
exportLog() {
// naive encoding to csv format
const content = [this.columns.map(col => wrapCsvValue(col.label))]
.concat(
this.auditLogs.map(row =>
this.columns
.map(col =>
wrapCsvValue(
typeof col.field === "function" ? col.field(row) : row[col.field === void 0 ? col.name : col.field],
col.format
)
)
.join(",")
)
)
.join("\r\n");
const status = exportFile("rmm-audit-export.csv", content, "text/csv");
if (status !== true) {
this.$q.notify({
message: "Browser denied file download...",
color: "negative",
icon: "warning",
});
}
},
onRequest(props) {
// needed to update external pagination object
const { page, rowsPerPage, sortBy, descending } = props.pagination;
this.pagination.page = page;
this.pagination.rowsPerPage = rowsPerPage;
this.pagination.sortBy = sortBy;
this.pagination.descending = descending;
this.search();
},
search() {
this.$q.loading.show();
this.searched = true;
let data = {
pagination: this.pagination,
};
if (!!this.agentFilter && this.agentFilter.length > 0) data["agentFilter"] = this.agentFilter;
else if (!!this.clientFilter && this.clientFilter.length > 0) data["clientFilter"] = this.clientFilter;
if (!!this.userFilter && this.userFilter.length > 0) data["userFilter"] = this.userFilter;
if (!!this.timeFilter) data["timeFilter"] = this.timeFilter;
if (!!this.actionFilter && this.actionFilter.length > 0) data["actionFilter"] = this.actionFilter;
if (!!this.objectFilter && this.objectFilter.length > 0) data["objectFilter"] = this.objectFilter;
this.$axios
.patch("/logs/auditlogs/", data)
.then(r => {
this.$q.loading.hide();
this.auditLogs = Object.freeze(r.data.audit_logs);
this.pagination.rowsNumber = r.data.total;
})
.catch(e => {
this.$q.loading.hide();
});
},
showDetails(evt, row, index) {
this.logDetails = row;
this.showLogDetails = true;
},
closeDetails() {
this.logDetails = null;
this.showLogDetails = false;
},
actionColor(action) {
if (action === "add") return "success";
else if (action === "agent_install") return "success";
else if (action === "modify") return "warning";
else if (action === "delete") return "negative";
else if (action === "failed_login") return "negative";
else return "primary";
},
actionText(action) {
if (action.includes("_")) {
let text = action.split("_");
return this.capitalize(text[0]) + " " + this.capitalize(text[1]);
} else {
return this.capitalize(action);
}
},
formatObject(text) {
if (text === "winupdatepolicy") return "Patch Policy";
else if (text === "automatedtask") return "Automated Task";
else if (text === "coresettings") return "Core Settings";
else return this.capitalize(text);
},
clear() {
this.clientFilter = null;
this.agentFilter = null;
},
},
computed: {
noDataText() {
return this.searched ? "No data found. Try to refine you search" : "Click search to find audit logs";
},
},
mounted() {
this.getClients();
},
};
</script>