Compare commits

...

81 Commits

Author SHA1 Message Date
wh1te909
20d534eab0 Release 0.101.31 2023-10-01 17:36:52 +00:00
wh1te909
0d87f5afee bump version 2023-10-01 17:36:39 +00:00
wh1te909
1b83c3c5d6 fix tooltip 2023-10-01 17:36:11 +00:00
wh1te909
1b2286c4f8 Release 0.101.30 2023-09-30 21:59:09 +00:00
wh1te909
34233fde2f bump version 2023-09-30 21:58:51 +00:00
wh1te909
e95dd5f6e7 use node 18 2023-09-30 21:57:37 +00:00
wh1te909
901e6986a0 update reqs 2023-09-30 21:56:24 +00:00
wh1te909
aa78929743 update reqs 2023-09-11 02:50:40 +00:00
wh1te909
8207f30234 Release 0.101.29 2023-08-30 04:10:11 +00:00
wh1te909
1879977b83 bump version [skip ci] 2023-08-30 04:09:53 +00:00
wh1te909
b4de579a74 fix lint 2023-08-29 20:42:23 +00:00
wh1te909
23f15ff9e5 feat: add reset all check status amidaware/tacticalrmm#1615 2023-08-29 20:21:22 +00:00
wh1te909
498e038bbb update reqs 2023-08-29 20:19:35 +00:00
wh1te909
bb1f1c19cf wording 2023-08-21 21:30:21 +00:00
Dan
74e3aa4e46 Merge pull request #12 from silversword411/develop
Fixing main view removing blank space along bottom
2023-08-21 14:28:28 -07:00
silversword411
07a8e3ebcb Clarifying TRMM agent 2023-08-21 09:28:23 -04:00
silversword411
89966dd006 Fixing main view removing blank space along bottom 2023-08-18 02:39:43 -04:00
wh1te909
68036f6837 Release 0.101.28 2023-08-14 06:39:49 +00:00
wh1te909
45ac82b1dd bump version 2023-08-14 06:39:31 +00:00
wh1te909
d94e5c7965 update reqs 2023-08-11 07:07:00 +00:00
wh1te909
e0c1b3199a update reqs 2023-08-06 23:01:46 +00:00
sadnub
fdbbdf7394 make it so the script manager doesn't close if escape is pressed on script editor 2023-08-05 09:54:14 -04:00
wh1te909
03fae45ac5 Release 0.101.25 2023-07-04 18:49:46 +00:00
wh1te909
346670e8ea bump version 2023-07-04 18:49:28 +00:00
wh1te909
e030efaecf bump dev ver 2023-07-03 23:32:40 +00:00
wh1te909
b8a4f9fe74 update reqs 2023-07-03 23:32:09 +00:00
wh1te909
f963b51d70 update quasar 2023-07-01 16:48:03 +00:00
wh1te909
feacb19cf9 bump dev version 2023-06-30 20:30:52 +00:00
wh1te909
7ce2c1e969 node 18 2023-06-30 20:26:30 +00:00
wh1te909
d1defcef4a update reqs 2023-06-30 20:26:21 +00:00
Dan
e674b4fa5d Merge pull request #9 from silversword411/develop
Adding customizable columns to script manager table
2023-06-07 11:59:29 -07:00
sadnub
b08a5a6c2d fix light mode colors 2023-06-06 16:17:09 -04:00
sadnub
9fa1d7209f fix duplicate columns on script manager close and open 2023-06-06 15:42:06 -04:00
Dan
2adfccfa1d Merge pull request #10 from dinger1986/develop
Update InitialSetup.vue
2023-06-06 11:51:01 -07:00
silversword411
04766efcd0 tactical-table and script manager table for column selection 2023-06-06 12:38:53 -04:00
dinger1986
4babb937f6 Update InitialSetup.vue 2023-06-01 21:37:06 +00:00
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
35 changed files with 3822 additions and 4441 deletions

View File

@@ -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

View File

@@ -13,7 +13,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
- run: npm install
- name: Run Prettier formatting

