@@ -130,287 +63,98 @@
-
+
-
+
-
\ No newline at end of file
diff --git a/src/components/DebugTab.vue b/src/components/DebugTab.vue
deleted file mode 100644
index 0658b3f..0000000
--- a/src/components/DebugTab.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-
- No agent selected
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/components/FileBar.vue b/src/components/FileBar.vue
index d6605c0..924f963 100644
--- a/src/components/FileBar.vue
+++ b/src/components/FileBar.vue
@@ -209,7 +209,8 @@
\ No newline at end of file
diff --git a/src/components/HistoryTab.vue b/src/components/agents/HistoryTab.vue
similarity index 100%
rename from src/components/HistoryTab.vue
rename to src/components/agents/HistoryTab.vue
diff --git a/src/components/logs/AuditLogDetailModal.vue b/src/components/logs/AuditLogDetailModal.vue
new file mode 100644
index 0000000..68f69ef
--- /dev/null
+++ b/src/components/logs/AuditLogDetailModal.vue
@@ -0,0 +1,41 @@
+
+
+
+
+ {{ log.message }}
+
+
+
+
+
+
Before
+
{{ JSON.stringify(log.before_value, null, 4) }}
+
+
+
+
After
+
{{ JSON.stringify(log.after_value, null, 4) }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/logs/AuditManager.vue b/src/components/logs/AuditManager.vue
new file mode 100644
index 0000000..c0cef75
--- /dev/null
+++ b/src/components/logs/AuditManager.vue
@@ -0,0 +1,166 @@
+
+
+
+
+ Audit Manager
+
+
+ Close
+
+
+ Filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/logs/DebugLog.vue b/src/components/logs/DebugLog.vue
new file mode 100644
index 0000000..dc2225f
--- /dev/null
+++ b/src/components/logs/DebugLog.vue
@@ -0,0 +1,111 @@
+
+
+
+ Debug Log
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/modals/logs/AuditLogDetail.vue b/src/components/modals/logs/AuditLogDetail.vue
deleted file mode 100644
index ac030e2..0000000
--- a/src/components/modals/logs/AuditLogDetail.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- {{ log.message }}
-
-
-
-
-
-
Before
-
{{ JSON.stringify(log.before_value, null, 4) }}
-
-
-
-
After
-
{{ JSON.stringify(log.after_value, null, 4) }}
-
-
-
-
-
\ No newline at end of file
diff --git a/src/components/modals/logs/DebugLogModal.vue b/src/components/modals/logs/DebugLogModal.vue
deleted file mode 100644
index 5fccf8f..0000000
--- a/src/components/modals/logs/DebugLogModal.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-
-
-
-
- Debug Log
-
-
- Close
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/components/ui/DialogWrapper.vue b/src/components/ui/DialogWrapper.vue
index b65e670..2d731cb 100644
--- a/src/components/ui/DialogWrapper.vue
+++ b/src/components/ui/DialogWrapper.vue
@@ -1,6 +1,6 @@
-
-
+
+
{{ title }}
@@ -9,16 +9,24 @@
-
+
+
\ No newline at end of file
diff --git a/src/components/ui/ExportTableBtn.vue b/src/components/ui/ExportTableBtn.vue
new file mode 100644
index 0000000..107739d
--- /dev/null
+++ b/src/components/ui/ExportTableBtn.vue
@@ -0,0 +1,22 @@
+
+
+ Export table as CSV
+
+
+
+
\ No newline at end of file
diff --git a/src/components/ui/TacticalDropdown.vue b/src/components/ui/TacticalDropdown.vue
index 783a7fd..d81f840 100644
--- a/src/components/ui/TacticalDropdown.vue
+++ b/src/components/ui/TacticalDropdown.vue
@@ -2,16 +2,17 @@
$emit('update:modelValue', value)"
:model-value="modelValue"
- map-options
- emit-value
+ :map-options="mapOptions"
+ :emit-value="mapOptions"
+ :multiple="multiple"
+ :use-chips="multiple"
>
-
+
{{ scope.opt.category }}
@@ -23,6 +24,14 @@ export default {
name: "tactical-dropdown",
props: {
modelValue: !String,
+ mapOptions: {
+ type: Boolean,
+ default: false,
+ },
+ multiple: {
+ type: Boolean,
+ default: false,
+ },
},
};
\ No newline at end of file
diff --git a/src/composables/accounts.js b/src/composables/accounts.js
new file mode 100644
index 0000000..b340c82
--- /dev/null
+++ b/src/composables/accounts.js
@@ -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
+ }
+}
diff --git a/src/composables/agents.js b/src/composables/agents.js
index 38031a1..1487480 100644
--- a/src/composables/agents.js
+++ b/src/composables/agents.js
@@ -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 {
diff --git a/src/composables/clients.js b/src/composables/clients.js
new file mode 100644
index 0000000..af373d9
--- /dev/null
+++ b/src/composables/clients.js
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/src/composables/logs.js b/src/composables/logs.js
index 28dd938..5da2feb 100644
--- a/src/composables/logs.js
+++ b/src/composables/logs.js
@@ -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",
+ },
]
\ No newline at end of file
diff --git a/src/utils/format.js b/src/utils/format.js
index 5ef6f9c..331ee25 100644
--- a/src/utils/format.js
+++ b/src/utils/format.js
@@ -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);
}