add typescript support and stricter formatting/linting

This commit is contained in:
sadnub
2022-04-27 17:37:33 -04:00
parent 98f64e057a
commit 2da0d5ee21
153 changed files with 11086 additions and 5488 deletions

23
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"recommendations": [
// frontend
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"johnsoncodehk.volar",
"wayou.vscode-todo-highlight",
// python
"matangover.mypy",
"ms-python.python",
// golang
"golang.go"
],
"unwantedRecommendations": [
"octref.vetur",
"hookyqr.beautify",
"dbaeumer.jshint",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}

53
.vscode/settings.json vendored
View File

@@ -1,17 +1,13 @@
{
"python.defaultInterpreterPath": "api/tacticalrmm/env/bin/python",
"python.languageServer": "Pylance",
"python.analysis.extraPaths": [
"api/tacticalrmm",
"api/env",
],
"python.analysis.extraPaths": ["api/tacticalrmm", "api/env"],
"python.analysis.diagnosticSeverityOverrides": {
"reportUnusedImport": "error",
"reportDuplicateImport": "error",
"reportGeneralTypeIssues": "none"
},
"python.analysis.typeCheckingMode": "basic",
"mypy.runUsingActiveInterpreter": true,
"python.linting.enabled": true,
"python.linting.mypyEnabled": true,
"python.linting.mypyArgs": [
@@ -20,20 +16,21 @@
"--show-column-numbers",
"--strict"
],
"python.linting.ignorePatterns": [
"**/site-packages/**/*.py",
".vscode/*.py",
"**env/**"
],
"python.formatting.provider": "black",
"mypy.targets": ["api/tacticalrmm"],
"mypy.runUsingActiveInterpreter": true,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"vetur.format.defaultFormatter.js": "prettier",
"vetur.format.defaultFormatterOptions": {
"prettier": {
"semi": true,
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "avoid",
}
},
"vetur.format.options.tabSize": 2,
"vetur.format.options.useTabs": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"typescript.tsdk": "node_modules/typescript/lib",
"files.watcherExclude": {
"files.watcherExclude": {
"**/.git/objects/**": true,
@@ -54,33 +51,25 @@
"**/*.parquet*": true,
"**/*.pyc": true,
"**/*.zip": true
},
}
},
"go.useLanguageServer": true,
"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": false,
"source.organizeImports": false
},
"editor.snippetSuggestions": "none",
"editor.snippetSuggestions": "none"
},
"[go.mod]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
},
"source.organizeImports": true
}
},
"gopls": {
"usePlaceholders": true,
"completeUnimported": true,
"staticcheck": true,
},
"mypy.targets": [
"api/tacticalrmm"
],
"python.linting.ignorePatterns": [
"**/site-packages/**/*.py",
".vscode/*.py",
"**env/**"
]
"staticcheck": true
}
}

View File

@@ -1,19 +0,0 @@
{
"plugins": ["@babel/plugin-syntax-dynamic-import"],
"env": {
"test": {
"plugins": ["dynamic-import-node"],
"presets": [
[
"@babel/preset-env",
{
"modules": "commonjs",
"targets": {
"node": "current"
}
}
]
]
}
}
}

9
web/.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

9
web/.eslintignore Normal file
View File

@@ -0,0 +1,9 @@
/dist
/src-bex/www
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.js
babel.config.js
/src-ssr

88
web/.eslintrc.js Normal file
View File

@@ -0,0 +1,88 @@
module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true,
// https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
// Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
// `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
parserOptions: {
parser: require.resolve("@typescript-eslint/parser"),
extraFileExtensions: [".vue"],
},
env: {
browser: true,
es2021: true,
node: true,
"vue/setup-compiler-macros": true,
},
// Rules order is important, please avoid shuffling them
extends: [
// Base ESLint recommended rules
// 'eslint:recommended',
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
// ESLint typescript rules
"plugin:@typescript-eslint/recommended",
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
"plugin:vue/vue3-essential", // Priority A: Essential (Error Prevention)
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
// https://github.com/prettier/eslint-config-prettier#installation
// usage with Prettier, provided by 'eslint-config-prettier'.
"prettier",
],
plugins: [
// required to apply rules which need type information
"@typescript-eslint",
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
"vue",
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
// Prettier has not been included as plugin to avoid performance impact
// add it as an extension for your IDE
],
globals: {
ga: "readonly", // Google Analytics
cordova: "readonly",
__statics: "readonly",
__QUASAR_SSR__: "readonly",
__QUASAR_SSR_SERVER__: "readonly",
__QUASAR_SSR_CLIENT__: "readonly",
__QUASAR_SSR_PWA__: "readonly",
process: "readonly",
Capacitor: "readonly",
chrome: "readonly",
},
// add your custom rules here
rules: {
"prefer-promise-reject-errors": "off",
quotes: ["warn", "double", { avoidEscape: true }],
// this rule, if on, would require explicit return type on the `render` function
"@typescript-eslint/explicit-function-return-type": "off",
// in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled
"@typescript-eslint/no-var-requires": "off",
// The core 'no-unused-vars' rules (in the eslint:recommended ruleset)
// does not work with type definitions
"no-unused-vars": "off",
// allow debugger during development only
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
},
};

View File

@@ -3,6 +3,6 @@
module.exports = {
plugins: [
// to edit target browsers: use "browserslist" field in package.json
require('autoprefixer')
]
}
require("autoprefixer"),
],
};

4
web/.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"singleQuote": false,
"semi": true
}

View File

@@ -1,18 +1,14 @@
const fs = require('fs-extra')
let extend = undefined
/* eslint-disable */
/**
* The .babelrc file has been created to assist Jest for transpiling.
* You should keep your application's babel rules in this file.
*/
if (fs.existsSync('./.babelrc')) {
extend = './.babelrc'
}
module.exports = {
module.exports = (api) => {
return {
presets: [
'@quasar/babel-preset-app'
[
"@quasar/babel-preset-app",
api.caller((caller) => caller && caller.target === "node")
? { targets: { node: "current" } }
: {},
],
extends: extend
}
],
};
};

1406
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,14 @@
"scripts": {
"serve": "quasar dev",
"build": "quasar build",
"test:e2e": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress open\"",
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
"lint": "eslint --ext .js,.ts,.vue ./",
"format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
},
"dependencies": {
"@quasar/extras": "^1.13.5",
"apexcharts": "^3.35.0",
"axios": "^0.26.1",
"core-js": "^3.6.5",
"dotenv": "^16.0.0",
"qrcode.vue": "^3.3.3",
"quasar": "^2.6.6",
@@ -20,16 +21,34 @@
"vue3-ace-editor": "^2.2.2",
"vue3-apexcharts": "^1.4.1",
"vuedraggable": "^4.1.0",
"vue-router": "^4.0.0",
"vuex": "^4.0.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"eslint": "^8.10.0",
"eslint-plugin-vue": "^8.5.0",
"eslint-config-prettier": "^8.1.0",
"prettier": "^2.5.1",
"@types/node": "^12.20.21",
"@quasar/app-webpack": "^3.5.1",
"@quasar/cli": "^1.3.2"
},
"browserslist": [
"last 2 Chrome versions",
"last 2 Firefox versions",
"last 2 Edge versions",
"last 1 Safari versions"
]
"last 10 Chrome versions",
"last 10 Firefox versions",
"last 4 Edge versions",
"last 7 Safari versions",
"last 8 Android versions",
"last 8 ChromeAndroid versions",
"last 8 FirefoxAndroid versions",
"last 10 iOS versions",
"last 5 Opera versions"
],
"engines": {
"node": ">= 12.22.1",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
}
}

View File

@@ -1,38 +1,48 @@
require('dotenv').config();
const path = require('path');
require("dotenv").config();
const path = require("path");
module.exports = function () {
return {
supportTS: false,
supportTS: {
tsCheckerConfig: {
eslint: {
enabled: true,
files: "./src/**/*.{ts,tsx,js,jsx,vue}",
},
},
},
// https://quasar.dev/quasar-cli/cli-documentation/prefetch-feature
// preFetch: true,
boot: [
'axios',
],
boot: ["axios"],
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
css: [
'app.sass'
],
css: ["app.sass"],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
'mdi-v5',
'fontawesome-v5',
"mdi-v5",
"fontawesome-v5",
// 'eva-icons',
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font', // optional, you are not bound to it
'material-icons', // optional, you are not bound to it
"roboto-font", // optional, you are not bound to it
"material-icons", // optional, you are not bound to it
],
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
build: {
env: { DEV_API: process.env.DEV_URL, PROD_API: process.env.PROD_URL, DOCKER_BUILD: process.env.DOCKER_BUILD },
vueRouterMode: 'history', // available values: 'hash', 'history'
env: {
DEV_API: process.env.DEV_URL,
PROD_API: process.env.PROD_URL,
DOCKER_BUILD: process.env.DOCKER_BUILD,
},
vueRouterMode: "history", // available values: 'hash', 'history'
distDir: "dist/",
devtool: process.env.NODE_ENV === "production" ? "cheap-module-eval-source-map" : "source-map",
devtool:
process.env.NODE_ENV === "production"
? "cheap-module-eval-source-map"
: "source-map",
// Add dependencies for transpiling with Babel (Array of regexes)
// (from node_modules, which are by default not transpiled).
@@ -52,8 +62,8 @@ module.exports = function () {
extendWebpack(cfg) {
cfg.resolve.alias = {
...cfg.resolve.alias,
"@": path.resolve(__dirname, './src'),
}
"@": path.resolve(__dirname, "./src"),
};
},
},
@@ -62,36 +72,30 @@ module.exports = function () {
https: false,
host: process.env.DEV_HOST,
port: process.env.DEV_PORT,
open: false
open: false,
},
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
framework: {
iconSet: 'material-icons', // Quasar icon set
lang: 'en-US', // Quasar language pack
iconSet: "material-icons", // Quasar icon set
lang: "en-US", // Quasar language pack
// Quasar plugins
plugins: [
'Dialog',
'Loading',
'LoadingBar',
'Meta',
'Notify'
],
plugins: ["Dialog", "Loading", "LoadingBar", "Meta", "Notify"],
config: {
loadingBar: {
size: "4px"
size: "4px",
},
notify: {
position: "top",
timeout: 2000,
textColor: "white",
actions: [{ icon: "close", color: "white" }]
actions: [{ icon: "close", color: "white" }],
},
loading: {
delay: 50
}
}
delay: 50,
},
},
},
// animations: 'all', // --- includes all animations
@@ -100,7 +104,7 @@ module.exports = function () {
// https://quasar.dev/quasar-cli/developing-ssr/configuring-ssr
ssr: {
pwa: false
}
}
}
pwa: false,
},
};
};

View File

@@ -1,57 +1,63 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/accounts"
const baseUrl = "/accounts";
// user api functions
export async function fetchUsers(params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/users/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/users/`, { params: params });
return data;
} catch (e) {
console.error(e);
}
}
// role api function
export async function fetchRoles(params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/roles/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/roles/`, { params: params });
return data;
} catch (e) {
console.error(e);
}
}
export async function removeRole(id) {
const { data } = await axios.delete(`${baseUrl}/roles/${id}/`)
return data
const { data } = await axios.delete(`${baseUrl}/roles/${id}/`);
return data;
}
export async function saveRole(payload) {
const { data } = await axios.post(`${baseUrl}/roles/`, payload)
return data
export async function saveRole(role) {
const { data } = await axios.post(`${baseUrl}/roles/`, role);
return data;
}
export async function editRole(id, payload) {
const { data } = await axios.put(`${baseUrl}/roles/${id}/`, payload)
return data
export async function editRole(id, role) {
const { data } = await axios.put(`${baseUrl}/roles/${id}/`, role);
return data;
}
// api key api functions
export async function fetchAPIKeys(params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/apikeys/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/apikeys/`, { params: params });
return data;
} catch (e) {
console.error(e);
}
}
export async function saveAPIKey(payload) {
const { data } = await axios.post(`${baseUrl}/apikeys/`, payload)
return data
export async function saveAPIKey(apiKey) {
const { data } = await axios.post(`${baseUrl}/apikeys/`, apiKey);
return data;
}
export async function editAPIKey(payload) {
const { data } = await axios.put(`${baseUrl}/apikeys/${payload.id}/`, payload)
return data
export async function editAPIKey(id, apiKey) {
const { data } = await axios.put(`${baseUrl}/apikeys/${id}/`, apiKey);
return data;
}
export async function removeAPIKey(id) {
const { data } = await axios.delete(`${baseUrl}/apikeys/${id}/`)
return data
const { data } = await axios.delete(`${baseUrl}/apikeys/${id}/`);
return data;
}

View File

@@ -1,167 +1,234 @@
import axios from "axios"
import axios from "axios";
import { openURL } from "quasar";
import { router } from "@/router"
import { router } from "@/router";
const baseUrl = "/agents"
const baseUrl = "/agents";
export function runTakeControl(agent_id) {
const url = router.resolve(`/takecontrol/${agent_id}`).href;
openURL(url, null, { popup: true, scrollbars: false, location: false, status: false, toolbar: false, menubar: false, width: 1600, height: 900 });
openURL(url, null, {
popup: true,
scrollbars: false,
location: false,
status: false,
toolbar: false,
menubar: false,
width: 1600,
height: 900,
});
}
export function openAgentWindow(agent_id) {
const url = router.resolve(`/agents/${agent_id}`).href;
openURL(url, null, { popup: true, scrollbars: false, location: false, status: false, toolbar: false, menubar: false, width: 1600, height: 900 });
openURL(url, null, {
popup: true,
scrollbars: false,
location: false,
status: false,
toolbar: false,
menubar: false,
width: 1600,
height: 900,
});
}
export function runRemoteBackground(agent_id, agentPlatform) {
const url = router.resolve(`/remotebackground/${agent_id}?agentPlatform=${agentPlatform}`).href;
openURL(url, null, { popup: true, scrollbars: false, location: false, status: false, toolbar: false, menubar: false, width: 1280, height: 900 });
const url = router.resolve(
`/remotebackground/${agent_id}?agentPlatform=${agentPlatform}`
).href;
openURL(url, null, {
popup: true,
scrollbars: false,
location: false,
status: false,
toolbar: false,
menubar: false,
width: 1280,
height: 900,
});
}
export async function fetchAgents(params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/`, { params: params });
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function fetchAgent(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/${agent_id}/`, {
params: params,
});
return data;
} catch (e) {
console.error(e);
}
}
export async function editAgent(agent_id, payload) {
const { data } = await axios.put(`${baseUrl}/${agent_id}/`, payload)
return data
const { data } = await axios.put(`${baseUrl}/${agent_id}/`, payload);
return data;
}
export async function removeAgent(agent_id) {
const { data } = await axios.delete(`${baseUrl}/${agent_id}/`)
return data
const { data } = await axios.delete(`${baseUrl}/${agent_id}/`);
return data;
}
export async function fetchAgentHistory(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/history/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/${agent_id}/history/`, {
params: params,
});
return data;
} catch (e) {
console.error(e);
}
}
export async function fetchAgentChecks(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/checks/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/${agent_id}/checks/`, {
params: params,
});
return data;
} catch (e) {
console.error(e);
}
}
export async function fetchAgentTasks(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/tasks/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/${agent_id}/tasks/`, {
params: params,
});
return data;
} catch (e) {
console.error(e);
}
}
export async function sendAgentRecovery(agent_id, payload) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/recover/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/${agent_id}/recover/`, payload);
return data;
}
export async function sendAgentCommand(agent_id, payload) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/cmd/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/${agent_id}/cmd/`, payload);
return data;
}
export async function refreshAgentWMI(agent_id) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/wmi/`)
return data
const { data } = await axios.post(`${baseUrl}/${agent_id}/wmi/`);
return data;
}
export async function runScript(agent_id, payload) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/runscript/`, payload)
return data
const { data } = await axios.post(
`${baseUrl}/${agent_id}/runscript/`,
payload
);
return data;
}
export async function runBulkAction(payload) {
const { data } = await axios.post(`${baseUrl}/actions/bulk/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/actions/bulk/`, payload);
return data;
}
export async function fetchAgentProcesses(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/processes/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/${agent_id}/processes/`, {
params: params,
});
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function killAgentProcess(agent_id, pid, params = {}) {
const { data } = await axios.delete(`${baseUrl}/${agent_id}/processes/${pid}/`, { params: params })
return data
const { data } = await axios.delete(
`${baseUrl}/${agent_id}/processes/${pid}/`,
{ params: params }
);
return data;
}
export async function fetchAgentEventLog(agent_id, logType, days, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/eventlog/${logType}/${days}/`, { params: params })
return data
const { data } = await axios.get(
`${baseUrl}/${agent_id}/eventlog/${logType}/${days}/`,
{ params: params }
);
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function fetchAgentMeshCentralURLs(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/meshcentral/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/${agent_id}/meshcentral/`, {
params: params,
});
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function scheduleAgentReboot(agent_id, payload) {
const { data } = await axios.patch(`${baseUrl}/${agent_id}/reboot/`, payload)
return data
const { data } = await axios.patch(`${baseUrl}/${agent_id}/reboot/`, payload);
return data;
}
export async function agentRebootNow(agent_id) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/reboot/`)
return data
const { data } = await axios.post(`${baseUrl}/${agent_id}/reboot/`);
return data;
}
export async function sendAgentRecoverMesh(agent_id, params = {}) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/meshcentral/recover/`, { params: params })
return data
const { data } = await axios.post(
`${baseUrl}/${agent_id}/meshcentral/recover/`,
{ params: params }
);
return data;
}
export async function sendAgentPing(agent_id, params = {}) {
const { data } = await axios.get(`${baseUrl}/${agent_id}/ping/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/${agent_id}/ping/`, {
params: params,
});
return data;
}
// agent notes
export async function fetchAgentNotes(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/notes/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/${agent_id}/notes/`, {
params: params,
});
return data;
} catch (e) {
console.error(e);
}
}
export async function saveAgentNote(payload) {
const { data } = await axios.post(`${baseUrl}/notes/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/notes/`, payload);
return data;
}
export async function editAgentNote(pk, payload) {
const { data } = await axios.put(`${baseUrl}/notes/${pk}/`, payload)
return data
const { data } = await axios.put(`${baseUrl}/notes/${pk}/`, payload);
return data;
}
export async function removeAgentNote(pk) {
const { data } = await axios.delete(`${baseUrl}/notes/${pk}/`)
return data
const { data } = await axios.delete(`${baseUrl}/notes/${pk}/`);
return data;
}

View File

@@ -1,15 +1,17 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/automation"
const baseUrl = "/automation";
export async function sendPatchPolicyReset(payload) {
const { data } = await axios.post(`${baseUrl}/patchpolicy/reset/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/patchpolicy/reset/`, payload);
return data;
}
export async function fetchPolicyChecks(id) {
try {
const { data } = await axios.get(`${baseUrl}/policies/${id}/checks/`)
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/policies/${id}/checks/`);
return data;
} catch (e) {
console.error(e);
}
}

View File

@@ -1,37 +1,37 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/checks"
const baseUrl = "/checks";
export async function fetchChecks(params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/`, { params: params });
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function saveCheck(payload) {
const { data } = await axios.post(`${baseUrl}/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/`, payload);
return data;
}
export async function updateCheck(id, payload) {
const { data } = await axios.put(`${baseUrl}/${id}/`, payload)
return data
const { data } = await axios.put(`${baseUrl}/${id}/`, payload);
return data;
}
export async function removeCheck(id) {
const { data } = await axios.delete(`${baseUrl}/${id}/`)
return data
const { data } = await axios.delete(`${baseUrl}/${id}/`);
return data;
}
export async function resetCheck(id) {
const { data } = await axios.post(`${baseUrl}/${id}/reset/`)
return data
const { data } = await axios.post(`${baseUrl}/${id}/reset/`);
return data;
}
export async function runAgentChecks(agent_id) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/run/`)
return data
const { data } = await axios.post(`${baseUrl}/${agent_id}/run/`);
return data;
}

View File

@@ -1,81 +1,95 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/clients"
const baseUrl = "/clients";
// client endpoints
export async function fetchClients() {
try {
const { data } = await axios.get(`${baseUrl}/`)
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/`);
return data;
} catch (e) {
console.error(e);
}
}
export async function fetchClient(id) {
try {
const { data } = await axios.get(`${baseUrl}/${id}/`)
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/${id}/`);
return data;
} catch (e) {
console.error(e);
}
}
export async function saveClient(payload) {
const { data } = await axios.post(`${baseUrl}/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/`, payload);
return data;
}
export async function editClient(id, payload) {
const { data } = await axios.put(`${baseUrl}/${id}/`, payload)
return data
const { data } = await axios.put(`${baseUrl}/${id}/`, payload);
return data;
}
export async function removeClient(id, params = {}) {
const { data } = await axios.delete(`${baseUrl}/${id}/`, { params: params })
return data
const { data } = await axios.delete(`${baseUrl}/${id}/`, { params: params });
return data;
}
// site endpoints
export async function fetchSites() {
try {
const { data } = await axios.get(`${baseUrl}/sites/`)
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/sites/`);
return data;
} catch (e) {
console.error(e);
}
}
export async function fetchSite(id) {
try {
const { data } = await axios.get(`${baseUrl}/sites/${id}/`)
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/sites/${id}/`);
return data;
} catch (e) {
console.error(e);
}
}
export async function saveSite(payload) {
const { data } = await axios.post(`${baseUrl}/sites/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/sites/`, payload);
return data;
}
export async function editSite(id, payload) {
const { data } = await axios.put(`${baseUrl}/sites/${id}/`, payload)
return data
const { data } = await axios.put(`${baseUrl}/sites/${id}/`, payload);
return data;
}
export async function removeSite(id, params = {}) {
const { data } = await axios.delete(`${baseUrl}/sites/${id}/`, { params: params })
return data
const { data } = await axios.delete(`${baseUrl}/sites/${id}/`, {
params: params,
});
return data;
}
// deployment endpoints
export async function fetchDeployments() {
try {
const { data } = await axios.get(`${baseUrl}/deployments/`)
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/deployments/`);
return data;
} catch (e) {
console.error(e);
}
}
export async function saveDeployment(payload) {
const { data } = await axios.post(`${baseUrl}/deployments/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/deployments/`, payload);
return data;
}
export async function removeDeployment(id, params = {}) {
const { data } = await axios.delete(`${baseUrl}/deployments/${id}/`, { params: params })
return data
const { data } = await axios.delete(`${baseUrl}/deployments/${id}/`, {
params: params,
});
return data;
}

View File

@@ -1,30 +1,40 @@
import axios from "axios"
import axios from "axios";
import { openURL } from "quasar";
const baseUrl = "/core"
const baseUrl = "/core";
export async function fetchCustomFields(params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/customfields/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/customfields/`, {
params: params,
});
return data;
} catch (e) {
console.error(e);
}
}
export async function fetchDashboardInfo(params = {}) {
const { data } = await axios.get(`${baseUrl}/dashinfo/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/dashinfo/`, { params: params });
return data;
}
export async function fetchURLActions(params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/urlaction/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/urlaction/`, {
params: params,
});
return data;
} catch (e) {
console.error(e);
}
}
export async function runURLAction(payload) {
try {
const { data } = await axios.patch(`${baseUrl}/urlaction/run/`, payload)
openURL(data)
} catch (e) { console.error(e) }
const { data } = await axios.patch(`${baseUrl}/urlaction/run/`, payload);
openURL(data);
} catch (e) {
console.error(e);
}
}

View File

@@ -1,35 +1,37 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/logs"
const baseUrl = "/logs";
export async function fetchDebugLog(payload) {
try {
const { data } = await axios.patch(`${baseUrl}/debug/`, payload)
return data
const { data } = await axios.patch(`${baseUrl}/debug/`, payload);
return data;
} catch (e) {}
}
export async function fetchAuditLog(payload) {
const { data } = await axios.patch(`${baseUrl}/audit/`, payload)
return data
const { data } = await axios.patch(`${baseUrl}/audit/`, payload);
return data;
}
// pending actions
export async function fetchPendingActions(params = {}) {
const { data } = await axios.get(`${baseUrl}/pendingactions/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/pendingactions/`, {
params: params,
});
return data;
}
export async function fetchAgentPendingActions(agent_id) {
try {
const { data } = await axios.get(`/agents/${agent_id}/pendingactions/`)
return data
const { data } = await axios.get(`/agents/${agent_id}/pendingactions/`);
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function deletePendingAction(id) {
const { data } = await axios.delete(`${baseUrl}/pendingactions/${id}/`)
return data
const { data } = await axios.delete(`${baseUrl}/pendingactions/${id}/`);
return data;
}

View File

@@ -1,62 +1,67 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/scripts"
const baseUrl = "/scripts";
// script operations
export async function fetchScripts(params = {}) {
const { data } = await axios.get(`${baseUrl}/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/`, { params: params });
return data;
}
export async function testScript(agent_id, payload) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/test/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/${agent_id}/test/`, payload);
return data;
}
export async function saveScript(payload) {
const { data } = await axios.post(`${baseUrl}/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/`, payload);
return data;
}
export async function editScript(payload) {
const { data } = await axios.put(`${baseUrl}/${payload.id}/`, payload)
return data
const { data } = await axios.put(`${baseUrl}/${payload.id}/`, payload);
return data;
}
export async function removeScript(id) {
const { data } = await axios.delete(`${baseUrl}/${id}/`)
return data
const { data } = await axios.delete(`${baseUrl}/${id}/`);
return data;
}
export async function downloadScript(id, params = {}) {
const { data } = await axios.get(`${baseUrl}/${id}/download/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/${id}/download/`, {
params: params,
});
return data;
}
// script snippet operations
export async function fetchScriptSnippets(params = {}) {
const { data } = await axios.get(`${baseUrl}/snippets/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/snippets/`, { params: params });
return data;
}
export async function saveScriptSnippet(payload) {
const { data } = await axios.post(`${baseUrl}/snippets/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/snippets/`, payload);
return data;
}
export async function fetchScriptSnippet(id, params = {}) {
const { data } = await axios.get(`${baseUrl}/snippets/${id}/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/snippets/${id}/`, {
params: params,
});
return data;
}
export async function editScriptSnippet(payload) {
const { data } = await axios.put(`${baseUrl}/snippets/${payload.id}/`, payload)
return data
const { data } = await axios.put(
`${baseUrl}/snippets/${payload.id}/`,
payload
);
return data;
}
export async function removeScriptSnippet(id) {
const { data } = await axios.delete(`${baseUrl}/snippets/${id}/`)
return data
const { data } = await axios.delete(`${baseUrl}/snippets/${id}/`);
return data;
}

View File

@@ -1,31 +1,41 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/services"
const baseUrl = "/services";
export async function getAgentServices(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/${agent_id}/`, {
params: params,
});
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function getAgentServiceDetails(agent_id, svcname, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/${svcname}/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/${agent_id}/${svcname}/`, {
params: params,
});
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function editAgentServiceStartType(agent_id, svcname, payload) {
const { data } = await axios.put(`${baseUrl}/${agent_id}/${svcname}/`, payload)
return data
const { data } = await axios.put(
`${baseUrl}/${agent_id}/${svcname}/`,
payload
);
return data;
}
export async function sendAgentServiceAction(agent_id, svcname, payload) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/${svcname}/`, payload)
return data
const { data } = await axios.post(
`${baseUrl}/${agent_id}/${svcname}/`,
payload
);
return data;
}

View File

@@ -1,35 +1,37 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/software"
const baseUrl = "/software";
export async function fetchChocosSoftware(params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/chocos/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/chocos/`, { params: params });
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function fetchAgentSoftware(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/`, { params: params })
return data.software
const { data } = await axios.get(`${baseUrl}/${agent_id}/`, {
params: params,
});
return data.software;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function installAgentSoftware(agent_id, payload) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/${agent_id}/`, payload);
return data;
}
export async function refreshAgentSoftware(agent_id) {
try {
const { data } = await axios.put(`${baseUrl}/${agent_id}/`)
return data
const { data } = await axios.put(`${baseUrl}/${agent_id}/`);
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}

View File

@@ -1,32 +1,32 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/tasks"
const baseUrl = "/tasks";
export async function fetchTasks(params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/`, { params: params })
return data
const { data } = await axios.get(`${baseUrl}/`, { params: params });
return data;
} catch (e) {
console.error(e)
console.error(e);
}
}
export async function saveTask(payload) {
const { data } = await axios.post(`${baseUrl}/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/`, payload);
return data;
}
export async function updateTask(id, payload) {
const { data } = await axios.put(`${baseUrl}/${id}/`, payload)
return data
const { data } = await axios.put(`${baseUrl}/${id}/`, payload);
return data;
}
export async function removeTask(id) {
const { data } = await axios.delete(`${baseUrl}/${id}/`)
return data
const { data } = await axios.delete(`${baseUrl}/${id}/`);
return data;
}
export async function runTask(id, payload) {
const { data } = await axios.post(`${baseUrl}/${id}/run/`, payload)
return data
const { data } = await axios.post(`${baseUrl}/${id}/run/`, payload);
return data;
}

View File

@@ -1,26 +1,30 @@
import axios from "axios"
import axios from "axios";
const baseUrl = "/winupdate"
const baseUrl = "/winupdate";
// win updates api functions
export async function fetchAgentUpdates(agent_id, params = {}) {
try {
const { data } = await axios.get(`${baseUrl}/${agent_id}/`, { params: params })
return data
} catch (e) { console.error(e) }
const { data } = await axios.get(`${baseUrl}/${agent_id}/`, {
params: params,
});
return data;
} catch (e) {
console.error(e);
}
}
export async function runAgentUpdateScan(agent_id) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/scan/`)
return data
const { data } = await axios.post(`${baseUrl}/${agent_id}/scan/`);
return data;
}
export async function runAgentUpdateInstall(agent_id) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/install/`)
return data
const { data } = await axios.post(`${baseUrl}/${agent_id}/install/`);
return data;
}
export async function editAgentUpdate(id, payload) {
const { data } = await axios.put(`${baseUrl}/${id}/`, payload)
return data
const { data } = await axios.put(`${baseUrl}/${id}/`, payload);
return data;
}

View File