6861
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "web",
"version": "0.101.13",
"version": "0.101.31",
"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.15.9",
"apexcharts": "3.36.3",
"axios": "0.27.2",
"dotenv": "16.0.3",
"qrcode.vue": "3.3.3",
"quasar": "2.11.5",
"vue": "3.2.45",
"vue3-ace-editor": "2.2.2",
"vue3-apexcharts": "1.4.1",
"@quasar/extras": "1.16.7",
"apexcharts": "3.41.1",
"axios": "1.5.1",
"dotenv": "16.3.1",
"qrcode.vue": "3.4.1",
"quasar": "2.12.7",
"vue": "3.3.4",
"vue3-ace-editor": "2.2.3",
"vue3-apexcharts": "1.4.4",
"vuedraggable": "4.1.0",
"vue-router": "4.1.6",
"vue-router": "4.2.5",
"vuex": "4.1.0"
},
"devDependencies": {
"@quasar/cli": "^1.4.0",
"@intlify/vite-plugin-vue-i18n": "^6.0.3",
"@quasar/app-vite": "^1.2.0",
"@types/node": "^18.11.18",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"autoprefixer": "10.4.13",
"eslint": "8.32.0",
"eslint-config-prettier": "8.6.0",
"@quasar/cli": "2.3.0",
"@intlify/unplugin-vue-i18n": "1.4.0",
"@quasar/app-vite": "1.6.2",
"@types/node": "20.8.0",
"@typescript-eslint/eslint-plugin": "6.7.3",
"@typescript-eslint/parser": "6.7.3",
"autoprefixer": "10.4.16",
"eslint": "8.50.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-vue": "8.7.1",
"prettier": "2.8.3",
"typescript": "4.9.4"
"prettier": "3.0.3",
"typescript": "5.2.2"
}
}
}

View File

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

View File

@@ -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;
}

View File

@@ -31,6 +31,11 @@ export async function resetCheck(id) {
return data;
}
export async function resetAllChecksStatus(agent_id) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/resetall/`);
return data;
}
export async function runAgentChecks(agent_id) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/run/`);
return data;

View File

@@ -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;
}

View File

@@ -211,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>
@@ -219,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>
@@ -227,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>
@@ -235,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>
@@ -243,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>
@@ -279,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
@@ -303,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>
@@ -311,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>
@@ -373,17 +378,13 @@ export default {
"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
for (const elem of hiddenFields) {
if (!cols.find((o) => o.name === elem)) {
cols.push({
name: elem,
field: elem,
});
}
}
// 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;
@@ -437,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;
@@ -489,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,
@@ -533,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;
},

View File

@@ -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+";

View File

@@ -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,

View File

@@ -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,
};
},
};

View File

@@ -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,

View File

