Compare commits
67 Commits
v0.100.0-d
...
v0.101.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f24c6a7a80 | ||
|
|
d87861c212 | ||
|
|
5f56e7017b | ||
|
|
9c033c1c90 | ||
|
|
ba14ed348e | ||
|
|
99490bf859 | ||
|
|
7e25db6622 | ||
|
|
78636c436f | ||
|
|
72cdeeaa6a | ||
|
|
d37122386f | ||
|
|
17d960fca9 | ||
|
|
d2e0b8ad9b | ||
|
|
1eca4d605b | ||
|
|
776c27ec26 | ||
|
|
41c61ce152 | ||
|
|
8e9de8b6b6 | ||
|
|
4cf5f7a3cb | ||
|
|
9729492d1c | ||
|
|
52ee98f6f8 | ||
|
|
d6da8b4a96 | ||
|
|
9264cf4044 | ||
|
|
3a45c2a309 | ||
|
|
59de35c698 | ||
|
|
d270b877c9 | ||
|
|
5b8ac2c809 | ||
|
|
83d0ff1c0a | ||
|
|
8a6ec6ceab | ||
|
|
93dbc74e33 | ||
|
|
5f2add48a9 | ||
|
|
b7369875af | ||
|
|
2eb6580fed | ||
|
|
fd8b2a1d98 | ||
|
|
9f85fbb330 | ||
|
|
ee9715a4cf | ||
|
|
76f330fb9c | ||
|
|
f518043d8d | ||
|
|
e67c1ff331 | ||
|
|
137a5648ce | ||
|
|
cc2335558d | ||
|
|
a944bc50d1 | ||
|
|
0a4b00298d | ||
|
|
1eaed284a3 | ||
|
|
b278e0bed4 | ||
|
|
6ee3df7e4e | ||
|
|
a8a171ba2c | ||
|
|
7ee87da3b6 | ||
|
|
7bce958633 | ||
|
|
24a63f477e | ||
|
|
57963f6d1a | ||
|
|
c9d76bdddc | ||
|
|
c279a44679 | ||
|
|
974ba53926 | ||
|
|
021fbbe14f | ||
|
|
bbd74c34b7 | ||
|
|
dfef0a5b4b | ||
|
|
ee687bf559 | ||
|
|
627d0e91f1 | ||
|
|
bffaba1f60 | ||
|
|
fdf28539cb | ||
|
|
ac1246c81c | ||
|
|
4feed0c65c | ||
|
|
197f2f237b | ||
|
|
0dc0d010bd | ||
|
|
b17aff8c6f | ||
|
|
63147ce116 | ||
|
|
ba9f93962a | ||
|
|
ddeb6293a1 |
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}
|
||||
@@ -2,4 +2,5 @@ PROD_URL = "https://api.example.com"
|
||||
DEV_URL = "https://api.example.com"
|
||||
APP_URL = "https://app.example.com"
|
||||
DEV_HOST = 0.0.0.0
|
||||
DEV_PORT = 80
|
||||
DEV_PORT = 80
|
||||
USE_HTTPS = false
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,3 +33,4 @@ yarn-error.log*
|
||||
*.sln
|
||||
|
||||
.env
|
||||
/public/env-config.js
|
||||
|
||||
2282
package-lock.json
generated
2282
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
56
package.json
56
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.100.0-dev",
|
||||
"version": "0.101.9",
|
||||
"private": true,
|
||||
"productName": "Tactical RMM",
|
||||
"scripts": {
|
||||
@@ -10,47 +10,31 @@
|
||||
"format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "1.14.0",
|
||||
"apexcharts": "3.35.2",
|
||||
"@quasar/extras": "1.15.6",
|
||||
"apexcharts": "3.35.5",
|
||||
"axios": "0.27.2",
|
||||
"dotenv": "16.0.0",
|
||||
"dotenv": "16.0.3",
|
||||
"qrcode.vue": "3.3.3",
|
||||
"quasar": "2.7.1",
|
||||
"vue": "3.2.31",
|
||||
"quasar": "2.10.2",
|
||||
"vue": "3.2.45",
|
||||
"vue3-ace-editor": "2.2.2",
|
||||
"vue3-apexcharts": "1.4.1",
|
||||
"vuedraggable": "4.1.0",
|
||||
"vue-router": "4.0.15",
|
||||
"vuex": "4.0.2"
|
||||
"vue-router": "4.1.6",
|
||||
"vuex": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/cli": "^1.3.2",
|
||||
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
|
||||
"@quasar/app-vite": "^1.0.1",
|
||||
"@types/node": "^12.20.21",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-vue": "^8.5.0",
|
||||
"prettier": "^2.5.1",
|
||||
"typescript": "^4.6.4"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 3 Chrome versions",
|
||||
"last 3 Firefox versions",
|
||||
"last 3 Edge versions",
|
||||
"last 2 Safari versions",
|
||||
"last 3 Android versions",
|
||||
"last 3 ChromeAndroid versions",
|
||||
"last 3 FirefoxAndroid versions",
|
||||
"last 2 iOS versions",
|
||||
"last 3 Opera versions"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.22.1",
|
||||
"npm": ">= 6.13.4",
|
||||
"yarn": ">= 1.21.1"
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.3",
|
||||
"@quasar/app-vite": "^1.1.3",
|
||||
"@types/node": "^18.11.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"autoprefixer": "10.4.13",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-vue": "8.7.1",
|
||||
"prettier": "2.8.0",
|
||||
"typescript": "4.9.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
||||
build: {
|
||||
target: {
|
||||
browser: ["es2019", "edge88", "firefox78", "chrome87", "safari13.1"],
|
||||
browser: ["es2021"],
|
||||
node: "node16",
|
||||
},
|
||||
|
||||
@@ -86,7 +86,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||
devServer: {
|
||||
https: false,
|
||||
https: process.env.USE_HTTPS === "true",
|
||||
open: false, // opens browser window automatically
|
||||
host: process.env.DEV_HOST,
|
||||
port: process.env.DEV_PORT,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -142,6 +142,10 @@
|
||||
<q-item clickable v-close-popup @click="clearCache">
|
||||
<q-item-section>Clear Cache</q-item-section>
|
||||
</q-item>
|
||||
<!-- bulk recover agents -->
|
||||
<q-item clickable v-close-popup @click="bulkRecoverAgents">
|
||||
<q-item-section>Recover All Agents</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
@@ -262,6 +266,20 @@ export default {
|
||||
.get("/core/clearcache/")
|
||||
.then((r) => this.notifySuccess(r.data));
|
||||
},
|
||||
bulkRecoverAgents() {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: "Bulk Recover All Agents?",
|
||||
message:
|
||||
"This will restart the Tactical and Mesh Agent services on all agents",
|
||||
cancel: true,
|
||||
})
|
||||
.onOk(() => {
|
||||
this.$axios
|
||||
.get("/agents/bulkrecovery/")
|
||||
.then((r) => this.notifySuccess(r.data));
|
||||
});
|
||||
},
|
||||
openHelp(mode) {
|
||||
let url;
|
||||
switch (mode) {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -39,6 +39,19 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-select
|
||||
dense
|
||||
:label="envVarsLabel"
|
||||
filled
|
||||
v-model="state.env_vars"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
label="Informational return codes (press Enter after typing each code)"
|
||||
@@ -115,6 +128,7 @@ import { useDialogPluginComponent } from "quasar";
|
||||
import { useCheckModal } from "@/composables/checks";
|
||||
import { useScriptDropdown } from "@/composables/scripts";
|
||||
import { validateRetcode } from "@/utils/validation";
|
||||
import { envVarsLabel } from "@/constants/constants"
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
@@ -132,7 +146,7 @@ export default {
|
||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
// setup script dropdown
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs } =
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars } =
|
||||
useScriptDropdown(props.check ? props.check.script : undefined, {
|
||||
onMount: true,
|
||||
});
|
||||
@@ -145,6 +159,7 @@ export default {
|
||||
...props.parent,
|
||||
script,
|
||||
script_args: defaultArgs,
|
||||
env_vars: defaultEnvVars,
|
||||
timeout: defaultTimeout,
|
||||
check_type: "script",
|
||||
fails_b4_alert: 1,
|
||||
@@ -163,6 +178,7 @@ export default {
|
||||
failOptions,
|
||||
scriptOptions,
|
||||
severityOptions,
|
||||
envVarsLabel,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
|
||||
@@ -61,10 +61,7 @@
|
||||
<q-td key="client" :props="props">{{ props.row.client_name }}</q-td>
|
||||
<q-td key="site" :props="props">{{ props.row.site_name }}</q-td>
|
||||
<q-td key="mon_type" :props="props">{{ props.row.mon_type }}</q-td>
|
||||
<q-td key="arch" :props="props"
|
||||
><span v-if="props.row.arch === '64'">64 bit</span
|
||||
><span v-else>32 bit</span></q-td
|
||||
>
|
||||
<q-td key="goarch" :props="props">{{ props.row.goarch }}</q-td>
|
||||
<q-td key="expiry" :props="props">{{
|
||||
formatDate(props.row.expiry)
|
||||
}}</q-td>
|
||||
@@ -130,7 +127,13 @@ const columns = [
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{ name: "arch", label: "Arch", field: "arch", align: "left", sortable: true },
|
||||
{
|
||||
name: "goarch",
|
||||
label: "Arch",
|
||||
field: "goarch",
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: "expiry",
|
||||
label: "Expiry",
|
||||
|
||||
@@ -54,9 +54,9 @@
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="q-pl-sm">OS</div>
|
||||
<q-radio v-model="state.arch" val="64" label="64 bit" />
|
||||
<q-radio v-model="state.arch" val="32" label="32 bit" />
|
||||
<div class="q-pl-sm">Arch</div>
|
||||
<q-radio v-model="state.goarch" :val="GOARCH_AMD64" label="64 bit" />
|
||||
<q-radio v-model="state.goarch" :val="GOARCH_i386" label="32 bit" />
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn dense flat label="Cancel" v-close-popup />
|
||||
@@ -84,6 +84,7 @@ import {
|
||||
formatDateInputField,
|
||||
formatDateStringwithTimezone,
|
||||
} from "@/utils/format";
|
||||
import { GOARCH_AMD64, GOARCH_i386 } from "@/constants/constants";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
@@ -108,7 +109,7 @@ export default {
|
||||
power: false,
|
||||
rdp: false,
|
||||
ping: false,
|
||||
arch: "64",
|
||||
goarch: GOARCH_AMD64,
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -145,6 +146,10 @@ export default {
|
||||
// quasar dialog
|
||||
dialogRef,
|
||||
onDialogHide,
|
||||
|
||||
// constants
|
||||
GOARCH_AMD64,
|
||||
GOARCH_i386,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -208,7 +208,7 @@ export default {
|
||||
}
|
||||
|
||||
// component lifecycle hooks
|
||||
onMounted(getAPIKeys());
|
||||
onMounted(getAPIKeys);
|
||||
return {
|
||||
// reactive data
|
||||
keys,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -102,6 +102,18 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="mode === 'script'" class="q-pt-none">
|
||||
<tactical-dropdown
|
||||
v-model="state.env_vars"
|
||||
:label="envVarsLabel"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section v-if="mode === 'command'">
|
||||
<p>Shell</p>
|
||||
@@ -135,6 +147,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 +220,7 @@ import { runBulkAction } from "@/api/agents";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
import { cmdPlaceholder } from "@/composables/agents";
|
||||
import { removeExtraOptionCategories } from "@/utils/format";
|
||||
import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
@@ -217,6 +235,7 @@ const monTypeOptions = [
|
||||
const osTypeOptions = [
|
||||
{ label: "Windows", value: "windows" },
|
||||
{ label: "Linux", value: "linux" },
|
||||
{ label: "macOS", value: "darwin" },
|
||||
{ label: "All", value: "all" },
|
||||
];
|
||||
|
||||
@@ -277,6 +296,7 @@ export default {
|
||||
scriptOptions,
|
||||
defaultTimeout,
|
||||
defaultArgs,
|
||||
defaultEnvVars,
|
||||
getScriptOptions,
|
||||
} = useScriptDropdown();
|
||||
const { agents, agentOptions, getAgentOptions } = useAgentDropdown();
|
||||
@@ -300,6 +320,8 @@ export default {
|
||||
script,
|
||||
timeout: defaultTimeout,
|
||||
args: defaultArgs,
|
||||
env_vars: defaultEnvVars,
|
||||
run_as_user: false,
|
||||
});
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -316,6 +338,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 +360,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 +417,8 @@ export default {
|
||||
osTypeOptions,
|
||||
targetOptions,
|
||||
patchModeOptions,
|
||||
runAsUserToolTip,
|
||||
envVarsLabel,
|
||||
|
||||
//computed
|
||||
modalTitle,
|
||||
@@ -394,6 +426,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() {
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
label="Windows"
|
||||
@update:model-value="
|
||||
installMethod = 'exe';
|
||||
arch = '64';
|
||||
goarch = GOARCH_AMD64;
|
||||
"
|
||||
/>
|
||||
<q-radio
|
||||
@@ -48,8 +48,17 @@
|
||||
val="linux"
|
||||
label="Linux"
|
||||
@update:model-value="
|
||||
installMethod = 'linux';
|
||||
arch = 'amd64';
|
||||
installMethod = 'bash';
|
||||
goarch = GOARCH_AMD64;
|
||||
"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="agentOS"
|
||||
val="darwin"
|
||||
label="macOS"
|
||||
@update:model-value="
|
||||
installMethod = 'mac';
|
||||
goarch = GOARCH_AMD64;
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
@@ -102,40 +111,40 @@
|
||||
Arch
|
||||
<div class="q-gutter-sm">
|
||||
<q-radio
|
||||
v-model="arch"
|
||||
val="64"
|
||||
v-model="goarch"
|
||||
:val="GOARCH_AMD64"
|
||||
label="64 bit"
|
||||
v-show="agentOS === 'windows'"
|
||||
v-show="agentOS === 'windows' || agentOS === 'linux'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="arch"
|
||||
val="32"
|
||||
v-model="goarch"
|
||||
:val="GOARCH_AMD64"
|
||||
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="arch"
|
||||
val="amd64"
|
||||
label="64 bit"
|
||||
v-show="agentOS !== 'windows'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="arch"
|
||||
val="386"
|
||||
label="32 bit"
|
||||
v-show="agentOS !== 'windows'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="arch"
|
||||
val="arm64"
|
||||
v-model="goarch"
|
||||
:val="GOARCH_ARM64"
|
||||
label="ARM 64 bit"
|
||||
v-show="agentOS !== 'windows'"
|
||||
v-show="agentOS === 'linux'"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="arch"
|
||||
val="arm"
|
||||
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>
|
||||
@@ -177,6 +186,12 @@
|
||||
import mixins from "@/mixins/mixins";
|
||||
import AgentDownload from "@/components/modals/agents/AgentDownload.vue";
|
||||
import { getBaseUrl } from "@/boot/axios";
|
||||
import {
|
||||
GOARCH_AMD64,
|
||||
GOARCH_i386,
|
||||
GOARCH_ARM64,
|
||||
GOARCH_ARM32,
|
||||
} from "@/constants/constants";
|
||||
|
||||
export default {
|
||||
name: "InstallAgent",
|
||||
@@ -187,6 +202,10 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
GOARCH_AMD64: GOARCH_AMD64,
|
||||
GOARCH_i386: GOARCH_i386,
|
||||
GOARCH_ARM64: GOARCH_ARM64,
|
||||
GOARCH_ARM32: GOARCH_ARM32,
|
||||
client_options: [],
|
||||
client: null,
|
||||
site: null,
|
||||
@@ -198,7 +217,7 @@ export default {
|
||||
showAgentDownload: false,
|
||||
info: {},
|
||||
installMethod: "exe",
|
||||
arch: "64",
|
||||
goarch: GOARCH_AMD64,
|
||||
agentOS: "windows",
|
||||
};
|
||||
},
|
||||
@@ -239,10 +258,7 @@ export default {
|
||||
.toLowerCase()
|
||||
.replace(/([^a-zA-Z0-9]+)/g, "");
|
||||
|
||||
const fileName =
|
||||
this.arch === "64"
|
||||
? `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,
|
||||
@@ -253,18 +269,19 @@ export default {
|
||||
power: this.power ? 1 : 0,
|
||||
rdp: this.rdp ? 1 : 0,
|
||||
ping: this.ping ? 1 : 0,
|
||||
arch: this.arch,
|
||||
goarch: this.goarch,
|
||||
api,
|
||||
fileName,
|
||||
os: this.agentOS,
|
||||
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,
|
||||
arch: this.arch,
|
||||
goarch: this.goarch,
|
||||
plat: this.agentOS,
|
||||
};
|
||||
this.showAgentDownload = true;
|
||||
});
|
||||
@@ -289,7 +306,7 @@ export default {
|
||||
});
|
||||
} else if (
|
||||
this.installMethod === "powershell" ||
|
||||
this.installMethod === "linux"
|
||||
this.installMethod === "bash"
|
||||
) {
|
||||
this.$q.loading.show();
|
||||
let ext = this.installMethod === "powershell" ? "ps1" : "sh";
|
||||
@@ -333,9 +350,12 @@ export default {
|
||||
case "manual":
|
||||
text = "Show manual installation instructions";
|
||||
break;
|
||||
case "linux":
|
||||
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);
|
||||
|
||||
@@ -77,6 +77,18 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
v-model="state.env_vars"
|
||||
:label="envVarsLabel"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-option-group
|
||||
v-model="state.output"
|
||||
@@ -128,6 +140,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 +190,7 @@ import { useScriptDropdown } from "@/composables/scripts";
|
||||
import { useCustomFieldDropdown } from "@/composables/core";
|
||||
import { runScript } from "@/api/agents";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
|
||||
import {
|
||||
formatScriptSyntax,
|
||||
removeExtraOptionCategories,
|
||||
@@ -203,7 +221,7 @@ export default {
|
||||
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||
|
||||
// setup dropdowns
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs, syntax, link } =
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars, syntax, link } =
|
||||
useScriptDropdown(props.script, {
|
||||
onMount: true,
|
||||
filterByPlatform: props.agent.plat,
|
||||
@@ -219,7 +237,9 @@ export default {
|
||||
save_all_output: false,
|
||||
script,
|
||||
args: defaultArgs,
|
||||
env_vars: defaultEnvVars,
|
||||
timeout: defaultTimeout,
|
||||
run_as_user: false,
|
||||
});
|
||||
|
||||
const ret = ref(null);
|
||||
@@ -273,6 +293,8 @@ export default {
|
||||
|
||||
// non-reactive data
|
||||
outputOptions,
|
||||
runAsUserToolTip,
|
||||
envVarsLabel,
|
||||
|
||||
//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,
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<q-separator />
|
||||
<q-banner class="bg-warning">
|
||||
<q-banner class="bg-info">
|
||||
<template v-slot:avatar>
|
||||
<q-icon name="info" />
|
||||
</template>
|
||||
Agents will now automatically self update, this tool is no longer needed.
|
||||
Agents will automatically self update at 35 min past the hour, every hour.
|
||||
Use this tool to manually trigger an agent update cycle.
|
||||
</q-banner>
|
||||
<q-card-section>
|
||||
Select Version
|
||||
|
||||
211
src/components/modals/agents/WebsocketSendCommand.vue
Normal file
211
src/components/modals/agents/WebsocketSendCommand.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<q-dialog
|
||||
ref="dialogRef"
|
||||
@hide="onDialogHide"
|
||||
persistent
|
||||
@keydown.esc="onDialogHide"
|
||||
>
|
||||
<q-card
|
||||
class="q-dialog-plugin"
|
||||
:style="{ 'min-width': !ret ? '40vw' : '70vw' }"
|
||||
>
|
||||
<q-bar>
|
||||
Send command on {{ agent.hostname }}
|
||||
<q-space />
|
||||
<q-chip v-if="!wsConnected" color="red" text-color="white" icon="error"
|
||||
>Websocket diconnected!</q-chip
|
||||
>
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<q-form @submit="submit">
|
||||
<q-card-section>
|
||||
<p>Shell</p>
|
||||
<div class="q-gutter-sm">
|
||||
<q-radio
|
||||
v-if="agent.plat !== 'windows'"
|
||||
dense
|
||||
v-model="state.shell"
|
||||
val="/bin/bash"
|
||||
label="Bash"
|
||||
@update:model-value="state.custom_shell = null"
|
||||
/>
|
||||
<q-radio
|
||||
v-if="agent.plat !== 'windows'"
|
||||
dense
|
||||
v-model="state.shell"
|
||||
val="custom"
|
||||
label="Custom"
|
||||
/>
|
||||
<q-radio
|
||||
v-if="agent.plat === 'windows'"
|
||||
dense
|
||||
v-model="state.shell"
|
||||
val="cmd"
|
||||
label="CMD"
|
||||
/>
|
||||
<q-radio
|
||||
v-if="agent.plat === 'windows'"
|
||||
dense
|
||||
v-model="state.shell"
|
||||
val="powershell"
|
||||
label="Powershell"
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="state.shell === 'custom'">
|
||||
<q-input
|
||||
v-model="state.custom_shell"
|
||||
outlined
|
||||
label="Custom shell"
|
||||
stack-label
|
||||
placeholder="/usr/bin/python3"
|
||||
:rules="[(val) => !!val || '*Required']"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input
|
||||
v-model.number="state.timeout"
|
||||
dense
|
||||
outlined
|
||||
type="number"
|
||||
style="max-width: 150px"
|
||||
label="Timeout (seconds)"
|
||||
stack-label
|
||||
:rules="[
|
||||
(val) => !!val || '*Required',
|
||||
(val) => val >= 10 || 'Minimum is 10 seconds',
|
||||
(val) => val <= 3600 || 'Maximum is 3600 seconds',
|
||||
]"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input
|
||||
v-model="state.cmd"
|
||||
outlined
|
||||
label="Command"
|
||||
stack-label
|
||||
:placeholder="cmdPlaceholder(state.shell)"
|
||||
:rules="[(val) => !!val || '*Required']"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat dense push label="Cancel" v-close-popup />
|
||||
<q-btn
|
||||
:loading="loading"
|
||||
:disable="!wsConnected"
|
||||
flat
|
||||
dense
|
||||
push
|
||||
label="Send"
|
||||
color="primary"
|
||||
type="submit"
|
||||
>
|
||||
</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section
|
||||
v-if="ret !== null"
|
||||
class="q-pl-md q-pr-md q-pt-none q-ma-none scroll"
|
||||
style="max-height: 50vh"
|
||||
>
|
||||
<pre>{{ ret }}</pre>
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import { cmdPlaceholder } from "@/composables/agents";
|
||||
import { getWSUrl } from "@/websocket/channels";
|
||||
|
||||
export default {
|
||||
name: "SendCommand",
|
||||
emits: [...useDialogPluginComponent.emits],
|
||||
props: {
|
||||
agent: !Object,
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
// setup quasar dialog plugin
|
||||
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||
|
||||
// run command logic
|
||||
const state = ref({
|
||||
shell: props.agent.plat === "windows" ? "cmd" : "/bin/bash",
|
||||
cmd: null,
|
||||
timeout: 30,
|
||||
custom_shell: null,
|
||||
agent_id: props.agent.agent_id,
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const ret = ref(null);
|
||||
|
||||
// websocket
|
||||
const ws = ref(null);
|
||||
const wsConnected = ref(false);
|
||||
|
||||
function setupWS() {
|
||||
const token = computed(() => store.state.token);
|
||||
console.log("Starting send command websocket");
|
||||
let url = getWSUrl("sendcmd", token.value);
|
||||
ws.value = new WebSocket(url);
|
||||
ws.value.onopen = () => {
|
||||
wsConnected.value = true;
|
||||
console.log("Send command websocket connected");
|
||||
};
|
||||
ws.value.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
ret.value = data.ret;
|
||||
loading.value = false;
|
||||
};
|
||||
ws.value.onclose = () => {
|
||||
console.log("Send command websocket disconnected");
|
||||
wsConnected.value = false;
|
||||
};
|
||||
ws.value.onerror = () => {
|
||||
wsConnected.value = false;
|
||||
console.log("Send command websocket error");
|
||||
ws.value.onclose();
|
||||
};
|
||||
}
|
||||
|
||||
function submit() {
|
||||
ret.value = null;
|
||||
loading.value = true;
|
||||
ret.value = ws.value.send(JSON.stringify(state.value));
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setupWS();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.value.close();
|
||||
});
|
||||
|
||||
return {
|
||||
// reactive data
|
||||
state,
|
||||
loading,
|
||||
ret,
|
||||
wsConnected,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
cmdPlaceholder,
|
||||
|
||||
// quasar dialog
|
||||
dialogRef,
|
||||
onDialogHide,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -204,6 +204,20 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
class="q-mb-sm"
|
||||
dense
|
||||
label="Failure action environment vars (press Enter after typing each key=value pair)"
|
||||
filled
|
||||
v-model="template.action_env_vars"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
class="q-mb-sm"
|
||||
label="Failure action timeout (seconds)"
|
||||
@@ -277,6 +291,20 @@
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
class="q-mb-sm"
|
||||
dense
|
||||
label="Resolved action environment vars (press Enter after typing each key=value pair)"
|
||||
filled
|
||||
v-model="template.resolved_action_env_vars"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
class="q-mb-sm"
|
||||
label="Resolved action timeout (seconds)"
|
||||
@@ -696,9 +724,11 @@ export default {
|
||||
is_active: true,
|
||||
action: null,
|
||||
action_args: [],
|
||||
action_env_vars: [],
|
||||
action_timeout: 15,
|
||||
resolved_action: null,
|
||||
resolved_action_args: [],
|
||||
resolved_action_env_vars: [],
|
||||
resolved_action_timeout: 15,
|
||||
email_recipients: [],
|
||||
email_from: "",
|
||||
@@ -762,11 +792,13 @@ export default {
|
||||
(i) => i.value === this.template.action
|
||||
);
|
||||
this.template.action_args = script.args;
|
||||
this.template.action_env_vars = script.env_vars;
|
||||
} else if (type === "resolved") {
|
||||
const script = this.scriptOptions.find(
|
||||
(i) => i.value === this.template.resolved_action
|
||||
);
|
||||
this.template.resolved_action_args = script.args;
|
||||
this.template.resolved_action_env_vars = script.env_vars;
|
||||
}
|
||||
},
|
||||
toggleAddEmail() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -118,6 +118,17 @@
|
||||
new-value-mode="add"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<tactical-dropdown
|
||||
v-model="formScript.env_vars"
|
||||
:label="envVarsLabel"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
@@ -128,6 +139,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.
|
||||
</q-tooltip>
|
||||
</q-checkbox>
|
||||
<q-input
|
||||
label="Syntax"
|
||||
type="textarea"
|
||||
@@ -217,6 +240,7 @@ import "ace-builds/src-noconflict/theme-tomorrow";
|
||||
|
||||
// static data
|
||||
import { shellOptions } from "@/composables/scripts";
|
||||
import { envVarsLabel } from "@/constants/constants";
|
||||
|
||||
export default {
|
||||
name: "ScriptFormModal",
|
||||
@@ -253,6 +277,8 @@ export default {
|
||||
default_timeout: 90,
|
||||
args: [],
|
||||
script_body: "",
|
||||
run_as_user: false,
|
||||
env_vars: [],
|
||||
});
|
||||
|
||||
if (props.clone) script.value.name = `(Copy) ${script.value.name}`;
|
||||
@@ -350,6 +376,7 @@ export default {
|
||||
// non-reactive data
|
||||
shellOptions,
|
||||
agentPlatformOptions,
|
||||
envVarsLabel,
|
||||
|
||||
//computed
|
||||
title,
|
||||
|
||||
@@ -867,7 +867,7 @@ export default {
|
||||
}
|
||||
|
||||
// component life cycle hooks
|
||||
onMounted(getScripts());
|
||||
onMounted(getScripts);
|
||||
|
||||
return {
|
||||
// reactive data
|
||||
|
||||
@@ -93,6 +93,20 @@
|
||||
/>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
v-model="script.env_vars"
|
||||
label="Environment Variables"
|
||||
placeholder="(press Enter after typing each key=value pair)"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section>
|
||||
<q-input
|
||||
label="Default Timeout"
|
||||
|
||||
@@ -44,6 +44,8 @@ export default {
|
||||
timeout: props.script.default_timeout,
|
||||
args: props.script.args,
|
||||
shell: props.script.shell,
|
||||
run_as_user: props.script.run_as_user,
|
||||
env_vars: props.script.env_vars,
|
||||
};
|
||||
try {
|
||||
ret.value = await testScript(props.agent, data);
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
<tactical-dropdown
|
||||
v-if="actionType === 'script'"
|
||||
class="col-4"
|
||||
class="col-3"
|
||||
label="Select script"
|
||||
v-model="script"
|
||||
:options="scriptOptions"
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<q-select
|
||||
v-if="actionType === 'script'"
|
||||
class="col-5"
|
||||
class="col-3"
|
||||
dense
|
||||
label="Script Arguments (press Enter after typing each argument)"
|
||||
filled
|
||||
@@ -126,6 +126,21 @@
|
||||
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"
|
||||
@@ -210,6 +225,9 @@
|
||||
<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>
|
||||
@@ -727,6 +745,7 @@ import { useCheckDropdown } from "@/composables/checks";
|
||||
import { useCustomFieldDropdown } from "@/composables/core";
|
||||
import { notifySuccess, notifyError } from "@/utils/notify";
|
||||
import { validateTimePeriod } from "@/utils/validation";
|
||||
import { envVarsLabel } from "@/constants/constants";
|
||||
import {
|
||||
convertPeriodToSeconds,
|
||||
convertToBitArray,
|
||||
@@ -817,7 +836,7 @@ export default {
|
||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
// setup dropdowns
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs } =
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars } =
|
||||
useScriptDropdown(undefined, {
|
||||
onMount: true,
|
||||
});
|
||||
@@ -914,6 +933,7 @@ export default {
|
||||
script: script.value,
|
||||
timeout: defaultTimeout.value,
|
||||
script_args: defaultArgs.value,
|
||||
env_vars: defaultEnvVars.value,
|
||||
});
|
||||
} else if (actionType.value === "cmd") {
|
||||
task.value.actions.push({
|
||||
@@ -927,6 +947,7 @@ export default {
|
||||
// clear fields after add
|
||||
script.value = null;
|
||||
defaultArgs.value = [];
|
||||
defaultEnvVars.value = [];
|
||||
defaultTimeout.value = 30;
|
||||
command.value = "";
|
||||
}
|
||||
@@ -991,10 +1012,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") {
|
||||
@@ -1083,6 +1110,7 @@ export default {
|
||||
script,
|
||||
defaultTimeout,
|
||||
defaultArgs,
|
||||
defaultEnvVars,
|
||||
actionType,
|
||||
command,
|
||||
shell,
|
||||
@@ -1110,6 +1138,7 @@ export default {
|
||||
monthOptions,
|
||||
taskTypeOptions,
|
||||
taskInstancePolicyOptions,
|
||||
envVarsLabel,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
|
||||
@@ -31,7 +31,7 @@ export function useUserDropdown(onMount = false) {
|
||||
}
|
||||
|
||||
if (onMount) {
|
||||
onMounted(getUserOptions());
|
||||
onMounted(getUserOptions);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -37,4 +37,5 @@ export function cmdPlaceholder(shell) {
|
||||
export const agentPlatformOptions = [
|
||||
{ value: "windows", label: "Windows" },
|
||||
{ value: "linux", label: "Linux" },
|
||||
{ value: "darwin", label: "macOS" },
|
||||
];
|
||||
|
||||
@@ -8,6 +8,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
|
||||
const scriptOptions = ref([]);
|
||||
const defaultTimeout = ref(30);
|
||||
const defaultArgs = ref([]);
|
||||
const defaultEnvVars = ref([]);
|
||||
const script = ref(setScript);
|
||||
const syntax = ref("");
|
||||
const link = ref("");
|
||||
@@ -29,6 +30,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
|
||||
);
|
||||
defaultTimeout.value = tmpScript.timeout;
|
||||
defaultArgs.value = tmpScript.args;
|
||||
defaultEnvVars.value = tmpScript.env_vars,
|
||||
syntax.value = tmpScript.syntax;
|
||||
link.value =
|
||||
tmpScript.script_type === "builtin"
|
||||
@@ -49,6 +51,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
|
||||
scriptOptions,
|
||||
defaultTimeout,
|
||||
defaultArgs,
|
||||
defaultEnvVars,
|
||||
syntax,
|
||||
link,
|
||||
|
||||
|
||||
10
src/constants/constants.ts
Normal file
10
src/constants/constants.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const GOARCH_AMD64 = "amd64";
|
||||
export const GOARCH_i386 = "386";
|
||||
export const GOARCH_ARM64 = "arm64";
|
||||
export const GOARCH_ARM32 = "arm";
|
||||
|
||||
export const runAsUserToolTip =
|
||||
"Run in the context of the logged in user. If no user is logged in, the script will not run and an error will be returned.";
|
||||
|
||||
export const envVarsLabel =
|
||||
"Environment vars (press Enter after typing each key=value pair)"
|
||||
@@ -14,6 +14,22 @@
|
||||
@click="$store.dispatch('reload')"
|
||||
/>
|
||||
</q-banner>
|
||||
<q-banner
|
||||
v-if="!hosted && tokenExpired"
|
||||
inline-actions
|
||||
class="bg-yellow text-black text-center"
|
||||
>
|
||||
<q-icon size="xl" name="warning" />
|
||||
<span><br />Your code signing token is no longer valid.<br/><br/>
|
||||
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/></span>
|
||||
<q-btn
|
||||
color="dark"
|
||||
icon="refresh"
|
||||
label="Refresh"
|
||||
@click="$store.dispatch('reload')"
|
||||
/>
|
||||
</q-banner>
|
||||
<q-toolbar>
|
||||
<q-btn
|
||||
dense
|
||||
@@ -35,12 +51,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
|
||||
@@ -144,7 +155,7 @@ import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
||||
import { useQuasar } from "quasar";
|
||||
import { useStore } from "vuex";
|
||||
import axios from "axios";
|
||||
import { getBaseUrl } from "@/boot/axios";
|
||||
import { getWSUrl } from "@/websocket/channels";
|
||||
|
||||
// ui imports
|
||||
import AlertsIcon from "@/components/AlertsIcon.vue";
|
||||
@@ -171,6 +182,8 @@ 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 tokenExpired = computed(() => store.state.tokenExpired);
|
||||
|
||||
const latestReleaseURL = computed(() => {
|
||||
return latestTRMMVersion.value
|
||||
@@ -184,10 +197,6 @@ export default {
|
||||
}).onOk(() => store.dispatch("getDashInfo"));
|
||||
}
|
||||
|
||||
function wsUrl() {
|
||||
return getBaseUrl().split("://")[1];
|
||||
}
|
||||
|
||||
const serverCount = ref(0);
|
||||
const serverOfflineCount = ref(0);
|
||||
const workstationCount = ref(0);
|
||||
@@ -200,13 +209,8 @@ export default {
|
||||
// when ws is closed causing ws to connect with expired token
|
||||
const token = computed(() => store.state.token);
|
||||
console.log("Starting websocket");
|
||||
const proto =
|
||||
process.env.NODE_ENV === "production" || process.env.DOCKER_BUILD
|
||||
? "wss"
|
||||
: "ws";
|
||||
ws.value = new WebSocket(
|
||||
`${proto}://${wsUrl()}/ws/dashinfo/?access_token=${token.value}`
|
||||
);
|
||||
let url = getWSUrl("dashinfo", token.value);
|
||||
ws.value = new WebSocket(url);
|
||||
ws.value.onopen = () => {
|
||||
console.log("Connected to ws");
|
||||
};
|
||||
@@ -242,6 +246,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");
|
||||
@@ -267,9 +276,12 @@ export default {
|
||||
user,
|
||||
needRefresh,
|
||||
darkMode,
|
||||
hosted,
|
||||
tokenExpired,
|
||||
|
||||
// methods
|
||||
showUserPreferences,
|
||||
updateAvailable,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -193,6 +193,7 @@ export default {
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
env_vars: script.env_vars,
|
||||
});
|
||||
} else if (cat === "Unassigned" && !script.category) {
|
||||
tmp.push({
|
||||
@@ -200,6 +201,7 @@ export default {
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
env_vars: script.env_vars,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ export default function () {
|
||||
agentPlatform: "windows",
|
||||
agentTableLoading: false,
|
||||
needrefresh: false,
|
||||
tokenExpired: false,
|
||||
refreshSummaryTab: false,
|
||||
tableHeight: "300px",
|
||||
tabHeight: "300px",
|
||||
@@ -83,6 +84,9 @@ export default function () {
|
||||
SET_REFRESH_NEEDED(state, action) {
|
||||
state.needrefresh = action;
|
||||
},
|
||||
SET_TOKEN_EXPIRED(state, action) {
|
||||
state.tokenExpired = action;
|
||||
},
|
||||
SET_SPLITTER(state, val) {
|
||||
// top toolbar is 50px. Filebar is 40px and agent filter tabs are 44px
|
||||
state.tableHeight = `${Screen.height - 50 - 40 - 78 - val}px`;
|
||||
@@ -212,6 +216,7 @@ export default function () {
|
||||
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);
|
||||
|
||||
if (data.date_format && data.date_format !== "")
|
||||
context.commit("setDateFormat", data.date_format);
|
||||
|
||||
@@ -68,6 +68,7 @@ export function formatScriptOptions(data) {
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
env_vars: script.env_vars,
|
||||
filename: script.filename,
|
||||
syntax: script.syntax,
|
||||
script_type: script.script_type,
|
||||
@@ -80,6 +81,7 @@ export function formatScriptOptions(data) {
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
env_vars: script.env_vars,
|
||||
filename: script.filename,
|
||||
syntax: script.syntax,
|
||||
script_type: script.script_type,
|
||||
@@ -285,7 +287,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
|
||||
|
||||
11
src/websocket/channels.js
Normal file
11
src/websocket/channels.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { getBaseUrl } from "@/boot/axios";
|
||||
|
||||
export function getWSUrl(path, token) {
|
||||
const url = getBaseUrl().split("://")[1];
|
||||
|
||||
const proto =
|
||||
process.env.NODE_ENV === "production" || process.env.DOCKER_BUILD
|
||||
? "wss"
|
||||
: "ws";
|
||||
return `${proto}://${url}/ws/${path}/?access_token=${token}`;
|
||||
}
|
||||
Reference in New Issue
Block a user