Compare commits

...

51 Commits

Author SHA1 Message Date
wh1te909
c2591c9e7d Release 0.101.22 2023-05-30 22:11:30 +00:00
wh1te909
69403def2a bump version 2023-05-30 22:11:11 +00:00
wh1te909
3fdd8272f6 bump version 2023-05-29 20:36:02 +00:00
wh1te909
339227bedc add cert expiring soon indicator closes amidaware/tacticalrmm#722 2023-05-27 23:31:07 +00:00
wh1te909
17c7c95cc1 group processors for cleaner UI closes amidaware/tacticalrmm#583 2023-05-27 20:05:53 +00:00
wh1te909
a3ceb5e81b add serial number to summary tab closes amidaware/tacticalrmm#389 2023-05-27 19:48:19 +00:00
wh1te909
679d8cab77 add wake-on-lan amidaware/tacticalrmm#1180 2023-05-27 00:38:34 +00:00
wh1te909
c4c1474e09 add serial number to search amidaware/tacticalrmm#1355 2023-05-26 22:54:46 +00:00
wh1te909
82677b0b82 make cmd placeholder text customizable closes #5 2023-05-26 22:18:01 +00:00
wh1te909
b78af07f11 allow customizing dashboard colors closes amidaware/tacticalrmm#1514 2023-05-25 22:31:16 +00:00
wh1te909
24acef19c5 formatting 2023-05-25 22:30:05 +00:00
wh1te909
fee6edb39e update reqs 2023-05-25 22:21:34 +00:00
wh1te909
89e7db905d fix client sorting fixes amidaware/tacticalrmm#1439 2023-05-25 21:27:45 +00:00
wh1te909
827e81dcda don't retry null token fixes amidaware/tacticalrmm#1199 2023-05-17 07:03:13 +00:00
wh1te909
6ea3a053f2 update reqs 2023-05-17 07:00:38 +00:00
sadnub
88d297f7c6 Change the default color of anchor tags to make them look better in dark mode 2023-05-13 00:00:36 -04:00
sadnub
6c57d3e6b1 Make the alert icon menu height fixed 2023-05-12 23:59:18 -04:00
wh1te909
7fcbe6fbd8 Release 0.101.20 2023-05-09 21:09:45 +00:00
wh1te909
0113fbc761 hide openai until next release 2023-05-09 21:06:40 +00:00
wh1te909
95df8c1889 update reqs 2023-05-07 02:16:19 +00:00
sadnub
819a364207 Merge pull request #8 from sadnub/develop
open ai integration
2023-04-10 19:06:18 -04:00
sadnub
ed2b07fb0b change wording on default prompt 2023-04-10 19:04:01 -04:00
sadnub
64ed5e8740 open ai integration 2023-04-09 22:36:20 -04:00
wh1te909
a2f472ef9c Release 0.101.18 2023-04-09 03:28:23 +00:00
wh1te909
cdeaa3d9c4 bump version 2023-04-09 03:28:00 +00:00
wh1te909
8c6ac164ba bump dev ver 2023-04-07 22:46:24 +00:00
wh1te909
dc68b16ff2 format and align the icon 2023-04-07 21:52:53 +00:00
Dan
a4f15fd05a Merge pull request #6 from jpros/custom-fields-in-summary
Add custom fields to summary view
2023-04-07 13:51:50 -07:00
wh1te909
176675abd8 update reqs 2023-04-07 20:50:21 +00:00
João Paulo Ros
73dc278ac4 Hide custom fields with no value 2023-04-04 20:36:53 -07:00
wh1te909
d6b443296b update reqs 2023-04-04 06:34:11 +00:00
Dan
f3c718d29c Merge pull request #7 from jpros/custom-fields-search
Add custom fields to search in the dashboard
2023-04-03 22:46:20 -07:00
João Paulo Ros
5955af08c7 Hide custom fields that are not supposed to appear in the UI. 2023-03-31 15:22:10 -07:00
João Paulo Ros
dec1ccc98a Add Search from query parameter 2023-03-30 15:40:47 -07:00
João Paulo Ros
a78780b837 Add custom fields to summary view 2023-03-30 14:38:55 -07:00
João Paulo Ros
beff8eb10e Add custom fields to search in dashboard 2023-03-30 12:37:26 -07:00
wh1te909
8403ac0e93 Release 0.101.16 2023-03-22 17:00:29 +00:00
wh1te909
c2f21b70dd bump version 2023-03-22 16:59:35 +00:00
wh1te909
520145e0e3 bump dev ver 2023-03-21 18:49:28 +00:00
wh1te909
6a132187a2 fix phantom column fixes amidaware/tacticalrmm#1264 2023-03-20 14:08:58 +00:00
wh1te909
a63a9ccd76 update reqs 2023-03-20 01:28:54 +00:00
wh1te909
ff1eb791db feat: increase size of notes text box closes amidaware/tacticalrmm#1407 2023-03-10 05:31:50 +00:00
wh1te909
13bd88b979 feat: bulk run checks by client or site amidaware/tacticalrmm@7d017f9494 2023-03-10 00:22:12 +00:00
wh1te909
5b0c244920 update reqs 2023-03-10 00:20:51 +00:00
wh1te909
0318a17cac update reqs 2023-03-05 20:49:03 +00:00
wh1te909
b7a91563b0 Release 0.101.13 2023-01-18 20:05:20 +00:00
wh1te909
75296ed8ee bump version 2023-01-18 20:04:26 +00:00
wh1te909
09bee45b2f update reqs and dev version 2023-01-16 23:03:30 +00:00
wh1te909
3573c48872 formatting 2023-01-16 08:38:34 +00:00
wh1te909
784841c221 syntax typo 2023-01-16 08:37:15 +00:00
wh1te909
ed788a1861 update reqs 2023-01-16 08:35:07 +00:00
39 changed files with 3863 additions and 4480 deletions

View File

@@ -5,7 +5,7 @@
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"editorconfig.editorconfig", "editorconfig.editorconfig",
"vue.volar", "vue.volar",
"wayou.vscode-todo-highlight", "wayou.vscode-todo-highlight"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": [
"octref.vetur", "octref.vetur",

17
.vscode/settings.json vendored
View File

@@ -4,16 +4,9 @@
"editor.formatOnSave": true, "editor.formatOnSave": true,
"[vue][javascript][typescript][javascriptreact]": { "[vue][javascript][typescript][javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": [ "editor.codeActionsOnSave": ["source.fixAll.eslint"]
"source.fixAll.eslint"
],
}, },
"eslint.validate": [ "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"javascript",
"javascriptreact",
"typescript",
"vue"
],
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"files.watcherExclude": { "files.watcherExclude": {
"files.watcherExclude": { "files.watcherExclude": {
@@ -22,7 +15,7 @@
"**/node_modules/": true, "**/node_modules/": true,
"/node_modules/**": true, "/node_modules/**": true,
"**/env/": true, "**/env/": true,
"/env/**": true, "/env/**": true
} }
}, }
} }

View File

@@ -1,24 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<title><%= productName %></title>
<head> <meta charset="utf-8" />
<title> <meta name="robots" content="noindex" />
<%= productName %> <meta name="description" content="<%= productDescription %>" />
</title> <meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<meta charset="utf-8" /> <meta
<meta name="robots" content="noindex" /> name="viewport"
<meta name="description" content="<%= productDescription %>" /> 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<% } %>"
<meta name="format-detection" content="telephone=no" /> />
<meta name="msapplication-tap-highlight" content="no" /> <link rel="icon" type="image/ico" href="favicon.ico" />
<meta name="viewport" <script src="/env-config.js"></script>
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<% } %>" /> </head>
<link rel="icon" type="image/ico" href="favicon.ico" />
<script src="/env-config.js"></script>
</head>
<body>
<!-- quasar:entry-point -->
</body>
<body>
<!-- quasar:entry-point -->
</body>
</html> </html>

7009
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "web", "name": "web",
"version": "0.101.11", "version": "0.101.22",
"private": true, "private": true,
"productName": "Tactical RMM", "productName": "Tactical RMM",
"scripts": { "scripts": {
@@ -10,13 +10,13 @@
"format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore" "format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "1.15.8", "@quasar/extras": "1.16.4",
"apexcharts": "3.36.3", "apexcharts": "3.40.0",
"axios": "0.27.2", "axios": "1.4.0",
"dotenv": "16.0.3", "dotenv": "16.0.3",
"qrcode.vue": "3.3.3", "qrcode.vue": "3.4.0",
"quasar": "2.11.1", "quasar": "2.12.0",
"vue": "3.2.45", "vue": "3.2.47",
"vue3-ace-editor": "2.2.2", "vue3-ace-editor": "2.2.2",
"vue3-apexcharts": "1.4.1", "vue3-apexcharts": "1.4.1",
"vuedraggable": "4.1.0", "vuedraggable": "4.1.0",
@@ -24,17 +24,17 @@
"vuex": "4.1.0" "vuex": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@quasar/cli": "^1.3.2", "@quasar/cli": "^2.2.1",
"@intlify/vite-plugin-vue-i18n": "^6.0.3", "@intlify/unplugin-vue-i18n": "^0.10.0",
"@quasar/app-vite": "^1.1.3", "@quasar/app-vite": "^1.4.3",
"@types/node": "^18.11.17", "@types/node": "^20.2.4",
"@typescript-eslint/eslint-plugin": "^5.47.0", "@typescript-eslint/eslint-plugin": "^5.59.7",
"@typescript-eslint/parser": "^5.47.0", "@typescript-eslint/parser": "^5.59.7",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.14",
"eslint": "8.30.0", "eslint": "8.41.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.8.0",
"eslint-plugin-vue": "8.7.1", "eslint-plugin-vue": "8.7.1",
"prettier": "2.8.1", "prettier": "2.8.8",
"typescript": "4.9.4" "typescript": "5.0.4"
} }
} }

