Compare commits

...

63 Commits

Author SHA1 Message Date
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
a2f472ef9c Release 0.101.18 2023-04-09 03:28:23 +00:00
wh1te909
8403ac0e93 Release 0.101.16 2023-03-22 17:00:29 +00:00
wh1te909
b7a91563b0 Release 0.101.13 2023-01-18 20:05:20 +00:00
wh1te909
ab19afca16 Release 0.101.11 2022-12-21 18:44:46 +00:00
wh1te909
f24c6a7a80 Release 0.101.9 2022-12-04 23:01:59 +00:00
wh1te909
99490bf859 Release 0.101.7 2022-11-13 01:20:33 +00:00
wh1te909
72cdeeaa6a Release 0.101.5 2022-10-25 22:02:34 +00:00
wh1te909
1eca4d605b Release 0.101.3 2022-10-19 22:35:54 +00:00
wh1te909
52ee98f6f8 Release 0.101.0 2022-09-24 02:43:53 +00:00
wh1te909
d270b877c9 Release 0.100.9 2022-08-23 05:04:57 +00:00
wh1te909
fd8b2a1d98 Release 0.100.8 2022-08-09 20:40:48 +00:00
wh1te909
f518043d8d Release 0.100.7 2022-08-01 17:36:11 +00:00
wh1te909
cc2335558d Release 0.100.6 2022-07-27 06:15:49 +00:00
wh1te909
a8a171ba2c Release 0.100.5 2022-07-10 00:00:08 +00:00
wh1te909
24a63f477e Release 0.100.4 2022-07-07 16:38:14 +00:00
wh1te909
ddeb6293a1 init 2022-05-17 20:46:22 +00:00
30 changed files with 1578 additions and 1203 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

1823
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.19-dev",
"version": "0.101.29",
"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.16.3",
"apexcharts": "3.40.0",
"@quasar/extras": "1.16.6",
"apexcharts": "3.41.1",
"axios": "1.4.0",
"dotenv": "16.0.3",
"qrcode.vue": "3.3.4",
"quasar": "2.12.0",
"vue": "3.2.47",
"vue3-ace-editor": "2.2.2",
"vue3-apexcharts": "1.4.1",
"dotenv": "16.3.1",
"qrcode.vue": "3.4.1",
"quasar": "2.12.5",
"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.4",
"vuex": "4.1.0"
},
"devDependencies": {
"@quasar/cli": "^2.1.0",
"@intlify/unplugin-vue-i18n": "^0.10.0",
"@quasar/app-vite": "^1.3.0",
"@types/node": "^18.16.5",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"autoprefixer": "10.4.14",
"eslint": "8.40.0",
"eslint-config-prettier": "8.8.0",
"@quasar/cli": "2.2.3",
"@intlify/unplugin-vue-i18n": "0.12.3",
"@quasar/app-vite": "1.4.6",
"@types/node": "20.5.7",
"@typescript-eslint/eslint-plugin": "6.5.0",
"@typescript-eslint/parser": "6.5.0",
"autoprefixer": "10.4.15",
"eslint": "8.48.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-vue": "8.7.1",
"prettier": "2.8.8",
"typescript": "5.0.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

@@ -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,7 +378,8 @@ export default {
"local_ips",
"make_model",
"physical_disks",
"custom_fields"
"custom_fields",
"serial_number",
];
// quasar filter only does visible columns so this is a hack to add hidden columns we want to filter
// originally I was modifying cols directly but this led to phantom colum so doing it this way now
@@ -435,7 +441,7 @@ export default {
return allColumns.some((col) => {
let valObj = cellValue(col, row);
if (Array.isArray(valObj)) {
valObj = valObj.map((item) => item.value ? item.value : item);
valObj = valObj.map((item) => (item.value ? item.value : item));
}
const val = valObj + "";
const haystack =
@@ -488,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,
@@ -532,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

@@ -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>
@@ -222,19 +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;
}
}
@@ -290,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();
@@ -325,6 +375,12 @@ export default {
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,7 +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-tab name="openai" label="Open AI" /> -->
</q-tabs>
</template>
<template v-slot:after>
@@ -511,7 +511,7 @@
</q-tab-panel>
<!-- Open AI -->
<q-tab-panel name="openai">
<!-- <q-tab-panel name="openai">
<div class="text-subtitle2">Open AI</div>
<q-separator />
<q-card-section class="row">
@@ -551,7 +551,7 @@
</template>
</q-input>
</q-card-section>
</q-tab-panel>
</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

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

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

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

@@ -34,6 +34,15 @@ export default function () {
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: {
@@ -140,6 +149,21 @@ export default function () {
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) {
@@ -164,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);
@@ -198,111 +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);
context.commit(
"setOpenAIIntegrationStatus",
data.open_ai_integration_enabled
);
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
@@ -452,7 +452,7 @@ export default {
showInstallAgentModal: false,
sitePk: null,
innerModel: (this.$q.screen.height - 82) / 2,
search: (this.$route.query.search ? this.$route.query.search : ""),
search: this.$route.query.search ? this.$route.query.search : "",
filterTextLength: 0,
filterAvailability: "all",
filterPatchesPending: false,

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,