mirror of
https://github.com/jpros/tacticalrmm-web.git
synced 2026-02-28 23:31:54 +00:00
debug log rework
This commit is contained in:
10
src/api/accounts.js
Normal file
10
src/api/accounts.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import axios from "axios"
|
||||
|
||||
const baseUrl = "/accounts"
|
||||
|
||||
export async function fetchUsers(params = {}) {
|
||||
try {
|
||||
const { data } = await axios.get(`${baseUrl}/users/`, { params: params })
|
||||
return data
|
||||
} catch (e) { }
|
||||
}
|
||||
@@ -3,7 +3,8 @@ import axios from "axios"
|
||||
const baseUrl = "/agents"
|
||||
|
||||
export async function fetchAgents() {
|
||||
const { data } = await axios.get(`${baseUrl}/listagentsnodetail/`)
|
||||
|
||||
return data
|
||||
try {
|
||||
const { data } = await axios.get(`${baseUrl}/listagentsnodetail/`)
|
||||
return data
|
||||
} catch (e) { }
|
||||
}
|
||||
10
src/api/clients.js
Normal file
10
src/api/clients.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import axios from "axios"
|
||||
|
||||
const baseUrl = "/clients"
|
||||
|
||||
export async function fetchClients() {
|
||||
try {
|
||||
const { data } = await axios.get(`${baseUrl}/clients/`)
|
||||
return data
|
||||
} catch (e) { }
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
import axios from "axios"
|
||||
|
||||
const baseUrl = "/logs/debuglog/"
|
||||
const baseUrl = "/logs"
|
||||
|
||||
export async function fetchDebugLog(payload) {
|
||||
const { data } = await axios.patch(`${baseUrl}`, payload)
|
||||
try {
|
||||
const { data } = await axios.patch(`${baseUrl}/debuglog/`, payload)
|
||||
return data
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
export async function fetchAuditLog(payload) {
|
||||
try {
|
||||
const { data } = await axios.patch(`${baseUrl}/auditlogs/`, payload)
|
||||
return data
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
@@ -11,116 +11,49 @@
|
||||
<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" @update:model-value="clear" />
|
||||
<q-option-group v-model="filterType" :options="filterTypeOptions" color="primary" />
|
||||
</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>
|
||||
<template v-slot:option="scope">
|
||||
<q-item v-if="!scope.opt.category" v-bind="scope.itemProps" class="q-pl-lg">
|
||||
<q-item-section>
|
||||
<q-item-label v-html="scope.opt.label"></q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item-label v-if="scope.opt.category" v-bind="scope.itemProps" header class="q-pa-sm">{{
|
||||
scope.opt.category
|
||||
}}</q-item-label>
|
||||
</template>
|
||||
</q-select>
|
||||
<tactical-dropdown v-model="agentFilter" :options="agentOptions" label="Agent" clearable multiple filled />
|
||||
</div>
|
||||
<div class="q-pa-sm col-2" v-if="filterType === 'clients'">
|
||||
<q-select
|
||||
clearable
|
||||
multiple
|
||||
filled
|
||||
dense
|
||||
<tactical-dropdown
|
||||
v-model="clientFilter"
|
||||
fill-input
|
||||
:options="clientOptions"
|
||||
label="Clients"
|
||||
map-options
|
||||
emit-value
|
||||
:options="clientsOptions"
|
||||
clearable
|
||||
multiple
|
||||
filled
|
||||
mapOptions
|
||||
/>
|
||||
</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>
|
||||
<tactical-dropdown v-model="userFilter" :options="userOptions" label="Users" clearable filled multiple />
|
||||
</div>
|
||||
<div class="q-pa-sm col-2">
|
||||
<q-select
|
||||
clearable
|
||||
filled
|
||||
multiple
|
||||
use-chips
|
||||
dense
|
||||
<tactical-dropdown
|
||||
v-model="actionFilter"
|
||||
label="Action"
|
||||
emit-value
|
||||
map-options
|
||||
:options="actionOptions"
|
||||
/>
|
||||
</div>
|
||||
<div class="q-pa-sm col-2">
|
||||
<q-select
|
||||
label="Action"
|
||||
clearable
|
||||
filled
|
||||
multiple
|
||||
use-chips
|
||||
dense
|
||||
v-model="objectFilter"
|
||||
label="Object"
|
||||
emit-value
|
||||
map-options
|
||||
:options="objectOptions"
|
||||
mapOptions
|
||||
/>
|
||||
</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>
|
||||
<tactical-dropdown
|
||||
v-model="objectFilter"
|
||||
:options="objectOptions"
|
||||
label="Object"
|
||||
clearable
|
||||
filled
|
||||
multiple
|
||||
mapOptions
|
||||
/>
|
||||
</div>
|
||||
<div class="q-pa-sm col-2">
|
||||
<tactical-dropdown v-model="timeFilter" :options="timeOptions" label="Time" filled mapOptions />
|
||||
</div>
|
||||
<div class="q-pa-sm col-1">
|
||||
<q-btn color="primary" label="Search" @click="search" />
|
||||
@@ -130,287 +63,98 @@
|
||||
<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"
|
||||
dense
|
||||
binary-state-sort
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[25, 50, 100, 500, 1000]"
|
||||
:no-data-label="noDataText"
|
||||
@row-click="showDetails"
|
||||
virtual-scroll
|
||||
:no-data-label="tableNoDataText"
|
||||
@row-click="openAuditDetail"
|
||||
:loading="loading"
|
||||
>
|
||||
<template v-slot:top-right>
|
||||
<q-btn color="primary" icon-right="archive" label="Export to csv" no-caps @click="exportLog" />
|
||||
<q-btn dense color="primary" icon-right="archive" @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)" />
|
||||
<q-badge :color="formatActionColor(props.value)" :label="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 { formatAgentOptions } from "@/utils/format";
|
||||
// composition imports
|
||||
import { onMounted } from "vue";
|
||||
import { useAuditLog } from "@/composables/logs";
|
||||
import { useClientDropdown } from "@/composables/clients";
|
||||
import { useAgentDropdown } from "@/composables/agents";
|
||||
import { useUserDropdown } from "@/composables/accounts";
|
||||
import { exportTableToCSV } from "@/utils/csv";
|
||||
|
||||
// ui imported
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown";
|
||||
|
||||
export default {
|
||||
name: "AuditManager",
|
||||
mixins: [mixins],
|
||||
components: { AuditLogDetail },
|
||||
data() {
|
||||
components: { TacticalDropdown },
|
||||
setup() {
|
||||
// setup dropdowns
|
||||
const { clientOptions, getClientOptions } = useClientDropdown();
|
||||
const { agentOptions, getAgentOptions } = useAgentDropdown();
|
||||
const { userOptions, userDropdownLoading, getUserOptions } = useUserDropdown();
|
||||
|
||||
onMounted(() => {
|
||||
getClientOptions();
|
||||
getAgentOptions(true);
|
||||
getUserOptions(true);
|
||||
});
|
||||
|
||||
const AuditLog = useAuditLog();
|
||||
|
||||
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,
|
||||
// data
|
||||
auditLogs: AuditLog.auditLogs,
|
||||
agentFilter: AuditLog.agentFilter,
|
||||
userFilter: AuditLog.userFilter,
|
||||
actionFilter: AuditLog.actionFilter,
|
||||
clientFilter: AuditLog.clientFilter,
|
||||
objectFilter: AuditLog.objectFilter,
|
||||
timeFilter: AuditLog.timeFilter,
|
||||
filterType: AuditLog.filterType,
|
||||
loading: AuditLog.loading,
|
||||
pagination: AuditLog.pagination,
|
||||
userOptions,
|
||||
userDropdownLoading,
|
||||
|
||||
// non-reactive data
|
||||
clientOptions,
|
||||
agentOptions,
|
||||
columns: useAuditLog.tableColumns,
|
||||
actionOptions: useAuditLog.actionOptions,
|
||||
objectOptions: useAuditLog.objectOptions,
|
||||
timeOptions: useAuditLog.timeOptions,
|
||||
filterTypeOptions: useAuditLog.filterTypeOptions,
|
||||
|
||||
//computed
|
||||
tableNoDataText: AuditLog.noDataText,
|
||||
|
||||
// methods
|
||||
search: AuditLog.search,
|
||||
onRequest: AuditLog.onRequest,
|
||||
openAuditDetail: AuditLog.openAuditDetail,
|
||||
formatActionColor: AuditLog.formatActionColor,
|
||||
exportLog: () => {
|
||||
exportTableToCSV(auditLogs, useAuditLog.tableColumns);
|
||||
},
|
||||
};
|
||||
},
|
||||
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.agentOptions = Object.freeze(formatAgentOptions(r.data));
|
||||
this.$q.loading.hide();
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
});
|
||||
},
|
||||
exportLog() {
|
||||
exportTableToCSV(this.auditLogs, this.columns);
|
||||
},
|
||||
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>
|
||||
@@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<div v-if="!selectedAgent">No agent selected</div>
|
||||
<div v-else class="bg-grey-10 text-white">
|
||||
<div class="q-pa-md row">
|
||||
<tactical-dropdown
|
||||
class="col-2 q-pr-sm"
|
||||
v-model="logTypeFilter"
|
||||
label="Log Type Filter"
|
||||
:options="logTypeOptions"
|
||||
dark
|
||||
clearable
|
||||
/>
|
||||
<q-radio dark v-model="logLevelFilter" color="cyan" val="info" label="Info" />
|
||||
<q-radio dark v-model="logLevelFilter" color="red" val="critical" label="Critical" />
|
||||
<q-radio dark v-model="logLevelFilter" color="red" val="error" label="Error" />
|
||||
<q-radio dark v-model="logLevelFilter" color="yellow" val="warning" label="Warning" />
|
||||
</div>
|
||||
<q-separator />
|
||||
|
||||
<q-table
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
:rows="debugLog"
|
||||
:columns="columns"
|
||||
dense
|
||||
title="Debug Logs"
|
||||
:pagination="{ sortBy: 'entry_time', descending: true }"
|
||||
>
|
||||
<template v-slot:top-right>
|
||||
<q-btn color="primary" icon-right="archive" label="Export to csv" no-caps @click="exportDebugLog" />
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { watch, computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useDebugLog } from "@/composables/logs";
|
||||
import { exportTableToCSV } from "@/utils/csv";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown";
|
||||
|
||||
export default {
|
||||
name: "DebugTab",
|
||||
components: {
|
||||
TacticalDropdown,
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const selectedAgent = computed(() => store.state.selectedRow);
|
||||
const { debugLog, logLevelFilter, logTypeFilter, agentFilter, getDebugLog } = useDebugLog();
|
||||
|
||||
watch([logLevelFilter, logTypeFilter], () => getDebugLog());
|
||||
watch(selectedAgent, (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
agentFilter.value = selectedAgent.value;
|
||||
getDebugLog();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
// data
|
||||
debugLog,
|
||||
logLevelFilter,
|
||||
logTypeFilter,
|
||||
|
||||
// non-reactive data
|
||||
columns: useDebugLog.debugLogTableColumns,
|
||||
logTypeOptions: useDebugLog.logTypeOptions,
|
||||
|
||||
// computed
|
||||
selectedAgent,
|
||||
|
||||
// methods
|
||||
getDebugLog,
|
||||
exportDebugLog: () => {
|
||||
exportTableToCSV(debugLog.value, useDebugLog.debugLogTableColumns);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -209,7 +209,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DebugLogModal from "@/components/modals/logs/DebugLogModal";
|
||||
import DialogWrapper from "@/components/ui/DialogWrapper";
|
||||
import DebugLog from "@/components/logs/DebugLog";
|
||||
import PendingActions from "@/components/modals/logs/PendingActions";
|
||||
import ClientsManager from "@/components/ClientsManager";
|
||||
import ClientsForm from "@/components/modals/clients/ClientsForm";
|
||||
@@ -328,7 +329,19 @@ export default {
|
||||
},
|
||||
showDebugLog() {
|
||||
this.$q.dialog({
|
||||
component: DebugLogModal,
|
||||
component: DialogWrapper,
|
||||
componentProps: {
|
||||
vuecomponent: DebugLog,
|
||||
noCard: true,
|
||||
componentProps: {
|
||||
modal: true,
|
||||
},
|
||||
dialogProps: {
|
||||
maximized: true,
|
||||
["transition-show"]: "slide-up",
|
||||
["transition-hide"]: "slide-down",
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
edited() {
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
<q-tab-panel name="assets" class="q-pb-xs q-pt-none">
|
||||
<AssetsTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="debug" class="q-pb-xs q-pt-none">
|
||||
<q-tab-panel name="debug" class="q-pa-none">
|
||||
<DebugTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="audit" class="q-pb-xs q-pt-none">
|
||||
<q-tab-panel name="audit" class="q-pa-none">
|
||||
<AuditTab />
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
@@ -64,9 +64,9 @@ import ChecksTab from "@/components/ChecksTab";
|
||||
import AutomatedTasksTab from "@/components/AutomatedTasksTab";
|
||||
import WindowsUpdates from "@/components/WindowsUpdates";
|
||||
import SoftwareTab from "@/components/SoftwareTab";
|
||||
import HistoryTab from "@/components/HistoryTab";
|
||||
import AuditTab from "@/components/AuditTab";
|
||||
import DebugTab from "@/components/DebugTab";
|
||||
import HistoryTab from "@/components/agents/HistoryTab";
|
||||
import AuditTab from "@/components/agents/AuditTab";
|
||||
import DebugTab from "@/components/agents/DebugTab";
|
||||
import AssetsTab from "@/components/AssetsTab";
|
||||
import NotesTab from "@/components/NotesTab";
|
||||
|
||||
|
||||
31
src/components/agents/DebugTab.vue
Normal file
31
src/components/agents/DebugTab.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div v-if="!selectedAgent">No agent selected</div>
|
||||
<div v-else>
|
||||
<DebugLog :agentpk="selectedAgent" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
// ui imports
|
||||
import DebugLog from "@/components/logs/DebugLog";
|
||||
|
||||
export default {
|
||||
name: "DebugTab",
|
||||
components: {
|
||||
DebugLog,
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const selectedAgent = computed(() => store.state.selectedRow);
|
||||
|
||||
return {
|
||||
// computed
|
||||
selectedAgent,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
41
src/components/logs/AuditLogDetailModal.vue
Normal file
41
src/components/logs/AuditLogDetailModal.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<q-dialog ref="dialogRef" @hide="onDialogHide" maximized transition-show="slide-up" transition-hide="slide-down">
|
||||
<q-card class="q-dialog-plugin" style="width: 70vw; max-width: 90vw">
|
||||
<q-bar>
|
||||
<span class="text-caption">{{ log.message }}</span>
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup />
|
||||
</q-bar>
|
||||
<q-card-section class="row scroll" style="max-height: 65vh">
|
||||
<div class="col-6" v-if="log.before_value !== null">
|
||||
<div class="text-h6">Before</div>
|
||||
<pre>{{ JSON.stringify(log.before_value, null, 4) }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<div class="text-h6">After</div>
|
||||
<pre>{{ JSON.stringify(log.after_value, null, 4) }}</pre>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
export default {
|
||||
name: "AuditLogDetail",
|
||||
emits: [...useDialogPluginComponent.emits],
|
||||
props: {
|
||||
log: !Object,
|
||||
},
|
||||
setup() {
|
||||
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||
|
||||
return {
|
||||
dialogRef,
|
||||
onDialogHide,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
166
src/components/logs/AuditManager.vue
Normal file
166
src/components/logs/AuditManager.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<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" />
|
||||
</div>
|
||||
<div class="q-pa-sm col-2" v-if="filterType === 'agents'">
|
||||
<tactical-dropdown v-model="agentFilter" :options="agentOptions" label="Agent" clearable multiple filled />
|
||||
</div>
|
||||
<div class="q-pa-sm col-2" v-if="filterType === 'clients'">
|
||||
<tactical-dropdown
|
||||
v-model="clientFilter"
|
||||
:options="clientOptions"
|
||||
label="Clients"
|
||||
clearable
|
||||
multiple
|
||||
filled
|
||||
mapOptions
|
||||
/>
|
||||
</div>
|
||||
<div class="q-pa-sm col-2">
|
||||
<tactical-dropdown v-model="userFilter" :options="userOptions" label="Users" clearable filled multiple />
|
||||
</div>
|
||||
<div class="q-pa-sm col-2">
|
||||
<tactical-dropdown
|
||||
v-model="actionFilter"
|
||||
:options="actionOptions"
|
||||
label="Action"
|
||||
clearable
|
||||
filled
|
||||
multiple
|
||||
mapOptions
|
||||
/>
|
||||
</div>
|
||||
<div class="q-pa-sm col-2">
|
||||
<tactical-dropdown
|
||||
v-model="objectFilter"
|
||||
:options="objectOptions"
|
||||
label="Object"
|
||||
clearable
|
||||
filled
|
||||
multiple
|
||||
mapOptions
|
||||
/>
|
||||
</div>
|
||||
<div class="q-pa-sm col-2">
|
||||
<tactical-dropdown v-model="timeFilter" :options="timeOptions" label="Time" filled mapOptions />
|
||||
</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"
|
||||
:title="modal ? 'Audit Logs' : ''"
|
||||
:rows="auditLogs"
|
||||
:columns="columns"
|
||||
row-key="id"
|
||||
dense
|
||||
binary-state-sort
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[25, 50, 100, 500, 1000]"
|
||||
:no-data-label="tableNoDataText"
|
||||
@row-click="openAuditDetail"
|
||||
:loading="loading"
|
||||
>
|
||||
<template v-slot:top-right>
|
||||
<export-table-btn :data="auditLogs" :columns="columns" />
|
||||
</template>
|
||||
<template v-slot:body-cell-action="props">
|
||||
<q-td :props="props">
|
||||
<div>
|
||||
<q-badge :color="formatActionColor(props.value)" :label="props.value" />
|
||||
</div>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { onMounted } from "vue";
|
||||
import { useAuditLog } from "@/composables/logs";
|
||||
import { useClientDropdown } from "@/composables/clients";
|
||||
import { useAgentDropdown } from "@/composables/agents";
|
||||
import { useUserDropdown } from "@/composables/accounts";
|
||||
|
||||
// ui imported
|
||||
import ExportTableBtn from "@/components/ui/ExportTableBtn"
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown";
|
||||
import ExportTableBtn from '../ui/ExportTableBtn.vue';
|
||||
|
||||
export default {
|
||||
name: "AuditManager",
|
||||
components: { TacticalDropdown, ExportTableBtn },
|
||||
props: {
|
||||
agentpk:
|
||||
ExportTableBtn Number,
|
||||
modal: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
// setup dropdowns
|
||||
const { clientOptions, getClientOptions } = useClientDropdown();
|
||||
const { agentOptions, getAgentOptions } = useAgentDropdown();
|
||||
const { userOptions, userDropdownLoading, getUserOptions } = useUserDropdown();
|
||||
|
||||
onMounted(() => {
|
||||
getClientOptions();
|
||||
getAgentOptions(true);
|
||||
getUserOptions(true);
|
||||
});
|
||||
|
||||
const AuditLog = useAuditLog();
|
||||
|
||||
return {
|
||||
// data
|
||||
auditLogs: AuditLog.auditLogs,
|
||||
agentFilter: AuditLog.agentFilter,
|
||||
userFilter: AuditLog.userFilter,
|
||||
actionFilter: AuditLog.actionFilter,
|
||||
clientFilter: AuditLog.clientFilter,
|
||||
objectFilter: AuditLog.objectFilter,
|
||||
timeFilter: AuditLog.timeFilter,
|
||||
filterType: AuditLog.filterType,
|
||||
loading: AuditLog.loading,
|
||||
pagination: AuditLog.pagination,
|
||||
userOptions,
|
||||
userDropdownLoading,
|
||||
|
||||
// non-reactive data
|
||||
clientOptions,
|
||||
agentOptions,
|
||||
columns: useAuditLog.tableColumns,
|
||||
actionOptions: useAuditLog.actionOptions,
|
||||
objectOptions: useAuditLog.objectOptions,
|
||||
timeOptions: useAuditLog.timeOptions,
|
||||
filterTypeOptions: useAuditLog.filterTypeOptions,
|
||||
|
||||
//computed
|
||||
tableNoDataText: AuditLog.noDataText,
|
||||
|
||||
// methods
|
||||
search: AuditLog.search,
|
||||
onRequest: AuditLog.onRequest,
|
||||
openAuditDetail: AuditLog.openAuditDetail,
|
||||
formatActionColor: AuditLog.formatActionColor,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
111
src/components/logs/DebugLog.vue
Normal file
111
src/components/logs/DebugLog.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<q-card>
|
||||
<q-bar v-if="modal">
|
||||
<q-btn @click="getDebugLog" class="q-mr-sm" dense flat push icon="refresh" />Debug Log
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<q-card-section class="row">
|
||||
<tactical-dropdown
|
||||
v-if="!agentpk"
|
||||
class="col-2 q-pr-sm"
|
||||
v-model="agentFilter"
|
||||
label="Agents Filter"
|
||||
:options="agentOptions"
|
||||
mapOptions
|
||||
outlined
|
||||
clearable
|
||||
/>
|
||||
<tactical-dropdown
|
||||
class="col-2 q-pr-sm"
|
||||
v-model="logTypeFilter"
|
||||
label="Log Type Filter"
|
||||
:options="logTypeOptions"
|
||||
mapOptions
|
||||
outlined
|
||||
clearable
|
||||
/>
|
||||
<q-radio v-model="logLevelFilter" color="cyan" val="info" label="Info" />
|
||||
<q-radio v-model="logLevelFilter" color="red" val="critical" label="Critical" />
|
||||
<q-radio v-model="logLevelFilter" color="red" val="error" label="Error" />
|
||||
<q-radio v-model="logLevelFilter" color="yellow" val="warning" label="Warning" />
|
||||
<q-space />
|
||||
<export-table-btn v-if="!modal" :data="debugLog" :columns="columns" />
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-section>
|
||||
<q-table
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
:rows="debugLog"
|
||||
:columns="columns"
|
||||
:title="modal ? 'Debug Logs' : ''"
|
||||
:pagination="{ sortBy: 'entry_time', descending: true }"
|
||||
dense
|
||||
binary-state-sort
|
||||
>
|
||||
<template v-slot:top-right>
|
||||
<export-table-btn v-if="modal" :data="debugLog" :columns="columns" />
|
||||
</template>
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition api
|
||||
import { watch, onMounted } from "vue";
|
||||
import { useDebugLog } from "@/composables/logs";
|
||||
import { useAgentDropdown } from "@/composables/agents";
|
||||
|
||||
// ui components
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown";
|
||||
import ExportTableBtn from "@/components/ui/ExportTableBtn.vue";
|
||||
|
||||
export default {
|
||||
name: "LogModal",
|
||||
components: {
|
||||
TacticalDropdown,
|
||||
ExportTableBtn,
|
||||
},
|
||||
props: {
|
||||
agentpk: Number,
|
||||
modal: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { debugLog, logLevelFilter, logTypeFilter, agentFilter, getDebugLog } = useDebugLog();
|
||||
const { agentOptions, getAgentOptions } = useAgentDropdown();
|
||||
|
||||
if (props.agentpk) {
|
||||
agentFilter.value = props.agentpk;
|
||||
}
|
||||
|
||||
watch([logLevelFilter, agentFilter, logTypeFilter], getDebugLog);
|
||||
|
||||
onMounted(() => {
|
||||
if (!props.agentpk) getAgentOptions();
|
||||
getDebugLog();
|
||||
});
|
||||
|
||||
return {
|
||||
// data
|
||||
debugLog,
|
||||
logLevelFilter,
|
||||
logTypeFilter,
|
||||
agentFilter,
|
||||
agentOptions,
|
||||
|
||||
// non-reactive data
|
||||
columns: useDebugLog.tableColumns,
|
||||
logTypeOptions: useDebugLog.logTypeOptions,
|
||||
|
||||
// methods
|
||||
getDebugLog,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,31 +0,0 @@
|
||||
<template>
|
||||
<q-card style="width: 70vw; max-width: 90vw">
|
||||
<q-bar>
|
||||
<span class="text-caption">{{ log.message }}</span>
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup />
|
||||
</q-bar>
|
||||
<q-card-section class="row scroll" style="max-height: 65vh">
|
||||
<div class="col-6" v-if="log.before_value !== null">
|
||||
<div class="text-h6">Before</div>
|
||||
<pre>{{ JSON.stringify(log.before_value, null, 4) }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<div class="text-h6">After</div>
|
||||
<pre>{{ JSON.stringify(log.after_value, null, 4) }}</pre>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "AuditLogDetail",
|
||||
props: {
|
||||
log: Object,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,105 +0,0 @@
|
||||
<template>
|
||||
<q-dialog ref="dialogRef" @hide="onDialogHide" maximized transition-show="slide-up" transition-hide="slide-down">
|
||||
<q-card class="q-dialog-plugin bg-grey-10 text-white">
|
||||
<q-bar>
|
||||
<q-btn @click="getDebugLog" class="q-mr-sm" dense flat push icon="refresh" />Debug Log
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<q-card-section class="row">
|
||||
<tactical-dropdown
|
||||
class="col-2 q-pr-sm"
|
||||
v-model="agentFilter"
|
||||
label="Agents Filter"
|
||||
:options="agentOptions"
|
||||
dark
|
||||
clearable
|
||||
/>
|
||||
<tactical-dropdown
|
||||
class="col-2 q-pr-sm"
|
||||
v-model="logTypeFilter"
|
||||
label="Log Type Filter"
|
||||
:options="logTypeOptions"
|
||||
dark
|
||||
clearable
|
||||
/>
|
||||
<q-radio dark v-model="logLevelFilter" color="cyan" val="info" label="Info" />
|
||||
<q-radio dark v-model="logLevelFilter" color="red" val="critical" label="Critical" />
|
||||
<q-radio dark v-model="logLevelFilter" color="red" val="error" label="Error" />
|
||||
<q-radio dark v-model="logLevelFilter" color="yellow" val="warning" label="Warning" />
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-section>
|
||||
<q-table
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
:rows="debugLog"
|
||||
:columns="columns"
|
||||
dense
|
||||
title="Debug Logs"
|
||||
:pagination="{ sortBy: 'entry_time', descending: true }"
|
||||
>
|
||||
<template v-slot:top-right>
|
||||
<q-btn color="primary" icon-right="archive" label="Export to csv" no-caps @click="exportDebugLog" />
|
||||
</template>
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition api
|
||||
import { watch, onMounted } from "vue";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import { useDebugLog } from "@/composables/logs";
|
||||
import { useAgentDropdown } from "@/composables/agents";
|
||||
import { exportTableToCSV } from "@/utils/csv";
|
||||
|
||||
// ui components
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown";
|
||||
|
||||
export default {
|
||||
name: "LogModal",
|
||||
components: {
|
||||
TacticalDropdown,
|
||||
},
|
||||
emits: [...useDialogPluginComponent.emits],
|
||||
setup() {
|
||||
const { debugLog, logLevelFilter, logTypeFilter, agentFilter, getDebugLog } = useDebugLog();
|
||||
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||
const { agentOptions, getAgentOptions } = useAgentDropdown();
|
||||
|
||||
watch([logLevelFilter, agentFilter, logTypeFilter], getDebugLog);
|
||||
|
||||
onMounted(() => {
|
||||
getAgentOptions();
|
||||
getDebugLog();
|
||||
});
|
||||
|
||||
return {
|
||||
// data
|
||||
debugLog,
|
||||
logLevelFilter,
|
||||
logTypeFilter,
|
||||
agentFilter,
|
||||
agentOptions,
|
||||
|
||||
// non-reactive data
|
||||
columns: useDebugLog.debugLogTableColumns,
|
||||
logTypeOptions: useDebugLog.logTypeOptions,
|
||||
|
||||
// methods
|
||||
getDebugLog,
|
||||
exportDebugLog: () => {
|
||||
exportTableToCSV(debugLog.value, useDebugLog.debugLogTableColumns);
|
||||
},
|
||||
|
||||
// quasar dialog plugin
|
||||
dialogRef,
|
||||
onDialogHide,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<q-dialog ref="dialog" @hide="onHide" v-bind="dialogProps">
|
||||
<q-card class="q-dialog-plugin" :style="`min-width: ${width}vw`">
|
||||
<q-dialog ref="dialogRef" @hide="onDialogHide" v-bind="dialogProps">
|
||||
<q-card v-if="!noCard" class="q-dialog-plugin" :style="`min-width: ${width}vw`">
|
||||
<q-bar>
|
||||
{{ title }}
|
||||
<q-space />
|
||||
@@ -9,16 +9,24 @@
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<div class="scroll" :style="`height: ${height}vh`">
|
||||
<component :is="vuecomponent" v-bind="{ ...$attrs, ...componentProps }" @close="onOk" @hide="hide" />
|
||||
<component
|
||||
:is="vuecomponent"
|
||||
v-bind="{ ...$attrs, ...componentProps }"
|
||||
@close="onDialogOK"
|
||||
@hide="onDialogHide"
|
||||
/>
|
||||
</div>
|
||||
</q-card>
|
||||
<component v-else class="q-dialog-plugin" :is="vuecomponent" v-bind="{ ...$attrs, ...componentProps }" />
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
|
||||
export default {
|
||||
name: "DialogWrapper",
|
||||
emits: ["hide", "ok", "cancel"],
|
||||
emits: [...useDialogPluginComponent.emits],
|
||||
props: {
|
||||
vuecomponent: {},
|
||||
title: String,
|
||||
@@ -30,24 +38,24 @@ export default {
|
||||
type: String,
|
||||
default: "50",
|
||||
},
|
||||
noCard: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
componentProps: Object,
|
||||
dialogProps: Object,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
methods: {
|
||||
show() {
|
||||
this.$refs.dialog.show();
|
||||
},
|
||||
hide() {
|
||||
this.$refs.dialog.hide();
|
||||
},
|
||||
onHide() {
|
||||
this.$emit("hide");
|
||||
},
|
||||
onOk() {
|
||||
this.$emit("ok");
|
||||
this.hide();
|
||||
},
|
||||
setup() {
|
||||
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
|
||||
|
||||
return {
|
||||
// quasar dialog plugin
|
||||
dialogRef,
|
||||
onDialogHide,
|
||||
onDialogOK,
|
||||
onDialogCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
22
src/components/ui/ExportTableBtn.vue
Normal file
22
src/components/ui/ExportTableBtn.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<q-btn dense color="primary" icon-right="archive" @click="export">
|
||||
<q-tooltip>Export table as CSV</q-tooltip>
|
||||
</q-btn>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { exportTableToCSV } from "@/utils/csv";
|
||||
|
||||
export default {
|
||||
name: "export-table-btn",
|
||||
props: {
|
||||
columns: !Array,
|
||||
data: !Array,
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
export: () => exportTableToCSV(props.data, props.columns),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -2,16 +2,17 @@
|
||||
<q-select
|
||||
dense
|
||||
options-dense
|
||||
outlined
|
||||
@update:model-value="value => $emit('update:modelValue', value)"
|
||||
:model-value="modelValue"
|
||||
map-options
|
||||
emit-value
|
||||
:map-options="mapOptions"
|
||||
:emit-value="mapOptions"
|
||||
:multiple="multiple"
|
||||
:use-chips="multiple"
|
||||
>
|
||||
<template v-slot:option="scope">
|
||||
<q-item v-if="!scope.opt.category" v-bind="scope.itemProps" class="q-pl-lg">
|
||||
<q-item-section>
|
||||
<q-item-label v-html="scope.opt.label"></q-item-label>
|
||||
<q-item-label v-html="mapOptions ? scope.opt.label : scope.opt"></q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item-label v-if="scope.opt.category" header class="q-pa-sm">{{ scope.opt.category }}</q-item-label>
|
||||
@@ -23,6 +24,14 @@ export default {
|
||||
name: "tactical-dropdown",
|
||||
props: {
|
||||
modelValue: !String,
|
||||
mapOptions: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
44
src/composables/accounts.js
Normal file
44
src/composables/accounts.js
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
import { ref } from "vue"
|
||||
import { fetchUsers } from "@/api/accounts"
|
||||
import { formatUserOptions } from "@/utils/format"
|
||||
|
||||
export function useUserDropdown() {
|
||||
|
||||
const userOptions = ref([])
|
||||
const userDropdownLoading = ref(false)
|
||||
|
||||
async function getUserOptions(flat = false) {
|
||||
userOptions.value = formatUserOptions(await fetchUsers(), flat)
|
||||
}
|
||||
|
||||
function getDynamicUserOptions(val, update, abort) {
|
||||
if (!val || val.length < 2) {
|
||||
abort()
|
||||
return
|
||||
}
|
||||
|
||||
update(async () => {
|
||||
userDropdownLoading.value = true
|
||||
|
||||
const params = {
|
||||
search: val.toLowerCase()
|
||||
}
|
||||
|
||||
const options = await fetchUsers(params)
|
||||
|
||||
userOptions.value = options.map(user => user.username)
|
||||
userDropdownLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
//data
|
||||
userOptions,
|
||||
userDropdownLoading,
|
||||
|
||||
//methods
|
||||
getUserOptions,
|
||||
getDynamicUserOptions
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ export function useAgentDropdown() {
|
||||
|
||||
const agentOptions = ref([])
|
||||
|
||||
const getAgentOptions = async () => {
|
||||
agentOptions.value = formatAgentOptions(await fetchAgents())
|
||||
async function getAgentOptions(flat = false) {
|
||||
agentOptions.value = formatAgentOptions(await fetchAgents(), flat)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
37
src/composables/clients.js
Normal file
37
src/composables/clients.js
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
import { ref } from "vue"
|
||||
import { fetchClients } from "@/api/clients"
|
||||
import { formatClientOptions, formatSiteOptions } from "@/utils/format"
|
||||
|
||||
export function useClientDropdown() {
|
||||
|
||||
const clientOptions = ref([])
|
||||
|
||||
async function getClientOptions(flat = false) {
|
||||
clientOptions.value = formatClientOptions(await fetchClients(), flat)
|
||||
}
|
||||
|
||||
return {
|
||||
//data
|
||||
clientOptions,
|
||||
|
||||
//methods
|
||||
getClientOptions
|
||||
}
|
||||
}
|
||||
|
||||
export function useSiteDropdown() {
|
||||
const siteOptions = ref([])
|
||||
|
||||
async function getSiteOptions() {
|
||||
siteOptions.value = formatSiteOptions(await fetchSites())
|
||||
}
|
||||
|
||||
return {
|
||||
//data
|
||||
siteOptions,
|
||||
|
||||
//methods
|
||||
getSiteOptions
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ref } from "vue"
|
||||
import { fetchDebugLog } from "@/api/logs"
|
||||
import { ref, computed, watch } from "vue"
|
||||
import { useQuasar } from "quasar"
|
||||
import { fetchDebugLog, fetchAuditLog } from "@/api/logs"
|
||||
import { formatDate, formatTableColumnText } from "@/utils/format"
|
||||
import AuditLogDetailModal from "@/components/logs/AuditLogDetailModal";
|
||||
|
||||
// debug log
|
||||
export function useDebugLog() {
|
||||
@@ -10,7 +12,7 @@ export function useDebugLog() {
|
||||
const logLevelFilter = ref("info")
|
||||
const logTypeFilter = ref(null)
|
||||
|
||||
const getDebugLog = async () => {
|
||||
async function getDebugLog() {
|
||||
const data = {
|
||||
logLevelFilter: logLevelFilter.value
|
||||
}
|
||||
@@ -33,14 +35,14 @@ export function useDebugLog() {
|
||||
}
|
||||
|
||||
useDebugLog.logTypeOptions = [
|
||||
{ label: "Agent Update", value: "agent_value" },
|
||||
{ label: "Agent Update", value: "agent_update" },
|
||||
{ label: "Agent Issues", value: "agent_issues" },
|
||||
{ label: "Windows Updates", value: "windows_updates" },
|
||||
{ label: "System Issues", value: "system_issues" },
|
||||
{ label: "Scripting", value: "scripting" }
|
||||
]
|
||||
|
||||
useDebugLog.debugLogTableColumns = [
|
||||
useDebugLog.tableColumns = [
|
||||
{
|
||||
name: "entry_time",
|
||||
label: "Time",
|
||||
@@ -60,4 +62,189 @@ useDebugLog.debugLogTableColumns = [
|
||||
format: (val, row) => formatTableColumnText(val),
|
||||
},
|
||||
{ name: "message", label: "Message", field: "message", align: "left", sortable: true },
|
||||
]
|
||||
|
||||
// audit Log
|
||||
export function useAuditLog() {
|
||||
const auditLogs = ref([])
|
||||
const agentFilter = ref(null)
|
||||
const userFilter = ref(null)
|
||||
const actionFilter = ref(null)
|
||||
const clientFilter = ref(null)
|
||||
const objectFilter = ref(null)
|
||||
const timeFilter = ref(7)
|
||||
const filterType = ref("clients")
|
||||
const loading = ref(false)
|
||||
const searched = ref(false)
|
||||
|
||||
const pagination = ref({
|
||||
rowsPerPage: 25,
|
||||
rowsNumber: null,
|
||||
sortBy: "entry_time",
|
||||
descending: true,
|
||||
page: 1,
|
||||
})
|
||||
|
||||
async function search() {
|
||||
loading.value = true
|
||||
searched.value = true;
|
||||
|
||||
const data = {
|
||||
pagination: pagination.value
|
||||
};
|
||||
|
||||
if (!!agentFilter.value && agentFilter.value.length > 0) data["agentFilter"] = agentFilter.value;
|
||||
else if (!!clientFilter.value && clientFilter.value.length > 0) data["clientFilter"] = clientFilter.value;
|
||||
if (!!userFilter.value && userFilter.value.length > 0) data["userFilter"] = userFilter.value;
|
||||
if (!!timeFilter.value) data["timeFilter"] = timeFilter.value;
|
||||
if (!!actionFilter.value && actionFilter.value.length > 0) data["actionFilter"] = actionFilter.value;
|
||||
if (!!objectFilter.value && objectFilter.value.length > 0) data["objectFilter"] = objectFilter.value;
|
||||
|
||||
const { audit_logs, total } = await fetchAuditLog(data)
|
||||
auditLogs.value = audit_logs
|
||||
pagination.value.rowsNumber = total
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function onRequest(props) {
|
||||
const { page, rowsPerPage, sortBy, descending } = props.pagination;
|
||||
|
||||
pagination.value.page = page;
|
||||
pagination.value.rowsPerPage = rowsPerPage;
|
||||
pagination.value.sortBy = sortBy;
|
||||
pagination.value.descending = descending;
|
||||
|
||||
search();
|
||||
}
|
||||
|
||||
const { dialog } = useQuasar()
|
||||
function openAuditDetail(evt, log) {
|
||||
dialog({
|
||||
component: AuditLogDetailModal,
|
||||
componentProps: {
|
||||
log
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function formatActionColor(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";
|
||||
}
|
||||
|
||||
watch(filterType, () => {
|
||||
agentFilter.value = null
|
||||
clientFilter.value = null
|
||||
})
|
||||
|
||||
const noDataText = computed(() => searched ? "No data found. Try to refine you search" : "Click search to find audit logs")
|
||||
|
||||
return {
|
||||
// data
|
||||
auditLogs,
|
||||
agentFilter,
|
||||
userFilter,
|
||||
actionFilter,
|
||||
clientFilter,
|
||||
objectFilter,
|
||||
timeFilter,
|
||||
filterType,
|
||||
loading,
|
||||
searched,
|
||||
pagination,
|
||||
|
||||
//computed
|
||||
noDataText,
|
||||
|
||||
// methods
|
||||
search,
|
||||
onRequest,
|
||||
openAuditDetail,
|
||||
formatActionColor
|
||||
}
|
||||
}
|
||||
|
||||
useAuditLog.tableColumns = [
|
||||
{
|
||||
name: "entry_time",
|
||||
label: "Time",
|
||||
field: "entry_time",
|
||||
align: "left",
|
||||
sortable: true,
|
||||
format: (val, row) => 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,
|
||||
format: (val, row) => formatTableColumnText(val)
|
||||
},
|
||||
{
|
||||
name: "object_type",
|
||||
label: "Object",
|
||||
field: "object_type",
|
||||
align: "left",
|
||||
sortable: true,
|
||||
format: (val, row) => formatTableColumnText(val),
|
||||
},
|
||||
{ name: "message", label: "Message", field: "message", align: "left", sortable: true },
|
||||
]
|
||||
|
||||
useAuditLog.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" },
|
||||
]
|
||||
|
||||
useAuditLog.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" },
|
||||
]
|
||||
|
||||
useAuditLog.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" },
|
||||
]
|
||||
|
||||
useAuditLog.filterTypeOptions = [
|
||||
{
|
||||
label: "Clients",
|
||||
value: "clients",
|
||||
},
|
||||
{
|
||||
label: "Agents",
|
||||
value: "agents",
|
||||
},
|
||||
]
|
||||
@@ -1,35 +1,84 @@
|
||||
|
||||
export function formatAgentOptions(data) {
|
||||
let options = []
|
||||
const agents = data.map(agent => ({
|
||||
label: agent.hostname,
|
||||
value: agent.pk,
|
||||
cat: `${agent.client} > ${agent.site}`,
|
||||
}));
|
||||
// dropdown options formatting
|
||||
|
||||
let categories = [];
|
||||
agents.forEach(option => {
|
||||
if (!categories.includes(option.cat)) {
|
||||
categories.push(option.cat);
|
||||
function _formatOptions(data, { label, value = "id", flat = false, allowDuplicates = true }) {
|
||||
if (!flat)
|
||||
// returns array of options in object format [{label: label, value: 1}]
|
||||
return data.map(i => ({ label: i[label], value: i[value] }));
|
||||
else
|
||||
// returns options as an array of strings ["label", "label1"]
|
||||
if (!allowDuplicates)
|
||||
return data.map(i => i[label]);
|
||||
else {
|
||||
const options = []
|
||||
data.forEach(i => {
|
||||
if (!options.includes(i[label]))
|
||||
options.push(i[label])
|
||||
});
|
||||
return options
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
categories.sort().forEach(cat => {
|
||||
options.push({ category: cat });
|
||||
let tmp = []
|
||||
agents.forEach(agent => {
|
||||
if (agent.cat === cat) {
|
||||
tmp.push(agent);
|
||||
export function formatAgentOptions(data, flat = false) {
|
||||
|
||||
if (flat) {
|
||||
// returns just agent hostnames in array
|
||||
return _formatOptions(data, { label: "hostname", value: "pk", flat: true, allowDuplicates: false })
|
||||
} else {
|
||||
// returns options with categories in object format
|
||||
let options = []
|
||||
const agents = data.map(agent => ({
|
||||
label: agent.hostname,
|
||||
value: agent.pk,
|
||||
cat: `${agent.client} > ${agent.site}`,
|
||||
}));
|
||||
|
||||
let categories = [];
|
||||
agents.forEach(option => {
|
||||
if (!categories.includes(option.cat)) {
|
||||
categories.push(option.cat);
|
||||
}
|
||||
});
|
||||
|
||||
const sorted = tmp.sort((a, b) => a.label.localeCompare(b.label));
|
||||
options.push(...sorted);
|
||||
categories.sort().forEach(cat => {
|
||||
options.push({ category: cat });
|
||||
let tmp = []
|
||||
agents.forEach(agent => {
|
||||
if (agent.cat === cat) {
|
||||
tmp.push(agent);
|
||||
}
|
||||
});
|
||||
|
||||
const sorted = tmp.sort((a, b) => a.label.localeCompare(b.label));
|
||||
options.push(...sorted);
|
||||
});
|
||||
|
||||
return options
|
||||
}
|
||||
}
|
||||
|
||||
export function formatClientOptions(data, flat = false) {
|
||||
return _formatOptions(data, { label: "name", flat: flat })
|
||||
}
|
||||
|
||||
export function formatSiteOptions(data, flat = false) {
|
||||
const options = []
|
||||
|
||||
data.forEach(client => {
|
||||
options.push({ category: client.name });
|
||||
options.push(..._formatOptions(client.sites, { label: "name", flat: flat }))
|
||||
});
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
export function formatUserOptions(data, flat = false) {
|
||||
return _formatOptions(data, { label: "username", flat: flat })
|
||||
}
|
||||
|
||||
|
||||
// date formatting
|
||||
|
||||
function _appendLeadingZeroes(n) {
|
||||
if (n <= 9) {
|
||||
return "0" + n;
|
||||
@@ -46,6 +95,8 @@ export function formatDate(date, includeSeconds = false) {
|
||||
return includeSeconds ? formatted + ":" + _appendLeadingZeroes(dt.getSeconds()) : formatted
|
||||
}
|
||||
|
||||
|
||||
// string formatting
|
||||
export function capitalize(string) {
|
||||
return string[0].toUpperCase() + string.substring(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user