Compare commits

...

112 Commits

Author SHA1 Message Date
wh1te909
e837c494cb Release 0.101.43 2024-03-25 17:38:24 +00:00
wh1te909
afc40fcbe3 bump version 2024-03-25 17:38:12 +00:00
wh1te909
185f50787b wording 2024-03-23 19:29:12 +00:00
Dan
6c33676f73 Merge pull request #26 from dinger1986/dinger1986-add-confirm-and-info-for-enable-sync
Dinger1986 add confirm and info for enable sync
2024-03-23 12:12:44 -07:00
Dan
0290002444 update wording 2024-03-23 12:11:44 -07:00
wh1te909
fc5195e817 update reqs 2024-03-21 18:19:11 +00:00
Dan
efd5c3dca1 Merge pull request #25 from dinger1986/dinger1986-add-coname-to-initialsetup
Update InitialSetup.vue
2024-03-20 17:08:03 -07:00
Dan
2f438feec2 not needed 2024-03-20 17:07:17 -07:00
Dan
07ae9dfddf make style consistent with the other q-inputs 2024-03-20 17:04:34 -07:00
dinger1986
64575c5f7d Update EditCoreSettings.vue 2024-03-20 21:40:50 +00:00
dinger1986
e0fa339644 Update EditCoreSettings.vue 2024-03-20 21:32:18 +00:00
dinger1986
b72a86e514 Update InitialSetup.vue 2024-03-20 21:21:36 +00:00
wh1te909
62f0414afa fix date filter fixes amidaware/tacticalrmm#1803 2024-03-20 05:35:16 +00:00
wh1te909
200a02b87b update ci and bump version 2024-03-15 19:57:05 +00:00
wh1te909
da5dbeaf0f update reqs 2024-03-15 19:49:24 +00:00
wh1te909
4b6d099f72 add icon for tooltip and don't show in hosted 2024-03-13 01:30:37 +00:00
wh1te909
842661ada6 update browser/node 2024-03-13 01:29:22 +00:00
wh1te909
f5148c87c8 no longer need disable auto login 2024-03-12 05:27:18 +00:00
wh1te909
16164c0bbc update apexcharts 2024-03-10 22:37:17 +00:00
wh1te909
f38ddb840b update reqs 2024-03-08 18:42:40 +00:00
wh1te909
f86fe26ffe fix wording 2024-03-08 18:42:32 +00:00
Dan
162360bf45 Merge pull request #21 from dinger1986/develop
Update TakeControl.vue
2024-03-02 12:43:27 -08:00
dinger1986
612aaa7880 Update TakeControl.vue 2024-02-26 21:51:29 +00:00
wh1te909
e91f3fe53d sync mesh users/perms with trmm amidaware/tacticalrmm#182 2024-02-23 21:30:27 +00:00
wh1te909
f0fe4d64bc same perms 2024-02-23 03:17:35 +00:00
Dan
07cc6aca6a Merge pull request #20 from conlan0/develop
Add Shutdown option to agent action menu
2024-02-22 13:50:14 -08:00
wh1te909
23bf81efbb fix js/typescript monaco support in editor 2024-02-22 20:50:03 +00:00
wh1te909
a55105e5ee format 2024-02-22 20:49:33 +00:00
Dan
5832a426bc Merge pull request #14 from NiceGuyIT/feature/cross-platform-scripting
[Feature] Add cross site scripting
2024-02-21 21:23:26 -08:00
conlan0
38dc709108 Add shutdown url 2024-02-21 21:22:37 -05:00
conlan0
5696d3359b Add Shutdown option 2024-02-21 21:19:20 -05:00
wh1te909
1b4fa84753 update reqs 2024-02-21 01:13:38 +00:00
wh1te909
13f0f117da Release 0.101.40 2024-02-03 01:44:41 +00:00
wh1te909
2db4eeec05 bump version 2024-02-03 01:44:28 +00:00
Dan
fe5e8aa5fe Merge pull request #17 from JordanLukeJones/JordanLukeJones-OTP-1
Update LoginView.vue
2024-02-02 17:40:36 -08:00
Jordan Jones
13e35d24a2 Update LoginView.vue
Add's ability to auto-populate OTP on compatible devices
2024-02-02 09:29:19 +00:00
wh1te909
0b6ae80777 Release 0.101.39 2024-02-02 00:55:23 +00:00
wh1te909
5e0fab88a3 bump version 2024-02-02 00:53:27 +00:00
wh1te909
bf8797264b feat: hide custom fields in summary tab only amidaware/tacticalrmm#1745 2024-01-28 03:22:54 +00:00
wh1te909
14bde967bd feat: add serial number to linux/mac amidaware/tacticalrmm#1683 2024-01-27 02:47:23 +00:00
wh1te909
596ce69789 feat: add from name to email amidaware/tacticalrmm#1726 2024-01-26 00:38:12 +00:00
wh1te909
c5491dcb73 feat: add time and ret code to script test closes amidaware/tacticalrmm#1713 2024-01-26 00:04:58 +00:00
wh1te909
3f6340f0a1 update reqs 2024-01-26 00:04:19 +00:00
wh1te909
351f0870a9 update reqs 2024-01-19 08:09:06 +00:00
Dan
f2638a4c5e Merge pull request #16 from silversword411/develop
Increase user preferences width
2024-01-18 23:14:41 -08:00
silversword411
2bd00d5ca0 Increase user preferences width 2024-01-19 05:31:47 +00:00
wh1te909
00a40dd450 forgot to include 80% 2024-01-17 19:19:00 +00:00
wh1te909
cfe1cb2dbf Release 0.101.38 2023-12-22 17:50:19 +00:00
wh1te909
16fb75b56c bump version 2023-12-22 17:49:31 +00:00
wh1te909
094cf45ce3 add M3 2023-12-22 17:44:55 +00:00
wh1te909
d6984b3da9 use ticket instead of email 2023-12-22 17:44:36 +00:00
sadnub
53fc6f4cde fix folder view 2023-12-12 10:50:32 -05:00
wh1te909
e1dc8050e3 Release 0.101.37 2023-12-01 18:55:37 +00:00
wh1te909
49da10cf0b bump version 2023-12-01 18:53:26 +00:00
wh1te909
a3e10910bf wording 2023-12-01 18:52:01 +00:00
wh1te909
3ff9edc424 update reqs 2023-11-29 22:41:49 +00:00
sadnub
69414d4083 add unsaved changes to new scripts and close and cancel buttons 2023-11-29 10:24:13 -05:00
sadnub
e06b7a7775 add last_seen date to summary tab as a tooltip 2023-11-24 18:34:29 -05:00
sadnub
c006e4d922 make policy tasks sort ascending 2023-11-24 18:19:39 -05:00
sadnub
df6fe0863b change sort order for reports manager 2023-11-24 18:17:29 -05:00
sadnub
d55a29911c add trmm logo to community scripts in dropdown and improve img loading with es6 import 2023-11-24 17:47:07 -05:00
sadnub
d0e49d27fd add prompt for close if using esc in script manager 2023-11-24 17:02:45 -05:00
sadnub
1299bfc93e add trmm icon to builtin scripts 2023-11-24 16:19:33 -05:00
sadnub
be999646d4 make policy status width larger 2023-11-24 15:50:44 -05:00
sadnub
e57d32f122 add onboarding task in ui 2023-11-22 23:37:18 -05:00
wh1te909
3e6365574e Release 0.101.36 2023-11-23 00:03:20 +00:00
wh1te909
08fa8da735 bump version 2023-11-23 00:03:10 +00:00
David Randall
fe8d88497f [Feature] Add cross site scripting 2023-11-12 15:37:55 -05:00
sadnub
4ab31a529e update to node 18 2023-11-11 13:51:48 -05:00
wh1te909
466725d5c2 rework uninstall perm amidaware/tacticalrmm#1673 2023-11-10 01:20:25 +00:00
wh1te909
5114ff40aa Release 0.101.35 2023-11-07 17:25:24 +00:00
wh1te909
908b337797 bump version 2023-11-07 17:24:22 +00:00
wh1te909
fea5258903 update deps 2023-11-07 17:23:25 +00:00
Dan
5521e4ea3e Merge pull request #13 from silversword411/develop
Increase name field to show 50chars
2023-11-06 13:07:24 -08:00
silversword411
6cc01596cb Increase name field to show 50chars 2023-11-06 12:14:35 -05:00
sadnub
0694538482 fix editors not closing properly on Escape key press 2023-11-04 23:56:22 -04:00
wh1te909
6ea7c92b20 Release 0.101.34 2023-10-31 17:51:19 +00:00
wh1te909
ac05ad40c0 bump version 2023-10-31 17:50:59 +00:00
wh1te909
239b0182fb add some icons 2023-10-31 07:12:54 +00:00
sadnub
4c57e5da4b put shared template scripts in ascending order 2023-10-29 15:47:27 -04:00
wh1te909
298d039028 remove ace 2023-10-29 19:33:04 +00:00
sadnub
021a066074 watch for language changes and update the snippet editor 2023-10-29 15:16:54 -04:00
sadnub
7dc2f5a658 watch for language changes and update the editor 2023-10-29 15:05:51 -04:00
sadnub
57bd8bafac exported templates now are readable with spaces 2023-10-29 10:55:07 -04:00
sadnub
7d5216aba9 prevent linux shebang banner opening and closing on script load 2023-10-29 10:31:51 -04:00
sadnub
076ab0c465 fix script body not loading in editor. Fix slow unload times for monaco editor 2023-10-29 10:24:16 -04:00
wh1te909
be37e89e16 update reqs 2023-10-29 09:08:02 +00:00
wh1te909
0bdc841084 change wording 2023-10-29 08:57:53 +00:00
wh1te909
96086d0b5d fix wording 2023-10-28 22:06:52 +00:00
wh1te909
20d534eab0 Release 0.101.31 2023-10-01 17:36:52 +00:00
wh1te909
1b2286c4f8 Release 0.101.30 2023-09-30 21:59:09 +00:00
wh1te909
8207f30234 Release 0.101.29 2023-08-30 04:10:11 +00:00
wh1te909
68036f6837 Release 0.101.28 2023-08-14 06:39:49 +00:00
wh1te909
03fae45ac5 Release 0.101.25 2023-07-04 18:49:46 +00:00
wh1te909
c2591c9e7d Release 0.101.22 2023-05-30 22:11:30 +00:00
wh1te909
7fcbe6fbd8 Release 0.101.20 2023-05-09 21:09:45 +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
38 changed files with 1524 additions and 5989 deletions

