Compare commits
31 Commits
v0.100.1-d
...
v0.101.1-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cf5f7a3cb | ||
|
|
9729492d1c | ||
|
|
d6da8b4a96 | ||
|
|
9264cf4044 | ||
|
|
3a45c2a309 | ||
|
|
59de35c698 | ||
|
|
5b8ac2c809 | ||
|
|
83d0ff1c0a | ||
|
|
8a6ec6ceab | ||
|
|
93dbc74e33 | ||
|
|
5f2add48a9 | ||
|
|
b7369875af | ||
|
|
2eb6580fed | ||
|
|
9f85fbb330 | ||
|
|
ee9715a4cf | ||
|
|
76f330fb9c | ||
|
|
e67c1ff331 | ||
|
|
137a5648ce | ||
|
|
a944bc50d1 | ||
|
|
0a4b00298d | ||
|
|
1eaed284a3 | ||
|
|
b278e0bed4 | ||
|
|
6ee3df7e4e | ||
|
|
7ee87da3b6 | ||
|
|
7bce958633 | ||
|
|
57963f6d1a | ||
|
|
c9d76bdddc | ||
|
|
c279a44679 | ||
|
|
974ba53926 | ||
|
|
021fbbe14f | ||
|
|
bbd74c34b7 |
7
.devcontainer/.env.example
Normal file
7
.devcontainer/.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
COMPOSE_PROJECT_NAME=trmm
|
||||
IMAGE_REPO=tacticalrmm/
|
||||
VERSION=latest
|
||||
|
||||
# DEV SETTINGS
|
||||
APP_PORT=443
|
||||
DOCKER_NETWORK=172.21.0.0/24
|
||||
26
.devcontainer/docker-compose.yml
Normal file
26
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
app-dev:
|
||||
container_name: trmm-app-dev
|
||||
image: node:16-alpine
|
||||
restart: always
|
||||
command: /bin/sh -c "npm install --cache ~/.npm && npm run serve"
|
||||
user: 1000:1000
|
||||
working_dir: /workspace/web
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
ports:
|
||||
- "8080:443"
|
||||
networks:
|
||||
dev:
|
||||
aliases:
|
||||
- tactical-frontend
|
||||
|
||||
networks:
|
||||
dev:
|
||||
driver: bridge
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: ${DOCKER_NETWORK}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,3 +33,4 @@ yarn-error.log*
|
||||
*.sln
|
||||
|
||||
.env
|
||||
/public/env-config.js
|
||||
|
||||
1965
package-lock.json
generated
1965
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.100.1-dev",
|
||||
"version": "0.101.1-dev",
|
||||
"private": true,
|
||||
"productName": "Tactical RMM",
|
||||
"scripts": {
|
||||
@@ -10,31 +10,31 @@
|
||||
"format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "1.14.1",
|
||||
"apexcharts": "3.35.3",
|
||||
"@quasar/extras": "1.15.5",
|
||||
"apexcharts": "3.35.5",
|
||||
"axios": "0.27.2",
|
||||
"dotenv": "16.0.1",
|
||||
"dotenv": "16.0.3",
|
||||
"qrcode.vue": "3.3.3",
|
||||
"quasar": "2.7.3",
|
||||
"vue": "3.2.37",
|
||||
"quasar": "2.10.0",
|
||||
"vue": "3.2.41",
|
||||
"vue3-ace-editor": "2.2.2",
|
||||
"vue3-apexcharts": "1.4.1",
|
||||
"vuedraggable": "4.1.0",
|
||||
"vue-router": "4.0.16",
|
||||
"vuex": "4.0.2"
|
||||
"vue-router": "4.1.5",
|
||||
"vuex": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/cli": "^1.3.2",
|
||||
"@intlify/vite-plugin-vue-i18n": "^3.4.0",
|
||||
"@quasar/app-vite": "^1.0.2",
|
||||
"@types/node": "^18.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
||||
"@typescript-eslint/parser": "^5.29.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"eslint": "^8.18.0",
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.3",
|
||||
"@quasar/app-vite": "^1.1.3",
|
||||
"@types/node": "^18.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||
"@typescript-eslint/parser": "^5.38.0",
|
||||
"autoprefixer": "^10.4.11",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-vue": "^8.5.0",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4"
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,17 @@ export default function ({ app, router, store }) {
|
||||
return response;
|
||||
},
|
||||
async function (error) {
|
||||
if (error.code && error.code === "ERR_NETWORK") {
|
||||
Notify.create({
|
||||
color: "negative",
|
||||
message: "Backend is offline (network error)",
|
||||
caption:
|
||||
"Open your browser's dev tools and check the console tab for more detailed error messages",
|
||||
timeout: 5000,
|
||||
});
|
||||
return Promise.reject({ ...error });
|
||||
}
|
||||
|
||||
let text;
|
||||
|
||||
if (!error.response) {
|
||||
|
||||
@@ -196,6 +196,14 @@
|
||||
>
|
||||
<q-tooltip>Linux</q-tooltip>
|
||||
</q-icon>
|
||||
<q-icon
|
||||
v-else-if="props.row.plat === 'darwin'"
|
||||
name="mdi-apple"
|
||||
size="sm"
|
||||
color="primary"
|
||||
>
|
||||
<q-tooltip>macOS</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
|
||||
<q-td key="checks-status" :props="props">
|
||||
@@ -356,6 +364,27 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
filterTable(rows, terms, cols, cellValue) {
|
||||
const hiddenFields = [
|
||||
"version",
|
||||
"operating_system",
|
||||
"public_ip",
|
||||
"cpu_model",
|
||||
"graphics",
|
||||
"local_ips",
|
||||
"make_model",
|
||||
"physical_disks",
|
||||
];
|
||||
|
||||
// quasar filter only does visible columns so this is a hack to add hidden columns we want to filter
|
||||
for (const elem of hiddenFields) {
|
||||
if (!cols.find((o) => o.name === elem)) {
|
||||
cols.push({
|
||||
name: elem,
|
||||
field: elem,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const lowerTerms = terms ? terms.toLowerCase() : "";
|
||||
let advancedFilter = false;
|
||||
let availability = null;
|
||||
|
||||
@@ -310,9 +310,10 @@ export default {
|
||||
}
|
||||
|
||||
function showUpdateDetails(update) {
|
||||
const color = $q.dark.isActive ? "white" : "";
|
||||
let support_urls = "";
|
||||
update.more_info_urls.forEach((u) => {
|
||||
support_urls += `<a href='${u}' target='_blank'>${u}</a><br/>`;
|
||||
support_urls += `<a style='color: ${color}' href='${u}' target='_blank'>${u}</a><br/>`;
|
||||
});
|
||||
let cats = update.categories.join(", ");
|
||||
$q.dialog({
|
||||
|
||||
@@ -7,6 +7,17 @@
|
||||
<q-badge color="primary" class="q-ml-sm text-caption">{{
|
||||
v
|
||||
}}</q-badge>
|
||||
<q-btn
|
||||
v-if="!!v"
|
||||
size="sm"
|
||||
class="q-ml-xs"
|
||||
flat
|
||||
round
|
||||
icon="content_copy"
|
||||
@click="copyValueToClip(v)"
|
||||
>
|
||||
<q-tooltip>Copy to Clipboard</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<q-separator v-if="info.length > 1" />
|
||||
@@ -15,6 +26,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { copyToClipboard } from "quasar";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
// composition imports
|
||||
import { computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
@@ -28,9 +41,17 @@ export default {
|
||||
const store = useStore();
|
||||
const tabHeight = computed(() => store.state.tabHeight);
|
||||
|
||||
function copyValueToClip(val) {
|
||||
copyToClipboard(val)
|
||||
.then(() => {
|
||||
notifySuccess("Copied to clipboard");
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
tabHeight,
|
||||
uid,
|
||||
copyValueToClip,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,10 +10,13 @@
|
||||
</q-card-actions>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<p class="text-subtitle1">
|
||||
<p v-if="info.plat === 'windows'" class="text-subtitle1">
|
||||
Download the agent then run the following command from an elevated
|
||||
command prompt on the device you want to add.
|
||||
</p>
|
||||
<p v-else-if="info.plat === 'darwin'" class="text-subtitle1">
|
||||
Run the following command from a terminal
|
||||
</p>
|
||||
<p>
|
||||
<q-field outlined :color="$q.dark.isActive ? 'white' : 'black'">
|
||||
<code>{{ info.data.cmd }}</code>
|
||||
@@ -37,7 +40,7 @@
|
||||
</q-badge>
|
||||
<span>Do not popup any message boxes during install</span>
|
||||
</div>
|
||||
<div class="q-pa-xs q-gutter-xs">
|
||||
<div v-if="info.plat === 'windows'" class="q-pa-xs q-gutter-xs">
|
||||
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
||||
<code
|
||||
>-local-mesh "C:\\<some folder or
|
||||
@@ -46,7 +49,7 @@
|
||||
</q-badge>
|
||||
<span> To skip downloading the Mesh Agent during the install.</span>
|
||||
</div>
|
||||
<div class="q-pa-xs q-gutter-xs">
|
||||
<div v-if="info.plat === 'windows'" class="q-pa-xs q-gutter-xs">
|
||||
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
||||
<code
|
||||
>-meshdir "C:\Program Files\Your Company Name\Mesh Agent"</code
|
||||
@@ -63,7 +66,7 @@
|
||||
</q-badge>
|
||||
<span>Don't install the mesh agent</span>
|
||||
</div>
|
||||
<div class="q-pa-xs q-gutter-xs">
|
||||
<div v-if="info.plat === 'windows'" class="q-pa-xs q-gutter-xs">
|
||||
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
||||
<code>-cert "C:\\<some folder or path>\\ca.pem"</code>
|
||||
</q-badge>
|
||||
@@ -86,12 +89,12 @@
|
||||
<p class="text-italic">
|
||||
Note: the auth token above will be valid for {{ info.expires }} hours.
|
||||
</p>
|
||||
<q-btn
|
||||
<q-btn v-if="info.plat === 'windows'"
|
||||
type="a"
|
||||
:href="info.data.url"
|
||||
color="primary"
|
||||
label="Download Agent"
|
||||
/>
|
||||
></q-btn>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
@@ -135,6 +135,11 @@
|
||||
:rules="[(val) => !!val || '*Required']"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="supportsRunAsUser()" class="q-pt-none">
|
||||
<q-checkbox v-model="state.run_as_user" label="Run As User">
|
||||
<q-tooltip>{{ runAsUserToolTip }}</q-tooltip>
|
||||
</q-checkbox>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section v-if="mode === 'script' || mode === 'command'">
|
||||
<q-input
|
||||
@@ -203,6 +208,7 @@ import { runBulkAction } from "@/api/agents";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
import { cmdPlaceholder } from "@/composables/agents";
|
||||
import { removeExtraOptionCategories } from "@/utils/format";
|
||||
import { runAsUserToolTip } from "@/constants/constants";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
@@ -217,6 +223,7 @@ const monTypeOptions = [
|
||||
const osTypeOptions = [
|
||||
{ label: "Windows", value: "windows" },
|
||||
{ label: "Linux", value: "linux" },
|
||||
{ label: "macOS", value: "darwin" },
|
||||
{ label: "All", value: "all" },
|
||||
];
|
||||
|
||||
@@ -300,6 +307,7 @@ export default {
|
||||
script,
|
||||
timeout: defaultTimeout,
|
||||
args: defaultArgs,
|
||||
run_as_user: false,
|
||||
});
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -316,6 +324,7 @@ export default {
|
||||
() => state.value.osType,
|
||||
(newValue) => {
|
||||
state.value.custom_shell = null;
|
||||
state.value.run_as_user = false;
|
||||
|
||||
if (newValue === "windows") {
|
||||
state.value.shell = "cmd";
|
||||
@@ -337,6 +346,13 @@ export default {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
const supportsRunAsUser = () => {
|
||||
const modes = ["script", "command"];
|
||||
return (
|
||||
state.value.osType === "windows" && modes.includes(state.value.mode)
|
||||
);
|
||||
};
|
||||
|
||||
// set modal title and caption
|
||||
const modalTitle = computed(() => {
|
||||
return props.mode === "command"
|
||||
@@ -387,6 +403,7 @@ export default {
|
||||
osTypeOptions,
|
||||
targetOptions,
|
||||
patchModeOptions,
|
||||
runAsUserToolTip,
|
||||
|
||||
//computed
|
||||
modalTitle,
|
||||
@@ -394,6 +411,7 @@ export default {
|
||||
//methods
|
||||
submit,
|
||||
cmdPlaceholder,
|
||||
supportsRunAsUser,
|
||||
|
||||
// quasar dialog plugin
|
||||
dialogRef,
|
||||
|
||||
@@ -465,8 +465,51 @@ export default {
|
||||
});
|
||||
},
|
||||
editAgent() {
|
||||
delete this.agent.all_timezones;
|
||||
delete this.agent.timezone;
|
||||
// TODO we need to fix the serializer to not send this stuff
|
||||
const toRemove = [
|
||||
"created_by",
|
||||
"created_time",
|
||||
"modified_by",
|
||||
"modified_time",
|
||||
"all_timezones",
|
||||
"timezone",
|
||||
"wmi_detail",
|
||||
"services",
|
||||
"status",
|
||||
"cpu_model",
|
||||
"local_ips",
|
||||
"make_model",
|
||||
"physical_disks",
|
||||
"graphics",
|
||||
"checks",
|
||||
"patches_last_installed",
|
||||
"last_seen",
|
||||
"applied_policies",
|
||||
"effective_patch_policy",
|
||||
"version",
|
||||
"operating_system",
|
||||
"plat",
|
||||
"goarch",
|
||||
"hostname",
|
||||
"public_ip",
|
||||
"total_ram",
|
||||
"disks",
|
||||
"boot_time",
|
||||
"logged_in_username",
|
||||
"last_logged_in_user",
|
||||
"needs_reboot",
|
||||
"choco_installed",
|
||||
"policy",
|
||||
"mesh_node_id",
|
||||
"block_policy_inheritance",
|
||||
"maintenance_mode",
|
||||
"alert_template",
|
||||
"client",
|
||||
"site_name",
|
||||
];
|
||||
for (const elem of toRemove) {
|
||||
delete this.agent[elem];
|
||||
}
|
||||
|
||||
// only send the timezone data if it has changed
|
||||
// this way django will keep the db column as null and inherit from the global setting
|
||||
@@ -503,7 +546,7 @@ export default {
|
||||
else if (day === 0) result += "Sun, ";
|
||||
}
|
||||
|
||||
return result.trimRight(",");
|
||||
return result.trimEnd(",");
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -52,6 +52,15 @@
|
||||
goarch = GOARCH_AMD64;
|
||||
"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="agentOS"
|
||||
val="darwin"
|
||||
label="macOS"
|
||||
@update:model-value="
|
||||
installMethod = 'mac';
|
||||
goarch = GOARCH_AMD64;
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
@@ -105,37 +114,37 @@
|
||||
v-model="goarch"
|
||||
:val="GOARCH_AMD64"
|
||||
label="64 bit"
|
||||
v-show="agentOS === 'windows'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_i386"
|
||||
label="32 bit"
|
||||
v-show="agentOS === 'windows'"
|
||||
v-show="agentOS === 'windows' || agentOS === 'linux'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_AMD64"
|
||||
label="64 bit"
|
||||
v-show="agentOS !== 'windows'"
|
||||
label="Intel 64 bit"
|
||||
v-show="agentOS === 'darwin'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_i386"
|
||||
label="32 bit"
|
||||
v-show="agentOS !== 'windows'"
|
||||
v-show="agentOS !== 'darwin'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_ARM64"
|
||||
label="ARM 64 bit"
|
||||
v-show="agentOS !== 'windows'"
|
||||
v-show="agentOS === 'linux'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_ARM64"
|
||||
label="Apple Silicon (M1, M2)"
|
||||
v-show="agentOS === 'darwin'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="goarch"
|
||||
:val="GOARCH_ARM32"
|
||||
label="ARM 32 bit (Rasp Pi)"
|
||||
v-show="agentOS !== 'windows'"
|
||||
v-show="agentOS === 'linux'"
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
@@ -249,10 +258,7 @@ export default {
|
||||
.toLowerCase()
|
||||
.replace(/([^a-zA-Z0-9]+)/g, "");
|
||||
|
||||
const fileName =
|
||||
this.goarch === GOARCH_AMD64
|
||||
? `rmm-${clientStripped}-${siteStripped}-${this.agenttype}.exe`
|
||||
: `rmm-${clientStripped}-${siteStripped}-${this.agenttype}-x86.exe`;
|
||||
const fileName = `trmm-${clientStripped}-${siteStripped}-${this.agenttype}-${this.goarch}.exe`;
|
||||
|
||||
const data = {
|
||||
installMethod: this.installMethod,
|
||||
@@ -269,12 +275,13 @@ export default {
|
||||
plat: this.agentOS,
|
||||
};
|
||||
|
||||
if (this.installMethod === "manual") {
|
||||
if (this.installMethod === "manual" || this.installMethod === "mac") {
|
||||
this.$axios.post("/agents/installer/", data).then((r) => {
|
||||
this.info = {
|
||||
expires: this.expires,
|
||||
data: r.data,
|
||||
goarch: this.goarch,
|
||||
plat: this.agentOS,
|
||||
};
|
||||
this.showAgentDownload = true;
|
||||
});
|
||||
@@ -346,6 +353,9 @@ export default {
|
||||
case "bash":
|
||||
text = "Download linux install script";
|
||||
break;
|
||||
case "mac":
|
||||
text = "Show installation instructions";
|
||||
break;
|
||||
}
|
||||
|
||||
return text;
|
||||
|
||||
@@ -129,37 +129,37 @@
|
||||
<div class="q-gutter-sm">
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="1"
|
||||
:val="0"
|
||||
label="Monday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="2"
|
||||
:val="1"
|
||||
label="Tuesday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="3"
|
||||
:val="2"
|
||||
label="Wednesday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="4"
|
||||
:val="3"
|
||||
label="Thursday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="5"
|
||||
:val="4"
|
||||
label="Friday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="6"
|
||||
:val="5"
|
||||
label="Saturday"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="winupdatepolicy.run_time_days"
|
||||
:val="0"
|
||||
:val="6"
|
||||
label="Sunday"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -63,11 +63,14 @@ export default {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
await scheduleAgentReboot(props.agent.agent_id, state.value);
|
||||
const ret = await scheduleAgentReboot(
|
||||
props.agent.agent_id,
|
||||
state.value
|
||||
);
|
||||
$q.dialog({
|
||||
title: "Reboot pending",
|
||||
style: "width: 40vw",
|
||||
message: `A reboot has been scheduled for <strong>${state.value.datetime}</strong> on ${props.agent.hostname}.
|
||||
message: `A reboot has been scheduled for <strong>${ret.time}</strong> on ${props.agent.hostname}.
|
||||
<br />It can be cancelled from the Pending Actions menu until the scheduled time.`,
|
||||
html: true,
|
||||
}).onDismiss(onDialogOK);
|
||||
|
||||
@@ -128,6 +128,11 @@
|
||||
/>
|
||||
<q-checkbox v-model="state.save_all_output" label="Save all output" />
|
||||
</q-card-section>
|
||||
<q-card-section v-if="agent.plat === 'windows'">
|
||||
<q-checkbox v-model="state.run_as_user" label="Run As User">
|
||||
<q-tooltip>{{ runAsUserToolTip }}</q-tooltip>
|
||||
</q-checkbox>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input
|
||||
v-model.number="state.timeout"
|
||||
@@ -173,6 +178,7 @@ import { useScriptDropdown } from "@/composables/scripts";
|
||||
import { useCustomFieldDropdown } from "@/composables/core";
|
||||
import { runScript } from "@/api/agents";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
import { runAsUserToolTip } from "@/constants/constants";
|
||||
import {
|
||||
formatScriptSyntax,
|
||||
removeExtraOptionCategories,
|
||||
@@ -220,6 +226,7 @@ export default {
|
||||
script,
|
||||
args: defaultArgs,
|
||||
timeout: defaultTimeout,
|
||||
run_as_user: false,
|
||||
});
|
||||
|
||||
const ret = ref(null);
|
||||
@@ -273,6 +280,7 @@ export default {
|
||||
|
||||
// non-reactive data
|
||||
outputOptions,
|
||||
runAsUserToolTip,
|
||||
|
||||
//methods
|
||||
formatScriptSyntax,
|
||||
|
||||
@@ -51,6 +51,11 @@
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="agent.plat === 'windows'">
|
||||
<q-checkbox v-model="state.run_as_user" label="Run As User">
|
||||
<q-tooltip>{{ runAsUserToolTip }}</q-tooltip>
|
||||
</q-checkbox>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="state.shell === 'custom'">
|
||||
<q-input
|
||||
v-model="state.custom_shell"
|
||||
@@ -117,6 +122,7 @@ import { ref } from "vue";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import { sendAgentCommand } from "@/api/agents";
|
||||
import { cmdPlaceholder } from "@/composables/agents";
|
||||
import { runAsUserToolTip } from "@/constants/constants";
|
||||
|
||||
export default {
|
||||
name: "SendCommand",
|
||||
@@ -134,6 +140,7 @@ export default {
|
||||
cmd: null,
|
||||
timeout: 30,
|
||||
custom_shell: null,
|
||||
run_as_user: false,
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -156,6 +163,9 @@ export default {
|
||||
loading,
|
||||
ret,
|
||||
|
||||
// non reactivete data
|
||||
runAsUserToolTip,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
cmdPlaceholder,
|
||||
|
||||
@@ -12,11 +12,15 @@
|
||||
color="positive"
|
||||
class="full-width"
|
||||
@click="doCodeSign"
|
||||
:loading="loading"
|
||||
>
|
||||
<q-tooltip
|
||||
>Force all existing agents to be updated to the code-signed
|
||||
version</q-tooltip
|
||||
>
|
||||
<template v-slot:loading>
|
||||
<q-spinner-facebook />
|
||||
</template>
|
||||
</q-btn>
|
||||
</q-card-section>
|
||||
<q-form @submit.prevent="editToken">
|
||||
@@ -33,56 +37,92 @@
|
||||
</q-card-section>
|
||||
<q-card-section class="row items-center">
|
||||
<q-btn label="Save" color="primary" type="submit" />
|
||||
<q-space />
|
||||
<q-btn label="Delete" color="negative" @click="confirmDelete" />
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixins from "@/mixins/mixins";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useQuasar } from "quasar";
|
||||
import axios from "axios";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
|
||||
const endpoint = "/core/codesign/";
|
||||
|
||||
export default {
|
||||
name: "CodeSign",
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
settings: {
|
||||
token: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getToken() {
|
||||
this.$axios.get("/core/codesign/").then((r) => {
|
||||
this.settings = r.data;
|
||||
setup() {
|
||||
const $q = useQuasar();
|
||||
const settings = ref({ token: "" });
|
||||
const loading = ref(false);
|
||||
|
||||
async function getToken() {
|
||||
try {
|
||||
const { data } = await axios.get(endpoint);
|
||||
settings.value = data;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteToken() {
|
||||
try {
|
||||
await axios.delete(endpoint);
|
||||
notifySuccess("Token was deleted!");
|
||||
await getToken();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
$q.dialog({
|
||||
title: "Delete token?",
|
||||
cancel: true,
|
||||
persistent: true,
|
||||
}).onOk(() => {
|
||||
deleteToken();
|
||||
});
|
||||
},
|
||||
editToken() {
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.patch("/core/codesign/", this.settings)
|
||||
.then((r) => {
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess(r.data);
|
||||
})
|
||||
.catch(() => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
},
|
||||
doCodeSign() {
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.post("/core/codesign/")
|
||||
.then((r) => {
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess(r.data);
|
||||
})
|
||||
.catch(() => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getToken();
|
||||
}
|
||||
|
||||
async function doCodeSign() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await axios.post(endpoint);
|
||||
loading.value = false;
|
||||
notifySuccess(data);
|
||||
} catch (e) {
|
||||
loading.value = false;
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function editToken() {
|
||||
$q.loading.show();
|
||||
try {
|
||||
const { data } = await axios.patch(endpoint, settings.value);
|
||||
$q.loading.hide();
|
||||
notifySuccess(data);
|
||||
} catch (e) {
|
||||
$q.loading.hide();
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getToken();
|
||||
});
|
||||
|
||||
return {
|
||||
settings,
|
||||
loading,
|
||||
confirmDelete,
|
||||
doCodeSign,
|
||||
editToken,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -128,6 +128,18 @@
|
||||
:rules="[(val) => val >= 5 || 'Minimum is 5']"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="formScript.run_as_user"
|
||||
label="Run As User (Windows only)"
|
||||
>
|
||||
<q-tooltip
|
||||
>Setting this value on the script model will always override any
|
||||
'Run As User' checkboxes in the UI and force this script to
|
||||
always be run in the context of the logged in user. If no user
|
||||
is logged in, the script will not run and an error will be
|
||||
returned. Not supported on Windows Server.
|
||||
</q-tooltip>
|
||||
</q-checkbox>
|
||||
<q-input
|
||||
label="Syntax"
|
||||
type="textarea"
|
||||
@@ -253,6 +265,7 @@ export default {
|
||||
default_timeout: 90,
|
||||
args: [],
|
||||
script_body: "",
|
||||
run_as_user: false,
|
||||
});
|
||||
|
||||
if (props.clone) script.value.name = `(Copy) ${script.value.name}`;
|
||||
|
||||
@@ -44,6 +44,7 @@ export default {
|
||||
timeout: props.script.default_timeout,
|
||||
args: props.script.args,
|
||||
shell: props.script.shell,
|
||||
run_as_user: props.script.run_as_user,
|
||||
};
|
||||
try {
|
||||
ret.value = await testScript(props.agent, data);
|
||||
|
||||
@@ -991,10 +991,16 @@ export default {
|
||||
: [];
|
||||
|
||||
// remove milliseconds and Z to work with native date input
|
||||
task.value.run_time_date = formatDateInputField(task.value.run_time_date);
|
||||
task.value.run_time_date = formatDateInputField(
|
||||
task.value.run_time_date,
|
||||
true
|
||||
);
|
||||
|
||||
if (task.value.expire_date)
|
||||
task.value.expire_date = formatDateInputField(task.value.expire_date);
|
||||
task.value.expire_date = formatDateInputField(
|
||||
task.value.expire_date,
|
||||
true
|
||||
);
|
||||
|
||||
// set task type if monthlydow is being used
|
||||
if (task.value.task_type === "monthlydow") {
|
||||
|
||||
@@ -37,4 +37,5 @@ export function cmdPlaceholder(shell) {
|
||||
export const agentPlatformOptions = [
|
||||
{ value: "windows", label: "Windows" },
|
||||
{ value: "linux", label: "Linux" },
|
||||
{ value: "darwin", label: "macOS" },
|
||||
];
|
||||
|
||||
@@ -3,4 +3,13 @@ const GOARCH_i386 = "386";
|
||||
const GOARCH_ARM64 = "arm64";
|
||||
const GOARCH_ARM32 = "arm";
|
||||
|
||||
export { GOARCH_AMD64, GOARCH_i386, GOARCH_ARM64, GOARCH_ARM32 };
|
||||
const runAsUserToolTip =
|
||||
"Run in the context of the logged in user. If no user is logged in, the script will not run and an error will be returned. Not supported on Windows Server.";
|
||||
|
||||
export {
|
||||
GOARCH_AMD64,
|
||||
GOARCH_i386,
|
||||
GOARCH_ARM64,
|
||||
GOARCH_ARM32,
|
||||
runAsUserToolTip,
|
||||
};
|
||||
|
||||
@@ -35,12 +35,7 @@
|
||||
Tactical RMM<span class="text-overline q-ml-sm"
|
||||
>v{{ currentTRMMVersion }}</span
|
||||
>
|
||||
<span
|
||||
class="text-overline q-ml-md"
|
||||
v-if="
|
||||
latestTRMMVersion !== 'error' &&
|
||||
currentTRMMVersion !== latestTRMMVersion
|
||||
"
|
||||
<span class="text-overline q-ml-md" v-if="updateAvailable()"
|
||||
><q-badge color="warning"
|
||||
><a :href="latestReleaseURL" target="_blank"
|
||||
>v{{ latestTRMMVersion }} available</a
|
||||
@@ -171,6 +166,7 @@ export default {
|
||||
const latestTRMMVersion = computed(() => store.state.latestTRMMVersion);
|
||||
const needRefresh = computed(() => store.state.needrefresh);
|
||||
const user = computed(() => store.state.username);
|
||||
const hosted = computed(() => store.state.hosted);
|
||||
|
||||
const latestReleaseURL = computed(() => {
|
||||
return latestTRMMVersion.value
|
||||
@@ -233,6 +229,11 @@ export default {
|
||||
}, 60 * 5 * 1000);
|
||||
}
|
||||
|
||||
function updateAvailable() {
|
||||
if (latestTRMMVersion.value === "error" || hosted.value) return false;
|
||||
return currentTRMMVersion.value !== latestTRMMVersion.value;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setupWS();
|
||||
store.dispatch("getDashInfo");
|
||||
@@ -261,6 +262,7 @@ export default {
|
||||
|
||||
// methods
|
||||
showUserPreferences,
|
||||
updateAvailable,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -285,7 +285,7 @@ export function formatDateInputField(isoDateString, noTimezone = false) {
|
||||
if (noTimezone) {
|
||||
isoDateString = isoDateString.replace("Z", "");
|
||||
}
|
||||
return date.formatDate(isoDateString, "YYYY-MM-DDTHH:mm:ss");
|
||||
return date.formatDate(isoDateString, "YYYY-MM-DDTHH:mm");
|
||||
}
|
||||
|
||||
// converts a local date string "YYYY-MM-DDTHH:mm:ss" to an iso date string with the local timezone
|
||||
|
||||
Reference in New Issue
Block a user