@@ -1,5 +1,5 @@
import axios from 'axios';
import { Notify } from "quasar"
import axios from "axios";
import { Notify } from "quasar";
export const getBaseUrl = () => {
if (process.env.NODE_ENV === "production") {
@@ -14,12 +14,11 @@ export const getBaseUrl = () => {
};
export default function ({ app, router, store }) {
app.config.globalProperties.$axios = axios;
axios.interceptors.request.use(
function (config) {
config.baseURL = getBaseUrl()
config.baseURL = getBaseUrl();
const token = store.state.token;
if (token != null) {
config.headers.Authorization = `Token ${token}`;
@@ -36,10 +35,10 @@ export default function ({ app, router, store }) {
return response;
},
async function (error) {
let text
let text;
if (!error.response) {
text = error.message
text = error.message;
}
// unauthorized
else if (error.response.status === 401) {
@@ -48,25 +47,22 @@ export default function ({ app, router, store }) {
// perms
else if (error.response.status === 403) {
// don't notify user if method is GET
if (error.config.method === "get" || error.config.method === "patch") return Promise.reject({ ...error });
if (error.config.method === "get" || error.config.method === "patch")
return Promise.reject({ ...error });
text = error.response.data.detail;
}
// catch all for other 400 error messages
else if (error.response.status >= 400 && error.response.status < 500) {
if (error.config.responseType === "blob") {
text = (await error.response.data.text()).replace(/^"|"$/g, '')
}
else if (error.response.data.non_field_errors) {
text = error.response.data.non_field_errors[0]
text = (await error.response.data.text()).replace(/^"|"$/g, "");
} else if (error.response.data.non_field_errors) {
text = error.response.data.non_field_errors[0];
} else {
if (typeof error.response.data === "string") {
text = error.response.data
text = error.response.data;
} else if (typeof error.response.data === "object") {
let [key, value] = Object.entries(error.response.data)[0]
text = key + ": " + value[0]
let [key, value] = Object.entries(error.response.data)[0];
text = key + ": " + value[0];
}
}
}
@@ -75,13 +71,14 @@ export default function ({ app, router, store }) {
Notify.create({
color: "negative",
message: text ? text : "",
caption: error.response ? error.response.status + ": " + error.response.statusText : "",
timeout: 2500
})
caption: error.response
? error.response.status + ": " + error.response.statusText
: "",
timeout: 2500,
});
}
return Promise.reject({ ...error });
}
);
}

View File

@@ -2,7 +2,15 @@
<div style="width: 900px; max-width: 90vw">
<q-card>
<q-bar>
<q-btn ref="refresh" @click="getUsers" class="q-mr-sm" dense flat push icon="refresh" />User Administration
<q-btn
ref="refresh"
@click="getUsers"
class="q-mr-sm"
dense
flat
push
icon="refresh"
/>User Administration
<q-space />
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
@@ -10,7 +18,17 @@
</q-bar>
<div class="q-pa-md">
<div class="q-gutter-sm">
<q-btn ref="new" label="New" dense flat push unelevated no-caps icon="add" @click="showAddUserModal" />
<q-btn
ref="new"
label="New"
dense
flat
push
unelevated
no-caps
icon="add"
@click="showAddUserModal"
/>
</div>
<q-table
dense
@@ -40,11 +58,19 @@
<!-- body slots -->
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="showEditUserModal(props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="showEditUserModal(props.row)"
>
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="showEditUserModal(props.row)">
<q-item
clickable
v-close-popup
@click="showEditUserModal(props.row)"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
@@ -64,14 +90,24 @@
<q-separator></q-separator>
<q-item clickable v-close-popup @click="ResetPassword(props.row)" id="context-reset">
<q-item
clickable
v-close-popup
@click="ResetPassword(props.row)"
id="context-reset"
>
<q-item-section side>
<q-icon name="autorenew" />
</q-item-section>
<q-item-section>Reset Password</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="reset2FA(props.row)" id="context-reset">
<q-item
clickable
v-close-popup
@click="reset2FA(props.row)"
id="context-reset"
>
<q-item-section side>
<q-icon name="autorenew" />
</q-item-section>
@@ -97,7 +133,9 @@
<q-td>{{ props.row.username }}</q-td>
<q-td>{{ props.row.first_name }} {{ props.row.last_name }}</q-td>
<q-td>{{ props.row.email }}</q-td>
<q-td v-if="props.row.last_login">{{ formatDate(props.row.last_login) }}</q-td>
<q-td v-if="props.row.last_login">{{
formatDate(props.row.last_login)
}}</q-td>
<q-td v-else>Never</q-td>
<q-td>{{ props.row.last_login_ip }}</q-td>
</q-tr>
@@ -118,7 +156,7 @@ import UserResetPasswordForm from "@/components/modals/admin/UserResetPasswordFo
export default {
name: "AdminManager",
mixins: [mixins],
setup(props) {
setup() {
// setup vuex
const store = useStore();
const formatDate = computed(() => store.getters.formatDate);
@@ -131,8 +169,19 @@ export default {
return {
users: [],
columns: [
{ name: "is_active", label: "Active", field: "is_active", align: "left" },
{ name: "username", label: "Username", field: "username", align: "left", sortable: true },
{
name: "is_active",
label: "Active",
field: "is_active",
align: "left",
},
{
name: "username",
label: "Username",
field: "username",
align: "left",
sortable: true,
},
{
name: "name",
label: "Name",
@@ -174,7 +223,7 @@ export default {
this.$q.loading.show();
this.$axios
.get("/accounts/users/")
.then(r => {
.then((r) => {
this.users = r.data;
this.$q.loading.hide();
})
@@ -190,13 +239,10 @@ export default {
ok: { label: "Delete", color: "negative" },
})
.onOk(() => {
this.$axios
.delete(`/accounts/${user.id}/users/`)
.then(() => {
this.$axios.delete(`/accounts/${user.id}/users/`).then(() => {
this.getUsers();
this.notifySuccess(`User ${user.username} was deleted!`);
})
.catch(e => {});
});
});
},
showEditUserModal(user) {
@@ -224,19 +270,18 @@ export default {
if (user.username === this.logged_in_user) {
return;
}
let text = !user.is_active ? "User enabled successfully" : "User disabled successfully";
let text = !user.is_active
? "User enabled successfully"
: "User disabled successfully";
const data = {
id: user.id,
is_active: !user.is_active,
};
this.$axios
.put(`/accounts/${data.id}/users/`, data)
.then(() => {
this.$axios.put(`/accounts/${data.id}/users/`, data).then(() => {
this.notifySuccess(text);
})
.catch(e => {});
});
},
ResetPassword(user) {
this.$q
@@ -262,7 +307,9 @@ export default {
ok: { label: "Reset", color: "positive" },
})
.onOk(() => {
this.$axios.put("/accounts/users/reset_totp/", data).then(response => {
this.$axios
.put("/accounts/users/reset_totp/", data)
.then((response) => {
this.notifySuccess(response.data, 4000);
});
});
@@ -270,7 +317,7 @@ export default {
},
computed: {
...mapState({
logged_in_user: state => state.username,
logged_in_user: (state) => state.username,
}),
},
mounted() {

View File

@@ -2,7 +2,10 @@
<div class="q-pa-none">
<q-table
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="agents-tbl-sticky"
:table-style="{ 'max-height': tableHeight }"
:rows="agents"
@@ -92,18 +95,26 @@
</q-menu>
<q-td>
<q-checkbox
v-if="props.row.alert_template && props.row.alert_template.always_text !== null"
v-if="
props.row.alert_template &&
props.row.alert_template.always_text !== null
"
v-model="props.row.alert_template.always_text"
disable
dense
>
<q-tooltip> Setting is overridden by alert template: {{ props.row.alert_template.name }} </q-tooltip>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="overdueAlert('text', props.row, props.row.overdue_text_alert)"
@update:model-value="
overdueAlert('text', props.row, props.row.overdue_text_alert)
"
v-model="props.row.overdue_text_alert"
>
<q-tooltip>{{ sms_overdue_text }}</q-tooltip>
@@ -111,18 +122,26 @@
</q-td>
<q-td>
<q-checkbox
v-if="props.row.alert_template && props.row.alert_template.always_email !== null"
v-if="
props.row.alert_template &&
props.row.alert_template.always_email !== null
"
v-model="props.row.alert_template.always_email"
disable
dense
>
<q-tooltip> Setting is overridden by alert template: {{ props.row.alert_template.name }} </q-tooltip>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="overdueAlert('email', props.row, props.row.overdue_email_alert)"
@update:model-value="
overdueAlert('email', props.row, props.row.overdue_email_alert)
"
v-model="props.row.overdue_email_alert"
>
<q-tooltip>{{ email_overdue_text }}</q-tooltip>
@@ -130,18 +149,30 @@
</q-td>
<q-td>
<q-checkbox
v-if="props.row.alert_template && props.row.alert_template.always_alert !== null"
v-if="
props.row.alert_template &&
props.row.alert_template.always_alert !== null
"
v-model="props.row.alert_template.always_alert"
disable
dense
>
<q-tooltip> Setting is overridden by alert template: {{ props.row.alert_template.name }} </q-tooltip>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="overdueAlert('dashboard', props.row, props.row.overdue_dashboard_alert)"
@update:model-value="
overdueAlert(
'dashboard',
props.row,
props.row.overdue_dashboard_alert
)
"
v-model="props.row.overdue_dashboard_alert"
>
<q-tooltip>{{ dashboard_overdue_text }}</q-tooltip>
@@ -149,42 +180,88 @@
</q-td>
<q-td key="plat" :props="props">
<q-icon v-if="props.row.plat === 'windows'" name="mdi-microsoft-windows" size="sm" color="primary">
<q-icon
v-if="props.row.plat === 'windows'"
name="mdi-microsoft-windows"
size="sm"
color="primary"
>
<q-tooltip>Microsoft Windows</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.plat === 'linux'" name="mdi-linux" size="sm" color="primary">
<q-icon
v-else-if="props.row.plat === 'linux'"
name="mdi-linux"
size="sm"
color="primary"
>
<q-tooltip>Linux</q-tooltip>
</q-icon>
</q-td>
<q-td key="checks-status" :props="props">
<q-icon v-if="props.row.maintenance_mode" name="construction" size="1.2em" color="green">
<q-icon
v-if="props.row.maintenance_mode"
name="construction"
size="1.2em"
color="green"
>
<q-tooltip>Maintenance Mode Enabled</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.checks.failing > 0" name="fas fa-check-double" size="1.2em" color="negative">
<q-icon
v-else-if="props.row.checks.failing > 0"
name="fas fa-check-double"
size="1.2em"
color="negative"
>
<q-tooltip>Checks failing</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.checks.warning > 0" name="fas fa-check-double" size="1.2em" color="warning">
<q-icon
v-else-if="props.row.checks.warning > 0"
name="fas fa-check-double"
size="1.2em"
color="warning"
>
<q-tooltip>Checks warning</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.checks.info > 0" name="fas fa-check-double" size="1.2em" color="info">
<q-icon
v-else-if="props.row.checks.info > 0"
name="fas fa-check-double"
size="1.2em"
color="info"
>
<q-tooltip>Checks info</q-tooltip>
</q-icon>
<q-icon v-else name="fas fa-check-double" size="1.2em" color="positive">
<q-icon
v-else
name="fas fa-check-double"
size="1.2em"
color="positive"
>
<q-tooltip>Checks passing</q-tooltip>
</q-icon>
</q-td>
<q-td key="client_name" :props="props">{{ props.row.client_name }}</q-td>
<q-td key="client_name" :props="props">{{
props.row.client_name
}}</q-td>
<q-td key="site_name" :props="props">{{ props.row.site_name }}</q-td>
<q-td key="hostname" :props="props">{{ props.row.hostname }}</q-td>
<q-td key="description" :props="props">{{ props.row.description }}</q-td>
<q-td key="description" :props="props">{{
props.row.description
}}</q-td>
<q-td key="user" :props="props">
<span class="text-italic" v-if="props.row.italic">{{ props.row.logged_username }}</span>
<span class="text-italic" v-if="props.row.italic">{{
props.row.logged_username
}}</span>
<span v-else>{{ props.row.logged_username }}</span>
</q-td>
<q-td :props="props" key="patchespending">
<q-icon v-if="props.row.has_patches_pending" name="verified_user" size="1.5em" color="primary">
<q-icon
v-if="props.row.has_patches_pending"
name="verified_user"
size="1.5em"
color="primary"
>
<q-tooltip>Patches Pending</q-tooltip>
</q-icon>
</q-td>
@@ -197,28 +274,49 @@
color="warning"
class="cursor-pointer"
>
<q-tooltip>Pending Action Count: {{ props.row.pending_actions_count }}</q-tooltip>
<q-tooltip
>Pending Action Count:
{{ props.row.pending_actions_count }}</q-tooltip
>
</q-icon>
</q-td>
<!-- needs reboot -->
<q-td key="needsreboot">
<q-icon v-if="props.row.needs_reboot" name="fas fa-power-off" color="primary">
<q-icon
v-if="props.row.needs_reboot"
name="fas fa-power-off"
color="primary"
>
<q-tooltip>Reboot required</q-tooltip>
</q-icon>
</q-td>
<q-td key="agentstatus">
<q-icon v-if="props.row.status === 'overdue'" name="fas fa-signal" size="1.2em" color="negative">
<q-icon
v-if="props.row.status === 'overdue'"
name="fas fa-signal"
size="1.2em"
color="negative"
>
<q-tooltip>Agent overdue</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.status === 'offline'" name="fas fa-signal" size="1.2em" color="warning">
<q-icon
v-else-if="props.row.status === 'offline'"
name="fas fa-signal"
size="1.2em"
color="warning"
>
<q-tooltip>Agent offline</q-tooltip>
</q-icon>
<q-icon v-else name="fas fa-signal" size="1.2em" color="positive">
<q-tooltip>Agent online</q-tooltip>
</q-icon>
</q-td>
<q-td key="last_seen" :props="props">{{ formatDate(props.row.last_seen) }}</q-td>
<q-td key="boot_time" :props="props">{{ bootTime(props.row.boot_time) }}</q-td>
<q-td key="last_seen" :props="props">{{
formatDate(props.row.last_seen)
}}</q-td>
<q-td key="boot_time" :props="props">{{
bootTime(props.row.boot_time)
}}</q-td>
</q-tr>
</template>
</q-table>
@@ -269,7 +367,7 @@ export default {
const params = lowerTerms.trim().split(" ");
// parse search text and set variables
params.forEach(param => {
params.forEach((param) => {
if (param.includes("is:")) {
advancedFilter = true;
let filter = param.split(":")[1];
@@ -277,22 +375,30 @@ export default {
if (filter === "actionspending") actions = true;
else if (filter === "checksfailing") checks = true;
else if (filter === "rebootneeded") reboot = true;
else if (filter === "online" || filter === "offline" || filter === "expired" || filter === "overdue")
else if (
filter === "online" ||
filter === "offline" ||
filter === "expired" ||
filter === "overdue"
)
availability = filter;
} else {
search = param + "";
}
});
return rows.filter(row => {
return rows.filter((row) => {
if (advancedFilter) {
if (checks && !row.checks.has_failing_checks) return false;
if (patches && !row.has_patches_pending) return false;
if (actions && row.pending_actions_count === 0) return false;
if (reboot && !row.needs_reboot) return false;
if (availability === "online" && row.status !== "online") return false;
else if (availability === "offline" && row.status !== "offline") return false;
else if (availability === "overdue" && row.status !== "overdue") return false;
if (availability === "online" && row.status !== "online")
return false;
else if (availability === "offline" && row.status !== "offline")
return false;
else if (availability === "overdue" && row.status !== "overdue")
return false;
else if (availability === "expired") {
let now = new Date();
let lastSeen = date.extractDate(row.last_seen, "MM DD YYYY HH:mm");
@@ -302,9 +408,10 @@ export default {
}
// Normal text filter
return cols.some(col => {
return cols.some((col) => {
const val = cellValue(col, row) + "";
const haystack = val === "undefined" || val === "null" ? "" : val.toLowerCase();
const haystack =
val === "undefined" || val === "null" ? "" : val.toLowerCase();
return haystack.indexOf(search) !== -1;
});
});
@@ -354,18 +461,17 @@ export default {
[db_field]: !alert_action,
};
const alertColor = !alert_action ? "positive" : "info";
this.$axios
.put(`/agents/${agent.agent_id}/`, data)
.then(r => {
this.$axios.put(`/agents/${agent.agent_id}/`, data).then(() => {
this.$q.notify({
color: alertColor,
textColor: "black",
icon: "fas fa-check-circle",
message: `${capitalize(category)} alerts will now be ${action} when ${agent.hostname} is overdue.`,
message: `${capitalize(category)} alerts will now be ${action} when ${
agent.hostname
} is overdue.`,
timeout: 5000,
});
})
.catch(e => {});
});
},
agentClass(status) {
if (status === "offline") {
@@ -417,4 +523,3 @@ export default {
},
};
</script>

View File

@@ -1,6 +1,8 @@
<template>
<q-btn dense flat icon="notifications">
<q-badge v-if="alertsCount > 0" :color="badgeColor" floating transparent>{{ alertsCountText() }}</q-badge>
<q-badge v-if="alertsCount > 0" :color="badgeColor" floating transparent>{{
alertsCountText()
}}</q-badge>
<q-menu style="max-height: 30vh">
<q-list separator>
<q-item v-if="alertsCount === 0">No New Alerts</q-item>
@@ -8,28 +10,49 @@
<q-item-section>
<q-item-label overline
><router-link :to="`/agents/${alert.agent_id}`"
>{{ alert.client }} - {{ alert.site }} - {{ alert.hostname }}</router-link
>{{ alert.client }} - {{ alert.site }} -
{{ alert.hostname }}</router-link
></q-item-label
>
<q-item-label lines="1">
<q-icon size="xs" :class="`text-${alertIconColor(alert.severity)}`" :name="alert.severity"></q-icon>
<q-icon
size="xs"
:class="`text-${alertIconColor(alert.severity)}`"
:name="alert.severity"
></q-icon>
{{ alert.message }}
</q-item-label>
</q-item-section>
<q-item-section side top>
<q-item-label caption>{{ getTimeLapse(alert.alert_time) }}</q-item-label>
<q-item-label caption>{{
getTimeLapse(alert.alert_time)
}}</q-item-label>
<q-item-label>
<q-icon name="snooze" size="xs" class="cursor-pointer" @click="snoozeAlert(alert)" v-close-popup>
<q-icon
name="snooze"
size="xs"
class="cursor-pointer"
@click="snoozeAlert(alert)"
v-close-popup
>
<q-tooltip>Snooze alert</q-tooltip>
</q-icon>
<q-icon name="flag" size="xs" class="cursor-pointer" @click="resolveAlert(alert)" v-close-popup>
<q-icon
name="flag"
size="xs"
class="cursor-pointer"
@click="resolveAlert(alert)"
v-close-popup
>
<q-tooltip>Resolve alert</q-tooltip>
</q-icon>
</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showOverview">View All Alerts ({{ alertsCount }})</q-item>
<q-item clickable v-close-popup @click="showOverview"
>View All Alerts ({{ alertsCount }})</q-item
>
</q-list>
</q-menu>
</q-btn>
@@ -43,7 +66,7 @@ import { getTimeLapse } from "@/utils/format";
export default {
name: "AlertsIcon",
mixins: [mixins],
setup(props) {
setup() {
return {
getTimeLapse,
};
@@ -60,7 +83,7 @@ export default {
},
computed: {
badgeColor() {
const severities = this.topAlerts.map(alert => alert.severity);
const severities = this.topAlerts.map((alert) => alert.severity);
if (severities.includes("error")) return this.errorColor;
else if (severities.includes("warning")) return this.warningColor;
@@ -69,13 +92,10 @@ export default {
},
methods: {
getAlerts() {
this.$axios
.patch("alerts/", { top: 10 })
.then(r => {
this.$axios.patch("alerts/", { top: 10 }).then((r) => {
this.alertsCount = r.data.alerts_count;
this.topAlerts = r.data.alerts;
})
.catch(e => {});
});
},
showOverview() {
this.$q
@@ -94,11 +114,11 @@ export default {
prompt: {
model: "",
type: "number",
isValid: val => !!val && val > 0 && val < 9999,
isValid: (val) => !!val && val > 0 && val < 9999,
},
cancel: true,
})
.onOk(days => {
.onOk((days) => {
this.$q.loading.show();
const data = {
@@ -109,12 +129,12 @@ export default {
this.$axios
.put(`alerts/${alert.id}/`, data)
.then(r => {
.then(() => {
this.getAlerts();
this.$q.loading.hide();
this.notifySuccess(`The alert has been snoozed for ${days} days`);
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
});
@@ -129,12 +149,12 @@ export default {
this.$axios
.put(`alerts/${alert.id}/`, data)
.then(r => {
.then(() => {
this.getAlerts();
this.$q.loading.hide();
this.notifySuccess("The alert has been resolved");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},

View File

@@ -3,7 +3,15 @@
<div class="q-dialog-plugin" style="width: 90vw; max-width: 90vw">
<q-card>
<q-bar>
<q-btn ref="refresh" @click="refresh" class="q-mr-sm" dense flat push icon="refresh" />Alerts Manager
<q-btn
ref="refresh"
@click="refresh"
class="q-mr-sm"
dense
flat
push
icon="refresh"
/>Alerts Manager
<q-space />
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
@@ -11,7 +19,17 @@
</q-bar>
<div class="q-pa-sm" style="min-height: 65vh; max-height: 65vh">
<div class="q-gutter-sm">
<q-btn ref="new" label="New" dense flat push unelevated no-caps icon="add" @click="showAddTemplateModal" />
<q-btn
ref="new"
label="New"
dense
flat
push
unelevated
no-caps
icon="add"
@click="showAddTemplateModal"
/>
</div>
<q-table
dense
@@ -67,13 +85,21 @@
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="showEditTemplateModal(props.row)">
<q-item
clickable
v-close-popup
@click="showEditTemplateModal(props.row)"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="deleteTemplate(props.row)">
<q-item
clickable
v-close-popup
@click="deleteTemplate(props.row)"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
@@ -82,7 +108,11 @@
<q-separator></q-separator>
<q-item clickable v-close-popup @click="showAlertExclusions(props.row)">
<q-item
clickable
v-close-popup
@click="showAlertExclusions(props.row)"
>
<q-item-section side>
<q-icon name="rule" />
</q-item-section>
@@ -98,30 +128,59 @@
</q-menu>
<!-- enabled checkbox -->
<q-td>
<q-checkbox dense @update:model-value="toggleEnabled(props.row)" v-model="props.row.is_active" />
<q-checkbox
dense
@update:model-value="toggleEnabled(props.row)"
v-model="props.row.is_active"
/>
</q-td>
<!-- agent settings -->
<q-td>
<q-icon v-if="props.row.agent_settings" color="primary" name="done" size="sm">
<q-tooltip>Alert template has agent alert settings</q-tooltip>
<q-icon
v-if="props.row.agent_settings"
color="primary"
name="done"
size="sm"
>
<q-tooltip
>Alert template has agent alert settings</q-tooltip
>
</q-icon>
</q-td>
<!-- text settings -->
<q-td>
<q-icon v-if="props.row.check_settings" color="primary" name="done" size="sm">
<q-tooltip>Alert template has check alert settings</q-tooltip>
<q-icon
v-if="props.row.check_settings"
color="primary"
name="done"
size="sm"
>
<q-tooltip
>Alert template has check alert settings</q-tooltip
>
</q-icon>
</q-td>
<!-- dashboard settings -->
<q-td>
<q-icon v-if="props.row.task_settings" color="primary" name="done" size="sm">
<q-tooltip>Alert template has task alert settings</q-tooltip>
<q-icon
v-if="props.row.task_settings"
color="primary"
name="done"
size="sm"
>
<q-tooltip
>Alert template has task alert settings</q-tooltip
>
</q-icon>
</q-td>
<!-- name -->
<q-td
>{{ props.row.name }}
<q-chip v-if="props.row.default_template" color="primary" text-color="white" size="sm"
<q-chip
v-if="props.row.default_template"
color="primary"
text-color="white"
size="sm"
>Default</q-chip
>
</q-td>
@@ -131,7 +190,9 @@
style="cursor: pointer; text-decoration: underline"
class="text-primary"
@click="showTemplateApplied(props.row)"
>Show where template is applied ({{ props.row.applied_count }})</span
>Show where template is applied ({{
props.row.applied_count
}})</span
></q-td
>
<!-- alert exclusions -->
@@ -175,10 +236,27 @@ export default {
selectedTemplate: null,
templates: [],
columns: [
{ name: "is_active", label: "Active", field: "is_active", align: "left" },
{ name: "agent_settings", label: "Agent Settings", field: "agent_settings" },
{ name: "check_settings", label: "Check Settings", field: "check_settings" },
{ name: "task_settings", label: "Task Settings", field: "task_settings" },
{
name: "is_active",
label: "Active",
field: "is_active",
align: "left",
},
{
name: "agent_settings",
label: "Agent Settings",
field: "agent_settings",
},
{
name: "check_settings",
label: "Check Settings",
field: "check_settings",
},
{
name: "task_settings",
label: "Task Settings",
field: "task_settings",
},
{ name: "name", label: "Name", field: "name", align: "left" },
{
name: "applied_to",
@@ -217,11 +295,11 @@ export default {
this.$q.loading.show();
this.$axios
.get("alerts/templates/")
.then(r => {
.then((r) => {
this.templates = r.data;
this.$q.loading.hide();
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -244,12 +322,14 @@ export default {
this.$q.loading.show();
this.$axios
.delete(`alerts/templates/${template.id}/`)
.then(r => {
.then(() => {
this.refresh();
this.$q.loading.hide();
this.notifySuccess(`Alert template ${template.name} was deleted!`);
this.notifySuccess(
`Alert template ${template.name} was deleted!`
);
})
.catch(error => {
.catch(() => {
this.$q.loading.hide();
});
});
@@ -297,23 +377,23 @@ export default {
});
},
toggleEnabled(template) {
let text = !template.is_active ? "Template enabled successfully" : "Template disabled successfully";
let text = !template.is_active
? "Template enabled successfully"
: "Template disabled successfully";
const data = {
id: template.id,
is_active: !template.is_active,
};
this.$axios
.put(`alerts/templates/${template.id}/`, data)
.then(r => {
this.$axios.put(`alerts/templates/${template.id}/`, data).then(() => {
this.notifySuccess(text);
this.$store.dispatch("refreshDashboard");
})
.catch(error => {});
});
},
rowSelectedClass(id, selectedTemplate) {
if (selectedTemplate && selectedTemplate.id === id) return this.$q.dark.isActive ? "highlight-dark" : "highlight";
if (selectedTemplate && selectedTemplate.id === id)
return this.$q.dark.isActive ? "highlight-dark" : "highlight";
},
show() {
this.$refs.dialog.show();

View File

@@ -51,7 +51,11 @@
<q-item clickable v-close-popup @click="showDeployments">
<q-item-section>Manage Deployments</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showUpdateAgentsModal = true">
<q-item
clickable
v-close-popup
@click="showUpdateAgentsModal = true"
>
<q-item-section>Update Agents</q-item-section>
</q-item>
</q-list>
@@ -87,11 +91,20 @@
<q-item-section>User Administration</q-item-section>
</q-item>
<!-- core settings -->
<q-item clickable v-close-popup @click="showEditCoreSettingsModal = true">
<q-item
clickable
v-close-popup
@click="showEditCoreSettingsModal = true"
>
<q-item-section>Global Settings</q-item-section>
</q-item>
<!-- code sign -->
<q-item v-if="!hosted" clickable v-close-popup @click="showCodeSign = true">
<q-item
v-if="!hosted"
clickable
v-close-popup
@click="showCodeSign = true"
>
<q-item-section>Code Signing</q-item-section>
</q-item>
</q-list>
@@ -102,7 +115,11 @@
<q-menu auto-close>
<q-list dense style="min-width: 100px">
<!-- bulk command -->
<q-item clickable v-close-popup @click="showBulkAction('command')">
<q-item
clickable
v-close-popup
@click="showBulkAction('command')"
>
<q-item-section>Bulk Command</q-item-section>
</q-item>
<!-- bulk script -->
@@ -114,7 +131,11 @@
<q-item-section>Bulk Patch Management</q-item-section>
</q-item>
<!-- server maintenance -->
<q-item clickable v-close-popup @click="showServerMaintenance = true">
<q-item
clickable
v-close-popup
@click="showServerMaintenance = true"
>
<q-item-section>Server Maintenance</q-item-section>
</q-item>
<!-- clear cache -->
@@ -160,7 +181,12 @@
</div>
<!-- Update Agents Modal -->
<div class="q-pa-md q-gutter-sm">
<q-dialog v-model="showUpdateAgentsModal" maximized transition-show="slide-up" transition-hide="slide-down">
<q-dialog
v-model="showUpdateAgentsModal"
maximized
transition-show="slide-up"
transition-hide="slide-down"
>
<UpdateAgents @close="showUpdateAgentsModal = false" />
</q-dialog>
</div>
@@ -199,7 +225,7 @@ import AdminManager from "@/components/AdminManager";
import InstallAgent from "@/components/modals/agents/InstallAgent";
import AuditManager from "@/components/logs/AuditManager";
import BulkAction from "@/components/modals/agents/BulkAction";
import Deployment from "@/components/clients/Deployment";
import DeploymentTable from "@/components/clients/DeploymentTable";
import ServerMaintenance from "@/components/modals/core/ServerMaintenance";
import CodeSign from "@/components/modals/coresettings/CodeSign";
import PermissionsManager from "@/components/accounts/PermissionsManager";
@@ -214,7 +240,6 @@ export default {
AdminManager,
ServerMaintenance,
CodeSign,
PermissionsManager,
},
data() {
return {
@@ -235,8 +260,7 @@ export default {
clearCache() {
this.$axios
.get("/core/clearcache/")
.then(r => this.notifySuccess(r.data))
.catch(() => {});
.then((r) => this.notifySuccess(r.data));
},
openHelp(mode) {
let url;
@@ -248,10 +272,12 @@ export default {
url = "https://docs.tacticalrmm.com";
break;
case "bug":
url = "https://github.com/amidaware/tacticalrmm/issues/new?template=bug_report.md";
url =
"https://github.com/amidaware/tacticalrmm/issues/new?template=bug_report.md";
break;
case "feature":
url = "https://github.com/amidaware/tacticalrmm/issues/new?template=feature_request.md";
url =
"https://github.com/amidaware/tacticalrmm/issues/new?template=feature_request.md";
break;
case "discord":
url = "https://discord.gg/upGTkWp";
@@ -349,7 +375,7 @@ export default {
},
showDeployments() {
this.$q.dialog({
component: Deployment,
component: DeploymentTable,
});
},
},

View File

@@ -1,6 +1,8 @@
<template>
<q-layout container view="hHh lpr lfr">
<q-header :class="{ 'bg-dark': $q.dark.isActive, 'bg-light': !$q.dark.isActive }">
<q-header
:class="{ 'bg-dark': $q.dark.isActive, 'bg-light': !$q.dark.isActive }"
>
<q-tabs
v-model="subtab"
dense
@@ -88,34 +90,74 @@
</q-header>
<q-page-container>
<q-tab-panels v-model="subtab" :animated="false">
<q-tab-panel v-if="activeTabs.includes('summary')" name="summary" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('summary')"
name="summary"
class="q-pa-none"
>
<SummaryTab />
</q-tab-panel>
<q-tab-panel v-if="activeTabs.includes('checks')" name="checks" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('checks')"
name="checks"
class="q-pa-none"
>
<ChecksTab />
</q-tab-panel>
<q-tab-panel v-if="activeTabs.includes('tasks')" name="tasks" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('tasks')"
name="tasks"
class="q-pa-none"
>
<AutomatedTasksTab />
</q-tab-panel>
<q-tab-panel v-if="activeTabs.includes('patches')" name="patches" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('patches')"
name="patches"
class="q-pa-none"
>
<WinUpdateTab />
</q-tab-panel>
<q-tab-panel v-if="activeTabs.includes('software')" name="software" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('software')"
name="software"
class="q-pa-none"
>
<SoftwareTab />
</q-tab-panel>
<q-tab-panel v-if="activeTabs.includes('history')" name="history" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('history')"
name="history"
class="q-pa-none"
>
<HistoryTab />
</q-tab-panel>
<q-tab-panel v-if="activeTabs.includes('notes')" name="notes" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('notes')"
name="notes"
class="q-pa-none"
>
<NotesTab />
</q-tab-panel>
<q-tab-panel v-if="activeTabs.includes('assets')" name="assets" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('assets')"
name="assets"
class="q-pa-none"
>
<AssetsTab />
</q-tab-panel>
<q-tab-panel v-if="activeTabs.includes('debug')" name="debug" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('debug')"
name="debug"
class="q-pa-none"
>
<DebugTab />
</q-tab-panel>
<q-tab-panel v-if="activeTabs.includes('audit')" name="audit" class="q-pa-none">
<q-tab-panel
v-if="activeTabs.includes('audit')"
name="audit"
class="q-pa-none"
>
<AuditTab />
</q-tab-panel>
</q-tab-panels>
@@ -156,7 +198,18 @@ export default {
props: {
activeTabs: {
type: Array,
default: ["summary", "checks", "tasks", "patches", "software", "history", "notes", "assets", "debug", "audit"],
default: () => [
"summary",
"checks",
"tasks",
"patches",
"software",
"history",
"notes",
"assets",
"debug",
"audit",
],
},
},
setup(props) {

View File

@@ -9,7 +9,10 @@
</q-bar>
<q-table
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
style="max-height: 70vh"
binary-state-sort
@@ -22,10 +25,20 @@
:rows-per-page-options="[0]"
>
<template v-slot:top>
<q-btn flat dense icon="add" label="New Role" @click="showAddRoleModal" />
<q-btn
flat
dense
icon="add"
label="New Role"
@click="showAddRoleModal"
/>
</template>
<template v-slot:body="props">
<q-tr :props="props" @dblclick="showEditRoleModal(props.row)" class="cursor-pointer">
<q-tr
:props="props"
@dblclick="showEditRoleModal(props.row)"
class="cursor-pointer"
>
<q-menu context-menu auto-close>
<q-list dense style="min-width: 200px">
<q-item clickable @click="showEditRoleModal(props.row)">
@@ -34,7 +47,11 @@
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable @click="deleteRole(props.row)" :disable="props.row.user_count > 0">
<q-item
clickable
@click="deleteRole(props.row)"
:disable="props.row.user_count > 0"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
@@ -50,7 +67,12 @@
</q-menu>
<q-td key="name" :props="props">{{ props.row.name }}</q-td>
<q-td key="is_superuser" :props="props">
<q-icon v-if="props.row.is_superuser" name="done" color="primary" size="sm" />
<q-icon
v-if="props.row.is_superuser"
name="done"
color="primary"
size="sm"
/>
</q-td>
<q-td key="user_count" :props="props">
{{ props.row.user_count }}
@@ -75,14 +97,26 @@ import RolesForm from "@/components/accounts/RolesForm";
// static data
const columns = [
{ name: "name", label: "Name", field: "name", align: "left", sortable: true },
{ name: "is_superuser", label: "Superuser", field: "is_superuser", align: "left", sortable: true },
{ name: "user_count", label: "Assigned Users", field: "user_count", align: "left", sortable: true },
{
name: "is_superuser",
label: "Superuser",
field: "is_superuser",
align: "left",
sortable: true,
},
{
name: "user_count",
label: "Assigned Users",
field: "user_count",
align: "left",
sortable: true,
},
];
export default {
name: "PermissionsManager",
emits: [...useDialogPluginComponent.emits],
setup(props) {
setup() {
// setup quasar
const $q = useQuasar();
const { dialogRef, onDialogHide } = useDialogPluginComponent();

View File

@@ -14,7 +14,7 @@
dense
outlined
v-model="localRole.name"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section class="scroll" style="height: 70vh">
@@ -31,10 +31,22 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_accounts" label="List User Accounts" />
<q-checkbox v-model="localRole.can_manage_accounts" label="Manage User Accounts" />
<q-checkbox v-model="localRole.can_list_roles" label="List Roles" />
<q-checkbox v-model="localRole.can_manage_roles" label="Manage Roles" />
<q-checkbox
v-model="localRole.can_list_accounts"
label="List User Accounts"
/>
<q-checkbox
v-model="localRole.can_manage_accounts"
label="Manage User Accounts"
/>
<q-checkbox
v-model="localRole.can_list_roles"
label="List Roles"
/>
<q-checkbox
v-model="localRole.can_manage_roles"
label="Manage Roles"
/>
</div>
</q-card-section>
@@ -42,38 +54,116 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_agents" label="List Agents" />
<q-checkbox v-model="localRole.can_list_agent_history" label="List Agent History" />
<q-checkbox v-model="localRole.can_use_mesh" label="Use MeshCentral" />
<q-checkbox v-model="localRole.can_uninstall_agents" label="Uninstall Agents" />
<q-checkbox v-model="localRole.can_ping_agents" label="Ping Agents" />
<q-checkbox v-model="localRole.can_update_agents" label="Update Agents" />
<q-checkbox v-model="localRole.can_edit_agent" label="Edit Agents" />
<q-checkbox v-model="localRole.can_manage_procs" label="Manage Processes" />
<q-checkbox v-model="localRole.can_view_eventlogs" label="View Event Logs" />
<q-checkbox v-model="localRole.can_send_cmd" label="Send Command" />
<q-checkbox v-model="localRole.can_reboot_agents" label="Reboot Agents" />
<q-checkbox v-model="localRole.can_install_agents" label="Install Agents" />
<q-checkbox v-model="localRole.can_run_scripts" label="Run Script" />
<q-checkbox v-model="localRole.can_run_bulk" label="Bulk Actions" />
<q-checkbox v-model="localRole.can_recover_agents" label="Recover Agents" />
<q-checkbox
v-model="localRole.can_list_agents"
label="List Agents"
/>
<q-checkbox
v-model="localRole.can_list_agent_history"
label="List Agent History"
/>
<q-checkbox
v-model="localRole.can_use_mesh"
label="Use MeshCentral"
/>
<q-checkbox
v-model="localRole.can_uninstall_agents"
label="Uninstall Agents"
/>
<q-checkbox
v-model="localRole.can_ping_agents"
label="Ping Agents"
/>
<q-checkbox
v-model="localRole.can_update_agents"
label="Update Agents"
/>
<q-checkbox
v-model="localRole.can_edit_agent"
label="Edit Agents"
/>
<q-checkbox
v-model="localRole.can_manage_procs"
label="Manage Processes"
/>
<q-checkbox
v-model="localRole.can_view_eventlogs"
label="View Event Logs"
/>
<q-checkbox
v-model="localRole.can_send_cmd"
label="Send Command"
/>
<q-checkbox
v-model="localRole.can_reboot_agents"
label="Reboot Agents"
/>
<q-checkbox
v-model="localRole.can_install_agents"
label="Install Agents"
/>
<q-checkbox
v-model="localRole.can_run_scripts"
label="Run Script"
/>
<q-checkbox
v-model="localRole.can_run_bulk"
label="Bulk Actions"
/>
<q-checkbox
v-model="localRole.can_recover_agents"
label="Recover Agents"
/>
</div>
</q-card-section>
<div class="text-subtitle2">Core</div>
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_notes" label="List Notes" />
<q-checkbox v-model="localRole.can_manage_notes" label="Manage Notes" />
<q-checkbox v-model="localRole.can_view_core_settings" label="View Global Settings" />
<q-checkbox v-model="localRole.can_edit_core_settings" label="Edit Global Settings" />
<q-checkbox v-model="localRole.can_do_server_maint" label="Do Server Maintenance" />
<q-checkbox v-model="localRole.can_code_sign" label="Manage Code Signing" />
<q-checkbox v-model="localRole.can_list_api_keys" label="List API Keys" />
<q-checkbox v-model="localRole.can_manage_api_keys" label="Manage API Keys" />
<q-checkbox v-model="localRole.can_run_urlactions" label="Run URL Actions" />
<q-checkbox v-model="localRole.can_view_customfields" label="View Custom Fields" />
<q-checkbox v-model="localRole.can_manage_customfields" label="Edit Custom Fields" />
<q-checkbox
v-model="localRole.can_list_notes"
label="List Notes"
/>
<q-checkbox
v-model="localRole.can_manage_notes"
label="Manage Notes"
/>
<q-checkbox
v-model="localRole.can_view_core_settings"
label="View Global Settings"
/>
<q-checkbox
v-model="localRole.can_edit_core_settings"
label="Edit Global Settings"
/>
<q-checkbox
v-model="localRole.can_do_server_maint"
label="Do Server Maintenance"
/>
<q-checkbox
v-model="localRole.can_code_sign"
label="Manage Code Signing"
/>
<q-checkbox
v-model="localRole.can_list_api_keys"
label="List API Keys"
/>
<q-checkbox
v-model="localRole.can_manage_api_keys"
label="Manage API Keys"
/>
<q-checkbox
v-model="localRole.can_run_urlactions"
label="Run URL Actions"
/>
<q-checkbox
v-model="localRole.can_view_customfields"
label="View Custom Fields"
/>
<q-checkbox
v-model="localRole.can_manage_customfields"
label="Edit Custom Fields"
/>
</div>
</q-card-section>
@@ -81,9 +171,18 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_checks" label="List Checks" />
<q-checkbox v-model="localRole.can_manage_checks" label="Manage Checks" />
<q-checkbox v-model="localRole.can_run_checks" label="Run Checks" />
<q-checkbox
v-model="localRole.can_list_checks"
label="List Checks"
/>
<q-checkbox
v-model="localRole.can_manage_checks"
label="Manage Checks"
/>
<q-checkbox
v-model="localRole.can_run_checks"
label="Run Checks"
/>
</div>
</q-card-section>
@@ -91,12 +190,30 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_clients" label="List Clients" />
<q-checkbox v-model="localRole.can_manage_clients" label="Manage Clients" />
<q-checkbox v-model="localRole.can_list_sites" label="List Sites" />
<q-checkbox v-model="localRole.can_manage_sites" label="Manage Sites" />
<q-checkbox v-model="localRole.can_list_deployments" label="List Deployments" />
<q-checkbox v-model="localRole.can_manage_deployments" label="Manage Deployments" />
<q-checkbox
v-model="localRole.can_list_clients"
label="List Clients"
/>
<q-checkbox
v-model="localRole.can_manage_clients"
label="Manage Clients"
/>
<q-checkbox
v-model="localRole.can_list_sites"
label="List Sites"
/>
<q-checkbox
v-model="localRole.can_manage_sites"
label="Manage Sites"
/>
<q-checkbox
v-model="localRole.can_list_deployments"
label="List Deployments"
/>
<q-checkbox
v-model="localRole.can_manage_deployments"
label="Manage Deployments"
/>
</div>
</q-card-section>
@@ -131,8 +248,14 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_automation_policies" label="List Automation Policies" />
<q-checkbox v-model="localRole.can_manage_automation_policies" label="Manage Automation Policies" />
<q-checkbox
v-model="localRole.can_list_automation_policies"
label="List Automation Policies"
/>
<q-checkbox
v-model="localRole.can_manage_automation_policies"
label="Manage Automation Policies"
/>
</div>
</q-card-section>
@@ -140,9 +263,18 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_autotasks" label="List Tasks" />
<q-checkbox v-model="localRole.can_manage_autotasks" label="Manage Tasks" />
<q-checkbox v-model="localRole.can_run_autotasks" label="Run Tasks" />
<q-checkbox
v-model="localRole.can_list_autotasks"
label="List Tasks"
/>
<q-checkbox
v-model="localRole.can_manage_autotasks"
label="Manage Tasks"
/>
<q-checkbox
v-model="localRole.can_run_autotasks"
label="Run Tasks"
/>
</div>
</q-card-section>
@@ -150,10 +282,22 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_view_auditlogs" label="View Audit Logs" />
<q-checkbox v-model="localRole.can_view_debuglogs" label="View Debug Logs" />
<q-checkbox v-model="localRole.can_list_pendingactions" label="List Pending Actions" />
<q-checkbox v-model="localRole.can_manage_pendingactions" label="Manage Pending Actions" />
<q-checkbox
v-model="localRole.can_view_auditlogs"
label="View Audit Logs"
/>
<q-checkbox
v-model="localRole.can_view_debuglogs"
label="View Debug Logs"
/>
<q-checkbox
v-model="localRole.can_list_pendingactions"
label="List Pending Actions"
/>
<q-checkbox
v-model="localRole.can_manage_pendingactions"
label="Manage Pending Actions"
/>
</div>
</q-card-section>
@@ -161,8 +305,14 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_scripts" label="List Scripts" />
<q-checkbox v-model="localRole.can_manage_scripts" label="Manage Scripts" />
<q-checkbox
v-model="localRole.can_list_scripts"
label="List Scripts"
/>
<q-checkbox
v-model="localRole.can_manage_scripts"
label="Manage Scripts"
/>
</div>
</q-card-section>
@@ -170,10 +320,22 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_alerts" label="List Alerts" />
<q-checkbox v-model="localRole.can_manage_alerts" label="Manage Alerts" />
<q-checkbox v-model="localRole.can_list_alerttemplates" label="List Alert Templates" />
<q-checkbox v-model="localRole.can_manage_alerttemplates" label="Manage Alert Templates" />
<q-checkbox
v-model="localRole.can_list_alerts"
label="List Alerts"
/>
<q-checkbox
v-model="localRole.can_manage_alerts"
label="Manage Alerts"
/>
<q-checkbox
v-model="localRole.can_list_alerttemplates"
label="List Alert Templates"
/>
<q-checkbox
v-model="localRole.can_manage_alerttemplates"
label="Manage Alert Templates"
/>
</div>
</q-card-section>
@@ -181,7 +343,10 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_manage_winsvcs" label="Manage Windows Services" />
<q-checkbox
v-model="localRole.can_manage_winsvcs"
label="Manage Windows Services"
/>
</div>
</q-card-section>
@@ -189,8 +354,14 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_list_software" label="List Software" />
<q-checkbox v-model="localRole.can_manage_software" label="Manage Software" />
<q-checkbox
v-model="localRole.can_list_software"
label="List Software"
/>
<q-checkbox
v-model="localRole.can_manage_software"
label="Manage Software"
/>
</div>
</q-card-section>
@@ -198,13 +369,23 @@
<q-separator />
<q-card-section class="row">
<div class="q-gutter-sm">
<q-checkbox v-model="localRole.can_manage_winupdates" label="Manage Windows Updates" />
<q-checkbox
v-model="localRole.can_manage_winupdates"
label="Manage Windows Updates"
/>
</div>
</q-card-section>
</q-card-section>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -323,7 +504,9 @@ export default {
async function onSubmit() {
loading.value = true;
try {
const result = props.role ? await editRole(role.value.id, role.value) : await saveRole(role.value);
const result = props.role
? await editRole(role.value.id, role.value)
: await saveRole(role.value);
notifySuccess(result);
onDialogOK();
} catch (e) {
@@ -334,7 +517,8 @@ export default {
watch(
() => role.value.is_superuser,
(newValue, oldValue) => {
(newValue) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Object.keys(role.value).forEach((key, index) => {
if (typeof role.value[key] === "boolean") {
role.value[key] = newValue;

View File

@@ -15,7 +15,12 @@
<q-item-section>Pending Agent Actions</q-item-section>
</q-item>
<!-- take control -->
<q-item clickable v-ripple v-close-popup @click="runTakeControl(agent.agent_id)">
<q-item
clickable
v-ripple
v-close-popup
@click="runTakeControl(agent.agent_id)"
>
<q-item-section side>
<q-icon size="xs" name="fas fa-desktop" />
</q-item-section>
@@ -39,7 +44,9 @@
dense
clickable
v-close-popup
@click="runURLAction({ agent_id: agent.agent_id, action: action.id })"
@click="
runURLAction({ agent_id: agent.agent_id, action: action.id })
"
>
{{ action.name }}
</q-item>
@@ -85,7 +92,11 @@
</q-menu>
</q-item>
<q-item clickable v-close-popup @click="runRemoteBackground(agent.agent_id, agent.plat)">
<q-item
clickable
v-close-popup
@click="runRemoteBackground(agent.agent_id, agent.plat)"
>
<q-item-section side>
<q-icon size="xs" name="fas fa-cogs" />
</q-item-section>
@@ -98,7 +109,11 @@
<q-icon size="xs" name="construction" />
</q-item-section>
<q-item-section>
{{ agent.maintenance_mode ? "Disable Maintenance Mode" : "Enable Maintenance Mode" }}
{{
agent.maintenance_mode
? "Disable Maintenance Mode"
: "Enable Maintenance Mode"
}}
</q-item-section>
</q-item>
@@ -215,7 +230,7 @@ export default {
props: {
agent: !Object,
},
setup(props) {
setup() {
// setup quasar
const $q = useQuasar();
@@ -252,7 +267,9 @@ export default {
urlActions.value = await fetchURLActions();
if (urlActions.value.length === 0) {
notifyWarning("No URL Actions configured. Go to Settings > Global Settings > URL Actions");
notifyWarning(
"No URL Actions configured. Go to Settings > Global Settings > URL Actions"
);
return;
}
} catch (e) {}
@@ -283,9 +300,11 @@ export default {
menuLoading.value = true;
try {
const data = await fetchScripts({ showCommunityScripts: store.state.showCommunityScripts });
const data = await fetchScripts({
showCommunityScripts: store.state.showCommunityScripts,
});
const scripts = data.filter(script => !!script.favorite);
const scripts = data.filter((script) => !!script.favorite);
if (scripts.length === 0) {
notifyWarning("You don't have any scripts favorited!");
@@ -293,7 +312,7 @@ export default {
}
favoriteScripts.value = scripts
.map(script => ({
.map((script) => ({
label: script.name,
value: script.id,
timeout: script.default_timeout,
@@ -311,8 +330,12 @@ export default {
};
try {
const result = await editAgent(agent.agent_id, data);
notifySuccess(`Maintenance mode was ${agent.maintenance_mode ? "disabled" : "enabled"} on ${agent.hostname}`);
await editAgent(agent.agent_id, data);
notifySuccess(
`Maintenance mode was ${
agent.maintenance_mode ? "disabled" : "enabled"
} on ${agent.hostname}`
);
store.commit("setRefreshSummaryTab", true);
refreshDashboard();
} catch (e) {
@@ -322,7 +345,7 @@ export default {
async function runPatchStatusScan(agent) {
try {
const result = await runAgentUpdateScan(agent.agent_id);
await runAgentUpdateScan(agent.agent_id);
notifySuccess(`Scan will be run shortly on ${agent.hostname}`);
} catch (e) {
console.error(e);
@@ -365,7 +388,7 @@ export default {
}).onOk(async () => {
$q.loading.show();
try {
const result = await agentRebootNow(agent.agent_id);
await agentRebootNow(agent.agent_id);
notifySuccess(`${agent.hostname} will now be restarted`);
$q.loading.hide();
} catch (e) {
@@ -426,21 +449,25 @@ export default {
function deleteAgent(agent) {
$q.dialog({
title: `Please type <code style="color:red">yes</code> in the box below to confirm deletion.`,
title:
'Please type <code style="color:red">yes</code> in the box below to confirm deletion.',
prompt: {
model: "",
type: "text",
isValid: val => val === "yes",
isValid: (val) => val === "yes",
},
cancel: true,
ok: { label: "Uninstall", color: "negative" },
persistent: true,
html: true,
}).onOk(async val => {
}).onOk(async () => {
try {
const data = await removeAgent(agent.agent_id);
notifySuccess(data);
refreshDashboard(false /* clearTreeSelected */, true /* clearSubTable */);
refreshDashboard(
false /* clearTreeSelected */,
true /* clearSubTable */
);
} catch (e) {
console.error(e);
}

View File

@@ -87,7 +87,7 @@ import WmiDetail from "@/components/agents/WmiDetail";
export default {
name: "AssetsTab",
components: { WmiDetail },
setup(props) {
setup() {
// setup vuex
const store = useStore();
const selectedAgent = computed(() => store.state.selectedRow);
@@ -105,7 +105,7 @@ export default {
loading.value = false;
}
watch(selectedAgent, (newValue, oldValue) => {
watch(selectedAgent, (newValue) => {
if (newValue) {
getWMIData();
}
@@ -125,4 +125,3 @@ export default {
},
};
</script>

View File

@@ -6,7 +6,10 @@
<div v-else>
<q-table
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:style="{ 'max-height': tabHeight }"
:rows="tasks"
@@ -20,8 +23,23 @@
no-data-label="No tasks"
>
<template v-slot:top>
<q-btn class="q-mr-sm" dense flat push @click="getTasks" icon="refresh" />
<q-btn icon="add" label="Add Task" no-caps dense flat push @click="showAddTask" />
<q-btn
class="q-mr-sm"
dense
flat
push
@click="getTasks"
icon="refresh"
/>
<q-btn
icon="add"
label="Add Task"
no-caps
dense
flat
push
@click="showAddTask"
/>
</template>
<template v-slot:loading>
@@ -79,7 +97,11 @@
<!-- body slots -->
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="showEditTask(props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="showEditTask(props.row)"
>
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
@@ -89,13 +111,23 @@
</q-item-section>
<q-item-section>Run task now</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showEditTask(props.row)" v-if="!props.row.policy">
<q-item
clickable
v-close-popup
@click="showEditTask(props.row)"
v-if="!props.row.policy"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="deleteTask(props.row)" v-if="!props.row.policy">
<q-item
clickable
v-close-popup
@click="deleteTask(props.row)"
v-if="!props.row.policy"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
@@ -111,7 +143,9 @@
<q-td>
<q-checkbox
dense
@update:model-value="editTask(props.row, { enabled: !props.row.enabled })"
@update:model-value="
editTask(props.row, { enabled: !props.row.enabled })
"
v-model="props.row.enabled"
:disable="!!props.row.policy"
/>
@@ -119,18 +153,26 @@
<!-- text alert -->
<q-td>
<q-checkbox
v-if="props.row.alert_template && props.row.alert_template.always_text !== null"
v-if="
props.row.alert_template &&
props.row.alert_template.always_text !== null
"
v-model="props.row.alert_template.always_text"
disable
dense
>
<q-tooltip> Setting is overridden by alert template: {{ props.row.alert_template.name }} </q-tooltip>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="editTask(props.row, { text_alert: !props.row.text_alert })"
@update:model-value="
editTask(props.row, { text_alert: !props.row.text_alert })
"
v-model="props.row.text_alert"
:disable="!!props.row.policy"
/>
@@ -138,18 +180,26 @@
<!-- email alert -->
<q-td>
<q-checkbox
v-if="props.row.alert_template && props.row.alert_template.always_email !== null"
v-if="
props.row.alert_template &&
props.row.alert_template.always_email !== null
"
v-model="props.row.alert_template.always_email"
disable
dense
>
<q-tooltip> Setting is overridden by alert template: {{ props.row.alert_template.name }} </q-tooltip>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="editTask(props.row, { email_alert: !props.row.email_alert })"
@update:model-value="
editTask(props.row, { email_alert: !props.row.email_alert })
"
v-model="props.row.email_alert"
:disable="!!props.row.policy"
/>
@@ -157,44 +207,73 @@
<!-- dashboard alert -->
<q-td>
<q-checkbox
v-if="props.row.alert_template && props.row.alert_template.always_alert !== null"
v-if="
props.row.alert_template &&
props.row.alert_template.always_alert !== null
"
v-model="props.row.alert_template.always_alert"
disable
dense
>
<q-tooltip> Setting is overridden by alert template: {{ props.row.alert_template.name }} </q-tooltip>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="editTask(props.row, { dashboard_alert: !props.row.dashboard_alert })"
@update:model-value="
editTask(props.row, {
dashboard_alert: !props.row.dashboard_alert,
})
"
v-model="props.row.dashboard_alert"
:disable="!!props.row.policy"
/>
</q-td>
<!-- policy check icon -->
<q-td>
<q-icon v-if="props.row.policy" style="font-size: 1.3rem" name="policy">
<q-icon
v-if="props.row.policy"
style="font-size: 1.3rem"
name="policy"
>
<q-tooltip>This task is managed by a policy</q-tooltip>
</q-icon>
</q-td>
<!-- is collector task -->
<q-td>
<q-icon v-if="!!props.row.custom_field" style="font-size: 1.3rem" name="check">
<q-tooltip>The task updates a custom field on the agent</q-tooltip>
<q-icon
v-if="!!props.row.custom_field"
style="font-size: 1.3rem"
name="check"
>
<q-tooltip
>The task updates a custom field on the agent</q-tooltip
>
</q-icon>
</q-td>
<!-- status icon -->
<q-td v-if="Object.keys(props.row.task_result).length === 0"></q-td>
<q-td v-else-if="props.row.task_result.status === 'passing'">
<q-icon style="font-size: 1.3rem" color="positive" name="check_circle">
<q-icon
style="font-size: 1.3rem"
color="positive"
name="check_circle"
>
<q-tooltip>Passing</q-tooltip>
</q-icon>
</q-td>
<q-td v-else-if="props.row.task_result.status === 'failing'">
<q-icon v-if="props.row.alert_severity === 'info'" style="font-size: 1.3rem" color="info" name="info">
<q-icon
v-if="props.row.alert_severity === 'info'"
style="font-size: 1.3rem"
color="info"
name="info"
>
<q-tooltip>Informational</q-tooltip>
</q-icon>
<q-icon
@@ -205,7 +284,12 @@
>
<q-tooltip>Warning</q-tooltip>
</q-icon>
<q-icon v-else style="font-size: 1.3rem" color="negative" name="error">
<q-icon
v-else
style="font-size: 1.3rem"
color="negative"
name="error"
>
<q-tooltip>Error</q-tooltip>
</q-icon>
</q-td>
@@ -213,13 +297,22 @@
<!-- name -->
<q-td>{{ props.row.name }}</q-td>
<!-- sync status -->
<q-td v-if="props.row.task_result.sync_status === 'notsynced'">Will sync on next agent checkin</q-td>
<q-td v-else-if="props.row.task_result.sync_status === 'synced'">Synced with agent</q-td>
<q-td v-else-if="props.row.task_result.sync_status === 'pendingdeletion'">Pending deletion on agent</q-td>
<q-td v-if="props.row.task_result.sync_status === 'notsynced'"
>Will sync on next agent checkin</q-td
>
<q-td v-else-if="props.row.task_result.sync_status === 'synced'"
>Synced with agent</q-td
>
<q-td
v-else-if="props.row.task_result.sync_status === 'pendingdeletion'"
>Pending deletion on agent</q-td
>
<q-td v-else>Waiting for task creation on agent</q-td>
<q-td
v-if="
props.row.task_result.retcode !== null || props.row.task_result.stdout || props.row.task_result.stderr
props.row.task_result.retcode !== null ||
props.row.task_result.stdout ||
props.row.task_result.stderr
"
>
<span
@@ -230,13 +323,17 @@
>
</q-td>
<q-td v-else>Awaiting output</q-td>
<q-td v-if="props.row.task_result.last_run">{{ formatDate(props.row.task_result.last_run) }}</q-td>
<q-td v-if="props.row.task_result.last_run">{{
formatDate(props.row.task_result.last_run)
}}</q-td>
<q-td v-else>Has not run yet</q-td>
<q-td>{{ props.row.schedule }}</q-td>
<q-td>
<span v-if="props.row.check_name">
{{ truncateText(props.row.check_name, 40) }}
<q-tooltip v-if="props.row.check_name.length > 40">{{ props.row.check_name }}</q-tooltip>
<q-tooltip v-if="props.row.check_name.length > 40">{{
props.row.check_name
}}</q-tooltip>
</span>
</q-td>
</q-tr>
@@ -266,10 +363,22 @@ const columns = [
{ name: "emailalert", field: "email_alert", align: "left" },
{ name: "dashboardalert", field: "dashboard_alert", align: "left" },
{ name: "policystatus", align: "left" },
{ name: "collector", label: "Collector", field: "custom_field", align: "left", sortable: true },
{
name: "collector",
label: "Collector",
field: "custom_field",
align: "left",
sortable: true,
},
{ name: "status", align: "left" },
{ name: "name", label: "Name", field: "name", align: "left", sortable: true },
{ name: "sync_status", label: "Sync Status", field: "sync_status", align: "left", sortable: true },
{
name: "sync_status",
label: "Sync Status",
field: "sync_status",
align: "left",
sortable: true,
},
{
name: "moreinfo",
label: "More Info",
@@ -302,7 +411,7 @@ const columns = [
export default {
name: "AutomatedTasksTab",
setup(props) {
setup() {
// setup vuex
const store = useStore();
const selectedAgent = computed(() => store.state.selectedRow);
@@ -327,7 +436,9 @@ export default {
loading.value = true;
try {
const result = await fetchAgentTasks(selectedAgent.value);
tasks.value = result.filter(task => task.sync_status !== "pendingdeletion");
tasks.value = result.filter(
(task) => task.sync_status !== "pendingdeletion"
);
} catch (e) {
console.error(e);
}
@@ -378,7 +489,10 @@ export default {
loading.value = true;
try {
const result = await runTask(task.id, task.policy ? { agent_id: selectedAgent.value } : {});
const result = await runTask(
task.id,
task.policy ? { agent_id: selectedAgent.value } : {}
);
notifySuccess(result);
} catch (e) {
console.error(e);
@@ -420,7 +534,7 @@ export default {
});
}
watch(selectedAgent, (newValue, oldValue) => {
watch(selectedAgent, (newValue) => {
if (newValue) {
getTasks();
}
@@ -458,4 +572,3 @@ export default {
},
};
</script>

View File

@@ -3,7 +3,10 @@
<div v-else>
<q-table
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:style="{ 'max-height': tabHeight }"
:rows="checks"
@@ -22,10 +25,29 @@
<!-- table top slot -->
<template v-slot:top>
<q-btn class="q-mr-sm" dense flat push @click="getChecks" icon="refresh" />
<q-btn-dropdown icon="add" label="New" no-caps dense flat class="q-mr-md">
<q-btn
class="q-mr-sm"
dense
flat
push
@click="getChecks"
icon="refresh"
/>
<q-btn-dropdown
icon="add"
label="New"
no-caps
dense
flat
class="q-mr-md"
>
<q-list dense style="min-width: 200px">
<q-item v-if="agentPlatform === 'windows'" clickable v-close-popup @click="showCheckModal('diskspace')">
<q-item
v-if="agentPlatform === 'windows'"
clickable
v-close-popup
@click="showCheckModal('diskspace')"
>
<q-item-section side>
<q-icon size="xs" name="far fa-hdd" />
</q-item-section>
@@ -37,19 +59,34 @@
</q-item-section>
<q-item-section>Ping Check</q-item-section>
</q-item>
<q-item v-if="agentPlatform === 'windows'" clickable v-close-popup @click="showCheckModal('cpuload')">
<q-item
v-if="agentPlatform === 'windows'"
clickable
v-close-popup
@click="showCheckModal('cpuload')"
>
<q-item-section side>
<q-icon size="xs" name="fas fa-microchip" />
</q-item-section>
<q-item-section>CPU Load Check</q-item-section>
</q-item>
<q-item v-if="agentPlatform === 'windows'" clickable v-close-popup @click="showCheckModal('memory')">
<q-item
v-if="agentPlatform === 'windows'"
clickable
v-close-popup
@click="showCheckModal('memory')"
>
<q-item-section side>
<q-icon size="xs" name="fas fa-memory" />
</q-item-section>
<q-item-section>Memory Check</q-item-section>
</q-item>
<q-item v-if="agentPlatform === 'windows'" clickable v-close-popup @click="showCheckModal('winsvc')">
<q-item
v-if="agentPlatform === 'windows'"
clickable
v-close-popup
@click="showCheckModal('winsvc')"
>
<q-item-section side>
<q-icon size="xs" name="fas fa-cogs" />
</q-item-section>
@@ -61,7 +98,12 @@
</q-item-section>
<q-item-section>Script Check</q-item-section>
</q-item>
<q-item v-if="agentPlatform === 'windows'" clickable v-close-popup @click="showCheckModal('eventlog')">
<q-item
v-if="agentPlatform === 'windows'"
clickable
v-close-popup
@click="showCheckModal('eventlog')"
>
<q-item-section side>
<q-icon size="xs" name="fas fa-clipboard-list" />
</q-item-section>
@@ -69,7 +111,15 @@
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn label="Run Checks Now" dense flat push no-caps icon="play_arrow" @click="runChecks" />
<q-btn
label="Run Checks Now"
dense
flat
push
no-caps
icon="play_arrow"
@click="runChecks"
/>
</template>
<!-- header slots -->
@@ -103,7 +153,11 @@
<!-- body slots -->
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="showCheckModal(props.row.check_type, props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="showCheckModal(props.row.check_type, props.row)"
>
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
@@ -118,14 +172,23 @@
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="deleteCheck(props.row)" :disable="!!props.row.policy">
<q-item
clickable
v-close-popup
@click="deleteCheck(props.row)"
:disable="!!props.row.policy"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
<q-item-section>Delete</q-item-section>
</q-item>
<q-separator></q-separator>
<q-item clickable v-close-popup @click="resetCheckStatus(props.row)">
<q-item
clickable
v-close-popup
@click="resetCheckStatus(props.row)"
>
<q-item-section side>
<q-icon name="info" />
</q-item-section>
@@ -141,18 +204,26 @@
<!-- text alert -->
<q-td>
<q-checkbox
v-if="props.row.alert_template && props.row.alert_template.always_text != null"
v-if="
props.row.alert_template &&
props.row.alert_template.always_text != null
"
v-model="props.row.alert_template.always_text"
disable
dense
>
<q-tooltip> Setting is overridden by alert template: {{ props.row.alert_template.name }} </q-tooltip>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="editCheck(props.row, { text_alert: !props.row.text_alert })"
@update:model-value="
editCheck(props.row, { text_alert: !props.row.text_alert })
"
v-model="props.row.text_alert"
:disable="!!props.row.policy"
/>
@@ -160,18 +231,26 @@
<!-- email alert -->
<q-td>
<q-checkbox
v-if="props.row.alert_template && props.row.alert_template.always_email != null"
v-if="
props.row.alert_template &&
props.row.alert_template.always_email != null
"
v-model="props.row.alert_template.always_email"
disable
dense
>
<q-tooltip> Setting is overridden by alert template: {{ props.row.alert_template.name }} </q-tooltip>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="editCheck(props.row, { email_alert: !props.row.email_alert })"
@update:model-value="
editCheck(props.row, { email_alert: !props.row.email_alert })
"
v-model="props.row.email_alert"
:disable="!!props.row.policy"
/>
@@ -179,18 +258,28 @@
<!-- dashboard alert -->
<q-td>
<q-checkbox
v-if="props.row.alert_template && props.row.alert_template.always_alert !== null"
v-if="
props.row.alert_template &&
props.row.alert_template.always_alert !== null
"
v-model="props.row.alert_template.always_alert"
disable
dense
>
<q-tooltip> Setting is overridden by alert template: {{ props.row.alert_template.name }} </q-tooltip>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="editCheck(props.row, { dashboard_alert: !props.row.dashboard_alert })"
@update:model-value="
editCheck(props.row, {
dashboard_alert: !props.row.dashboard_alert,
})
"
v-model="props.row.dashboard_alert"
:disable="!!props.row.policy"
/>
@@ -210,12 +299,21 @@
<!-- status icon -->
<q-td v-if="Object.keys(props.row.check_result).length === 0"></q-td>
<q-td v-else-if="props.row.check_result.status === 'passing'">
<q-icon style="font-size: 1.3rem" color="positive" name="check_circle">
<q-icon
style="font-size: 1.3rem"
color="positive"
name="check_circle"
>
<q-tooltip>Passing</q-tooltip>
</q-icon>
</q-td>
<q-td v-else-if="props.row.check_result.status === 'failing'">
<q-icon v-if="getAlertSeverity(props.row) === 'info'" style="font-size: 1.3rem" color="info" name="info">
<q-icon
v-if="getAlertSeverity(props.row) === 'info'"
style="font-size: 1.3rem"
color="info"
name="info"
>
<q-tooltip>Informational</q-tooltip>
</q-icon>
<q-icon
@@ -226,7 +324,12 @@
>
<q-tooltip>Warning</q-tooltip>
</q-icon>
<q-icon v-else style="font-size: 1.3rem" color="negative" name="error">
<q-icon
v-else
style="font-size: 1.3rem"
color="negative"
name="error"
>
<q-tooltip>Error</q-tooltip>
</q-icon>
</q-td>
@@ -235,7 +338,9 @@
<q-td>
<span>
{{ truncateText(props.row.readable_desc, 40) }}
<q-tooltip v-if="props.row.readable_desc.length > 40">{{ props.row.readable_desc }}</q-tooltip>
<q-tooltip v-if="props.row.readable_desc.length > 40">{{
props.row.readable_desc
}}</q-tooltip>
</span></q-td
>
<!-- more info -->
@@ -249,21 +354,27 @@
>
&nbsp;&nbsp;&nbsp;
<span
v-if="props.row.check_type === 'ping' && props.row.check_result.id"
v-if="
props.row.check_type === 'ping' && props.row.check_result.id
"
style="cursor: pointer; text-decoration: underline"
class="text-primary"
@click="showPingInfo(props.row)"
>Last Output</span
>
<span
v-else-if="props.row.check_type === 'script' && props.row.check_result.id"
v-else-if="
props.row.check_type === 'script' && props.row.check_result.id
"
style="cursor: pointer; text-decoration: underline"
class="text-primary"
@click="showScriptOutput(props.row.check_result)"
>Last Output</span
>
<span
v-else-if="props.row.check_type === 'eventlog' && props.row.check_result.id"
v-else-if="
props.row.check_type === 'eventlog' && props.row.check_result.id
"
style="cursor: pointer; text-decoration: underline"
class="text-primary"
@click="showEventInfo(props.row)"
@@ -271,14 +382,23 @@
>
<span
v-else-if="
props.row.check_type === 'diskspace' || (props.row.check_type === 'winsvc' && props.row.check_result.id)
props.row.check_type === 'diskspace' ||
(props.row.check_type === 'winsvc' && props.row.check_result.id)
"
>{{ props.row.check_result.more_info }}</span
>
</q-td>
<q-td>{{ props.row.check_result.last_run ? formatDate(props.row.check_result.last_run) : "Never" }}</q-td>
<q-td v-if="props.row.assignedtasks.length > 1">{{ props.row.assignedtasks.length }} Tasks</q-td>
<q-td v-else-if="props.row.assignedtasks.length === 1">{{ props.row.assignedtasks[0].name }}</q-td>
<q-td>{{
props.row.check_result.last_run
? formatDate(props.row.check_result.last_run)
: "Never"
}}</q-td>
<q-td v-if="props.row.assignedtasks.length > 1"
>{{ props.row.assignedtasks.length }} Tasks</q-td
>
<q-td v-else-if="props.row.assignedtasks.length === 1">{{
props.row.assignedtasks[0].name
}}</q-td>
<q-td v-else></q-td>
</q-tr>
</template>
@@ -291,7 +411,12 @@
import { ref, computed, watch, inject, onMounted } from "vue";
import { useStore } from "vuex";
import { useQuasar } from "quasar";
import { updateCheck, removeCheck, resetCheck, runAgentChecks } from "@/api/checks";
import {
updateCheck,
removeCheck,
resetCheck,
runAgentChecks,
} from "@/api/checks";
import { fetchAgentChecks } from "@/api/agents";
import { truncateText } from "@/utils/format";
import { notifySuccess, notifyWarning } from "@/utils/notify";
@@ -315,7 +440,13 @@ const columns = [
{ name: "dashboardalert", field: "dashboard_alert", align: "left" },
{ name: "policystatus", align: "left" },
{ name: "statusicon", align: "left" },
{ name: "desc", field: "readable_desc", label: "Description", align: "left", sortable: true },
{
name: "desc",
field: "readable_desc",
label: "Description",
align: "left",
sortable: true,
},
{
name: "moreinfo",
label: "More Info",
@@ -330,12 +461,18 @@ const columns = [
align: "left",
sortable: true,
},
{ name: "assignedtasks", label: "Assigned Tasks", field: "assigned_task", align: "left", sortable: true },
{
name: "assignedtasks",
label: "Assigned Tasks",
field: "assigned_task",
align: "left",
sortable: true,
},
];
export default {
name: "ChecksTab",
setup(props) {
setup() {
// setup vuex
const store = useStore();
const selectedAgent = computed(() => store.state.selectedRow);
@@ -429,7 +566,10 @@ export default {
const result = await resetCheck(check.check_result.id);
await getChecks();
notifySuccess(result);
refreshDashboard(false /* clearTreeSelected */, false /* clearSubTable */);
refreshDashboard(
false /* clearTreeSelected */,
false /* clearSubTable */
);
} catch (e) {
console.error(e);
}
@@ -495,7 +635,7 @@ export default {
}).onOk(getChecks);
}
watch(selectedAgent, (newValue, oldValue) => {
watch(selectedAgent, (newValue) => {
if (newValue) {
getChecks();
}
@@ -537,4 +677,3 @@ export default {
},
};
</script>

View File

@@ -2,7 +2,10 @@
<div v-if="!selectedAgent" class="q-pa-sm">No agent selected</div>
<div v-else>
<q-table
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
:rows="history"
:columns="columns"
:pagination="{ sortBy: 'time', descending: true, rowsPerPage: 0 }"
@@ -17,7 +20,14 @@
<template v-slot:top>
<q-btn dense flat push @click="getHistory" icon="refresh" />
<q-space />
<q-input v-model="filter" outlined label="Search" dense clearable class="q-pr-sm">
<q-input
v-model="filter"
outlined
label="Search"
dense
clearable
class="q-pr-sm"
>
<template v-slot:prepend>
<q-icon name="search" color="primary" />
</template>
@@ -80,7 +90,7 @@ const columns = [
field: "type",
align: "left",
sortable: true,
format: (val, row) => formatTableColumnText(val),
format: (val) => formatTableColumnText(val),
},
/* {
name: "status",
@@ -93,13 +103,28 @@ const columns = [
{
name: "command",
label: "Script/Command",
field: row => (row.type === "script_run" || row.type === "task_run" ? row.script_name : row.command),
field: (row) =>
row.type === "script_run" || row.type === "task_run"
? row.script_name
: row.command,
align: "left",
sortable: true,
format: (val) => truncateText(val, 30),
},
{
name: "username",
label: "Initiated By",
field: "username",
align: "left",
sortable: true,
},
{
name: "output",
label: "Output",
field: "output",
align: "left",
sortable: true,
format: (val, row) => truncateText(val, 30),
},
{ name: "username", label: "Initiated By", field: "username", align: "left", sortable: true },
{ name: "output", label: "Output", field: "output", align: "left", sortable: true },
];
export default {
@@ -126,7 +151,7 @@ export default {
loading.value = false;
}
watch(selectedAgent, (newValue, oldValue) => {
watch(selectedAgent, (newValue) => {
if (newValue) {
getHistory();
}

View File

@@ -16,8 +16,23 @@
no-data-label="No notes"
>
<template v-slot:top>
<q-btn class="q-mr-sm" dense flat push @click="getNotes" icon="refresh" />
<q-btn icon="add" label="Add Note" no-caps dense flat push @click="addNote" />
<q-btn
class="q-mr-sm"
dense
flat
push
@click="getNotes"
icon="refresh"
/>
<q-btn
icon="add"
label="Add Note"
no-caps
dense
flat
push
@click="addNote"
/>
<q-space />
<export-table-btn :data="notes" :columns="columns" />
</template>
@@ -31,21 +46,31 @@
<q-card-section>
<div class="row">
<div class="col">
<div class="text-subtitle2">{{ formatDate(props.row.entry_time) }}</div>
<div class="text-subtitle2">
{{ formatDate(props.row.entry_time) }}
</div>
<div class="text-caption">{{ props.row.username }}</div>
</div>
<div class="col-auto">
<q-btn color="grey-7" round flat icon="more_vert">
<q-menu cover auto-close>
<q-list dense>
<q-item clickable v-close-popup @click="editNote(props.row)">
<q-item
clickable
v-close-popup
@click="editNote(props.row)"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="deleteNote(props.row)">
<q-item
clickable
v-close-popup
@click="deleteNote(props.row)"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
@@ -71,7 +96,12 @@
import { ref, computed, watch, onMounted } from "vue";
import { useStore } from "vuex";
import { useQuasar } from "quasar";
import { fetchAgentNotes, editAgentNote, saveAgentNote, removeAgentNote } from "@/api/agents";
import {
fetchAgentNotes,
editAgentNote,
saveAgentNote,
removeAgentNote,
} from "@/api/agents";
import { notifySuccess } from "@/utils/notify";
// ui imports
@@ -101,7 +131,7 @@ export default {
components: {
ExportTableBtn,
},
setup(props) {
setup() {
// setup vuex
const store = useStore();
const selectedAgent = computed(() => store.state.selectedRow);
@@ -134,7 +164,7 @@ export default {
prompt: {
model: noteText,
type: "textarea",
isValid: val => !!val,
isValid: (val) => !!val,
},
style: "width: 30vw; max-width: 50vw;",
ok: { label: "Add" },
@@ -142,7 +172,10 @@ export default {
}).onOk(async () => {
loading.value = true;
try {
const result = await saveAgentNote({ agent_id: selectedAgent.value, note: noteText.value });
const result = await saveAgentNote({
agent_id: selectedAgent.value,
note: noteText.value,
});
notifySuccess(result);
await getNotes();
} catch (e) {
@@ -158,12 +191,12 @@ export default {
prompt: {
model: note.note,
type: "textarea",
isValid: val => !!val,
isValid: (val) => !!val,
},
style: "width: 30vw; max-width: 50vw;",
ok: { label: "Save" },
cancel: true,
}).onOk(async data => {
}).onOk(async (data) => {
loading.value = true;
try {
const result = await editAgentNote(note.pk, { note: data });
@@ -194,7 +227,7 @@ export default {
});
}
watch(selectedAgent, (newValue, oldValue) => {
watch(selectedAgent, (newValue) => {
if (newValue) {
getNotes();
}

View File

@@ -5,7 +5,10 @@
</div>
<div v-else>
<q-table
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
dense
:rows="software"
@@ -24,12 +27,34 @@
</template>
<template v-slot:top>
<q-btn class="q-mr-sm" dense flat push @click="refreshSoftware" icon="refresh" />
<q-btn icon="add" label="Install Software" no-caps dense flat push @click="showInstallSoftwareModal" />
<q-btn
class="q-mr-sm"
dense
flat
push
@click="refreshSoftware"
icon="refresh"
/>
<q-btn
icon="add"
label="Install Software"
no-caps
dense
flat
push
@click="showInstallSoftwareModal"
/>
<q-space />
<q-input v-model="filter" outlined label="Search" dense clearable class="q-pr-sm">
<q-input
v-model="filter"
outlined
label="Search"
dense
clearable
class="q-pr-sm"
>
<template v-slot:prepend>
<q-icon name="search" color="primary" />
</template>
@@ -73,7 +98,7 @@ const columns = [
label: "Installed On",
field: "install_date",
sortable: false,
format: (val, row) => {
format: (val) => {
return val === "01/01/1" || val === "01-1-01" ? "" : val;
},
},
@@ -98,7 +123,7 @@ export default {
components: {
ExportTableBtn,
},
setup(props) {
setup() {
// setup quasar
const $q = useQuasar();
@@ -140,7 +165,7 @@ export default {
});
}
watch(selectedAgent, (newValue, oldValue) => {
watch(selectedAgent, (newValue) => {
if (newValue) {
getSoftware();
}
@@ -170,4 +195,3 @@ export default {
},
};
</script>

View File

@@ -1,13 +1,27 @@
<template>
<div v-if="!selectedAgent" class="q-pa-sm">No agent selected</div>
<div v-else-if="!summary && loading" class="q-pa-md flex flex-center">
<q-circular-progress indeterminate size="50px" color="primary" class="q-ma-md" />
<q-circular-progress
indeterminate
size="50px"
color="primary"
class="q-ma-md"
/>
</div>
<div v-else-if="summary" class="q-pa-sm">
<q-bar dense style="background-color: transparent">
<q-btn dense flat size="md" class="q-mr-sm" icon="refresh" @click="refreshSummary" />
<q-btn
dense
flat
size="md"
class="q-mr-sm"
icon="refresh"
@click="refreshSummary"
/>
<b>{{ summary.hostname }}</b>
<span v-if="summary.maintenance_mode"> &bull; <q-badge color="green"> Maintenance Mode </q-badge> </span>
<span v-if="summary.maintenance_mode">
&bull; <q-badge color="green"> Maintenance Mode </q-badge>
</span>
&bull; {{ summary.operating_system }} &bull; Agent v{{ summary.version }}
<q-space />
<q-btn
@@ -92,19 +106,43 @@
<br />
<div v-if="summary.checks.total !== 0">
<q-chip v-if="summary.checks.passing" square size="lg">
<q-avatar size="lg" square icon="done" color="green" text-color="white" />
<q-avatar
size="lg"
square
icon="done"
color="green"
text-color="white"
/>
<small>{{ summary.checks.passing }} checks passing</small>
</q-chip>
<q-chip v-if="summary.checks.failing" square size="lg">
<q-avatar size="lg" square icon="cancel" color="red" text-color="white" />
<q-avatar
size="lg"
square
icon="cancel"
color="red"
text-color="white"
/>
<small>{{ summary.checks.failing }} checks failing</small>
</q-chip>
<q-chip v-if="summary.checks.warning" square size="lg">
<q-avatar size="lg" square icon="warning" color="warning" text-color="white" />
<q-avatar
size="lg"
square
icon="warning"
color="warning"
text-color="white"
/>
<small>{{ summary.checks.warning }} checks warning</small>
</q-chip>
<q-chip v-if="summary.checks.info" square size="lg">
<q-avatar size="lg" square icon="info" color="info" text-color="white" />
<q-avatar
size="lg"
square
icon="info"
color="info"
text-color="white"
/>
<small>{{ summary.checks.info }} checks info</small>
</q-chip>
<span
@@ -115,7 +153,8 @@
summary.checks.warning === 0 &&
summary.checks.info === 0
"
>{{ summary.checks.total }} checks awaiting first synchronization</span
>{{ summary.checks.total }} checks awaiting first
synchronization</span
>
</div>
<div v-else>No checks</div>
@@ -147,7 +186,12 @@
// composition imports
import { ref, computed, watch, onMounted } from "vue";
import { useStore } from "vuex";
import { fetchAgent, refreshAgentWMI, runTakeControl, openAgentWindow } from "@/api/agents";
import {
fetchAgent,
refreshAgentWMI,
runTakeControl,
openAgentWindow,
} from "@/api/agents";
import { notifySuccess } from "@/utils/notify";
// ui imports
@@ -158,7 +202,7 @@ export default {
components: {
AgentActionMenu,
},
setup(props) {
setup() {
// vuex setup
const store = useStore();
const selectedAgent = computed(() => store.state.selectedRow);
@@ -185,6 +229,7 @@ export default {
const entries = Object.entries(summary.value.disks);
const ret = [];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (let [k, v] of entries) {
ret.push(v);
}
@@ -210,13 +255,13 @@ export default {
loading.value = false;
}
watch(selectedAgent, (newValue, oldValue) => {
watch(selectedAgent, (newValue) => {
if (newValue) {
getSummary();
}
});
watch(refreshSummaryTab, (newValue, oldValue) => {
watch(refreshSummaryTab, (newValue) => {
if (newValue && selectedAgent.value) {
getSummary();
}
@@ -245,4 +290,3 @@ export default {
},
};
</script>

View File

@@ -6,7 +6,10 @@
<div v-else>
<q-table
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:style="{ 'max-height': tabHeight }"
:rows="updates"
@@ -21,12 +24,42 @@
no-data-label="No Windows Updates"
>
<template v-slot:top>
<q-btn dense flat push @click="getUpdates" icon="refresh" class="q-mr-sm" />
<q-btn label="Run Update Scan" dense flat push no-caps @click="updateScan" class="q-mr-sm" />
<q-btn label="Install Approved Updates" dense flat push no-caps @click="installUpdates" class="q-mr-sm" />
<q-btn
dense
flat
push
@click="getUpdates"
icon="refresh"
class="q-mr-sm"
/>
<q-btn
label="Run Update Scan"
dense
flat
push
no-caps
@click="updateScan"
class="q-mr-sm"
/>
<q-btn
label="Install Approved Updates"
dense
flat
push
no-caps
@click="installUpdates"
class="q-mr-sm"
/>
<q-space />
<q-input v-model="filter" outlined label="Search" dense clearable class="q-pr-sm">
<q-input
v-model="filter"
outlined
label="Search"
dense
clearable
class="q-pr-sm"
>
<template v-slot:prepend>
<q-icon name="search" color="primary" />
</template>
@@ -78,27 +111,55 @@
</q-menu>
<!-- policy -->
<q-td>
<q-icon v-if="props.row.action === 'nothing'" name="fiber_manual_record" color="grey">
<q-icon
v-if="props.row.action === 'nothing'"
name="fiber_manual_record"
color="grey"
>
<q-tooltip>Do Nothing</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.action === 'approve'" name="fas fa-check" color="primary">
<q-icon
v-else-if="props.row.action === 'approve'"
name="fas fa-check"
color="primary"
>
<q-tooltip>Approve</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.action === 'ignore'" name="fas fa-check" color="negative">
<q-icon
v-else-if="props.row.action === 'ignore'"
name="fas fa-check"
color="negative"
>
<q-tooltip>Ignore</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.action === 'inherit'" name="fiber_manual_record" color="accent">
<q-icon
v-else-if="props.row.action === 'inherit'"
name="fiber_manual_record"
color="accent"
>
<q-tooltip>Inherit</q-tooltip>
</q-icon>
</q-td>
<q-td>
<q-icon v-if="props.row.installed" name="fas fa-check" color="positive">
<q-icon
v-if="props.row.installed"
name="fas fa-check"
color="positive"
>
<q-tooltip>Installed</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.action == 'approve'" name="fas fa-tasks" color="primary">
<q-icon
v-else-if="props.row.action == 'approve'"
name="fas fa-tasks"
color="primary"
>
<q-tooltip>Pending</q-tooltip>
</q-icon>
<q-icon v-else-if="props.row.action == 'ignore'" name="fas fa-ban" color="negative">
<q-icon
v-else-if="props.row.action == 'ignore'"
name="fas fa-ban"
color="negative"
>
<q-tooltip>Ignored</q-tooltip>
</q-icon>
<q-icon v-else name="fas fa-exclamation" color="warning">
@@ -108,9 +169,11 @@
<q-td>{{ !props.row.severity ? "Other" : props.row.severity }}</q-td>
<q-td>{{ truncateText(props.row.title, 50) }}</q-td>
<q-td @click="showUpdateDetails(props.row)">
<span style="cursor: pointer; text-decoration: underline" class="text-primary">{{
truncateText(props.row.description, 50)
}}</span>
<span
style="cursor: pointer; text-decoration: underline"
class="text-primary"
>{{ truncateText(props.row.description, 50) }}</span
>
</q-td>
<q-td>{{ formatDate(props.row.date_installed) }}</q-td>
</q-tr>
@@ -124,7 +187,12 @@
import { ref, computed, watch, inject, onMounted } from "vue";
import { useStore } from "vuex";
import { useQuasar } from "quasar";
import { fetchAgentUpdates, editAgentUpdate, runAgentUpdateScan, runAgentUpdateInstall } from "@/api/winupdates";
import {
fetchAgentUpdates,
editAgentUpdate,
runAgentUpdateScan,
runAgentUpdateInstall,
} from "@/api/winupdates";
import { notifySuccess } from "@/utils/notify";
import { truncateText } from "@/utils/format";
@@ -176,7 +244,7 @@ const columns = [
export default {
name: "WindowsUpdates",
components: { ExportTableBtn },
setup(props) {
setup() {
// setup vuex
const store = useStore();
const selectedAgent = computed(() => store.state.selectedRow);
@@ -243,7 +311,7 @@ export default {
function showUpdateDetails(update) {
let support_urls = "";
update.more_info_urls.forEach(u => {
update.more_info_urls.forEach((u) => {
support_urls += `<a href='${u}' target='_blank'>${u}</a><br/>`;
});
let cats = update.categories.join(", ");
@@ -259,7 +327,7 @@ export default {
});
}
watch(selectedAgent, (newValue, oldValue) => {
watch(selectedAgent, (newValue) => {
if (newValue) {
getUpdates();
}

View File

@@ -4,7 +4,9 @@
<div v-for="j in i" :key="j + uid()">
<div v-for="(v, k) in j" :key="v + uid()">
<span class="text-overline">{{ k }}:</span>
<q-badge color="primary" class="q-ml-sm text-caption">{{ v }}</q-badge>
<q-badge color="primary" class="q-ml-sm text-caption">{{
v
}}</q-badge>
</div>
</div>
<q-separator v-if="info.length > 1" />
@@ -21,7 +23,7 @@ import { uid } from "quasar";
export default {
name: "WmiDetail",
props: { info: !Object },
setup(props) {
setup() {
// setup vuex
const store = useStore();
const tabHeight = computed(() => store.state.tabHeight);
@@ -33,4 +35,3 @@ export default {
},
};
</script>

View File

@@ -5,16 +5,28 @@
<div v-else>
<div class="row q-pt-sm q-pl-sm">
<div class="col-2">
<q-select dense options-dense outlined v-model="days" :options="lastDaysOptions" :label="showDays" />
<q-select
dense
options-dense
outlined
v-model="days"
:options="lastDaysOptions"
:label="showDays"
/>
</div>
<div class="col-7"></div>
<div class="col-3">
<code v-if="events">{{ logType }} log total records: {{ events.length }}</code>
<code v-if="events"
>{{ logType }} log total records: {{ events.length }}</code
>
</div>
</div>
<q-table
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="remote-bg-tbl-sticky"
:rows="events"
:columns="columns"
@@ -38,15 +50,32 @@
@update:model-value="getEventLog"
/>
<q-radio v-model="logType" color="cyan" val="System" label="System" />
<q-radio v-model="logType" color="cyan" val="Security" label="Security" />
<q-radio
v-model="logType"
color="cyan"
val="Security"
label="Security"
/>
<q-space />
<q-input v-model="filter" style="width: 300px" outlined label="Search" dense clearable>
<q-input
v-model="filter"
style="width: 300px"
outlined
label="Search"
dense
clearable
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
<!-- file download doesn't work so disabling -->
<export-table-btn v-show="false" class="q-ml-sm" :columns="columns" :data="events" />
<export-table-btn
v-show="false"
class="q-ml-sm"
:columns="columns"
:data="events"
/>
</template>
<template v-slot:body="props">
<q-tr :props="props">
@@ -55,9 +84,11 @@
<q-td>{{ props.row.eventID }}</q-td>
<q-td>{{ props.row.time }}</q-td>
<q-td @click="showEventMessage(props.row.message)">
<span style="cursor: pointer; text-decoration: underline" class="text-primary">{{
truncateText(props.row.message, 30)
}}</span>
<span
style="cursor: pointer; text-decoration: underline"
class="text-primary"
>{{ truncateText(props.row.message, 30) }}</span
>
</q-td>
</q-tr>
</template>
@@ -77,11 +108,35 @@ import ExportTableBtn from "@/components/ui/ExportTableBtn";
// static data
const columns = [
{ name: "eventType", label: "Type", field: "eventType", align: "left", sortable: true },
{ name: "source", label: "Source", field: "source", align: "left", sortable: true },
{ name: "eventID", label: "Event ID", field: "eventID", align: "left", sortable: true },
{
name: "eventType",
label: "Type",
field: "eventType",
align: "left",
sortable: true,
},
{
name: "source",
label: "Source",
field: "source",
align: "left",
sortable: true,
},
{
name: "eventID",
label: "Event ID",
field: "eventID",
align: "left",
sortable: true,
},
{ name: "time", label: "Time", field: "time", align: "left", sortable: true },
{ name: "message", label: "Message (click to view full)", field: "message", align: "left", sortable: true },
{
name: "message",
label: "Message (click to view full)",
field: "message",
align: "left",
sortable: true,
},
];
const lastDaysOptions = [1, 2, 3, 4, 5, 10, 30, 60, 90, 180, 360, 9999];
@@ -112,7 +167,11 @@ export default {
async function getEventLog() {
loading.value = true;
events.value = await fetchAgentEventLog(props.agent_id, logType.value, days.value);
events.value = await fetchAgentEventLog(
props.agent_id,
logType.value,
days.value
);
loading.value = false;
}

View File

@@ -1,7 +1,10 @@
<template>
<q-table
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="remote-bg-tbl-sticky"
:style="{ 'max-height': `${$q.screen.height - 36}px` }"
:rows="processes"
@@ -14,8 +17,24 @@
:loading="loading"
>
<template v-slot:top>
<q-btn v-if="isPolling" dense flat push @click="stopPoll" icon="stop" label="Stop Live Refresh" />
<q-btn v-else dense flat push @click="startPoll" icon="play_arrow" label="Resume Live Refresh" />
<q-btn
v-if="isPolling"
dense
flat
push
@click="stopPoll"
icon="stop"
label="Stop Live Refresh"
/>
<q-btn
v-else
dense
flat
push
@click="startPoll"
icon="play_arrow"
label="Resume Live Refresh"
/>
<q-space />
@@ -29,10 +48,23 @@
size="sm"
color="grey"
/>
<q-btn dense push icon="add" size="sm" color="grey" @click="pollIntervalChanged('add')" />
<q-btn
dense
push
icon="add"
size="sm"
color="grey"
@click="pollIntervalChanged('add')"
/>
</div>
<div class="text-overline">
<q-badge align="middle" size="sm" class="text-h6" color="blue" :label="pollInterval" />
<q-badge
align="middle"
size="sm"
class="text-h6"
color="blue"
:label="pollInterval"
/>
Refresh interval (seconds)
</div>
@@ -43,13 +75,21 @@
</template>
</q-input>
<!-- file download doesn't work so disabling -->
<export-table-btn v-show="false" class="q-ml-sm" :columns="columns" :data="processes" />
<export-table-btn
v-show="false"
class="q-ml-sm"
:columns="columns"
:data="processes"
/>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer">
<q-menu context-menu auto-close>
<q-list dense style="min-width: 200px">
<q-item clickable @click="killProcess(props.row.pid, props.row.name)">
<q-item
clickable
@click="killProcess(props.row.pid, props.row.name)"
>
<q-item-section side>
<q-icon name="fas fa-trash-alt" size="xs" />
</q-item-section>
@@ -73,7 +113,11 @@
<script>
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
import { fetchAgent, fetchAgentProcesses, killAgentProcess } from "@/api/agents";
import {
fetchAgent,
fetchAgentProcesses,
killAgentProcess,
} from "@/api/agents";
import { bytes2Human } from "@/utils/format";
import { notifySuccess } from "@/utils/notify";
@@ -94,7 +138,7 @@ const columns = [
field: "cpu_percent",
align: "left",
sortable: true,
sort: (a, b, rowA, rowB) => parseFloat(b) < parseFloat(a),
sort: (a, b) => parseFloat(b) < parseFloat(a),
},
{
name: "membytes",
@@ -177,7 +221,7 @@ export default {
}, pollInterval.value * 1000);
}
async function killProcess(pid, name) {
async function killProcess(pid) {
loading.value = true;
let result = "";
try {

View File

@@ -23,7 +23,9 @@
<div class="row">
<div class="col-3">Description:</div>
<div class="col-9">
<q-field outlined :color="$q.dark.isActive ? 'white' : 'black'">{{ service.description }}</q-field>
<q-field outlined :color="$q.dark.isActive ? 'white' : 'black'">{{
service.description
}}</q-field>
</div>
</div>
<br />
@@ -61,7 +63,10 @@
<q-btn-group color="primary" push>
<q-btn label="Start" @click="sendServiceAction(service, 'start')" />
<q-btn label="Stop" @click="sendServiceAction(service, 'stop')" />
<q-btn label="Restart" @click="sendServiceAction(service, 'restart')" />
<q-btn
label="Restart"
@click="sendServiceAction(service, 'restart')"
/>
</q-btn-group>
</div>
</q-card-section>
@@ -86,7 +91,10 @@
// composition imports
import { ref, computed, onMounted } from "vue";
import { useDialogPluginComponent } from "quasar";
import { editAgentServiceStartType, sendAgentServiceAction } from "@/api/services";
import {
editAgentServiceStartType,
sendAgentServiceAction,
} from "@/api/services";
import { notifySuccess } from "@/utils/notify";
// static data
@@ -124,7 +132,10 @@ export default {
const loading = ref(false);
const startupTypeEdited = computed(() => {
if (props.service.start_type.toLowerCase() === "automatic" && props.service.autodelay)
if (
props.service.start_type.toLowerCase() === "automatic" &&
props.service.autodelay
)
return startupType.value !== "autodelay";
else return props.service.start_type.toLowerCase() !== startupType.value;
});
@@ -132,7 +143,11 @@ export default {
async function sendServiceAction(service, action) {
loading.value = true;
try {
const result = await sendAgentServiceAction(props.agent_id, service.name, { sv_action: action });
const result = await sendAgentServiceAction(
props.agent_id,
service.name,
{ sv_action: action }
);
notifySuccess(result);
onDialogOK();
} catch (e) {
@@ -143,12 +158,17 @@ export default {
async function editServiceStartup() {
const data = {
startType: startupType.value === "automatic" ? "auto" : startupType.value,
startType:
startupType.value === "automatic" ? "auto" : startupType.value,
};
loading.value = true;
try {
const result = await editAgentServiceStartType(props.agent_id, props.service.name, data);
const result = await editAgentServiceStartType(
props.agent_id,
props.service.name,
data
);
notifySuccess(result);
onDialogOK();
} catch (e) {
@@ -158,7 +178,10 @@ export default {
}
onMounted(() => {
if (props.service.start_type.toLowerCase() === "automatic" && props.service.autodelay)
if (
props.service.start_type.toLowerCase() === "automatic" &&
props.service.autodelay
)
startupType.value = "autodelay";
else startupType.value = props.service.start_type.toLowerCase();
});

View File

@@ -5,7 +5,10 @@
<q-table
v-else
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="remote-bg-tbl-sticky"
:style="{ 'max-height': `${$q.screen.height - 36}px` }"
:rows="services"
@@ -26,10 +29,19 @@
</template>
</q-input>
<!-- file download doesn't work so disabling -->
<export-table-btn v-show="false" class="q-ml-sm" :columns="columns" :data="services" />
<export-table-btn
v-show="false"
class="q-ml-sm"
:columns="columns"
:data="services"
/>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="showServiceDetail(props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="showServiceDetail(props.row)"
>
<q-menu context-menu auto-close>
<q-list dense style="min-width: 200px">
<q-item clickable @click="sendServiceAction(props.row, 'start')">
@@ -57,13 +69,18 @@
</q-td>
<q-td key="name" :props="props">{{ props.row.name }}</q-td>
<q-td key="start_type" :props="props">{{
props.row.start_type.toLowerCase() === "automatic" && props.row.autodelay
props.row.start_type.toLowerCase() === "automatic" &&
props.row.autodelay
? `${props.row.start_type} (Delayed)`
: `${props.row.start_type}`
}}</q-td>
<q-td key="pid" :props="props">{{ props.row.pid === 0 ? "" : props.row.pid }}</q-td>
<q-td key="pid" :props="props">{{
props.row.pid === 0 ? "" : props.row.pid
}}</q-td>
<q-td key="status" :props="props">{{ props.row.status }}</q-td>
<q-td key="username" :props="props">{{ props.row.username ? props.row.username : "LocalSystem" }}</q-td>
<q-td key="username" :props="props">{{
props.row.username ? props.row.username : "LocalSystem"
}}</q-td>
</q-tr>
</template>
</q-table>
@@ -185,7 +202,11 @@ export default {
loading.value = true;
try {
const result = await sendAgentServiceAction(props.agent_id, service.name, { sv_action: action });
const result = await sendAgentServiceAction(
props.agent_id,
service.name,
{ sv_action: action }
);
notifySuccess(result);
await getServices();
} catch (e) {

View File

@@ -3,7 +3,15 @@
<div class="q-dialog-plugin" style="width: 90vw; max-width: 90vw">
<q-card>
<q-bar>
<q-btn ref="refresh" @click="refresh" class="q-mr-sm" dense flat push icon="refresh" />Automation Manager
<q-btn
ref="refresh"
@click="refresh"
class="q-mr-sm"
dense
flat
push
icon="refresh"
/>Automation Manager
<q-space />
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
@@ -11,7 +19,16 @@
</q-bar>
<q-card-section>
<div class="q-gutter-sm">
<q-btn label="New" dense flat push unelevated no-caps icon="add" @click="showAddPolicyForm" />
<q-btn
label="New"
dense
flat
push
unelevated
no-caps
icon="add"
@click="showAddPolicyForm"
/>
<q-btn
label="Policy Overview"
dense
@@ -25,7 +42,10 @@
</div>
<div class="scroll" style="min-height: 35vh; max-height: 35vh">
<q-table
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:rows="policies"
:columns="columns"
@@ -50,7 +70,10 @@
<template v-slot:header-cell-enforced="props">
<q-th :props="props" auto-width>
<q-icon name="security" size="1.5em">
<q-tooltip>Enforce Policy (Will override Agent tasks/checks)</q-tooltip>
<q-tooltip
>Enforce Policy (Will override Agent
tasks/checks)</q-tooltip
>
</q-icon>
</q-th>
</template>
@@ -68,21 +91,33 @@
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="showEditPolicyForm(props.row)">
<q-item
clickable
v-close-popup
@click="showEditPolicyForm(props.row)"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showCopyPolicyForm(props.row)">
<q-item
clickable
v-close-popup
@click="showCopyPolicyForm(props.row)"
>
<q-item-section side>
<q-icon name="content_copy" />
</q-item-section>
<q-item-section>Copy</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="deletePolicy(props.row)">
<q-item
clickable
v-close-popup
@click="deletePolicy(props.row)"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
@@ -91,32 +126,52 @@
<q-separator></q-separator>
<q-item clickable v-close-popup @click="showRelations(props.row)">
<q-item
clickable
v-close-popup
@click="showRelations(props.row)"
>
<q-item-section side>
<q-icon name="account_tree" />
</q-item-section>
<q-item-section>Show Relations</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showPolicyExclusions(props.row)">
<q-item
clickable
v-close-popup
@click="showPolicyExclusions(props.row)"
>
<q-item-section side>
<q-icon name="rule" />
</q-item-section>
<q-item-section>Policy Exclusions</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showPatchPolicyForm(props.row)">
<q-item
clickable
v-close-popup
@click="showPatchPolicyForm(props.row)"
>
<q-item-section side>
<q-icon name="system_update" />
</q-item-section>
<q-item-section>{{ patchPolicyText(props.row) }}</q-item-section>
<q-item-section>{{
patchPolicyText(props.row)
}}</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showAlertTemplateAdd(props.row)">
<q-item
clickable
v-close-popup
@click="showAlertTemplateAdd(props.row)"
>
<q-item-section side>
<q-icon name="warning" />
</q-item-section>
<q-item-section>{{ alertTemplateText(props.row) }}</q-item-section>
<q-item-section>{{
alertTemplateText(props.row)
}}</q-item-section>
</q-item>
<q-separator></q-separator>
@@ -138,16 +193,26 @@
<q-td>
<q-checkbox
dense
@update:model-value="toggleCheckbox(props.row, 'Enforced')"
@update:model-value="
toggleCheckbox(props.row, 'Enforced')
"
v-model="props.row.enforced"
/>
</q-td>
<q-td>
{{ props.row.name }}
<q-chip v-if="props.row.default_server_policy" color="primary" text-color="white" size="sm"
<q-chip
v-if="props.row.default_server_policy"
color="primary"
text-color="white"
size="sm"
>Default Server</q-chip
>
<q-chip v-if="props.row.default_workstation_policy" color="primary" text-color="white" size="sm"
<q-chip
v-if="props.row.default_workstation_policy"
color="primary"
text-color="white"
size="sm"
>Default Workstation</q-chip
>
</q-td>
@@ -191,7 +256,11 @@
>
</q-td>
<q-td>
<q-icon name="content_copy" size="1.5em" @click="showCopyPolicyForm(props.row)">
<q-icon
name="content_copy"
size="1.5em"
@click="showCopyPolicyForm(props.row)"
>
<q-tooltip>Create a copy of this policy</q-tooltip>
</q-icon>
</q-td>
@@ -220,12 +289,18 @@
<q-tab-panels v-model="subtab" :animated="false">
<q-tab-panel name="checks">
<div class="scroll" style="min-height: 25vh; max-height: 25vh">
<PolicyChecksTab v-if="!!selectedPolicy" :selectedPolicy="selectedPolicy.id" />
<PolicyChecksTab
v-if="!!selectedPolicy"
:selectedPolicy="selectedPolicy.id"
/>
</div>
</q-tab-panel>
<q-tab-panel name="tasks">
<div class="scroll" style="min-height: 25vh; max-height: 25vh">
<PolicyAutomatedTasksTab v-if="!!selectedPolicy" :selectedPolicy="selectedPolicy.id" />
<PolicyAutomatedTasksTab
v-if="!!selectedPolicy"
:selectedPolicy="selectedPolicy.id"
/>
</div>
</q-tab-panel>
</q-tab-panels>
@@ -259,7 +334,12 @@ export default {
selectedPolicy: null,
columns: [
{ name: "active", label: "Active", field: "active", align: "left" },
{ name: "enforced", label: "Enforced", field: "enforced", align: "left" },
{
name: "enforced",
label: "Enforced",
field: "enforced",
align: "left",
},
{
name: "name",
label: "Name",
@@ -315,11 +395,11 @@ export default {
this.$q.loading.show();
this.$axios
.get("/automation/policies/")
.then(r => {
.then((r) => {
this.policies = r.data;
this.$q.loading.hide();
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -341,13 +421,13 @@ export default {
this.$q.loading.show();
this.$axios
.delete(`/automation/policies/${policy.id}/`)
.then(r => {
.then(() => {
this.refresh();
this.$q.loading.hide();
this.notifySuccess("Policy was deleted!");
this.$store.dispatch("loadTree");
})
.catch(error => {
.catch(() => {
this.$q.loading.hide();
});
});
@@ -365,7 +445,7 @@ export default {
component: PolicyOverview,
});
},
showAddPolicyForm(policy = undefined) {
showAddPolicyForm() {
this.$q
.dialog({
component: PolicyForm,
@@ -374,7 +454,7 @@ export default {
this.refresh();
});
},
showCopyPolicyForm(policy = undefined) {
showCopyPolicyForm(policy) {
this.$q
.dialog({
component: PolicyForm,
@@ -416,7 +496,10 @@ export default {
.dialog({
component: DialogWrapper,
componentProps: {
title: policy.winupdatepolicy.length > 0 ? "Edit Patch Policy" : "Add Patch Policy",
title:
policy.winupdatepolicy.length > 0
? "Edit Patch Policy"
: "Add Patch Policy",
vuecomponent: PatchPolicyForm,
componentProps: {
policy: policy,
@@ -450,32 +533,41 @@ export default {
};
if (type === "Active") {
text = !policy.active ? "Policy enabled successfully" : "Policy disabled successfully";
text = !policy.active
? "Policy enabled successfully"
: "Policy disabled successfully";
data["active"] = !policy.active;
} else if (type === "Enforced") {
text = !policy.enforced ? "Policy enforced successfully" : "Policy enforcement disabled";
text = !policy.enforced
? "Policy enforced successfully"
: "Policy enforcement disabled";
data["enforced"] = !policy.enforced;
}
this.$axios
.put(`/automation/policies/${data.id}/`, data)
.then(r => {
.then(() => {
this.refresh();
this.$q.loading.hide();
this.notifySuccess(text);
})
.catch(error => {
.catch(() => {
this.$q.loading.hide();
});
},
patchPolicyText(policy) {
return policy.winupdatepolicy.length > 0 ? "Modify Patch Policy" : "Create Patch Policy";
return policy.winupdatepolicy.length > 0
? "Modify Patch Policy"
: "Create Patch Policy";
},
alertTemplateText(policy) {
return policy.alert_template ? "Modify Alert Template" : "Assign Alert Template";
return policy.alert_template
? "Modify Alert Template"
: "Assign Alert Template";
},
rowSelectedClass(id, selectedPolicy) {
if (selectedPolicy && selectedPolicy.id === id) return this.$q.dark.isActive ? "highlight-dark" : "highlight";
if (selectedPolicy && selectedPolicy.id === id)
return this.$q.dark.isActive ? "highlight-dark" : "highlight";
},
show() {
this.$refs.dialog.show();

View File

@@ -1,10 +1,30 @@
<template>
<div class="row">
<div class="col-12">
<q-btn v-if="!!selectedPolicy" class="q-mr-sm" dense flat push @click="getTasks" icon="refresh" />
<q-btn v-if="!!selectedPolicy" icon="add" label="Add Task" no-caps dense flat push @click="showAddTask" />
<q-btn
v-if="!!selectedPolicy"
class="q-mr-sm"
dense
flat
push
@click="getTasks"
icon="refresh"
/>
<q-btn
v-if="!!selectedPolicy"
icon="add"
label="Add Task"
no-caps
dense
flat
push
@click="showAddTask"
/>
<q-table
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:rows="tasks"
:columns="columns"
@@ -19,7 +39,9 @@
<!-- No data Slot -->
<template v-slot:no-data>
<div class="full-width row flex-center q-gutter-sm">
<span v-if="!selectedPolicy">Click on a policy to see the tasks</span>
<span v-if="!selectedPolicy"
>Click on a policy to see the tasks</span
>
<span v-else>There are no tasks added to this policy</span>
</div>
</template>
@@ -62,7 +84,7 @@
</template>
<!-- body slots -->
<template v-slot:body="props" :props="props">
<template v-slot:body="props">
<q-tr class="cursor-pointer" @dblclick="showEditTask(props.row)">
<!-- context menu -->
<q-menu context-menu>
@@ -73,7 +95,11 @@
</q-item-section>
<q-item-section>Run task now</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showEditTask(props.row)">
<q-item
clickable
v-close-popup
@click="showEditTask(props.row)"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
@@ -102,7 +128,9 @@
<q-td>
<q-checkbox
dense
@update:model-value="editTask(props.row, { enabled: !props.row.enabled })"
@update:model-value="
editTask(props.row, { enabled: !props.row.enabled })
"
v-model="props.row.enabled"
/>
</q-td>
@@ -110,7 +138,9 @@
<q-td>
<q-checkbox
dense
@update:model-value="editTask(props.row, { text_alert: !props.row.text_alert })"
@update:model-value="
editTask(props.row, { text_alert: !props.row.text_alert })
"
v-model="props.row.text_alert"
/>
</q-td>
@@ -118,7 +148,9 @@
<q-td>
<q-checkbox
dense
@update:model-value="editTask(props.row, { email_alert: !props.row.email_alert })"
@update:model-value="
editTask(props.row, { email_alert: !props.row.email_alert })
"
v-model="props.row.email_alert"
/>
</q-td>
@@ -126,14 +158,24 @@
<q-td>
<q-checkbox
dense
@update:model-value="editTask(props.row, { dashboard_alert: !props.row.dashboard_alert })"
@update:model-value="
editTask(props.row, {
dashboard_alert: !props.row.dashboard_alert,
})
"
v-model="props.row.dashboard_alert"
/>
</q-td>
<!-- is collector task -->
<q-td>
<q-icon v-if="!!props.row.custom_field" style="font-size: 1.3rem" name="check">
<q-tooltip>The task updates a custom field on the agent</q-tooltip>
<q-icon
v-if="!!props.row.custom_field"
style="font-size: 1.3rem"
name="check"
>
<q-tooltip
>The task updates a custom field on the agent</q-tooltip
>
</q-icon>
</q-td>
<q-td>{{ props.row.name }}</q-td>
@@ -173,8 +215,20 @@ export default {
{ name: "smsalert", field: "text_alert", align: "left" },
{ name: "emailalert", field: "email_alert", align: "left" },
{ name: "dashboardalert", field: "dashboard_alert", align: "left" },
{ name: "collector", label: "Collector", field: "custom_field", align: "left", sortable: true },
{ name: "name", label: "Name", field: "name", align: "left", sortable: true },
{
name: "collector",
label: "Collector",
field: "custom_field",
align: "left",
sortable: true,
},
{
name: "name",
label: "Name",
field: "name",
align: "left",
sortable: true,
},
{
name: "schedule",
label: "Schedule",
@@ -214,23 +268,23 @@ export default {
this.$q.loading.show();
this.$axios
.get(`/automation/policies/${this.selectedPolicy}/tasks/`)
.then(r => {
.then((r) => {
this.tasks = r.data;
this.$q.loading.hide();
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
editTask(task, data) {
this.$axios
.put(`/tasks/${task.id}/`, data)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.notifySuccess(r.data);
this.getTasks();
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -266,18 +320,20 @@ export default {
},
runTask(task) {
if (!task.enabled) {
this.notifyError("Task cannot be run when it's disabled. Enable it first.");
this.notifyError(
"Task cannot be run when it's disabled. Enable it first."
);
return;
}
this.$q.loading.show();
this.$axios
.post(`/automation/tasks/${task.id}/run/`)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.notifySuccess("The task was initated on all affected agents");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -293,12 +349,12 @@ export default {
this.$q.loading.show();
this.$axios
.delete(`/tasks/${task.id}/`)
.then(r => {
.then(() => {
this.getTasks();
this.$q.loading.hide();
this.notifySuccess("Task was deleted successfully");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
});
@@ -309,4 +365,3 @@ export default {
},
};
</script>

View File

@@ -1,8 +1,23 @@
<template>
<div class="row">
<div class="col-12">
<q-btn v-if="!!selectedPolicy" class="q-mr-sm" dense flat push @click="getChecks" icon="refresh" />
<q-btn-dropdown v-if="!!selectedPolicy" icon="add" label="New" no-caps dense flat>
<q-btn
v-if="!!selectedPolicy"
class="q-mr-sm"
dense
flat
push
@click="getChecks"
icon="refresh"
/>
<q-btn-dropdown
v-if="!!selectedPolicy"
icon="add"
label="New"
no-caps
dense
flat
>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="showCheckModal('diskspace')">
<q-item-section side>
@@ -50,7 +65,10 @@
</q-btn-dropdown>
<q-table
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:rows="checks"
:columns="columns"
@@ -65,7 +83,9 @@
<!-- No data Slot -->
<template v-slot:no-data>
<div class="full-width row flex-center q-gutter-sm">
<span v-if="!selectedPolicy">Click on a policy to see the checks</span>
<span v-if="!selectedPolicy"
>Click on a policy to see the checks</span
>
<span v-else>There are no checks added to this policy</span>
</div>
</template>
@@ -96,11 +116,19 @@
</template>
<!-- body slots -->
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="showCheckModal(props.row.check_type, props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="showCheckModal(props.row.check_type, props.row)"
>
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="showCheckModal(props.row.check_type, props.row)">
<q-item
clickable
v-close-popup
@click="showCheckModal(props.row.check_type, props.row)"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
@@ -115,7 +143,11 @@
<q-separator></q-separator>
<q-item clickable v-close-popup @click="showPolicyStatus(props.row)">
<q-item
clickable
v-close-popup
@click="showPolicyStatus(props.row)"
>
<q-item-section side>
<q-icon name="sync" />
</q-item-section>
@@ -133,21 +165,31 @@
<q-td>
<q-checkbox
dense
@update:model-value="checkAlert(props.row.id, 'Text', props.row.text_alert)"
@update:model-value="
checkAlert(props.row.id, 'Text', props.row.text_alert)
"
v-model="props.row.text_alert"
/>
</q-td>
<q-td>
<q-checkbox
dense
@update:model-value="checkAlert(props.row.id, 'Email', props.row.email_alert)"
@update:model-value="
checkAlert(props.row.id, 'Email', props.row.email_alert)
"
v-model="props.row.email_alert"
/>
</q-td>
<q-td>
<q-checkbox
dense
@update:model-value="checkAlert(props.row.id, 'Dashboard', props.row.dashboard_alert)"
@update:model-value="
checkAlert(
props.row.id,
'Dashboard',
props.row.dashboard_alert
)
"
v-model="props.row.dashboard_alert"
/>
</q-td>
@@ -160,8 +202,12 @@
>See Status</span
>
</q-td>
<q-td v-if="props.row.assignedtasks.length > 1">{{ props.row.assignedtasks.length }} Tasks</q-td>
<q-td v-else-if="props.row.assignedtasks.length === 1">{{ props.row.assignedtasks[0].name }}</q-td>
<q-td v-if="props.row.assignedtasks.length > 1"
>{{ props.row.assignedtasks.length }} Tasks</q-td
>
<q-td v-else-if="props.row.assignedtasks.length === 1">{{
props.row.assignedtasks[0].name
}}</q-td>
<q-td v-else></q-td>
</q-tr>
</template>
@@ -194,9 +240,21 @@ export default {
{ name: "smsalert", field: "text_alert", align: "left" },
{ name: "emailalert", field: "email_alert", align: "left" },
{ name: "dashboardalert", field: "dashboard_alert", align: "left" },
{ name: "desc", field: "readable_desc", label: "Description", align: "left", sortable: true },
{
name: "desc",
field: "readable_desc",
label: "Description",
align: "left",
sortable: true,
},
{ name: "status", label: "Status", field: "status", align: "left" },
{ name: "assigned_task", label: "Assigned Tasks", field: "assigned_task", align: "left", sortable: true },
{
name: "assigned_task",
label: "Assigned Tasks",
field: "assigned_task",
align: "left",
sortable: true,
},
],
pagination: {
rowsPerPage: 0,
@@ -215,11 +273,11 @@ export default {
this.$q.loading.show();
this.$axios
.get(`/automation/policies/${this.selectedPolicy}/checks/`)
.then(r => {
.then((r) => {
this.checks = r.data;
this.$q.loading.hide();
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -240,7 +298,7 @@ export default {
const color = !action ? "positive" : "warning";
this.$axios
.put(`/checks/${id}/`, data)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.$q.notify({
color: color,
@@ -248,7 +306,7 @@ export default {
message: `${alert_type} alerts ${act}`,
});
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -263,12 +321,12 @@ export default {
this.$q.loading.show();
this.$axios
.delete(`/checks/${check.id}/`)
.then(r => {
.then(() => {
this.getChecks();
this.$q.loading.hide();
this.notifySuccess("Check Deleted!");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
});
@@ -310,4 +368,3 @@ export default {
},
};
</script>

View File

@@ -2,7 +2,14 @@
<q-dialog ref="dialog" @hide="onHide">
<q-card class="q-dialog-plugin" style="width: 90vw; max-width: 90vw">
<q-bar>
<q-btn @click="getPolicyTree" class="q-mr-sm" dense flat push icon="refresh" />Policy Overview
<q-btn
@click="getPolicyTree"
class="q-mr-sm"
dense
flat
push
icon="refresh"
/>Policy Overview
<q-space />
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
@@ -36,7 +43,12 @@
<q-tab name="checks" icon="fas fa-check-double" label="Checks" />
<q-tab name="tasks" icon="fas fa-tasks" label="Tasks" />
</q-tabs>
<q-tab-panels v-model="selectedTab" animated transition-prev="jump-up" transition-next="jump-up">
<q-tab-panels
v-model="selectedTab"
animated
transition-prev="jump-up"
transition-next="jump-up"
>
<q-tab-panel name="checks">
<PolicyChecksTab
v-if="!!selectedPolicyId"
@@ -82,11 +94,11 @@ export default {
this.$q.loading.show();
this.$axios
.get("/automation/policies/overview/")
.then(r => {
.then((r) => {
this.processTreeDataFromApi(r.data);
this.$q.loading.hide();
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -152,7 +164,8 @@ export default {
disabled = " (disabled)";
}
const label = client.workstation_policy.name + " (Workstations)" + disabled;
const label =
client.workstation_policy.name + " (Workstations)" + disabled;
client_temp["children"].push({
label: label,
icon: "policy",
@@ -201,7 +214,8 @@ export default {
disabled = " (disabled)";
}
const label = site.workstation_policy.name + " (Workstations)" + disabled;
const label =
site.workstation_policy.name + " (Workstations)" + disabled;
site_temp["children"].push({
label: label,
icon: "policy",

View File

@@ -42,16 +42,29 @@
filterable
/>
<q-checkbox label="Block policy inheritance" v-model="blockInheritance">
<q-tooltip>This {{ type }} will not inherit from higher policies</q-tooltip>
<q-checkbox
label="Block policy inheritance"
v-model="blockInheritance"
>
<q-tooltip
>This {{ type }} will not inherit from higher policies</q-tooltip
>
</q-checkbox>
</q-card-section>
<q-card-section v-else>
No Automation Policies have been setup. Go to Settings > Automation Manager
No Automation Policies have been setup. Go to Settings > Automation
Manager
</q-card-section>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn v-if="options.length > 0" dense flat label="Submit" color="primary" type="submit" />
<q-btn
v-if="options.length > 0"
dense
flat
label="Submit"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -142,12 +155,12 @@ export default {
this.$axios
.put(url, data)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Policies Updated Successfully!");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -155,15 +168,15 @@ export default {
this.$q.loading.show();
this.$axios
.get("/automation/policies/")
.then(r => {
this.options = r.data.map(policy => ({
.then((r) => {
this.options = r.data.map((policy) => ({
label: policy.name,
value: policy.id,
}));
this.$q.loading.hide();
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},

View File

@@ -77,12 +77,12 @@ export default {
onSubmit() {
this.$axios
.put(`automation/policies/${this.policy.id}/`, this.localPolicy)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Policy exclusions added");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -90,12 +90,17 @@ export default {
this.$q.loading.show();
this.$axios
.get("/clients/")
.then(r => {
this.clientOptions = r.data.map(client => ({ label: client.name, value: client.id }));
.then((r) => {
this.clientOptions = r.data.map((client) => ({
label: client.name,
value: client.id,
}));
r.data.forEach(client => {
r.data.forEach((client) => {
this.siteOptions.push({ category: client.name });
client.sites.forEach(site => this.siteOptions.push({ label: site.name, value: site.id }));
client.sites.forEach((site) =>
this.siteOptions.push({ label: site.name, value: site.id })
);
});
this.$q.loading.hide();
})
@@ -104,7 +109,9 @@ export default {
});
},
getOptions() {
this.getAgentOptions("id").then(options => (this.agentOptions = Object.freeze(options)));
this.getAgentOptions("id").then(
(options) => (this.agentOptions = Object.freeze(options))
);
this.getClientsandSites();
},
show() {

View File

@@ -18,7 +18,12 @@
<q-card-section class="row">
<div class="col-2">Name:</div>
<div class="col-10">
<q-input outlined dense v-model="localPolicy.name" :rules="[val => !!val || '*Required']" />
<q-input
outlined
dense
v-model="localPolicy.name"
:rules="[(val) => !!val || '*Required']"
/>
</div>
</q-card-section>
<q-card-section class="row">
@@ -85,12 +90,12 @@ export default {
if (this.editing) {
this.$axios
.put(`/automation/policies/${data.id}/`, data)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Policy edited!");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
} else {
@@ -100,12 +105,14 @@ export default {
this.$axios
.post("/automation/policies/", data)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Policy added. Now you can add Tasks and Checks!");
this.notifySuccess(
"Policy added. Now you can add Tasks and Checks!"
);
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
}

View File

@@ -11,7 +11,10 @@
<q-card-section>
<q-table
style="max-height: 35vh"
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:rows="data"
:columns="columns"
@@ -36,12 +39,21 @@
<q-td>{{ props.row.hostname }}</q-td>
<!-- status icon -->
<q-td v-if="props.row.status === 'passing'">
<q-icon style="font-size: 1.3rem" color="positive" name="check_circle">
<q-icon
style="font-size: 1.3rem"
color="positive"
name="check_circle"
>
<q-tooltip>Passing</q-tooltip>
</q-icon>
</q-td>
<q-td v-else-if="props.row.status === 'failing'">
<q-icon v-if="props.row.alert_severity === 'info'" style="font-size: 1.3rem" color="info" name="info">
<q-icon
v-if="props.row.alert_severity === 'info'"
style="font-size: 1.3rem"
color="info"
name="info"
>
<q-tooltip>Informational</q-tooltip>
</q-icon>
<q-icon
@@ -52,17 +64,32 @@
>
<q-tooltip>Warning</q-tooltip>
</q-icon>
<q-icon v-else style="font-size: 1.3rem" color="negative" name="error">
<q-icon
v-else
style="font-size: 1.3rem"
color="negative"
name="error"
>
<q-tooltip>Error</q-tooltip>
</q-icon>
</q-td>
<q-td v-else></q-td>
<!-- status text -->
<q-td v-if="props.row.status === 'pending'">Awaiting First Synchronization</q-td>
<q-td v-else-if="props.row.sync_status === 'notsynced'">Will sync on next agent checkin</q-td>
<q-td v-else-if="props.row.sync_status === 'synced'">Synced with agent</q-td>
<q-td v-else-if="props.row.sync_status === 'pendingdeletion'">Pending deletion on agent</q-td>
<q-td v-else-if="props.row.sync_status === 'initial'">Waiting for task creation on agent</q-td>
<q-td v-if="props.row.status === 'pending'"
>Awaiting First Synchronization</q-td
>
<q-td v-else-if="props.row.sync_status === 'notsynced'"
>Will sync on next agent checkin</q-td
>
<q-td v-else-if="props.row.sync_status === 'synced'"
>Synced with agent</q-td
>
<q-td v-else-if="props.row.sync_status === 'pendingdeletion'"
>Pending deletion on agent</q-td
>
<q-td v-else-if="props.row.sync_status === 'initial'"
>Waiting for task creation on agent</q-td
>
<q-td v-else></q-td>
<!-- more info -->
<q-td v-if="props.row.check_type === 'ping'">
@@ -75,7 +102,10 @@
</q-td>
<q-td
v-else-if="
props.row.check_type === 'script' || props.row.retcode || props.row.stdout || props.row.stderr
props.row.check_type === 'script' ||
props.row.retcode ||
props.row.stdout ||
props.row.stderr
"
>
<span
@@ -93,13 +123,21 @@
>output</span
>
</q-td>
<q-td v-else-if="props.row.check_type === 'cpuload' || props.row.check_type === 'memory'">{{
props.row.history_info
<q-td
v-else-if="
props.row.check_type === 'cpuload' ||
props.row.check_type === 'memory'
"
>{{ props.row.history_info }}</q-td
>
<q-td v-else-if="props.row.more_info">{{
props.row.more_info
}}</q-td>
<q-td v-else-if="props.row.more_info">{{ props.row.more_info }}</q-td>
<q-td v-else>Awaiting Output</q-td>
<!-- last run -->
<q-td>{{ props.row.last_run ? formatDate(props.row.last_run) : "Never" }}</q-td>
<q-td>{{
props.row.last_run ? formatDate(props.row.last_run) : "Never"
}}</q-td>
</q-tr>
</template>
</q-table>
@@ -131,7 +169,7 @@ export default {
},
},
},
setup(props) {
setup() {
// setup vuex store
const store = useStore();
const formatDate = computed(() => store.getters.formatDate);
@@ -144,9 +182,21 @@ export default {
return {
data: [],
columns: [
{ name: "agent", label: "Hostname", field: "agent", align: "left", sortable: true },
{
name: "agent",
label: "Hostname",
field: "agent",
align: "left",
sortable: true,
},
{ name: "statusicon", align: "left" },
{ name: "status", label: "Status", field: "status", align: "left", sortable: true },
{
name: "status",
label: "Status",
field: "status",
align: "left",
sortable: true,
},
{
name: "moreinfo",
label: "More Info",
@@ -171,7 +221,9 @@ export default {
},
computed: {
title() {
return !!this.item.readable_desc ? this.item.readable_desc + " Status" : this.item.name + " Status";
return !!this.item.readable_desc
? this.item.readable_desc + " Status"
: this.item.name + " Status";
},
},
methods: {
@@ -179,11 +231,11 @@ export default {
this.$q.loading.show();
this.$axios
.get(`/automation/checks/${this.item.id}/status/`)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.data = r.data;
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -191,11 +243,11 @@ export default {
this.$q.loading.show();
this.$axios
.get(`/automation/tasks/${this.item.id}/status/`)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.data = r.data;
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},

View File

@@ -10,13 +10,17 @@
</q-bar>
<q-card-section
class="row items-center"
v-if="related.default_server_policy || related.default_workstation_policy"
v-if="
related.default_server_policy || related.default_workstation_policy
"
>
<div v-if="related.default_server_policy" class="text-body">
<q-icon name="error_outline" color="info" size="1.5em" />This policy is set as the Default Server Policy.
<q-icon name="error_outline" color="info" size="1.5em" />This policy
is set as the Default Server Policy.
</div>
<div v-if="related.default_workstation_policy" class="text-body">
<q-icon name="error_outline" color="info" size="1.5em" />This policy is set as the Default Workstation Policy.
<q-icon name="error_outline" color="info" size="1.5em" />This policy
is set as the Default Workstation Policy.
</div>
</q-card-section>
<q-card-section>
@@ -41,7 +45,10 @@
<q-tab-panels v-model="tab" :animated="false">
<q-tab-panel name="clients">
<q-list separator padding>
<q-item v-for="item in related.server_clients" :key="item.id + 'servers'">
<q-item
v-for="item in related.server_clients"
:key="item.id + 'servers'"
>
<q-item-section>
<q-item-label>{{ item.name }}</q-item-label>
</q-item-section>
@@ -51,7 +58,10 @@
</q-item-label>
</q-item-section>
</q-item>
<q-item v-for="item in related.workstation_clients" :key="item.id + 'workstations'">
<q-item
v-for="item in related.workstation_clients"
:key="item.id + 'workstations'"
>
<q-item-section>
<q-item-label>{{ item.name }}</q-item-label>
</q-item-section>
@@ -66,7 +76,10 @@
<q-tab-panel name="sites">
<q-list separator padding>
<q-item v-for="item in related.server_sites" :key="item.id + 'servers'">
<q-item
v-for="item in related.server_sites"
:key="item.id + 'servers'"
>
<q-item-section>
<q-item-label>{{ item.name }}</q-item-label>
<q-item-label caption>{{ item.client_name }}</q-item-label>
@@ -77,7 +90,10 @@
</q-item-label>
</q-item-section>
</q-item>
<q-item v-for="item in related.workstation_sites" :key="item.id + 'workstations'">
<q-item
v-for="item in related.workstation_sites"
:key="item.id + 'workstations'"
>
<q-item-section>
<q-item-label>{{ item.name }}</q-item-label>
<q-item-label caption>{{ item.client_name }}</q-item-label>
@@ -150,11 +166,11 @@ export default {
this.$axios
.get(`/automation/policies/${this.policy.id}/related/`)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.related = r.data;
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},

View File

@@ -17,7 +17,10 @@
type="number"
v-model.number="state.warning_threshold"
label="Warning Threshold (%)"
:rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
:rules="[
(val) => val >= 0 || 'Minimum threshold is 0',
(val) => val < 100 || 'Maximum threshold is 99',
]"
/>
</q-card-section>
<q-card-section>
@@ -27,7 +30,10 @@
type="number"
v-model.number="state.error_threshold"
label="Error Threshold (%)"
:rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
:rules="[
(val) => val >= 0 || 'Minimum threshold is 0',
(val) => val < 100 || 'Maximum threshold is 99',
]"
/>
</q-card-section>
<q-card-section>
@@ -53,7 +59,14 @@
</div>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>

View File

@@ -19,7 +19,7 @@
v-model="state.disk"
:options="diskOptions"
label="Disk"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section>
@@ -29,7 +29,10 @@
type="number"
v-model.number="state.warning_threshold"
label="Warning Threshold Remaining (%)"
:rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
:rules="[
(val) => val >= 0 || 'Minimum threshold is 0',
(val) => val < 100 || 'Maximum threshold is 99',
]"
/>
</q-card-section>
<q-card-section>
@@ -39,7 +42,10 @@
type="number"
v-model.number="state.error_threshold"
label="Error Threshold Remaining (%)"
:rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
:rules="[
(val) => val >= 0 || 'Minimum threshold is 0',
(val) => val < 100 || 'Maximum threshold is 99',
]"
/>
</q-card-section>
<q-card-section>
@@ -65,7 +71,14 @@
</div>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>

View File

@@ -17,7 +17,7 @@
outlined
v-model="state.name"
label="Descriptive Name"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section>
@@ -48,16 +48,29 @@
outlined
v-model="state.event_id"
label="Event ID (Use * to match every event ID)"
:rules="[val => validateEventID(val) || 'Invalid Event ID']"
:rules="[(val) => validateEventID(val) || 'Invalid Event ID']"
/>
</q-card-section>
<q-card-section>
<q-checkbox v-model="eventSource" label="Event source" />
<q-input dense outlined v-model="state.event_source" :disable="!eventSource" />
<q-input
dense
outlined
v-model="state.event_source"
:disable="!eventSource"
/>
</q-card-section>
<q-card-section>
<q-checkbox v-model="eventMessage" label="Message contains string" />
<q-input dense outlined v-model="state.event_message" :disable="!eventMessage" />
<q-checkbox
v-model="eventMessage"
label="Message contains string"
/>
<q-input
dense
outlined
v-model="state.event_message"
:disable="!eventMessage"
/>
</q-card-section>
<q-card-section>
<q-input
@@ -66,20 +79,45 @@
v-model.number="state.search_last_days"
label="How many previous days to search (Enter 0 for the entire log)"
:rules="[
val => !!val.toString() || '*Required',
val => val >= 0 || 'Min 0',
val => val <= 9999 || 'Max 9999',
(val) => !!val.toString() || '*Required',
(val) => val >= 0 || 'Min 0',
(val) => val <= 9999 || 'Max 9999',
]"
/>
</q-card-section>
<q-card-section>
<span>Event Type:</span>
<div class="q-gutter-sm">
<q-radio dense v-model="state.event_type" val="INFO" label="Information" />
<q-radio dense v-model="state.event_type" val="WARNING" label="Warning" />
<q-radio dense v-model="state.event_type" val="ERROR" label="Error" />
<q-radio dense v-model="state.event_type" val="AUDIT_SUCCESS" label="Success Audit" />
<q-radio dense v-model="state.event_type" val="AUDIT_FAILURE" label="Failure Audit" />
<q-radio
dense
v-model="state.event_type"
val="INFO"
label="Information"
/>
<q-radio
dense
v-model="state.event_type"
val="WARNING"
label="Warning"
/>
<q-radio
dense
v-model="state.event_type"
val="ERROR"
label="Error"
/>
<q-radio
dense
v-model="state.event_type"
val="AUDIT_SUCCESS"
label="Success Audit"
/>
<q-radio
dense
v-model="state.event_type"
val="AUDIT_FAILURE"
label="Failure Audit"
/>
</div>
</q-card-section>
<q-card-section>
@@ -126,7 +164,14 @@
</div>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -152,7 +197,15 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// check logic
const { state, loading, submit, failOptions, logNameOptions, failWhenOptions, severityOptions } = useCheckModal({
const {
state,
loading,
submit,
failOptions,
logNameOptions,
failWhenOptions,
severityOptions,
} = useCheckModal({
editCheck: props.check,
initialState: {
...props.parent,
@@ -188,17 +241,18 @@ export default {
}
}
watch(eventMessage, (newValue, oldValue) => {
watch(eventMessage, () => {
state.value.event_message = null;
});
watch(eventSource, (newValue, oldValue) => {
watch(eventSource, () => {
state.value.event_source = null;
});
function beforeSubmit() {
// format check data for saving
state.value.event_id_is_wildcard = state.value.event_id === "*" ? true : false;
state.value.event_id_is_wildcard =
state.value.event_id === "*" ? true : false;
if (state.value.event_source === "") state.value.event_source = null;
if (state.value.event_message === "") state.value.event_message = null;

View File

@@ -12,7 +12,10 @@
<q-table
dense
style="height: 65vh"
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:filter="filter"
:rows="evtLogData.check_result.extra_details.log"
@@ -26,12 +29,22 @@
>
<template v-slot:top>
<q-space />
<q-input v-model="filter" outlined label="Search" dense clearable class="q-pr-sm">
<q-input
v-model="filter"
outlined
label="Search"
dense
clearable
class="q-pr-sm"
>
<template v-slot:prepend>
<q-icon name="search" color="primary" />
</template>
</q-input>
<export-table-btn :data="evtLogData.check_result.extra_details.log" :columns="columns" />
<export-table-btn
:data="evtLogData.check_result.extra_details.log"
:columns="columns"
/>
</template>
</q-table>
</div>
@@ -50,23 +63,51 @@ import ExportTableBtn from "@/components/ui/ExportTableBtn";
// static data
const columns = [
{ name: "eventType", label: "Type", field: "eventType", align: "left", sortable: true },
{ name: "source", label: "Source", field: "source", align: "left", sortable: true },
{ name: "eventID", label: "Event ID", field: "eventID", align: "left", sortable: true },
{
name: "eventType",
label: "Type",
field: "eventType",
align: "left",
sortable: true,
},
{
name: "source",
label: "Source",
field: "source",
align: "left",
sortable: true,
},
{
name: "eventID",
label: "Event ID",
field: "eventID",
align: "left",
sortable: true,
},
{ name: "time", label: "Time", field: "time", align: "left", sortable: true },
{ name: "message", label: "Message", field: "message", align: "left", sortable: true },
{
name: "message",
label: "Message",
field: "message",
align: "left",
sortable: true,
},
];
export default {
name: "EventLogCheckOutput",
components: { ExportTableBtn },
emits: [...useDialogPluginComponent.emits],
props: { evtLogData: !Object },
setup(props) {
setup() {
// setup quasar
const { dialogRef, onDialogHide } = useDialogPluginComponent();
const filter = ref("");
const pagination = ref({ rowsPerPage: 0, sortBy: "time", descending: true });
const pagination = ref({
rowsPerPage: 0,
sortBy: "time",
descending: true,
});
return {
// reactive data

View File

@@ -17,7 +17,10 @@
type="number"
v-model.number="state.warning_threshold"
label="Warning Threshold (%)"
:rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
:rules="[
(val) => val >= 0 || 'Minimum threshold is 0',
(val) => val < 100 || 'Maximum threshold is 99',
]"
/>
</q-card-section>
<q-card-section>
@@ -27,7 +30,10 @@
type="number"
v-model.number="state.error_threshold"
label="Error Threshold (%)"
:rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
:rules="[
(val) => val >= 0 || 'Minimum threshold is 0',
(val) => val < 100 || 'Maximum threshold is 99',
]"
/>
</q-card-section>
<q-card-section>
@@ -53,7 +59,14 @@
</div>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>

View File

@@ -17,11 +17,17 @@
dense
v-model="state.name"
label="Descriptive Name"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section>
<q-input dense outlined v-model="state.ip" label="Hostname or IP" :rules="[val => !!val || '*Required']" />
<q-input
dense
outlined
v-model="state.ip"
label="Hostname or IP"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section>
<q-select
@@ -60,7 +66,14 @@
</div>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -84,7 +97,8 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// check logic
const { state, loading, submit, failOptions, severityOptions } = useCheckModal({
const { state, loading, submit, failOptions, severityOptions } =
useCheckModal({
editCheck: props.check,
initialState: {
...props.parent,
@@ -115,23 +129,5 @@ export default {
onDialogOK,
};
},
data() {
return {
pingcheck: {
check_type: "ping",
name: null,
ip: null,
alert_severity: "warning",
fails_b4_alert: 1,
run_interval: 0,
},
severityOptions: [
{ label: "Informational", value: "info" },
{ label: "Warning", value: "warning" },
{ label: "Error", value: "error" },
],
failOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
};
},
};
</script>

View File

@@ -16,7 +16,7 @@
<q-form v-else @submit.prevent="submit(onDialogOK)">
<q-card-section>
<tactical-dropdown
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
outlined
v-model="state.script"
:options="scriptOptions"
@@ -66,7 +66,12 @@
/>
</q-card-section>
<q-card-section>
<q-input outlined dense v-model.number="state.timeout" label="Script Timeout (seconds)" />
<q-input
outlined
dense
v-model.number="state.timeout"
label="Script Timeout (seconds)"
/>
</q-card-section>
<q-card-section>
<q-select
@@ -90,7 +95,14 @@
</q-card-section>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -120,13 +132,14 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// setup script dropdown
const { script, scriptOptions, defaultTimeout, defaultArgs } = useScriptDropdown(
props.check ? props.check.script : undefined,
{ onMount: true }
);
const { script, scriptOptions, defaultTimeout, defaultArgs } =
useScriptDropdown(props.check ? props.check.script : undefined, {
onMount: true,
});
// check logic
const { state, loading, submit, failOptions, severityOptions } = useCheckModal({
const { state, loading, submit, failOptions, severityOptions } =
useCheckModal({
editCheck: props.check,
initialState: {
...props.parent,

View File

@@ -47,7 +47,7 @@ export default {
name: "ScriptOutput",
emits: [...useDialogPluginComponent.emits],
props: { scriptInfo: !Object },
setup(props) {
setup() {
// setup vuex
const store = useStore();
const formatDate = computed(() => store.getters.formatDate);

View File

@@ -19,10 +19,15 @@
val="default"
label="Choose from defaults"
/>
<q-radio v-if="isPolicy && !check" v-model="state.svc_policy_mode" val="manual" label="Enter manually" />
<q-radio
v-if="isPolicy && !check"
v-model="state.svc_policy_mode"
val="manual"
label="Enter manually"
/>
<q-select
v-if="isPolicy && state.svc_policy_mode === 'default' && !check"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
dense
options-dense
outlined
@@ -35,7 +40,7 @@
/>
<q-input
v-if="isPolicy && state.svc_policy_mode === 'manual'"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
outlined
dense
v-model="state.svc_name"
@@ -43,7 +48,7 @@
/>
<q-input
v-if="isPolicy && state.svc_policy_mode === 'manual'"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
outlined
dense
v-model="state.svc_display_name"
@@ -53,7 +58,7 @@
<!-- disable selection if editing -->
<q-select
v-if="isAgent"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
dense
options-dense
outlined
@@ -66,11 +71,20 @@
/>
</q-card-section>
<q-card-section>
<q-checkbox v-model="state.pass_if_start_pending" label="PASS if service is in 'Start Pending' mode" />
<q-checkbox
v-model="state.pass_if_start_pending"
label="PASS if service is in 'Start Pending' mode"
/>
<br />
<q-checkbox v-model="state.pass_if_svc_not_exist" label="PASS if service doesn't exist" />
<q-checkbox
v-model="state.pass_if_svc_not_exist"
label="PASS if service doesn't exist"
/>
<br />
<q-checkbox v-model="state.restart_if_stopped" label="Restart service if it's stopped" />
<q-checkbox
v-model="state.restart_if_stopped"
label="Restart service if it's stopped"
/>
</q-card-section>
<q-card-section>
<q-select
@@ -107,7 +121,14 @@
</div>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -132,7 +153,14 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// check logic
const { state, loading, submit, failOptions, severityOptions, serviceOptions } = useCheckModal({
const {
state,
loading,
submit,
failOptions,
severityOptions,
serviceOptions,
} = useCheckModal({
editCheck: props.check,
initialState: {
...props.parent,
@@ -151,17 +179,19 @@ export default {
watch(
() => state.value.svc_name,
(newvalue, oldValue) => {
() => {
// prevent error when in manual mode
try {
state.value.svc_display_name = serviceOptions.value.find(i => i.value === state.value.svc_name).label;
state.value.svc_display_name = serviceOptions.value.find(
(i) => i.value === state.value.svc_name
).label;
} catch {}
}
);
watch(
() => state.value.svc_policy_mode,
(newValue, oldValue) => {
() => {
state.value.svc_name = null;
state.value.svc_display_name = null;
}

View File

@@ -15,12 +15,12 @@
dense
v-model="state.name"
label="Name"
:rules="[val => (val && val.length > 0) || '*Required']"
:rules="[(val) => (val && val.length > 0) || '*Required']"
/>
</q-card-section>
<q-card-section v-if="!client">
<q-input
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
outlined
dense
v-model="site.name"
@@ -28,13 +28,23 @@
/>
</q-card-section>
<div class="q-pl-sm text-h6" v-if="customFields.length > 0">Custom Fields</div>
<div class="q-pl-sm text-h6" v-if="customFields.length > 0">
Custom Fields
</div>
<q-card-section v-for="field in customFields" :key="field.id">
<CustomField v-model="custom_fields[field.name]" :field="field" />
</q-card-section>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat push label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
push
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -67,7 +77,9 @@ export default {
const { dialogRef, onDialogOK, onDialogHide } = useDialogPluginComponent();
// clients form logic
const state = !!props.client ? ref(Object.assign({}, props.client)) : ref({ name: "" });
const state = !!props.client
? ref(Object.assign({}, props.client))
: ref({ name: "" });
const site = ref({ name: "" });
const custom_fields = ref({});
const customFields = ref([]);
@@ -78,10 +90,15 @@ export default {
const data = {
client: state.value,
site: site.value,
custom_fields: formatCustomFields(customFields.value, custom_fields.value),
custom_fields: formatCustomFields(
customFields.value,
custom_fields.value
),
};
try {
const result = !!props.client ? await editClient(props.client.id, data) : await saveClient(data);
const result = !!props.client
? await editClient(props.client.id, data)
: await saveClient(data);
notifySuccess(result);
onDialogOK();
} catch (e) {
@@ -95,7 +112,9 @@ export default {
const data = await fetchClient(props.client.id);
for (let field of customFields.value) {
const value = data.custom_fields.find(value => value.field === field.id);
const value = data.custom_fields.find(
(value) => value.field === field.id
);
if (field.type === "multiple") {
if (value) custom_fields.value[field.name] = value.value;
@@ -113,7 +132,7 @@ export default {
onMounted(async () => {
const fields = await fetchCustomFields({ model: "client" });
customFields.value = fields.filter(field => !field.hide_in_ui);
customFields.value = fields.filter((field) => !field.hide_in_ui);
if (props.client) getClientCustomFieldValues();
});

View File

@@ -2,7 +2,14 @@
<q-dialog ref="dialogRef" @hide="onDialogHide">
<q-card class="q-dialog-plugin" style="width: 70vw">
<q-bar>
<q-btn @click="getClients" class="q-mr-sm" dense flat push icon="refresh" />Clients Manager
<q-btn
@click="getClients"
class="q-mr-sm"
dense
flat
push
icon="refresh"
/>Clients Manager
<q-space />
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
@@ -11,7 +18,10 @@
<q-table
:rows="clients"
:columns="columns"
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="settings-tbl-sticky"
style="height: 70vh"
:pagination="{ rowsPerPage: 0, sortBy: 'name', descending: false }"
@@ -25,7 +35,15 @@
>
<!-- top slot -->
<template v-slot:top>
<q-btn label="New" dense flat push no-caps icon="add" @click="showAddClient" />
<q-btn
label="New"
dense
flat
push
no-caps
icon="add"
@click="showAddClient"
/>
</template>
<!-- loading slot -->
@@ -35,17 +53,29 @@
<!-- body slots -->
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="showEditClient(props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="showEditClient(props.row)"
>
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="showEditClient(props.row)">
<q-item
clickable
v-close-popup
@click="showEditClient(props.row)"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showClientDeleteModal(props.row)">
<q-item
clickable
v-close-popup
@click="showClientDeleteModal(props.row)"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
@@ -105,13 +135,18 @@ import SitesTable from "@/components/clients/SitesTable";
const columns = [
{ name: "name", label: "Name", field: "name", align: "left" },
{ name: "sites", label: "Sites", field: "sites", align: "left" },
{ name: "agent_count", label: "Total Agents", field: "agent_count", align: "left" },
{
name: "agent_count",
label: "Total Agents",
field: "agent_count",
align: "left",
},
];
export default {
name: "ClientsManager",
emits: [...useDialogPluginComponent.emits],
setup(props) {
setup() {
// setup quasar dialog
const $q = useQuasar();
const { dialogRef, onDialogHide } = useDialogPluginComponent();

View File

@@ -10,7 +10,8 @@
</q-bar>
<q-form @submit="submit">
<q-card-section v-if="siteOptions.length === 0">
There are no valid sites to move agents to. Add another site and try again
There are no valid sites to move agents to. Add another site and try
again
</q-card-section>
<q-card-section v-if="siteOptions.length > 0">
<tactical-dropdown
@@ -19,7 +20,10 @@
v-model="site"
:options="siteOptions"
mapOptions
:rules="[val => !!val || 'Select the site that the agents should be moved to']"
:rules="[
(val) =>
!!val || 'Select the site that the agents should be moved to',
]"
hint="The client you are deleting has agents assigned to it. Select a Site below to move the agents to."
filterable
/>
@@ -84,7 +88,9 @@ export default {
try {
const result =
props.type === "client"
? await removeClient(props.object.id, { move_to_site: site.value })
? await removeClient(props.object.id, {
move_to_site: site.value,
})
: await removeSite(props.object.id, { move_to_site: site.value });
notifySuccess(result);
onDialogOK();
@@ -102,10 +108,19 @@ export default {
if (props.type === "client") {
// filter out client that is being deleted
siteOptions.value = Object.freeze(formatSiteOptions(clients.filter(client => client.id !== props.object.id)));
siteOptions.value = Object.freeze(
formatSiteOptions(
clients.filter((client) => client.id !== props.object.id)
)
);
} else {
// filter out site that is being dleted
clients.forEach(client => (client.sites = client.sites.filter(site => site.id !== props.object.id)));
clients.forEach(
(client) =>
(client.sites = client.sites.filter(
(site) => site.id !== props.object.id
))
);
siteOptions.value = Object.freeze(formatSiteOptions(clients));
}
}

View File

@@ -2,7 +2,14 @@
<q-dialog ref="dialogRef" @hide="onDialogHide">
<q-card style="min-width: 70vw; height: 70vh">
<q-bar>
<q-btn @click="getDeployments" class="q-mr-sm" dense flat push icon="refresh" />
<q-btn
@click="getDeployments"
class="q-mr-sm"
dense
flat
push
icon="refresh"
/>
Manage Deployments
<q-space />
<q-btn dense flat icon="close" v-close-popup>
@@ -11,7 +18,10 @@
</q-bar>
<q-table
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="audit-mgr-tbl-sticky"
style="max-height: 65vh"
binary-state-sort
@@ -29,7 +39,11 @@
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="copyLink(props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="copyLink(props.row)"
>
<q-menu context-menu auto-close>
<q-list dense style="min-width: 200px">
<q-item clickable @click="deleteDeployment(props.row)">
@@ -48,13 +62,20 @@
<q-td key="site" :props="props">{{ props.row.site_name }}</q-td>
<q-td key="mon_type" :props="props">{{ props.row.mon_type }}</q-td>
<q-td key="arch" :props="props"
><span v-if="props.row.arch === '64'">64 bit</span><span v-else>32 bit</span></q-td
><span v-if="props.row.arch === '64'">64 bit</span
><span v-else>32 bit</span></q-td
>
<q-td key="expiry" :props="props">{{ formatDate(props.row.expiry) }}</q-td>
<q-td key="created" :props="props">{{ formatDate(props.row.created) }}</q-td>
<q-td key="expiry" :props="props">{{
formatDate(props.row.expiry)
}}</q-td>
<q-td key="created" :props="props">{{
formatDate(props.row.created)
}}</q-td>
<q-td key="flags" :props="props"
><q-badge color="grey-8" label="View Flags" />
<q-tooltip style="font-size: 12px">{{ props.row.install_flags }}</q-tooltip>
<q-tooltip style="font-size: 12px">{{
props.row.install_flags
}}</q-tooltip>
</q-td>
<q-td key="link" :props="props">
<q-btn
@@ -88,20 +109,50 @@ import NewDeployment from "@/components/clients/NewDeployment";
// static data
const columns = [
{ name: "client", label: "Client", field: "client_name", align: "left", sortable: true },
{ name: "site", label: "Site", field: "site_name", align: "left", sortable: true },
{ name: "mon_type", label: "Type", field: "mon_type", align: "left", sortable: true },
{
name: "client",
label: "Client",
field: "client_name",
align: "left",
sortable: true,
},
{
name: "site",
label: "Site",
field: "site_name",
align: "left",
sortable: true,
},
{
name: "mon_type",
label: "Type",
field: "mon_type",
align: "left",
sortable: true,
},
{ name: "arch", label: "Arch", field: "arch", align: "left", sortable: true },
{ name: "expiry", label: "Expiry", field: "expiry", align: "left", sortable: true },
{ name: "created", label: "Created", field: "created", align: "left", sortable: true },
{
name: "expiry",
label: "Expiry",
field: "expiry",
align: "left",
sortable: true,
},
{
name: "created",
label: "Created",
field: "created",
align: "left",
sortable: true,
},
{ name: "flags", label: "Flags", field: "install_flags", align: "left" },
{ name: "link", label: "Download Link", align: "left" },
];
export default {
name: "Deployment",
name: "DeploymentTable",
emits: [...useDialogPluginComponent.emits],
setup(props) {
setup() {
// quasar dialog setup
const { dialogRef, onDialogHide } = useDialogPluginComponent();
const $q = useQuasar();

View File

@@ -10,7 +10,7 @@
</q-bar>
<q-card-section>
<tactical-dropdown
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
outlined
label="Site"
v-model="state.site"
@@ -21,11 +21,27 @@
</q-card-section>
<q-card-section>
<div class="q-pl-sm">Agent Type</div>
<q-radio v-model="state.agenttype" val="server" label="Server" @update:model-value="power = false" />
<q-radio v-model="state.agenttype" val="workstation" label="Workstation" />
<q-radio
v-model="state.agenttype"
val="server"
label="Server"
@update:model-value="power = false"
/>
<q-radio
v-model="state.agenttype"
val="workstation"
label="Workstation"
/>
</q-card-section>
<q-card-section>
<q-input type="datetime-local" dense label="Expiry" stack-label filled v-model="state.expires" />
<q-input
type="datetime-local"
dense
label="Expiry"
stack-label
filled
v-model="state.expires"
/>
</q-card-section>
<q-card-section class="q-gutter-sm">
<q-checkbox v-model="state.rdp" dense label="Enable RDP" />
@@ -44,7 +60,14 @@
</q-card-section>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat label="Create" color="primary" @click="submit" />
<q-btn
:loading="loading"
dense
flat
label="Create"
color="primary"
@click="submit"
/>
</q-card-actions>
</q-card>
</q-dialog>
@@ -57,7 +80,10 @@ import { useDialogPluginComponent, date } from "quasar";
import { useSiteDropdown } from "@/composables/clients";
import { saveDeployment } from "@/api/clients";
import { notifySuccess } from "@/utils/notify";
import { formatDateInputField, formatDateStringwithTimezone } from "@/utils/format";
import {
formatDateInputField,
formatDateStringwithTimezone,
} from "@/utils/format";
// ui imports
import TacticalDropdown from "@/components/ui/TacticalDropdown";
@@ -67,7 +93,7 @@ export default {
TacticalDropdown,
},
emits: [...useDialogPluginComponent.emits],
setup(props) {
setup() {
// setup quasar dialog
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
@@ -94,7 +120,8 @@ export default {
...state.value,
};
if (data.expires) data.expires = formatDateStringwithTimezone(data.expires);
if (data.expires)
data.expires = formatDateStringwithTimezone(data.expires);
try {
const result = await saveDeployment(data);

View File

@@ -16,22 +16,38 @@
:options="clientOptions"
outlined
mapOptions
:rules="[val => !!val || 'Client is required']"
:rules="[(val) => !!val || 'Client is required']"
filterable
/>
</q-card-section>
<q-card-section>
<q-input :rules="[val => !!val || 'Name is required']" outlined dense v-model="state.name" label="Name" />
<q-input
:rules="[(val) => !!val || 'Name is required']"
outlined
dense
v-model="state.name"
label="Name"
/>
</q-card-section>
<div class="q-pl-sm text-h6" v-if="customFields.length > 0">Custom Fields</div>
<div class="q-pl-sm text-h6" v-if="customFields.length > 0">
Custom Fields
</div>
<q-card-section v-for="field in customFields" :key="field.id">
<CustomField v-model="custom_fields[field.name]" :field="field" />
</q-card-section>
<q-card-actions align="right">
<q-btn dense flat push label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat push label="Save" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
push
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -72,7 +88,9 @@ export default {
const { clientOptions } = useClientDropdown(true);
// sites for logic
const state = !!props.site ? ref(Object.assign({}, props.site)) : ref({ client: props.client, name: "" });
const state = !!props.site
? ref(Object.assign({}, props.site))
: ref({ client: props.client, name: "" });
const custom_fields = ref({});
const customFields = ref([]);
const loading = ref(false);
@@ -81,10 +99,15 @@ export default {
loading.value = true;
const data = {
site: state.value,
custom_fields: formatCustomFields(customFields.value, custom_fields.value),
custom_fields: formatCustomFields(
customFields.value,
custom_fields.value
),
};
try {
const result = !!props.site ? await editSite(props.site.id, data) : await saveSite(data);
const result = !!props.site
? await editSite(props.site.id, data)
: await saveSite(data);
notifySuccess(result);
onDialogOK();
} catch (e) {
@@ -98,7 +121,9 @@ export default {
const data = await fetchSite(props.site.id);
for (let field of customFields.value) {
const value = data.custom_fields.find(value => value.field === field.id);
const value = data.custom_fields.find(
(value) => value.field === field.id
);
if (field.type === "multiple") {
if (value) custom_fields.value[field.name] = value.value;
@@ -118,7 +143,7 @@ export default {
$q.loading.show();
try {
const fields = await fetchCustomFields({ model: "site" });
customFields.value = fields.filter(field => !field.hide_in_ui);
customFields.value = fields.filter((field) => !field.hide_in_ui);
if (props.site) getSiteCustomFieldValues();
} catch (e) {
console.error(e);

View File

@@ -2,7 +2,14 @@
<q-dialog ref="dialogRef" @hide="onDialogHide">
<q-card class="q-dialog-plugin" style="width: 60vw">
<q-bar>
<q-btn @click="getSites" class="q-mr-sm" dense flat push icon="refresh" />Sites for {{ client.name }}
<q-btn
@click="getSites"
class="q-mr-sm"
dense
flat
push
icon="refresh"
/>Sites for {{ client.name }}
<q-space />
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
@@ -18,13 +25,25 @@
virtual-scroll
:rows-per-page-options="[0]"
no-data-label="No Sites"
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="settings-tbl-sticky"
style="height: 65vh"
:loading="loading"
>
<template v-slot:top>
<q-btn label="New" dense flat push unelevated no-caps icon="add" @click="showAddSite" />
<q-btn
label="New"
dense
flat
push
unelevated
no-caps
icon="add"
@click="showAddSite"
/>
</template>
<!-- loading slot -->
@@ -34,17 +53,29 @@
<!-- body slots -->
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="showEditSite(props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="showEditSite(props.row)"
>
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="showEditSite(props.row)">
<q-item
clickable
v-close-popup
@click="showEditSite(props.row)"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showSiteDeleteModal(props.row)">
<q-item
clickable
v-close-popup
@click="showSiteDeleteModal(props.row)"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
@@ -85,7 +116,12 @@ import DeleteClient from "@/components/clients/DeleteClient";
// static data
const columns = [
{ name: "name", label: "Name", field: "name", align: "left" },
{ name: "agent_count", label: "Total Agents", field: "agent_count", align: "left" },
{
name: "agent_count",
label: "Total Agents",
field: "agent_count",
align: "left",
},
];
export default {

View File

@@ -14,7 +14,13 @@
</q-card-section>
<!-- name -->
<q-card-section>
<q-input label="Name" outlined dense v-model="localKey.name" :rules="[val => !!val || '*Required']" />
<q-input
label="Name"
outlined
dense
v-model="localKey.name"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<!-- user -->
@@ -48,7 +54,13 @@
<q-card-actions align="right">
<q-btn flat label="Cancel" v-close-popup />
<q-btn flat label="Submit" color="primary" type="submit" :loading="loading" />
<q-btn
flat
label="Submit"
color="primary"
type="submit"
:loading="loading"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -62,7 +74,10 @@ import { useDialogPluginComponent } from "quasar";
import { saveAPIKey, editAPIKey } from "@/api/accounts";
import { useUserDropdown } from "@/composables/accounts";
import { notifySuccess } from "@/utils/notify";
import { formatDateInputField, formatDateStringwithTimezone } from "@/utils/format";
import {
formatDateInputField,
formatDateStringwithTimezone,
} from "@/utils/format";
// ui imports
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
@@ -80,7 +95,9 @@ export default {
const { userOptions } = useUserDropdown(true);
// setup api key form logic
const key = props.APIKey ? ref(Object.assign({}, props.APIKey)) : ref({ name: "", expiration: null });
const key = props.APIKey
? ref(Object.assign({}, props.APIKey))
: ref({ name: "", expiration: null });
const loading = ref(false);
// remove Z from date string
@@ -88,7 +105,9 @@ export default {
key.value.expiration = formatDateInputField(key.value.expiration);
}
const title = computed(() => (props.APIKey ? "Edit API Key" : "Add API Key"));
const title = computed(() =>
props.APIKey ? "Edit API Key" : "Add API Key"
);
async function submitForm() {
loading.value = true;
@@ -98,10 +117,13 @@ export default {
};
// convert date to local timezone if exists
if (data.expiration) data.expiration = formatDateStringwithTimezone(data.expiration);
if (data.expiration)
data.expiration = formatDateStringwithTimezone(data.expiration);
try {
const result = props.APIKey ? await editAPIKey(data) : await saveAPIKey(data);
const result = props.APIKey
? await editAPIKey(data)
: await saveAPIKey(data);
onDialogOK();
notifySuccess(result);
loading.value = false;

View File

@@ -3,7 +3,14 @@
<div class="row">
<div class="text-subtitle2">API Keys</div>
<q-space />
<q-btn size="sm" color="grey-5" icon="fas fa-plus" text-color="black" label="Add key" @click="addAPIKey" />
<q-btn
size="sm"
color="grey-5"
icon="fas fa-plus"
text-color="black"
label="Add key"
@click="addAPIKey"
/>
</div>
<q-separator />
<q-table
@@ -25,7 +32,11 @@
<!-- body slots -->
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="editAPIKey(props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="editAPIKey(props.row)"
>
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
@@ -65,7 +76,11 @@
{{ formatDate(props.row.created_time) }}
</q-td>
<q-td>
<q-icon size="sm" name="content_copy" @click="copyKeyToClipboard(props.row.key)">
<q-icon
size="sm"
name="content_copy"
@click="copyKeyToClipboard(props.row.key)"
>
<q-tooltip>Copy API Key to clipboard</q-tooltip>
</q-icon>
</q-td>

View File

@@ -1,8 +1,18 @@
<template>
<q-dialog ref="dialog" @hide="onHide">
<q-card class="q-dialog-plugin" style="min-width: 80vw; min-height: 65vh; overflow-x: hidden">
<q-card
class="q-dialog-plugin"
style="min-width: 80vw; min-height: 65vh; overflow-x: hidden"
>
<q-bar>
<q-btn @click="getChartData" class="q-mr-sm" dense flat push icon="refresh" />
<q-btn
@click="getChartData"
class="q-mr-sm"
dense
flat
push
icon="refresh"
/>
{{ title }}
<q-space />
<q-btn dense flat icon="close" v-close-popup>
@@ -112,11 +122,13 @@ export default {
seriesName() {
if (this.check.check_type === "cpuload") return "CPU Load";
else if (this.check.check_type === "memory") return "Memory Usage";
else if (this.check.check_type === "diskspace") return "Disk Space Remaining";
else if (this.check.check_type === "diskspace")
return "Disk Space Remaining";
else if (this.check.check_type === "script") return "Script Results";
else if (this.check.check_type === "eventlog") return "Status";
else if (this.check.check_type === "winsvc") return "Status";
else if (this.check.check_type === "ping") return "Status";
else return "";
},
},
methods: {
@@ -124,8 +136,10 @@ export default {
this.$q.loading.show();
this.$axios
.patch(`/checks/${this.check.check_result.id}/history/`, { timeFilter: this.timeFilter })
.then(r => {
.patch(`/checks/${this.check.check_result.id}/history/`, {
timeFilter: this.timeFilter,
})
.then((r) => {
this.history = Object.freeze(r.data);
// save copy of data to reference results in chart tooltip
@@ -139,7 +153,7 @@ export default {
this.$q.loading.hide();
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -208,7 +222,7 @@ export default {
min: 0,
max: 100,
labels: {
formatter: (val, index) => {
formatter: (val) => {
return val + "%";
},
},
@@ -227,7 +241,7 @@ export default {
forceNiceScale: true,
labels: {
minWidth: 50,
formatter: (val, index) => {
formatter: (val) => {
if (val === 0) return "Passing";
else if (val === 1) return "Failing";
else return "";
@@ -238,17 +252,29 @@ export default {
// customize the yaxis tooltip to include more information
this.chartOptions["tooltip"]["y"] = {
title: {
formatter: val => {
formatter: () => {
return "";
},
},
formatter: (value, { series, seriesIndex, dataPointIndex, w }) => {
formatter: (value, { dataPointIndex }) => {
let formatted = "";
if (this.check.check_type === "script") {
formatted += "Return Code: " + this.results[dataPointIndex].results.retcode + "<br/>";
formatted += "Std Out: " + this.results[dataPointIndex].results.stdout + "<br/>";
formatted += "Err Out: " + this.results[dataPointIndex].results.errout + "<br/>";
formatted += "Execution Time: " + this.results[dataPointIndex].results.execution_time + "<br/>";
formatted +=
"Return Code: " +
this.results[dataPointIndex].results.retcode +
"<br/>";
formatted +=
"Std Out: " +
this.results[dataPointIndex].results.stdout +
"<br/>";
formatted +=
"Err Out: " +
this.results[dataPointIndex].results.errout +
"<br/>";
formatted +=
"Execution Time: " +
this.results[dataPointIndex].results.execution_time +
"<br/>";
} else {
formatted += this.results[dataPointIndex].results;
}

View File

@@ -14,8 +14,13 @@
:rows="auditLogs"
:columns="columns"
class="tabs-tbl-sticky"
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:style="{ 'max-height': tabHeight ? tabHeight : `${$q.screen.height - 33}px` }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
:style="{
'max-height': tabHeight ? tabHeight : `${$q.screen.height - 33}px`,
}"
row-key="id"
dense
binary-state-sort
@@ -27,7 +32,15 @@
:loading="loading"
>
<template v-slot:top>
<q-btn v-if="agent" class="q-pr-sm" dense flat push @click="search" icon="refresh" />
<q-btn
v-if="agent"
class="q-pr-sm"
dense
flat
push
@click="search"
icon="refresh"
/>
<q-option-group
v-if="!agent"
class="q-pr-sm"
@@ -111,7 +124,10 @@
<template v-slot:body-cell-action="props">
<q-td :props="props">
<div>
<q-badge :color="formatActionColor(props.value)" :label="props.value" />
<q-badge
:color="formatActionColor(props.value)"
:label="props.value"
/>
</div>
</q-td>
</template>
@@ -160,9 +176,27 @@ const columns = [
align: "left",
sortable: true,
},
{ name: "username", label: "Username", field: "username", align: "left", sortable: true },
{ name: "agent", label: "Agent", field: "agent", align: "left", sortable: true },
{ name: "client", label: "Client", field: "site", align: "left", sortable: true },
{
name: "username",
label: "Username",
field: "username",
align: "left",
sortable: true,
},
{
name: "agent",
label: "Agent",
field: "agent",
align: "left",
sortable: true,
},
{
name: "client",
label: "Client",
field: "site",
align: "left",
sortable: true,
},
{ name: "site", label: "Site", field: "site", align: "left", sortable: true },
{
name: "action",
@@ -170,7 +204,7 @@ const columns = [
field: "action",
align: "left",
sortable: true,
format: (val, row) => formatTableColumnText(val),
format: (val) => formatTableColumnText(val),
},
{
name: "object_type",
@@ -178,10 +212,22 @@ const columns = [
field: "object_type",
align: "left",
sortable: true,
format: (val, row) => formatTableColumnText(val),
format: (val) => formatTableColumnText(val),
},
{
name: "message",
label: "Message",
field: "message",
align: "left",
sortable: true,
},
{
name: "client_ip",
label: "Client IP",
field: "ip_address",
align: "left",
sortable: true,
},
{ name: "message", label: "Message", field: "message", align: "left", sortable: true },
{ name: "client_ip", label: "Client IP", field: "ip_address", align: "left", sortable: true },
];
const agentActionOptions = [
@@ -292,12 +338,17 @@ export default {
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 (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;
if (actionFilter.value && actionFilter.value.length > 0)
data["actionFilter"] = actionFilter.value;
if (objectFilter.value && objectFilter.value.length > 0)
data["objectFilter"] = objectFilter.value;
try {
const { audit_logs, total } = await fetchAuditLog(data);
auditLogs.value = audit_logs;
@@ -349,7 +400,7 @@ export default {
watch([userFilter, actionFilter, timeFilter], search);
watch(
() => props.agent,
(newValue, oldValue) => {
(newValue) => {
if (newValue) {
agentFilter.value = [props.agent];
search();
@@ -389,14 +440,18 @@ export default {
clientOptions,
agentOptions,
columns,
actionOptions: props.agent ? [...agentActionOptions] : [...agentActionOptions, ...actionOptions],
actionOptions: props.agent
? [...agentActionOptions]
: [...agentActionOptions, ...actionOptions],
objectOptions,
timeOptions,
filterTypeOptions,
//computed
tableNoDataText: computed(() =>
searched.value ? "No data found. Try to refine you search" : "Click search to find audit logs"
searched.value
? "No data found. Try to refine you search"
: "Click search to find audit logs"
),
// methods

View File

@@ -1,16 +1,28 @@
<template>
<q-card>
<q-bar v-if="modal">
<q-btn @click="getDebugLog" class="q-mr-sm" dense flat push icon="refresh" />Debug Log
<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-table
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:style="{ 'max-height': tabHeight ? tabHeight : `${$q.screen.height - 33}px` }"
:style="{
'max-height': tabHeight ? tabHeight : `${$q.screen.height - 33}px`,
}"
:rows="debugLog"
:columns="columns"
:title="modal ? 'Debug Logs' : ''"
@@ -23,7 +35,15 @@
:rows-per-page-options="[0]"
>
<template v-slot:top>
<q-btn v-if="agent" class="q-pr-sm" dense flat push @click="getDebugLog" icon="refresh" />
<q-btn
v-if="agent"
class="q-pr-sm"
dense
flat
push
@click="getDebugLog"
icon="refresh"
/>
<tactical-dropdown
v-if="!agent"
class="q-pr-sm"
@@ -46,12 +66,39 @@
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-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 />
<q-input v-model="filter" outlined label="Search" dense clearable class="q-pr-sm">
<q-input
v-model="filter"
outlined
label="Search"
dense
clearable
class="q-pr-sm"
>
<template v-slot:prepend>
<q-icon name="search" color="primary" />
</template>
@@ -79,7 +126,7 @@
<script>
// composition api
import { ref, watch, computed, onMounted } from "vue";
import { ref, toRef, watch, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { useAgentDropdown } from "@/composables/agents";
import { fetchDebugLog } from "@/api/logs";
@@ -106,17 +153,35 @@ const columns = [
align: "left",
sortable: true,
},
{ name: "log_level", label: "Log Level", field: "log_level", align: "left", sortable: true },
{ name: "agent", label: "Agent", field: "agent", align: "left", sortable: true },
{
name: "log_level",
label: "Log Level",
field: "log_level",
align: "left",
sortable: true,
},
{
name: "agent",
label: "Agent",
field: "agent",
align: "left",
sortable: true,
},
{
name: "log_type",
label: "Log Type",
field: "log_type",
align: "left",
sortable: true,
format: (val, row) => formatTableColumnText(val),
format: (val) => formatTableColumnText(val),
},
{
name: "message",
label: "Message",
field: "message",
align: "left",
sortable: true,
},
{ name: "message", label: "Message", field: "message", align: "left", sortable: true },
];
export default {
@@ -144,7 +209,7 @@ export default {
// set main debug log functionality
const debugLog = ref([]);
const agentFilter = ref(null);
const agentFilter = props.agent ? toRef(props, "agent") : ref(null);
const logLevelFilter = ref("info");
const logTypeFilter = ref(null);
const loading = ref(false);
@@ -167,10 +232,9 @@ export default {
}
if (props.agent) {
agentFilter.value = props.agent;
watch(
() => props.agent,
(newValue, oldValue) => {
(newValue) => {
if (newValue) {
agentFilter.value = props.agent;
getDebugLog();

View File

@@ -2,14 +2,28 @@
<q-dialog ref="dialogRef" @hide="onDialogHide">
<q-card class="q-dialog-plugin" style="height: 70vh; min-width: 70vw">
<q-bar>
<q-btn @click="getPendingActions" class="q-mr-sm" dense flat push icon="refresh" />
{{ agent ? `Pending Actions for ${agent.hostname}` : "All Pending Actions" }}
<q-btn
@click="getPendingActions"
class="q-mr-sm"
dense
flat
push
icon="refresh"
/>
{{
agent
? `Pending Actions for ${agent.hostname}`
: "All Pending Actions"
}}
<q-space />
<q-btn dense flat icon="close" v-close-popup />
</q-bar>
<q-table
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="remote-bg-tbl-sticky"
style="max-height: 65vh"
:rows="filteredActions"
@@ -25,7 +39,11 @@
<template v-slot:top>
<q-space />
<q-btn
:label="showCompleted ? `Hide ${completedCount} Completed` : `Show ${completedCount} Completed`"
:label="
showCompleted
? `Hide ${completedCount} Completed`
: `Show ${completedCount} Completed`
"
:icon="showCompleted ? 'visibility_off' : 'visibility'"
@click="showCompleted = !showCompleted"
dense
@@ -38,7 +56,10 @@
<q-menu context-menu auto-close>
<q-list dense>
<q-item
:disable="props.row.status === 'completed' || props.row.action_type === 'agentinstall'"
:disable="
props.row.status === 'completed' ||
props.row.action_type === 'agentinstall'
"
clickable
@click="cancelPendingAction(props.row)"
>
@@ -63,9 +84,13 @@
<q-icon name="download" size="sm" />
</q-td>
<q-td v-if="props.row.status !== 'completed'">
<span v-if="props.row.action_type === 'agentupdate'">{{ getNextAgentUpdateTime() }}</span>
<span v-if="props.row.action_type === 'agentupdate'">{{
getNextAgentUpdateTime()
}}</span>
<span v-else>{{
props.row.action_type === "schedreboot" ? formatDate(props.row.due) : props.row.due
props.row.action_type === "schedreboot"
? formatDate(props.row.due)
: props.row.due
}}</span>
</q-td>
<q-td v-else>Completed</q-td>
@@ -73,7 +98,12 @@
<q-td v-if="!agent">{{ props.row.hostname }}</q-td>
<q-td v-if="!agent">{{ props.row.client }}</q-td>
<q-td v-if="!agent">{{ props.row.site }}</q-td>
<q-td v-if="props.row.action_type === 'chocoinstall' && props.row.status === 'completed'">
<q-td
v-if="
props.row.action_type === 'chocoinstall' &&
props.row.status === 'completed'
"
>
<q-btn
color="primary"
icon="preview"
@@ -95,7 +125,11 @@
import { ref, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { useQuasar, useDialogPluginComponent } from "quasar";
import { fetchPendingActions, fetchAgentPendingActions, deletePendingAction } from "@/api/logs";
import {
fetchPendingActions,
fetchAgentPendingActions,
deletePendingAction,
} from "@/api/logs";
import { getNextAgentUpdateTime } from "@/utils/format";
import { notifySuccess } from "@/utils/notify";
@@ -103,11 +137,35 @@ import { notifySuccess } from "@/utils/notify";
const columns = [
{ name: "id", field: "id" },
{ name: "status", field: "status" },
{ name: "type", label: "Type", field: "action_type", align: "left", sortable: true },
{
name: "type",
label: "Type",
field: "action_type",
align: "left",
sortable: true,
},
{ name: "due", label: "Due", field: "due", align: "left", sortable: true },
{ name: "desc", label: "Description", field: "description", align: "left", sortable: true },
{ name: "agent", label: "Agent", field: "hostname", align: "left", sortable: true },
{ name: "client", label: "Client", field: "client", align: "left", sortable: true },
{
name: "desc",
label: "Description",
field: "description",
align: "left",
sortable: true,
},
{
name: "agent",
label: "Agent",
field: "hostname",
align: "left",
sortable: true,
},
{
name: "client",
label: "Client",
field: "client",
align: "left",
sortable: true,
},
{ name: "site", label: "Site", field: "site", align: "left", sortable: true },
{ name: "details", field: "details", align: "left", sortable: false },
];
@@ -133,7 +191,8 @@ export default {
const loading = ref(false);
const completedCount = computed(() => {
try {
return actions.value.filter(action => action.status === "completed").length;
return actions.value.filter((action) => action.status === "completed")
.length;
} catch (e) {
console.error(e);
return 0;
@@ -147,7 +206,8 @@ export default {
const filteredActions = computed(() => {
if (showCompleted.value) return actions.value;
else return actions.value.filter(action => action.status !== "completed");
else
return actions.value.filter((action) => action.status !== "completed");
});
function showOutput(details) {

View File

@@ -15,7 +15,7 @@
outlined
dense
v-model="localUser.username"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
class="q-pa-none"
/>
</div>
@@ -28,7 +28,7 @@
dense
v-model="localUser.password"
:type="isPwd ? 'password' : 'text'"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
class="q-pa-none"
>
<template v-slot:append>
@@ -48,7 +48,7 @@
outlined
dense
v-model="localUser.email"
:rules="[val => isValidEmail(val) || 'Invalid email']"
:rules="[(val) => isValidEmail(val) || 'Invalid email']"
class="q-pa-none"
/>
</div>
@@ -68,13 +68,19 @@
<q-card-section class="row">
<div class="col-2">Active:</div>
<div class="col-10">
<q-checkbox v-model="localUser.is_active" :disable="localUser.username === logged_in_user" />
<q-checkbox
v-model="localUser.is_active"
:disable="localUser.username === logged_in_user"
/>
</div>
</q-card-section>
<q-card-section class="row">
<div class="col-2">Role:</div>
<template v-if="roles.length === 0"
><span>No roles have been created. Create some from Settings > Permissions Manager</span></template
><span
>No roles have been created. Create some from Settings >
Permissions Manager</span
></template
>
<template v-else
><q-select
@@ -97,7 +103,12 @@
/>
</q-card-section>
<q-card-section class="row items-center">
<q-btn :disable="!disableSave" label="Save" color="primary" type="submit" />
<q-btn
:disable="!disableSave"
label="Save"
color="primary"
type="submit"
/>
</q-card-section>
</q-form>
</q-card>
@@ -135,17 +146,17 @@ export default {
return this.user ? "Edit User" : "Add User";
},
...mapState({
logged_in_user: state => state.username,
logged_in_user: (state) => state.username,
}),
},
methods: {
getRoles() {
this.$axios
.get("/accounts/roles/")
.then(r => {
this.roles = r.data.map(role => ({ label: role.name, value: role.id }));
})
.catch(() => {});
this.$axios.get("/accounts/roles/").then((r) => {
this.roles = r.data.map((role) => ({
label: role.name,
value: role.id,
}));
});
},
onSubmit() {
this.$q.loading.show();
@@ -171,7 +182,7 @@ export default {
} else {
this.$axios
.post("/accounts/users/", this.localUser)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess(`User ${r.data} was added!`);

View File

@@ -15,7 +15,7 @@
dense
v-model="password"
:type="isPwd ? 'password' : 'text'"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
>
<template v-slot:append>
<q-icon
@@ -59,12 +59,12 @@ export default {
this.$axios
.post("/accounts/users/reset/", data)
.then(r => {
.then(() => {
this.onOk();
this.$q.loading.hide();
this.notifySuccess("User Password Reset!");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},

View File

@@ -11,7 +11,8 @@
</q-card-section>
<q-card-section>
<p class="text-subtitle1">
Download the agent then run the following command from an elevated command prompt on the device you want to add.
Download the agent then run the following command from an elevated
command prompt on the device you want to add.
</p>
<p>
<q-field outlined :color="$q.dark.isActive ? 'white' : 'black'">
@@ -38,15 +39,23 @@
</div>
<div class="q-pa-xs q-gutter-xs">
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
<code>-local-mesh "C:\\&lt;some folder or path&gt;\\meshagent.exe"</code>
<code
>-local-mesh "C:\\&lt;some folder or
path&gt;\\meshagent.exe"</code
>
</q-badge>
<span> To skip downloading the Mesh Agent during the install.</span>
</div>
<div class="q-pa-xs q-gutter-xs">
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
<code>-meshdir "C:\Program Files\Your Company Name\Mesh Agent"</code>
<code
>-meshdir "C:\Program Files\Your Company Name\Mesh Agent"</code
>
</q-badge>
<span>Specify full path to the directory containing MeshAgent.exe if using custom agent branding</span>
<span
>Specify full path to the directory containing MeshAgent.exe if
using custom agent branding</span
>
</div>
<div class="q-pa-xs q-gutter-xs">
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
@@ -74,8 +83,15 @@
</div>
</q-expansion-item>
<br />
<p class="text-italic">Note: the auth token above will be valid for {{ info.expires }} hours.</p>
<q-btn type="a" :href="info.data.url" color="primary" label="Download Agent" />
<p class="text-italic">
Note: the auth token above will be valid for {{ info.expires }} hours.
</p>
<q-btn
type="a"
:href="info.data.url"
color="primary"
label="Download Agent"
/>
</q-card-section>
</q-card>
</template>

View File

@@ -12,18 +12,32 @@
<q-card-section>
<div class="q-gutter-sm">
<q-radio dense v-model="state.mode" val="mesh" label="Mesh Agent" />
<q-radio dense v-model="state.mode" val="tacagent" label="Tactical Agent" />
<q-radio
dense
v-model="state.mode"
val="tacagent"
label="Tactical Agent"
/>
</div>
</q-card-section>
<q-card-section v-if="state.mode === 'mesh'">
Fix issues with the Mesh Agent which handles take control, live terminal and file browser.
Fix issues with the Mesh Agent which handles take control, live
terminal and file browser.
</q-card-section>
<q-card-section v-else-if="state.mode === 'tacagent'">
Fix issues with the Tactical RMM Agent service.
</q-card-section>
<q-card-actions align="right">
<q-btn dense flat push label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat push label="Recover" color="primary" type="submit" />
<q-btn
:loading="loading"
dense
flat
push
label="Recover"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -57,7 +71,10 @@ export default {
async function sendRecovery() {
loading.value = true;
try {
const result = await sendAgentRecovery(props.agent.agent_id, state.value);
const result = await sendAgentRecovery(
props.agent.agent_id,
state.value
);
notifySuccess(result);
onDialogOK();
} catch (e) {

View File

@@ -24,7 +24,7 @@
<q-card-section>
<tactical-dropdown
v-if="state.target === 'client'"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
v-model="state.client"
:options="clientOptions"
label="Select Client"
@@ -34,7 +34,7 @@
/>
<tactical-dropdown
v-else-if="state.target === 'site'"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
v-model="state.site"
:options="siteOptions"
label="Select Site"
@@ -44,7 +44,7 @@
/>
<tactical-dropdown
v-else-if="state.target === 'agents'"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
v-model="state.agents"
:options="agentOptions"
label="Select Agents"
@@ -81,7 +81,7 @@
<q-card-section v-if="mode === 'script'" class="q-pt-none">
<tactical-dropdown
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
v-model="state.script"
:options="filteredScriptOptions"
label="Select Script"
@@ -122,7 +122,7 @@
label="Custom shell"
stack-label
placeholder="/usr/bin/python3"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section v-if="mode === 'command'">
@@ -132,7 +132,7 @@
label="Command"
stack-label
:placeholder="cmdPlaceholder(state.shell)"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
@@ -145,7 +145,10 @@
style="max-width: 150px"
label="Timeout (seconds)"
stack-label
:rules="[val => !!val || '*Required', val => val >= 5 || 'Minimum is 5 seconds']"
:rules="[
(val) => !!val || '*Required',
(val) => val >= 5 || 'Minimum is 5 seconds',
]"
/>
</q-card-section>
@@ -162,14 +165,26 @@
</q-card-section>
<q-card-section v-show="false">
<q-checkbox v-model="state.offlineAgents" label="Offline Agents (Run on next checkin)">
<q-tooltip>If the agent is offline, a pending action will be created to run on agent checkin</q-tooltip>
<q-checkbox
v-model="state.offlineAgents"
label="Offline Agents (Run on next checkin)"
>
<q-tooltip
>If the agent is offline, a pending action will be created to run
on agent checkin</q-tooltip
>
</q-checkbox>
</q-card-section>
<q-card-actions align="right">
<q-btn label="Cancel" v-close-popup />
<q-btn label="Run" color="primary" type="submit" :disable="loading" :loading="loading" />
<q-btn
label="Run"
color="primary"
type="submit"
:disable="loading"
:loading="loading"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -227,7 +242,9 @@ export default {
setup(props) {
// setup vuex store
const store = useStore();
const showCommunityScripts = computed(() => store.state.showCommunityScripts);
const showCommunityScripts = computed(
() => store.state.showCommunityScripts
);
const shellOptions = computed(() => {
if (state.value.osType === "windows") {
@@ -244,8 +261,10 @@ export default {
});
const filteredOsTypeOptions = computed(() => {
if (props.mode === "command") return osTypeOptions.filter(i => i.value !== "all");
else if (props.mode === "patch") return osTypeOptions.filter(i => i.value === "windows");
if (props.mode === "command")
return osTypeOptions.filter((i) => i.value !== "all");
else if (props.mode === "patch")
return osTypeOptions.filter((i) => i.value === "windows");
return osTypeOptions;
});
@@ -253,7 +272,13 @@ export default {
const { dialogRef, onDialogHide } = useDialogPluginComponent();
// dropdown setup
const { script, scriptOptions, defaultTimeout, defaultArgs, getScriptOptions } = useScriptDropdown();
const {
script,
scriptOptions,
defaultTimeout,
defaultArgs,
getScriptOptions,
} = useScriptDropdown();
const { agents, agentOptions, getAgentOptions } = useAgentDropdown();
const { site, siteOptions, getSiteOptions } = useSiteDropdown();
const { client, clientOptions, getClientOptions } = useClientDropdown();
@@ -280,7 +305,7 @@ export default {
watch(
() => state.value.target,
(newValue, oldValue) => {
() => {
client.value = null;
site.value = null;
agents.value = [];
@@ -289,7 +314,7 @@ export default {
watch(
() => state.value.osType,
(newValue, oldValue) => {
(newValue) => {
state.value.custom_shell = null;
if (newValue === "windows") {
@@ -329,7 +354,7 @@ export default {
return removeExtraOptionCategories(
scriptOptions.value.filter(
script =>
(script) =>
script.category ||
!script.supported_platforms ||
script.supported_platforms.length === 0 ||

View File

@@ -18,7 +18,12 @@
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<div class="scroll" style="height: 65vh; max-height: 65vh">
<q-tab-panels v-model="tab" animated transition-prev="jump-up" transition-next="jump-up">
<q-tab-panels
v-model="tab"
animated
transition-prev="jump-up"
transition-next="jump-up"
>
<!-- general -->
<q-tab-panel name="general">
<q-card-section class="row">
@@ -48,12 +53,24 @@
<q-card-section class="row">
<div class="col-2">Description:</div>
<div class="col-2"></div>
<q-input outlined dense v-model="agent.description" class="col-8" />
<q-input
outlined
dense
v-model="agent.description"
class="col-8"
/>
</q-card-section>
<q-card-section class="row">
<div class="col-2">Timezone:</div>
<div class="col-2"></div>
<q-select outlined dense options-dense v-model="timezone" :options="allTimezones" class="col-8" />
<q-select
outlined
dense
options-dense
v-model="timezone"
:options="allTimezones"
class="col-8"
/>
</q-card-section>
<q-card-section class="row">
<div class="col-10">Run checks every:</div>
@@ -65,16 +82,23 @@
v-model.number="agent.check_interval"
class="col-2"
:rules="[
val => !!val || '*Required',
val => val >= 15 || 'Minimum is 15 seconds',
val => val <= 86400 || 'Maximum is 86400 seconds',
(val) => !!val || '*Required',
(val) => val >= 15 || 'Minimum is 15 seconds',
(val) => val <= 86400 || 'Maximum is 86400 seconds',
]"
/>
</q-card-section>
<q-card-section class="row">
<div class="col-10">
<q-icon class="q-pr-sm" name="fas fa-signal" size="1.2em" color="warning" /> Mark an agent as
<span class="text-weight-bold">offline</span> if it has not checked in after:
<q-icon
class="q-pr-sm"
name="fas fa-signal"
size="1.2em"
color="warning"
/>
Mark an agent as
<span class="text-weight-bold">offline</span> if it has
not checked in after:
</div>
<q-input
dense
@@ -84,16 +108,23 @@
v-model.number="agent.offline_time"
class="col-2"
:rules="[
val => !!val || '*Required',
val => val >= 2 || 'Minimum is 2 minutes',
val => val < 9999999 || 'Maximum is 9999999 minutes',
(val) => !!val || '*Required',
(val) => val >= 2 || 'Minimum is 2 minutes',
(val) => val < 9999999 || 'Maximum is 9999999 minutes',
]"
/>
</q-card-section>
<q-card-section class="row">
<div class="col-10">
<q-icon class="q-pr-sm" name="fas fa-signal" size="1.2em" color="negative" /> Mark an agent as
<span class="text-weight-bold">overdue</span> if it has not checked in after:
<q-icon
class="q-pr-sm"
name="fas fa-signal"
size="1.2em"
color="negative"
/>
Mark an agent as
<span class="text-weight-bold">overdue</span> if it has
not checked in after:
</div>
<q-input
dense
@@ -103,26 +134,39 @@
v-model.number="agent.overdue_time"
class="col-2"
:rules="[
val => !!val || '*Required',
val => val >= 3 || 'Minimum is 3 minutes',
val => val < 9999999 || 'Maximum is 9999999 minutes',
(val) => !!val || '*Required',
(val) => val >= 3 || 'Minimum is 3 minutes',
(val) => val < 9999999 || 'Maximum is 9999999 minutes',
]"
/>
</q-card-section>
<q-card-section class="row">
<q-checkbox v-model="agent.overdue_email_alert" label="Get overdue email alerts" />
<q-checkbox v-model="agent.overdue_text_alert" label="Get overdue sms alerts" />
<q-checkbox v-model="agent.overdue_dashboard_alert" label="Get overdue dashboard alerts" />
<q-checkbox
v-model="agent.overdue_email_alert"
label="Get overdue email alerts"
/>
<q-checkbox
v-model="agent.overdue_text_alert"
label="Get overdue sms alerts"
/>
<q-checkbox
v-model="agent.overdue_dashboard_alert"
label="Get overdue dashboard alerts"
/>
</q-card-section>
</q-tab-panel>
<!-- custom fields -->
<q-tab-panel name="customfields">
<div class="text-subtitle" v-if="customFields.length === 0">
No agent custom fields found. Go to **Settings > Global Settings > Custom Settings**
No agent custom fields found. Go to **Settings > Global
Settings > Custom Settings**
</div>
<q-card-section v-for="field in customFields" :key="field.id">
<CustomField v-model="custom_fields[field.name]" :field="field" />
<CustomField
v-model="custom_fields[field.name]"
:field="field"
/>
</q-card-section>
</q-tab-panel>
@@ -135,12 +179,17 @@
<q-tab-panel name="policies">
<div class="text-subtitle2">Policies</div>
<q-list separator padding dense>
<q-item v-for="(policy, key) in agent.applied_policies" :key="key">
<q-item
v-for="(policy, key) in agent.applied_policies"
:key="key"
>
<q-item-section>
<q-item-label overline>
{{ capitalize(key).split("_").join(" ") }}
</q-item-label>
<q-item-label>{{ policy ? policy.name : "None" }}</q-item-label>
<q-item-label>{{
policy ? policy.name : "None"
}}</q-item-label>
</q-item-section>
<q-item-section side v-if="policy">
<q-item-label>
@@ -154,11 +203,17 @@
<q-list dense padding>
<q-item>
<q-item-section>
<q-item-label>{{ agent.alert_template ? agent.alert_template.name : "None" }}</q-item-label>
<q-item-label>{{
agent.alert_template
? agent.alert_template.name
: "None"
}}</q-item-label>
</q-item-section>
<q-item-section side v-if="agent.alert_template">
<q-item-label>
<i>{{ agent.alert_template.is_active ? "" : "disabled" }}</i>
<i>{{
agent.alert_template.is_active ? "" : "disabled"
}}</i>
</q-item-label>
</q-item-section>
</q-item>
@@ -218,18 +273,39 @@
<q-item>
<q-item-section>
<q-item-label overline>Run Time Frequency</q-item-label>
<q-item-label>{{ capitalize(agent.effective_patch_policy.run_time_frequency) }}</q-item-label>
<q-item-label>{{
capitalize(
agent.effective_patch_policy.run_time_frequency
)
}}</q-item-label>
</q-item-section>
<q-item-section v-if="agent.effective_patch_policy.run_time_frequency === 'daily'">
<q-item-section
v-if="
agent.effective_patch_policy.run_time_frequency ===
'daily'
"
>
<q-item-label>
<b>week days:</b>
{{ weekDaystoString(agent.effective_patch_policy.run_time_days) }} <b>at hour:</b>
{{
weekDaystoString(
agent.effective_patch_policy.run_time_days
)
}}
<b>at hour:</b>
{{ agent.effective_patch_policy.run_time_hour }}
</q-item-label>
</q-item-section>
<q-item-section v-else-if="agent.effective_patch_policy.run_time_frequency === 'monthly'">
<q-item-section
v-else-if="
agent.effective_patch_policy.run_time_frequency ===
'monthly'
"
>
<q-item-label>
<b>Every month on day:</b> {{ agent.effective_patch_policy.run_time_day }} <b>at hour:</b>
<b>Every month on day:</b>
{{ agent.effective_patch_policy.run_time_day }}
<b>at hour:</b>
{{ agent.effective_patch_policy.run_time_hour }}
</q-item-label>
</q-item-section>
@@ -239,28 +315,46 @@
</q-item>
<q-item>
<q-item-section>
<q-item-label overline>Reboot after installation</q-item-label>
<q-item-label overline
>Reboot after installation</q-item-label
>
<q-item-label>{{
agent.effective_patch_policy.reboot_after_install !== "inherit"
? capitalize(agent.effective_patch_policy.reboot_after_install)
agent.effective_patch_policy.reboot_after_install !==
"inherit"
? capitalize(
agent.effective_patch_policy
.reboot_after_install
)
: "Do Nothing"
}}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label overline>Failed patch options</q-item-label>
<q-item-label v-if="agent.effective_patch_policy.reprocess_failed_inherit"
<q-item-label overline
>Failed patch options</q-item-label
>
<q-item-label
v-if="
agent.effective_patch_policy
.reprocess_failed_inherit
"
>Do Nothing</q-item-label
>
<q-item-label v-else>
<b>Reprocess failed patches:</b>
{{
agent.effective_patch_policy.reprocess_failed
? agent.effective_patch_policy.reprocess_failed_times
? agent.effective_patch_policy
.reprocess_failed_times
: "Never"
}}
<b>Email on fail:</b>
{{
agent.effective_patch_policy.email_if_fail
? "Yes"
: "Never"
}}
<b>Email on fail:</b> {{ agent.effective_patch_policy.email_if_fail ? "Yes" : "Never" }}
</q-item-label>
</q-item-section>
</q-item>
@@ -294,7 +388,7 @@ export default {
props: {
agent_id: !String,
},
setup(props) {
setup() {
// quasar dialog setup
const { dialogRef, onDialogHide } = useDialogPluginComponent();
@@ -325,9 +419,7 @@ export default {
},
methods: {
getAgentInfo() {
this.$axios
.get(`/agents/${this.agent_id}/`)
.then(r => {
this.$axios.get(`/agents/${this.agent_id}/`).then((r) => {
this.agent = r.data;
this.allTimezones = Object.freeze(r.data.all_timezones);
@@ -345,7 +437,9 @@ export default {
}
for (let field of this.customFields) {
const value = r.data.custom_fields.find(value => value.field === field.id);
const value = r.data.custom_fields.find(
(value) => value.field === field.id
);
if (field.type === "multiple") {
if (value) this.custom_fields[field.name] = value.value;
@@ -358,19 +452,17 @@ export default {
else this.custom_fields[field.name] = "";
}
}
})
.catch(e => {});
});
},
getSiteOptions() {
this.$axios
.get("/clients/")
.then(r => {
r.data.forEach(client => {
this.$axios.get("/clients/").then((r) => {
r.data.forEach((client) => {
this.siteOptions.push({ category: client.name });
client.sites.forEach(site => this.siteOptions.push({ label: site.name, value: site.id }));
client.sites.forEach((site) =>
this.siteOptions.push({ label: site.name, value: site.id })
);
});
});
})
.catch(e => {});
},
editAgent() {
delete this.agent.all_timezones;
@@ -386,14 +478,16 @@ export default {
this.$axios
.put(`/agents/${this.agent_id}/`, {
...this.agent,
custom_fields: this.formatCustomFields(this.customFields, this.custom_fields),
custom_fields: this.formatCustomFields(
this.customFields,
this.custom_fields
),
})
.then(r => {
.then(() => {
this.$refs.dialogRef.hide();
this.$emit("ok");
this.notifySuccess("Agent was edited!");
})
.catch(e => {});
});
},
weekDaystoString(array) {
if (array.length === 0) return "not set";
@@ -414,8 +508,8 @@ export default {
},
mounted() {
// Get custom fields
this.getCustomFields("agent").then(r => {
this.customFields = r.data.filter(field => !field.hide_in_ui);
this.getCustomFields("agent").then((r) => {
this.customFields = r.data.filter((field) => !field.hide_in_ui);
});
this.getAgentInfo();
this.getSiteOptions();

View File

@@ -23,7 +23,14 @@
/>
</q-card-section>
<q-card-section class="q-gutter-sm">
<q-select dense options-dense outlined label="Site" v-model="site" :options="sites" />
<q-select
dense
options-dense
outlined
label="Site"
v-model="site"
:options="sites"
/>
</q-card-section>
<q-card-section>
<div class="q-gutter-sm">
@@ -49,8 +56,17 @@
</q-card-section>
<q-card-section>
<div class="q-gutter-sm">
<q-radio v-model="agenttype" val="server" label="Server" @update:model-value="power = false" />
<q-radio v-model="agenttype" val="workstation" label="Workstation" />
<q-radio
v-model="agenttype"
val="server"
label="Server"
@update:model-value="power = false"
/>
<q-radio
v-model="agenttype"
val="workstation"
label="Workstation"
/>
</div>
</q-card-section>
<q-card-section>
@@ -70,20 +86,57 @@
<div class="q-gutter-sm">
<q-checkbox v-model="rdp" dense label="Enable RDP" />
<q-checkbox v-model="ping" dense label="Enable Ping">
<q-tooltip> Enable ICMP echo requests in the local firewall </q-tooltip>
<q-tooltip>
Enable ICMP echo requests in the local firewall
</q-tooltip>
</q-checkbox>
<q-checkbox v-model="power" dense v-show="agenttype === 'workstation'" label="Disable sleep/hibernate" />
<q-checkbox
v-model="power"
dense
v-show="agenttype === 'workstation'"
label="Disable sleep/hibernate"
/>
</div>
</q-card-section>
<q-card-section>
Arch
<div class="q-gutter-sm">
<q-radio v-model="arch" val="64" label="64 bit" v-show="agentOS === 'windows'" />
<q-radio v-model="arch" val="32" label="32 bit" v-show="agentOS === 'windows'" />
<q-radio v-model="arch" val="amd64" label="64 bit" v-show="agentOS !== 'windows'" />
<q-radio v-model="arch" val="386" label="32 bit" v-show="agentOS !== 'windows'" />
<q-radio v-model="arch" val="arm64" label="ARM 64 bit" v-show="agentOS !== 'windows'" />
<q-radio v-model="arch" val="arm" label="ARM 32 bit (Rasp Pi)" v-show="agentOS !== 'windows'" />
<q-radio
v-model="arch"
val="64"
label="64 bit"
v-show="agentOS === 'windows'"
/>
<q-radio
v-model="arch"
val="32"
label="32 bit"
v-show="agentOS === 'windows'"
/>
<q-radio
v-model="arch"
val="amd64"
label="64 bit"
v-show="agentOS !== 'windows'"
/>
<q-radio
v-model="arch"
val="386"
label="32 bit"
v-show="agentOS !== 'windows'"
/>
<q-radio
v-model="arch"
val="arm64"
label="ARM 64 bit"
v-show="agentOS !== 'windows'"
/>
<q-radio
v-model="arch"
val="arm"
label="ARM 32 bit (Rasp Pi)"
v-show="agentOS !== 'windows'"
/>
</div>
</q-card-section>
<q-card-section>
@@ -95,8 +148,18 @@
v-show="agentOS === 'windows'"
label="Dynamically generated exe"
/>
<q-radio v-model="installMethod" val="powershell" v-show="agentOS === 'windows'" label="Powershell" />
<q-radio v-model="installMethod" val="manual" v-show="agentOS === 'windows'" label="Manual" />
<q-radio
v-model="installMethod"
val="powershell"
v-show="agentOS === 'windows'"
label="Powershell"
/>
<q-radio
v-model="installMethod"
val="manual"
v-show="agentOS === 'windows'"
label="Manual"
/>
</div>
</q-card-section>
<q-card-actions align="left">
@@ -144,11 +207,11 @@ export default {
this.$q.loading.show();
this.$axios
.get("/clients/")
.then(r => {
.then((r) => {
this.client_options = this.formatClientOptions(r.data);
if (this.sitepk !== undefined && this.sitepk !== null) {
this.client_options.forEach(client => {
let site = client.sites.find(site => site.id === this.sitepk);
this.client_options.forEach((client) => {
let site = client.sites.find((site) => site.id === this.sitepk);
if (site !== undefined) {
this.client = client;
@@ -197,25 +260,24 @@ export default {
};
if (this.installMethod === "manual") {
this.$axios
.post("/agents/installer/", data)
.then(r => {
this.$axios.post("/agents/installer/", data).then((r) => {
this.info = {
expires: this.expires,
data: r.data,
arch: this.arch,
};
this.showAgentDownload = true;
})
.catch(e => {});
});
} else if (this.installMethod === "exe") {
this.$q.loading.show({ message: "Generating executable..." });
this.$axios
.post("/agents/installer/", data, { responseType: "blob" })
.then(r => {
.then((r) => {
this.$q.loading.hide();
const blob = new Blob([r.data], { type: "application/vnd.microsoft.portable-executable" });
const blob = new Blob([r.data], {
type: "application/vnd.microsoft.portable-executable",
});
let link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
@@ -225,7 +287,10 @@ export default {
.catch(() => {
this.$q.loading.hide();
});
} else if (this.installMethod === "powershell" || this.installMethod === "linux") {
} else if (
this.installMethod === "powershell" ||
this.installMethod === "linux"
) {
this.$q.loading.show();
let ext = this.installMethod === "powershell" ? "ps1" : "sh";
const scriptName = `rmm-${clientStripped}-${siteStripped}-${this.agenttype}.${ext}`;

View File

@@ -89,7 +89,10 @@
map-options
/>
</q-card-section>
<q-card-section class="row" v-if="winupdatepolicy.run_time_frequency === 'monthly'">
<q-card-section
class="row"
v-if="winupdatepolicy.run_time_frequency === 'monthly'"
>
<div class="col-3">Day of month to run:</div>
<div class="col-4"></div>
<q-select
@@ -103,7 +106,10 @@
map-options
/>
</q-card-section>
<q-card-section class="row" v-show="winupdatepolicy.run_time_frequency !== 'inherit'">
<q-card-section
class="row"
v-show="winupdatepolicy.run_time_frequency !== 'inherit'"
>
<div class="col-3">Scheduled Time:</div>
<div class="col-4"></div>
<q-select
@@ -121,13 +127,41 @@
v-show="winupdatepolicy.run_time_frequency !== 'inherit'"
>
<div class="q-gutter-sm">
<q-checkbox v-model="winupdatepolicy.run_time_days" :val="1" label="Monday" />
<q-checkbox v-model="winupdatepolicy.run_time_days" :val="2" label="Tuesday" />
<q-checkbox v-model="winupdatepolicy.run_time_days" :val="3" label="Wednesday" />
<q-checkbox v-model="winupdatepolicy.run_time_days" :val="4" label="Thursday" />
<q-checkbox v-model="winupdatepolicy.run_time_days" :val="5" label="Friday" />
<q-checkbox v-model="winupdatepolicy.run_time_days" :val="6" label="Saturday" />
<q-checkbox v-model="winupdatepolicy.run_time_days" :val="0" label="Sunday" />
<q-checkbox
v-model="winupdatepolicy.run_time_days"
:val="1"
label="Monday"
/>
<q-checkbox
v-model="winupdatepolicy.run_time_days"
:val="2"
label="Tuesday"
/>
<q-checkbox
v-model="winupdatepolicy.run_time_days"
:val="3"
label="Wednesday"
/>
<q-checkbox
v-model="winupdatepolicy.run_time_days"
:val="4"
label="Thursday"
/>
<q-checkbox
v-model="winupdatepolicy.run_time_days"
:val="5"
label="Friday"
/>
<q-checkbox
v-model="winupdatepolicy.run_time_days"
:val="6"
label="Saturday"
/>
<q-checkbox
v-model="winupdatepolicy.run_time_days"
:val="0"
label="Sunday"
/>
</div>
</q-card-section>
<!-- Reboot After Installation -->
@@ -151,12 +185,21 @@
<q-separator />
<q-card-section class="row" v-if="!policy">
<div class="col-5">
<q-checkbox v-model="winupdatepolicy.reprocess_failed_inherit" label="Inherit failed patch settings" />
<q-checkbox
v-model="winupdatepolicy.reprocess_failed_inherit"
label="Inherit failed patch settings"
/>
</div>
</q-card-section>
<q-card-section class="row" v-show="!winupdatepolicy.reprocess_failed_inherit">
<q-card-section
class="row"
v-show="!winupdatepolicy.reprocess_failed_inherit"
>
<div class="col-5">
<q-checkbox v-model="winupdatepolicy.reprocess_failed" label="Reprocess failed patches" />
<q-checkbox
v-model="winupdatepolicy.reprocess_failed"
label="Reprocess failed patches"
/>
</div>
<div class="col-3">
@@ -166,17 +209,25 @@
type="number"
filled
label="Times"
:rules="[val => val > 0 || 'Must be greater than 0']"
:rules="[(val) => val > 0 || 'Must be greater than 0']"
/>
</div>
<div class="col-3"></div>
<q-checkbox v-model="winupdatepolicy.email_if_fail" label="Send an email when patch installation fails" />
<q-checkbox
v-model="winupdatepolicy.email_if_fail"
label="Send an email when patch installation fails"
/>
</q-card-section>
<q-card-actions align="left" v-if="policy">
<q-btn label="Submit" color="primary" @click="submit" />
<q-btn label="Cancel" @click="$emit('hide')" />
<q-space />
<q-btn v-if="editing" label="Remove Policy" color="negative" @click="deletePolicy(winupdatepolicy)" />
<q-btn
v-if="editing"
label="Remove Policy"
color="negative"
@click="deletePolicy(winupdatepolicy)"
/>
</q-card-actions>
</div>
</template>
@@ -240,25 +291,28 @@ export default {
// editing patch policy
if (this.editing) {
this.$axios
.put(`/automation/patchpolicy/${this.winupdatepolicy.id}/`, this.winupdatepolicy)
.then(response => {
.put(
`/automation/patchpolicy/${this.winupdatepolicy.id}/`,
this.winupdatepolicy
)
.then(() => {
this.$q.loading.hide();
this.$emit("close");
this.notifySuccess("Patch policy was edited successfully!");
})
.catch(error => {
.catch(() => {
this.$q.loading.hide();
});
} else {
// adding patch policy
this.$axios
.post("/automation/patchpolicy/", this.winupdatepolicy)
.then(response => {
.then(() => {
this.$q.loading.hide();
this.$emit("close");
this.notifySuccess("Patch policy was created successfully!");
})
.catch(error => {
.catch(() => {
this.$q.loading.hide();
});
}
@@ -275,12 +329,12 @@ export default {
this.$q.loading.show();
this.$axios
.delete(`/automation/patchpolicy/${policy.id}/`)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.$emit("close");
this.notifySuccess("Patch policy was deleted successfully!");
})
.catch(error => {
.catch(() => {
this.$q.loading.hide();
});
});

View File

@@ -21,7 +21,15 @@
</q-card-section>
<q-card-actions align="right">
<q-btn dense flat push label="Cancel" v-close-popup />
<q-btn :loading="loading" dense flat push label="Schedule Reboot" color="primary" @click="scheduleReboot" />
<q-btn
:loading="loading"
dense
flat
push
label="Schedule Reboot"
color="primary"
@click="scheduleReboot"
/>
</q-card-actions>
</q-card>
</q-dialog>
@@ -55,7 +63,7 @@ export default {
loading.value = true;
try {
const result = await scheduleAgentReboot(props.agent.agent_id, state.value);
await scheduleAgentReboot(props.agent.agent_id, state.value);
$q.dialog({
title: "Reboot pending",
style: "width: 40vw",

View File

@@ -1,14 +1,36 @@
<template>
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent @keydown.esc="onDialogHide" :maximized="maximized">
<q-dialog
ref="dialogRef"
@hide="onDialogHide"
persistent
@keydown.esc="onDialogHide"
:maximized="maximized"
>
<q-card class="dialog-plugin" style="min-width: 60vw">
<q-bar>
Run a script on {{ agent.hostname }}
<q-space />
<q-btn dense flat icon="minimize" @click="maximized = false" :disable="!maximized">
<q-tooltip v-if="maximized" class="bg-white text-primary">Minimize</q-tooltip>
<q-btn
dense
flat
icon="minimize"
@click="maximized = false"
:disable="!maximized"
>
<q-tooltip v-if="maximized" class="bg-white text-primary"
>Minimize</q-tooltip
>
</q-btn>
<q-btn dense flat icon="crop_square" @click="maximized = true" :disable="maximized">
<q-tooltip v-if="!maximized" class="bg-white text-primary">Maximize</q-tooltip>
<q-btn
dense
flat
icon="crop_square"
@click="maximized = true"
:disable="maximized"
>
<q-tooltip v-if="!maximized" class="bg-white text-primary"
>Maximize</q-tooltip
>
</q-btn>
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
@@ -17,7 +39,7 @@
<q-form @submit.prevent="sendScript">
<q-card-section>
<tactical-dropdown
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
v-model="state.script"
:options="filteredScriptOptions"
label="Select script"
@@ -26,8 +48,19 @@
filterable
>
<template v-slot:after>
<q-btn size="sm" round dense flat icon="info" @click="openScriptURL">
<q-tooltip v-if="syntax" class="bg-white text-primary text-body1" v-html="formatScriptSyntax(syntax)" />
<q-btn
size="sm"
round
dense
flat
icon="info"
@click="openScriptURL"
>
<q-tooltip
v-if="syntax"
class="bg-white text-primary text-body1"
v-html="formatScriptSyntax(syntax)"
/>
</q-btn>
</template>
</tactical-dropdown>
@@ -45,15 +78,33 @@
/>
</q-card-section>
<q-card-section>
<q-option-group v-model="state.output" :options="outputOptions" color="primary" inline dense />
<q-option-group
v-model="state.output"
:options="outputOptions"
color="primary"
inline
dense
/>
</q-card-section>
<q-card-section v-if="state.output === 'email'">
<div class="q-gutter-sm">
<q-radio dense v-model="state.emailMode" val="default" label="Use email addresses from global settings" />
<q-radio dense v-model="state.emailMode" val="custom" label="Custom emails" />
<q-radio
dense
v-model="state.emailMode"
val="default"
label="Use email addresses from global settings"
/>
<q-radio
dense
v-model="state.emailMode"
val="custom"
label="Custom emails"
/>
</div>
</q-card-section>
<q-card-section v-if="state.emailMode === 'custom' && state.output === 'email'">
<q-card-section
v-if="state.emailMode === 'custom' && state.output === 'email'"
>
<tactical-dropdown
v-model="state.emails"
label="Email recipients (press Enter after typing each email)"
@@ -67,7 +118,7 @@
</q-card-section>
<q-card-section v-if="state.output === 'collector'">
<tactical-dropdown
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
outlined
v-model="state.custom_field"
:options="customFieldOptions"
@@ -86,14 +137,27 @@
style="max-width: 150px"
label="Timeout (seconds)"
stack-label
:rules="[val => !!val || '*Required', val => val >= 5 || 'Minimum is 5 seconds']"
:rules="[
(val) => !!val || '*Required',
(val) => val >= 5 || 'Minimum is 5 seconds',
]"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn label="Cancel" v-close-popup />
<q-btn :loading="loading" :disabled="loading" label="Run" color="primary" type="submit" />
<q-btn
:loading="loading"
:disabled="loading"
label="Run"
color="primary"
type="submit"
/>
</q-card-actions>
<q-card-section v-if="ret !== null" class="q-pl-md q-pr-md q-pt-none q-ma-none scroll" style="max-height: 50vh">
<q-card-section
v-if="ret !== null"
class="q-pl-md q-pr-md q-pt-none q-ma-none scroll"
style="max-height: 50vh"
>
<pre>{{ ret }}</pre>
</q-card-section>
</q-form>
@@ -109,7 +173,10 @@ import { useScriptDropdown } from "@/composables/scripts";
import { useCustomFieldDropdown } from "@/composables/core";
import { runScript } from "@/api/agents";
import { notifySuccess } from "@/utils/notify";
import { formatScriptSyntax, removeExtraOptionCategories } from "@/utils/format";
import {
formatScriptSyntax,
removeExtraOptionCategories,
} from "@/utils/format";
//ui imports
import TacticalDropdown from "@/components/ui/TacticalDropdown";
@@ -136,7 +203,8 @@ export default {
const { dialogRef, onDialogHide } = useDialogPluginComponent();
// setup dropdowns
const { script, scriptOptions, defaultTimeout, defaultArgs, syntax, link } = useScriptDropdown(props.script, {
const { script, scriptOptions, defaultTimeout, defaultArgs, syntax, link } =
useScriptDropdown(props.script, {
onMount: true,
filterByPlatform: props.agent.plat,
});
@@ -177,7 +245,7 @@ export default {
const filteredScriptOptions = computed(() => {
return removeExtraOptionCategories(
scriptOptions.value.filter(
script =>
(script) =>
script.category ||
!script.supported_platforms ||
script.supported_platforms.length === 0 ||
@@ -187,7 +255,10 @@ export default {
});
// watchers
watch([() => state.value.output, () => state.value.emailMode], () => (state.value.emails = []));
watch(
[() => state.value.output, () => state.value.emailMode],
() => (state.value.emails = [])
);
return {
// reactive data

View File

@@ -1,6 +1,14 @@
<template>
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent @keydown.esc="onDialogHide">
<q-card class="q-dialog-plugin" :style="{ 'min-width': !ret ? '40vw' : '70vw' }">
<q-dialog
ref="dialogRef"
@hide="onDialogHide"
persistent
@keydown.esc="onDialogHide"
>
<q-card
class="q-dialog-plugin"
:style="{ 'min-width': !ret ? '40vw' : '70vw' }"
>
<q-bar>
Send command on {{ agent.hostname }}
<q-space />
@@ -20,9 +28,27 @@
label="Bash"
@update:model-value="state.custom_shell = null"
/>
<q-radio v-if="agent.plat !== 'windows'" dense v-model="state.shell" val="custom" label="Custom" />
<q-radio v-if="agent.plat === 'windows'" dense v-model="state.shell" val="cmd" label="CMD" />
<q-radio v-if="agent.plat === 'windows'" dense v-model="state.shell" val="powershell" label="Powershell" />
<q-radio
v-if="agent.plat !== 'windows'"
dense
v-model="state.shell"
val="custom"
label="Custom"
/>
<q-radio
v-if="agent.plat === 'windows'"
dense
v-model="state.shell"
val="cmd"
label="CMD"
/>
<q-radio
v-if="agent.plat === 'windows'"
dense
v-model="state.shell"
val="powershell"
label="Powershell"
/>
</div>
</q-card-section>
<q-card-section v-if="state.shell === 'custom'">
@@ -32,7 +58,7 @@
label="Custom shell"
stack-label
placeholder="/usr/bin/python3"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section>
@@ -45,9 +71,9 @@
label="Timeout (seconds)"
stack-label
:rules="[
val => !!val || '*Required',
val => val >= 10 || 'Minimum is 10 seconds',
val => val <= 3600 || 'Maximum is 3600 seconds',
(val) => !!val || '*Required',
(val) => val >= 10 || 'Minimum is 10 seconds',
(val) => val <= 3600 || 'Maximum is 3600 seconds',
]"
/>
</q-card-section>
@@ -58,14 +84,26 @@
label="Command"
stack-label
:placeholder="cmdPlaceholder(state.shell)"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn flat dense push label="Cancel" v-close-popup />
<q-btn :loading="loading" flat dense push label="Send" color="primary" type="submit" />
<q-btn
:loading="loading"
flat
dense
push
label="Send"
color="primary"
type="submit"
/>
</q-card-actions>
<q-card-section v-if="ret !== null" class="q-pl-md q-pr-md q-pt-none q-ma-none scroll" style="max-height: 50vh">
<q-card-section
v-if="ret !== null"
class="q-pl-md q-pr-md q-pt-none q-ma-none scroll"
style="max-height: 50vh"
>
<pre>{{ ret }}</pre>
</q-card-section>
</q-form>

View File

@@ -16,14 +16,32 @@
</q-banner>
<q-card-section>
Select Version
<q-select square disable dense options-dense outlined v-model="version" :options="versions" />
<q-select
square
disable
dense
options-dense
outlined
v-model="version"
:options="versions"
/>
</q-card-section>
<q-card-section v-show="version !== null">
Select Agent
<br />
<q-separator />
<q-checkbox v-model="selectAll" label="Select All" @update:model-value="selectAllAction" />
<q-btn v-show="group.length !== 0" label="Update" color="primary" @click="update" class="q-ml-xl" />
<q-checkbox
v-model="selectAll"
label="Select All"
@update:model-value="selectAllAction"
/>
<q-btn
v-show="group.length !== 0"
label="Update"
color="primary"
@click="update"
class="q-ml-xl"
/>
<q-separator />
<q-option-group
v-model="group"
@@ -60,7 +78,7 @@ export default {
this.$q.loading.show();
this.$axios
.get("/agents/versions/")
.then(r => {
.then((r) => {
this.versions = r.data.versions;
this.version = r.data.versions[0];
this.agents = r.data.agents;
@@ -72,18 +90,15 @@ export default {
},
update() {
const data = { agent_ids: this.group };
this.$axios
.post("/agents/update/", data)
.then(r => {
this.$axios.post("/agents/update/", data).then(() => {
this.$emit("close");
this.notifySuccess("Agents will now be updated");
})
.catch(e => {});
});
},
},
computed: {
agentIds() {
return this.agents.map(k => k.agent_id);
return this.agents.map((k) => k.agent_id);
},
agentOptions() {
const options = [];

View File

@@ -47,8 +47,14 @@
</q-card-section>
<q-card-section>
<q-checkbox v-model="localTemplate.exclude_workstations" label="Exclude Workstations" />
<q-checkbox v-model="localTemplate.exclude_servers" label="Exclude Servers" />
<q-checkbox
v-model="localTemplate.exclude_workstations"
label="Exclude Workstations"
/>
<q-checkbox
v-model="localTemplate.exclude_servers"
label="Exclude Servers"
/>
</q-card-section>
<q-card-actions align="right">
@@ -90,12 +96,12 @@ export default {
this.$q.loading.show();
this.$axios
.put(`alerts/templates/${this.template.id}/`, this.localTemplate)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Alert Template exclusions added");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -103,12 +109,17 @@ export default {
this.$q.loading.show();
this.$axios
.get("/clients/")
.then(r => {
this.clientOptions = r.data.map(client => ({ label: client.name, value: client.id }));
.then((r) => {
this.clientOptions = r.data.map((client) => ({
label: client.name,
value: client.id,
}));
r.data.forEach(client => {
r.data.forEach((client) => {
this.siteOptions.push({ category: client.name });
client.sites.forEach(site => this.siteOptions.push({ label: site.name, value: site.id }));
client.sites.forEach((site) =>
this.siteOptions.push({ label: site.name, value: site.id })
);
});
this.$q.loading.hide();
})
@@ -117,7 +128,9 @@ export default {
});
},
getOptions() {
this.getAgentOptions("id").then(options => (this.agentOptions = Object.freeze(options)));
this.getAgentOptions("id").then(
(options) => (this.agentOptions = Object.freeze(options))
);
this.getClientsandSites();
},
show() {
@@ -141,7 +154,8 @@ export default {
this.localTemplate.excluded_sites = this.template.excluded_sites;
this.localTemplate.excluded_agents = this.template.excluded_agents;
this.localTemplate.exclude_servers = this.template.exclude_servers;
this.localTemplate.exclude_workstations = this.template.exclude_workstations;
this.localTemplate.exclude_workstations =
this.template.exclude_workstations;
this.getOptions();
},
};

View File

@@ -22,10 +22,18 @@
>
</q-select>
</q-card-section>
<q-card-section v-else> No Alert Templates have been setup. Go to Settings > Alerts Manager </q-card-section>
<q-card-section v-else>
No Alert Templates have been setup. Go to Settings > Alerts Manager
</q-card-section>
<q-card-actions align="right">
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn v-if="options.length > 0" flat label="Submit" color="primary" type="submit" />
<q-btn
v-if="options.length > 0"
flat
label="Submit"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
@@ -86,12 +94,12 @@ export default {
const text = this.selectedTemplate ? "assigned" : "removed";
this.$axios
.put(url, data)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess(`Alert Template ${text} successfully!`);
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -99,14 +107,14 @@ export default {
this.$q.loading.show();
this.$axios
.get("/alerts/templates/")
.then(r => {
this.options = r.data.map(template => ({
.then((r) => {
this.options = r.data.map((template) => ({
label: template.name,
value: template.id,
}));
this.$q.loading.hide();
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},

View File

@@ -8,8 +8,20 @@
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
</q-btn>
</q-bar>
<q-stepper v-model="step" ref="stepper" alternative-labels header-nav color="primary" animated>
<q-step :name="1" :error="!template.name && step > 1" title="General Settings" icon="settings">
<q-stepper
v-model="step"
ref="stepper"
alternative-labels
header-nav
color="primary"
animated
>
<q-step
:name="1"
:error="!template.name && step > 1"
title="General Settings"
icon="settings"
>
<q-card flat>
<q-card-section>
<q-input
@@ -18,30 +30,52 @@
outlined
dense
v-model="template.name"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section>
<q-toggle v-model="template.is_active" color="green" label="Enabled" left-label />
<q-toggle
v-model="template.is_active"
color="green"
label="Enabled"
left-label
/>
</q-card-section>
<div class="q-pl-md text-subtitle1">Email Settings (Overrides global email settings)</div>
<div class="q-pl-md text-subtitle1">
Email Settings (Overrides global email settings)
</div>
<q-card-section>
<q-input label="Email From address" class="q-mb-sm" outlined dense v-model="template.email_from" />
<q-input
label="Email From address"
class="q-mb-sm"
outlined
dense
v-model="template.email_from"
/>
</q-card-section>
<q-card-section class="row">
<div class="col-2 q-mb-sm">Email recipients</div>
<div class="col-4 q-mb-sm">
<q-list dense v-if="template.email_recipients.length !== 0">
<q-item v-for="email in template.email_recipients" :key="email" dense>
<q-item
v-for="email in template.email_recipients"
:key="email"
dense
>
<q-item-section>
<q-item-label>{{ email }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon class="cursor-pointer" name="delete" color="red" @click="removeEmail(email)" />
<q-icon
class="cursor-pointer"
name="delete"
color="red"
@click="removeEmail(email)"
/>
</q-item-section>
</q-item>
</q-list>
@@ -53,22 +87,39 @@
</div>
<div class="col-3 q-mb-sm"></div>
<div class="col-3 q-mb-sm">
<q-btn size="sm" icon="fas fa-plus" color="secondary" label="Add email" @click="toggleAddEmail" />
<q-btn
size="sm"
icon="fas fa-plus"
color="secondary"
label="Add email"
@click="toggleAddEmail"
/>
</div>
</q-card-section>
<div class="q-pl-md text-subtitle1">SMS Settings (Overrides global SMS settings)</div>
<div class="q-pl-md text-subtitle1">
SMS Settings (Overrides global SMS settings)
</div>
<q-card-section class="row">
<div class="col-2 q-mb-sm">SMS recipients</div>
<div class="col-4 q-mb-md">
<q-list dense v-if="template.text_recipients.length !== 0">
<q-item v-for="num in template.text_recipients" :key="num" dense>
<q-item
v-for="num in template.text_recipients"
:key="num"
dense
>
<q-item-section>
<q-item-label>{{ num }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon class="cursor-pointer" name="delete" color="red" @click="removeSMSNumber(num)" />
<q-icon
class="cursor-pointer"
name="delete"
color="red"
@click="removeSMSNumber(num)"
/>
</q-item-section>
</q-item>
</q-list>
@@ -99,7 +150,8 @@
<span style="text-decoration: underline; cursor: help"
>Alert Failure Settings
<q-tooltip>
The selected script will run when an alert is triggered. This script will run on any online agent.
The selected script will run when an alert is triggered. This
script will run on any online agent.
</q-tooltip>
</span>
</div>
@@ -119,14 +171,22 @@
@update:model-value="setScriptDefaults('failure')"
>
<template v-slot:option="scope">
<q-item v-if="!scope.opt.category" v-bind="scope.itemProps" class="q-pl-lg">
<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>
<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>
@@ -152,9 +212,9 @@
v-model.number="template.action_timeout"
dense
:rules="[
val => !!val || 'Failure action timeout is required',
val => val > 0 || 'Timeout must be greater than 0',
val => val <= 60 || 'Timeout must be 60 or less',
(val) => !!val || 'Failure action timeout is required',
(val) => val > 0 || 'Timeout must be greater than 0',
(val) => val <= 60 || 'Timeout must be 60 or less',
]"
/>
</q-card-section>
@@ -163,7 +223,8 @@
<span style="text-decoration: underline; cursor: help"
>Alert Resolved Settings
<q-tooltip>
The selected script will run when an alert is resolved. This script will run on any online agent.
The selected script will run when an alert is resolved. This
script will run on any online agent.
</q-tooltip>
</span>
</div>
@@ -183,14 +244,22 @@
@update:model-value="setScriptDefaults('resolved')"
>
<template v-slot:option="scope">
<q-item v-if="!scope.opt.category" v-bind="scope.itemProps" class="q-pl-lg">
<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>
<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>
@@ -216,9 +285,9 @@
v-model.number="template.resolved_action_timeout"
dense
:rules="[
val => !!val || 'Resolved action timeout is required',
val => val > 0 || 'Timeout must be greater than 0',
val => val <= 60 || 'Timeout must be 60 or less',
(val) => !!val || 'Resolved action timeout is required',
(val) => val > 0 || 'Timeout must be greater than 0',
(val) => val <= 60 || 'Timeout must be 60 or less',
]"
/>
</q-card-section>
@@ -226,16 +295,34 @@
<div class="q-pl-md text-subtitle1">
<span style="text-decoration: underline; cursor: help"
>Run actions only on
<q-tooltip> The selected script will only run on the following types of alerts </q-tooltip>
<q-tooltip>
The selected script will only run on the following types of
alerts
</q-tooltip>
</span>
</div>
<q-card-section>
<q-toggle v-model="template.agent_script_actions" label="Agents" color="green" left-label />
<q-toggle
v-model="template.agent_script_actions"
label="Agents"
color="green"
left-label
/>
<q-toggle v-model="template.check_script_actions" label="Checks" color="green" left-label />
<q-toggle
v-model="template.check_script_actions"
label="Checks"
color="green"
left-label
/>
<q-toggle v-model="template.task_script_actions" label="Tasks" color="green" left-label />
<q-toggle
v-model="template.task_script_actions"
label="Tasks"
color="green"
left-label
/>
</q-card-section>
</q-card>
</q-step>
@@ -246,9 +333,11 @@
<span style="text-decoration: underline; cursor: help"
>Alert Failure Settings
<q-tooltip>
Select what notifications should be sent when an agent is overdue. Enabled will override the agent
notification setting sand always notify. Not configured will use what notification settings are
configured on the agent. Disabled will override the agent notification settings and never notify.
Select what notifications should be sent when an agent is
overdue. Enabled will override the agent notification setting
sand always notify. Not configured will use what notification
settings are configured on the agent. Disabled will override
the agent notification settings and never notify.
</q-tooltip>
</span>
</div>
@@ -282,19 +371,34 @@
type="number"
v-model.number="template.agent_periodic_alert_days"
dense
:rules="[val => val >= 0 || 'Periodic days must be 0 or greater']"
:rules="[
(val) => val >= 0 || 'Periodic days must be 0 or greater',
]"
/>
</q-card-section>
<div class="q-pl-md text-subtitle1">
<span style="text-decoration: underline; cursor: help"
>Alert Resolved Settings
<q-tooltip> Select what notifications should be sent when an overdue agent is back online. </q-tooltip>
<q-tooltip>
Select what notifications should be sent when an overdue agent
is back online.
</q-tooltip>
</span>
</div>
<q-card-section>
<q-toggle v-model="template.agent_email_on_resolved" label="Email" color="green" left-label />
<q-toggle v-model="template.agent_text_on_resolved" label="Text" color="green" left-label />
<q-toggle
v-model="template.agent_email_on_resolved"
label="Email"
color="green"
left-label
/>
<q-toggle
v-model="template.agent_text_on_resolved"
label="Text"
color="green"
left-label
/>
</q-card-section>
</q-card>
</q-step>
@@ -305,9 +409,11 @@
<span style="text-decoration: underline; cursor: help"
>Alert Failure Settings
<q-tooltip>
Select what notifications are sent when a check fails. Enabled will override the check notification
settings and always notify. Not configured will use the notification settings configured on the check.
Disabled will override the check notification settings and never notify.
Select what notifications are sent when a check fails. Enabled
will override the check notification settings and always
notify. Not configured will use the notification settings
configured on the check. Disabled will override the check
notification settings and never notify.
</q-tooltip>
</span>
</div>
@@ -391,19 +497,34 @@
type="number"
v-model.number="template.check_periodic_alert_days"
dense
:rules="[val => val >= 0 || 'Periodic days must be 0 or greater']"
:rules="[
(val) => val >= 0 || 'Periodic days must be 0 or greater',
]"
/>
</q-card-section>
<div class="q-pl-md text-subtitle1">
<span style="text-decoration: underline; cursor: help"
>Alert Resolved Settings
<q-tooltip> Select what notifications are sent when a failed check is resolved. </q-tooltip>
<q-tooltip>
Select what notifications are sent when a failed check is
resolved.
</q-tooltip>
</span>
</div>
<q-card-section>
<q-toggle v-model="template.check_email_on_resolved" label="Email" color="green" left-label />
<q-toggle v-model="template.check_text_on_resolved" label="Text" color="green" left-label />
<q-toggle
v-model="template.check_email_on_resolved"
label="Email"
color="green"
left-label
/>
<q-toggle
v-model="template.check_text_on_resolved"
label="Text"
color="green"
left-label
/>
</q-card-section>
</q-card>
</q-step>
@@ -414,9 +535,11 @@
<span style="text-decoration: underline; cursor: help"
>Alert Failure Settings
<q-tooltip>
Select what notifications are sent when an automated task fails. Enabled will override the task
notification settings and always notify. Not configured will use the notification settings configured
on the task. Disabled will override the task notification settings and never notify.
Select what notifications are sent when an automated task
fails. Enabled will override the task notification settings
and always notify. Not configured will use the notification
settings configured on the task. Disabled will override the
task notification settings and never notify.
</q-tooltip>
</span>
</div>
@@ -500,19 +623,34 @@
type="number"
v-model.number="template.task_periodic_alert_days"
dense
:rules="[val => val >= 0 || 'Periodic days must be 0 or greater']"
:rules="[
(val) => val >= 0 || 'Periodic days must be 0 or greater',
]"
/>
</q-card-section>
<div class="q-pl-md text-subtitle1">
<span style="text-decoration: underline; cursor: help"
>Alert Resolved Settings
<q-tooltip> Select what notifications are sent when a failed task is resolved. </q-tooltip>
<q-tooltip>
Select what notifications are sent when a failed task is
resolved.
</q-tooltip>
</span>
</div>
<q-card-section>
<q-toggle v-model="template.task_email_on_resolved" label="Email" color="green" left-label />
<q-toggle v-model="template.check_text_on_resolved" label="Text" color="green" left-label />
<q-toggle
v-model="template.task_email_on_resolved"
label="Email"
color="green"
left-label
/>
<q-toggle
v-model="template.check_text_on_resolved"
label="Text"
color="green"
left-label
/>
</q-card-section>
</q-card>
</q-step>
@@ -526,7 +664,12 @@
label="Back"
class="q-mr-xs"
/>
<q-btn v-if="step < 5" @click="$refs.stepper.next()" color="primary" label="Next" />
<q-btn
v-if="step < 5"
@click="$refs.stepper.next()"
color="primary"
label="Next"
/>
<q-space />
<q-btn @click="onSubmit" color="primary" label="Submit" />
</q-stepper-navigation>
@@ -615,10 +758,14 @@ export default {
methods: {
setScriptDefaults(type) {
if (type === "failure") {
const script = this.scriptOptions.find(i => i.value === this.template.action);
const script = this.scriptOptions.find(
(i) => i.value === this.template.action
);
this.template.action_args = script.args;
} else if (type === "resolved") {
const script = this.scriptOptions.find(i => i.value === this.template.resolved_action);
const script = this.scriptOptions.find(
(i) => i.value === this.template.resolved_action
);
this.template.resolved_action_args = script.args;
}
},
@@ -628,14 +775,14 @@ export default {
title: "Add email",
prompt: {
model: "",
isValid: val => this.isValidEmail(val),
isValid: (val) => this.isValidEmail(val),
type: "email",
},
cancel: true,
ok: { label: "Add", color: "primary" },
persistent: false,
})
.onOk(data => {
.onOk((data) => {
this.template.email_recipients.push(data);
});
},
@@ -653,16 +800,16 @@ export default {
ok: { label: "Add", color: "primary" },
persistent: false,
})
.onOk(data => {
.onOk((data) => {
this.template.text_recipients.push(data);
});
},
removeEmail(email) {
const removed = this.template.email_recipients.filter(k => k !== email);
const removed = this.template.email_recipients.filter((k) => k !== email);
this.template.email_recipients = removed;
},
removeSMSNumber(num) {
const removed = this.template.text_recipients.filter(k => k !== num);
const removed = this.template.text_recipients.filter((k) => k !== num);
this.template.text_recipients = removed;
},
onSubmit() {
@@ -676,23 +823,23 @@ export default {
if (this.editing) {
this.$axios
.put(`alerts/templates/${this.template.id}/`, this.template)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Alert Template edited!");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
} else {
this.$axios
.post("alerts/templates/", this.template)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess(`Alert Template was added!`);
this.notifySuccess("Alert Template was added!");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
}
@@ -712,7 +859,9 @@ export default {
},
},
mounted() {
this.getScriptOptions(this.showCommunityScripts).then(options => (this.scriptOptions = Object.freeze(options)));
this.getScriptOptions(this.showCommunityScripts).then(
(options) => (this.scriptOptions = Object.freeze(options))
);
// Copy alertTemplate prop locally
if (this.editing) Object.assign(this.template, this.alertTemplate);
},

View File

@@ -100,11 +100,11 @@ export default {
this.$axios
.get(`/alerts/templates/${this.template.id}/related/`)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.related = r.data;
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},

View File

@@ -1,5 +1,11 @@
<template>
<q-dialog ref="dialog" @hide="onHide" maximized transition-show="slide-up" transition-hide="slide-down">
<q-dialog
ref="dialog"
@hide="onHide"
maximized
transition-show="slide-up"
transition-hide="slide-down"
>
<q-card>
<q-bar>
<q-btn @click="search" class="q-mr-sm" dense flat push icon="refresh" />
@@ -40,11 +46,29 @@
/>
</div>
<div class="q-pa-sm col-2">
<q-select outlined dense v-model="timeFilter" label="Time" emit-value map-options :options="timeOptions" />
<q-select
outlined
dense
v-model="timeFilter"
label="Time"
emit-value
map-options
:options="timeOptions"
/>
</div>
<div class="q-pa-sm col-2">
<q-checkbox outlined dense v-model="includeSnoozed" label="Include snoozed" />
<q-checkbox outlined dense v-model="includeResolved" label="Include resolved" />
<q-checkbox
outlined
dense
v-model="includeSnoozed"
label="Include snoozed"
/>
<q-checkbox
outlined
dense
v-model="includeResolved"
label="Include resolved"
/>
</div>
<div class="q-pa-sm col-2">
<q-btn color="primary" label="Search" @click="search" />
@@ -55,7 +79,10 @@
<q-card-section>
<q-table
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="audit-mgr-tbl-sticky"
:rows="alerts"
:columns="columns"
@@ -73,9 +100,17 @@
<template v-slot:top>
<div class="col-1 q-table__title">Alerts</div>
<q-btn-dropdown flat label="Bulk Actions" :disable="selectedAlerts.length === 0 || includeResolved">
<q-btn-dropdown
flat
label="Bulk Actions"
:disable="selectedAlerts.length === 0 || includeResolved"
>
<q-list dense>
<q-item clickable v-close-popup @click="snoozeAlertBulk(selectedAlerts)">
<q-item
clickable
v-close-popup
@click="snoozeAlertBulk(selectedAlerts)"
>
<q-item-section avatar>
<q-icon name="alarm_off" />
</q-item-section>
@@ -83,7 +118,11 @@
<q-item-label>Snooze alerts</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="resolveAlertBulk(selectedAlerts)">
<q-item
clickable
v-close-popup
@click="resolveAlertBulk(selectedAlerts)"
>
<q-item-section avatar>
<q-icon name="flag" />
</q-item-section>
@@ -147,7 +186,9 @@
<template v-slot:body-cell-severity="props">
<q-td :props="props">
<q-badge :color="alertColor(props.row.severity)">{{ capitalize(props.row.severity) }}</q-badge>
<q-badge :color="alertColor(props.row.severity)">{{
capitalize(props.row.severity)
}}</q-badge>
</q-td>
</template>
@@ -184,7 +225,7 @@ export default {
name: "AlertsOverview",
emits: ["hide"],
mixins: [mixins],
setup(props) {
setup() {
// setup vuex store
const store = useStore();
const formatDate = computed(() => store.getters.formatDate);
@@ -225,17 +266,35 @@ export default {
align: "left",
sortable: true,
},
{ name: "hostname", label: "Agent", field: "hostname", align: "left", sortable: true },
{
name: "hostname",
label: "Agent",
field: "hostname",
align: "left",
sortable: true,
},
{
name: "alert_type",
label: "Type",
field: "alert_type",
align: "left",
sortable: true,
format: a => this.capitalize(a, true),
format: (a) => this.capitalize(a, true),
},
{
name: "severity",
label: "Severity",
field: "severity",
align: "left",
sortable: true,
},
{
name: "message",
label: "Message",
field: "message",
align: "left",
sortable: true,
},
{ name: "severity", label: "Severity", field: "severity", align: "left", sortable: true },
{ name: "message", label: "Message", field: "message", align: "left", sortable: true },
{
name: "resolve_on",
label: "Resolved On",
@@ -261,10 +320,12 @@ export default {
},
computed: {
noDataText() {
return this.searched ? "No data found. Try to refine you search" : "Click search to find alerts";
return this.searched
? "No data found. Try to refine you search"
: "Click search to find alerts";
},
visibleColumns() {
return this.columns.map(column => {
return this.columns.map((column) => {
if (column.name === "snoozed_until") {
if (this.includeSnoozed) return column.name;
} else if (column.name === "resolve_on") {
@@ -277,12 +338,11 @@ export default {
},
methods: {
getClients() {
this.$axios
.get("/clients/")
.then(r => {
this.clientsOptions = Object.freeze(r.data.map(client => ({ label: client.name, value: client.id })));
})
.catch(e => {});
this.$axios.get("/clients/").then((r) => {
this.clientsOptions = Object.freeze(
r.data.map((client) => ({ label: client.name, value: client.id }))
);
});
},
search() {
this.$q.loading.show();
@@ -295,17 +355,19 @@ export default {
resolvedFilter: this.includeResolved,
};
if (this.clientFilter.length > 0) data["clientFilter"] = this.clientFilter;
if (this.clientFilter.length > 0)
data["clientFilter"] = this.clientFilter;
if (this.timeFilter) data["timeFilter"] = this.timeFilter;
if (this.severityFilter.length > 0) data["severityFilter"] = this.severityFilter;
if (this.severityFilter.length > 0)
data["severityFilter"] = this.severityFilter;
this.$axios
.patch("/alerts/", data)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.alerts = Object.freeze(r.data);
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -317,11 +379,11 @@ export default {
prompt: {
model: "",
type: "number",
isValid: val => !!val && val > 0 && val < 9999,
isValid: (val) => !!val && val > 0 && val < 9999,
},
cancel: true,
})
.onOk(days => {
.onOk((days) => {
this.$q.loading.show();
const data = {
@@ -332,12 +394,12 @@ export default {
this.$axios
.put(`alerts/${alert.id}/`, data)
.then(r => {
.then(() => {
this.search();
this.$q.loading.hide();
this.notifySuccess(`The alert has been snoozed for ${days} days`);
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
});
@@ -352,12 +414,12 @@ export default {
this.$axios
.put(`alerts/${alert.id}/`, data)
.then(r => {
.then(() => {
this.search();
this.$q.loading.hide();
this.notifySuccess(`The alert has been unsnoozed`);
this.notifySuccess("The alert has been unsnoozed");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -371,12 +433,12 @@ export default {
this.$axios
.put(`alerts/${alert.id}/`, data)
.then(r => {
.then(() => {
this.search();
this.$q.loading.hide();
this.notifySuccess("The alert has been resolved");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -384,18 +446,18 @@ export default {
this.$q.loading.show();
const data = {
alerts: alerts.map(alert => alert.id),
alerts: alerts.map((alert) => alert.id),
bulk_action: "resolve",
};
this.$axios
.post("alerts/bulk/", data)
.then(r => {
.then(() => {
this.search();
this.$q.loading.hide();
this.notifySuccess("Alerts were resolved");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -407,27 +469,27 @@ export default {
prompt: {
model: "",
type: "number",
isValid: val => !!val && val > 0 && val < 9999,
isValid: (val) => !!val && val > 0 && val < 9999,
},
cancel: true,
})
.onOk(days => {
.onOk((days) => {
this.$q.loading.show();
const data = {
alerts: alerts.map(alert => alert.id),
alerts: alerts.map((alert) => alert.id),
bulk_action: "snooze",
snooze_days: days,
};
this.$axios
.post("alerts/bulk/", data)
.then(r => {
.then(() => {
this.search();
this.$q.loading.hide();
this.notifySuccess(`Alerts were snoozed for ${days} days`);
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
});

View File

@@ -13,7 +13,7 @@
<q-form @submit.prevent="submit">
<q-card-section>
<q-select
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
outlined
options-dense
label="Actions"
@@ -29,7 +29,11 @@
<q-checkbox v-model="prune_tables" val="audit_logs" label="Audit Log">
<q-tooltip>Removes agent check results</q-tooltip>
</q-checkbox>
<q-checkbox v-model="prune_tables" val="pending_actions" label="Pending Actions">
<q-checkbox
v-model="prune_tables"
val="pending_actions"
label="Pending Actions"
>
<q-tooltip>Removes completed pending actions</q-tooltip>
</q-checkbox>
<q-checkbox v-model="prune_tables" val="alerts" label="Alerts">
@@ -38,7 +42,12 @@
</q-card-section>
<q-card-actions align="left">
<q-btn label="Submit" color="primary" type="submit" class="full-width" />
<q-btn
label="Submit"
color="primary"
type="submit"
class="full-width"
/>
</q-card-actions>
</q-form>
</q-card-section>
@@ -85,11 +94,11 @@ export default {
this.$axios
.post("core/servermaintenance/", data)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.notifySuccess(r.data);
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},

View File

@@ -13,7 +13,10 @@
class="full-width"
@click="doCodeSign"
>
<q-tooltip>Force all existing agents to be updated to the code-signed version</q-tooltip>
<q-tooltip
>Force all existing agents to be updated to the code-signed
version</q-tooltip
>
</q-btn>
</q-card-section>
<q-form @submit.prevent="editToken">
@@ -25,7 +28,7 @@
dense
v-model="settings.token"
class="col-9 q-pa-none"
:rules="[val => !!val || 'Token is required']"
:rules="[(val) => !!val || 'Token is required']"
/>
</q-card-section>
<q-card-section class="row items-center">
@@ -49,22 +52,19 @@ export default {
},
methods: {
getToken() {
this.$axios
.get("/core/codesign/")
.then(r => {
this.$axios.get("/core/codesign/").then((r) => {
this.settings = r.data;
})
.catch(e => {});
});
},
editToken() {
this.$q.loading.show();
this.$axios
.patch("/core/codesign/", this.settings)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.notifySuccess(r.data);
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},
@@ -72,7 +72,7 @@ export default {
this.$q.loading.show();
this.$axios
.post("/core/codesign/")
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.notifySuccess(r.data);
})

View File

@@ -34,7 +34,10 @@
<q-scroll-area :thumb-style="thumbStyle" style="height: 50vh">
<q-tab-panels v-model="tab" :animated="false">
<q-tab-panel name="client">
<CustomFieldsTable @refresh="getCustomFields" :data="clientFields" />
<CustomFieldsTable
@refresh="getCustomFields"
:data="clientFields"
/>
</q-tab-panel>
<q-tab-panel name="site">
@@ -74,13 +77,13 @@ export default {
},
computed: {
agentFields() {
return this.customFields.filter(field => field.model === "agent");
return this.customFields.filter((field) => field.model === "agent");
},
siteFields() {
return this.customFields.filter(field => field.model === "site");
return this.customFields.filter((field) => field.model === "site");
},
clientFields() {
return this.customFields.filter(field => field.model === "client");
return this.customFields.filter((field) => field.model === "client");
},
},
methods: {
@@ -88,12 +91,12 @@ export default {
this.$q.loading.show();
this.$axios
.get(`/core/customfields/`)
.then(r => {
.get("/core/customfields/")
.then((r) => {
this.$q.loading.hide();
this.customFields = r.data;
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
},

View File

@@ -20,12 +20,18 @@
dense
:disable="editing"
v-model="localField.model"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<!-- name -->
<q-card-section>
<q-input label="Name" outlined dense v-model="localField.name" :rules="[val => !!val || '*Required']" />
<q-input
label="Name"
outlined
dense
v-model="localField.name"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<!-- type select -->
<q-card-section>
@@ -39,11 +45,13 @@
dense
:disable="editing"
v-model="localField.type"
:rules="[val => !!val || '*Required']"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<!-- input options select for single and multiple input type -->
<q-card-section v-if="localField.type === 'single' || localField.type == 'multiple'">
<q-card-section
v-if="localField.type === 'single' || localField.type == 'multiple'"
>
<q-select
dense
label="Input Options (press Enter after typing each option)"
@@ -129,7 +137,11 @@
v-model="localField.required"
color="green"
/>
<q-toggle label="Hide in Dashboard" v-model="localField.hide_in_ui" color="green" />
<q-toggle
label="Hide in Dashboard"
v-model="localField.hide_in_ui"
color="green"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" v-close-popup />
@@ -185,7 +197,9 @@ export default {
},
defaultValueRules() {
if (this.localField.required) {
return [val => !!val || `Default Value needs to be set for required fields`];
return [
(val) => !!val || "Default Value needs to be set for required fields",
];
} else {
return [];
}
@@ -202,23 +216,23 @@ export default {
if (this.editing) {
this.$axios
.put(`/core/customfields/${data.id}/`, data)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Custom field edited!");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
} else {
this.$axios
.post("/core/customfields/", data)
.then(r => {
.then(() => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Custom field added!");
})
.catch(e => {
.catch(() => {
this.$q.loading.hide();
});
}

View File

@@ -13,7 +13,11 @@
>
<!-- body slots -->
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="editCustomField(props.row)">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="editCustomField(props.row)"
>
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
@@ -23,7 +27,11 @@
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="deleteCustomField(props.row)">
<q-item
clickable
v-close-popup
@click="deleteCustomField(props.row)"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
@@ -54,13 +62,17 @@
{{ props.row.default_value_bool }}
</q-td>
<q-td v-else-if="props.row.type === 'multiple'">
<span v-if="props.row.default_values_multiple.length > 0">{{ props.row.default_values_multiple }}</span>
<span v-if="props.row.default_values_multiple.length > 0">{{
props.row.default_values_multiple
}}</span>
</q-td>
<q-td v-else>
{{ truncateText(props.row.default_value_string) }}
<q-tooltip v-if="props.row.default_value_string.length >= 60" style="font-size: 12px">{{
props.row.default_value_string
}}</q-tooltip>
<q-tooltip
v-if="props.row.default_value_string.length >= 60"
style="font-size: 12px"
>{{ props.row.default_value_string }}</q-tooltip
>
</q-td>
<!-- required -->
<q-td>
@@ -104,9 +116,27 @@ export default {
align: "left",
sortable: true,
},
{ name: "hide_in_ui", label: "Hide in UI", field: "hide_in_ui", align: "left", sortable: true },
{ name: "default_value", label: "Default Value", field: "default_value", align: "left", sortable: true },
{ name: "required", label: "Required", field: "required", align: "left", sortable: true },
{
name: "hide_in_ui",
label: "Hide in UI",
field: "hide_in_ui",
align: "left",
sortable: true,
},
{
name: "default_value",
label: "Default Value",
field: "default_value",
align: "left",
sortable: true,
},
{
name: "required",
label: "Required",
field: "required",
align: "left",
sortable: true,
},
],
};
},
@@ -134,12 +164,12 @@ export default {
this.$q.loading.show();
this.$axios
.delete(`/core/customfields/${field.id}/`)
.then(r => {
.then(() => {
this.refresh();
this.$q.loading.hide();
this.notifySuccess(`Custom Field ${field.name} was deleted!`);
})
.catch(error => {
.catch(() => {
this.$q.loading.hide();
});
});

Some files were not shown because too many files have changed in this diff Show More