Compare commits
127 Commits
v0.100.3-d
...
v0.101.28
Author | SHA1 | Date | |
---|---|---|---|
|
68036f6837 | ||
|
45ac82b1dd | ||
|
d94e5c7965 | ||
|
e0c1b3199a | ||
|
fdbbdf7394 | ||
|
03fae45ac5 | ||
|
346670e8ea | ||
|
e030efaecf | ||
|
b8a4f9fe74 | ||
|
f963b51d70 | ||
|
feacb19cf9 | ||
|
7ce2c1e969 | ||
|
d1defcef4a | ||
|
e674b4fa5d | ||
|
b08a5a6c2d | ||
|
9fa1d7209f | ||
|
2adfccfa1d | ||
|
04766efcd0 | ||
|
4babb937f6 | ||
|
c2591c9e7d | ||
|
69403def2a | ||
|
3fdd8272f6 | ||
|
339227bedc | ||
|
17c7c95cc1 | ||
|
a3ceb5e81b | ||
|
679d8cab77 | ||
|
c4c1474e09 | ||
|
82677b0b82 | ||
|
b78af07f11 | ||
|
24acef19c5 | ||
|
fee6edb39e | ||
|
89e7db905d | ||
|
827e81dcda | ||
|
6ea3a053f2 | ||
|
88d297f7c6 | ||
|
6c57d3e6b1 | ||
|
7fcbe6fbd8 | ||
|
0113fbc761 | ||
|
95df8c1889 | ||
|
819a364207 | ||
|
ed2b07fb0b | ||
|
64ed5e8740 | ||
|
a2f472ef9c | ||
|
cdeaa3d9c4 | ||
|
8c6ac164ba | ||
|
dc68b16ff2 | ||
|
a4f15fd05a | ||
|
176675abd8 | ||
|
73dc278ac4 | ||
|
d6b443296b | ||
|
f3c718d29c | ||
|
5955af08c7 | ||
|
dec1ccc98a | ||
|
a78780b837 | ||
|
beff8eb10e | ||
|
8403ac0e93 | ||
|
c2f21b70dd | ||
|
520145e0e3 | ||
|
6a132187a2 | ||
|
a63a9ccd76 | ||
|
ff1eb791db | ||
|
13bd88b979 | ||
|
5b0c244920 | ||
|
0318a17cac | ||
|
b7a91563b0 | ||
|
75296ed8ee | ||
|
09bee45b2f | ||
|
3573c48872 | ||
|
784841c221 | ||
|
ed788a1861 | ||
|
ab19afca16 | ||
|
bd6b08505a | ||
|
acd64f25f2 | ||
|
087be2c232 | ||
|
91a3272843 | ||
|
6e64f0a11b | ||
|
8f34f76a1d | ||
|
f24c6a7a80 | ||
|
d87861c212 | ||
|
5f56e7017b | ||
|
9c033c1c90 | ||
|
ba14ed348e | ||
|
99490bf859 | ||
|
7e25db6622 | ||
|
78636c436f | ||
|
72cdeeaa6a | ||
|
d37122386f | ||
|
17d960fca9 | ||
|
d2e0b8ad9b | ||
|
1eca4d605b | ||
|
776c27ec26 | ||
|
41c61ce152 | ||
|
8e9de8b6b6 | ||
|
4cf5f7a3cb | ||
|
9729492d1c | ||
|
52ee98f6f8 | ||
|
d6da8b4a96 | ||
|
9264cf4044 | ||
|
3a45c2a309 | ||
|
59de35c698 | ||
|
d270b877c9 | ||
|
5b8ac2c809 | ||
|
83d0ff1c0a | ||
|
8a6ec6ceab | ||
|
93dbc74e33 | ||
|
5f2add48a9 | ||
|
b7369875af | ||
|
2eb6580fed | ||
|
fd8b2a1d98 | ||
|
9f85fbb330 | ||
|
ee9715a4cf | ||
|
76f330fb9c | ||
|
f518043d8d | ||
|
e67c1ff331 | ||
|
137a5648ce | ||
|
cc2335558d | ||
|
a944bc50d1 | ||
|
0a4b00298d | ||
|
1eaed284a3 | ||
|
b278e0bed4 | ||
|
6ee3df7e4e | ||
|
a8a171ba2c | ||
|
7ee87da3b6 | ||
|
7bce958633 | ||
|
24a63f477e | ||
|
57963f6d1a | ||
|
ddeb6293a1 |
7
.devcontainer/.env.example
Normal file
7
.devcontainer/.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
COMPOSE_PROJECT_NAME=trmm
|
||||
IMAGE_REPO=tacticalrmm/
|
||||
VERSION=latest
|
||||
|
||||
# DEV SETTINGS
|
||||
APP_PORT=443
|
||||
DOCKER_NETWORK=172.21.0.0/24
|
26
.devcontainer/docker-compose.yml
Normal file
26
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
app-dev:
|
||||
container_name: trmm-app-dev
|
||||
image: node:16-alpine
|
||||
restart: always
|
||||
command: /bin/sh -c "npm install --cache ~/.npm && npm run serve"
|
||||
user: 1000:1000
|
||||
working_dir: /workspace/web
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
ports:
|
||||
- "8080:443"
|
||||
networks:
|
||||
dev:
|
||||
aliases:
|
||||
- tactical-frontend
|
||||
|
||||
networks:
|
||||
dev:
|
||||
driver: bridge
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: ${DOCKER_NETWORK}
|
3
.github/workflows/build-release.yml
vendored
3
.github/workflows/build-release.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- run: touch env-config.js
|
||||
|
||||
@@ -32,4 +32,3 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: trmm-web-${{github.ref_name}}.tar.gz
|
||||
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,3 +33,4 @@ yarn-error.log*
|
||||
*.sln
|
||||
|
||||
.env
|
||||
/public/env-config.js
|
||||
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -5,7 +5,7 @@
|
||||
"esbenp.prettier-vscode",
|
||||
"editorconfig.editorconfig",
|
||||
"vue.volar",
|
||||
"wayou.vscode-todo-highlight",
|
||||
"wayou.vscode-todo-highlight"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"octref.vetur",
|
||||
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -4,7 +4,7 @@
|
||||
"editor.formatOnSave": true,
|
||||
"[vue][javascript][typescript][javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
|
||||
"editor.codeActionsOnSave": ["source.fixAll.eslint"]
|
||||
},
|
||||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
@@ -15,7 +15,7 @@
|
||||
"**/node_modules/": true,
|
||||
"/node_modules/**": true,
|
||||
"**/env/": true,
|
||||
"/env/**": true,
|
||||
"/env/**": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
36
index.html
36
index.html
@@ -1,24 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= productName %></title>
|
||||
|
||||
<head>
|
||||
<title>
|
||||
<%= productName %>
|
||||
</title>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta name="description" content="<%= productDescription %>" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
<meta name="viewport"
|
||||
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>" />
|
||||
<link rel="icon" type="image/ico" href="favicon.ico" />
|
||||
<script src="/env-config.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- quasar:entry-point -->
|
||||
</body>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta name="description" content="<%= productDescription %>" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>"
|
||||
/>
|
||||
<link rel="icon" type="image/ico" href="favicon.ico" />
|
||||
<script src="/env-config.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- quasar:entry-point -->
|
||||
</body>
|
||||
</html>
|
||||
|
8195
package-lock.json
generated
8195
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.100.3-dev",
|
||||
"version": "0.101.28",
|
||||
"private": true,
|
||||
"productName": "Tactical RMM",
|
||||
"scripts": {
|
||||
@@ -10,31 +10,31 @@
|
||||
"format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "1.14.2",
|
||||
"apexcharts": "3.35.3",
|
||||
"axios": "0.27.2",
|
||||
"dotenv": "16.0.1",
|
||||
"qrcode.vue": "3.3.3",
|
||||
"quasar": "2.7.5",
|
||||
"vue": "3.2.37",
|
||||
"vue3-ace-editor": "2.2.2",
|
||||
"vue3-apexcharts": "1.4.1",
|
||||
"@quasar/extras": "1.16.5",
|
||||
"apexcharts": "3.41.1",
|
||||
"axios": "1.4.0",
|
||||
"dotenv": "16.3.1",
|
||||
"qrcode.vue": "3.4.1",
|
||||
"quasar": "2.12.4",
|
||||
"vue": "3.3.4",
|
||||
"vue3-ace-editor": "2.2.3",
|
||||
"vue3-apexcharts": "1.4.4",
|
||||
"vuedraggable": "4.1.0",
|
||||
"vue-router": "4.1.1",
|
||||
"vuex": "4.0.2"
|
||||
"vue-router": "4.2.4",
|
||||
"vuex": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/cli": "^1.3.2",
|
||||
"@intlify/vite-plugin-vue-i18n": "^3.4.0",
|
||||
"@quasar/app-vite": "^1.0.5",
|
||||
"@types/node": "^18.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||
"@typescript-eslint/parser": "^5.30.5",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"eslint": "^8.18.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-vue": "^8.5.0",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4"
|
||||
"@quasar/cli": "^2.2.2",
|
||||
"@intlify/unplugin-vue-i18n": "^0.12.2",
|
||||
"@quasar/app-vite": "^1.4.3",
|
||||
"@types/node": "^20.4.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"autoprefixer": "10.4.14",
|
||||
"eslint": "8.46.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-plugin-vue": "8.7.1",
|
||||
"prettier": "3.0.1",
|
||||
"typescript": "5.1.6"
|
||||
}
|
||||
}
|
||||
|
@@ -4,18 +4,18 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
// https://github.com/postcss/autoprefixer
|
||||
require('autoprefixer')({
|
||||
require("autoprefixer")({
|
||||
overrideBrowserslist: [
|
||||
'last 4 Chrome versions',
|
||||
'last 4 Firefox versions',
|
||||
'last 4 Edge versions',
|
||||
'last 4 Safari versions',
|
||||
'last 4 Android versions',
|
||||
'last 4 ChromeAndroid versions',
|
||||
'last 4 FirefoxAndroid versions',
|
||||
'last 4 iOS versions'
|
||||
]
|
||||
})
|
||||
"last 4 Chrome versions",
|
||||
"last 4 Firefox versions",
|
||||
"last 4 Edge versions",
|
||||
"last 4 Safari versions",
|
||||
"last 4 Android versions",
|
||||
"last 4 ChromeAndroid versions",
|
||||
"last 4 FirefoxAndroid versions",
|
||||
"last 4 iOS versions",
|
||||
],
|
||||
}),
|
||||
|
||||
// https://github.com/elchininet/postcss-rtlcss
|
||||
// If you want to support RTL css, then
|
||||
@@ -23,5 +23,5 @@ module.exports = {
|
||||
// 2. optionally set quasar.config.js > framework > lang to an RTL language
|
||||
// 3. uncomment the following line:
|
||||
// require('postcss-rtlcss')
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
|
@@ -12,6 +12,9 @@ export default {
|
||||
body
|
||||
overflow-y: hidden
|
||||
|
||||
a
|
||||
color: #1976D2
|
||||
|
||||
.tbl-sticky
|
||||
thead tr th
|
||||
position: sticky
|
||||
|
@@ -12,6 +12,25 @@ export async function fetchUsers(params = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetPass(pass) {
|
||||
const payload = { password: pass };
|
||||
try {
|
||||
const { data } = await axios.put(`${baseUrl}/resetpw/`, payload);
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetTwoFactor() {
|
||||
try {
|
||||
const { data } = await axios.put(`${baseUrl}/reset2fa/`);
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// role api function
|
||||
export async function fetchRoles(params = {}) {
|
||||
try {
|
||||
|
@@ -232,3 +232,8 @@ export async function removeAgentNote(pk) {
|
||||
const { data } = await axios.delete(`${baseUrl}/notes/${pk}/`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function wakeUpWOL(agent_id) {
|
||||
const { data } = await axios.post(`${baseUrl}/${agent_id}/wol/`);
|
||||
return data;
|
||||
}
|
||||
|
@@ -38,3 +38,8 @@ export async function runURLAction(payload) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateScript(payload) {
|
||||
const { data } = await axios.post(`${baseUrl}/openai/generate/`, payload);
|
||||
return data;
|
||||
}
|
||||
|
@@ -196,6 +196,14 @@
|
||||
>
|
||||
<q-tooltip>Linux</q-tooltip>
|
||||
</q-icon>
|
||||
<q-icon
|
||||
v-else-if="props.row.plat === 'darwin'"
|
||||
name="mdi-apple"
|
||||
size="sm"
|
||||
color="primary"
|
||||
>
|
||||
<q-tooltip>macOS</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
|
||||
<q-td key="checks-status" :props="props">
|
||||
@@ -203,7 +211,7 @@
|
||||
v-if="props.row.maintenance_mode"
|
||||
name="construction"
|
||||
size="1.2em"
|
||||
color="green"
|
||||
:color="dash_positive_color"
|
||||
>
|
||||
<q-tooltip>Maintenance Mode Enabled</q-tooltip>
|
||||
</q-icon>
|
||||
@@ -211,7 +219,7 @@
|
||||
v-else-if="props.row.checks.failing > 0"
|
||||
name="fas fa-check-double"
|
||||
size="1.2em"
|
||||
color="negative"
|
||||
:color="dash_negative_color"
|
||||
>
|
||||
<q-tooltip>Checks failing</q-tooltip>
|
||||
</q-icon>
|
||||
@@ -219,7 +227,7 @@
|
||||
v-else-if="props.row.checks.warning > 0"
|
||||
name="fas fa-check-double"
|
||||
size="1.2em"
|
||||
color="warning"
|
||||
:color="dash_warning_color"
|
||||
>
|
||||
<q-tooltip>Checks warning</q-tooltip>
|
||||
</q-icon>
|
||||
@@ -227,7 +235,7 @@
|
||||
v-else-if="props.row.checks.info > 0"
|
||||
name="fas fa-check-double"
|
||||
size="1.2em"
|
||||
color="info"
|
||||
:color="dash_info_color"
|
||||
>
|
||||
<q-tooltip>Checks info</q-tooltip>
|
||||
</q-icon>
|
||||
@@ -235,7 +243,7 @@
|
||||
v-else
|
||||
name="fas fa-check-double"
|
||||
size="1.2em"
|
||||
color="positive"
|
||||
:color="dash_positive_color"
|
||||
>
|
||||
<q-tooltip>Checks passing</q-tooltip>
|
||||
</q-icon>
|
||||
@@ -271,7 +279,7 @@
|
||||
@click="showPendingActionsModal(props.row)"
|
||||
name="far fa-clock"
|
||||
size="1.4em"
|
||||
color="warning"
|
||||
:color="dash_warning_color"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<q-tooltip
|
||||
@@ -295,7 +303,7 @@
|
||||
v-if="props.row.status === 'overdue'"
|
||||
name="fas fa-signal"
|
||||
size="1.2em"
|
||||
color="negative"
|
||||
:color="dash_negative_color"
|
||||
>
|
||||
<q-tooltip>Agent overdue</q-tooltip>
|
||||
</q-icon>
|
||||
@@ -303,11 +311,16 @@
|
||||
v-else-if="props.row.status === 'offline'"
|
||||
name="fas fa-signal"
|
||||
size="1.2em"
|
||||
color="warning"
|
||||
:color="dash_warning_color"
|
||||
>
|
||||
<q-tooltip>Agent offline</q-tooltip>
|
||||
</q-icon>
|
||||
<q-icon v-else name="fas fa-signal" size="1.2em" color="positive">
|
||||
<q-icon
|
||||
v-else
|
||||
name="fas fa-signal"
|
||||
size="1.2em"
|
||||
:color="dash_positive_color"
|
||||
>
|
||||
<q-tooltip>Agent online</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
@@ -356,6 +369,23 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
filterTable(rows, terms, cols, cellValue) {
|
||||
const hiddenFields = [
|
||||
"version",
|
||||
"operating_system",
|
||||
"public_ip",
|
||||
"cpu_model",
|
||||
"graphics",
|
||||
"local_ips",
|
||||
"make_model",
|
||||
"physical_disks",
|
||||
"custom_fields",
|
||||
"serial_number",
|
||||
];
|
||||
// quasar filter only does visible columns so this is a hack to add hidden columns we want to filter
|
||||
// originally I was modifying cols directly but this led to phantom colum so doing it this way now
|
||||
// https://github.com/amidaware/tacticalrmm/issues/1264
|
||||
const allColumns = [...cols, ...hiddenFields.map((field) => ({ field }))];
|
||||
|
||||
const lowerTerms = terms ? terms.toLowerCase() : "";
|
||||
let advancedFilter = false;
|
||||
let availability = null;
|
||||
@@ -408,8 +438,12 @@ export default {
|
||||
}
|
||||
|
||||
// Normal text filter
|
||||
return cols.some((col) => {
|
||||
const val = cellValue(col, row) + "";
|
||||
return allColumns.some((col) => {
|
||||
let valObj = cellValue(col, row);
|
||||
if (Array.isArray(valObj)) {
|
||||
valObj = valObj.map((item) => (item.value ? item.value : item));
|
||||
}
|
||||
const val = valObj + "";
|
||||
const haystack =
|
||||
val === "undefined" || val === "null" ? "" : val.toLowerCase();
|
||||
return haystack.indexOf(search) !== -1;
|
||||
@@ -460,7 +494,9 @@ export default {
|
||||
const data = {
|
||||
[db_field]: !alert_action,
|
||||
};
|
||||
const alertColor = !alert_action ? "positive" : "info";
|
||||
const alertColor = !alert_action
|
||||
? this.dash_positive_color
|
||||
: this.dash_info_color;
|
||||
this.$axios.put(`/agents/${agent.agent_id}/`, data).then(() => {
|
||||
this.$q.notify({
|
||||
color: alertColor,
|
||||
@@ -504,7 +540,13 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(["tableHeight"]),
|
||||
...mapState([
|
||||
"tableHeight",
|
||||
"dash_info_color",
|
||||
"dash_positive_color",
|
||||
"dash_negative_color",
|
||||
"dash_warning_color",
|
||||
]),
|
||||
agentDblClickAction() {
|
||||
return this.$store.state.agentDblClickAction;
|
||||
},
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<q-badge v-if="alertsCount > 0" :color="badgeColor" floating transparent>{{
|
||||
alertsCountText()
|
||||
}}</q-badge>
|
||||
<q-menu style="max-height: 30vh">
|
||||
<q-menu :style="{ 'max-height': `${$q.screen.height - 100}px` }">
|
||||
<q-list separator>
|
||||
<q-item v-if="alertsCount === 0">No New Alerts</q-item>
|
||||
<q-item v-for="alert in topAlerts" :key="alert.id">
|
||||
@@ -59,6 +59,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import mixins from "@/mixins/mixins";
|
||||
import AlertsOverview from "@/components/modals/alerts/AlertsOverview.vue";
|
||||
import { getTimeLapse } from "@/utils/format";
|
||||
@@ -75,19 +76,21 @@ export default {
|
||||
return {
|
||||
alertsCount: 0,
|
||||
topAlerts: [],
|
||||
errorColor: "red",
|
||||
warningColor: "orange",
|
||||
infoColor: "blue",
|
||||
poll: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
"dash_info_color",
|
||||
"dash_warning_color",
|
||||
"dash_negative_color",
|
||||
]),
|
||||
badgeColor() {
|
||||
const severities = this.topAlerts.map((alert) => alert.severity);
|
||||
|
||||
if (severities.includes("error")) return this.errorColor;
|
||||
else if (severities.includes("warning")) return this.warningColor;
|
||||
else return this.infoColor;
|
||||
if (severities.includes("error")) return this.dash_negative_color;
|
||||
else if (severities.includes("warning")) return this.dash_warning_color;
|
||||
else return this.dash_info_color;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -159,9 +162,9 @@ export default {
|
||||
});
|
||||
},
|
||||
alertIconColor(severity) {
|
||||
if (severity === "error") return this.errorColor;
|
||||
else if (severity === "warning") return this.warningColor;
|
||||
else return this.infoColor;
|
||||
if (severity === "error") return this.dash_negative_color;
|
||||
else if (severity === "warning") return this.dash_warning_color;
|
||||
else return this.dash_info_color;
|
||||
},
|
||||
alertsCountText() {
|
||||
if (this.alertsCount > 99) return "99+";
|
||||
|
75
src/components/accounts/ResetPass.vue
Normal file
75
src/components/accounts/ResetPass.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<q-dialog ref="dialogRef" @hide="onDialogHide">
|
||||
<q-card class="q-dialog-plugin" style="width: 60vw">
|
||||
<q-card-section class="row">
|
||||
<div class="col-3">New password:</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
outlined
|
||||
dense
|
||||
v-model="pass"
|
||||
:type="isPwd ? 'password' : 'text'"
|
||||
:rules="[(val) => !!val || '*Required']"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||
class="cursor-pointer"
|
||||
@click="isPwd = !isPwd"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-3">Confirm password:</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
outlined
|
||||
dense
|
||||
v-model="pass2"
|
||||
:type="isPwd ? 'password' : 'text'"
|
||||
:rules="[(val) => val === pass || 'Passwords do not match']"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||
class="cursor-pointer"
|
||||
@click="isPwd = !isPwd"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn
|
||||
color="primary"
|
||||
label="Reset"
|
||||
@click="onOKClick"
|
||||
:disable="!pass || pass !== pass2"
|
||||
/>
|
||||
<q-btn color="negative" label="Cancel" @click="onDialogCancel" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import { resetPass } from "@/api/accounts";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
|
||||
const pass = ref("");
|
||||
const pass2 = ref("");
|
||||
const isPwd = ref(true);
|
||||
|
||||
defineEmits([...useDialogPluginComponent.emits]);
|
||||
|
||||
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
||||
useDialogPluginComponent();
|
||||
|
||||
async function onOKClick() {
|
||||
const ret = await resetPass(pass.value);
|
||||
notifySuccess(ret);
|
||||
onDialogOK();
|
||||
}
|
||||
</script>
|
@@ -98,6 +98,10 @@
|
||||
v-model="localRole.can_reboot_agents"
|
||||
label="Reboot Agents"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="localRole.can_send_wol"
|
||||
label="Wake-Up (WoL) Agents"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="localRole.can_install_agents"
|
||||
label="Install Agents"
|
||||
@@ -437,8 +441,8 @@ export default {
|
||||
can_run_scripts: false,
|
||||
can_run_bulk: false,
|
||||
can_manage_winsvcs: false,
|
||||
can_recover_agents: false,
|
||||
can_list_agent_history: false,
|
||||
can_send_wol: false,
|
||||
// software perms
|
||||
can_list_software: false,
|
||||
can_manage_software: false,
|
||||
|
@@ -146,6 +146,13 @@
|
||||
<q-item-section>Run Checks</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click="wakeUp(agent)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="offline_bolt" />
|
||||
</q-item-section>
|
||||
<q-item-section>Wake-Up (WoL)</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable>
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="power_settings_new" />
|
||||
@@ -210,6 +217,7 @@ import {
|
||||
removeAgent,
|
||||
runRemoteBackground,
|
||||
runTakeControl,
|
||||
wakeUpWOL,
|
||||
} from "@/api/agents";
|
||||
import { runAgentUpdateScan, runAgentUpdateInstall } from "@/api/winupdates";
|
||||
import { runAgentChecks } from "@/api/checks";
|
||||
@@ -370,6 +378,15 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
async function wakeUp(agent) {
|
||||
try {
|
||||
const data = await wakeUpWOL(agent.agent_id);
|
||||
notifySuccess(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function showRebootLaterModal(agent) {
|
||||
$q.dialog({
|
||||
component: RebootLater,
|
||||
@@ -498,6 +515,7 @@ export default {
|
||||
showPolicyAdd,
|
||||
showAgentRecovery,
|
||||
pingAgent,
|
||||
wakeUp,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@@ -261,7 +261,7 @@
|
||||
<q-td v-else-if="props.row.task_result.status === 'passing'">
|
||||
<q-icon
|
||||
style="font-size: 1.3rem"
|
||||
color="positive"
|
||||
:color="dash_positive_color"
|
||||
name="check_circle"
|
||||
>
|
||||
<q-tooltip>Passing</q-tooltip>
|
||||
@@ -271,7 +271,7 @@
|
||||
<q-icon
|
||||
v-if="props.row.alert_severity === 'info'"
|
||||
style="font-size: 1.3rem"
|
||||
color="info"
|
||||
:color="dash_info_color"
|
||||
name="info"
|
||||
>
|
||||
<q-tooltip>Informational</q-tooltip>
|
||||
@@ -279,7 +279,7 @@
|
||||
<q-icon
|
||||
v-else-if="props.row.alert_severity === 'warning'"
|
||||
style="font-size: 1.3rem"
|
||||
color="warning"
|
||||
:color="dash_warning_color"
|
||||
name="warning"
|
||||
>
|
||||
<q-tooltip>Warning</q-tooltip>
|
||||
@@ -287,7 +287,7 @@
|
||||
<q-icon
|
||||
v-else
|
||||
style="font-size: 1.3rem"
|
||||
color="negative"
|
||||
:color="dash_negative_color"
|
||||
name="error"
|
||||
>
|
||||
<q-tooltip>Error</q-tooltip>
|
||||
@@ -418,6 +418,10 @@ export default {
|
||||
const tabHeight = computed(() => store.state.tabHeight);
|
||||
const agentPlatform = computed(() => store.state.agentPlatform);
|
||||
const formatDate = computed(() => store.getters.formatDate);
|
||||
const dash_info_color = computed(() => store.state.dash_info_color);
|
||||
const dash_positive_color = computed(() => store.state.dash_positive_color);
|
||||
const dash_negative_color = computed(() => store.state.dash_negative_color);
|
||||
const dash_warning_color = computed(() => store.state.dash_warning_color);
|
||||
|
||||
// setup quasar
|
||||
const $q = useQuasar();
|
||||
@@ -552,6 +556,10 @@ export default {
|
||||
selectedAgent,
|
||||
tabHeight,
|
||||
agentPlatform,
|
||||
dash_info_color,
|
||||
dash_positive_color,
|
||||
dash_warning_color,
|
||||
dash_negative_color,
|
||||
|
||||
// non-reactive data
|
||||
columns,
|
||||
|
@@ -301,7 +301,7 @@
|
||||
<q-td v-else-if="props.row.check_result.status === 'passing'">
|
||||
<q-icon
|
||||
style="font-size: 1.3rem"
|
||||
color="positive"
|
||||
:color="dash_positive_color"
|
||||
name="check_circle"
|
||||
>
|
||||
<q-tooltip>Passing</q-tooltip>
|
||||
@@ -311,7 +311,7 @@
|
||||
<q-icon
|
||||
v-if="getAlertSeverity(props.row) === 'info'"
|
||||
style="font-size: 1.3rem"
|
||||
color="info"
|
||||
:color="dash_info_color"
|
||||
name="info"
|
||||
>
|
||||
<q-tooltip>Informational</q-tooltip>
|
||||
@@ -319,7 +319,7 @@
|
||||
<q-icon
|
||||
v-else-if="getAlertSeverity(props.row) === 'warning'"
|
||||
style="font-size: 1.3rem"
|
||||
color="warning"
|
||||
:color="dash_warning_color"
|
||||
name="warning"
|
||||
>
|
||||
<q-tooltip>Warning</q-tooltip>
|
||||
@@ -327,7 +327,7 @@
|
||||
<q-icon
|
||||
v-else
|
||||
style="font-size: 1.3rem"
|
||||
color="negative"
|
||||
:color="dash_negative_color"
|
||||
name="error"
|
||||
>
|
||||
<q-tooltip>Error</q-tooltip>
|
||||
@@ -479,6 +479,10 @@ export default {
|
||||
const tabHeight = computed(() => store.state.tabHeight);
|
||||
const agentPlatform = computed(() => store.state.agentPlatform);
|
||||
const formatDate = computed(() => store.getters.formatDate);
|
||||
const dash_info_color = computed(() => store.state.dash_info_color);
|
||||
const dash_positive_color = computed(() => store.state.dash_positive_color);
|
||||
const dash_negative_color = computed(() => store.state.dash_negative_color);
|
||||
const dash_warning_color = computed(() => store.state.dash_warning_color);
|
||||
|
||||
// setup quasar
|
||||
const $q = useQuasar();
|
||||
@@ -653,6 +657,10 @@ export default {
|
||||
tabHeight,
|
||||
selectedAgent,
|
||||
agentPlatform,
|
||||
dash_info_color,
|
||||
dash_positive_color,
|
||||
dash_warning_color,
|
||||
dash_negative_color,
|
||||
|
||||
// non-reactive data
|
||||
columns,
|
||||
|
@@ -166,7 +166,7 @@ export default {
|
||||
type: "textarea",
|
||||
isValid: (val) => !!val,
|
||||
},
|
||||
style: "width: 30vw; max-width: 50vw;",
|
||||
style: "width: 90vw; max-width: 90vw",
|
||||
ok: { label: "Add" },
|
||||
cancel: true,
|
||||
}).onOk(async () => {
|
||||
@@ -193,7 +193,7 @@ export default {
|
||||
type: "textarea",
|
||||
isValid: (val) => !!val,
|
||||
},
|
||||
style: "width: 30vw; max-width: 50vw;",
|
||||
style: "width: 90vw; max-width: 90vw",
|
||||
ok: { label: "Save" },
|
||||
cancel: true,
|
||||
}).onOk(async (data) => {
|
||||
|
@@ -18,6 +18,33 @@
|
||||
icon="refresh"
|
||||
@click="refreshSummary"
|
||||
/>
|
||||
<q-icon
|
||||
v-if="summary.status === 'overdue'"
|
||||
name="fas fa-signal"
|
||||
size="1.2em"
|
||||
:color="dash_negative_color"
|
||||
class="q-mr-sm"
|
||||
>
|
||||
<q-tooltip>Agent overdue</q-tooltip>
|
||||
</q-icon>
|
||||
<q-icon
|
||||
v-else-if="summary.status === 'offline'"
|
||||
name="fas fa-signal"
|
||||
size="1.2em"
|
||||
:color="dash_warning_color"
|
||||
class="q-mr-sm"
|
||||
>
|
||||
<q-tooltip>Agent offline</q-tooltip>
|
||||
</q-icon>
|
||||
<q-icon
|
||||
v-else
|
||||
name="fas fa-signal"
|
||||
size="1.2em"
|
||||
:color="dash_positive_color"
|
||||
class="q-mr-sm"
|
||||
>
|
||||
<q-tooltip>Agent online</q-tooltip>
|
||||
</q-icon>
|
||||
<b>{{ summary.hostname }}</b>
|
||||
<span v-if="summary.maintenance_mode">
|
||||
• <q-badge color="green"> Maintenance Mode </q-badge>
|
||||
@@ -60,7 +87,7 @@
|
||||
</q-item-section>
|
||||
<q-item-section>{{ summary.make_model }}</q-item-section>
|
||||
</q-item>
|
||||
<q-item v-for="(cpu, i) in summary.cpu_model" :key="cpu + i">
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="fas fa-microchip" />
|
||||
</q-item-section>
|
||||
@@ -87,6 +114,13 @@
|
||||
</q-item-section>
|
||||
<q-item-section>{{ summary.graphics }}</q-item-section>
|
||||
</q-item>
|
||||
<!-- serial -->
|
||||
<q-item v-if="serial_number">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="fa-solid fa-barcode" />
|
||||
</q-item-section>
|
||||
<q-item-section>{{ serial_number }}</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="fas fa-globe-americas" />
|
||||
@@ -110,7 +144,7 @@
|
||||
size="lg"
|
||||
square
|
||||
icon="done"
|
||||
color="green"
|
||||
:color="dash_positive_color"
|
||||
text-color="white"
|
||||
/>
|
||||
<small>{{ summary.checks.passing }} checks passing</small>
|
||||
@@ -120,7 +154,7 @@
|
||||
size="lg"
|
||||
square
|
||||
icon="cancel"
|
||||
color="red"
|
||||
:color="dash_negative_color"
|
||||
text-color="white"
|
||||
/>
|
||||
<small>{{ summary.checks.failing }} checks failing</small>
|
||||
@@ -130,7 +164,7 @@
|
||||
size="lg"
|
||||
square
|
||||
icon="warning"
|
||||
color="warning"
|
||||
:color="dash_warning_color"
|
||||
text-color="white"
|
||||
/>
|
||||
<small>{{ summary.checks.warning }} checks warning</small>
|
||||
@@ -140,7 +174,7 @@
|
||||
size="lg"
|
||||
square
|
||||
icon="info"
|
||||
color="info"
|
||||
:color="dash_info_color"
|
||||
text-color="white"
|
||||
/>
|
||||
<small>{{ summary.checks.info }} checks info</small>
|
||||
@@ -158,6 +192,20 @@
|
||||
>
|
||||
</div>
|
||||
<div v-else>No checks</div>
|
||||
|
||||
<span
|
||||
v-if="customFields.length > 0"
|
||||
class="text-subtitle2 text-bold block q-mt-xl"
|
||||
>Custom Fields</span
|
||||
>
|
||||
<q-list dense>
|
||||
<q-item v-for="(field, i) in customFields" :key="field + i">
|
||||
<q-item-section thumbnail>
|
||||
<q-icon name="fas fa-user" size="xs" />
|
||||
</q-item-section>
|
||||
<q-item-section>{{ field.name }}: {{ field.value }}</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
<div class="col-1"></div>
|
||||
<!-- right -->
|
||||
@@ -193,6 +241,7 @@ import {
|
||||
openAgentWindow,
|
||||
} from "@/api/agents";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
import { fetchCustomFields } from "@/api/core";
|
||||
|
||||
// ui imports
|
||||
import AgentActionMenu from "@/components/agents/AgentActionMenu.vue";
|
||||
@@ -207,18 +256,34 @@ export default {
|
||||
const store = useStore();
|
||||
const selectedAgent = computed(() => store.state.selectedRow);
|
||||
const refreshSummaryTab = computed(() => store.state.refreshSummaryTab);
|
||||
const dash_info_color = computed(() => store.state.dash_info_color);
|
||||
const dash_positive_color = computed(() => store.state.dash_positive_color);
|
||||
const dash_negative_color = computed(() => store.state.dash_negative_color);
|
||||
const dash_warning_color = computed(() => store.state.dash_warning_color);
|
||||
|
||||
// summary tab logic
|
||||
const summary = ref(null);
|
||||
const customFieldsDefinitions = ref(null);
|
||||
const loading = ref(false);
|
||||
|
||||
const serial_number = computed(() => {
|
||||
return summary.value.wmi_detail.bios?.[0]?.[0]?.SerialNumber;
|
||||
});
|
||||
|
||||
const cpu = computed(() => {
|
||||
if (summary.value.cpu_model?.length > 1) {
|
||||
return `${summary.value.cpu_model.length}x ${summary.value.cpu_model[0]}`;
|
||||
}
|
||||
return summary.value.cpu_model[0];
|
||||
});
|
||||
|
||||
function diskBarColor(percent) {
|
||||
if (percent < 80) {
|
||||
return "positive";
|
||||
return dash_positive_color.value;
|
||||
} else if (percent > 80 && percent < 95) {
|
||||
return "warning";
|
||||
return dash_warning_color.value;
|
||||
} else {
|
||||
return "negative";
|
||||
return dash_negative_color.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,9 +301,37 @@ export default {
|
||||
return ret;
|
||||
});
|
||||
|
||||
const customFields = computed(() => {
|
||||
if (!summary.value.custom_fields) {
|
||||
return [];
|
||||
}
|
||||
if (!customFieldsDefinitions.value) {
|
||||
return [];
|
||||
}
|
||||
const ret = [];
|
||||
for (const customField of summary.value.custom_fields) {
|
||||
const definition = customFieldsDefinitions.value.find(
|
||||
(def) => def.id === customField.field
|
||||
);
|
||||
if (
|
||||
definition &&
|
||||
!definition.hide_in_ui &&
|
||||
customField.value?.length > 0
|
||||
) {
|
||||
ret.push({
|
||||
name: definition.name,
|
||||
value: customField.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
async function getSummary() {
|
||||
loading.value = true;
|
||||
summary.value = await fetchAgent(selectedAgent.value);
|
||||
customFieldsDefinitions.value = await fetchCustomFields();
|
||||
store.commit("setRefreshSummaryTab", false);
|
||||
store.commit("setAgentPlatform", summary.value.plat);
|
||||
loading.value = false;
|
||||
@@ -246,6 +339,7 @@ export default {
|
||||
|
||||
async function refreshSummary() {
|
||||
loading.value = true;
|
||||
summary.value = await fetchAgent(selectedAgent.value);
|
||||
try {
|
||||
const result = await refreshAgentWMI(selectedAgent.value);
|
||||
await getSummary();
|
||||
@@ -277,9 +371,16 @@ export default {
|
||||
return {
|
||||
// reactive data
|
||||
summary,
|
||||
customFields,
|
||||
loading,
|
||||
selectedAgent,
|
||||
disks,
|
||||
dash_info_color,
|
||||
dash_positive_color,
|
||||
dash_warning_color,
|
||||
dash_negative_color,
|
||||
serial_number,
|
||||
cpu,
|
||||
|
||||
// methods
|
||||
getSummary,
|
||||
|
@@ -128,7 +128,7 @@
|
||||
<q-icon
|
||||
v-else-if="props.row.action === 'ignore'"
|
||||
name="fas fa-check"
|
||||
color="negative"
|
||||
:color="dash_negative_color"
|
||||
>
|
||||
<q-tooltip>Ignore</q-tooltip>
|
||||
</q-icon>
|
||||
@@ -144,7 +144,7 @@
|
||||
<q-icon
|
||||
v-if="props.row.installed"
|
||||
name="fas fa-check"
|
||||
color="positive"
|
||||
:color="dash_positive_color"
|
||||
>
|
||||
<q-tooltip>Installed</q-tooltip>
|
||||
</q-icon>
|
||||
@@ -158,11 +158,15 @@
|
||||
<q-icon
|
||||
v-else-if="props.row.action == 'ignore'"
|
||||
name="fas fa-ban"
|
||||
color="negative"
|
||||
:color="dash_negative_color"
|
||||
>
|
||||
<q-tooltip>Ignored</q-tooltip>
|
||||
</q-icon>
|
||||
<q-icon v-else name="fas fa-exclamation" color="warning">
|
||||
<q-icon
|
||||
v-else
|
||||
name="fas fa-exclamation"
|
||||
:color="dash_warning_color"
|
||||
>
|
||||
<q-tooltip>Missing</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
@@ -251,6 +255,9 @@ export default {
|
||||
const tabHeight = computed(() => store.state.tabHeight);
|
||||
const agentPlatform = computed(() => store.state.agentPlatform);
|
||||
const formatDate = computed(() => store.getters.formatDate);
|
||||
const dash_positive_color = computed(() => store.state.dash_positive_color);
|
||||
const dash_negative_color = computed(() => store.state.dash_negative_color);
|
||||
const dash_warning_color = computed(() => store.state.dash_warning_color);
|
||||
|
||||
// setup quasar
|
||||
const $q = useQuasar();
|
||||
@@ -310,9 +317,10 @@ export default {
|
||||
}
|
||||
|
||||
function showUpdateDetails(update) {
|
||||
const color = $q.dark.isActive ? "white" : "";
|
||||
let support_urls = "";
|
||||
update.more_info_urls.forEach((u) => {
|
||||
support_urls += `<a href='${u}' target='_blank'>${u}</a><br/>`;
|
||||
support_urls += `<a style='color: ${color}' href='${u}' target='_blank'>${u}</a><br/>`;
|
||||
});
|
||||
let cats = update.categories.join(", ");
|
||||
$q.dialog({
|
||||
@@ -347,6 +355,9 @@ export default {
|
||||
selectedAgent,
|
||||
tabHeight,
|
||||
agentPlatform,
|
||||
dash_positive_color,
|
||||
dash_warning_color,
|
||||
dash_negative_color,
|
||||
|
||||
// non-reactive data
|
||||
columns,
|
||||
|
@@ -7,6 +7,17 @@
|
||||
<q-badge color="primary" class="q-ml-sm text-caption">{{
|
||||
v
|
||||
}}</q-badge>
|
||||
<q-btn
|
||||
v-if="!!v"
|
||||
size="sm"
|
||||
class="q-ml-xs"
|
||||
flat
|
||||
round
|
||||
icon="content_copy"
|
||||
@click="copyValueToClip(v)"
|
||||
>
|
||||
<q-tooltip>Copy to Clipboard</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<q-separator v-if="info.length > 1" />
|
||||
@@ -15,6 +26,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { copyToClipboard } from "quasar";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
// composition imports
|
||||
import { computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
@@ -28,9 +41,16 @@ export default {
|
||||
const store = useStore();
|
||||
const tabHeight = computed(() => store.state.tabHeight);
|
||||
|
||||
function copyValueToClip(val) {
|
||||
copyToClipboard(val).then(() => {
|
||||
notifySuccess("Copied to clipboard");
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tabHeight,
|
||||
uid,
|
||||
copyValueToClip,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@@ -217,6 +217,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import mixins from "@/mixins/mixins";
|
||||
import PolicyStatus from "@/components/automation/modals/PolicyStatus.vue";
|
||||
import DiskSpaceCheck from "@/components/checks/DiskSpaceCheck.vue";
|
||||
@@ -268,6 +269,9 @@ export default {
|
||||
if (newValue !== oldValue) this.getChecks();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(["dash_positive_color", "dash_warning_color"]),
|
||||
},
|
||||
methods: {
|
||||
getChecks() {
|
||||
this.$q.loading.show();
|
||||
@@ -295,7 +299,9 @@ export default {
|
||||
|
||||
data.check_alert = true;
|
||||
const act = !action ? "enabled" : "disabled";
|
||||
const color = !action ? "positive" : "warning";
|
||||
const color = !action
|
||||
? this.dash_positive_color
|
||||
: this.dash_warning_color;
|
||||
this.$axios
|
||||
.put(`/checks/${id}/`, data)
|
||||
.then(() => {
|
||||
|
@@ -41,7 +41,7 @@
|
||||
<q-td v-if="props.row.status === 'passing'">
|
||||
<q-icon
|
||||
style="font-size: 1.3rem"
|
||||
color="positive"
|
||||
:color="dash_positive_color"
|
||||
name="check_circle"
|
||||
>
|
||||
<q-tooltip>Passing</q-tooltip>
|
||||
@@ -51,7 +51,7 @@
|
||||
<q-icon
|
||||
v-if="props.row.alert_severity === 'info'"
|
||||
style="font-size: 1.3rem"
|
||||
color="info"
|
||||
:color="dash_info_color"
|
||||
name="info"
|
||||
>
|
||||
<q-tooltip>Informational</q-tooltip>
|
||||
@@ -59,7 +59,7 @@
|
||||
<q-icon
|
||||
v-else-if="props.row.alert_severity === 'warning'"
|
||||
style="font-size: 1.3rem"
|
||||
color="warning"
|
||||
:color="dash_warning_color"
|
||||
name="warning"
|
||||
>
|
||||
<q-tooltip>Warning</q-tooltip>
|
||||
@@ -67,7 +67,7 @@
|
||||
<q-icon
|
||||
v-else
|
||||
style="font-size: 1.3rem"
|
||||
color="negative"
|
||||
:color="dash_negative_color"
|
||||
name="error"
|
||||
>
|
||||
<q-tooltip>Error</q-tooltip>
|
||||
@@ -148,7 +148,7 @@
|
||||
|
||||
<script>
|
||||
import { computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useStore, mapState } from "vuex";
|
||||
import ScriptOutput from "@/components/checks/ScriptOutput.vue";
|
||||
import EventLogCheckOutput from "@/components/checks/EventLogCheckOutput.vue";
|
||||
|
||||
@@ -220,6 +220,12 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
"dash_info_color",
|
||||
"dash_positive_color",
|
||||
"dash_negative_color",
|
||||
"dash_warning_color",
|
||||
]),
|
||||
title() {
|
||||
return !!this.item.readable_desc
|
||||
? this.item.readable_desc + " Status"
|
||||
|
@@ -39,6 +39,19 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-select
|
||||
dense
|
||||
:label="envVarsLabel"
|
||||
filled
|
||||
v-model="state.env_vars"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
label="Informational return codes (press Enter after typing each code)"
|
||||
@@ -115,6 +128,7 @@ import { useDialogPluginComponent } from "quasar";
|
||||
import { useCheckModal } from "@/composables/checks";
|
||||
import { useScriptDropdown } from "@/composables/scripts";
|
||||
import { validateRetcode } from "@/utils/validation";
|
||||
import { envVarsLabel } from "@/constants/constants";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
@@ -132,10 +146,15 @@ 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,
|
||||
defaultEnvVars,
|
||||
} = useScriptDropdown(props.check ? props.check.script : undefined, {
|
||||
onMount: true,
|
||||
});
|
||||
|
||||
// check logic
|
||||
const { state, loading, submit, failOptions, severityOptions } =
|
||||
@@ -145,6 +164,7 @@ export default {
|
||||
...props.parent,
|
||||
script,
|
||||
script_args: defaultArgs,
|
||||
env_vars: defaultEnvVars,
|
||||
timeout: defaultTimeout,
|
||||
check_type: "script",
|
||||
fails_b4_alert: 1,
|
||||
@@ -163,6 +183,7 @@ export default {
|
||||
failOptions,
|
||||
scriptOptions,
|
||||
severityOptions,
|
||||
envVarsLabel,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
|
@@ -122,7 +122,7 @@ export default {
|
||||
|
||||
try {
|
||||
const result = props.APIKey
|
||||
? await editAPIKey(data)
|
||||
? await editAPIKey(data.id, data)
|
||||
: await saveAPIKey(data);
|
||||
onDialogOK();
|
||||
notifySuccess(result);
|
||||
|
@@ -208,7 +208,7 @@ export default {
|
||||
}
|
||||
|
||||
// component lifecycle hooks
|
||||
onMounted(getAPIKeys());
|
||||
onMounted(getAPIKeys);
|
||||
return {
|
||||
// reactive data
|
||||
keys,
|
||||
|
@@ -304,6 +304,9 @@ export default {
|
||||
// setup vuex
|
||||
const store = useStore();
|
||||
const formatDate = computed(() => store.getters.formatDate);
|
||||
const dash_positive_color = computed(() => store.state.dash_positive_color);
|
||||
const dash_negative_color = computed(() => store.state.dash_negative_color);
|
||||
const dash_warning_color = computed(() => store.state.dash_warning_color);
|
||||
|
||||
// setup dropdowns
|
||||
const { clientOptions, getClientOptions } = useClientDropdown();
|
||||
@@ -381,12 +384,18 @@ export default {
|
||||
}
|
||||
|
||||
function formatActionColor(action) {
|
||||
if (action === "add") return "success";
|
||||
else if (action === "agent_install") return "success";
|
||||
else if (action === "modify") return "warning";
|
||||
else if (action === "delete") return "negative";
|
||||
else if (action === "failed_login") return "negative";
|
||||
else return "primary";
|
||||
switch (action.toLowerCase()) {
|
||||
case "modify":
|
||||
return dash_warning_color.value;
|
||||
case "add":
|
||||
case "agent_install":
|
||||
return dash_positive_color.value;
|
||||
case "delete":
|
||||
case "failed_login":
|
||||
return dash_negative_color.value;
|
||||
default:
|
||||
return "primary";
|
||||
}
|
||||
}
|
||||
|
||||
// watchers
|
||||
|
@@ -68,25 +68,25 @@
|
||||
/>
|
||||
<q-radio
|
||||
v-model="logLevelFilter"
|
||||
color="cyan"
|
||||
:color="dash_info_color"
|
||||
val="info"
|
||||
label="Info"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="logLevelFilter"
|
||||
color="red"
|
||||
:color="dash_negative_color"
|
||||
val="critical"
|
||||
label="Critical"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="logLevelFilter"
|
||||
color="red"
|
||||
:color="dash_negative_color"
|
||||
val="error"
|
||||
label="Error"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="logLevelFilter"
|
||||
color="yellow"
|
||||
:color="dash_warning_color"
|
||||
val="warning"
|
||||
label="Warning"
|
||||
/>
|
||||
@@ -109,7 +109,7 @@
|
||||
<template v-slot:top-row>
|
||||
<q-tr v-if="Array.isArray(debugLog) && debugLog.length === 1000">
|
||||
<q-td colspan="100%">
|
||||
<q-icon name="warning" color="warning" />
|
||||
<q-icon name="warning" :color="dash_warning_color" />
|
||||
Results are limited to 1000 rows.
|
||||
</q-td>
|
||||
</q-tr>
|
||||
@@ -203,6 +203,10 @@ export default {
|
||||
const store = useStore();
|
||||
|
||||
const formatDate = computed(() => store.getters.formatDate);
|
||||
const dash_info_color = computed(() => store.state.dash_info_color);
|
||||
const dash_positive_color = computed(() => store.state.dash_positive_color);
|
||||
const dash_negative_color = computed(() => store.state.dash_negative_color);
|
||||
const dash_warning_color = computed(() => store.state.dash_warning_color);
|
||||
|
||||
// setup dropdowns
|
||||
const { agentOptions, getAgentOptions } = useAgentDropdown();
|
||||
@@ -261,6 +265,10 @@ export default {
|
||||
agentOptions,
|
||||
loading,
|
||||
filter,
|
||||
dash_info_color,
|
||||
dash_positive_color,
|
||||
dash_warning_color,
|
||||
dash_negative_color,
|
||||
|
||||
// non-reactive data
|
||||
columns,
|
||||
|
@@ -10,10 +10,13 @@
|
||||
</q-card-actions>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<p class="text-subtitle1">
|
||||
<p v-if="info.plat === 'windows'" class="text-subtitle1">
|
||||
Download the agent then run the following command from an elevated
|
||||
command prompt on the device you want to add.
|
||||
</p>
|
||||
<p v-else-if="info.plat === 'darwin'" class="text-subtitle1">
|
||||
Run the following command from a terminal
|
||||
</p>
|
||||
<p>
|
||||
<q-field outlined :color="$q.dark.isActive ? 'white' : 'black'">
|
||||
<code>{{ info.data.cmd }}</code>
|
||||
@@ -37,7 +40,7 @@
|
||||
</q-badge>
|
||||
<span>Do not popup any message boxes during install</span>
|
||||
</div>
|
||||
<div class="q-pa-xs q-gutter-xs">
|
||||
<div v-if="info.plat === 'windows'" class="q-pa-xs q-gutter-xs">
|
||||
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
||||
<code
|
||||
>-local-mesh "C:\\<some folder or
|
||||
@@ -46,7 +49,7 @@
|
||||
</q-badge>
|
||||
<span> To skip downloading the Mesh Agent during the install.</span>
|
||||
</div>
|
||||
<div class="q-pa-xs q-gutter-xs">
|
||||
<div v-if="info.plat === 'windows'" 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
|
||||
@@ -63,7 +66,7 @@
|
||||
</q-badge>
|
||||
<span>Don't install the mesh agent</span>
|
||||
</div>
|
||||
<div class="q-pa-xs q-gutter-xs">
|
||||
<div v-if="info.plat === 'windows'" class="q-pa-xs q-gutter-xs">
|
||||
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
||||
<code>-cert "C:\\<some folder or path>\\ca.pem"</code>
|
||||
</q-badge>
|
||||
@@ -87,11 +90,12 @@
|
||||
Note: the auth token above will be valid for {{ info.expires }} hours.
|
||||
</p>
|
||||
<q-btn
|
||||
v-if="info.plat === 'windows'"
|
||||
type="a"
|
||||
:href="info.data.url"
|
||||
color="primary"
|
||||
label="Download Agent"
|
||||
/>
|
||||
></q-btn>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
|
@@ -102,6 +102,18 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="mode === 'script'" class="q-pt-none">
|
||||
<tactical-dropdown
|
||||
v-model="state.env_vars"
|
||||
:label="envVarsLabel"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section v-if="mode === 'command'">
|
||||
<p>Shell</p>
|
||||
@@ -135,6 +147,11 @@
|
||||
:rules="[(val) => !!val || '*Required']"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="supportsRunAsUser()" class="q-pt-none">
|
||||
<q-checkbox v-model="state.run_as_user" label="Run As User">
|
||||
<q-tooltip>{{ runAsUserToolTip }}</q-tooltip>
|
||||
</q-checkbox>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section v-if="mode === 'script' || mode === 'command'">
|
||||
<q-input
|
||||
@@ -203,6 +220,7 @@ import { runBulkAction } from "@/api/agents";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
import { cmdPlaceholder } from "@/composables/agents";
|
||||
import { removeExtraOptionCategories } from "@/utils/format";
|
||||
import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
@@ -217,6 +235,7 @@ const monTypeOptions = [
|
||||
const osTypeOptions = [
|
||||
{ label: "Windows", value: "windows" },
|
||||
{ label: "Linux", value: "linux" },
|
||||
{ label: "macOS", value: "darwin" },
|
||||
{ label: "All", value: "all" },
|
||||
];
|
||||
|
||||
@@ -277,6 +296,7 @@ export default {
|
||||
scriptOptions,
|
||||
defaultTimeout,
|
||||
defaultArgs,
|
||||
defaultEnvVars,
|
||||
getScriptOptions,
|
||||
} = useScriptDropdown();
|
||||
const { agents, agentOptions, getAgentOptions } = useAgentDropdown();
|
||||
@@ -300,6 +320,8 @@ export default {
|
||||
script,
|
||||
timeout: defaultTimeout,
|
||||
args: defaultArgs,
|
||||
env_vars: defaultEnvVars,
|
||||
run_as_user: false,
|
||||
});
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -316,6 +338,7 @@ export default {
|
||||
() => state.value.osType,
|
||||
(newValue) => {
|
||||
state.value.custom_shell = null;
|
||||
state.value.run_as_user = false;
|
||||
|
||||
if (newValue === "windows") {
|
||||
state.value.shell = "cmd";
|
||||
@@ -337,6 +360,13 @@ export default {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
const supportsRunAsUser = () => {
|
||||
const modes = ["script", "command"];
|
||||
return (
|
||||
state.value.osType === "windows" && modes.includes(state.value.mode)
|
||||
);
|
||||
};
|
||||
|
||||
// set modal title and caption
|
||||
const modalTitle = computed(() => {
|
||||
return props.mode === "command"
|
||||
@@ -387,6 +417,8 @@ export default {
|
||||
osTypeOptions,
|
||||
targetOptions,
|
||||
patchModeOptions,
|
||||
runAsUserToolTip,
|
||||
envVarsLabel,
|
||||
|
||||
//computed
|
||||
modalTitle,
|
||||
@@ -394,6 +426,7 @@ export default {
|
||||
//methods
|
||||
submit,
|
||||
cmdPlaceholder,
|
||||
supportsRunAsUser,
|
||||
|
||||
// quasar dialog plugin
|
||||
dialogRef,
|
||||
|
@@ -94,7 +94,7 @@
|
||||
class="q-pr-sm"
|
||||
name="fas fa-signal"
|
||||
size="1.2em"
|
||||
color="warning"
|
||||
:color="dash_warning_color"
|
||||
/>
|
||||
Mark an agent as
|
||||
<span class="text-weight-bold">offline</span> if it has
|
||||
@@ -120,7 +120,7 @@
|
||||
class="q-pr-sm"
|
||||
name="fas fa-signal"
|
||||
size="1.2em"
|
||||
color="negative"
|
||||
:color="dash_negative_color"
|
||||
/>
|
||||
Mark an agent as
|
||||
<span class="text-weight-bold">overdue</span> if it has
|
||||
@@ -373,6 +373,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import mixins from "@/mixins/mixins";
|
||||
import PatchPolicyForm from "@/components/modals/agents/PatchPolicyForm.vue";
|
||||
@@ -465,8 +466,51 @@ export default {
|
||||
});
|
||||
},
|
||||
editAgent() {
|
||||
delete this.agent.all_timezones;
|
||||
delete this.agent.timezone;
|
||||
// TODO we need to fix the serializer to not send this stuff
|
||||
const toRemove = [
|
||||
"created_by",
|
||||
"created_time",
|
||||
"modified_by",
|
||||
"modified_time",
|
||||
"all_timezones",
|
||||
"timezone",
|
||||
"wmi_detail",
|
||||
"services",
|
||||
"status",
|
||||
"cpu_model",
|
||||
"local_ips",
|
||||
"make_model",
|
||||
"physical_disks",
|
||||
"graphics",
|
||||
"checks",
|
||||
"patches_last_installed",
|
||||
"last_seen",
|
||||
"applied_policies",
|
||||
"effective_patch_policy",
|
||||
"version",
|
||||
"operating_system",
|
||||
"plat",
|
||||
"goarch",
|
||||
"hostname",
|
||||
"public_ip",
|
||||
"total_ram",
|
||||
"disks",
|
||||
"boot_time",
|
||||
"logged_in_username",
|
||||
"last_logged_in_user",
|
||||
"needs_reboot",
|
||||
"choco_installed",
|
||||
"policy",
|
||||
"mesh_node_id",
|
||||
"block_policy_inheritance",
|
||||
"maintenance_mode",
|
||||
"alert_template",
|
||||
"client",
|
||||
"site_name",
|
||||
];
|
||||
for (const elem of toRemove) {
|
||||
delete this.agent[elem];
|
||||
}
|
||||
|
||||
// only send the timezone data if it has changed
|
||||
// this way django will keep the db column as null and inherit from the global setting
|
||||
@@ -503,9 +547,12 @@ export default {
|
||||
else if (day === 0) result += "Sun, ";
|
||||
}
|
||||
|
||||
return result.trimRight(",");
|
||||
return result.trimEnd(",");
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(["dash_warning_color", "dash_negative_color"]),
|
||||
},
|
||||
mounted() {
|
||||
// Get custom fields
|
||||
this.getCustomFields("agent").then((r) => {
|
||||
|
@@ -52,6 +52,15 @@
|
||||
goarch = GOARCH_AMD64;
|
||||
"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="agentOS"
|
||||
val="darwin"
|
||||
label="macOS"
|
||||
@update:model-value="
|
||||
installMethod = 'mac';
|
||||
goarch = GOARCH_AMD64;
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
@@ -105,37 +114,37 @@
|
||||
v-model="goarch"
|
||||
:val="GOARCH_AMD64"
|
||||
label="64 bit"
|
||||
v-show="agentOS === 'windows'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_i386"
|
||||
label="32 bit"
|
||||
v-show="agentOS === 'windows'"
|
||||
v-show="agentOS === 'windows' || agentOS === 'linux'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_AMD64"
|
||||
label="64 bit"
|
||||
v-show="agentOS !== 'windows'"
|
||||
label="Intel 64 bit"
|
||||
v-show="agentOS === 'darwin'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_i386"
|
||||
label="32 bit"
|
||||
v-show="agentOS !== 'windows'"
|
||||
v-show="agentOS !== 'darwin'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_ARM64"
|
||||
label="ARM 64 bit"
|
||||
v-show="agentOS !== 'windows'"
|
||||
v-show="agentOS === 'linux'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_ARM64"
|
||||
label="Apple Silicon (M1, M2)"
|
||||
v-show="agentOS === 'darwin'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_ARM32"
|
||||
label="ARM 32 bit (Rasp Pi)"
|
||||
v-show="agentOS !== 'windows'"
|
||||
v-show="agentOS === 'linux'"
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
@@ -266,12 +275,13 @@ export default {
|
||||
plat: this.agentOS,
|
||||
};
|
||||
|
||||
if (this.installMethod === "manual") {
|
||||
if (this.installMethod === "manual" || this.installMethod === "mac") {
|
||||
this.$axios.post("/agents/installer/", data).then((r) => {
|
||||
this.info = {
|
||||
expires: this.expires,
|
||||
data: r.data,
|
||||
goarch: this.goarch,
|
||||
plat: this.agentOS,
|
||||
};
|
||||
this.showAgentDownload = true;
|
||||
});
|
||||
@@ -343,6 +353,9 @@ export default {
|
||||
case "bash":
|
||||
text = "Download linux install script";
|
||||
break;
|
||||
case "mac":
|
||||
text = "Show installation instructions";
|
||||
break;
|
||||
}
|
||||
|
||||
return text;
|
||||
|
@@ -129,37 +129,37 @@
|
||||
<div class="q-gutter-sm">
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="1"
|
||||
:val="0"
|
||||
label="Monday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="2"
|
||||
:val="1"
|
||||
label="Tuesday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="3"
|
||||
:val="2"
|
||||
label="Wednesday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="4"
|
||||
:val="3"
|
||||
label="Thursday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="5"
|
||||
:val="4"
|
||||
label="Friday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="6"
|
||||
:val="5"
|
||||
label="Saturday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="0"
|
||||
:val="6"
|
||||
label="Sunday"
|
||||
/>
|
||||
</div>
|
||||
|
@@ -63,11 +63,14 @@ export default {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
await scheduleAgentReboot(props.agent.agent_id, state.value);
|
||||
const ret = await scheduleAgentReboot(
|
||||
props.agent.agent_id,
|
||||
state.value
|
||||
);
|
||||
$q.dialog({
|
||||
title: "Reboot pending",
|
||||
style: "width: 40vw",
|
||||
message: `A reboot has been scheduled for <strong>${state.value.datetime}</strong> on ${props.agent.hostname}.
|
||||
message: `A reboot has been scheduled for <strong>${ret.time}</strong> on ${props.agent.hostname}.
|
||||
<br />It can be cancelled from the Pending Actions menu until the scheduled time.`,
|
||||
html: true,
|
||||
}).onDismiss(onDialogOK);
|
||||
|
@@ -77,6 +77,18 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
v-model="state.env_vars"
|
||||
:label="envVarsLabel"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-option-group
|
||||
v-model="state.output"
|
||||
@@ -128,6 +140,11 @@
|
||||
/>
|
||||
<q-checkbox v-model="state.save_all_output" label="Save all output" />
|
||||
</q-card-section>
|
||||
<q-card-section v-if="agent.plat === 'windows'">
|
||||
<q-checkbox v-model="state.run_as_user" label="Run As User">
|
||||
<q-tooltip>{{ runAsUserToolTip }}</q-tooltip>
|
||||
</q-checkbox>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input
|
||||
v-model.number="state.timeout"
|
||||
@@ -173,6 +190,7 @@ import { useScriptDropdown } from "@/composables/scripts";
|
||||
import { useCustomFieldDropdown } from "@/composables/core";
|
||||
import { runScript } from "@/api/agents";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
|
||||
import {
|
||||
formatScriptSyntax,
|
||||
removeExtraOptionCategories,
|
||||
@@ -203,11 +221,18 @@ export default {
|
||||
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||
|
||||
// setup dropdowns
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs, syntax, link } =
|
||||
useScriptDropdown(props.script, {
|
||||
onMount: true,
|
||||
filterByPlatform: props.agent.plat,
|
||||
});
|
||||
const {
|
||||
script,
|
||||
scriptOptions,
|
||||
defaultTimeout,
|
||||
defaultArgs,
|
||||
defaultEnvVars,
|
||||
syntax,
|
||||
link,
|
||||
} = useScriptDropdown(props.script, {
|
||||
onMount: true,
|
||||
filterByPlatform: props.agent.plat,
|
||||
});
|
||||
const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
|
||||
|
||||
// main run script functionaity
|
||||
@@ -219,7 +244,9 @@ export default {
|
||||
save_all_output: false,
|
||||
script,
|
||||
args: defaultArgs,
|
||||
env_vars: defaultEnvVars,
|
||||
timeout: defaultTimeout,
|
||||
run_as_user: false,
|
||||
});
|
||||
|
||||
const ret = ref(null);
|
||||
@@ -273,6 +300,8 @@ export default {
|
||||
|
||||
// non-reactive data
|
||||
outputOptions,
|
||||
runAsUserToolTip,
|
||||
envVarsLabel,
|
||||
|
||||
//methods
|
||||
formatScriptSyntax,
|
||||
|
@@ -51,6 +51,11 @@
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="agent.plat === 'windows'">
|
||||
<q-checkbox v-model="state.run_as_user" label="Run As User">
|
||||
<q-tooltip>{{ runAsUserToolTip }}</q-tooltip>
|
||||
</q-checkbox>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="state.shell === 'custom'">
|
||||
<q-input
|
||||
v-model="state.custom_shell"
|
||||
@@ -117,6 +122,7 @@ import { ref } from "vue";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import { sendAgentCommand } from "@/api/agents";
|
||||
import { cmdPlaceholder } from "@/composables/agents";
|
||||
import { runAsUserToolTip } from "@/constants/constants";
|
||||
|
||||
export default {
|
||||
name: "SendCommand",
|
||||
@@ -134,6 +140,7 @@ export default {
|
||||
cmd: null,
|
||||
timeout: 30,
|
||||
custom_shell: null,
|
||||
run_as_user: false,
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -156,6 +163,9 @@ export default {
|
||||
loading,
|
||||
ret,
|
||||
|
||||
// non reactivete data
|
||||
runAsUserToolTip,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
cmdPlaceholder,
|
||||
|
@@ -204,6 +204,20 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
class="q-mb-sm"
|
||||
dense
|
||||
label="Failure action environment vars (press Enter after typing each key=value pair)"
|
||||
filled
|
||||
v-model="template.action_env_vars"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
class="q-mb-sm"
|
||||
label="Failure action timeout (seconds)"
|
||||
@@ -277,6 +291,20 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
class="q-mb-sm"
|
||||
dense
|
||||
label="Resolved action environment vars (press Enter after typing each key=value pair)"
|
||||
filled
|
||||
v-model="template.resolved_action_env_vars"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
class="q-mb-sm"
|
||||
label="Resolved action timeout (seconds)"
|
||||
@@ -696,9 +724,11 @@ export default {
|
||||
is_active: true,
|
||||
action: null,
|
||||
action_args: [],
|
||||
action_env_vars: [],
|
||||
action_timeout: 15,
|
||||
resolved_action: null,
|
||||
resolved_action_args: [],
|
||||
resolved_action_env_vars: [],
|
||||
resolved_action_timeout: 15,
|
||||
email_recipients: [],
|
||||
email_from: "",
|
||||
@@ -762,11 +792,13 @@ export default {
|
||||
(i) => i.value === this.template.action
|
||||
);
|
||||
this.template.action_args = script.args;
|
||||
this.template.action_env_vars = script.env_vars;
|
||||
} else if (type === "resolved") {
|
||||
const script = this.scriptOptions.find(
|
||||
(i) => i.value === this.template.resolved_action
|
||||
);
|
||||
this.template.resolved_action_args = script.args;
|
||||
this.template.resolved_action_env_vars = script.env_vars;
|
||||
}
|
||||
},
|
||||
toggleAddEmail() {
|
||||
|
@@ -12,6 +12,7 @@
|
||||
<q-tab name="urlactions" label="URL Actions" />
|
||||
<q-tab name="retention" label="Retention" />
|
||||
<q-tab name="apikeys" label="API Keys" />
|
||||
<!-- <q-tab name="openai" label="Open AI" /> -->
|
||||
</q-tabs>
|
||||
</template>
|
||||
<template v-slot:after>
|
||||
@@ -508,6 +509,49 @@
|
||||
<q-tab-panel name="apikeys">
|
||||
<APIKeysTable />
|
||||
</q-tab-panel>
|
||||
|
||||
<!-- Open AI -->
|
||||
<!-- <q-tab-panel name="openai">
|
||||
<div class="text-subtitle2">Open AI</div>
|
||||
<q-separator />
|
||||
<q-card-section class="row">
|
||||
<div class="col-4">API Key:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
v-model="settings.open_ai_token"
|
||||
class="col-6"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-4">Open AI Model:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
v-model="settings.open_ai_model"
|
||||
class="col-6"
|
||||
>
|
||||
<template v-slot:after>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
flat
|
||||
icon="info"
|
||||
size="sm"
|
||||
@click="
|
||||
openURL(
|
||||
'https://platform.openai.com/docs/models/overview'
|
||||
)
|
||||
"
|
||||
>
|
||||
<q-tooltip>Click to see available options</q-tooltip>
|
||||
</q-btn>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-card-section>
|
||||
</q-tab-panel> -->
|
||||
</q-tab-panels>
|
||||
</q-scroll-area>
|
||||
<q-card-section class="row items-center">
|
||||
|
@@ -82,6 +82,98 @@
|
||||
class="col-4"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-2">Dashboard Info Color:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-input
|
||||
outlined
|
||||
dense
|
||||
v-model="dash_info_color"
|
||||
class="col-8"
|
||||
>
|
||||
<template v-slot:after>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
flat
|
||||
size="sm"
|
||||
icon="info"
|
||||
@click="openURL(quasar_color_url)"
|
||||
>
|
||||
<q-tooltip>Click to see color options</q-tooltip>
|
||||
</q-btn>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-2">Dashboard Positive Color:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-input
|
||||
outlined
|
||||
dense
|
||||
v-model="dash_positive_color"
|
||||
class="col-8"
|
||||
>
|
||||
<template v-slot:after>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
flat
|
||||
size="sm"
|
||||
icon="info"
|
||||
@click="openURL(quasar_color_url)"
|
||||
>
|
||||
<q-tooltip>Click to see color options</q-tooltip>
|
||||
</q-btn>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-2">Dashboard Negative Color:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-input
|
||||
outlined
|
||||
dense
|
||||
v-model="dash_negative_color"
|
||||
class="col-8"
|
||||
>
|
||||
<template v-slot:after>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
flat
|
||||
size="sm"
|
||||
icon="info"
|
||||
@click="openURL(quasar_color_url)"
|
||||
>
|
||||
<q-tooltip>Click to see color options</q-tooltip>
|
||||
</q-btn>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-2">Dashboard Warning Color:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-input
|
||||
outlined
|
||||
dense
|
||||
v-model="dash_warning_color"
|
||||
class="col-8"
|
||||
>
|
||||
<template v-slot:after>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
flat
|
||||
size="sm"
|
||||
icon="info"
|
||||
@click="openURL(quasar_color_url)"
|
||||
>
|
||||
<q-tooltip>Click to see color options</q-tooltip>
|
||||
</q-btn>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-2">Client Sort:</div>
|
||||
<div class="col-2"></div>
|
||||
@@ -156,9 +248,14 @@ export default {
|
||||
tab: "ui",
|
||||
splitterModel: 20,
|
||||
loading_bar_color: "",
|
||||
dash_info_color: "",
|
||||
dash_positive_color: "",
|
||||
dash_negative_color: "",
|
||||
dash_warning_color: "",
|
||||
urlActions: [],
|
||||
clear_search_when_switching: true,
|
||||
date_format: "",
|
||||
quasar_color_url: "https://quasar.dev/style/color-palette",
|
||||
clientTreeSortOptions: [
|
||||
{
|
||||
label: "Sort alphabetically, moving failing clients to the top",
|
||||
@@ -235,6 +332,10 @@ export default {
|
||||
this.defaultAgentTblTab = r.data.default_agent_tbl_tab;
|
||||
this.clientTreeSort = r.data.client_tree_sort;
|
||||
this.loading_bar_color = r.data.loading_bar_color;
|
||||
this.dash_info_color = r.data.dash_info_color;
|
||||
this.dash_positive_color = r.data.dash_positive_color;
|
||||
this.dash_negative_color = r.data.dash_negative_color;
|
||||
this.dash_warning_color = r.data.dash_warning_color;
|
||||
this.clear_search_when_switching = r.data.clear_search_when_switching;
|
||||
this.date_format = r.data.date_format;
|
||||
});
|
||||
@@ -253,6 +354,10 @@ export default {
|
||||
default_agent_tbl_tab: this.defaultAgentTblTab,
|
||||
client_tree_sort: this.clientTreeSort,
|
||||
loading_bar_color: this.loading_bar_color,
|
||||
dash_info_color: this.dash_info_color,
|
||||
dash_positive_color: this.dash_positive_color,
|
||||
dash_negative_color: this.dash_negative_color,
|
||||
dash_warning_color: this.dash_warning_color,
|
||||
clear_search_when_switching: this.clear_search_when_switching,
|
||||
date_format: this.date_format,
|
||||
};
|
||||
|
@@ -3,7 +3,7 @@
|
||||
ref="dialogRef"
|
||||
@hide="onDialogHide"
|
||||
persistent
|
||||
@keydown.esc="onDialogHide"
|
||||
@keydown.esc.stop="onDialogHide"
|
||||
:maximized="maximized"
|
||||
>
|
||||
<q-card
|
||||
@@ -11,7 +11,17 @@
|
||||
:style="maximized ? '' : 'width: 90vw; max-width: 90vw'"
|
||||
>
|
||||
<q-bar>
|
||||
{{ title }}
|
||||
<span class="q-pr-sm">{{ title }}</span>
|
||||
<q-btn
|
||||
v-if="!script && openAIEnabled"
|
||||
size="xs"
|
||||
:disable="loading"
|
||||
dense
|
||||
label="Generate Script"
|
||||
color="primary"
|
||||
no-caps
|
||||
@click="generateScriptOpenAI"
|
||||
/>
|
||||
<q-space />
|
||||
<q-btn
|
||||
dense
|
||||
@@ -57,93 +67,133 @@
|
||||
><br />Add one to get rid of this warning. Ignore if windows.
|
||||
</q-banner>
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-4 q-gutter-sm q-pr-sm">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model="formScript.name"
|
||||
label="Name"
|
||||
:rules="[(val) => !!val || '*Required']"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model="formScript.description"
|
||||
label="Description"
|
||||
/>
|
||||
<q-select
|
||||
:readonly="readonly"
|
||||
options-dense
|
||||
filled
|
||||
dense
|
||||
v-model="formScript.shell"
|
||||
:options="shellOptions"
|
||||
emit-value
|
||||
map-options
|
||||
label="Shell Type"
|
||||
/>
|
||||
<tactical-dropdown
|
||||
v-model="formScript.supported_platforms"
|
||||
:options="agentPlatformOptions"
|
||||
label="Supported Platforms (All supported if blank)"
|
||||
clearable
|
||||
mapOptions
|
||||
filled
|
||||
multiple
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<tactical-dropdown
|
||||
filled
|
||||
v-model="formScript.category"
|
||||
:options="categories"
|
||||
use-input
|
||||
clearable
|
||||
new-value-mode="add-unique"
|
||||
filterable
|
||||
label="Category"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<tactical-dropdown
|
||||
v-model="formScript.args"
|
||||
label="Script Arguments (press Enter after typing each argument)"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model.number="formScript.default_timeout"
|
||||
label="Timeout (seconds)"
|
||||
:rules="[(val) => val >= 5 || 'Minimum is 5']"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<q-input
|
||||
label="Syntax"
|
||||
type="textarea"
|
||||
style="height: 150px; overflow-y: auto; resize: none"
|
||||
v-model="formScript.syntax"
|
||||
dense
|
||||
filled
|
||||
:readonly="readonly"
|
||||
/>
|
||||
</div>
|
||||
<q-scroll-area
|
||||
:thumb-style="{
|
||||
right: '4px',
|
||||
borderRadius: '5px',
|
||||
width: '5px',
|
||||
opacity: 0.75,
|
||||
}"
|
||||
:bar-style="{
|
||||
right: '2px',
|
||||
borderRadius: '9px',
|
||||
width: '9px',
|
||||
opacity: 0.2,
|
||||
}"
|
||||
class="col-4 q-mb-none q-pb-none"
|
||||
:style="{ height: `${maximized ? '82vh' : '64vh'}` }"
|
||||
>
|
||||
<div class="q-gutter-sm q-pr-sm">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model="formScript.name"
|
||||
label="Name"
|
||||
:rules="[(val) => !!val || '*Required']"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model="formScript.description"
|
||||
label="Description"
|
||||
/>
|
||||
<q-select
|
||||
:readonly="readonly"
|
||||
options-dense
|
||||
filled
|
||||
dense
|
||||
v-model="formScript.shell"
|
||||
:options="shellOptions"
|
||||
emit-value
|
||||
map-options
|
||||
label="Shell Type"
|
||||
/>
|
||||
<tactical-dropdown
|
||||
v-model="formScript.supported_platforms"
|
||||
:options="agentPlatformOptions"
|
||||
label="Supported Platforms (All supported if blank)"
|
||||
clearable
|
||||
mapOptions
|
||||
filled
|
||||
multiple
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<tactical-dropdown
|
||||
filled
|
||||
v-model="formScript.category"
|
||||
:options="categories"
|
||||
use-input
|
||||
clearable
|
||||
new-value-mode="add-unique"
|
||||
filterable
|
||||
label="Category"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<tactical-dropdown
|
||||
v-model="formScript.args"
|
||||
label="Script Arguments (press Enter after typing each argument)"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<tactical-dropdown
|
||||
v-model="formScript.env_vars"
|
||||
:label="envVarsLabel"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model.number="formScript.default_timeout"
|
||||
label="Timeout (seconds)"
|
||||
:rules="[(val) => val >= 5 || 'Minimum is 5']"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="formScript.run_as_user"
|
||||
label="Run As User (Windows only)"
|
||||
>
|
||||
<q-tooltip
|
||||
>Setting this value on the script model will always override
|
||||
any 'Run As User' checkboxes in the UI and force this script
|
||||
to always be run in the context of the logged in user. If no
|
||||
user is logged in, the script will not run and an error will
|
||||
be returned.
|
||||
</q-tooltip>
|
||||
</q-checkbox>
|
||||
<q-input
|
||||
label="Syntax"
|
||||
type="textarea"
|
||||
style="height: 150px; overflow-y: auto; resize: none"
|
||||
v-model="formScript.syntax"
|
||||
dense
|
||||
filled
|
||||
:readonly="readonly"
|
||||
/>
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
<v-ace-editor
|
||||
v-model:value="formScript.script_body"
|
||||
class="col-8"
|
||||
:lang="lang"
|
||||
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
|
||||
:style="{ height: `${maximized ? '87vh' : '64vh'}` }"
|
||||
:style="{ height: `${maximized ? '82vh' : '64vh'}` }"
|
||||
wrap
|
||||
:printMargin="false"
|
||||
:options="{ fontSize: '14px' }"
|
||||
@@ -197,9 +247,11 @@
|
||||
<script>
|
||||
// composable imports
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useQuasar, useDialogPluginComponent } from "quasar";
|
||||
import { saveScript, editScript, downloadScript } from "@/api/scripts";
|
||||
import { useAgentDropdown, agentPlatformOptions } from "@/composables/agents";
|
||||
import { generateScript } from "@/api/core";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
|
||||
// ui imports
|
||||
@@ -217,6 +269,7 @@ import "ace-builds/src-noconflict/theme-tomorrow";
|
||||
|
||||
// static data
|
||||
import { shellOptions } from "@/composables/scripts";
|
||||
import { envVarsLabel } from "@/constants/constants";
|
||||
|
||||
export default {
|
||||
name: "ScriptFormModal",
|
||||
@@ -242,6 +295,10 @@ export default {
|
||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
|
||||
const $q = useQuasar();
|
||||
|
||||
// setup store
|
||||
const store = useStore();
|
||||
const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled);
|
||||
|
||||
// setup agent dropdown
|
||||
const { agent, agentOptions, getAgentOptions } = useAgentDropdown();
|
||||
|
||||
@@ -253,6 +310,8 @@ export default {
|
||||
default_timeout: 90,
|
||||
args: [],
|
||||
script_body: "",
|
||||
run_as_user: false,
|
||||
env_vars: [],
|
||||
});
|
||||
|
||||
if (props.clone) script.value.name = `(Copy) ${script.value.name}`;
|
||||
@@ -329,6 +388,23 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
function generateScriptOpenAI() {
|
||||
$q.dialog({
|
||||
title: "Ask ChatGPT what you need!",
|
||||
prompt: {
|
||||
model: `${lang.value} code that `,
|
||||
type: "text",
|
||||
},
|
||||
cancel: true,
|
||||
persistent: true,
|
||||
}).onOk(async (data) => {
|
||||
const completion = await generateScript({
|
||||
prompt: data,
|
||||
});
|
||||
script.value.script_body = completion;
|
||||
});
|
||||
}
|
||||
|
||||
// component life cycle hooks
|
||||
onMounted(async () => {
|
||||
agentLoading.value = true;
|
||||
@@ -350,13 +426,16 @@ export default {
|
||||
// non-reactive data
|
||||
shellOptions,
|
||||
agentPlatformOptions,
|
||||
envVarsLabel,
|
||||
|
||||
//computed
|
||||
title,
|
||||
openAIEnabled,
|
||||
|
||||
//methods
|
||||
submitForm,
|
||||
openTestScriptModal,
|
||||
generateScriptOpenAI,
|
||||
|
||||
// quasar dialog plugin
|
||||
dialogRef,
|
||||
|
@@ -286,15 +286,10 @@
|
||||
</template>
|
||||
</q-tree>
|
||||
</div>
|
||||
<q-table
|
||||
<tactical-table
|
||||
v-if="tableView"
|
||||
dense
|
||||
:table-class="{
|
||||
'table-bgcolor': !$q.dark.isActive,
|
||||
'table-bgcolor-dark': $q.dark.isActive,
|
||||
}"
|
||||
:style="{ 'max-height': `${$q.screen.height - 182}px` }"
|
||||
class="tbl-sticky"
|
||||
:rows="visibleScripts"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
@@ -304,6 +299,7 @@
|
||||
binary-state-sort
|
||||
virtual-scroll
|
||||
:rows-per-page-options="[0]"
|
||||
column-select
|
||||
>
|
||||
<template v-slot:header-cell-favorite="props">
|
||||
<q-th :props="props" auto-width>
|
||||
@@ -425,7 +421,7 @@
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<!-- favorite -->
|
||||
<q-td>
|
||||
<q-td key="favorite" :props="props">
|
||||
<q-icon
|
||||
v-if="props.row.favorite"
|
||||
color="yellow-8"
|
||||
@@ -434,7 +430,7 @@
|
||||
/>
|
||||
</q-td>
|
||||
<!-- shell icon -->
|
||||
<q-td>
|
||||
<q-td key="shell" :props="props">
|
||||
<q-icon
|
||||
v-if="props.row.shell === 'powershell'"
|
||||
name="mdi-powershell"
|
||||
@@ -469,7 +465,7 @@
|
||||
</q-icon>
|
||||
</q-td>
|
||||
<!-- supported platforms -->
|
||||
<q-td>
|
||||
<q-td key="supported_platforms" :props="props">
|
||||
<q-badge
|
||||
v-if="
|
||||
!props.row.supported_platforms ||
|
||||
@@ -487,7 +483,11 @@
|
||||
>
|
||||
</q-td>
|
||||
<!-- name -->
|
||||
<q-td :style="{ color: props.row.hidden ? 'grey' : '' }">
|
||||
<q-td
|
||||
key="name"
|
||||
:props="props"
|
||||
:style="{ color: props.row.hidden ? 'grey' : '' }"
|
||||
>
|
||||
{{ truncateText(props.row.name, 50) }}
|
||||
<q-tooltip
|
||||
v-if="props.row.name.length >= 50"
|
||||
@@ -497,7 +497,7 @@
|
||||
</q-tooltip>
|
||||
</q-td>
|
||||
<!-- args -->
|
||||
<q-td>
|
||||
<q-td key="args" :props="props">
|
||||
<span v-if="props.row.args.length > 0">
|
||||
{{ truncateText(props.row.args.toString(), 30) }}
|
||||
<q-tooltip
|
||||
@@ -509,8 +509,8 @@
|
||||
</span>
|
||||
</q-td>
|
||||
|
||||
<q-td>{{ props.row.category }}</q-td>
|
||||
<q-td>
|
||||
<q-td key="category" :props="props">{{ props.row.category }}</q-td>
|
||||
<q-td key="desc" :props="props">
|
||||
{{ truncateText(props.row.description, 30) }}
|
||||
<q-tooltip
|
||||
v-if="props.row.description.length >= 30"
|
||||
@@ -518,10 +518,13 @@
|
||||
>{{ props.row.description }}</q-tooltip
|
||||
>
|
||||
</q-td>
|
||||
<q-td>{{ props.row.default_timeout }}</q-td>
|
||||
<q-td key="default_timeout" :props="props">{{
|
||||
props.row.default_timeout
|
||||
}}</q-td>
|
||||
<q-td></q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</tactical-table>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
@@ -545,12 +548,13 @@ import { notifySuccess } from "@/utils/notify";
|
||||
import ScriptUploadModal from "@/components/scripts/ScriptUploadModal.vue";
|
||||
import ScriptFormModal from "@/components/scripts/ScriptFormModal.vue";
|
||||
import ScriptSnippets from "@/components/scripts/ScriptSnippets.vue";
|
||||
import TacticalTable from "@/components/ui/TacticalTable.vue";
|
||||
|
||||
// static data
|
||||
const columns = [
|
||||
{
|
||||
name: "favorite",
|
||||
label: "",
|
||||
label: "Favorites",
|
||||
field: "favorite",
|
||||
align: "left",
|
||||
sortable: true,
|
||||
@@ -608,6 +612,9 @@ const columns = [
|
||||
|
||||
export default {
|
||||
name: "ScriptManager",
|
||||
components: {
|
||||
TacticalTable,
|
||||
},
|
||||
emits: [...useDialogPluginComponent.emits],
|
||||
setup() {
|
||||
// setup vuex store
|
||||
@@ -867,7 +874,7 @@ export default {
|
||||
}
|
||||
|
||||
// component life cycle hooks
|
||||
onMounted(getScripts());
|
||||
onMounted(getScripts);
|
||||
|
||||
return {
|
||||
// reactive data
|
||||
|
@@ -11,7 +11,17 @@
|
||||
:style="maximized ? '' : 'width: 70vw; max-width: 90vw'"
|
||||
>
|
||||
<q-bar>
|
||||
{{ title }}
|
||||
<span class="q-pr-sm">{{ title }}</span>
|
||||
<q-btn
|
||||
v-if="!snippet && openAIEnabled"
|
||||
:disable="loading"
|
||||
dense
|
||||
size="xs"
|
||||
label="Generate Script"
|
||||
color="primary"
|
||||
no-caps
|
||||
@click="generateScriptOpenAI"
|
||||
/>
|
||||
<q-space />
|
||||
<q-btn
|
||||
dense
|
||||
@@ -97,6 +107,9 @@
|
||||
<script>
|
||||
// composable imports
|
||||
import { ref, computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useQuasar } from "quasar";
|
||||
import { generateScript } from "@/api/core";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import { saveScriptSnippet, editScriptSnippet } from "@/api/scripts";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
@@ -128,6 +141,13 @@ export default {
|
||||
// setup quasar plugins
|
||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
// setup quasar
|
||||
const $q = useQuasar();
|
||||
|
||||
// setup store
|
||||
const store = useStore();
|
||||
const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled);
|
||||
|
||||
// snippet form logic
|
||||
const snippet = props.snippet
|
||||
? ref(Object.assign({}, props.snippet))
|
||||
@@ -167,6 +187,23 @@ export default {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function generateScriptOpenAI() {
|
||||
$q.dialog({
|
||||
title: "Ask ChatGPT what you need!",
|
||||
prompt: {
|
||||
model: `${lang.value} code that `,
|
||||
type: "text",
|
||||
},
|
||||
cancel: true,
|
||||
persistent: true,
|
||||
}).onOk(async (data) => {
|
||||
const completion = await generateScript({
|
||||
prompt: data,
|
||||
});
|
||||
snippet.value.code = completion;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// reactive data
|
||||
formSnippet: snippet.value,
|
||||
@@ -179,9 +216,11 @@ export default {
|
||||
|
||||
//computed
|
||||
title,
|
||||
openAIEnabled,
|
||||
|
||||
//methods
|
||||
submitForm,
|
||||
generateScriptOpenAI,
|
||||
|
||||
// quasar dialog plugin
|
||||
dialogRef,
|
||||
|
@@ -93,6 +93,20 @@
|
||||
/>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
v-model="script.env_vars"
|
||||
label="Environment Variables"
|
||||
placeholder="(press Enter after typing each key=value pair)"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section>
|
||||
<q-input
|
||||
label="Default Timeout"
|
||||
|
@@ -44,6 +44,8 @@ export default {
|
||||
timeout: props.script.default_timeout,
|
||||
args: props.script.args,
|
||||
shell: props.script.shell,
|
||||
run_as_user: props.script.run_as_user,
|
||||
env_vars: props.script.env_vars,
|
||||
};
|
||||
try {
|
||||
ret.value = await testScript(props.agent, data);
|
||||
|
@@ -102,7 +102,7 @@
|
||||
|
||||
<tactical-dropdown
|
||||
v-if="actionType === 'script'"
|
||||
class="col-4"
|
||||
class="col-3"
|
||||
label="Select script"
|
||||
v-model="script"
|
||||
:options="scriptOptions"
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<q-select
|
||||
v-if="actionType === 'script'"
|
||||
class="col-5"
|
||||
class="col-3"
|
||||
dense
|
||||
label="Script Arguments (press Enter after typing each argument)"
|
||||
filled
|
||||
@@ -126,6 +126,21 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
v-if="actionType === 'script'"
|
||||
class="col-3"
|
||||
dense
|
||||
:label="envVarsLabel"
|
||||
filled
|
||||
v-model="defaultEnvVars"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
v-if="actionType === 'script'"
|
||||
class="col-2"
|
||||
@@ -210,6 +225,9 @@
|
||||
<q-item-label caption>
|
||||
Arguments: {{ element.script_args }}
|
||||
</q-item-label>
|
||||
<q-item-label caption>
|
||||
Env Vars: {{ element.env_vars }}
|
||||
</q-item-label>
|
||||
<q-item-label caption>
|
||||
Timeout: {{ element.timeout }}
|
||||
</q-item-label>
|
||||
@@ -727,6 +745,7 @@ import { useCheckDropdown } from "@/composables/checks";
|
||||
import { useCustomFieldDropdown } from "@/composables/core";
|
||||
import { notifySuccess, notifyError } from "@/utils/notify";
|
||||
import { validateTimePeriod } from "@/utils/validation";
|
||||
import { envVarsLabel } from "@/constants/constants";
|
||||
import {
|
||||
convertPeriodToSeconds,
|
||||
convertToBitArray,
|
||||
@@ -817,10 +836,15 @@ export default {
|
||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
// setup dropdowns
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs } =
|
||||
useScriptDropdown(undefined, {
|
||||
onMount: true,
|
||||
});
|
||||
const {
|
||||
script,
|
||||
scriptOptions,
|
||||
defaultTimeout,
|
||||
defaultArgs,
|
||||
defaultEnvVars,
|
||||
} = useScriptDropdown(undefined, {
|
||||
onMount: true,
|
||||
});
|
||||
|
||||
// set defaultTimeout to 30
|
||||
defaultTimeout.value = 30;
|
||||
@@ -914,6 +938,7 @@ export default {
|
||||
script: script.value,
|
||||
timeout: defaultTimeout.value,
|
||||
script_args: defaultArgs.value,
|
||||
env_vars: defaultEnvVars.value,
|
||||
});
|
||||
} else if (actionType.value === "cmd") {
|
||||
task.value.actions.push({
|
||||
@@ -927,6 +952,7 @@ export default {
|
||||
// clear fields after add
|
||||
script.value = null;
|
||||
defaultArgs.value = [];
|
||||
defaultEnvVars.value = [];
|
||||
defaultTimeout.value = 30;
|
||||
command.value = "";
|
||||
}
|
||||
@@ -991,10 +1017,16 @@ export default {
|
||||
: [];
|
||||
|
||||
// remove milliseconds and Z to work with native date input
|
||||
task.value.run_time_date = formatDateInputField(task.value.run_time_date);
|
||||
task.value.run_time_date = formatDateInputField(
|
||||
task.value.run_time_date,
|
||||
true
|
||||
);
|
||||
|
||||
if (task.value.expire_date)
|
||||
task.value.expire_date = formatDateInputField(task.value.expire_date);
|
||||
task.value.expire_date = formatDateInputField(
|
||||
task.value.expire_date,
|
||||
true
|
||||
);
|
||||
|
||||
// set task type if monthlydow is being used
|
||||
if (task.value.task_type === "monthlydow") {
|
||||
@@ -1083,6 +1115,7 @@ export default {
|
||||
script,
|
||||
defaultTimeout,
|
||||
defaultArgs,
|
||||
defaultEnvVars,
|
||||
actionType,
|
||||
command,
|
||||
shell,
|
||||
@@ -1110,6 +1143,7 @@ export default {
|
||||
monthOptions,
|
||||
taskTypeOptions,
|
||||
taskInstancePolicyOptions,
|
||||
envVarsLabel,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
|
107
src/components/ui/TacticalTable.vue
Normal file
107
src/components/ui/TacticalTable.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<q-table
|
||||
:columns="localColumns"
|
||||
:visible-columns="visibleColumns"
|
||||
:table-class="{
|
||||
'table-bgcolor': !$q.dark.isActive,
|
||||
'table-bgcolor-dark': $q.dark.isActive,
|
||||
'column-bgcolor-dark': $q.dark.isActive && columnSelect,
|
||||
'column-bgcolor': !$q.dark.isActive && columnSelect,
|
||||
'sticky-header-right-column': columnSelect,
|
||||
'tbl-sticky': !columnSelect,
|
||||
}"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template v-for="(_, slot) in $slots" v-slot:[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope || {}" />
|
||||
</template>
|
||||
|
||||
<template v-slot:header-cell-columnSelect="props">
|
||||
<q-th :props="props" auto-width>
|
||||
<q-btn dense flat icon="more_horiz">
|
||||
<q-menu>
|
||||
<q-option-group
|
||||
v-model="visibleColumns"
|
||||
:options="columnOptions"
|
||||
type="checkbox"
|
||||
/>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</q-th>
|
||||
</template>
|
||||
</q-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { type QTableColumn } from "quasar";
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
columns: QTableColumn[];
|
||||
columnSelect?: boolean;
|
||||
excludeColumns?: string[];
|
||||
}>(),
|
||||
{ columnSelect: false, excludeColumns: () => ["columnSelect"] }
|
||||
);
|
||||
// save a non-reactive copy of columns to modify
|
||||
const localColumns: QTableColumn[] = Object.assign([], props.columns);
|
||||
if (props.columnSelect)
|
||||
localColumns.push({
|
||||
name: "columnSelect",
|
||||
label: "Column Select",
|
||||
field: "columnSelect",
|
||||
});
|
||||
const visibleColumns = ref(localColumns.map((column) => column.name));
|
||||
const columnOptions = ref(
|
||||
localColumns
|
||||
.filter((column) => !props.excludeColumns.includes(column.name))
|
||||
.map((column) => ({ label: column.label, value: column.name }))
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
||||
.column-bgcolor-dark
|
||||
td:last-child
|
||||
/* bg color is important for td; just specify one */
|
||||
background-color: #1d1d1d
|
||||
|
||||
.column-bgcolor
|
||||
td:last-child
|
||||
/* bg color is important for td; just specify one */
|
||||
background-color: #ffffff
|
||||
|
||||
.sticky-header-right-column
|
||||
tr th
|
||||
position: sticky
|
||||
/* higher than z-index for td below */
|
||||
z-index: 2
|
||||
/* this will be the loading indicator */
|
||||
thead tr:last-child th
|
||||
/* height of all previous header rows */
|
||||
top: 48px
|
||||
/* highest z-index */
|
||||
z-index: 3
|
||||
thead tr:last-child th
|
||||
top: 0
|
||||
z-index: 1
|
||||
tr:last-child th:last-child
|
||||
/* highest z-index */
|
||||
z-index: 3
|
||||
td:last-child
|
||||
z-index: 1
|
||||
td:last-child, th:last-child
|
||||
position: sticky
|
||||
right: 0
|
||||
/* prevent scrolling behind sticky top row on focus */
|
||||
tbody
|
||||
/* height of all previous header rows */
|
||||
scroll-margin-top: 48px
|
||||
</style>
|
@@ -31,7 +31,7 @@ export function useUserDropdown(onMount = false) {
|
||||
}
|
||||
|
||||
if (onMount) {
|
||||
onMounted(getUserOptions());
|
||||
onMounted(getUserOptions);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { fetchAgents } from "@/api/agents";
|
||||
import { formatAgentOptions } from "@/utils/format";
|
||||
|
||||
@@ -28,13 +29,16 @@ export function useAgentDropdown() {
|
||||
}
|
||||
|
||||
export function cmdPlaceholder(shell) {
|
||||
if (shell === "cmd") return "rmdir /S /Q C:\\Windows\\System32";
|
||||
else if (shell === "powershell")
|
||||
return "Remove-Item -Recurse -Force C:\\Windows\\System32";
|
||||
else return "rm -rf --no-preserve-root /";
|
||||
const store = useStore();
|
||||
const placeholders = computed(() => store.state.run_cmd_placeholder_text);
|
||||
|
||||
if (shell === "cmd") return placeholders.value.cmd;
|
||||
else if (shell === "powershell") return placeholders.value.powershell;
|
||||
else return placeholders.value.shell;
|
||||
}
|
||||
|
||||
export const agentPlatformOptions = [
|
||||
{ value: "windows", label: "Windows" },
|
||||
{ value: "linux", label: "Linux" },
|
||||
{ value: "darwin", label: "macOS" },
|
||||
];
|
||||
|
@@ -8,6 +8,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
|
||||
const scriptOptions = ref([]);
|
||||
const defaultTimeout = ref(30);
|
||||
const defaultArgs = ref([]);
|
||||
const defaultEnvVars = ref([]);
|
||||
const script = ref(setScript);
|
||||
const syntax = ref("");
|
||||
const link = ref("");
|
||||
@@ -29,6 +30,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
|
||||
);
|
||||
defaultTimeout.value = tmpScript.timeout;
|
||||
defaultArgs.value = tmpScript.args;
|
||||
defaultEnvVars.value = tmpScript.env_vars;
|
||||
syntax.value = tmpScript.syntax;
|
||||
link.value =
|
||||
tmpScript.script_type === "builtin"
|
||||
@@ -49,6 +51,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
|
||||
scriptOptions,
|
||||
defaultTimeout,
|
||||
defaultArgs,
|
||||
defaultEnvVars,
|
||||
syntax,
|
||||
link,
|
||||
|
||||
|
@@ -1,6 +1,10 @@
|
||||
const GOARCH_AMD64 = "amd64";
|
||||
const GOARCH_i386 = "386";
|
||||
const GOARCH_ARM64 = "arm64";
|
||||
const GOARCH_ARM32 = "arm";
|
||||
export const GOARCH_AMD64 = "amd64";
|
||||
export const GOARCH_i386 = "386";
|
||||
export const GOARCH_ARM64 = "arm64";
|
||||
export const GOARCH_ARM32 = "arm";
|
||||
|
||||
export { GOARCH_AMD64, GOARCH_i386, GOARCH_ARM64, GOARCH_ARM32 };
|
||||
export const runAsUserToolTip =
|
||||
"Run in the context of the logged in user. If no user is logged in, the script will not run and an error will be returned.";
|
||||
|
||||
export const envVarsLabel =
|
||||
"Environment vars (press Enter after typing each key=value pair)";
|
||||
|
@@ -14,6 +14,27 @@
|
||||
@click="$store.dispatch('reload')"
|
||||
/>
|
||||
</q-banner>
|
||||
<q-banner
|
||||
v-if="!hosted && tokenExpired"
|
||||
inline-actions
|
||||
class="bg-yellow text-black text-center"
|
||||
>
|
||||
<q-icon size="xl" name="warning" />
|
||||
<span
|
||||
><br />Your code signing token is no longer valid.<br /><br />
|
||||
If you have downgraded or cancelled your sponsorship, please delete
|
||||
your token from the Code Signing modal and refresh to get rid of this
|
||||
banner.<br /><br />
|
||||
For any issues or to renew your sponsorship please email
|
||||
support@amidaware.com<br /><br
|
||||
/></span>
|
||||
<q-btn
|
||||
color="dark"
|
||||
icon="refresh"
|
||||
label="Refresh"
|
||||
@click="$store.dispatch('reload')"
|
||||
/>
|
||||
</q-banner>
|
||||
<q-toolbar>
|
||||
<q-btn
|
||||
dense
|
||||
@@ -35,20 +56,27 @@
|
||||
Tactical RMM<span class="text-overline q-ml-sm"
|
||||
>v{{ currentTRMMVersion }}</span
|
||||
>
|
||||
<span
|
||||
class="text-overline q-ml-md"
|
||||
v-if="
|
||||
latestTRMMVersion !== 'error' &&
|
||||
currentTRMMVersion !== latestTRMMVersion
|
||||
"
|
||||
><q-badge color="warning"
|
||||
><a :href="latestReleaseURL" target="_blank"
|
||||
>v{{ latestTRMMVersion }} available</a
|
||||
></q-badge
|
||||
></span
|
||||
<!-- update check -->
|
||||
<q-chip
|
||||
v-if="updateAvailable"
|
||||
class="text-overline q-ml-sm"
|
||||
:color="dash_warning_color"
|
||||
icon="update"
|
||||
dense
|
||||
><a :href="latestReleaseURL" target="_blank"
|
||||
>v{{ latestTRMMVersion }} available</a
|
||||
></q-chip
|
||||
>
|
||||
<!-- cert expiring soon check -->
|
||||
<q-chip
|
||||
v-if="daysUntilCertExpires <= 15"
|
||||
dense
|
||||
:color="dash_negative_color"
|
||||
text-color="black"
|
||||
icon="warning"
|
||||
>Certificate expires in {{ daysUntilCertExpires }} days</q-chip
|
||||
>
|
||||
</q-toolbar-title>
|
||||
|
||||
<!-- temp dark mode toggle -->
|
||||
<q-toggle
|
||||
v-model="darkMode"
|
||||
@@ -78,7 +106,11 @@
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="power_off" size="sm" color="negative" />
|
||||
<q-icon
|
||||
name="power_off"
|
||||
size="sm"
|
||||
:color="dash_negative_color"
|
||||
/>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section no-wrap>
|
||||
@@ -97,7 +129,11 @@
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="power_off" size="sm" color="negative" />
|
||||
<q-icon
|
||||
name="power_off"
|
||||
size="sm"
|
||||
:color="dash_negative_color"
|
||||
/>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section no-wrap>
|
||||
@@ -124,6 +160,32 @@
|
||||
<q-item-label>Preferences</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable>
|
||||
<q-item-section>Account</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
</q-item-section>
|
||||
|
||||
<q-menu anchor="top end" self="top start">
|
||||
<q-list>
|
||||
<q-item
|
||||
clickable
|
||||
v-ripple
|
||||
@click="resetPassword"
|
||||
v-close-popup
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>Reset Password</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-ripple @click="reset2FA" v-close-popup>
|
||||
<q-item-section>
|
||||
<q-item-label>Reset 2FA</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
<q-item to="/expired" exact>
|
||||
<q-item-section>
|
||||
<q-item-label>Logout</q-item-label>
|
||||
@@ -145,10 +207,13 @@ import { useQuasar } from "quasar";
|
||||
import { useStore } from "vuex";
|
||||
import axios from "axios";
|
||||
import { getWSUrl } from "@/websocket/channels";
|
||||
import { resetTwoFactor } from "@/api/accounts";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
|
||||
// ui imports
|
||||
import AlertsIcon from "@/components/AlertsIcon.vue";
|
||||
import UserPreferences from "@/components/modals/coresettings/UserPreferences.vue";
|
||||
import ResetPass from "@/components/accounts/ResetPass.vue";
|
||||
|
||||
export default {
|
||||
name: "MainLayout",
|
||||
@@ -171,6 +236,10 @@ export default {
|
||||
const latestTRMMVersion = computed(() => store.state.latestTRMMVersion);
|
||||
const needRefresh = computed(() => store.state.needrefresh);
|
||||
const user = computed(() => store.state.username);
|
||||
const hosted = computed(() => store.state.hosted);
|
||||
const tokenExpired = computed(() => store.state.tokenExpired);
|
||||
const dash_warning_color = computed(() => store.state.dash_warning_color);
|
||||
const dash_negative_color = computed(() => store.state.dash_negative_color);
|
||||
|
||||
const latestReleaseURL = computed(() => {
|
||||
return latestTRMMVersion.value
|
||||
@@ -184,10 +253,31 @@ export default {
|
||||
}).onOk(() => store.dispatch("getDashInfo"));
|
||||
}
|
||||
|
||||
function resetPassword() {
|
||||
$q.dialog({
|
||||
component: ResetPass,
|
||||
});
|
||||
}
|
||||
|
||||
function reset2FA() {
|
||||
$q.dialog({
|
||||
title: "Reset 2FA",
|
||||
message: "Are you sure you would like to reset your 2FA token?",
|
||||
cancel: true,
|
||||
persistent: true,
|
||||
}).onOk(async () => {
|
||||
try {
|
||||
const ret = await resetTwoFactor();
|
||||
notifySuccess(ret, 3000);
|
||||
} catch {}
|
||||
});
|
||||
}
|
||||
|
||||
const serverCount = ref(0);
|
||||
const serverOfflineCount = ref(0);
|
||||
const workstationCount = ref(0);
|
||||
const workstationOfflineCount = ref(0);
|
||||
const daysUntilCertExpires = ref(100);
|
||||
|
||||
const ws = ref(null);
|
||||
|
||||
@@ -195,6 +285,13 @@ export default {
|
||||
// moved computed token inside the function since it is not refreshing
|
||||
// when ws is closed causing ws to connect with expired token
|
||||
const token = computed(() => store.state.token);
|
||||
|
||||
if (!token.value) {
|
||||
console.log(
|
||||
"Access token is null or invalid, not setting up WebSocket"
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.log("Starting websocket");
|
||||
let url = getWSUrl("dashinfo", token.value);
|
||||
ws.value = new WebSocket(url);
|
||||
@@ -207,6 +304,7 @@ export default {
|
||||
serverOfflineCount.value = data.total_server_offline_count;
|
||||
workstationCount.value = data.total_workstation_count;
|
||||
workstationOfflineCount.value = data.total_workstation_offline_count;
|
||||
daysUntilCertExpires.value = data.days_until_cert_expires;
|
||||
};
|
||||
ws.value.onclose = (e) => {
|
||||
try {
|
||||
@@ -230,9 +328,19 @@ export default {
|
||||
poll.value = setInterval(() => {
|
||||
store.dispatch("checkVer");
|
||||
store.dispatch("getDashInfo", false);
|
||||
}, 60 * 5 * 1000);
|
||||
}, 60 * 4 * 1000);
|
||||
}
|
||||
|
||||
const updateAvailable = computed(() => {
|
||||
if (
|
||||
latestTRMMVersion.value === "error" ||
|
||||
hosted.value ||
|
||||
currentTRMMVersion.value?.includes("-dev")
|
||||
)
|
||||
return false;
|
||||
return currentTRMMVersion.value !== latestTRMMVersion.value;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
setupWS();
|
||||
store.dispatch("getDashInfo");
|
||||
@@ -252,15 +360,23 @@ export default {
|
||||
serverOfflineCount,
|
||||
workstationCount,
|
||||
workstationOfflineCount,
|
||||
daysUntilCertExpires,
|
||||
latestReleaseURL,
|
||||
currentTRMMVersion,
|
||||
latestTRMMVersion,
|
||||
user,
|
||||
needRefresh,
|
||||
darkMode,
|
||||
hosted,
|
||||
tokenExpired,
|
||||
dash_warning_color,
|
||||
dash_negative_color,
|
||||
|
||||
// methods
|
||||
showUserPreferences,
|
||||
resetPassword,
|
||||
reset2FA,
|
||||
updateAvailable,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@@ -193,6 +193,7 @@ export default {
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
env_vars: script.env_vars,
|
||||
});
|
||||
} else if (cat === "Unassigned" && !script.category) {
|
||||
tmp.push({
|
||||
@@ -200,6 +201,7 @@ export default {
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
env_vars: script.env_vars,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@@ -17,6 +17,7 @@ export default function () {
|
||||
agentPlatform: "windows",
|
||||
agentTableLoading: false,
|
||||
needrefresh: false,
|
||||
tokenExpired: false,
|
||||
refreshSummaryTab: false,
|
||||
tableHeight: "300px",
|
||||
tabHeight: "300px",
|
||||
@@ -32,6 +33,16 @@ export default function () {
|
||||
currentTRMMVersion: null,
|
||||
latestTRMMVersion: null,
|
||||
dateFormat: "MMM-DD-YYYY - HH:mm",
|
||||
openAIIntegrationEnabled: false,
|
||||
dash_info_color: "info",
|
||||
dash_positive_color: "positive",
|
||||
dash_negative_color: "negative",
|
||||
dash_warning_color: "warning",
|
||||
run_cmd_placeholder_text: {
|
||||
cmd: "rmdir /S /Q C:\\Windows\\System32",
|
||||
powershell: "Remove-Item -Recurse -Force C:\\Windows\\System32",
|
||||
shell: "rm -rf --no-preserve-root /",
|
||||
},
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
@@ -83,6 +94,9 @@ export default function () {
|
||||
SET_REFRESH_NEEDED(state, action) {
|
||||
state.needrefresh = action;
|
||||
},
|
||||
SET_TOKEN_EXPIRED(state, action) {
|
||||
state.tokenExpired = action;
|
||||
},
|
||||
SET_SPLITTER(state, val) {
|
||||
// top toolbar is 50px. Filebar is 40px and agent filter tabs are 44px
|
||||
state.tableHeight = `${Screen.height - 50 - 40 - 78 - val}px`;
|
||||
@@ -132,6 +146,24 @@ export default function () {
|
||||
setDateFormat(state, val) {
|
||||
state.dateFormat = val;
|
||||
},
|
||||
setOpenAIIntegrationStatus(state, val) {
|
||||
state.openAIIntegrationEnabled = val;
|
||||
},
|
||||
setDashInfoColor(state, val) {
|
||||
state.dash_info_color = val;
|
||||
},
|
||||
setDashPositiveColor(state, val) {
|
||||
state.dash_positive_color = val;
|
||||
},
|
||||
setDashNegativeColor(state, val) {
|
||||
state.dash_negative_color = val;
|
||||
},
|
||||
setDashWarningColor(state, val) {
|
||||
state.dash_warning_color = val;
|
||||
},
|
||||
setRunCmdPlaceholders(state, obj) {
|
||||
state.run_cmd_placeholder_text = obj;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setClientTreeSplitter(context, val) {
|
||||
@@ -156,9 +188,9 @@ export default function () {
|
||||
}
|
||||
if (clearTreeSelected) commit("destroySubTable");
|
||||
|
||||
dispatch("getDashInfo", false);
|
||||
dispatch("loadAgents");
|
||||
dispatch("loadTree");
|
||||
dispatch("getDashInfo", false);
|
||||
},
|
||||
async loadAgents({ state, commit }) {
|
||||
commit("AGENT_TABLE_LOADING", true);
|
||||
@@ -190,106 +222,111 @@ export default function () {
|
||||
|
||||
commit("AGENT_TABLE_LOADING", false);
|
||||
},
|
||||
async getDashInfo(context, edited = true) {
|
||||
async getDashInfo({ commit }, edited = true) {
|
||||
const { data } = await axios.get("/core/dashinfo/");
|
||||
commit("setDashInfoColor", data.dash_info_color);
|
||||
commit("setDashPositiveColor", data.dash_positive_color);
|
||||
commit("setDashNegativeColor", data.dash_negative_color);
|
||||
commit("setDashWarningColor", data.dash_warning_color);
|
||||
if (edited) {
|
||||
LoadingBar.setDefaults({ color: data.loading_bar_color });
|
||||
context.commit(
|
||||
commit(
|
||||
"setClearSearchWhenSwitching",
|
||||
data.clear_search_when_switching
|
||||
);
|
||||
context.commit(
|
||||
"SET_DEFAULT_AGENT_TBL_TAB",
|
||||
data.default_agent_tbl_tab
|
||||
);
|
||||
context.commit("SET_CLIENT_TREE_SORT", data.client_tree_sort);
|
||||
context.commit("SET_CLIENT_SPLITTER", data.client_tree_splitter);
|
||||
commit("SET_DEFAULT_AGENT_TBL_TAB", data.default_agent_tbl_tab);
|
||||
commit("SET_CLIENT_TREE_SORT", data.client_tree_sort);
|
||||
commit("SET_CLIENT_SPLITTER", data.client_tree_splitter);
|
||||
}
|
||||
Dark.set(data.dark_mode);
|
||||
context.commit("setCurrentTRMMVersion", data.trmm_version);
|
||||
context.commit("setLatestTRMMVersion", data.latest_trmm_ver);
|
||||
context.commit("SET_AGENT_DBLCLICK_ACTION", data.dbl_click_action);
|
||||
context.commit("SET_URL_ACTION", data.url_action);
|
||||
context.commit("setShowCommunityScripts", data.show_community_scripts);
|
||||
context.commit("SET_HOSTED", data.hosted);
|
||||
commit("setCurrentTRMMVersion", data.trmm_version);
|
||||
commit("setLatestTRMMVersion", data.latest_trmm_ver);
|
||||
commit("SET_AGENT_DBLCLICK_ACTION", data.dbl_click_action);
|
||||
commit("SET_URL_ACTION", data.url_action);
|
||||
commit("setShowCommunityScripts", data.show_community_scripts);
|
||||
commit("SET_HOSTED", data.hosted);
|
||||
commit("SET_TOKEN_EXPIRED", data.token_is_expired);
|
||||
commit("setOpenAIIntegrationStatus", data.open_ai_integration_enabled);
|
||||
commit("setRunCmdPlaceholders", data.run_cmd_placeholder_text);
|
||||
|
||||
if (data.date_format && data.date_format !== "")
|
||||
context.commit("setDateFormat", data.date_format);
|
||||
else context.commit("setDateFormat", data.default_date_format);
|
||||
if (data?.date_format !== "") commit("setDateFormat", data.date_format);
|
||||
else commit("setDateFormat", data.default_date_format);
|
||||
},
|
||||
loadTree({ commit, state }) {
|
||||
axios
|
||||
.get("/clients/")
|
||||
.then((r) => {
|
||||
if (r.data.length === 0) {
|
||||
this.$router.push({ name: "InitialSetup" });
|
||||
}
|
||||
setTimeout(() => {
|
||||
axios
|
||||
.get("/clients/")
|
||||
.then((r) => {
|
||||
if (r.data.length === 0) {
|
||||
this.$router.push({ name: "InitialSetup" });
|
||||
}
|
||||
|
||||
let output = [];
|
||||
for (let client of r.data) {
|
||||
let childSites = [];
|
||||
for (let site of client.sites) {
|
||||
let siteNode = {
|
||||
label: site.name,
|
||||
id: site.id,
|
||||
raw: `Site|${site.id}`,
|
||||
header: "generic",
|
||||
icon: "apartment",
|
||||
selectable: true,
|
||||
site: site,
|
||||
};
|
||||
let output = [];
|
||||
for (let client of r.data) {
|
||||
let childSites = [];
|
||||
for (let site of client.sites) {
|
||||
let siteNode = {
|
||||
label: site.name,
|
||||
id: site.id,
|
||||
raw: `Site|${site.id}`,
|
||||
header: "generic",
|
||||
icon: "apartment",
|
||||
selectable: true,
|
||||
site: site,
|
||||
};
|
||||
|
||||
if (site.maintenance_mode) {
|
||||
siteNode["color"] = "green";
|
||||
} else if (site.failing_checks.error) {
|
||||
siteNode["color"] = "negative";
|
||||
} else if (site.failing_checks.warning) {
|
||||
siteNode["color"] = "warning";
|
||||
if (site.maintenance_mode) {
|
||||
siteNode["color"] = "green";
|
||||
} else if (site.failing_checks.error) {
|
||||
siteNode["color"] = "negative";
|
||||
} else if (site.failing_checks.warning) {
|
||||
siteNode["color"] = "warning";
|
||||
}
|
||||
|
||||
childSites.push(siteNode);
|
||||
}
|
||||
|
||||
childSites.push(siteNode);
|
||||
let clientNode = {
|
||||
label: client.name,
|
||||
id: client.id,
|
||||
raw: `Client|${client.id}`,
|
||||
header: "root",
|
||||
icon: "business",
|
||||
children: childSites,
|
||||
client: client,
|
||||
};
|
||||
|
||||
if (client.maintenance_mode) clientNode["color"] = "green";
|
||||
else if (client.failing_checks.error) {
|
||||
clientNode["color"] = "negative";
|
||||
} else if (client.failing_checks.warning) {
|
||||
clientNode["color"] = "warning";
|
||||
}
|
||||
|
||||
output.push(clientNode);
|
||||
}
|
||||
|
||||
let clientNode = {
|
||||
label: client.name,
|
||||
id: client.id,
|
||||
raw: `Client|${client.id}`,
|
||||
header: "root",
|
||||
icon: "business",
|
||||
children: childSites,
|
||||
client: client,
|
||||
};
|
||||
|
||||
if (client.maintenance_mode) clientNode["color"] = "green";
|
||||
else if (client.failing_checks.error) {
|
||||
clientNode["color"] = "negative";
|
||||
} else if (client.failing_checks.warning) {
|
||||
clientNode["color"] = "warning";
|
||||
const sorted = output.sort((a, b) =>
|
||||
a.label.localeCompare(b.label)
|
||||
);
|
||||
if (state.clientTreeSort === "alphafail") {
|
||||
// move failing clients to the top
|
||||
const failing = sorted.filter(
|
||||
(i) => i.color === "negative" || i.color === "warning"
|
||||
);
|
||||
const ok = sorted.filter(
|
||||
(i) => i.color !== "negative" && i.color !== "warning"
|
||||
);
|
||||
const sortedByFailing = [...failing, ...ok];
|
||||
commit("loadTree", sortedByFailing);
|
||||
} else {
|
||||
commit("loadTree", sorted);
|
||||
}
|
||||
|
||||
output.push(clientNode);
|
||||
}
|
||||
|
||||
const sorted = output.sort((a, b) =>
|
||||
a.label.localeCompare(b.label)
|
||||
);
|
||||
if (state.clientTreeSort === "alphafail") {
|
||||
// move failing clients to the top
|
||||
const failing = sorted.filter(
|
||||
(i) => i.color === "negative" || i.color === "warning"
|
||||
);
|
||||
const ok = sorted.filter(
|
||||
(i) => i.color !== "negative" && i.color !== "warning"
|
||||
);
|
||||
const sortedByFailing = [...failing, ...ok];
|
||||
commit("loadTree", sortedByFailing);
|
||||
} else {
|
||||
commit("loadTree", sorted);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
state.treeReady = true;
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
state.treeReady = true;
|
||||
});
|
||||
}, 150);
|
||||
},
|
||||
checkVer(context) {
|
||||
axios.get("/core/version/").then((r) => {
|
||||
|
@@ -68,6 +68,7 @@ export function formatScriptOptions(data) {
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
env_vars: script.env_vars,
|
||||
filename: script.filename,
|
||||
syntax: script.syntax,
|
||||
script_type: script.script_type,
|
||||
@@ -80,6 +81,7 @@ export function formatScriptOptions(data) {
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
env_vars: script.env_vars,
|
||||
filename: script.filename,
|
||||
syntax: script.syntax,
|
||||
script_type: script.script_type,
|
||||
@@ -285,7 +287,7 @@ export function formatDateInputField(isoDateString, noTimezone = false) {
|
||||
if (noTimezone) {
|
||||
isoDateString = isoDateString.replace("Z", "");
|
||||
}
|
||||
return date.formatDate(isoDateString, "YYYY-MM-DDTHH:mm:ss");
|
||||
return date.formatDate(isoDateString, "YYYY-MM-DDTHH:mm");
|
||||
}
|
||||
|
||||
// converts a local date string "YYYY-MM-DDTHH:mm:ss" to an iso date string with the local timezone
|
||||
|
@@ -173,6 +173,18 @@
|
||||
</q-menu>
|
||||
</q-item>
|
||||
|
||||
<!-- Bulk Run Checks -->
|
||||
<q-item
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="runChecks(props.node)"
|
||||
>
|
||||
<q-item-section side>
|
||||
<q-icon name="fas fa-check-double" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run Checks</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator></q-separator>
|
||||
|
||||
<q-item clickable v-close-popup>
|
||||
@@ -440,7 +452,7 @@ export default {
|
||||
showInstallAgentModal: false,
|
||||
sitePk: null,
|
||||
innerModel: (this.$q.screen.height - 82) / 2,
|
||||
search: "",
|
||||
search: this.$route.query.search ? this.$route.query.search : "",
|
||||
filterTextLength: 0,
|
||||
filterAvailability: "all",
|
||||
filterPatchesPending: false,
|
||||
@@ -690,6 +702,17 @@ export default {
|
||||
})
|
||||
.onOk(() => this.$store.dispatch("refreshDashboard"));
|
||||
},
|
||||
runChecks(node) {
|
||||
const target = node.children ? "client" : "site";
|
||||
this.$axios
|
||||
.post(`/checks/${target}/${node.id}/csbulkrun/`)
|
||||
.then((r) => {
|
||||
this.notifySuccess(r.data);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
},
|
||||
showToggleMaintenance(node) {
|
||||
let data = {
|
||||
id: node.id,
|
||||
|
@@ -4,8 +4,17 @@
|
||||
<div class="col"></div>
|
||||
<div class="col">
|
||||
<q-card>
|
||||
<q-card-actions align="center">
|
||||
<q-btn
|
||||
label="Getting Started"
|
||||
color="info"
|
||||
class="full-width"
|
||||
href="https://docs.tacticalrmm.com/guide_gettingstarted/"
|
||||
target="_blank"
|
||||
/>
|
||||
</q-card-actions>
|
||||
<q-card-section class="row items-center">
|
||||
<div class="text-h6">Initial Setup</div>
|
||||
<div class="text-h5 text-weight-bold">Initial Setup</div>
|
||||
</q-card-section>
|
||||
<q-form @submit.prevent="finish">
|
||||
<q-card-section>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
@click="restartMeshService"
|
||||
/>
|
||||
<q-btn
|
||||
color="negative"
|
||||
:color="dash_negative_color"
|
||||
size="sm"
|
||||
label="Recover Connection"
|
||||
icon="fas fa-first-aid"
|
||||
@@ -35,6 +35,7 @@
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useMeta, useQuasar } from "quasar";
|
||||
import { fetchAgentMeshCentralURLs, sendAgentRecoverMesh } from "@/api/agents";
|
||||
@@ -47,12 +48,17 @@ export default {
|
||||
setup() {
|
||||
// vue lifecycle hooks
|
||||
onMounted(() => {
|
||||
dashInfo();
|
||||
getDashInfo();
|
||||
getMeshURLs();
|
||||
});
|
||||
|
||||
// quasar setup
|
||||
const $q = useQuasar();
|
||||
const store = useStore();
|
||||
const dash_positive_color = computed(() => store.state.dash_positive_color);
|
||||
const dash_negative_color = computed(() => store.state.dash_negative_color);
|
||||
const dash_warning_color = computed(() => store.state.dash_warning_color);
|
||||
|
||||
// vue router
|
||||
const { params } = useRoute();
|
||||
@@ -64,14 +70,19 @@ export default {
|
||||
const statusColor = computed(() => {
|
||||
switch (status.value) {
|
||||
case "online":
|
||||
return "positive";
|
||||
return dash_positive_color.value;
|
||||
case "offline":
|
||||
return "warning";
|
||||
return dash_warning_color.value;
|
||||
default:
|
||||
return "negative";
|
||||
return dash_negative_color.value;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO refactor this so we're not calling the api twice
|
||||
const dashInfo = () => {
|
||||
store.dispatch("getDashInfo", false);
|
||||
};
|
||||
|
||||
async function getMeshURLs() {
|
||||
$q.loading.show();
|
||||
try {
|
||||
@@ -131,6 +142,7 @@ export default {
|
||||
control,
|
||||
status,
|
||||
statusColor,
|
||||
dash_negative_color,
|
||||
|
||||
// methods
|
||||
repairMeshCentral,
|
||||
|
Reference in New Issue
Block a user