@@ -119,6 +119,16 @@
no-caps
icon="play_arrow"
@click="runChecks"
class="q-mr-md"
/>
<q-btn
label="Reset All Checks Status"
dense
flat
push
no-caps
icon="restart_alt"
@click="resetAllChecks"
/>
</template>
@@ -301,7 +311,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 +321,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 +329,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 +337,7 @@
<q-icon
v-else
style="font-size: 1.3rem"
color="negative"
:color="dash_negative_color"
name="error"
>
<q-tooltip>Error</q-tooltip>
@@ -415,6 +425,7 @@ import {
updateCheck,
removeCheck,
resetCheck,
resetAllChecksStatus,
runAgentChecks,
} from "@/api/checks";
import { fetchAgentChecks } from "@/api/agents";
@@ -479,6 +490,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();
@@ -568,7 +583,7 @@ export default {
notifySuccess(result);
refreshDashboard(
false /* clearTreeSelected */,
false /* clearSubTable */
false /* clearSubTable */,
);
} catch (e) {
console.error(e);
@@ -576,6 +591,27 @@ export default {
loading.value = false;
}
function resetAllChecks() {
console.info(selectedAgent.value);
$q.dialog({
title: "Are you sure?",
message: "Reset all checks status",
cancel: true,
ok: { label: "Reset", color: "negative" },
persistent: true,
}).onOk(async () => {
loading.value = true;
try {
const result = await resetAllChecksStatus(selectedAgent.value);
await getChecks();
notifySuccess(result);
} catch (e) {
console.error(e);
}
loading.value = false;
});
}
function showEventInfo(data) {
$q.dialog({
component: EventLogCheckOutput,
@@ -653,6 +689,10 @@ export default {
tabHeight,
selectedAgent,
agentPlatform,
dash_info_color,
dash_positive_color,
dash_warning_color,
dash_negative_color,
// non-reactive data
columns,
@@ -666,6 +706,7 @@ export default {
formatDate,
getAlertSeverity,
runChecks,
resetAllChecks,
// dialogs
showScriptOutput,

View File

@@ -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) => {

View File

@@ -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">
&bull; <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,

View File

@@ -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();
@@ -348,6 +355,9 @@ export default {
selectedAgent,
tabHeight,
agentPlatform,
dash_positive_color,
dash_warning_color,
dash_negative_color,
// non-reactive data
columns,

View File

@@ -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(() => {

View File

@@ -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"

View File

@@ -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

View File

@@ -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,

View File

@@ -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";
@@ -549,6 +550,9 @@ export default {
return result.trimEnd(",");
},
},
computed: {
...mapState(["dash_warning_color", "dash_negative_color"]),
},
mounted() {
// Get custom fields
this.getCustomFields("agent").then((r) => {

View File

@@ -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">

View File

@@ -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,
};

View File

@@ -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,116 +67,132 @@
><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"
/>
<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
: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 run as SYSTEM.
</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' }"
@@ -220,9 +246,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
@@ -266,6 +294,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();
@@ -320,7 +352,7 @@ export default {
downloadScript(script.value.id, { with_snippets: props.readonly }).then(
(r) => {
script.value.script_body = r.code;
}
},
);
async function submitForm() {
@@ -355,6 +387,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;
@@ -380,10 +429,12 @@ export default {
//computed
title,
openAIEnabled,
//methods
submitForm,
openTestScriptModal,
generateScriptOpenAI,
// quasar dialog plugin
dialogRef,

View File

@@ -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

View File

@@ -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,

View 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>

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 { formatAgentOptions } from "@/utils/format";
@@ -28,10 +29,12 @@ 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 = [

View File

@@ -4,7 +4,7 @@ export const GOARCH_ARM64 = "arm64";
export const GOARCH_ARM32 = "arm";
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 run as SYSTEM";
export const envVarsLabel =
"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"
>v{{ currentTRMMVersion }}</span
>
<span class="text-overline q-ml-md" v-if="updateAvailable()"
><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"
>SSL certificate expires in {{ daysUntilCertExpires }} days</q-chip
>
</q-toolbar-title>
<!-- temp dark mode toggle -->
<q-toggle
v-model="darkMode"
@@ -94,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>
@@ -113,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>
@@ -218,6 +238,8 @@ export default {
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
@@ -255,6 +277,7 @@ export default {
const serverOfflineCount = ref(0);
const workstationCount = ref(0);
const workstationOfflineCount = ref(0);
const daysUntilCertExpires = ref(100);
const ws = ref(null);
@@ -262,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);
@@ -274,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 {
@@ -294,16 +325,24 @@ export default {
const poll = ref(null);
function livePoll() {
poll.value = setInterval(() => {
store.dispatch("checkVer");
store.dispatch("getDashInfo", false);
}, 60 * 5 * 1000);
poll.value = setInterval(
() => {
store.dispatch("checkVer");
store.dispatch("getDashInfo", false);
},
60 * 4 * 1000,
);
}
function updateAvailable() {
if (latestTRMMVersion.value === "error" || hosted.value) return false;
const updateAvailable = computed(() => {
if (
latestTRMMVersion.value === "error" ||
hosted.value ||
currentTRMMVersion.value?.includes("-dev")
)
return false;
return currentTRMMVersion.value !== latestTRMMVersion.value;
}
});
onMounted(() => {
setupWS();
@@ -324,6 +363,7 @@ export default {
serverOfflineCount,
workstationCount,
workstationOfflineCount,
daysUntilCertExpires,
latestReleaseURL,
currentTRMMVersion,
latestTRMMVersion,
@@ -332,6 +372,8 @@ export default {
darkMode,
hosted,
tokenExpired,
dash_warning_color,
dash_negative_color,
// methods
showUserPreferences,

View File

@@ -33,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: {
@@ -136,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) {
@@ -160,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);
@@ -194,107 +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);
context.commit("SET_TOKEN_EXPIRED", data.token_is_expired);
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) => {

View File

@@ -13,7 +13,7 @@
>
<q-spinner size="40px" color="primary" />
</div>
<div v-else class="q-pa-sm q-gutter-sm scroll" style="height: 85vh">
<div v-else class="q-pa-sm q-gutter-sm scroll" style="height: 85vh; overflow: initial;">
<q-list dense class="rounded-borders">
<q-item
clickable
@@ -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,

View File

@@ -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>

View File

@@ -2,7 +2,7 @@
<div>
<q-bar>
<span class="text-caption">
Agent Status:
TRMM Agent Status:
<q-badge :color="statusColor" :label="status" />
</span>
<q-space />
@@ -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,