View File

@@ -1,9 +1,9 @@
version: '3.4'
version: '3.7'
services:
app-dev:
container_name: trmm-app-dev
image: node:16-alpine
image: node:18-alpine
restart: always
command: /bin/sh -c "npm install --cache ~/.npm && npm run serve"
user: 1000:1000

View File

@@ -11,11 +11,11 @@ jobs:
name: Build web
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: "20.11.1"
- run: touch env-config.js
@@ -29,6 +29,6 @@ jobs:
run: tar -czvf trmm-web-${{github.ref_name}}.tar.gz dist/
- name: Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
files: trmm-web-${{github.ref_name}}.tar.gz

6400
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.32",
"version": "0.101.43",
"private": true,
"productName": "Tactical RMM",
"scripts": {
@@ -10,35 +10,37 @@
"format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
},
"dependencies": {
"@quasar/extras": "1.16.7",
"apexcharts": "3.41.1",
"axios": "1.5.1",
"dotenv": "16.3.1",
"@quasar/extras": "1.16.9",
"apexcharts": "3.48.0",
"axios": "1.6.8",
"dotenv": "16.4.5",
"pinia": "^2.1.7",
"qrcode.vue": "3.4.1",
"quasar": "2.13.0",
"vue": "3.3.7",
"vue3-ace-editor": "2.2.3",
"vue3-apexcharts": "1.4.4",
"quasar": "2.15.1",
"vue": "3.4.21",
"vue3-apexcharts": "1.5.2",
"vuedraggable": "4.1.0",
"vue-router": "4.2.5",
"@vueuse/core": "10.5.0",
"@vueuse/shared": "10.5.0",
"monaco-editor": "0.44.0",
"vue-router": "4.3.0",
"@vueuse/core": "10.9.0",
"@vueuse/shared": "10.9.0",
"monaco-editor": "0.47.0",
"vuex": "4.1.0",
"yaml": "2.3.3"
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",
"yaml": "2.4.1"
},
"devDependencies": {
"@quasar/cli": "2.3.0",
"@intlify/unplugin-vue-i18n": "1.4.0",
"@quasar/app-vite": "1.6.2",
"@types/node": "20.8.8",
"@typescript-eslint/eslint-plugin": "6.9.0",
"@typescript-eslint/parser": "6.9.0",
"autoprefixer": "10.4.16",
"eslint": "8.52.0",
"eslint-config-prettier": "9.0.0",
"@quasar/cli": "2.4.0",
"@intlify/unplugin-vue-i18n": "3.0.1",
"@quasar/app-vite": "1.8.0",
"@types/node": "20.11.30",
"@typescript-eslint/eslint-plugin": "7.3.1",
"@typescript-eslint/parser": "7.3.1",
"autoprefixer": "10.4.18",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-vue": "8.7.1",
"prettier": "3.0.3",
"typescript": "5.2.2"
"prettier": "3.2.5",
"typescript": "5.4.3"
}
}

View File

