Compare commits
24 Commits
v0.100.0-d
...
v0.100.6-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a4b00298d | ||
|
|
1eaed284a3 | ||
|
|
b278e0bed4 | ||
|
|
6ee3df7e4e | ||
|
|
7ee87da3b6 | ||
|
|
7bce958633 | ||
|
|
57963f6d1a | ||
|
|
c9d76bdddc | ||
|
|
c279a44679 | ||
|
|
974ba53926 | ||
|
|
021fbbe14f | ||
|
|
bbd74c34b7 | ||
|
|
dfef0a5b4b | ||
|
|
ee687bf559 | ||
|
|
627d0e91f1 | ||
|
|
bffaba1f60 | ||
|
|
fdf28539cb | ||
|
|
ac1246c81c | ||
|
|
4feed0c65c | ||
|
|
197f2f237b | ||
|
|
0dc0d010bd | ||
|
|
b17aff8c6f | ||
|
|
63147ce116 | ||
|
|
ba9f93962a |
@@ -3,3 +3,4 @@ DEV_URL = "https://api.example.com"
|
|||||||
APP_URL = "https://app.example.com"
|
APP_URL = "https://app.example.com"
|
||||||
DEV_HOST = 0.0.0.0
|
DEV_HOST = 0.0.0.0
|
||||||
DEV_PORT = 80
|
DEV_PORT = 80
|
||||||
|
USE_HTTPS = false
|
||||||
|
|||||||
1814
package-lock.json
generated
1814
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.100.0-dev",
|
"version": "0.100.6-dev",
|
||||||
"private": true,
|
"private": true,
|
||||||
"productName": "Tactical RMM",
|
"productName": "Tactical RMM",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -10,47 +10,31 @@
|
|||||||
"format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
|
"format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "1.14.0",
|
"@quasar/extras": "1.15.0",
|
||||||
"apexcharts": "3.35.2",
|
"apexcharts": "3.35.4",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
"dotenv": "16.0.0",
|
"dotenv": "16.0.1",
|
||||||
"qrcode.vue": "3.3.3",
|
"qrcode.vue": "3.3.3",
|
||||||
"quasar": "2.7.1",
|
"quasar": "2.7.5",
|
||||||
"vue": "3.2.31",
|
"vue": "3.2.37",
|
||||||
"vue3-ace-editor": "2.2.2",
|
"vue3-ace-editor": "2.2.2",
|
||||||
"vue3-apexcharts": "1.4.1",
|
"vue3-apexcharts": "1.4.1",
|
||||||
"vuedraggable": "4.1.0",
|
"vuedraggable": "4.1.0",
|
||||||
"vue-router": "4.0.15",
|
"vue-router": "4.1.2",
|
||||||
"vuex": "4.0.2"
|
"vuex": "4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/cli": "^1.3.2",
|
"@quasar/cli": "^1.3.2",
|
||||||
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
|
"@intlify/vite-plugin-vue-i18n": "^5.0.1",
|
||||||
"@quasar/app-vite": "^1.0.1",
|
"@quasar/app-vite": "^1.0.5",
|
||||||
"@types/node": "^12.20.21",
|
"@types/node": "^18.6.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||||
"@typescript-eslint/parser": "^5.10.0",
|
"@typescript-eslint/parser": "^5.30.5",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.7",
|
||||||
"eslint": "^8.10.0",
|
"eslint": "^8.20.0",
|
||||||
"eslint-config-prettier": "^8.1.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-vue": "^8.5.0",
|
"eslint-plugin-vue": "^8.5.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.7.1",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.7.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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
||||||
build: {
|
build: {
|
||||||
target: {
|
target: {
|
||||||
browser: ["es2019", "edge88", "firefox78", "chrome87", "safari13.1"],
|
browser: ["es2021"],
|
||||||
node: "node16",
|
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
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||||
devServer: {
|
devServer: {
|
||||||
https: false,
|
https: process.env.USE_HTTPS === "true",
|
||||||
open: false, // opens browser window automatically
|
open: false, // opens browser window automatically
|
||||||
host: process.env.DEV_HOST,
|
host: process.env.DEV_HOST,
|
||||||
port: process.env.DEV_PORT,
|
port: process.env.DEV_PORT,
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ export default function ({ app, router, store }) {
|
|||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
async function (error) {
|
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;
|
let text;
|
||||||
|
|
||||||
if (!error.response) {
|
if (!error.response) {
|
||||||
|
|||||||
@@ -142,6 +142,10 @@
|
|||||||
<q-item clickable v-close-popup @click="clearCache">
|
<q-item clickable v-close-popup @click="clearCache">
|
||||||
<q-item-section>Clear Cache</q-item-section>
|
<q-item-section>Clear Cache</q-item-section>
|
||||||
</q-item>
|
</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-list>
|
||||||
</q-menu>
|
</q-menu>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
@@ -262,6 +266,20 @@ export default {
|
|||||||
.get("/core/clearcache/")
|
.get("/core/clearcache/")
|
||||||
.then((r) => this.notifySuccess(r.data));
|
.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) {
|
openHelp(mode) {
|
||||||
let url;
|
let url;
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
|
|||||||
@@ -61,10 +61,7 @@
|
|||||||
<q-td key="client" :props="props">{{ props.row.client_name }}</q-td>
|
<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="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="mon_type" :props="props">{{ props.row.mon_type }}</q-td>
|
||||||
<q-td key="arch" :props="props"
|
<q-td key="goarch" :props="props">{{ props.row.goarch }}</q-td>
|
||||||
><span v-if="props.row.arch === '64'">64 bit</span
|
|
||||||
><span v-else>32 bit</span></q-td
|
|
||||||
>
|
|
||||||
<q-td key="expiry" :props="props">{{
|
<q-td key="expiry" :props="props">{{
|
||||||
formatDate(props.row.expiry)
|
formatDate(props.row.expiry)
|
||||||
}}</q-td>
|
}}</q-td>
|
||||||
@@ -130,7 +127,13 @@ const columns = [
|
|||||||
align: "left",
|
align: "left",
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{ name: "arch", label: "Arch", field: "arch", align: "left", sortable: true },
|
{
|
||||||
|
name: "goarch",
|
||||||
|
label: "Arch",
|
||||||
|
field: "goarch",
|
||||||
|
align: "left",
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "expiry",
|
name: "expiry",
|
||||||
label: "Expiry",
|
label: "Expiry",
|
||||||
|
|||||||
@@ -54,9 +54,9 @@
|
|||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="q-pl-sm">OS</div>
|
<div class="q-pl-sm">Arch</div>
|
||||||
<q-radio v-model="state.arch" val="64" label="64 bit" />
|
<q-radio v-model="state.goarch" :val="GOARCH_AMD64" label="64 bit" />
|
||||||
<q-radio v-model="state.arch" val="32" label="32 bit" />
|
<q-radio v-model="state.goarch" :val="GOARCH_i386" label="32 bit" />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn dense flat label="Cancel" v-close-popup />
|
<q-btn dense flat label="Cancel" v-close-popup />
|
||||||
@@ -84,6 +84,7 @@ import {
|
|||||||
formatDateInputField,
|
formatDateInputField,
|
||||||
formatDateStringwithTimezone,
|
formatDateStringwithTimezone,
|
||||||
} from "@/utils/format";
|
} from "@/utils/format";
|
||||||
|
import { GOARCH_AMD64, GOARCH_i386 } from "@/constants/constants";
|
||||||
|
|
||||||
// ui imports
|
// ui imports
|
||||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||||
@@ -108,7 +109,7 @@ export default {
|
|||||||
power: false,
|
power: false,
|
||||||
rdp: false,
|
rdp: false,
|
||||||
ping: false,
|
ping: false,
|
||||||
arch: "64",
|
goarch: GOARCH_AMD64,
|
||||||
});
|
});
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@@ -145,6 +146,10 @@ export default {
|
|||||||
// quasar dialog
|
// quasar dialog
|
||||||
dialogRef,
|
dialogRef,
|
||||||
onDialogHide,
|
onDialogHide,
|
||||||
|
|
||||||
|
// constants
|
||||||
|
GOARCH_AMD64,
|
||||||
|
GOARCH_i386,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
label="Windows"
|
label="Windows"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
installMethod = 'exe';
|
installMethod = 'exe';
|
||||||
arch = '64';
|
goarch = GOARCH_AMD64;
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<q-radio
|
<q-radio
|
||||||
@@ -48,8 +48,8 @@
|
|||||||
val="linux"
|
val="linux"
|
||||||
label="Linux"
|
label="Linux"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
installMethod = 'linux';
|
installMethod = 'bash';
|
||||||
arch = 'amd64';
|
goarch = GOARCH_AMD64;
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,38 +102,38 @@
|
|||||||
Arch
|
Arch
|
||||||
<div class="q-gutter-sm">
|
<div class="q-gutter-sm">
|
||||||
<q-radio
|
<q-radio
|
||||||
v-model="arch"
|
v-model="goarch"
|
||||||
val="64"
|
:val="GOARCH_AMD64"
|
||||||
label="64 bit"
|
label="64 bit"
|
||||||
v-show="agentOS === 'windows'"
|
v-show="agentOS === 'windows'"
|
||||||
/>
|
/>
|
||||||
<q-radio
|
<q-radio
|
||||||
v-model="arch"
|
v-model="goarch"
|
||||||
val="32"
|
:val="GOARCH_i386"
|
||||||
label="32 bit"
|
label="32 bit"
|
||||||
v-show="agentOS === 'windows'"
|
v-show="agentOS === 'windows'"
|
||||||
/>
|
/>
|
||||||
<q-radio
|
<q-radio
|
||||||
v-model="arch"
|
v-model="goarch"
|
||||||
val="amd64"
|
:val="GOARCH_AMD64"
|
||||||
label="64 bit"
|
label="64 bit"
|
||||||
v-show="agentOS !== 'windows'"
|
v-show="agentOS !== 'windows'"
|
||||||
/>
|
/>
|
||||||
<q-radio
|
<q-radio
|
||||||
v-model="arch"
|
v-model="goarch"
|
||||||
val="386"
|
:val="GOARCH_i386"
|
||||||
label="32 bit"
|
label="32 bit"
|
||||||
v-show="agentOS !== 'windows'"
|
v-show="agentOS !== 'windows'"
|
||||||
/>
|
/>
|
||||||
<q-radio
|
<q-radio
|
||||||
v-model="arch"
|
v-model="goarch"
|
||||||
val="arm64"
|
:val="GOARCH_ARM64"
|
||||||
label="ARM 64 bit"
|
label="ARM 64 bit"
|
||||||
v-show="agentOS !== 'windows'"
|
v-show="agentOS !== 'windows'"
|
||||||
/>
|
/>
|
||||||
<q-radio
|
<q-radio
|
||||||
v-model="arch"
|
v-model="goarch"
|
||||||
val="arm"
|
:val="GOARCH_ARM32"
|
||||||
label="ARM 32 bit (Rasp Pi)"
|
label="ARM 32 bit (Rasp Pi)"
|
||||||
v-show="agentOS !== 'windows'"
|
v-show="agentOS !== 'windows'"
|
||||||
/>
|
/>
|
||||||
@@ -177,6 +177,12 @@
|
|||||||
import mixins from "@/mixins/mixins";
|
import mixins from "@/mixins/mixins";
|
||||||
import AgentDownload from "@/components/modals/agents/AgentDownload.vue";
|
import AgentDownload from "@/components/modals/agents/AgentDownload.vue";
|
||||||
import { getBaseUrl } from "@/boot/axios";
|
import { getBaseUrl } from "@/boot/axios";
|
||||||
|
import {
|
||||||
|
GOARCH_AMD64,
|
||||||
|
GOARCH_i386,
|
||||||
|
GOARCH_ARM64,
|
||||||
|
GOARCH_ARM32,
|
||||||
|
} from "@/constants/constants";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "InstallAgent",
|
name: "InstallAgent",
|
||||||
@@ -187,6 +193,10 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
GOARCH_AMD64: GOARCH_AMD64,
|
||||||
|
GOARCH_i386: GOARCH_i386,
|
||||||
|
GOARCH_ARM64: GOARCH_ARM64,
|
||||||
|
GOARCH_ARM32: GOARCH_ARM32,
|
||||||
client_options: [],
|
client_options: [],
|
||||||
client: null,
|
client: null,
|
||||||
site: null,
|
site: null,
|
||||||
@@ -198,7 +208,7 @@ export default {
|
|||||||
showAgentDownload: false,
|
showAgentDownload: false,
|
||||||
info: {},
|
info: {},
|
||||||
installMethod: "exe",
|
installMethod: "exe",
|
||||||
arch: "64",
|
goarch: GOARCH_AMD64,
|
||||||
agentOS: "windows",
|
agentOS: "windows",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -239,10 +249,7 @@ export default {
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/([^a-zA-Z0-9]+)/g, "");
|
.replace(/([^a-zA-Z0-9]+)/g, "");
|
||||||
|
|
||||||
const fileName =
|
const fileName = `trmm-${clientStripped}-${siteStripped}-${this.agenttype}-${this.goarch}.exe`;
|
||||||
this.arch === "64"
|
|
||||||
? `rmm-${clientStripped}-${siteStripped}-${this.agenttype}.exe`
|
|
||||||
: `rmm-${clientStripped}-${siteStripped}-${this.agenttype}-x86.exe`;
|
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
installMethod: this.installMethod,
|
installMethod: this.installMethod,
|
||||||
@@ -253,10 +260,10 @@ export default {
|
|||||||
power: this.power ? 1 : 0,
|
power: this.power ? 1 : 0,
|
||||||
rdp: this.rdp ? 1 : 0,
|
rdp: this.rdp ? 1 : 0,
|
||||||
ping: this.ping ? 1 : 0,
|
ping: this.ping ? 1 : 0,
|
||||||
arch: this.arch,
|
goarch: this.goarch,
|
||||||
api,
|
api,
|
||||||
fileName,
|
fileName,
|
||||||
os: this.agentOS,
|
plat: this.agentOS,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.installMethod === "manual") {
|
if (this.installMethod === "manual") {
|
||||||
@@ -264,7 +271,7 @@ export default {
|
|||||||
this.info = {
|
this.info = {
|
||||||
expires: this.expires,
|
expires: this.expires,
|
||||||
data: r.data,
|
data: r.data,
|
||||||
arch: this.arch,
|
goarch: this.goarch,
|
||||||
};
|
};
|
||||||
this.showAgentDownload = true;
|
this.showAgentDownload = true;
|
||||||
});
|
});
|
||||||
@@ -289,7 +296,7 @@ export default {
|
|||||||
});
|
});
|
||||||
} else if (
|
} else if (
|
||||||
this.installMethod === "powershell" ||
|
this.installMethod === "powershell" ||
|
||||||
this.installMethod === "linux"
|
this.installMethod === "bash"
|
||||||
) {
|
) {
|
||||||
this.$q.loading.show();
|
this.$q.loading.show();
|
||||||
let ext = this.installMethod === "powershell" ? "ps1" : "sh";
|
let ext = this.installMethod === "powershell" ? "ps1" : "sh";
|
||||||
@@ -333,7 +340,7 @@ export default {
|
|||||||
case "manual":
|
case "manual":
|
||||||
text = "Show manual installation instructions";
|
text = "Show manual installation instructions";
|
||||||
break;
|
break;
|
||||||
case "linux":
|
case "bash":
|
||||||
text = "Download linux install script";
|
text = "Download linux install script";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,37 +129,37 @@
|
|||||||
<div class="q-gutter-sm">
|
<div class="q-gutter-sm">
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="winupdatepolicy.run_time_days"
|
v-model="winupdatepolicy.run_time_days"
|
||||||
:val="1"
|
:val="0"
|
||||||
label="Monday"
|
label="Monday"
|
||||||
/>
|
/>
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="winupdatepolicy.run_time_days"
|
v-model="winupdatepolicy.run_time_days"
|
||||||
:val="2"
|
:val="1"
|
||||||
label="Tuesday"
|
label="Tuesday"
|
||||||
/>
|
/>
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="winupdatepolicy.run_time_days"
|
v-model="winupdatepolicy.run_time_days"
|
||||||
:val="3"
|
:val="2"
|
||||||
label="Wednesday"
|
label="Wednesday"
|
||||||
/>
|
/>
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="winupdatepolicy.run_time_days"
|
v-model="winupdatepolicy.run_time_days"
|
||||||
:val="4"
|
:val="3"
|
||||||
label="Thursday"
|
label="Thursday"
|
||||||
/>
|
/>
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="winupdatepolicy.run_time_days"
|
v-model="winupdatepolicy.run_time_days"
|
||||||
:val="5"
|
:val="4"
|
||||||
label="Friday"
|
label="Friday"
|
||||||
/>
|
/>
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="winupdatepolicy.run_time_days"
|
v-model="winupdatepolicy.run_time_days"
|
||||||
:val="6"
|
:val="5"
|
||||||
label="Saturday"
|
label="Saturday"
|
||||||
/>
|
/>
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="winupdatepolicy.run_time_days"
|
v-model="winupdatepolicy.run_time_days"
|
||||||
:val="0"
|
:val="6"
|
||||||
label="Sunday"
|
label="Sunday"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,11 +63,14 @@ export default {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await scheduleAgentReboot(props.agent.agent_id, state.value);
|
const ret = await scheduleAgentReboot(
|
||||||
|
props.agent.agent_id,
|
||||||
|
state.value
|
||||||
|
);
|
||||||
$q.dialog({
|
$q.dialog({
|
||||||
title: "Reboot pending",
|
title: "Reboot pending",
|
||||||
style: "width: 40vw",
|
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.`,
|
<br />It can be cancelled from the Pending Actions menu until the scheduled time.`,
|
||||||
html: true,
|
html: true,
|
||||||
}).onDismiss(onDialogOK);
|
}).onDismiss(onDialogOK);
|
||||||
|
|||||||
@@ -8,11 +8,12 @@
|
|||||||
</q-btn>
|
</q-btn>
|
||||||
</q-bar>
|
</q-bar>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-banner class="bg-warning">
|
<q-banner class="bg-info">
|
||||||
<template v-slot:avatar>
|
<template v-slot:avatar>
|
||||||
<q-icon name="info" />
|
<q-icon name="info" />
|
||||||
</template>
|
</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-banner>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
Select Version
|
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>
|
||||||
@@ -12,11 +12,15 @@
|
|||||||
color="positive"
|
color="positive"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
@click="doCodeSign"
|
@click="doCodeSign"
|
||||||
|
:loading="loading"
|
||||||
>
|
>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
>Force all existing agents to be updated to the code-signed
|
>Force all existing agents to be updated to the code-signed
|
||||||
version</q-tooltip
|
version</q-tooltip
|
||||||
>
|
>
|
||||||
|
<template v-slot:loading>
|
||||||
|
<q-spinner-facebook />
|
||||||
|
</template>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-form @submit.prevent="editToken">
|
<q-form @submit.prevent="editToken">
|
||||||
@@ -33,56 +37,92 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
<q-btn label="Save" color="primary" type="submit" />
|
<q-btn label="Save" color="primary" type="submit" />
|
||||||
|
<q-space />
|
||||||
|
<q-btn label="Delete" color="negative" @click="confirmDelete" />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-form>
|
</q-form>
|
||||||
</q-card>
|
</q-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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 {
|
export default {
|
||||||
name: "CodeSign",
|
name: "CodeSign",
|
||||||
mixins: [mixins],
|
setup() {
|
||||||
data() {
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
return {
|
||||||
settings: {
|
settings,
|
||||||
token: "",
|
loading,
|
||||||
},
|
confirmDelete,
|
||||||
|
doCodeSign,
|
||||||
|
editToken,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
getToken() {
|
|
||||||
this.$axios.get("/core/codesign/").then((r) => {
|
|
||||||
this.settings = r.data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
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();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -991,10 +991,16 @@ export default {
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
// remove milliseconds and Z to work with native date input
|
// 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)
|
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
|
// set task type if monthlydow is being used
|
||||||
if (task.value.task_type === "monthlydow") {
|
if (task.value.task_type === "monthlydow") {
|
||||||
|
|||||||
6
src/constants/constants.ts
Normal file
6
src/constants/constants.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const GOARCH_AMD64 = "amd64";
|
||||||
|
const GOARCH_i386 = "386";
|
||||||
|
const GOARCH_ARM64 = "arm64";
|
||||||
|
const GOARCH_ARM32 = "arm";
|
||||||
|
|
||||||
|
export { GOARCH_AMD64, GOARCH_i386, GOARCH_ARM64, GOARCH_ARM32 };
|
||||||
@@ -35,12 +35,7 @@
|
|||||||
Tactical RMM<span class="text-overline q-ml-sm"
|
Tactical RMM<span class="text-overline q-ml-sm"
|
||||||
>v{{ currentTRMMVersion }}</span
|
>v{{ currentTRMMVersion }}</span
|
||||||
>
|
>
|
||||||
<span
|
<span class="text-overline q-ml-md" v-if="updateAvailable()"
|
||||||
class="text-overline q-ml-md"
|
|
||||||
v-if="
|
|
||||||
latestTRMMVersion !== 'error' &&
|
|
||||||
currentTRMMVersion !== latestTRMMVersion
|
|
||||||
"
|
|
||||||
><q-badge color="warning"
|
><q-badge color="warning"
|
||||||
><a :href="latestReleaseURL" target="_blank"
|
><a :href="latestReleaseURL" target="_blank"
|
||||||
>v{{ latestTRMMVersion }} available</a
|
>v{{ latestTRMMVersion }} available</a
|
||||||
@@ -144,7 +139,7 @@ import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
|||||||
import { useQuasar } from "quasar";
|
import { useQuasar } from "quasar";
|
||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { getBaseUrl } from "@/boot/axios";
|
import { getWSUrl } from "@/websocket/channels";
|
||||||
|
|
||||||
// ui imports
|
// ui imports
|
||||||
import AlertsIcon from "@/components/AlertsIcon.vue";
|
import AlertsIcon from "@/components/AlertsIcon.vue";
|
||||||
@@ -171,6 +166,7 @@ export default {
|
|||||||
const latestTRMMVersion = computed(() => store.state.latestTRMMVersion);
|
const latestTRMMVersion = computed(() => store.state.latestTRMMVersion);
|
||||||
const needRefresh = computed(() => store.state.needrefresh);
|
const needRefresh = computed(() => store.state.needrefresh);
|
||||||
const user = computed(() => store.state.username);
|
const user = computed(() => store.state.username);
|
||||||
|
const hosted = computed(() => store.state.hosted);
|
||||||
|
|
||||||
const latestReleaseURL = computed(() => {
|
const latestReleaseURL = computed(() => {
|
||||||
return latestTRMMVersion.value
|
return latestTRMMVersion.value
|
||||||
@@ -184,10 +180,6 @@ export default {
|
|||||||
}).onOk(() => store.dispatch("getDashInfo"));
|
}).onOk(() => store.dispatch("getDashInfo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function wsUrl() {
|
|
||||||
return getBaseUrl().split("://")[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverCount = ref(0);
|
const serverCount = ref(0);
|
||||||
const serverOfflineCount = ref(0);
|
const serverOfflineCount = ref(0);
|
||||||
const workstationCount = ref(0);
|
const workstationCount = ref(0);
|
||||||
@@ -200,13 +192,8 @@ export default {
|
|||||||
// when ws is closed causing ws to connect with expired token
|
// when ws is closed causing ws to connect with expired token
|
||||||
const token = computed(() => store.state.token);
|
const token = computed(() => store.state.token);
|
||||||
console.log("Starting websocket");
|
console.log("Starting websocket");
|
||||||
const proto =
|
let url = getWSUrl("dashinfo", token.value);
|
||||||
process.env.NODE_ENV === "production" || process.env.DOCKER_BUILD
|
ws.value = new WebSocket(url);
|
||||||
? "wss"
|
|
||||||
: "ws";
|
|
||||||
ws.value = new WebSocket(
|
|
||||||
`${proto}://${wsUrl()}/ws/dashinfo/?access_token=${token.value}`
|
|
||||||
);
|
|
||||||
ws.value.onopen = () => {
|
ws.value.onopen = () => {
|
||||||
console.log("Connected to ws");
|
console.log("Connected to ws");
|
||||||
};
|
};
|
||||||
@@ -242,6 +229,11 @@ export default {
|
|||||||
}, 60 * 5 * 1000);
|
}, 60 * 5 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateAvailable() {
|
||||||
|
if (latestTRMMVersion.value === "error" || hosted.value) return false;
|
||||||
|
return currentTRMMVersion.value !== latestTRMMVersion.value;
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setupWS();
|
setupWS();
|
||||||
store.dispatch("getDashInfo");
|
store.dispatch("getDashInfo");
|
||||||
@@ -270,6 +262,7 @@ export default {
|
|||||||
|
|
||||||
// methods
|
// methods
|
||||||
showUserPreferences,
|
showUserPreferences,
|
||||||
|
updateAvailable,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
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