View File

@@ -4,18 +4,18 @@
module.exports = { module.exports = {
plugins: [ plugins: [
// https://github.com/postcss/autoprefixer // https://github.com/postcss/autoprefixer
require('autoprefixer')({ require("autoprefixer")({
overrideBrowserslist: [ overrideBrowserslist: [
'last 4 Chrome versions', "last 4 Chrome versions",
'last 4 Firefox versions', "last 4 Firefox versions",
'last 4 Edge versions', "last 4 Edge versions",
'last 4 Safari versions', "last 4 Safari versions",
'last 4 Android versions', "last 4 Android versions",
'last 4 ChromeAndroid versions', "last 4 ChromeAndroid versions",
'last 4 FirefoxAndroid versions', "last 4 FirefoxAndroid versions",
'last 4 iOS versions' "last 4 iOS versions",
] ],
}) }),
// https://github.com/elchininet/postcss-rtlcss // https://github.com/elchininet/postcss-rtlcss
// If you want to support RTL css, then // 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 // 2. optionally set quasar.config.js > framework > lang to an RTL language
// 3. uncomment the following line: // 3. uncomment the following line:
// require('postcss-rtlcss') // require('postcss-rtlcss')
] ],
} };

View File

@@ -12,6 +12,9 @@ export default {
body body
overflow-y: hidden overflow-y: hidden
a
color: #1976D2
.tbl-sticky .tbl-sticky
thead tr th thead tr th
position: sticky position: sticky

View File

@@ -232,3 +232,8 @@ export async function removeAgentNote(pk) {
const { data } = await axios.delete(`${baseUrl}/notes/${pk}/`); const { data } = await axios.delete(`${baseUrl}/notes/${pk}/`);
return data; return data;
} }
export async function wakeUpWOL(agent_id) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/wol/`);
return data;
}

View File

@@ -38,3 +38,8 @@ export async function runURLAction(payload) {
console.error(e); console.error(e);
} }
} }
export async function generateScript(payload) {
const { data } = await axios.post(`${baseUrl}/openai/generate/`, payload);
return data;
}

View File

@@ -211,7 +211,7 @@
v-if="props.row.maintenance_mode" v-if="props.row.maintenance_mode"
name="construction" name="construction"
size="1.2em" size="1.2em"
color="green" :color="dash_positive_color"
> >
<q-tooltip>Maintenance Mode Enabled</q-tooltip> <q-tooltip>Maintenance Mode Enabled</q-tooltip>
</q-icon> </q-icon>
@@ -219,7 +219,7 @@
v-else-if="props.row.checks.failing > 0" v-else-if="props.row.checks.failing > 0"
name="fas fa-check-double" name="fas fa-check-double"
size="1.2em" size="1.2em"
color="negative" :color="dash_negative_color"
> >
<q-tooltip>Checks failing</q-tooltip> <q-tooltip>Checks failing</q-tooltip>
</q-icon> </q-icon>
@@ -227,7 +227,7 @@
v-else-if="props.row.checks.warning > 0" v-else-if="props.row.checks.warning > 0"
name="fas fa-check-double" name="fas fa-check-double"
size="1.2em" size="1.2em"
color="warning" :color="dash_warning_color"
> >
<q-tooltip>Checks warning</q-tooltip> <q-tooltip>Checks warning</q-tooltip>
</q-icon> </q-icon>
@@ -235,7 +235,7 @@
v-else-if="props.row.checks.info > 0" v-else-if="props.row.checks.info > 0"
name="fas fa-check-double" name="fas fa-check-double"
size="1.2em" size="1.2em"
color="info" :color="dash_info_color"
> >
<q-tooltip>Checks info</q-tooltip> <q-tooltip>Checks info</q-tooltip>
</q-icon> </q-icon>
@@ -243,7 +243,7 @@
v-else v-else
name="fas fa-check-double" name="fas fa-check-double"
size="1.2em" size="1.2em"
color="positive" :color="dash_positive_color"
> >
<q-tooltip>Checks passing</q-tooltip> <q-tooltip>Checks passing</q-tooltip>
</q-icon> </q-icon>
@@ -279,7 +279,7 @@
@click="showPendingActionsModal(props.row)" @click="showPendingActionsModal(props.row)"
name="far fa-clock" name="far fa-clock"
size="1.4em" size="1.4em"
color="warning" :color="dash_warning_color"
class="cursor-pointer" class="cursor-pointer"
> >
<q-tooltip <q-tooltip
@@ -303,7 +303,7 @@
v-if="props.row.status === 'overdue'" v-if="props.row.status === 'overdue'"
name="fas fa-signal" name="fas fa-signal"
size="1.2em" size="1.2em"
color="negative" :color="dash_negative_color"
> >
<q-tooltip>Agent overdue</q-tooltip> <q-tooltip>Agent overdue</q-tooltip>
</q-icon> </q-icon>
@@ -311,11 +311,16 @@
v-else-if="props.row.status === 'offline'" v-else-if="props.row.status === 'offline'"
name="fas fa-signal" name="fas fa-signal"
size="1.2em" size="1.2em"
color="warning" :color="dash_warning_color"
> >
<q-tooltip>Agent offline</q-tooltip> <q-tooltip>Agent offline</q-tooltip>
</q-icon> </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-tooltip>Agent online</q-tooltip>
</q-icon> </q-icon>
</q-td> </q-td>
@@ -373,17 +378,13 @@ export default {
"local_ips", "local_ips",
"make_model", "make_model",
"physical_disks", "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 // quasar filter only does visible columns so this is a hack to add hidden columns we want to filter
for (const elem of hiddenFields) { // originally I was modifying cols directly but this led to phantom colum so doing it this way now
if (!cols.find((o) => o.name === elem)) { // https://github.com/amidaware/tacticalrmm/issues/1264
cols.push({ const allColumns = [...cols, ...hiddenFields.map((field) => ({ field }))];
name: elem,
field: elem,
});
}
}
const lowerTerms = terms ? terms.toLowerCase() : ""; const lowerTerms = terms ? terms.toLowerCase() : "";
let advancedFilter = false; let advancedFilter = false;
@@ -437,8 +438,12 @@ export default {
} }
// Normal text filter // Normal text filter
return cols.some((col) => { return allColumns.some((col) => {
const val = cellValue(col, row) + ""; let valObj = cellValue(col, row);
if (Array.isArray(valObj)) {
valObj = valObj.map((item) => (item.value ? item.value : item));
}
const val = valObj + "";
const haystack = const haystack =
val === "undefined" || val === "null" ? "" : val.toLowerCase(); val === "undefined" || val === "null" ? "" : val.toLowerCase();
return haystack.indexOf(search) !== -1; return haystack.indexOf(search) !== -1;
@@ -489,7 +494,9 @@ export default {
const data = { const data = {
[db_field]: !alert_action, [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.$axios.put(`/agents/${agent.agent_id}/`, data).then(() => {
this.$q.notify({ this.$q.notify({
color: alertColor, color: alertColor,
@@ -533,7 +540,13 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(["tableHeight"]), ...mapState([
"tableHeight",
"dash_info_color",
"dash_positive_color",
"dash_negative_color",
"dash_warning_color",
]),
agentDblClickAction() { agentDblClickAction() {
return this.$store.state.agentDblClickAction; return this.$store.state.agentDblClickAction;
}, },

View File

@@ -3,7 +3,7 @@
<q-badge v-if="alertsCount > 0" :color="badgeColor" floating transparent>{{ <q-badge v-if="alertsCount > 0" :color="badgeColor" floating transparent>{{
alertsCountText() alertsCountText()
}}</q-badge> }}</q-badge>
<q-menu style="max-height: 30vh"> <q-menu :style="{ 'max-height': `${$q.screen.height - 100}px` }">
<q-list separator> <q-list separator>
<q-item v-if="alertsCount === 0">No New Alerts</q-item> <q-item v-if="alertsCount === 0">No New Alerts</q-item>
<q-item v-for="alert in topAlerts" :key="alert.id"> <q-item v-for="alert in topAlerts" :key="alert.id">
@@ -59,6 +59,7 @@
</template> </template>
<script> <script>
import { mapState } from "vuex";
import mixins from "@/mixins/mixins"; import mixins from "@/mixins/mixins";
import AlertsOverview from "@/components/modals/alerts/AlertsOverview.vue"; import AlertsOverview from "@/components/modals/alerts/AlertsOverview.vue";
import { getTimeLapse } from "@/utils/format"; import { getTimeLapse } from "@/utils/format";
@@ -75,19 +76,21 @@ export default {
return { return {
alertsCount: 0, alertsCount: 0,
topAlerts: [], topAlerts: [],
errorColor: "red",
warningColor: "orange",
infoColor: "blue",
poll: null, poll: null,
}; };
}, },
computed: { computed: {
...mapState([
"dash_info_color",
"dash_warning_color",
"dash_negative_color",
]),
badgeColor() { badgeColor() {
const severities = this.topAlerts.map((alert) => alert.severity); const severities = this.topAlerts.map((alert) => alert.severity);
if (severities.includes("error")) return this.errorColor; if (severities.includes("error")) return this.dash_negative_color;
else if (severities.includes("warning")) return this.warningColor; else if (severities.includes("warning")) return this.dash_warning_color;
else return this.infoColor; else return this.dash_info_color;
}, },
}, },
methods: { methods: {
@@ -159,9 +162,9 @@ export default {
}); });
}, },
alertIconColor(severity) { alertIconColor(severity) {
if (severity === "error") return this.errorColor; if (severity === "error") return this.dash_negative_color;
else if (severity === "warning") return this.warningColor; else if (severity === "warning") return this.dash_warning_color;
else return this.infoColor; else return this.dash_info_color;
}, },
alertsCountText() { alertsCountText() {
if (this.alertsCount > 99) return "99+"; if (this.alertsCount > 99) return "99+";

View File

@@ -98,6 +98,10 @@
v-model="localRole.can_reboot_agents" v-model="localRole.can_reboot_agents"
label="Reboot Agents" label="Reboot Agents"
/> />
<q-checkbox
v-model="localRole.can_send_wol"
label="Wake-Up (WoL) Agents"
/>
<q-checkbox <q-checkbox
v-model="localRole.can_install_agents" v-model="localRole.can_install_agents"
label="Install Agents" label="Install Agents"
@@ -437,8 +441,8 @@ export default {
can_run_scripts: false, can_run_scripts: false,
can_run_bulk: false, can_run_bulk: false,
can_manage_winsvcs: false, can_manage_winsvcs: false,
can_recover_agents: false,
can_list_agent_history: false, can_list_agent_history: false,
can_send_wol: false,
// software perms // software perms
can_list_software: false, can_list_software: false,
can_manage_software: false, can_manage_software: false,

View File

@@ -146,6 +146,13 @@
<q-item-section>Run Checks</q-item-section> <q-item-section>Run Checks</q-item-section>
</q-item> </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 clickable>
<q-item-section side> <q-item-section side>
<q-icon size="xs" name="power_settings_new" /> <q-icon size="xs" name="power_settings_new" />
@@ -210,6 +217,7 @@ import {
removeAgent, removeAgent,
runRemoteBackground, runRemoteBackground,
runTakeControl, runTakeControl,
wakeUpWOL,
} from "@/api/agents"; } from "@/api/agents";
import { runAgentUpdateScan, runAgentUpdateInstall } from "@/api/winupdates"; import { runAgentUpdateScan, runAgentUpdateInstall } from "@/api/winupdates";
import { runAgentChecks } from "@/api/checks"; 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) { function showRebootLaterModal(agent) {
$q.dialog({ $q.dialog({
component: RebootLater, component: RebootLater,
@@ -498,6 +515,7 @@ export default {
showPolicyAdd, showPolicyAdd,
showAgentRecovery, showAgentRecovery,
pingAgent, pingAgent,
wakeUp,
}; };
}, },
}; };

View File

@@ -261,7 +261,7 @@
<q-td v-else-if="props.row.task_result.status === 'passing'"> <q-td v-else-if="props.row.task_result.status === 'passing'">
<q-icon <q-icon
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="positive" :color="dash_positive_color"
name="check_circle" name="check_circle"
> >
<q-tooltip>Passing</q-tooltip> <q-tooltip>Passing</q-tooltip>
@@ -271,7 +271,7 @@
<q-icon <q-icon
v-if="props.row.alert_severity === 'info'" v-if="props.row.alert_severity === 'info'"
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="info" :color="dash_info_color"
name="info" name="info"
> >
<q-tooltip>Informational</q-tooltip> <q-tooltip>Informational</q-tooltip>
@@ -279,7 +279,7 @@
<q-icon <q-icon
v-else-if="props.row.alert_severity === 'warning'" v-else-if="props.row.alert_severity === 'warning'"
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="warning" :color="dash_warning_color"
name="warning" name="warning"
> >
<q-tooltip>Warning</q-tooltip> <q-tooltip>Warning</q-tooltip>
@@ -287,7 +287,7 @@
<q-icon <q-icon
v-else v-else
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="negative" :color="dash_negative_color"
name="error" name="error"
> >
<q-tooltip>Error</q-tooltip> <q-tooltip>Error</q-tooltip>
@@ -418,6 +418,10 @@ export default {
const tabHeight = computed(() => store.state.tabHeight); const tabHeight = computed(() => store.state.tabHeight);
const agentPlatform = computed(() => store.state.agentPlatform); const agentPlatform = computed(() => store.state.agentPlatform);
const formatDate = computed(() => store.getters.formatDate); 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 // setup quasar
const $q = useQuasar(); const $q = useQuasar();
@@ -552,6 +556,10 @@ export default {
selectedAgent, selectedAgent,
tabHeight, tabHeight,
agentPlatform, agentPlatform,
dash_info_color,
dash_positive_color,
dash_warning_color,
dash_negative_color,
// non-reactive data // non-reactive data
columns, columns,

View File

@@ -301,7 +301,7 @@
<q-td v-else-if="props.row.check_result.status === 'passing'"> <q-td v-else-if="props.row.check_result.status === 'passing'">
<q-icon <q-icon
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="positive" :color="dash_positive_color"
name="check_circle" name="check_circle"
> >
<q-tooltip>Passing</q-tooltip> <q-tooltip>Passing</q-tooltip>
@@ -311,7 +311,7 @@
<q-icon <q-icon
v-if="getAlertSeverity(props.row) === 'info'" v-if="getAlertSeverity(props.row) === 'info'"
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="info" :color="dash_info_color"
name="info" name="info"
> >
<q-tooltip>Informational</q-tooltip> <q-tooltip>Informational</q-tooltip>
@@ -319,7 +319,7 @@
<q-icon <q-icon
v-else-if="getAlertSeverity(props.row) === 'warning'" v-else-if="getAlertSeverity(props.row) === 'warning'"
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="warning" :color="dash_warning_color"
name="warning" name="warning"
> >
<q-tooltip>Warning</q-tooltip> <q-tooltip>Warning</q-tooltip>
@@ -327,7 +327,7 @@
<q-icon <q-icon
v-else v-else
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="negative" :color="dash_negative_color"
name="error" name="error"
> >
<q-tooltip>Error</q-tooltip> <q-tooltip>Error</q-tooltip>
@@ -479,6 +479,10 @@ export default {
const tabHeight = computed(() => store.state.tabHeight); const tabHeight = computed(() => store.state.tabHeight);
const agentPlatform = computed(() => store.state.agentPlatform); const agentPlatform = computed(() => store.state.agentPlatform);
const formatDate = computed(() => store.getters.formatDate); 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 // setup quasar
const $q = useQuasar(); const $q = useQuasar();
@@ -653,6 +657,10 @@ export default {
tabHeight, tabHeight,
selectedAgent, selectedAgent,
agentPlatform, agentPlatform,
dash_info_color,
dash_positive_color,
dash_warning_color,
dash_negative_color,
// non-reactive data // non-reactive data
columns, columns,

View File

@@ -166,7 +166,7 @@ export default {
type: "textarea", type: "textarea",
isValid: (val) => !!val, isValid: (val) => !!val,
}, },
style: "width: 30vw; max-width: 50vw;", style: "width: 90vw; max-width: 90vw",
ok: { label: "Add" }, ok: { label: "Add" },
cancel: true, cancel: true,
}).onOk(async () => { }).onOk(async () => {
@@ -193,7 +193,7 @@ export default {
type: "textarea", type: "textarea",
isValid: (val) => !!val, isValid: (val) => !!val,
}, },
style: "width: 30vw; max-width: 50vw;", style: "width: 90vw; max-width: 90vw",
ok: { label: "Save" }, ok: { label: "Save" },
cancel: true, cancel: true,
}).onOk(async (data) => { }).onOk(async (data) => {

View File

@@ -18,6 +18,33 @@
icon="refresh" icon="refresh"
@click="refreshSummary" @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> <b>{{ summary.hostname }}</b>
<span v-if="summary.maintenance_mode"> <span v-if="summary.maintenance_mode">
&bull; <q-badge color="green"> Maintenance Mode </q-badge> &bull; <q-badge color="green"> Maintenance Mode </q-badge>
@@ -60,7 +87,7 @@
</q-item-section> </q-item-section>
<q-item-section>{{ summary.make_model }}</q-item-section> <q-item-section>{{ summary.make_model }}</q-item-section>
</q-item> </q-item>
<q-item v-for="(cpu, i) in summary.cpu_model" :key="cpu + i"> <q-item>
<q-item-section avatar> <q-item-section avatar>
<q-icon name="fas fa-microchip" /> <q-icon name="fas fa-microchip" />
</q-item-section> </q-item-section>
@@ -87,6 +114,13 @@
</q-item-section> </q-item-section>
<q-item-section>{{ summary.graphics }}</q-item-section> <q-item-section>{{ summary.graphics }}</q-item-section>
</q-item> </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>
<q-item-section avatar> <q-item-section avatar>
<q-icon name="fas fa-globe-americas" /> <q-icon name="fas fa-globe-americas" />
@@ -110,7 +144,7 @@
size="lg" size="lg"
square square
icon="done" icon="done"
color="green" :color="dash_positive_color"
text-color="white" text-color="white"
/> />
<small>{{ summary.checks.passing }} checks passing</small> <small>{{ summary.checks.passing }} checks passing</small>
@@ -120,7 +154,7 @@
size="lg" size="lg"
square square
icon="cancel" icon="cancel"
color="red" :color="dash_negative_color"
text-color="white" text-color="white"
/> />
<small>{{ summary.checks.failing }} checks failing</small> <small>{{ summary.checks.failing }} checks failing</small>
@@ -130,7 +164,7 @@
size="lg" size="lg"
square square
icon="warning" icon="warning"
color="warning" :color="dash_warning_color"
text-color="white" text-color="white"
/> />
<small>{{ summary.checks.warning }} checks warning</small> <small>{{ summary.checks.warning }} checks warning</small>
@@ -140,7 +174,7 @@
size="lg" size="lg"
square square
icon="info" icon="info"
color="info" :color="dash_info_color"
text-color="white" text-color="white"
/> />
<small>{{ summary.checks.info }} checks info</small> <small>{{ summary.checks.info }} checks info</small>
@@ -158,6 +192,20 @@
> >
</div> </div>
<div v-else>No checks</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>
<div class="col-1"></div> <div class="col-1"></div>
<!-- right --> <!-- right -->
@@ -193,6 +241,7 @@ import {
openAgentWindow, openAgentWindow,
} from "@/api/agents"; } from "@/api/agents";
import { notifySuccess } from "@/utils/notify"; import { notifySuccess } from "@/utils/notify";
import { fetchCustomFields } from "@/api/core";
// ui imports // ui imports
import AgentActionMenu from "@/components/agents/AgentActionMenu.vue"; import AgentActionMenu from "@/components/agents/AgentActionMenu.vue";
@@ -207,18 +256,34 @@ export default {
const store = useStore(); const store = useStore();
const selectedAgent = computed(() => store.state.selectedRow); const selectedAgent = computed(() => store.state.selectedRow);
const refreshSummaryTab = computed(() => store.state.refreshSummaryTab); 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 // summary tab logic
const summary = ref(null); const summary = ref(null);
const customFieldsDefinitions = ref(null);
const loading = ref(false); 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) { function diskBarColor(percent) {
if (percent < 80) { if (percent < 80) {
return "positive"; return dash_positive_color.value;
} else if (percent > 80 && percent < 95) { } else if (percent > 80 && percent < 95) {
return "warning"; return dash_warning_color.value;
} else { } else {
return "negative"; return dash_negative_color.value;
} }
} }
@@ -236,9 +301,37 @@ export default {
return ret; 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() { async function getSummary() {
loading.value = true; loading.value = true;
summary.value = await fetchAgent(selectedAgent.value); summary.value = await fetchAgent(selectedAgent.value);
customFieldsDefinitions.value = await fetchCustomFields();
store.commit("setRefreshSummaryTab", false); store.commit("setRefreshSummaryTab", false);
store.commit("setAgentPlatform", summary.value.plat); store.commit("setAgentPlatform", summary.value.plat);
loading.value = false; loading.value = false;
@@ -246,6 +339,7 @@ export default {
async function refreshSummary() { async function refreshSummary() {
loading.value = true; loading.value = true;
summary.value = await fetchAgent(selectedAgent.value);
try { try {
const result = await refreshAgentWMI(selectedAgent.value); const result = await refreshAgentWMI(selectedAgent.value);
await getSummary(); await getSummary();
@@ -277,9 +371,16 @@ export default {
return { return {
// reactive data // reactive data
summary, summary,
customFields,
loading, loading,
selectedAgent, selectedAgent,
disks, disks,
dash_info_color,
dash_positive_color,
dash_warning_color,
dash_negative_color,
serial_number,
cpu,
// methods // methods
getSummary, getSummary,

View File

@@ -128,7 +128,7 @@
<q-icon <q-icon
v-else-if="props.row.action === 'ignore'" v-else-if="props.row.action === 'ignore'"
name="fas fa-check" name="fas fa-check"
color="negative" :color="dash_negative_color"
> >
<q-tooltip>Ignore</q-tooltip> <q-tooltip>Ignore</q-tooltip>
</q-icon> </q-icon>
@@ -144,7 +144,7 @@
<q-icon <q-icon
v-if="props.row.installed" v-if="props.row.installed"
name="fas fa-check" name="fas fa-check"
color="positive" :color="dash_positive_color"
> >
<q-tooltip>Installed</q-tooltip> <q-tooltip>Installed</q-tooltip>
</q-icon> </q-icon>
@@ -158,11 +158,15 @@
<q-icon <q-icon
v-else-if="props.row.action == 'ignore'" v-else-if="props.row.action == 'ignore'"
name="fas fa-ban" name="fas fa-ban"
color="negative" :color="dash_negative_color"
> >
<q-tooltip>Ignored</q-tooltip> <q-tooltip>Ignored</q-tooltip>
</q-icon> </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-tooltip>Missing</q-tooltip>
</q-icon> </q-icon>
</q-td> </q-td>
@@ -251,6 +255,9 @@ export default {
const tabHeight = computed(() => store.state.tabHeight); const tabHeight = computed(() => store.state.tabHeight);
const agentPlatform = computed(() => store.state.agentPlatform); const agentPlatform = computed(() => store.state.agentPlatform);
const formatDate = computed(() => store.getters.formatDate); 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 // setup quasar
const $q = useQuasar(); const $q = useQuasar();
@@ -348,6 +355,9 @@ export default {
selectedAgent, selectedAgent,
tabHeight, tabHeight,
agentPlatform, agentPlatform,
dash_positive_color,
dash_warning_color,
dash_negative_color,
// non-reactive data // non-reactive data
columns, columns,

View File

@@ -8,16 +8,16 @@
v v
}}</q-badge> }}</q-badge>
<q-btn <q-btn
v-if="!!v" v-if="!!v"
size="sm" size="sm"
class="q-ml-xs" class="q-ml-xs"
flat flat
round round
icon="content_copy" icon="content_copy"
@click="copyValueToClip(v)" @click="copyValueToClip(v)"
> >
<q-tooltip>Copy to Clipboard</q-tooltip> <q-tooltip>Copy to Clipboard</q-tooltip>
</q-btn> </q-btn>
</div> </div>
</div> </div>
<q-separator v-if="info.length > 1" /> <q-separator v-if="info.length > 1" />
@@ -42,10 +42,9 @@ export default {
const tabHeight = computed(() => store.state.tabHeight); const tabHeight = computed(() => store.state.tabHeight);
function copyValueToClip(val) { function copyValueToClip(val) {
copyToClipboard(val) copyToClipboard(val).then(() => {
.then(() => { notifySuccess("Copied to clipboard");
notifySuccess("Copied to clipboard"); });
})
} }
return { return {

View File

@@ -217,6 +217,7 @@
</template> </template>
<script> <script>
import { mapState } from "vuex";
import mixins from "@/mixins/mixins"; import mixins from "@/mixins/mixins";
import PolicyStatus from "@/components/automation/modals/PolicyStatus.vue"; import PolicyStatus from "@/components/automation/modals/PolicyStatus.vue";
import DiskSpaceCheck from "@/components/checks/DiskSpaceCheck.vue"; import DiskSpaceCheck from "@/components/checks/DiskSpaceCheck.vue";
@@ -268,6 +269,9 @@ export default {
if (newValue !== oldValue) this.getChecks(); if (newValue !== oldValue) this.getChecks();
}, },
}, },
computed: {
...mapState(["dash_positive_color", "dash_warning_color"]),
},
methods: { methods: {
getChecks() { getChecks() {
this.$q.loading.show(); this.$q.loading.show();
@@ -295,7 +299,9 @@ export default {
data.check_alert = true; data.check_alert = true;
const act = !action ? "enabled" : "disabled"; const act = !action ? "enabled" : "disabled";
const color = !action ? "positive" : "warning"; const color = !action
? this.dash_positive_color
: this.dash_warning_color;
this.$axios this.$axios
.put(`/checks/${id}/`, data) .put(`/checks/${id}/`, data)
.then(() => { .then(() => {

View File

@@ -41,7 +41,7 @@
<q-td v-if="props.row.status === 'passing'"> <q-td v-if="props.row.status === 'passing'">
<q-icon <q-icon
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="positive" :color="dash_positive_color"
name="check_circle" name="check_circle"
> >
<q-tooltip>Passing</q-tooltip> <q-tooltip>Passing</q-tooltip>
@@ -51,7 +51,7 @@
<q-icon <q-icon
v-if="props.row.alert_severity === 'info'" v-if="props.row.alert_severity === 'info'"
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="info" :color="dash_info_color"
name="info" name="info"
> >
<q-tooltip>Informational</q-tooltip> <q-tooltip>Informational</q-tooltip>
@@ -59,7 +59,7 @@
<q-icon <q-icon
v-else-if="props.row.alert_severity === 'warning'" v-else-if="props.row.alert_severity === 'warning'"
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="warning" :color="dash_warning_color"
name="warning" name="warning"
> >
<q-tooltip>Warning</q-tooltip> <q-tooltip>Warning</q-tooltip>
@@ -67,7 +67,7 @@
<q-icon <q-icon
v-else v-else
style="font-size: 1.3rem" style="font-size: 1.3rem"
color="negative" :color="dash_negative_color"
name="error" name="error"
> >
<q-tooltip>Error</q-tooltip> <q-tooltip>Error</q-tooltip>
@@ -148,7 +148,7 @@
<script> <script>
import { computed } from "vue"; import { computed } from "vue";
import { useStore } from "vuex"; import { useStore, mapState } from "vuex";
import ScriptOutput from "@/components/checks/ScriptOutput.vue"; import ScriptOutput from "@/components/checks/ScriptOutput.vue";
import EventLogCheckOutput from "@/components/checks/EventLogCheckOutput.vue"; import EventLogCheckOutput from "@/components/checks/EventLogCheckOutput.vue";
@@ -220,6 +220,12 @@ export default {
}; };
}, },
computed: { computed: {
...mapState([
"dash_info_color",
"dash_positive_color",
"dash_negative_color",
"dash_warning_color",
]),
title() { title() {
return !!this.item.readable_desc return !!this.item.readable_desc
? this.item.readable_desc + " Status" ? this.item.readable_desc + " Status"

View File

@@ -128,7 +128,7 @@ import { useDialogPluginComponent } from "quasar";
import { useCheckModal } from "@/composables/checks"; import { useCheckModal } from "@/composables/checks";
import { useScriptDropdown } from "@/composables/scripts"; import { useScriptDropdown } from "@/composables/scripts";
import { validateRetcode } from "@/utils/validation"; import { validateRetcode } from "@/utils/validation";
import { envVarsLabel } from "@/constants/constants" import { envVarsLabel } from "@/constants/constants";
// ui imports // ui imports
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue"; import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
@@ -146,10 +146,15 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// setup script dropdown // setup script dropdown
const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars } = const {
useScriptDropdown(props.check ? props.check.script : undefined, { script,
onMount: true, scriptOptions,
}); defaultTimeout,
defaultArgs,
defaultEnvVars,
} = useScriptDropdown(props.check ? props.check.script : undefined, {
onMount: true,
});
// check logic // check logic
const { state, loading, submit, failOptions, severityOptions } = const { state, loading, submit, failOptions, severityOptions } =

View File

@@ -304,6 +304,9 @@ export default {
// setup vuex // setup vuex
const store = useStore(); const store = useStore();
const formatDate = computed(() => store.getters.formatDate); 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 // setup dropdowns
const { clientOptions, getClientOptions } = useClientDropdown(); const { clientOptions, getClientOptions } = useClientDropdown();
@@ -381,12 +384,18 @@ export default {
} }
function formatActionColor(action) { function formatActionColor(action) {
if (action === "add") return "success"; switch (action.toLowerCase()) {
else if (action === "agent_install") return "success"; case "modify":
else if (action === "modify") return "warning"; return dash_warning_color.value;
else if (action === "delete") return "negative"; case "add":
else if (action === "failed_login") return "negative"; case "agent_install":
else return "primary"; return dash_positive_color.value;
case "delete":
case "failed_login":
return dash_negative_color.value;
default:
return "primary";
}
} }
// watchers // watchers

View File

@@ -68,25 +68,25 @@
/> />
<q-radio <q-radio
v-model="logLevelFilter" v-model="logLevelFilter"
color="cyan" :color="dash_info_color"
val="info" val="info"
label="Info" label="Info"
/> />
<q-radio <q-radio
v-model="logLevelFilter" v-model="logLevelFilter"
color="red" :color="dash_negative_color"
val="critical" val="critical"
label="Critical" label="Critical"
/> />
<q-radio <q-radio
v-model="logLevelFilter" v-model="logLevelFilter"
color="red" :color="dash_negative_color"
val="error" val="error"
label="Error" label="Error"
/> />
<q-radio <q-radio
v-model="logLevelFilter" v-model="logLevelFilter"
color="yellow" :color="dash_warning_color"
val="warning" val="warning"
label="Warning" label="Warning"
/> />
@@ -109,7 +109,7 @@
<template v-slot:top-row> <template v-slot:top-row>
<q-tr v-if="Array.isArray(debugLog) && debugLog.length === 1000"> <q-tr v-if="Array.isArray(debugLog) && debugLog.length === 1000">
<q-td colspan="100%"> <q-td colspan="100%">
<q-icon name="warning" color="warning" /> <q-icon name="warning" :color="dash_warning_color" />
Results are limited to 1000 rows. Results are limited to 1000 rows.
</q-td> </q-td>
</q-tr> </q-tr>
@@ -203,6 +203,10 @@ export default {
const store = useStore(); const store = useStore();
const formatDate = computed(() => store.getters.formatDate); 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 // setup dropdowns
const { agentOptions, getAgentOptions } = useAgentDropdown(); const { agentOptions, getAgentOptions } = useAgentDropdown();
@@ -261,6 +265,10 @@ export default {
agentOptions, agentOptions,
loading, loading,
filter, filter,
dash_info_color,
dash_positive_color,
dash_warning_color,
dash_negative_color,
// non-reactive data // non-reactive data
columns, columns,

View File

@@ -89,7 +89,8 @@
<p class="text-italic"> <p class="text-italic">
Note: the auth token above will be valid for {{ info.expires }} hours. Note: the auth token above will be valid for {{ info.expires }} hours.
</p> </p>
<q-btn v-if="info.plat === 'windows'" <q-btn
v-if="info.plat === 'windows'"
type="a" type="a"
:href="info.data.url" :href="info.data.url"
color="primary" color="primary"

View File

@@ -94,7 +94,7 @@
class="q-pr-sm" class="q-pr-sm"
name="fas fa-signal" name="fas fa-signal"
size="1.2em" size="1.2em"
color="warning" :color="dash_warning_color"
/> />
Mark an agent as Mark an agent as
<span class="text-weight-bold">offline</span> if it has <span class="text-weight-bold">offline</span> if it has
@@ -120,7 +120,7 @@
class="q-pr-sm" class="q-pr-sm"
name="fas fa-signal" name="fas fa-signal"
size="1.2em" size="1.2em"
color="negative" :color="dash_negative_color"
/> />
Mark an agent as Mark an agent as
<span class="text-weight-bold">overdue</span> if it has <span class="text-weight-bold">overdue</span> if it has
@@ -373,6 +373,7 @@
</template> </template>
<script> <script>
import { mapState } from "vuex";
import { useDialogPluginComponent } from "quasar"; import { useDialogPluginComponent } from "quasar";
import mixins from "@/mixins/mixins"; import mixins from "@/mixins/mixins";
import PatchPolicyForm from "@/components/modals/agents/PatchPolicyForm.vue"; import PatchPolicyForm from "@/components/modals/agents/PatchPolicyForm.vue";
@@ -549,6 +550,9 @@ export default {
return result.trimEnd(","); return result.trimEnd(",");
}, },
}, },
computed: {
...mapState(["dash_warning_color", "dash_negative_color"]),
},
mounted() { mounted() {
// Get custom fields // Get custom fields
this.getCustomFields("agent").then((r) => { this.getCustomFields("agent").then((r) => {

View File

@@ -221,11 +221,18 @@ export default {
const { dialogRef, onDialogHide } = useDialogPluginComponent(); const { dialogRef, onDialogHide } = useDialogPluginComponent();
// setup dropdowns // setup dropdowns
const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars, syntax, link } = const {
useScriptDropdown(props.script, { script,
onMount: true, scriptOptions,
filterByPlatform: props.agent.plat, defaultTimeout,
}); defaultArgs,
defaultEnvVars,
syntax,
link,
} = useScriptDropdown(props.script, {
onMount: true,
filterByPlatform: props.agent.plat,
});
const { customFieldOptions } = useCustomFieldDropdown({ onMount: true }); const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
// main run script functionaity // main run script functionaity

View File

@@ -12,6 +12,7 @@
<q-tab name="urlactions" label="URL Actions" /> <q-tab name="urlactions" label="URL Actions" />
<q-tab name="retention" label="Retention" /> <q-tab name="retention" label="Retention" />
<q-tab name="apikeys" label="API Keys" /> <q-tab name="apikeys" label="API Keys" />
<!-- <q-tab name="openai" label="Open AI" /> -->
</q-tabs> </q-tabs>
</template> </template>
<template v-slot:after> <template v-slot:after>
@@ -508,6 +509,49 @@
<q-tab-panel name="apikeys"> <q-tab-panel name="apikeys">
<APIKeysTable /> <APIKeysTable />
</q-tab-panel> </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-tab-panels>
</q-scroll-area> </q-scroll-area>
<q-card-section class="row items-center"> <q-card-section class="row items-center">

View File

@@ -82,6 +82,98 @@
class="col-4" class="col-4"
/> />
</q-card-section> </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"> <q-card-section class="row">
<div class="col-2">Client Sort:</div> <div class="col-2">Client Sort:</div>
<div class="col-2"></div> <div class="col-2"></div>
@@ -156,9 +248,14 @@ export default {
tab: "ui", tab: "ui",
splitterModel: 20, splitterModel: 20,
loading_bar_color: "", loading_bar_color: "",
dash_info_color: "",
dash_positive_color: "",
dash_negative_color: "",
dash_warning_color: "",
urlActions: [], urlActions: [],
clear_search_when_switching: true, clear_search_when_switching: true,
date_format: "", date_format: "",
quasar_color_url: "https://quasar.dev/style/color-palette",
clientTreeSortOptions: [ clientTreeSortOptions: [
{ {
label: "Sort alphabetically, moving failing clients to the top", label: "Sort alphabetically, moving failing clients to the top",
@@ -235,6 +332,10 @@ export default {
this.defaultAgentTblTab = r.data.default_agent_tbl_tab; this.defaultAgentTblTab = r.data.default_agent_tbl_tab;
this.clientTreeSort = r.data.client_tree_sort; this.clientTreeSort = r.data.client_tree_sort;
this.loading_bar_color = r.data.loading_bar_color; 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.clear_search_when_switching = r.data.clear_search_when_switching;
this.date_format = r.data.date_format; this.date_format = r.data.date_format;
}); });
@@ -253,6 +354,10 @@ export default {
default_agent_tbl_tab: this.defaultAgentTblTab, default_agent_tbl_tab: this.defaultAgentTblTab,
client_tree_sort: this.clientTreeSort, client_tree_sort: this.clientTreeSort,
loading_bar_color: this.loading_bar_color, 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, clear_search_when_switching: this.clear_search_when_switching,
date_format: this.date_format, date_format: this.date_format,
}; };

View File

@@ -11,7 +11,17 @@
:style="maximized ? '' : 'width: 90vw; max-width: 90vw'" :style="maximized ? '' : 'width: 90vw; max-width: 90vw'"
> >
<q-bar> <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-space />
<q-btn <q-btn
dense dense
@@ -57,116 +67,133 @@
><br />Add one to get rid of this warning. Ignore if windows. ><br />Add one to get rid of this warning. Ignore if windows.
</q-banner> </q-banner>
<div class="row q-pa-sm"> <div class="row q-pa-sm">
<div class="col-4 q-gutter-sm q-pr-sm"> <q-scroll-area
<q-input :thumb-style="{
filled right: '4px',
dense borderRadius: '5px',
:readonly="readonly" width: '5px',
v-model="formScript.name" opacity: 0.75,
label="Name" }"
:rules="[(val) => !!val || '*Required']" :bar-style="{
hide-bottom-space right: '2px',
/> borderRadius: '9px',
<q-input width: '9px',
filled opacity: 0.2,
dense }"
:readonly="readonly" class="col-4 q-mb-none q-pb-none"
v-model="formScript.description" :style="{ height: `${maximized ? '82vh' : '64vh'}` }"
label="Description" >
/> <div class="q-gutter-sm q-pr-sm">
<q-select <q-input
:readonly="readonly" filled
options-dense dense
filled :readonly="readonly"
dense v-model="formScript.name"
v-model="formScript.shell" label="Name"
:options="shellOptions" :rules="[(val) => !!val || '*Required']"
emit-value hide-bottom-space
map-options />
label="Shell Type" <q-input
/> filled
<tactical-dropdown dense
v-model="formScript.supported_platforms" :readonly="readonly"
:options="agentPlatformOptions" v-model="formScript.description"
label="Supported Platforms (All supported if blank)" label="Description"
clearable />
mapOptions <q-select
filled :readonly="readonly"
multiple options-dense
:readonly="readonly" filled
/> dense
<tactical-dropdown v-model="formScript.shell"
filled :options="shellOptions"
v-model="formScript.category" emit-value
:options="categories" map-options
use-input label="Shell Type"
clearable />
new-value-mode="add-unique" <tactical-dropdown
filterable v-model="formScript.supported_platforms"
label="Category" :options="agentPlatformOptions"
:readonly="readonly" label="Supported Platforms (All supported if blank)"
hide-bottom-space clearable
/> mapOptions
<tactical-dropdown filled
v-model="formScript.args" multiple
label="Script Arguments (press Enter after typing each argument)" :readonly="readonly"
filled />
use-input <tactical-dropdown
multiple filled
hide-dropdown-icon v-model="formScript.category"
input-debounce="0" :options="categories"
new-value-mode="add" use-input
:readonly="readonly" clearable
/> new-value-mode="add-unique"
<tactical-dropdown filterable
v-model="formScript.env_vars" label="Category"
:label="envVarsLabel" :readonly="readonly"
filled hide-bottom-space
use-input />
multiple <tactical-dropdown
hide-dropdown-icon v-model="formScript.args"
input-debounce="0" label="Script Arguments (press Enter after typing each argument)"
new-value-mode="add" filled
:readonly="readonly" use-input
/> multiple
<q-input hide-dropdown-icon
type="number" input-debounce="0"
filled new-value-mode="add"
dense :readonly="readonly"
:readonly="readonly" />
v-model.number="formScript.default_timeout" <tactical-dropdown
label="Timeout (seconds)" v-model="formScript.env_vars"
:rules="[(val) => val >= 5 || 'Minimum is 5']" :label="envVarsLabel"
hide-bottom-space filled
/> use-input
<q-checkbox multiple
v-model="formScript.run_as_user" hide-dropdown-icon
label="Run As User (Windows only)" input-debounce="0"
> new-value-mode="add"
<q-tooltip :readonly="readonly"
>Setting this value on the script model will always override any />
'Run As User' checkboxes in the UI and force this script to <q-input
always be run in the context of the logged in user. If no user type="number"
is logged in, the script will not run and an error will be filled
returned. dense
</q-tooltip> :readonly="readonly"
</q-checkbox> v-model.number="formScript.default_timeout"
<q-input label="Timeout (seconds)"
label="Syntax" :rules="[(val) => val >= 5 || 'Minimum is 5']"
type="textarea" hide-bottom-space
style="height: 150px; overflow-y: auto; resize: none" />
v-model="formScript.syntax" <q-checkbox
dense v-model="formScript.run_as_user"
filled label="Run As User (Windows only)"
:readonly="readonly" >
/> <q-tooltip
</div> >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-ace-editor
v-model:value="formScript.script_body" v-model:value="formScript.script_body"
class="col-8" class="col-8"
:lang="lang" :lang="lang"
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'" :theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
:style="{ height: `${maximized ? '87vh' : '64vh'}` }" :style="{ height: `${maximized ? '82vh' : '64vh'}` }"
wrap wrap
:printMargin="false" :printMargin="false"
:options="{ fontSize: '14px' }" :options="{ fontSize: '14px' }"
@@ -220,9 +247,11 @@
<script> <script>
// composable imports // composable imports
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { useQuasar, useDialogPluginComponent } from "quasar"; import { useQuasar, useDialogPluginComponent } from "quasar";
import { saveScript, editScript, downloadScript } from "@/api/scripts"; import { saveScript, editScript, downloadScript } from "@/api/scripts";
import { useAgentDropdown, agentPlatformOptions } from "@/composables/agents"; import { useAgentDropdown, agentPlatformOptions } from "@/composables/agents";
import { generateScript } from "@/api/core";
import { notifySuccess } from "@/utils/notify"; import { notifySuccess } from "@/utils/notify";
// ui imports // ui imports
@@ -266,6 +295,10 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
const $q = useQuasar(); const $q = useQuasar();
// setup store
const store = useStore();
const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled);
// setup agent dropdown // setup agent dropdown
const { agent, agentOptions, getAgentOptions } = useAgentDropdown(); const { agent, agentOptions, getAgentOptions } = useAgentDropdown();
@@ -355,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 // component life cycle hooks
onMounted(async () => { onMounted(async () => {
agentLoading.value = true; agentLoading.value = true;
@@ -380,10 +430,12 @@ export default {
//computed //computed
title, title,
openAIEnabled,
//methods //methods
submitForm, submitForm,
openTestScriptModal, openTestScriptModal,
generateScriptOpenAI,
// quasar dialog plugin // quasar dialog plugin
dialogRef, dialogRef,

View File

@@ -11,7 +11,17 @@
:style="maximized ? '' : 'width: 70vw; max-width: 90vw'" :style="maximized ? '' : 'width: 70vw; max-width: 90vw'"
> >
<q-bar> <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-space />
<q-btn <q-btn
dense dense
@@ -97,6 +107,9 @@
<script> <script>
// composable imports // composable imports
import { ref, computed } from "vue"; import { ref, computed } from "vue";
import { useStore } from "vuex";
import { useQuasar } from "quasar";
import { generateScript } from "@/api/core";
import { useDialogPluginComponent } from "quasar"; import { useDialogPluginComponent } from "quasar";
import { saveScriptSnippet, editScriptSnippet } from "@/api/scripts"; import { saveScriptSnippet, editScriptSnippet } from "@/api/scripts";
import { notifySuccess } from "@/utils/notify"; import { notifySuccess } from "@/utils/notify";
@@ -128,6 +141,13 @@ export default {
// setup quasar plugins // setup quasar plugins
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// setup quasar
const $q = useQuasar();
// setup store
const store = useStore();
const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled);
// snippet form logic // snippet form logic
const snippet = props.snippet const snippet = props.snippet
? ref(Object.assign({}, props.snippet)) ? ref(Object.assign({}, props.snippet))
@@ -167,6 +187,23 @@ export default {
loading.value = false; 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 { return {
// reactive data // reactive data
formSnippet: snippet.value, formSnippet: snippet.value,
@@ -179,9 +216,11 @@ export default {
//computed //computed
title, title,
openAIEnabled,
//methods //methods
submitForm, submitForm,
generateScriptOpenAI,
// quasar dialog plugin // quasar dialog plugin
dialogRef, dialogRef,

View File

@@ -836,10 +836,15 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// setup dropdowns // setup dropdowns
const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars } = const {
useScriptDropdown(undefined, { script,
onMount: true, scriptOptions,
}); defaultTimeout,
defaultArgs,
defaultEnvVars,
} = useScriptDropdown(undefined, {
onMount: true,
});
// set defaultTimeout to 30 // set defaultTimeout to 30
defaultTimeout.value = 30; defaultTimeout.value = 30;

View File

@@ -1,4 +1,5 @@
import { ref } from "vue"; import { computed, ref } from "vue";
import { useStore } from "vuex";
import { fetchAgents } from "@/api/agents"; import { fetchAgents } from "@/api/agents";
import { formatAgentOptions } from "@/utils/format"; import { formatAgentOptions } from "@/utils/format";
@@ -28,10 +29,12 @@ export function useAgentDropdown() {
} }
export function cmdPlaceholder(shell) { export function cmdPlaceholder(shell) {
if (shell === "cmd") return "rmdir /S /Q C:\\Windows\\System32"; const store = useStore();
else if (shell === "powershell") const placeholders = computed(() => store.state.run_cmd_placeholder_text);
return "Remove-Item -Recurse -Force C:\\Windows\\System32";
else return "rm -rf --no-preserve-root /"; if (shell === "cmd") return placeholders.value.cmd;
else if (shell === "powershell") return placeholders.value.powershell;
else return placeholders.value.shell;
} }
export const agentPlatformOptions = [ export const agentPlatformOptions = [

View File

@@ -30,7 +30,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
); );
defaultTimeout.value = tmpScript.timeout; defaultTimeout.value = tmpScript.timeout;
defaultArgs.value = tmpScript.args; defaultArgs.value = tmpScript.args;
defaultEnvVars.value = tmpScript.env_vars, defaultEnvVars.value = tmpScript.env_vars;
syntax.value = tmpScript.syntax; syntax.value = tmpScript.syntax;
link.value = link.value =
tmpScript.script_type === "builtin" tmpScript.script_type === "builtin"

View File

@@ -6,5 +6,5 @@ export const GOARCH_ARM32 = "arm";
export const runAsUserToolTip = 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."; "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 = export const envVarsLabel =
"Environment vars (press Enter after typing each key=value pair)" "Environment vars (press Enter after typing each key=value pair)";

View File

@@ -56,15 +56,27 @@
Tactical RMM<span class="text-overline q-ml-sm" Tactical RMM<span class="text-overline q-ml-sm"
>v{{ currentTRMMVersion }}</span >v{{ currentTRMMVersion }}</span
> >
<span class="text-overline q-ml-md" v-if="updateAvailable()" <!-- update check -->
><q-badge color="warning" <q-chip
><a :href="latestReleaseURL" target="_blank" v-if="updateAvailable"
>v{{ latestTRMMVersion }} available</a class="text-overline q-ml-sm"
></q-badge :color="dash_warning_color"
></span 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> </q-toolbar-title>
<!-- temp dark mode toggle --> <!-- temp dark mode toggle -->
<q-toggle <q-toggle
v-model="darkMode" v-model="darkMode"
@@ -94,7 +106,11 @@
</q-item> </q-item>
<q-item> <q-item>
<q-item-section avatar> <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>
<q-item-section no-wrap> <q-item-section no-wrap>
@@ -113,7 +129,11 @@
</q-item> </q-item>
<q-item> <q-item>
<q-item-section avatar> <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>
<q-item-section no-wrap> <q-item-section no-wrap>
@@ -218,6 +238,8 @@ export default {
const user = computed(() => store.state.username); const user = computed(() => store.state.username);
const hosted = computed(() => store.state.hosted); const hosted = computed(() => store.state.hosted);
const tokenExpired = computed(() => store.state.tokenExpired); 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(() => { const latestReleaseURL = computed(() => {
return latestTRMMVersion.value return latestTRMMVersion.value
@@ -255,6 +277,7 @@ export default {
const serverOfflineCount = ref(0); const serverOfflineCount = ref(0);
const workstationCount = ref(0); const workstationCount = ref(0);
const workstationOfflineCount = ref(0); const workstationOfflineCount = ref(0);
const daysUntilCertExpires = ref(100);
const ws = ref(null); const ws = ref(null);
@@ -262,6 +285,13 @@ export default {
// moved computed token inside the function since it is not refreshing // moved computed token inside the function since it is not refreshing
// when ws is closed causing ws to connect with expired token // when ws is closed causing ws to connect with expired token
const token = computed(() => store.state.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"); console.log("Starting websocket");
let url = getWSUrl("dashinfo", token.value); let url = getWSUrl("dashinfo", token.value);
ws.value = new WebSocket(url); ws.value = new WebSocket(url);
@@ -274,6 +304,7 @@ export default {
serverOfflineCount.value = data.total_server_offline_count; serverOfflineCount.value = data.total_server_offline_count;
workstationCount.value = data.total_workstation_count; workstationCount.value = data.total_workstation_count;
workstationOfflineCount.value = data.total_workstation_offline_count; workstationOfflineCount.value = data.total_workstation_offline_count;
daysUntilCertExpires.value = data.days_until_cert_expires;
}; };
ws.value.onclose = (e) => { ws.value.onclose = (e) => {
try { try {
@@ -297,13 +328,18 @@ export default {
poll.value = setInterval(() => { poll.value = setInterval(() => {
store.dispatch("checkVer"); store.dispatch("checkVer");
store.dispatch("getDashInfo", false); store.dispatch("getDashInfo", false);
}, 60 * 5 * 1000); }, 60 * 4 * 1000);
} }
function updateAvailable() { const updateAvailable = computed(() => {
if (latestTRMMVersion.value === "error" || hosted.value) return false; if (
latestTRMMVersion.value === "error" ||
hosted.value ||
currentTRMMVersion.value?.includes("-dev")
)
return false;
return currentTRMMVersion.value !== latestTRMMVersion.value; return currentTRMMVersion.value !== latestTRMMVersion.value;
} });
onMounted(() => { onMounted(() => {
setupWS(); setupWS();
@@ -324,6 +360,7 @@ export default {
serverOfflineCount, serverOfflineCount,
workstationCount, workstationCount,
workstationOfflineCount, workstationOfflineCount,
daysUntilCertExpires,
latestReleaseURL, latestReleaseURL,
currentTRMMVersion, currentTRMMVersion,
latestTRMMVersion, latestTRMMVersion,
@@ -332,6 +369,8 @@ export default {
darkMode, darkMode,
hosted, hosted,
tokenExpired, tokenExpired,
dash_warning_color,
dash_negative_color,
// methods // methods
showUserPreferences, showUserPreferences,

View File

@@ -33,6 +33,16 @@ export default function () {
currentTRMMVersion: null, currentTRMMVersion: null,
latestTRMMVersion: null, latestTRMMVersion: null,
dateFormat: "MMM-DD-YYYY - HH:mm", 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: { getters: {
@@ -136,6 +146,24 @@ export default function () {
setDateFormat(state, val) { setDateFormat(state, val) {
state.dateFormat = 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: { actions: {
setClientTreeSplitter(context, val) { setClientTreeSplitter(context, val) {
@@ -160,9 +188,9 @@ export default function () {
} }
if (clearTreeSelected) commit("destroySubTable"); if (clearTreeSelected) commit("destroySubTable");
dispatch("getDashInfo", false);
dispatch("loadAgents"); dispatch("loadAgents");
dispatch("loadTree"); dispatch("loadTree");
dispatch("getDashInfo", false);
}, },
async loadAgents({ state, commit }) { async loadAgents({ state, commit }) {
commit("AGENT_TABLE_LOADING", true); commit("AGENT_TABLE_LOADING", true);
@@ -194,107 +222,111 @@ export default function () {
commit("AGENT_TABLE_LOADING", false); commit("AGENT_TABLE_LOADING", false);
}, },
async getDashInfo(context, edited = true) { async getDashInfo({ commit }, edited = true) {
const { data } = await axios.get("/core/dashinfo/"); 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) { if (edited) {
LoadingBar.setDefaults({ color: data.loading_bar_color }); LoadingBar.setDefaults({ color: data.loading_bar_color });
context.commit( commit(
"setClearSearchWhenSwitching", "setClearSearchWhenSwitching",
data.clear_search_when_switching data.clear_search_when_switching
); );
context.commit( commit("SET_DEFAULT_AGENT_TBL_TAB", data.default_agent_tbl_tab);
"SET_DEFAULT_AGENT_TBL_TAB", commit("SET_CLIENT_TREE_SORT", data.client_tree_sort);
data.default_agent_tbl_tab commit("SET_CLIENT_SPLITTER", data.client_tree_splitter);
);
context.commit("SET_CLIENT_TREE_SORT", data.client_tree_sort);
context.commit("SET_CLIENT_SPLITTER", data.client_tree_splitter);
} }
Dark.set(data.dark_mode); Dark.set(data.dark_mode);
context.commit("setCurrentTRMMVersion", data.trmm_version); commit("setCurrentTRMMVersion", data.trmm_version);
context.commit("setLatestTRMMVersion", data.latest_trmm_ver); commit("setLatestTRMMVersion", data.latest_trmm_ver);
context.commit("SET_AGENT_DBLCLICK_ACTION", data.dbl_click_action); commit("SET_AGENT_DBLCLICK_ACTION", data.dbl_click_action);
context.commit("SET_URL_ACTION", data.url_action); commit("SET_URL_ACTION", data.url_action);
context.commit("setShowCommunityScripts", data.show_community_scripts); commit("setShowCommunityScripts", data.show_community_scripts);
context.commit("SET_HOSTED", data.hosted); commit("SET_HOSTED", data.hosted);
context.commit("SET_TOKEN_EXPIRED", data.token_is_expired); 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 !== "") if (data?.date_format !== "") commit("setDateFormat", data.date_format);
context.commit("setDateFormat", data.date_format); else commit("setDateFormat", data.default_date_format);
else context.commit("setDateFormat", data.default_date_format);
}, },
loadTree({ commit, state }) { loadTree({ commit, state }) {
axios setTimeout(() => {
.get("/clients/") axios
.then((r) => { .get("/clients/")
if (r.data.length === 0) { .then((r) => {
this.$router.push({ name: "InitialSetup" }); if (r.data.length === 0) {
} this.$router.push({ name: "InitialSetup" });
}
let output = []; let output = [];
for (let client of r.data) { for (let client of r.data) {
let childSites = []; let childSites = [];
for (let site of client.sites) { for (let site of client.sites) {
let siteNode = { let siteNode = {
label: site.name, label: site.name,
id: site.id, id: site.id,
raw: `Site|${site.id}`, raw: `Site|${site.id}`,
header: "generic", header: "generic",
icon: "apartment", icon: "apartment",
selectable: true, selectable: true,
site: site, site: site,
}; };
if (site.maintenance_mode) { if (site.maintenance_mode) {
siteNode["color"] = "green"; siteNode["color"] = "green";
} else if (site.failing_checks.error) { } else if (site.failing_checks.error) {
siteNode["color"] = "negative"; siteNode["color"] = "negative";
} else if (site.failing_checks.warning) { } else if (site.failing_checks.warning) {
siteNode["color"] = "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 = { const sorted = output.sort((a, b) =>
label: client.name, a.label.localeCompare(b.label)
id: client.id, );
raw: `Client|${client.id}`, if (state.clientTreeSort === "alphafail") {
header: "root", // move failing clients to the top
icon: "business", const failing = sorted.filter(
children: childSites, (i) => i.color === "negative" || i.color === "warning"
client: client, );
}; const ok = sorted.filter(
(i) => i.color !== "negative" && i.color !== "warning"
if (client.maintenance_mode) clientNode["color"] = "green"; );
else if (client.failing_checks.error) { const sortedByFailing = [...failing, ...ok];
clientNode["color"] = "negative"; commit("loadTree", sortedByFailing);
} else if (client.failing_checks.warning) { } else {
clientNode["color"] = "warning"; commit("loadTree", sorted);
} }
})
output.push(clientNode); .catch(() => {
} state.treeReady = true;
});
const sorted = output.sort((a, b) => }, 150);
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;
});
}, },
checkVer(context) { checkVer(context) {
axios.get("/core/version/").then((r) => { axios.get("/core/version/").then((r) => {

View File

@@ -173,6 +173,18 @@
</q-menu> </q-menu>
</q-item> </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-separator></q-separator>
<q-item clickable v-close-popup> <q-item clickable v-close-popup>
@@ -440,7 +452,7 @@ export default {
showInstallAgentModal: false, showInstallAgentModal: false,
sitePk: null, sitePk: null,
innerModel: (this.$q.screen.height - 82) / 2, innerModel: (this.$q.screen.height - 82) / 2,
search: "", search: this.$route.query.search ? this.$route.query.search : "",
filterTextLength: 0, filterTextLength: 0,
filterAvailability: "all", filterAvailability: "all",
filterPatchesPending: false, filterPatchesPending: false,
@@ -690,6 +702,17 @@ export default {
}) })
.onOk(() => this.$store.dispatch("refreshDashboard")); .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) { showToggleMaintenance(node) {
let data = { let data = {
id: node.id, id: node.id,

View File

@@ -15,7 +15,7 @@
@click="restartMeshService" @click="restartMeshService"
/> />
<q-btn <q-btn
color="negative" :color="dash_negative_color"
size="sm" size="sm"
label="Recover Connection" label="Recover Connection"
icon="fas fa-first-aid" icon="fas fa-first-aid"
@@ -35,6 +35,7 @@
<script> <script>
// composition imports // composition imports
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useMeta, useQuasar } from "quasar"; import { useMeta, useQuasar } from "quasar";
import { fetchAgentMeshCentralURLs, sendAgentRecoverMesh } from "@/api/agents"; import { fetchAgentMeshCentralURLs, sendAgentRecoverMesh } from "@/api/agents";
@@ -47,12 +48,17 @@ export default {
setup() { setup() {
// vue lifecycle hooks // vue lifecycle hooks
onMounted(() => { onMounted(() => {
dashInfo();
getDashInfo(); getDashInfo();
getMeshURLs(); getMeshURLs();
}); });
// quasar setup // quasar setup
const $q = useQuasar(); 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 // vue router
const { params } = useRoute(); const { params } = useRoute();
@@ -64,14 +70,19 @@ export default {
const statusColor = computed(() => { const statusColor = computed(() => {
switch (status.value) { switch (status.value) {
case "online": case "online":
return "positive"; return dash_positive_color.value;
case "offline": case "offline":
return "warning"; return dash_warning_color.value;
default: 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() { async function getMeshURLs() {
$q.loading.show(); $q.loading.show();
try { try {
@@ -131,6 +142,7 @@ export default {
control, control,
status, status,
statusColor, statusColor,
dash_negative_color,
// methods // methods
repairMeshCentral, repairMeshCentral,