@@ -36,7 +36,7 @@ module.exports = configure(function (/* ctx */) {
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
"ionicons-v4",
"mdi-v5",
"fontawesome-v6",
// 'eva-icons',
@@ -51,8 +51,8 @@ module.exports = configure(function (/* ctx */) {
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
build: {
target: {
browser: ["es2021"],
node: "node16",
browser: ["es2022"],
node: "node20",
},
vueRouterMode: "history", // available values: 'hash', 'history'

View File

@@ -191,6 +191,11 @@ export async function agentRebootNow(agent_id) {
return data;
}
export async function agentShutdown(agent_id) {
const { data } = await axios.post(`${baseUrl}/${agent_id}/shutdown/`);
return data;
}
export async function sendAgentRecoverMesh(agent_id, params = {}) {
const { data } = await axios.post(
`${baseUrl}/${agent_id}/meshcentral/recover/`,

BIN
src/assets/trmm_256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -170,7 +170,7 @@
overdueAlert(
'dashboard',
props.row,
props.row.overdue_dashboard_alert
props.row.overdue_dashboard_alert,
)
"
v-model="props.row.overdue_dashboard_alert"
@@ -431,8 +431,8 @@ export default {
return false;
else if (availability === "expired") {
let now = new Date();
let lastSeen = date.extractDate(row.last_seen, "MM DD YYYY HH:mm");
let diff = date.getDateDiff(now, lastSeen, "days");
let last_seen_unix = new Date(row.boot_time * 1000);
let diff = date.getDateDiff(now, last_seen_unix, "days");
if (diff < 30) return false;
}
}

View File

@@ -150,7 +150,7 @@
</q-menu>
</q-btn>
<!-- integrations -->
<q-btn size="md" dense no-caps flat label="Integrations">
<q-btn size="md" dense no-caps flat label="Reporting">
<q-menu auto-close>
<q-list
v-if="

View File

@@ -85,10 +85,6 @@
v-model="localRole.can_uninstall_agents"
label="Uninstall Agents"
/>
<q-checkbox
v-model="localRole.can_ping_agents"
label="Ping Agents"
/>
<q-checkbox
v-model="localRole.can_update_agents"
label="Update Agents"
@@ -111,7 +107,7 @@
/>
<q-checkbox
v-model="localRole.can_reboot_agents"
label="Reboot Agents"
label="Shutdown / Reboot Agents"
/>
<q-checkbox
v-model="localRole.can_send_wol"
@@ -447,7 +443,6 @@ export default {
can_uninstall_agents: false,
can_update_agents: false,
can_edit_agent: false,
can_ping_agents: false,
can_manage_procs: false,
can_view_eventlogs: false,
can_send_cmd: false,

View File

@@ -176,6 +176,13 @@
</q-menu>
</q-item>
<q-item clickable v-close-popup @click="shutdown(agent)">
<q-item-section side>
<q-icon size="xs" name="power" />
</q-item-section>
<q-item-section>Shutdown</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showPolicyAdd(agent)">
<q-item-section side>
<q-icon size="xs" name="policy" />
@@ -192,9 +199,9 @@
"
>
<q-item-section side>
<q-icon size="xs" name="integration_instructions" />
<q-icon size="xs" name="analytics" />
</q-item-section>
<q-item-section>Integrations</q-item-section>
<q-item-section>Reporting</q-item-section>
<q-item-section side>
<q-icon name="keyboard_arrow_right" />
</q-item-section>
@@ -231,6 +238,7 @@ import { fetchURLActions, runURLAction } from "@/api/core";
import {
editAgent,
agentRebootNow,
agentShutdown,
sendAgentPing,
removeAgent,
runRemoteBackground,
@@ -298,7 +306,7 @@ export default {
if (urlActions.value.length === 0) {
notifyWarning(
"No URL Actions configured. Go to Settings > Global Settings > URL Actions"
"No URL Actions configured. Go to Settings > Global Settings > URL Actions",
);
return;
}
@@ -364,7 +372,7 @@ export default {
notifySuccess(
`Maintenance mode was ${
agent.maintenance_mode ? "disabled" : "enabled"
} on ${agent.hostname}`
} on ${agent.hostname}`,
);
store.commit("setRefreshSummaryTab", true);
refreshDashboard();
@@ -437,6 +445,32 @@ export default {
});
}
function shutdown(agent) {
$q.dialog({
title:
'Please type <code style="color:red">yes</code> in the box below to confirm shutdown.',
prompt: {
model: "",
type: "text",
isValid: (val) => val === "yes",
},
cancel: true,
ok: { label: "Shutdown", color: "negative" },
persistent: true,
html: true,
}).onOk(async () => {
$q.loading.show();
try {
await agentShutdown(agent.agent_id);
notifySuccess(`${agent.hostname} will now be shutdown`);
$q.loading.hide();
} catch (e) {
$q.loading.hide();
console.error(e);
}
});
}
function showPolicyAdd(agent) {
$q.dialog({
component: PolicyAdd,
@@ -505,7 +539,7 @@ export default {
notifySuccess(data);
refreshDashboard(
false /* clearTreeSelected */,
true /* clearSubTable */
true /* clearSubTable */,
);
} catch (e) {
console.error(e);
@@ -534,6 +568,7 @@ export default {
runChecks,
showRebootLaterModal,
rebootNow,
shutdown,
showPolicyAdd,
showAgentRecovery,
pingAgent,

View File

@@ -34,7 +34,7 @@
:color="dash_warning_color"
class="q-mr-sm"
>
<q-tooltip>Agent offline</q-tooltip>
<q-tooltip>{{ store.getters.formatDate(summary.last_seen) }}</q-tooltip>
</q-icon>
<q-icon
v-else
@@ -43,7 +43,7 @@
:color="dash_positive_color"
class="q-mr-sm"
>
<q-tooltip>Agent online</q-tooltip>
<q-tooltip>{{ store.getters.formatDate(summary.last_seen) }}</q-tooltip>
</q-icon>
<b>{{ summary.hostname }}</b>
<span v-if="summary.maintenance_mode">
@@ -267,7 +267,11 @@ export default {
const loading = ref(false);
const serial_number = computed(() => {
return summary.value.wmi_detail.bios?.[0]?.[0]?.SerialNumber;
if (summary.value.plat === "windows") {
return summary.value.wmi_detail.bios?.[0]?.[0]?.SerialNumber;
} else {
return summary.value.wmi_detail.serialnumber;
}
});
const cpu = computed(() => {
@@ -280,7 +284,7 @@ export default {
function diskBarColor(percent) {
if (percent < 80) {
return dash_positive_color.value;
} else if (percent > 80 && percent < 95) {
} else if (percent >= 80 && percent < 95) {
return dash_warning_color.value;
} else {
return dash_negative_color.value;
@@ -311,11 +315,11 @@ export default {
const ret = [];
for (const customField of summary.value.custom_fields) {
const definition = customFieldsDefinitions.value.find(
(def) => def.id === customField.field
(def) => def.id === customField.field,
);
if (
definition &&
!definition.hide_in_ui &&
!definition.hide_in_summary &&
customField.value?.length > 0
) {
ret.push({
@@ -381,6 +385,7 @@ export default {
dash_negative_color,
serial_number,
cpu,
store,
// methods
getSummary,

View File

@@ -254,7 +254,7 @@ export default {
pagination: {
rowsPerPage: 0,
sortBy: "name",
descending: true,
descending: false,
},
};
},
@@ -321,7 +321,7 @@ export default {
runTask(task) {
if (!task.enabled) {
this.notifyError(
"Task cannot be run when it's disabled. Enable it first."
"Task cannot be run when it's disabled. Enable it first.",
);
return;
}

View File

@@ -1,6 +1,6 @@
<template>
<q-dialog ref="dialog" @hide="onHide">
<q-card class="q-dialog-plugin" style="width: 90vw">
<q-card class="q-dialog-plugin" style="min-width: 70vw">
<q-bar>
{{ title.slice(0, 27) }}
<q-space />

View File

@@ -137,7 +137,7 @@
<q-radio
v-model="goarch"
:val="GOARCH_ARM64"
label="Apple Silicon (M1, M2)"
label="Apple Silicon (M1, M2, M3)"
v-show="agentOS === 'darwin'"
/>
<q-radio

View File

@@ -142,6 +142,11 @@
v-model="localField.hide_in_ui"
color="green"
/>
<q-toggle
label="Hide in Summary Tab"
v-model="localField.hide_in_summary"
color="green"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" v-close-popup />
@@ -172,6 +177,7 @@ export default {
default_value_bool: false,
default_values_multiple: [],
hide_in_ui: false,
hide_in_summary: false,
},
modelOptions: [
{ label: "Client", value: "client" },

View File

@@ -57,6 +57,10 @@
<q-td>
<q-icon v-if="props.row.hide_in_ui" name="check" />
</q-td>
<!-- hide in summary tab -->
<q-td>
<q-icon v-if="props.row.hide_in_summary" name="check" />
</q-td>
<!-- default value -->
<q-td v-if="props.row.type === 'checkbox'">
{{ props.row.default_value_bool }}
@@ -123,6 +127,13 @@ export default {
align: "left",
sortable: true,
},
{
name: "hide_in_summary",
label: "Hide in Summary Tab",
field: "hide_in_summary",
align: "left",
sortable: true,
},
{
name: "default_value",
label: "Default Value",

View File

@@ -71,7 +71,7 @@
icon="info"
@click="
openURL(
'https://quasar.dev/quasar-utils/date-utils#format-for-display'
'https://quasar.dev/quasar-utils/date-utils#format-for-display',
)
"
>
@@ -216,7 +216,7 @@
<div class="text-subtitle2">SMTP Settings</div>
<q-separator />
<q-card-section class="row">
<div class="col-2">From:</div>
<div class="col-2">From email:</div>
<div class="col-4"></div>
<q-input
outlined
@@ -226,6 +226,16 @@
:rules="[(val) => isValidEmail(val) || 'Invalid email']"
/>
</q-card-section>
<q-card-section class="row">
<div class="col-2">From name:</div>
<div class="col-4"></div>
<q-input
outlined
dense
v-model="settings.smtp_from_name"
class="col-6 q-pa-none"
/>
</q-card-section>
<q-card-section class="row">
<div class="col-2">Host:</div>
<div class="col-4"></div>
@@ -379,7 +389,7 @@
<q-tab-panel name="meshcentral">
<div class="text-subtitle2">MeshCentral Settings</div>
<q-separator />
<q-card-section class="row">
<q-card-section class="row" v-if="!hosted">
<div class="col-4">Username:</div>
<div class="col-2"></div>
<q-input
@@ -395,7 +405,7 @@
]"
/>
</q-card-section>
<q-card-section class="row">
<q-card-section class="row" v-if="!hosted">
<div class="col-4">Mesh Site:</div>
<div class="col-2"></div>
<q-input
@@ -405,7 +415,7 @@
class="col-6"
/>
</q-card-section>
<q-card-section class="row">
<q-card-section class="row" v-if="!hosted">
<div class="col-4">Mesh Token:</div>
<div class="col-2"></div>
<q-input
@@ -415,7 +425,7 @@
class="col-6"
/>
</q-card-section>
<q-card-section class="row">
<q-card-section class="row" v-if="!hosted">
<div class="col-4">Mesh Device Group Name:</div>
<div class="col-2"></div>
<q-input
@@ -425,17 +435,58 @@
class="col-6"
/>
</q-card-section>
<q-card-section class="row">
<div class="col-4">
Disable Auto Login for Remote Control and Remote background:
<q-card-section class="row" v-if="!hosted">
<div class="col-4 flex items-center">
Sync Mesh Perms with TRMM:
<q-icon
right
name="ion-information-circle-outline"
size="sm"
class="cursor-pointer"
>
<q-tooltip class="text-caption">
It is recommended to keep this option enabled;
otherwise, all TRMM users will have full permissions in
MeshCentral regardless of their permissions in TRMM.
</q-tooltip>
</q-icon>
</div>
<div class="col-2"></div>
<q-checkbox
dense
v-model="settings.mesh_disable_auto_login"
:model-value="settings.sync_mesh_with_trmm"
@update:model-value="confirmSyncChange"
class="col-6"
/>
</q-card-section>
<q-card-section class="row items-center">
<div class="col-4 flex items-center">
Company Name:
<q-icon
name="ion-information-circle-outline"
size="sm"
class="q-ml-sm cursor-pointer"
>
<q-tooltip class="text-caption">
Adding your company name here will append it to the
user's full name that appears when doing a remote
control session, for example: 'John Doe - Amidaware
Inc.'
</q-tooltip>
</q-icon>
</div>
<div class="col-2"></div>
<q-input
dense
outlined
v-model="settings.mesh_company_name"
class="col-6"
>
</q-input>
</q-card-section>
</q-tab-panel>
<q-tab-panel name="customfields">
<CustomFields />
@@ -635,6 +686,11 @@ export default {
],
};
},
computed: {
hosted() {
return this.$store.state.hosted;
},
},
methods: {
openURL(url) {
openURL(url);
@@ -669,6 +725,19 @@ export default {
}));
});
},
confirmSyncChange(newValue) {
this.$q
.dialog({
title: "Are you sure?",
message:
"This operation may take several minutes to complete in the background and can be very CPU/disk intensive, depending on your hardware and number of agents. Please allow time for the sync to fully complete.",
ok: { label: "Yes", color: "primary" },
cancel: { label: "No", color: "negative" },
})
.onOk(() => {
this.settings.sync_mesh_with_trmm = newValue;
});
},
showResetPatchPolicy() {
this.$q.dialog({
component: ResetPatchPolicy,
@@ -711,13 +780,13 @@ export default {
},
removeEmail(email) {
const removed = this.settings.email_alert_recipients.filter(
(k) => k !== email
(k) => k !== email,
);
this.settings.email_alert_recipients = removed;
},
removeSMSNumber(num) {
const removed = this.settings.sms_alert_recipients.filter(
(k) => k !== num
(k) => k !== num,
);
this.settings.sms_alert_recipients = removed;
},

View File

@@ -1,6 +1,6 @@
<template>
<q-dialog ref="dialog" @hide="onHide">
<q-card class="q-dialog-plugin" style="min-width: 85vh">
<q-card class="q-dialog-plugin" style="min-width: 60vw">
<q-splitter v-model="splitterModel">
<template v-slot:before>
<q-tabs dense v-model="tab" vertical class="text-primary">
@@ -201,7 +201,7 @@
icon="info"
@click="
openURL(
'https://quasar.dev/quasar-utils/date-utils#format-for-display'
'https://quasar.dev/quasar-utils/date-utils#format-for-display',
)
"
>
@@ -315,7 +315,7 @@ export default {
this.$axios.get("/core/urlaction/").then((r) => {
if (r.data.length === 0) {
this.notifyWarning(
"No URL Actions configured. Go to Settings > Global Settings > URL Actions"
"No URL Actions configured. Go to Settings > Global Settings > URL Actions",
);
return;
}

View File

@@ -1,17 +1,14 @@
<template>
<q-dialog
ref="dialogRef"
persistent
@keydown.esc.stop="onDialogHide"
:maximized="maximized"
@keydown.esc="unloadEditor"
@hide="unloadEditor"
maximized
no-esc-dismiss
@hide="onDialogHide"
@show="loadEditor"
@before-hide="unloadEditor"
@keydown.esc.stop="closeEditor"
>
<q-card
class="q-dialog-plugin"
:style="maximized ? '' : 'width: 90vw; max-width: 90vw'"
>
<q-card class="q-dialog-plugin">
<q-bar>
<span class="q-pr-sm">{{ title }}</span>
<q-btn
@@ -25,34 +22,12 @@
@click="generateScriptOpenAI"
/>
<q-space />
<q-btn
dense
flat
icon="minimize"
@click="maximized = false"
:disable="!maximized"
>
<q-tooltip v-if="maximized" class="bg-white text-primary"
>Minimize</q-tooltip
>
</q-btn>
<q-btn
dense
flat
icon="crop_square"
@click="maximized = true"
:disable="maximized"
>
<q-tooltip v-if="!maximized" class="bg-white text-primary"
>Maximize</q-tooltip
>
</q-btn>
<q-btn dense flat icon="close" v-close-popup>
<q-btn dense flat icon="close" @click="closeEditor">
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
</q-btn>
</q-bar>
<q-banner
v-if="missingShebang"
v-if="script.script_body && missingShebang"
dense
inline-actions
class="text-black bg-warning"
@@ -78,7 +53,7 @@
opacity: '0.2',
}"
class="col-4 q-mb-none q-pb-none"
:style="{ height: `${maximized ? '82vh' : '64vh'}` }"
:style="{ height: `${$q.screen.height - 106}px` }"
>
<div class="q-gutter-sm q-pr-sm">
<q-input
@@ -170,8 +145,7 @@
>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.
is logged in, the script will run as SYSTEM.
</q-tooltip>
</q-checkbox>
<q-input
@@ -188,7 +162,7 @@
<div
ref="scriptEditor"
class="col-8 q-mb-none q-pb-none"
:style="{ height: `${maximized ? '82vh' : '64vh'}` }"
:style="{ height: `${$q.screen.height - 106}px` }"
></div>
</div>
<q-card-actions>
@@ -218,7 +192,7 @@
</template>
</tactical-dropdown>
<q-space />
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn dense flat label="Cancel" @click="closeEditor" />
<q-btn
v-if="!readonly"
:loading="loading"
@@ -235,7 +209,7 @@
<script setup lang="ts">
// composable imports
import { ref, reactive, computed, onMounted } from "vue";
import { ref, reactive, watch, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { useQuasar, useDialogPluginComponent } from "quasar";
import { saveScript, editScript, downloadScript } from "@/api/scripts";
@@ -248,6 +222,35 @@ import TestScriptModal from "@/components/scripts/TestScriptModal.vue";
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
import * as monaco from "monaco-editor";
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
import jsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
// https://github.com/microsoft/monaco-editor/issues/4045#issuecomment-1723787448
self.MonacoEnvironment = {
getWorker: function (workerId, label) {
switch (label) {
case "json":
return new jsonWorker();
case "css":
case "scss":
case "less":
return new cssWorker();
case "html":
case "handlebars":
case "razor":
return new htmlWorker();
case "typescript":
case "javascript":
return new jsWorker();
default:
return new editorWorker();
}
},
};
// types
import type { Script } from "@/types/scripts";
@@ -266,7 +269,7 @@ const props = withDefaults(
{
clone: false,
readonly: false,
}
},
);
// emits
@@ -297,7 +300,6 @@ const script: Script = props.script
});
if (props.clone) script.name = `(Copy) ${script.name}`;
const maximized = ref(false);
const loading = ref(false);
const agentLoading = ref(false);
@@ -314,8 +316,8 @@ const title = computed(() => {
return props.readonly
? `Viewing ${script.name}`
: props.clone
? `Copying ${script.name}`
: `Editing ${script.name}`;
? `Copying ${script.name}`
: `Editing ${script.name}`;
} else {
return "Adding new script";
}
@@ -323,19 +325,23 @@ const title = computed(() => {
// convert highlighter language to match what ace expects
const lang = computed(() => {
if (script.shell === "cmd") return "bat";
else if (script.shell === "powershell") return "powershell";
else if (script.shell === "python") return "python";
else if (script.shell === "shell") return "shell";
else return "";
switch (script.shell) {
case "cmd":
return "bat";
case "powershell":
return "powershell";
case "python":
return "python";
case "shell":
case "nushell":
return "shell";
case "deno":
return "typescript";
default:
return "";
}
});
// get code if editing or cloning script
if (props.script)
downloadScript(script.id, { with_snippets: props.readonly }).then((r) => {
script.script_body = r.code;
});
async function submit() {
loading.value = true;
let result = "";
@@ -372,12 +378,7 @@ const scriptEditor = ref<HTMLElement | null>(null);
let editor: monaco.editor.IStandaloneCodeEditor;
function loadEditor() {
var modelUri = monaco.Uri.parse("model://new"); // a made up unique URI for our model
var model = monaco.editor.createModel(
script.script_body,
lang.value,
modelUri
);
var model = monaco.editor.createModel(script.script_body, lang.value);
const theme = $q.dark.isActive ? "vs-dark" : "vs-light";
@@ -392,6 +393,34 @@ function loadEditor() {
editor.onDidChangeModelContent(() => {
script.script_body = editor.getValue();
});
// get code if editing or cloning script
if (props.script)
downloadScript(script.id, { with_snippets: props.readonly }).then((r) => {
script.script_body = r.code;
editor.setValue(r.code);
// need to add this in the download function otherwise the above will trigger an edit
watch(
() => script.script_body,
() => {
edited.value = true;
},
);
});
else {
watch(
() => script.script_body,
() => {
edited.value = true;
},
);
}
// watch for changes in language
watch(lang, () => {
monaco.editor.setModelLanguage(model, lang.value);
});
}
function unloadEditor() {
@@ -417,6 +446,21 @@ function generateScriptOpenAI() {
});
}
// add are you sure prompt to unsaved script
const edited = ref(false);
function closeEditor() {
if (edited.value)
$q.dialog({
title: "You have unsaved changes. Are you sure you want to close?",
cancel: true,
ok: true,
}).onOk(async () => {
unloadEditor();
});
else unloadEditor();
}
// component life cycle hooks
onMounted(async () => {
agentLoading.value = true;

View File

@@ -175,6 +175,28 @@
>
<q-tooltip> Shell </q-tooltip>
</q-icon>
<q-icon
v-else-if="props.node.shell === 'nushell'"
name="mdi-code-greater-than"
color="primary"
>
<q-tooltip> Nushell </q-tooltip>
</q-icon>
<q-icon
v-else-if="props.node.shell === 'deno'"
name="mdi-language-typescript"
color="primary"
>
<q-tooltip> Deno </q-tooltip>
</q-icon>
<!-- is community script icon -->
<img
v-if="props.node.script_type === 'builtin'"
class="vertical-middle"
:src="trmmLogo"
style="height: 20px; max-width: 20px"
/>
<span
class="q-pl-xs text-weight-bold"
@@ -463,6 +485,22 @@
>
<q-tooltip> Shell </q-tooltip>
</q-icon>
<q-icon
v-else-if="props.row.shell === 'nushell'"
size="sm"
name="mdi-code-greater-than"
color="primary"
>
<q-tooltip> Nushell </q-tooltip>
</q-icon>
<q-icon
v-else-if="props.row.shell === 'deno'"
size="sm"
name="mdi-language-typescript"
color="primary"
>
<q-tooltip> Deno </q-tooltip>
</q-icon>
</q-td>
<!-- supported platforms -->
<q-td key="supported_platforms" :props="props">
@@ -488,6 +526,12 @@
:props="props"
:style="{ color: props.row.hidden ? 'grey' : '' }"
>
<!-- is community script icon -->
<img
v-if="props.row.script_type === 'builtin'"
:src="trmmLogo"
style="height: 20px; max-width: 20px"
/>
{{ truncateText(props.row.name, 50) }}
<q-tooltip
v-if="props.row.name.length >= 50"
@@ -550,6 +594,8 @@ import ScriptFormModal from "@/components/scripts/ScriptFormModal.vue";
import ScriptSnippets from "@/components/scripts/ScriptSnippets.vue";
import TacticalTable from "@/components/ui/TacticalTable.vue";
import trmmLogo from "@/assets/trmm_256.png";
// static data
const columns = [
{
@@ -620,7 +666,7 @@ export default {
// setup vuex store
const store = useStore();
const showCommunityScripts = computed(
() => store.state.showCommunityScripts
() => store.state.showCommunityScripts,
);
// setup quasar plugins
@@ -721,7 +767,7 @@ export default {
return showCommunityScripts.value
? scripts.value.filter((i) => !i.hidden)
: scripts.value.filter(
(i) => i.script_type !== "builtin" && !i.hidden
(i) => i.script_type !== "builtin" && !i.hidden,
);
}
});
@@ -884,6 +930,7 @@ export default {
loading,
showCommunityScripts,
showHiddenScripts,
trmmLogo,
// computed
visibleScripts,

View File

@@ -1,16 +1,12 @@
<template>
<q-dialog
ref="dialogRef"
persistent
@keydown.esc="unloadEditor"
:maximized="maximized"
@hide="unloadEditor"
maximized
@hide="onDialogHide"
@show="loadEditor"
@before-hide="unloadEditor"
>
<q-card
class="q-dialog-plugin"
:style="maximized ? '' : 'width: 70vw; max-width: 90vw'"
>
<q-card class="q-dialog-plugin">
<q-bar>
<span class="q-pr-sm">{{ title }}</span>
<q-btn
@@ -24,35 +20,13 @@
@click="generateScriptOpenAI"
/>
<q-space />
<q-btn
dense
flat
icon="minimize"
@click="maximized = false"
:disable="!maximized"
>
<q-tooltip v-if="maximized" class="bg-white text-primary"
>Minimize</q-tooltip
>
</q-btn>
<q-btn
dense
flat
icon="crop_square"
@click="maximized = true"
:disable="maximized"
>
<q-tooltip v-if="!maximized" class="bg-white text-primary"
>Maximize</q-tooltip
>
</q-btn>
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
</q-btn>
</q-bar>
<div class="row">
<q-input
:rules="[(val) => !!val || '*Required']"
:rules="[(val: string) => !!val || '*Required']"
class="q-pa-sm col-4"
v-model="snippet.name"
label="Name"
@@ -81,7 +55,7 @@
<div
ref="snippetEditor"
:style="{ height: `${maximized ? '82vh' : '64vh'}` }"
:style="{ height: `${$q.screen.height - 132}px` }"
></div>
<q-card-actions align="right">
@@ -101,7 +75,7 @@
<script setup lang="ts">
// composable imports
import { ref, reactive, computed } from "vue";
import { ref, watch, reactive, computed } from "vue";
import { useStore } from "vuex";
import { useQuasar } from "quasar";
import { generateScript } from "@/api/core";
@@ -112,6 +86,35 @@ import { notifySuccess } from "@/utils/notify";
// ui imports
import * as monaco from "monaco-editor";
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
import jsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
// https://github.com/microsoft/monaco-editor/issues/4045#issuecomment-1723787448
self.MonacoEnvironment = {
getWorker: function (workerId, label) {
switch (label) {
case "json":
return new jsonWorker();
case "css":
case "scss":
case "less":
return new cssWorker();
case "html":
case "handlebars":
case "razor":
return new htmlWorker();
case "typescript":
case "javascript":
return new jsWorker();
default:
return new editorWorker();
}
},
};
// types
import type { ScriptSnippet } from "@/types/scripts";
@@ -138,7 +141,6 @@ const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled);
const snippet: ScriptSnippet = props.snippet
? reactive(Object.assign({}, props.snippet))
: reactive({ name: "", code: "", shell: "powershell" });
const maximized = ref(false);
const loading = ref(false);
const title = computed(() => {
@@ -151,11 +153,21 @@ const title = computed(() => {
// convert highlighter language to match what ace expects
const lang = computed(() => {
if (snippet.shell === "cmd") return "bat";
else if (snippet.shell === "powershell") return "powershell";
else if (snippet.shell === "python") return "python";
else if (snippet.shell === "shell") return "shell";
else return "";
switch (snippet.shell) {
case "cmd":
return "bat";
case "powershell":
return "powershell";
case "python":
return "python";
case "shell":
case "nushell":
return "shell";
case "deno":
return "typescript";
default:
return "";
}
});
async function submit() {
@@ -177,8 +189,7 @@ const snippetEditor = ref<HTMLElement | null>(null);
let editor: monaco.editor.IStandaloneCodeEditor;
function loadEditor() {
var modelUri = monaco.Uri.parse("model://new"); // a made up unique URI for our model
var model = monaco.editor.createModel(snippet.code, lang.value, modelUri);
var model = monaco.editor.createModel(snippet.code, lang.value);
const theme = $q.dark.isActive ? "vs-dark" : "vs-light";
@@ -192,6 +203,11 @@ function loadEditor() {
editor.onDidChangeModelContent(() => {
snippet.code = editor.getValue();
});
// watch for changes in language
watch(lang, () => {
monaco.editor.setModelLanguage(model, lang.value);
});
}
function unloadEditor() {

View File

@@ -124,6 +124,22 @@
>
<q-tooltip> Shell </q-tooltip>
</q-icon>
<q-icon
v-else-if="props.row.shell === 'nushell'"
name="mdi-nushell"
color="primary"
size="sm"
>
<q-tooltip> Nushell </q-tooltip>
</q-icon>
<q-icon
v-else-if="props.row.shell === 'deno'"
name="mdi-typescript"
color="primary"
size="sm"
>
<q-tooltip> Deno </q-tooltip>
</q-icon>
</q-td>
<!-- name -->
<q-td>{{ props.row.name }}</q-td>

View File

@@ -8,8 +8,25 @@
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
</q-btn>
</q-bar>
<q-card-section class="scroll" style="max-height: 70vh; height: 70vh">
<pre v-if="ret">{{ ret }}</pre>
<q-card-section style="height: 70vh" class="scroll">
<div>
Run Time:
<code>{{ ret.execution_time }} seconds</code>
<br />Return Code:
<code>{{ ret.retcode }}</code>
<br />
</div>
<br />
<div v-if="ret.stdout">
Standard Output
<q-separator />
<pre>{{ ret.stdout }}</pre>
</div>
<div v-if="ret.stderr">
Standard Error
<q-separator />
<pre>{{ ret.stderr }}</pre>
</div>
<q-inner-loading :showing="loading" />
</q-card-section>
</q-card>
@@ -34,7 +51,12 @@ export default {
const { dialogRef, onDialogHide } = useDialogPluginComponent();
// main run script functionality
const ret = ref(null);
const ret = ref({
execution_time: "",
retcode: "",
stdout: "",
stderr: "",
});
const loading = ref(false);
async function runTestScript() {

View File

@@ -87,181 +87,183 @@
:done="step > 2"
:error="!isValidStep2"
>
<q-form @submit.prevent="addAction">
<div class="row q-pa-sm q-gutter-x-xs items-center">
<div class="text-subtitle2 col-12">Action Type:</div>
<q-option-group
class="col-12"
inline
v-model="actionType"
:options="[
{ label: 'Script', value: 'script' },
{ label: 'Command', value: 'cmd' },
]"
/>
<div class="scroll" style="max-height: 60vh">
<q-form @submit.prevent="addAction">
<div class="row q-pa-sm q-gutter-x-xs items-center">
<div class="text-subtitle2 col-12">Action Type:</div>
<q-option-group
class="col-12"
inline
v-model="actionType"
:options="[
{ label: 'Script', value: 'script' },
{ label: 'Command', value: 'cmd' },
]"
/>
<tactical-dropdown
v-if="actionType === 'script'"
class="col-3"
label="Select script"
v-model="script"
:options="scriptOptions"
filled
mapOptions
filterable
/>
<tactical-dropdown
v-if="actionType === 'script'"
class="col-3"
label="Select script"
v-model="script"
:options="scriptOptions"
filled
mapOptions
filterable
/>
<q-select
v-if="actionType === 'script'"
class="col-3"
dense
label="Script Arguments (press Enter after typing each argument)"
filled
v-model="defaultArgs"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
/>
<q-select
v-if="actionType === 'script'"
class="col-3"
dense
label="Script Arguments (press Enter after typing each argument)"
filled
v-model="defaultArgs"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
/>
<q-select
v-if="actionType === 'script'"
class="col-3"
dense
:label="envVarsLabel"
filled
v-model="defaultEnvVars"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
/>
<q-select
v-if="actionType === 'script'"
class="col-3"
dense
:label="envVarsLabel"
filled
v-model="defaultEnvVars"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
/>
<q-input
v-if="actionType === 'script'"
class="col-2"
filled
dense
v-model.number="defaultTimeout"
type="number"
label="Timeout (seconds)"
/>
<q-input
v-if="actionType === 'script'"
class="col-2"
filled
dense
v-model.number="defaultTimeout"
type="number"
label="Timeout (seconds)"
/>
<q-input
v-if="actionType === 'cmd'"
label="Command"
v-model="command"
<q-input
v-if="actionType === 'cmd'"
label="Command"
v-model="command"
dense
filled
class="col-7"
/>
<q-input
v-if="actionType === 'cmd'"
class="col-2"
filled
dense
v-model.number="defaultTimeout"
type="number"
label="Timeout (seconds)"
/>
<q-option-group
v-if="actionType === 'cmd'"
class="col-2 q-pl-sm"
inline
v-model="shell"
:options="[
{ label: 'Batch', value: 'cmd' },
{ label: 'Powershell', value: 'powershell' },
]"
/>
<q-btn
class="col-1"
type="submit"
style="width: 50px"
flat
dense
icon="add"
color="primary"
/>
</div>
</q-form>
<div class="text-subtitle2 q-pa-sm">
Actions:
<q-checkbox
class="float-right"
label="Continue on Errors"
v-model="state.continue_on_error"
dense
filled
class="col-7"
/>
<q-input
v-if="actionType === 'cmd'"
class="col-2"
filled
dense
v-model.number="defaultTimeout"
type="number"
label="Timeout (seconds)"
/>
<q-option-group
v-if="actionType === 'cmd'"
class="col-2 q-pl-sm"
inline
v-model="shell"
:options="[
{ label: 'Batch', value: 'cmd' },
{ label: 'Powershell', value: 'powershell' },
]"
/>
<q-btn
class="col-1"
type="submit"
style="width: 50px"
flat
dense
icon="add"
color="primary"
/>
>
<q-tooltip>Continue task if an action fails</q-tooltip>
</q-checkbox>
</div>
</q-form>
<div class="text-subtitle2 q-pa-sm">
Actions:
<q-checkbox
class="float-right"
label="Continue on Errors"
v-model="state.continue_on_error"
dense
>
<q-tooltip>Continue task if an action fails</q-tooltip>
</q-checkbox>
</div>
<div class="scroll q-pt-sm" style="height: 40vh; max-height: 40vh">
<draggable
class="q-list"
handle=".handle"
ghost-class="ghost"
v-model="state.actions"
item-key="index"
>
<template v-slot:item="{ index, element }">
<q-item>
<q-item-section avatar>
<q-icon
class="handle"
style="cursor: move"
name="drag_handle"
/>
</q-item-section>
<q-item-section v-if="element.type === 'script'">
<q-item-label>
<q-icon size="sm" name="description" color="primary" />
&nbsp; {{ element.name }}
</q-item-label>
<q-item-label caption>
Arguments: {{ element.script_args }}
</q-item-label>
<q-item-label caption>
Env Vars: {{ element.env_vars }}
</q-item-label>
<q-item-label caption>
Timeout: {{ element.timeout }}
</q-item-label>
</q-item-section>
<q-item-section v-else>
<q-item-label>
<q-icon size="sm" name="terminal" color="primary" />
&nbsp;
<div class="q-pt-sm" style="height: 150px">
<draggable
class="q-list"
handle=".handle"
ghost-class="ghost"
v-model="state.actions"
item-key="index"
>
<template v-slot:item="{ index, element }">
<q-item>
<q-item-section avatar>
<q-icon
size="sm"
:name="
element.shell === 'cmd'
? 'mdi-microsoft-windows'
: 'mdi-powershell'
"
color="primary"
class="handle"
style="cursor: move"
name="drag_handle"
/>
{{ element.command }}
</q-item-label>
<q-item-label caption>
Timeout: {{ element.timeout }}
</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon
class="cursor-pointer"
color="negative"
name="close"
@click="removeAction(index)"
/>
</q-item-section>
</q-item>
</template>
</draggable>
</q-item-section>
<q-item-section v-if="element.type === 'script'">
<q-item-label>
<q-icon size="sm" name="description" color="primary" />
&nbsp; {{ element.name }}
</q-item-label>
<q-item-label caption>
Arguments: {{ element.script_args }}
</q-item-label>
<q-item-label caption>
Env Vars: {{ element.env_vars }}
</q-item-label>
<q-item-label caption>
Timeout: {{ element.timeout }}
</q-item-label>
</q-item-section>
<q-item-section v-else>
<q-item-label>
<q-icon size="sm" name="terminal" color="primary" />
&nbsp;
<q-icon
size="sm"
:name="
element.shell === 'cmd'
? 'mdi-microsoft-windows'
: 'mdi-powershell'
"
color="primary"
/>
{{ element.command }}
</q-item-label>
<q-item-label caption>
Timeout: {{ element.timeout }}
</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon
class="cursor-pointer"
color="negative"
name="close"
@click="removeAction(index)"
/>
</q-item-section>
</q-item>
</template>
</draggable>
</div>
</div>
</q-step>
@@ -283,7 +285,7 @@
<q-card-section
v-if="
['runonce', 'daily', 'weekly', 'monthly'].includes(
state.task_type
state.task_type,
)
"
class="row"
@@ -314,6 +316,22 @@
/>
</q-card-section>
<q-card-section
v-if="
state.task_type === 'onboarding' ||
state.task_type === 'runonce'
"
class="row"
>
<span v-if="state.task_type === 'onboarding'"
>This task will run as soon as it's created on the
agent.</span
>
<span v-else-if="state.task_type === 'runonce'"
>Start Time must be in the future for run once tasks.</span
>
</q-card-section>
<!-- daily options -->
<q-card-section v-if="state.task_type === 'daily'" class="row">
<!-- daily interval -->
@@ -579,7 +597,8 @@
<q-card-section
v-if="
state.task_type !== 'checkfailure' &&
state.task_type !== 'manual'
state.task_type !== 'manual' &&
state.task_type !== 'onboarding'
"
class="row"
>
@@ -617,7 +636,7 @@
(val) =>
convertPeriodToSeconds(val) >=
convertPeriodToSeconds(
state.task_repetition_interval
state.task_repetition_interval,
) ||
'Repetition duration must be greater than repetition interval',
]"
@@ -712,7 +731,7 @@
@click="
validateStep(
step === 1 ? $refs.taskGeneralForm : undefined,
$refs.stepper
$refs.stepper,
)
"
color="primary"
@@ -769,6 +788,7 @@ const taskTypeOptions = [
{ label: "Monthly", value: "monthly" },
{ label: "Run Once", value: "runonce" },
{ label: "On check failure", value: "checkfailure" },
{ label: "Onboarding", value: "onboarding" },
{ label: "Manual", value: "manual" },
];
@@ -933,7 +953,7 @@ export default {
task.value.actions.push({
type: "script",
name: scriptOptions.value.find(
(option) => option.value === script.value
(option) => option.value === script.value,
).label,
script: script.value,
timeout: defaultTimeout.value,
@@ -1019,13 +1039,13 @@ export default {
// remove milliseconds and Z to work with native date input
task.value.run_time_date = formatDateInputField(
task.value.run_time_date,
true
true,
);
if (task.value.expire_date)
task.value.expire_date = formatDateInputField(
task.value.expire_date,
true
true,
);
// set task type if monthlydow is being used
@@ -1069,7 +1089,7 @@ export default {
task.value.monthly_weeks_of_month = [];
task.value.task_instance_policy = 0;
task.value.expire_date = null;
}
},
);
// check the collector box when editing task and custom field is set

View File

@@ -25,13 +25,21 @@
:key="mapOptions ? scope.opt.value : scope.opt"
>
<q-item-section>
<q-item-label
v-html="mapOptions ? scope.opt.label : scope.opt"
></q-item-label>
<q-item-label v-html="mapOptions ? scope.opt.label : scope.opt" />
</q-item-section>
<q-item-section
v-if="
(filtered && mapOptions && scope.opt.cat) || scope.opt.img_right
"
side
>
{{ scope.opt.cat || "" }}
<img
v-if="scope.opt.img_right"
:src="scope.opt.img_right"
style="height: 20px; max-width: 20px"
/>
</q-item-section>
<q-item-section v-if="filtered && mapOptions && scope.opt.cat" side>{{
scope.opt.cat
}}</q-item-section>
</q-item>
<q-item-label
v-if="scope.opt.category"
@@ -80,7 +88,7 @@ export default {
if (!props.mapOptions)
filteredOptions.value = props.options.filter(
(v) => v.toLowerCase().indexOf(needle) > -1
(v) => v.toLowerCase().indexOf(needle) > -1,
);
else
filteredOptions.value = props.options.filter((v) => {

View File

@@ -18,7 +18,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
// specify parameters to filter out community scripts
async function getScriptOptions(showCommunityScripts = false) {
scriptOptions.value = Object.freeze(
formatScriptOptions(await fetchScripts({ showCommunityScripts }))
formatScriptOptions(await fetchScripts({ showCommunityScripts })),
);
}
@@ -26,7 +26,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
watch([script, scriptOptions], () => {
if (script.value && scriptOptions.value.length > 0) {
const tmpScript = scriptOptions.value.find(
(i) => i.value === script.value
(i) => i.value === script.value,
);
defaultTimeout.value = tmpScript.timeout;
defaultArgs.value = tmpScript.args;
@@ -65,4 +65,6 @@ export const shellOptions = [
{ label: "Batch", value: "cmd" },
{ label: "Python", value: "python" },
{ label: "Shell", value: "shell" },
{ label: "Nushell", value: "nushell" },
{ label: "Deno", value: "deno" },
];

View File

@@ -302,7 +302,10 @@ export function useReportTemplates(): useReportingTemplates {
axios
.post(`${baseUrl}/templates/${id}/export/`)
.then(({ data }) => {
exportFile(`${data.template.name}-export.json`, JSON.stringify(data));
exportFile(
`${data.template.name}-export.json`,
JSON.stringify(data, null, 2),
);
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));

View File

@@ -34,7 +34,7 @@ For details, see: https://license.tacticalrmm.com/ee
class="q-pr-sm"
filled
dense
style="width: 250px"
style="width: 425px"
:error="!isNameValid"
hide-bottom-space
/>

View File

@@ -32,7 +32,7 @@ For details, see: https://license.tacticalrmm.com/ee
:rows="reportTemplates"
:columns="columns"
:loading="isLoading"
:pagination="{ rowsPerPage: 0, sortBy: 'name', descending: true }"
:pagination="{ rowsPerPage: 0, sortBy: 'name', descending: false }"
:filter="search"
row-key="id"
binary-state-sort
@@ -54,6 +54,9 @@ For details, see: https://license.tacticalrmm.com/ee
clickable
@click="openNewReportTemplateForm('markdown')"
>
<q-item-section avatar>
<q-icon name="fa-brands fa-markdown" />
</q-item-section>
<q-item-section>
<q-item-label>Markdown Template</q-item-label>
</q-item-section>
@@ -64,8 +67,11 @@ For details, see: https://license.tacticalrmm.com/ee
clickable
@click="openNewReportTemplateForm('html')"
>
<q-item-section avatar>
<q-icon name="fa-brands fa-html5" />
</q-item-section>
<q-item-section>
<q-item-label>Html Template</q-item-label>
<q-item-label>HTML Template</q-item-label>
</q-item-section>
</q-item>
@@ -74,6 +80,9 @@ For details, see: https://license.tacticalrmm.com/ee
clickable
@click="openNewReportTemplateForm('plaintext')"
>
<q-item-section avatar>
<q-icon name="fa-solid fa-file-csv" />
</q-item-section>
<q-item-section>
<q-item-label>Plain Text Template</q-item-label>
</q-item-section>
@@ -82,6 +91,9 @@ For details, see: https://license.tacticalrmm.com/ee
<q-separator />
<q-item clickable v-close-popup @click="importReportTemplate">
<q-item-section avatar>
<q-icon name="fa-solid fa-file-import" />
</q-item-section>
<q-item-section>
<q-item-label>Import Report Template</q-item-label>
</q-item-section>
@@ -91,6 +103,7 @@ For details, see: https://license.tacticalrmm.com/ee
<q-btn
class="q-ml-sm"
label="Base Templates"
icon="fa-regular fa-file-code"
no-caps
dense
flat
@@ -99,6 +112,7 @@ For details, see: https://license.tacticalrmm.com/ee
<q-btn
class="q-ml-sm"
label="Report Assets"
icon="fa-regular fa-folder-closed"
no-caps
dense
flat
@@ -107,6 +121,7 @@ For details, see: https://license.tacticalrmm.com/ee
<q-btn
class="q-ml-sm"
label="Data Queries"
icon="fa-solid fa-database"
no-caps
dense
flat
@@ -115,6 +130,7 @@ For details, see: https://license.tacticalrmm.com/ee
<q-btn
class="q-ml-sm"
label="Shared Templates"
icon="fa-solid fa-share"
no-caps
dense
flat

View File

@@ -25,7 +25,7 @@ For details, see: https://license.tacticalrmm.com/ee
:rows="sharedTemplates"
:columns="columns"
:loading="isLoading"
:pagination="{ rowsPerPage: 0, sortBy: 'name', descending: true }"
:pagination="{ rowsPerPage: 0, sortBy: 'name', descending: false }"
:filter="search"
selection="multiple"
v-model:selected="selected"

View File

@@ -25,8 +25,8 @@
If you have downgraded or cancelled your sponsorship, please delete
your token from the Code Signing modal and refresh to get rid of this
banner.<br /><br />
For any issues or to renew your sponsorship please email
support@amidaware.com<br /><br
For any issues or to renew your sponsorship please open a ticket at
support.amidaware.com<br /><br
/></span>
<q-btn
color="dark"

View File

@@ -1,6 +1,6 @@
import type { AgentPlatformType } from "@/types/agents";
export type ScriptShellType = "powershell" | "cmd" | "shell" | "python";
export type ScriptShellType = "powershell" | "cmd" | "shell" | "python" | "nushell" | "deno";
export interface Script {
id?: number;

View File

@@ -1,5 +1,6 @@
import { date } from "quasar";
import { validateTimePeriod } from "@/utils/validation";
import trmmLogo from "@/assets/trmm_256.png";
// dropdown options formatting
export function removeExtraOptionCategories(array) {
@@ -24,7 +25,7 @@ function _formatOptions(
flat = false,
allowDuplicates = true,
appendToOptionObject = {},
}
},
) {
if (!flat)
// returns array of options in object format [{label: label, value: 1}]
@@ -64,6 +65,7 @@ export function formatScriptOptions(data) {
data.forEach((script) => {
if (script.category === cat) {
tmp.push({
img_right: script.script_type === "builtin" ? trmmLogo : undefined,
label: script.name,
value: script.id,
timeout: script.default_timeout,
@@ -100,7 +102,7 @@ export function formatScriptOptions(data) {
export function formatAgentOptions(
data,
flat = false,
value_field = "agent_id"
value_field = "agent_id",
) {
if (flat) {
// returns just agent hostnames in array
@@ -185,7 +187,7 @@ export function formatSiteOptions(data, flat = false) {
label: "name",
flat: flat,
appendToOptionObject: { cat: client.name },
})
}),
);
});
@@ -361,7 +363,7 @@ export function convertToBitArray(number) {
bitArray.push(1);
} else {
bitArray.push(
parseInt(binary.slice(i), 2) - parseInt(binary.slice(i + 1), 2)
parseInt(binary.slice(i), 2) - parseInt(binary.slice(i + 1), 2),
);
}
}

View File

@@ -13,7 +13,11 @@
>
<q-spinner size="40px" color="primary" />
</div>
<div v-else class="q-pa-sm q-gutter-sm scroll" style="height: 85vh; overflow: initial;">
<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
@@ -163,7 +167,7 @@
runURLAction(
props.node.id,
action.id,
props.node.children ? 'client' : 'site'
props.node.children ? 'client' : 'site',
)
"
>
@@ -196,9 +200,9 @@
"
>
<q-item-section side>
<q-icon name="integration_instructions" />
<q-icon name="analytics" />
</q-item-section>
<q-item-section>Integrations</q-item-section>
<q-item-section>Reporting</q-item-section>
<q-item-section side>
<q-icon name="keyboard_arrow_right" />
</q-item-section>
@@ -816,7 +820,7 @@ export default {
this.$axios.get("/core/urlaction/").then((r) => {
if (r.data.length === 0) {
this.notifyWarning(
"No URL Actions configured. Go to Settings > Global Settings > URL Actions"
"No URL Actions configured. Go to Settings > Global Settings > URL Actions",
);
return;
}

View File

@@ -53,6 +53,26 @@
:options="allTimezones"
/>
</q-card-section>
<q-card-section>
<div>
Company name:
<q-icon
name="ion-information-circle-outline"
size="sm"
class="q-ml-sm cursor-pointer"
>
<q-tooltip class="text-caption">
Adding your company name here will append it to the user's
full name that appears when doing a remote control session,
for example: 'John Doe - Amidaware Inc.'
</q-tooltip>
</q-icon>
</div>
<q-input dense outlined v-model="companyname"> </q-input>
</q-card-section>
<q-card-actions align="center">
<q-btn
label="Finish"
@@ -86,6 +106,7 @@ export default {
allTimezones: [],
timezone: null,
arch: "64",
companyname: "",
};
},
methods: {
@@ -95,6 +116,7 @@ export default {
client: this.client,
site: this.site,
timezone: this.timezone,
companyname: this.companyname,
initialsetup: true,
};
this.$axios

View File

@@ -63,6 +63,7 @@
autofocus
outlined
v-model="credentials.twofactor"
autocomplete="one-time-code"
:rules="[
(val) =>
(val && val.length > 0) || 'This field is required',

View File

@@ -90,7 +90,7 @@ export default {
control.value = data.control;
status.value = data.status;
useMeta({
title: `${data.hostname} - ${data.client} - ${data.site} | Remote Background`,
title: `${data.hostname} - ${data.client} - ${data.site} | Take Control`,
});
} catch (e) {
console.error(e);