Compare commits
	
		
			80 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b4b63826dc | ||
| 
						 | 
					45e2690a81 | ||
| 
						 | 
					2ff504db09 | ||
| 
						 | 
					0c89e58d8c | ||
| 
						 | 
					e1dc75e2d8 | ||
| 
						 | 
					6dd027c994 | ||
| 
						 | 
					263c1f1d75 | ||
| 
						 | 
					52ee688259 | ||
| 
						 | 
					ce4c3a74b5 | ||
| 
						 | 
					72e493bef0 | ||
| 
						 | 
					14903c888c | ||
| 
						 | 
					f2bdf0e9f1 | ||
| 
						 | 
					02871fdd66 | ||
| 
						 | 
					fd9f1bca8e | ||
| 
						 | 
					739181a7ec | ||
| 
						 | 
					a0c406251f | ||
| 
						 | 
					c420e063bd | ||
| 
						 | 
					24ba9fb598 | ||
| 
						 | 
					c872764541 | ||
| 
						 | 
					3e52924859 | ||
| 
						 | 
					51cc895d12 | ||
| 
						 | 
					0a1f33fede | ||
| 
						 | 
					b539df007b | ||
| 
						 | 
					1a6cb090fe | ||
| 
						 | 
					5e2602c3dc | ||
| 
						 | 
					8a388db603 | ||
| 
						 | 
					e0018871b1 | ||
| 
						 | 
					be508d9c9d | ||
| 
						 | 
					e670e67ef5 | ||
| 
						 | 
					32dae6e181 | ||
| 
						 | 
					0f0a7ed119 | ||
| 
						 | 
					e407a8c59e | ||
| 
						 | 
					6c4d95ebfd | ||
| 
						 | 
					7e54f2456e | ||
| 
						 | 
					2419179877 | ||
| 
						 | 
					58a120e5c8 | ||
| 
						 | 
					19315b6174 | ||
| 
						 | 
					b014f9afd9 | ||
| 
						 | 
					794e128504 | ||
| 
						 | 
					de25074861 | ||
| 
						 | 
					4da70dd23a | ||
| 
						 | 
					b840ee542a | ||
| 
						 | 
					a51939df32 | ||
| 
						 | 
					c3098f023a | ||
| 
						 | 
					857a744c74 | ||
| 
						 | 
					62fd3a207c | ||
| 
						 | 
					ae3acfbc98 | ||
| 
						 | 
					2bf32aeab5 | ||
| 
						 | 
					3642407de8 | ||
| 
						 | 
					f9333c5ffd | ||
| 
						 | 
					b2fb45fe16 | ||
| 
						 | 
					1864a4ea77 | ||
| 
						 | 
					c6e34dd900 | ||
| 
						 | 
					589b36d074 | ||
| 
						 | 
					575ef6fec7 | ||
| 
						 | 
					dd5c009d89 | ||
| 
						 | 
					3fa26a6b25 | ||
| 
						 | 
					1d14f5a8b6 | ||
| 
						 | 
					7f5d5db0ef | ||
| 
						 | 
					592909d890 | ||
| 
						 | 
					5113f42781 | ||
| 
						 | 
					60ddf07be9 | ||
| 
						 | 
					c8f1b1b247 | ||
| 
						 | 
					31f2807295 | ||
| 
						 | 
					08edca4fbf | ||
| 
						 | 
					cb2a740beb | ||
| 
						 | 
					e0f6f4f563 | ||
| 
						 | 
					34652110ca | ||
| 
						 | 
					d4d4bda519 | ||
| 
						 | 
					e83463a3cc | ||
| 
						 | 
					33216fd197 | ||
| 
						 | 
					b332332f79 | ||
| 
						 | 
					ff81f7a9d0 | ||
| 
						 | 
					b8379c4508 | ||
| 
						 | 
					0fbd3a59bd | ||
| 
						 | 
					b03d7b370f | ||
| 
						 | 
					8f1c694071 | ||
| 
						 | 
					789a8b0cf0 | ||
| 
						 | 
					c9dd02ace3 | ||
| 
						 | 
					ad5906c7b6 | 
@@ -3,10 +3,9 @@ version: '3.7'
 | 
				
			|||||||
services:
 | 
					services:
 | 
				
			||||||
  app-dev:
 | 
					  app-dev:
 | 
				
			||||||
    container_name: trmm-app-dev
 | 
					    container_name: trmm-app-dev
 | 
				
			||||||
    image: node:18-alpine
 | 
					    image: node:20-alpine
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    command: /bin/sh -c "npm install --cache ~/.npm && npm run serve"
 | 
					    command: /bin/sh -c "npm install --cache ~/.npm && npm i -g @quasar/cli && npm run serve"
 | 
				
			||||||
    user: 1000:1000
 | 
					 | 
				
			||||||
    working_dir: /workspace/web
 | 
					    working_dir: /workspace/web
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ..:/workspace:cached
 | 
					      - ..:/workspace:cached
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2836
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2836
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										49
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								package.json
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "web",
 | 
					  "name": "web",
 | 
				
			||||||
  "version": "0.101.43",
 | 
					  "version": "0.101.47",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "productName": "Tactical RMM",
 | 
					  "productName": "Tactical RMM",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
@@ -10,37 +10,38 @@
 | 
				
			|||||||
    "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.16.9",
 | 
					    "@quasar/extras": "1.16.12",
 | 
				
			||||||
    "apexcharts": "3.48.0",
 | 
					    "@vueuse/core": "10.11.0",
 | 
				
			||||||
    "axios": "1.6.8",
 | 
					    "@vueuse/integrations": "10.11.0",
 | 
				
			||||||
 | 
					    "@vueuse/shared": "10.11.0",
 | 
				
			||||||
 | 
					    "apexcharts": "3.49.2",
 | 
				
			||||||
 | 
					    "axios": "1.7.2",
 | 
				
			||||||
    "dotenv": "16.4.5",
 | 
					    "dotenv": "16.4.5",
 | 
				
			||||||
    "pinia": "^2.1.7",
 | 
					    "monaco-editor": "0.50.0",
 | 
				
			||||||
    "qrcode.vue": "3.4.1",
 | 
					    "pinia": "2.1.7",
 | 
				
			||||||
    "quasar": "2.15.1",
 | 
					    "qrcode": "1.5.3",
 | 
				
			||||||
    "vue": "3.4.21",
 | 
					    "quasar": "2.16.6",
 | 
				
			||||||
    "vue3-apexcharts": "1.5.2",
 | 
					    "vue": "3.4.31",
 | 
				
			||||||
 | 
					    "vue-router": "4.4.0",
 | 
				
			||||||
 | 
					    "vue3-apexcharts": "1.5.3",
 | 
				
			||||||
    "vuedraggable": "4.1.0",
 | 
					    "vuedraggable": "4.1.0",
 | 
				
			||||||
    "vue-router": "4.3.0",
 | 
					 | 
				
			||||||
    "@vueuse/core": "10.9.0",
 | 
					 | 
				
			||||||
    "@vueuse/shared": "10.9.0",
 | 
					 | 
				
			||||||
    "monaco-editor": "0.47.0",
 | 
					 | 
				
			||||||
    "vuex": "4.1.0",
 | 
					    "vuex": "4.1.0",
 | 
				
			||||||
    "xterm": "^5.3.0",
 | 
					    "@xterm/xterm": "5.5.0",
 | 
				
			||||||
    "xterm-addon-fit": "^0.8.0",
 | 
					    "@xterm/addon-fit": "0.10.0",
 | 
				
			||||||
    "yaml": "2.4.1"
 | 
					    "yaml": "2.4.5"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@quasar/cli": "2.4.0",
 | 
					    "@intlify/unplugin-vue-i18n": "4.0.0",
 | 
				
			||||||
    "@intlify/unplugin-vue-i18n": "3.0.1",
 | 
					    "@quasar/app-vite": "1.9.3",
 | 
				
			||||||
    "@quasar/app-vite": "1.8.0",
 | 
					    "@quasar/cli": "2.4.1",
 | 
				
			||||||
    "@types/node": "20.11.30",
 | 
					    "@types/node": "20.14.10",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "7.3.1",
 | 
					    "@typescript-eslint/eslint-plugin": "7.16.0",
 | 
				
			||||||
    "@typescript-eslint/parser": "7.3.1",
 | 
					    "@typescript-eslint/parser": "7.16.0",
 | 
				
			||||||
    "autoprefixer": "10.4.18",
 | 
					    "autoprefixer": "10.4.19",
 | 
				
			||||||
    "eslint": "8.57.0",
 | 
					    "eslint": "8.57.0",
 | 
				
			||||||
    "eslint-config-prettier": "9.1.0",
 | 
					    "eslint-config-prettier": "9.1.0",
 | 
				
			||||||
    "eslint-plugin-vue": "8.7.1",
 | 
					    "eslint-plugin-vue": "8.7.1",
 | 
				
			||||||
    "prettier": "3.2.5",
 | 
					    "prettier": "3.2.5",
 | 
				
			||||||
    "typescript": "5.4.3"
 | 
					    "typescript": "5.5.3"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,7 +29,7 @@ module.exports = configure(function (/* ctx */) {
 | 
				
			|||||||
    // app boot file (/src/boot)
 | 
					    // app boot file (/src/boot)
 | 
				
			||||||
    // --> boot files are part of "main.js"
 | 
					    // --> boot files are part of "main.js"
 | 
				
			||||||
    // https://v2.quasar.dev/quasar-cli-vite/boot-files
 | 
					    // https://v2.quasar.dev/quasar-cli-vite/boot-files
 | 
				
			||||||
    boot: ["axios", "monaco", "integrations"],
 | 
					    boot: ["pinia", "axios", "monaco", "integrations"],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
 | 
					    // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
 | 
				
			||||||
    css: ["app.sass"],
 | 
					    css: ["app.sass"],
 | 
				
			||||||
@@ -37,7 +37,7 @@ module.exports = configure(function (/* ctx */) {
 | 
				
			|||||||
    // https://github.com/quasarframework/quasar/tree/dev/extras
 | 
					    // https://github.com/quasarframework/quasar/tree/dev/extras
 | 
				
			||||||
    extras: [
 | 
					    extras: [
 | 
				
			||||||
      "ionicons-v4",
 | 
					      "ionicons-v4",
 | 
				
			||||||
      "mdi-v5",
 | 
					      "mdi-v7",
 | 
				
			||||||
      "fontawesome-v6",
 | 
					      "fontawesome-v6",
 | 
				
			||||||
      // 'eva-icons',
 | 
					      // 'eva-icons',
 | 
				
			||||||
      // 'themify',
 | 
					      // 'themify',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,7 @@ export function openAgentWindow(agent_id) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function runRemoteBackground(agent_id, agentPlatform) {
 | 
					export function runRemoteBackground(agent_id, agentPlatform) {
 | 
				
			||||||
  const url = router.resolve(
 | 
					  const url = router.resolve(
 | 
				
			||||||
    `/remotebackground/${agent_id}?agentPlatform=${agentPlatform}`
 | 
					    `/remotebackground/${agent_id}?agentPlatform=${agentPlatform}`,
 | 
				
			||||||
  ).href;
 | 
					  ).href;
 | 
				
			||||||
  openURL(url, null, {
 | 
					  openURL(url, null, {
 | 
				
			||||||
    popup: true,
 | 
					    popup: true,
 | 
				
			||||||
@@ -129,7 +129,7 @@ export async function refreshAgentWMI(agent_id) {
 | 
				
			|||||||
export async function runScript(agent_id, payload) {
 | 
					export async function runScript(agent_id, payload) {
 | 
				
			||||||
  const { data } = await axios.post(
 | 
					  const { data } = await axios.post(
 | 
				
			||||||
    `${baseUrl}/${agent_id}/runscript/`,
 | 
					    `${baseUrl}/${agent_id}/runscript/`,
 | 
				
			||||||
    payload
 | 
					    payload,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  return data;
 | 
					  return data;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -153,7 +153,7 @@ export async function fetchAgentProcesses(agent_id, params = {}) {
 | 
				
			|||||||
export async function killAgentProcess(agent_id, pid, params = {}) {
 | 
					export async function killAgentProcess(agent_id, pid, params = {}) {
 | 
				
			||||||
  const { data } = await axios.delete(
 | 
					  const { data } = await axios.delete(
 | 
				
			||||||
    `${baseUrl}/${agent_id}/processes/${pid}/`,
 | 
					    `${baseUrl}/${agent_id}/processes/${pid}/`,
 | 
				
			||||||
    { params: params }
 | 
					    { params: params },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  return data;
 | 
					  return data;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -162,7 +162,7 @@ export async function fetchAgentEventLog(agent_id, logType, days, params = {}) {
 | 
				
			|||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const { data } = await axios.get(
 | 
					    const { data } = await axios.get(
 | 
				
			||||||
      `${baseUrl}/${agent_id}/eventlog/${logType}/${days}/`,
 | 
					      `${baseUrl}/${agent_id}/eventlog/${logType}/${days}/`,
 | 
				
			||||||
      { params: params }
 | 
					      { params: params },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    return data;
 | 
					    return data;
 | 
				
			||||||
  } catch (e) {
 | 
					  } catch (e) {
 | 
				
			||||||
@@ -199,7 +199,7 @@ export async function agentShutdown(agent_id) {
 | 
				
			|||||||
export async function sendAgentRecoverMesh(agent_id, params = {}) {
 | 
					export async function sendAgentRecoverMesh(agent_id, params = {}) {
 | 
				
			||||||
  const { data } = await axios.post(
 | 
					  const { data } = await axios.post(
 | 
				
			||||||
    `${baseUrl}/${agent_id}/meshcentral/recover/`,
 | 
					    `${baseUrl}/${agent_id}/meshcentral/recover/`,
 | 
				
			||||||
    { params: params }
 | 
					    { params: params },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  return data;
 | 
					  return data;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								src/api/alerts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/api/alerts.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { AlertTemplate } from "@/types/alerts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function saveAlertTemplate(id: number, payload: AlertTemplate) {
 | 
				
			||||||
 | 
					  const { data } = await axios.put(`alerts/templates/${id}/`, payload);
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function addAlertTemplate(payload: AlertTemplate) {
 | 
				
			||||||
 | 
					  const { data } = await axios.post("alerts/templates/", payload);
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,45 +0,0 @@
 | 
				
			|||||||
import axios from "axios";
 | 
					 | 
				
			||||||
import { openURL } from "quasar";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const baseUrl = "/core";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function fetchCustomFields(params = {}) {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const { data } = await axios.get(`${baseUrl}/customfields/`, {
 | 
					 | 
				
			||||||
      params: params,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return data;
 | 
					 | 
				
			||||||
  } catch (e) {
 | 
					 | 
				
			||||||
    console.error(e);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function fetchDashboardInfo(params = {}) {
 | 
					 | 
				
			||||||
  const { data } = await axios.get(`${baseUrl}/dashinfo/`, { params: params });
 | 
					 | 
				
			||||||
  return data;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function fetchURLActions(params = {}) {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const { data } = await axios.get(`${baseUrl}/urlaction/`, {
 | 
					 | 
				
			||||||
      params: params,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return data;
 | 
					 | 
				
			||||||
  } catch (e) {
 | 
					 | 
				
			||||||
    console.error(e);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function runURLAction(payload) {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const { data } = await axios.patch(`${baseUrl}/urlaction/run/`, payload);
 | 
					 | 
				
			||||||
    openURL(data);
 | 
					 | 
				
			||||||
  } catch (e) {
 | 
					 | 
				
			||||||
    console.error(e);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function generateScript(payload) {
 | 
					 | 
				
			||||||
  const { data } = await axios.post(`${baseUrl}/openai/generate/`, payload);
 | 
					 | 
				
			||||||
  return data;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										97
									
								
								src/api/core.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/api/core.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import { openURL } from "quasar";
 | 
				
			||||||
 | 
					import { router } from "@/router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  URLAction,
 | 
				
			||||||
 | 
					  TestRunURLActionRequest,
 | 
				
			||||||
 | 
					  TestRunURLActionResponse,
 | 
				
			||||||
 | 
					} from "@/types/core/urlactions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const baseUrl = "/core";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function fetchDashboardInfo(params = {}) {
 | 
				
			||||||
 | 
					  const { data } = await axios.get(`${baseUrl}/dashinfo/`, { params: params });
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function fetchCustomFields(params = {}) {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const { data } = await axios.get(`${baseUrl}/customfields/`, {
 | 
				
			||||||
 | 
					      params: params,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return data;
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    console.error(e);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function fetchURLActions(params = {}): Promise<URLAction[]> {
 | 
				
			||||||
 | 
					  const { data } = await axios.get(`${baseUrl}/urlaction/`, {
 | 
				
			||||||
 | 
					    params: params,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function saveURLAction(action: URLAction) {
 | 
				
			||||||
 | 
					  const { data } = await axios.post(`${baseUrl}/urlaction/`, action);
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function editURLAction(id: number, action: URLAction) {
 | 
				
			||||||
 | 
					  const { data } = await axios.put(`${baseUrl}/urlaction/${id}/`, action);
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function removeURLAction(id: number) {
 | 
				
			||||||
 | 
					  const { data } = await axios.delete(`${baseUrl}/urlaction/${id}/`);
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface RunURLActionRequest {
 | 
				
			||||||
 | 
					  agent_id?: string;
 | 
				
			||||||
 | 
					  client?: number;
 | 
				
			||||||
 | 
					  site?: number;
 | 
				
			||||||
 | 
					  action: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function runURLAction(payload: RunURLActionRequest) {
 | 
				
			||||||
 | 
					  const { data } = await axios.patch(`${baseUrl}/urlaction/run/`, payload);
 | 
				
			||||||
 | 
					  openURL(data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function runTestURLAction(
 | 
				
			||||||
 | 
					  payload: TestRunURLActionRequest,
 | 
				
			||||||
 | 
					): Promise<TestRunURLActionResponse> {
 | 
				
			||||||
 | 
					  const { data } = await axios.post(`${baseUrl}/urlaction/run/test/`, payload);
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function checkWebTermPerms(): Promise<{
 | 
				
			||||||
 | 
					  message: string;
 | 
				
			||||||
 | 
					  status: number;
 | 
				
			||||||
 | 
					}> {
 | 
				
			||||||
 | 
					  const ret = await axios.post(`${baseUrl}/webtermperms/`);
 | 
				
			||||||
 | 
					  return { message: ret.data, status: ret.status };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function openWebTerminal(): void {
 | 
				
			||||||
 | 
					  const url: string = router.resolve("/webterm").href;
 | 
				
			||||||
 | 
					  openURL(url, undefined, {
 | 
				
			||||||
 | 
					    popup: true,
 | 
				
			||||||
 | 
					    scrollbars: false,
 | 
				
			||||||
 | 
					    location: false,
 | 
				
			||||||
 | 
					    status: false,
 | 
				
			||||||
 | 
					    toolbar: false,
 | 
				
			||||||
 | 
					    menubar: false,
 | 
				
			||||||
 | 
					    width: 1280,
 | 
				
			||||||
 | 
					    height: 720,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Build out type for openai payload
 | 
				
			||||||
 | 
					// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					export async function generateScript(payload: any) {
 | 
				
			||||||
 | 
					  const { data } = await axios.post(`${baseUrl}/openai/generate/`, payload);
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -13,6 +13,11 @@ export async function testScript(agent_id, payload) {
 | 
				
			|||||||
  return data;
 | 
					  return data;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function testScriptOnServer(payload) {
 | 
				
			||||||
 | 
					  const { data } = await axios.post("core/serverscript/test/", payload);
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function saveScript(payload) {
 | 
					export async function saveScript(payload) {
 | 
				
			||||||
  const { data } = await axios.post(`${baseUrl}/`, payload);
 | 
					  const { data } = await axios.post(`${baseUrl}/`, payload);
 | 
				
			||||||
  return data;
 | 
					  return data;
 | 
				
			||||||
@@ -56,7 +61,7 @@ export async function fetchScriptSnippet(id, params = {}) {
 | 
				
			|||||||
export async function editScriptSnippet(payload) {
 | 
					export async function editScriptSnippet(payload) {
 | 
				
			||||||
  const { data } = await axios.put(
 | 
					  const { data } = await axios.put(
 | 
				
			||||||
    `${baseUrl}/snippets/${payload.id}/`,
 | 
					    `${baseUrl}/snippets/${payload.id}/`,
 | 
				
			||||||
    payload
 | 
					    payload,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  return data;
 | 
					  return data;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import axios from "axios";
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import { useAuthStore } from "@/stores/auth";
 | 
				
			||||||
import { Notify } from "quasar";
 | 
					import { Notify } from "quasar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getBaseUrl = () => {
 | 
					export const getBaseUrl = () => {
 | 
				
			||||||
@@ -18,27 +19,22 @@ export function setErrorMessage(data, message) {
 | 
				
			|||||||
  ];
 | 
					  ];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function ({ app, router, store }) {
 | 
					export default function ({ app, router }) {
 | 
				
			||||||
  app.config.globalProperties.$axios = axios;
 | 
					  app.config.globalProperties.$axios = axios;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  axios.interceptors.request.use(
 | 
					  axios.interceptors.request.use(
 | 
				
			||||||
    function (config) {
 | 
					    function (config) {
 | 
				
			||||||
 | 
					      const auth = useAuthStore();
 | 
				
			||||||
      config.baseURL = getBaseUrl();
 | 
					      config.baseURL = getBaseUrl();
 | 
				
			||||||
      const token = store.state.token;
 | 
					      const token = auth.token;
 | 
				
			||||||
      if (token != null) {
 | 
					      if (token != null) {
 | 
				
			||||||
        config.headers.Authorization = `Token ${token}`;
 | 
					        config.headers.Authorization = `Token ${token}`;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // config.transformResponse = [
 | 
					 | 
				
			||||||
      //   function (data) {
 | 
					 | 
				
			||||||
      //     console.log(data);
 | 
					 | 
				
			||||||
      //     return data;
 | 
					 | 
				
			||||||
      //   },
 | 
					 | 
				
			||||||
      // ];
 | 
					 | 
				
			||||||
      return config;
 | 
					      return config;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    function (err) {
 | 
					    function (err) {
 | 
				
			||||||
      return Promise.reject(err);
 | 
					      return Promise.reject(err);
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  axios.interceptors.response.use(
 | 
					  axios.interceptors.response.use(
 | 
				
			||||||
@@ -101,6 +97,6 @@ export default function ({ app, router, store }) {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return Promise.reject({ ...error });
 | 
					      return Promise.reject({ ...error });
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/boot/pinia.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/boot/pinia.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { boot } from "quasar/wrappers";
 | 
				
			||||||
 | 
					import { createPinia } from "pinia";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default boot(({ app }) => {
 | 
				
			||||||
 | 
					  const pinia = createPinia();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  app.use(pinia);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // You can add Pinia plugins here
 | 
				
			||||||
 | 
					  // pinia.use(SomePiniaPlugin)
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -431,8 +431,8 @@ export default {
 | 
				
			|||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
          else if (availability === "expired") {
 | 
					          else if (availability === "expired") {
 | 
				
			||||||
            let now = new Date();
 | 
					            let now = new Date();
 | 
				
			||||||
            let last_seen_unix = new Date(row.boot_time * 1000);
 | 
					            let last_seen = new Date(row.last_seen);
 | 
				
			||||||
            let diff = date.getDateDiff(now, last_seen_unix, "days");
 | 
					            let diff = date.getDateDiff(now, last_seen, "days");
 | 
				
			||||||
            if (diff < 30) return false;
 | 
					            if (diff < 30) return false;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -278,7 +278,7 @@ export default {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          name: "resolved_action_name",
 | 
					          name: "resolved_action_name",
 | 
				
			||||||
          label: "Resolve Action",
 | 
					          label: "Resolved Action",
 | 
				
			||||||
          field: "resolved_action_name",
 | 
					          field: "resolved_action_name",
 | 
				
			||||||
          align: "left",
 | 
					          align: "left",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -326,7 +326,7 @@ export default {
 | 
				
			|||||||
              this.refresh();
 | 
					              this.refresh();
 | 
				
			||||||
              this.$q.loading.hide();
 | 
					              this.$q.loading.hide();
 | 
				
			||||||
              this.notifySuccess(
 | 
					              this.notifySuccess(
 | 
				
			||||||
                `Alert template ${template.name} was deleted!`
 | 
					                `Alert template ${template.name} was deleted!`,
 | 
				
			||||||
              );
 | 
					              );
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .catch(() => {
 | 
					            .catch(() => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -179,6 +179,11 @@
 | 
				
			|||||||
                v-model="localRole.can_manage_customfields"
 | 
					                v-model="localRole.can_manage_customfields"
 | 
				
			||||||
                label="Edit Custom Fields"
 | 
					                label="Edit Custom Fields"
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					              <q-checkbox
 | 
				
			||||||
 | 
					                v-if="!hosted"
 | 
				
			||||||
 | 
					                v-model="localRole.can_use_webterm"
 | 
				
			||||||
 | 
					                label="Use TRMM Server Web Terminal"
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </q-card-section>
 | 
					          </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -328,6 +333,11 @@
 | 
				
			|||||||
                v-model="localRole.can_manage_scripts"
 | 
					                v-model="localRole.can_manage_scripts"
 | 
				
			||||||
                label="Manage Scripts"
 | 
					                label="Manage Scripts"
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					              <q-checkbox
 | 
				
			||||||
 | 
					                v-if="!hosted"
 | 
				
			||||||
 | 
					                v-model="localRole.can_run_server_scripts"
 | 
				
			||||||
 | 
					                label="Run Scripts on TRMM Server"
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </q-card-section>
 | 
					          </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -409,7 +419,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { ref, watch } from "vue";
 | 
					import { computed, ref, watch } from "vue";
 | 
				
			||||||
 | 
					import { useStore } from "vuex";
 | 
				
			||||||
import { useDialogPluginComponent } from "quasar";
 | 
					import { useDialogPluginComponent } from "quasar";
 | 
				
			||||||
import { saveRole, editRole } from "@/api/accounts";
 | 
					import { saveRole, editRole } from "@/api/accounts";
 | 
				
			||||||
import { useClientDropdown, useSiteDropdown } from "@/composables/clients";
 | 
					import { useClientDropdown, useSiteDropdown } from "@/composables/clients";
 | 
				
			||||||
@@ -427,6 +438,10 @@ export default {
 | 
				
			|||||||
    // quasar setup
 | 
					    // quasar setup
 | 
				
			||||||
    const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
 | 
					    const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // store
 | 
				
			||||||
 | 
					    const store = useStore();
 | 
				
			||||||
 | 
					    const hosted = computed(() => store.state.hosted);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // dropdown setup
 | 
					    // dropdown setup
 | 
				
			||||||
    const { clientOptions } = useClientDropdown(true);
 | 
					    const { clientOptions } = useClientDropdown(true);
 | 
				
			||||||
    const { siteOptions } = useSiteDropdown(true);
 | 
					    const { siteOptions } = useSiteDropdown(true);
 | 
				
			||||||
@@ -511,6 +526,9 @@ export default {
 | 
				
			|||||||
          can_manage_roles: false,
 | 
					          can_manage_roles: false,
 | 
				
			||||||
          can_view_clients: [],
 | 
					          can_view_clients: [],
 | 
				
			||||||
          can_view_sites: [],
 | 
					          can_view_sites: [],
 | 
				
			||||||
 | 
					          // server scripts and web terminal
 | 
				
			||||||
 | 
					          can_run_server_scripts: false,
 | 
				
			||||||
 | 
					          can_use_webterm: false,
 | 
				
			||||||
          // reporting perms
 | 
					          // reporting perms
 | 
				
			||||||
          can_view_reports: false,
 | 
					          can_view_reports: false,
 | 
				
			||||||
          can_manage_reports: false,
 | 
					          can_manage_reports: false,
 | 
				
			||||||
@@ -550,6 +568,7 @@ export default {
 | 
				
			|||||||
      loading,
 | 
					      loading,
 | 
				
			||||||
      clientOptions,
 | 
					      clientOptions,
 | 
				
			||||||
      siteOptions,
 | 
					      siteOptions,
 | 
				
			||||||
 | 
					      hosted,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      onSubmit,
 | 
					      onSubmit,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -302,7 +302,9 @@ export default {
 | 
				
			|||||||
    async function getURLActions() {
 | 
					    async function getURLActions() {
 | 
				
			||||||
      menuLoading.value = true;
 | 
					      menuLoading.value = true;
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        urlActions.value = await fetchURLActions();
 | 
					        urlActions.value = (await fetchURLActions()).filter(
 | 
				
			||||||
 | 
					          (action) => action.action_type === "web",
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (urlActions.value.length === 0) {
 | 
					        if (urlActions.value.length === 0) {
 | 
				
			||||||
          notifyWarning(
 | 
					          notifyWarning(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -441,7 +441,7 @@ export default {
 | 
				
			|||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const result = await fetchAgentTasks(selectedAgent.value);
 | 
					        const result = await fetchAgentTasks(selectedAgent.value);
 | 
				
			||||||
        tasks.value = result.filter(
 | 
					        tasks.value = result.filter(
 | 
				
			||||||
          (task) => task.sync_status !== "pendingdeletion"
 | 
					          (task) => task.sync_status !== "pendingdeletion",
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      } catch (e) {
 | 
					      } catch (e) {
 | 
				
			||||||
        console.error(e);
 | 
					        console.error(e);
 | 
				
			||||||
@@ -495,7 +495,7 @@ export default {
 | 
				
			|||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const result = await runTask(
 | 
					        const result = await runTask(
 | 
				
			||||||
          task.id,
 | 
					          task.id,
 | 
				
			||||||
          task.policy ? { agent_id: selectedAgent.value } : {}
 | 
					          task.policy ? { agent_id: selectedAgent.value } : {},
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        notifySuccess(result);
 | 
					        notifySuccess(result);
 | 
				
			||||||
      } catch (e) {
 | 
					      } catch (e) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -666,6 +666,7 @@ export default {
 | 
				
			|||||||
        componentProps: {
 | 
					        componentProps: {
 | 
				
			||||||
          check: check,
 | 
					          check: check,
 | 
				
			||||||
          parent: !check ? { agent: selectedAgent.value } : undefined,
 | 
					          parent: !check ? { agent: selectedAgent.value } : undefined,
 | 
				
			||||||
 | 
					          plat: type === "script" ? agentPlatform.value : undefined,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      }).onOk(getChecks);
 | 
					      }).onOk(getChecks);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
          <q-tooltip class="bg-white text-primary">Close</q-tooltip>
 | 
					          <q-tooltip class="bg-white text-primary">Close</q-tooltip>
 | 
				
			||||||
        </q-btn>
 | 
					        </q-btn>
 | 
				
			||||||
      </q-bar>
 | 
					      </q-bar>
 | 
				
			||||||
      <q-card-section v-if="scriptOptions.length === 0">
 | 
					      <q-card-section v-if="filterByPlatformOptions.length === 0">
 | 
				
			||||||
        <p>You need to upload a script first</p>
 | 
					        <p>You need to upload a script first</p>
 | 
				
			||||||
        <p>Settings -> Script Manager</p>
 | 
					        <p>Settings -> Script Manager</p>
 | 
				
			||||||
      </q-card-section>
 | 
					      </q-card-section>
 | 
				
			||||||
@@ -19,7 +19,7 @@
 | 
				
			|||||||
            :rules="[(val) => !!val || '*Required']"
 | 
					            :rules="[(val) => !!val || '*Required']"
 | 
				
			||||||
            outlined
 | 
					            outlined
 | 
				
			||||||
            v-model="state.script"
 | 
					            v-model="state.script"
 | 
				
			||||||
            :options="scriptOptions"
 | 
					            :options="filterByPlatformOptions"
 | 
				
			||||||
            label="Select script"
 | 
					            label="Select script"
 | 
				
			||||||
            mapOptions
 | 
					            mapOptions
 | 
				
			||||||
            :disable="!!check"
 | 
					            :disable="!!check"
 | 
				
			||||||
@@ -140,6 +140,7 @@ export default {
 | 
				
			|||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    check: Object,
 | 
					    check: Object,
 | 
				
			||||||
    parent: Object, // {agent: agent.agent_id} or {policy: policy.id}
 | 
					    parent: Object, // {agent: agent.agent_id} or {policy: policy.id}
 | 
				
			||||||
 | 
					    plat: String,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  setup(props) {
 | 
					  setup(props) {
 | 
				
			||||||
    // setup quasar dialog
 | 
					    // setup quasar dialog
 | 
				
			||||||
@@ -148,11 +149,13 @@ export default {
 | 
				
			|||||||
    // setup script dropdown
 | 
					    // setup script dropdown
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
      script,
 | 
					      script,
 | 
				
			||||||
      scriptOptions,
 | 
					      filterByPlatformOptions,
 | 
				
			||||||
      defaultTimeout,
 | 
					      defaultTimeout,
 | 
				
			||||||
      defaultArgs,
 | 
					      defaultArgs,
 | 
				
			||||||
      defaultEnvVars,
 | 
					      defaultEnvVars,
 | 
				
			||||||
    } = useScriptDropdown(props.check ? props.check.script : undefined, {
 | 
					    } = useScriptDropdown({
 | 
				
			||||||
 | 
					      script: props.check ? props.check.script : undefined,
 | 
				
			||||||
 | 
					      plat: props.plat,
 | 
				
			||||||
      onMount: true,
 | 
					      onMount: true,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -181,7 +184,7 @@ export default {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // non-reactive data
 | 
					      // non-reactive data
 | 
				
			||||||
      failOptions,
 | 
					      failOptions,
 | 
				
			||||||
      scriptOptions,
 | 
					      filterByPlatformOptions,
 | 
				
			||||||
      severityOptions,
 | 
					      severityOptions,
 | 
				
			||||||
      envVarsLabel,
 | 
					      envVarsLabel,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,12 +20,18 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <br />
 | 
					        <br />
 | 
				
			||||||
        <div v-if="scriptInfo.stdout">
 | 
					        <div v-if="scriptInfo.stdout">
 | 
				
			||||||
          Standard Output
 | 
					          <script-output-copy-clip
 | 
				
			||||||
 | 
					            label="Standard Output"
 | 
				
			||||||
 | 
					            :data="scriptInfo.stdout"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
          <q-separator />
 | 
					          <q-separator />
 | 
				
			||||||
          <pre>{{ scriptInfo.stdout }}</pre>
 | 
					          <pre>{{ scriptInfo.stdout }}</pre>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div v-if="scriptInfo.stderr">
 | 
					        <div v-if="scriptInfo.stderr">
 | 
				
			||||||
          Standard Error
 | 
					          <script-output-copy-clip
 | 
				
			||||||
 | 
					            label="Standard Error"
 | 
				
			||||||
 | 
					            :data="scriptInfo.stderr"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
          <q-separator />
 | 
					          <q-separator />
 | 
				
			||||||
          <pre>{{ scriptInfo.stderr }}</pre>
 | 
					          <pre>{{ scriptInfo.stderr }}</pre>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@@ -43,8 +49,13 @@ import { computed } from "vue";
 | 
				
			|||||||
import { useStore } from "vuex";
 | 
					import { useStore } from "vuex";
 | 
				
			||||||
import { useDialogPluginComponent } from "quasar";
 | 
					import { useDialogPluginComponent } from "quasar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ScriptOutputCopyClip from "@/components/scripts/ScriptOutputCopyClip.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  name: "ScriptOutput",
 | 
					  name: "ScriptOutput",
 | 
				
			||||||
 | 
					  components: {
 | 
				
			||||||
 | 
					    ScriptOutputCopyClip,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  emits: [...useDialogPluginComponent.emits],
 | 
					  emits: [...useDialogPluginComponent.emits],
 | 
				
			||||||
  props: { scriptInfo: !Object },
 | 
					  props: { scriptInfo: !Object },
 | 
				
			||||||
  setup() {
 | 
					  setup() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,7 +83,7 @@
 | 
				
			|||||||
          <tactical-dropdown
 | 
					          <tactical-dropdown
 | 
				
			||||||
            :rules="[(val) => !!val || '*Required']"
 | 
					            :rules="[(val) => !!val || '*Required']"
 | 
				
			||||||
            v-model="state.script"
 | 
					            v-model="state.script"
 | 
				
			||||||
            :options="filteredScriptOptions"
 | 
					            :options="filterByPlatformOptions"
 | 
				
			||||||
            label="Select Script"
 | 
					            label="Select Script"
 | 
				
			||||||
            outlined
 | 
					            outlined
 | 
				
			||||||
            mapOptions
 | 
					            mapOptions
 | 
				
			||||||
@@ -210,8 +210,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { ref, computed, watch, onMounted } from "vue";
 | 
					import {
 | 
				
			||||||
import { useStore } from "vuex";
 | 
					  ref,
 | 
				
			||||||
 | 
					  reactive,
 | 
				
			||||||
 | 
					  computed,
 | 
				
			||||||
 | 
					  watch,
 | 
				
			||||||
 | 
					  onMounted,
 | 
				
			||||||
 | 
					  defineComponent,
 | 
				
			||||||
 | 
					} from "vue";
 | 
				
			||||||
import { useDialogPluginComponent } from "quasar";
 | 
					import { useDialogPluginComponent } from "quasar";
 | 
				
			||||||
import { useScriptDropdown } from "@/composables/scripts";
 | 
					import { useScriptDropdown } from "@/composables/scripts";
 | 
				
			||||||
import { useAgentDropdown } from "@/composables/agents";
 | 
					import { useAgentDropdown } from "@/composables/agents";
 | 
				
			||||||
@@ -219,7 +225,6 @@ import { useClientDropdown, useSiteDropdown } from "@/composables/clients";
 | 
				
			|||||||
import { runBulkAction } from "@/api/agents";
 | 
					import { runBulkAction } from "@/api/agents";
 | 
				
			||||||
import { notifySuccess } from "@/utils/notify";
 | 
					import { notifySuccess } from "@/utils/notify";
 | 
				
			||||||
import { cmdPlaceholder } from "@/composables/agents";
 | 
					import { cmdPlaceholder } from "@/composables/agents";
 | 
				
			||||||
import { removeExtraOptionCategories } from "@/utils/format";
 | 
					 | 
				
			||||||
import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
 | 
					import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ui imports
 | 
					// ui imports
 | 
				
			||||||
@@ -251,7 +256,7 @@ const patchModeOptions = [
 | 
				
			|||||||
  { label: "Install", value: "install" },
 | 
					  { label: "Install", value: "install" },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default defineComponent({
 | 
				
			||||||
  name: "BulkAction",
 | 
					  name: "BulkAction",
 | 
				
			||||||
  components: { TacticalDropdown },
 | 
					  components: { TacticalDropdown },
 | 
				
			||||||
  emits: [...useDialogPluginComponent.emits],
 | 
					  emits: [...useDialogPluginComponent.emits],
 | 
				
			||||||
@@ -259,14 +264,8 @@ export default {
 | 
				
			|||||||
    mode: !String,
 | 
					    mode: !String,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  setup(props) {
 | 
					  setup(props) {
 | 
				
			||||||
    // setup vuex store
 | 
					 | 
				
			||||||
    const store = useStore();
 | 
					 | 
				
			||||||
    const showCommunityScripts = computed(
 | 
					 | 
				
			||||||
      () => store.state.showCommunityScripts
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const shellOptions = computed(() => {
 | 
					    const shellOptions = computed(() => {
 | 
				
			||||||
      if (state.value.osType === "windows") {
 | 
					      if (state.osType === "windows") {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
          { label: "CMD", value: "cmd" },
 | 
					          { label: "CMD", value: "cmd" },
 | 
				
			||||||
          { label: "Powershell", value: "powershell" },
 | 
					          { label: "Powershell", value: "powershell" },
 | 
				
			||||||
@@ -293,7 +292,8 @@ export default {
 | 
				
			|||||||
    // dropdown setup
 | 
					    // dropdown setup
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
      script,
 | 
					      script,
 | 
				
			||||||
      scriptOptions,
 | 
					      plat,
 | 
				
			||||||
 | 
					      filterByPlatformOptions,
 | 
				
			||||||
      defaultTimeout,
 | 
					      defaultTimeout,
 | 
				
			||||||
      defaultArgs,
 | 
					      defaultArgs,
 | 
				
			||||||
      defaultEnvVars,
 | 
					      defaultEnvVars,
 | 
				
			||||||
@@ -304,7 +304,7 @@ export default {
 | 
				
			|||||||
    const { client, clientOptions, getClientOptions } = useClientDropdown();
 | 
					    const { client, clientOptions, getClientOptions } = useClientDropdown();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // bulk action logic
 | 
					    // bulk action logic
 | 
				
			||||||
    const state = ref({
 | 
					    const state = reactive({
 | 
				
			||||||
      mode: props.mode,
 | 
					      mode: props.mode,
 | 
				
			||||||
      target: "client",
 | 
					      target: "client",
 | 
				
			||||||
      monType: "all",
 | 
					      monType: "all",
 | 
				
			||||||
@@ -326,33 +326,39 @@ export default {
 | 
				
			|||||||
    const loading = ref(false);
 | 
					    const loading = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    watch(
 | 
					    watch(
 | 
				
			||||||
      () => state.value.target,
 | 
					      () => state.target,
 | 
				
			||||||
      () => {
 | 
					      () => {
 | 
				
			||||||
        client.value = null;
 | 
					        client.value = null;
 | 
				
			||||||
        site.value = null;
 | 
					        site.value = null;
 | 
				
			||||||
        agents.value = [];
 | 
					        agents.value = [];
 | 
				
			||||||
      }
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    plat.value = state.osType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    watch(
 | 
					    watch(
 | 
				
			||||||
      () => state.value.osType,
 | 
					      () => state.osType,
 | 
				
			||||||
      (newValue) => {
 | 
					      (newValue) => {
 | 
				
			||||||
        state.value.custom_shell = null;
 | 
					        state.custom_shell = null;
 | 
				
			||||||
        state.value.run_as_user = false;
 | 
					        state.run_as_user = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (newValue === "windows") {
 | 
					        if (newValue === "windows") {
 | 
				
			||||||
          state.value.shell = "cmd";
 | 
					          state.shell = "cmd";
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          state.value.shell = "/bin/bash";
 | 
					          state.shell = "/bin/bash";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					
 | 
				
			||||||
 | 
					        // set plat to filter script options
 | 
				
			||||||
 | 
					        if (newValue === "all") plat.value = undefined;
 | 
				
			||||||
 | 
					        else plat.value = newValue;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function submit() {
 | 
					    async function submit() {
 | 
				
			||||||
      loading.value = true;
 | 
					      loading.value = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const data = await runBulkAction(state.value);
 | 
					        const data = await runBulkAction(state);
 | 
				
			||||||
        notifySuccess(data);
 | 
					        notifySuccess(data);
 | 
				
			||||||
        onDialogHide();
 | 
					        onDialogHide();
 | 
				
			||||||
      } catch (e) {}
 | 
					      } catch (e) {}
 | 
				
			||||||
@@ -362,9 +368,7 @@ export default {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const supportsRunAsUser = () => {
 | 
					    const supportsRunAsUser = () => {
 | 
				
			||||||
      const modes = ["script", "command"];
 | 
					      const modes = ["script", "command"];
 | 
				
			||||||
      return (
 | 
					      return state.osType === "windows" && modes.includes(state.mode);
 | 
				
			||||||
        state.value.osType === "windows" && modes.includes(state.value.mode)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // set modal title and caption
 | 
					    // set modal title and caption
 | 
				
			||||||
@@ -372,25 +376,10 @@ export default {
 | 
				
			|||||||
      return props.mode === "command"
 | 
					      return props.mode === "command"
 | 
				
			||||||
        ? "Run Bulk Command"
 | 
					        ? "Run Bulk Command"
 | 
				
			||||||
        : props.mode === "script"
 | 
					        : props.mode === "script"
 | 
				
			||||||
        ? "Run Bulk Script"
 | 
					          ? "Run Bulk Script"
 | 
				
			||||||
        : props.mode === "patch"
 | 
					          : props.mode === "patch"
 | 
				
			||||||
        ? "Bulk Patch Management"
 | 
					            ? "Bulk Patch Management"
 | 
				
			||||||
        : "";
 | 
					            : "";
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const filteredScriptOptions = computed(() => {
 | 
					 | 
				
			||||||
      if (props.mode !== "script") return [];
 | 
					 | 
				
			||||||
      if (state.value.osType === "all") return scriptOptions.value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return removeExtraOptionCategories(
 | 
					 | 
				
			||||||
        scriptOptions.value.filter(
 | 
					 | 
				
			||||||
          (script) =>
 | 
					 | 
				
			||||||
            script.category ||
 | 
					 | 
				
			||||||
            !script.supported_platforms ||
 | 
					 | 
				
			||||||
            script.supported_platforms.length === 0 ||
 | 
					 | 
				
			||||||
            script.supported_platforms.includes(state.value.osType)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // component lifecycle hooks
 | 
					    // component lifecycle hooks
 | 
				
			||||||
@@ -398,7 +387,7 @@ export default {
 | 
				
			|||||||
      getAgentOptions();
 | 
					      getAgentOptions();
 | 
				
			||||||
      getSiteOptions();
 | 
					      getSiteOptions();
 | 
				
			||||||
      getClientOptions();
 | 
					      getClientOptions();
 | 
				
			||||||
      if (props.mode === "script") getScriptOptions(showCommunityScripts.value);
 | 
					      if (props.mode === "script") getScriptOptions();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
@@ -407,7 +396,7 @@ export default {
 | 
				
			|||||||
      agentOptions,
 | 
					      agentOptions,
 | 
				
			||||||
      clientOptions,
 | 
					      clientOptions,
 | 
				
			||||||
      siteOptions,
 | 
					      siteOptions,
 | 
				
			||||||
      filteredScriptOptions,
 | 
					      filterByPlatformOptions,
 | 
				
			||||||
      loading,
 | 
					      loading,
 | 
				
			||||||
      shellOptions,
 | 
					      shellOptions,
 | 
				
			||||||
      filteredOsTypeOptions,
 | 
					      filteredOsTypeOptions,
 | 
				
			||||||
@@ -433,5 +422,5 @@ export default {
 | 
				
			|||||||
      onDialogHide,
 | 
					      onDialogHide,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,9 +39,9 @@
 | 
				
			|||||||
      <q-form @submit.prevent="sendScript">
 | 
					      <q-form @submit.prevent="sendScript">
 | 
				
			||||||
        <q-card-section>
 | 
					        <q-card-section>
 | 
				
			||||||
          <tactical-dropdown
 | 
					          <tactical-dropdown
 | 
				
			||||||
            :rules="[(val) => !!val || '*Required']"
 | 
					            :rules="[(val: number) => !!val || '*Required']"
 | 
				
			||||||
            v-model="state.script"
 | 
					            v-model="state.script"
 | 
				
			||||||
            :options="filteredScriptOptions"
 | 
					            :options="filterByPlatformOptions"
 | 
				
			||||||
            label="Select script"
 | 
					            label="Select script"
 | 
				
			||||||
            outlined
 | 
					            outlined
 | 
				
			||||||
            mapOptions
 | 
					            mapOptions
 | 
				
			||||||
@@ -130,7 +130,7 @@
 | 
				
			|||||||
        </q-card-section>
 | 
					        </q-card-section>
 | 
				
			||||||
        <q-card-section v-if="state.output === 'collector'">
 | 
					        <q-card-section v-if="state.output === 'collector'">
 | 
				
			||||||
          <tactical-dropdown
 | 
					          <tactical-dropdown
 | 
				
			||||||
            :rules="[(val) => !!val || '*Required']"
 | 
					            :rules="[(val: number) => !!val || '*Required']"
 | 
				
			||||||
            outlined
 | 
					            outlined
 | 
				
			||||||
            v-model="state.custom_field"
 | 
					            v-model="state.custom_field"
 | 
				
			||||||
            :options="customFieldOptions"
 | 
					            :options="customFieldOptions"
 | 
				
			||||||
@@ -175,6 +175,8 @@
 | 
				
			|||||||
          class="q-pl-md q-pr-md q-pt-none q-ma-none scroll"
 | 
					          class="q-pl-md q-pr-md q-pt-none q-ma-none scroll"
 | 
				
			||||||
          style="max-height: 50vh"
 | 
					          style="max-height: 50vh"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					          <script-output-copy-clip label="Output" :data="ret" />
 | 
				
			||||||
 | 
					          <q-separator />
 | 
				
			||||||
          <pre>{{ ret }}</pre>
 | 
					          <pre>{{ ret }}</pre>
 | 
				
			||||||
        </q-card-section>
 | 
					        </q-card-section>
 | 
				
			||||||
      </q-form>
 | 
					      </q-form>
 | 
				
			||||||
@@ -182,22 +184,23 @@
 | 
				
			|||||||
  </q-dialog>
 | 
					  </q-dialog>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script setup lang="ts">
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { ref, watch, computed } from "vue";
 | 
					import { ref, watch } from "vue";
 | 
				
			||||||
import { useDialogPluginComponent, openURL } from "quasar";
 | 
					import { useDialogPluginComponent, openURL } from "quasar";
 | 
				
			||||||
import { useScriptDropdown } from "@/composables/scripts";
 | 
					import { useScriptDropdown } from "@/composables/scripts";
 | 
				
			||||||
import { useCustomFieldDropdown } from "@/composables/core";
 | 
					import { useCustomFieldDropdown } from "@/composables/core";
 | 
				
			||||||
import { runScript } from "@/api/agents";
 | 
					import { runScript } from "@/api/agents";
 | 
				
			||||||
import { notifySuccess } from "@/utils/notify";
 | 
					import { notifySuccess } from "@/utils/notify";
 | 
				
			||||||
import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
 | 
					import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
 | 
				
			||||||
import {
 | 
					import { formatScriptSyntax } from "@/utils/format";
 | 
				
			||||||
  formatScriptSyntax,
 | 
					 | 
				
			||||||
  removeExtraOptionCategories,
 | 
					 | 
				
			||||||
} from "@/utils/format";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
//ui imports
 | 
					//ui imports
 | 
				
			||||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
 | 
					import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
 | 
				
			||||||
 | 
					import ScriptOutputCopyClip from "@/components/scripts/ScriptOutputCopyClip.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// types
 | 
				
			||||||
 | 
					import type { Agent } from "@/types/agents";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// static data
 | 
					// static data
 | 
				
			||||||
const outputOptions = [
 | 
					const outputOptions = [
 | 
				
			||||||
@@ -208,110 +211,71 @@ const outputOptions = [
 | 
				
			|||||||
  { label: "Save results to Agent Notes", value: "note" },
 | 
					  { label: "Save results to Agent Notes", value: "note" },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					// emits
 | 
				
			||||||
  name: "RunScript",
 | 
					defineEmits([...useDialogPluginComponent.emits]);
 | 
				
			||||||
  emits: [...useDialogPluginComponent.emits],
 | 
					 | 
				
			||||||
  components: { TacticalDropdown },
 | 
					 | 
				
			||||||
  props: {
 | 
					 | 
				
			||||||
    agent: !Object,
 | 
					 | 
				
			||||||
    script: Number,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  setup(props) {
 | 
					 | 
				
			||||||
    // setup quasar dialog plugin
 | 
					 | 
				
			||||||
    const { dialogRef, onDialogHide } = useDialogPluginComponent();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // setup dropdowns
 | 
					// props
 | 
				
			||||||
    const {
 | 
					const props = defineProps<{
 | 
				
			||||||
      script,
 | 
					  agent: Agent;
 | 
				
			||||||
      scriptOptions,
 | 
					  script?: number;
 | 
				
			||||||
      defaultTimeout,
 | 
					}>();
 | 
				
			||||||
      defaultArgs,
 | 
					 | 
				
			||||||
      defaultEnvVars,
 | 
					 | 
				
			||||||
      syntax,
 | 
					 | 
				
			||||||
      link,
 | 
					 | 
				
			||||||
    } = useScriptDropdown(props.script, {
 | 
					 | 
				
			||||||
      onMount: true,
 | 
					 | 
				
			||||||
      filterByPlatform: props.agent.plat,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // main run script functionaity
 | 
					// setup quasar dialog plugin
 | 
				
			||||||
    const state = ref({
 | 
					const { dialogRef, onDialogHide } = useDialogPluginComponent();
 | 
				
			||||||
      output: "wait",
 | 
					 | 
				
			||||||
      emails: [],
 | 
					 | 
				
			||||||
      emailMode: "default",
 | 
					 | 
				
			||||||
      custom_field: null,
 | 
					 | 
				
			||||||
      save_all_output: false,
 | 
					 | 
				
			||||||
      script,
 | 
					 | 
				
			||||||
      args: defaultArgs,
 | 
					 | 
				
			||||||
      env_vars: defaultEnvVars,
 | 
					 | 
				
			||||||
      timeout: defaultTimeout,
 | 
					 | 
				
			||||||
      run_as_user: false,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const ret = ref(null);
 | 
					// setup dropdowns
 | 
				
			||||||
    const loading = ref(false);
 | 
					const {
 | 
				
			||||||
    const maximized = ref(false);
 | 
					  script,
 | 
				
			||||||
 | 
					  filterByPlatformOptions,
 | 
				
			||||||
 | 
					  defaultTimeout,
 | 
				
			||||||
 | 
					  defaultArgs,
 | 
				
			||||||
 | 
					  defaultEnvVars,
 | 
				
			||||||
 | 
					  syntax,
 | 
				
			||||||
 | 
					  link,
 | 
				
			||||||
 | 
					} = useScriptDropdown({
 | 
				
			||||||
 | 
					  script: props.script,
 | 
				
			||||||
 | 
					  plat: props.agent.plat,
 | 
				
			||||||
 | 
					  onMount: true,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function sendScript() {
 | 
					// main run script functionaity
 | 
				
			||||||
      ret.value = null;
 | 
					const state = ref({
 | 
				
			||||||
      loading.value = true;
 | 
					  output: "wait",
 | 
				
			||||||
 | 
					  emails: [],
 | 
				
			||||||
 | 
					  emailMode: "default",
 | 
				
			||||||
 | 
					  custom_field: null,
 | 
				
			||||||
 | 
					  save_all_output: false,
 | 
				
			||||||
 | 
					  script,
 | 
				
			||||||
 | 
					  args: defaultArgs,
 | 
				
			||||||
 | 
					  env_vars: defaultEnvVars,
 | 
				
			||||||
 | 
					  timeout: defaultTimeout,
 | 
				
			||||||
 | 
					  run_as_user: false,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      ret.value = await runScript(props.agent.agent_id, state.value);
 | 
					const ret = ref(null);
 | 
				
			||||||
      loading.value = false;
 | 
					const loading = ref(false);
 | 
				
			||||||
      if (state.value.output === "forget") {
 | 
					const maximized = ref(false);
 | 
				
			||||||
        onDialogHide();
 | 
					 | 
				
			||||||
        notifySuccess(ret.value);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function openScriptURL() {
 | 
					async function sendScript() {
 | 
				
			||||||
      link.value ? openURL(link.value) : null;
 | 
					  ret.value = null;
 | 
				
			||||||
    }
 | 
					  loading.value = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filteredScriptOptions = computed(() => {
 | 
					  ret.value = await runScript(props.agent.agent_id, state.value);
 | 
				
			||||||
      return removeExtraOptionCategories(
 | 
					  loading.value = false;
 | 
				
			||||||
        scriptOptions.value.filter(
 | 
					  if (state.value.output === "forget") {
 | 
				
			||||||
          (script) =>
 | 
					    onDialogHide();
 | 
				
			||||||
            script.category ||
 | 
					    if (ret.value) notifySuccess(ret.value);
 | 
				
			||||||
            !script.supported_platforms ||
 | 
					  }
 | 
				
			||||||
            script.supported_platforms.length === 0 ||
 | 
					}
 | 
				
			||||||
            script.supported_platforms.includes(props.agent.plat)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // watchers
 | 
					function openScriptURL() {
 | 
				
			||||||
    watch(
 | 
					  link.value ? openURL(link.value) : null;
 | 
				
			||||||
      [() => state.value.output, () => state.value.emailMode],
 | 
					}
 | 
				
			||||||
      () => (state.value.emails = [])
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					// watchers
 | 
				
			||||||
      // reactive data
 | 
					watch(
 | 
				
			||||||
      state,
 | 
					  [() => state.value.output, () => state.value.emailMode],
 | 
				
			||||||
      loading,
 | 
					  () => (state.value.emails = []),
 | 
				
			||||||
      filteredScriptOptions,
 | 
					);
 | 
				
			||||||
      link,
 | 
					 | 
				
			||||||
      syntax,
 | 
					 | 
				
			||||||
      ret,
 | 
					 | 
				
			||||||
      maximized,
 | 
					 | 
				
			||||||
      customFieldOptions,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // non-reactive data
 | 
					 | 
				
			||||||
      outputOptions,
 | 
					 | 
				
			||||||
      runAsUserToolTip,
 | 
					 | 
				
			||||||
      envVarsLabel,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      //methods
 | 
					 | 
				
			||||||
      formatScriptSyntax,
 | 
					 | 
				
			||||||
      sendScript,
 | 
					 | 
				
			||||||
      openScriptURL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // quasar dialog plugin
 | 
					 | 
				
			||||||
      dialogRef,
 | 
					 | 
				
			||||||
      onDialogHide,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,6 +104,9 @@
 | 
				
			|||||||
            type="submit"
 | 
					            type="submit"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </q-card-actions>
 | 
					        </q-card-actions>
 | 
				
			||||||
 | 
					        <q-card-section v-if="ret !== null"
 | 
				
			||||||
 | 
					          ><script-output-copy-clip label="Output" :data="ret" /> <q-separator
 | 
				
			||||||
 | 
					        /></q-card-section>
 | 
				
			||||||
        <q-card-section
 | 
					        <q-card-section
 | 
				
			||||||
          v-if="ret !== null"
 | 
					          v-if="ret !== null"
 | 
				
			||||||
          class="q-pl-md q-pr-md q-pt-none q-ma-none scroll"
 | 
					          class="q-pl-md q-pr-md q-pt-none q-ma-none scroll"
 | 
				
			||||||
@@ -124,8 +127,13 @@ import { sendAgentCommand } from "@/api/agents";
 | 
				
			|||||||
import { cmdPlaceholder } from "@/composables/agents";
 | 
					import { cmdPlaceholder } from "@/composables/agents";
 | 
				
			||||||
import { runAsUserToolTip } from "@/constants/constants";
 | 
					import { runAsUserToolTip } from "@/constants/constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ScriptOutputCopyClip from "@/components/scripts/ScriptOutputCopyClip.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  name: "SendCommand",
 | 
					  name: "SendCommand",
 | 
				
			||||||
 | 
					  components: {
 | 
				
			||||||
 | 
					    ScriptOutputCopyClip,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  emits: [...useDialogPluginComponent.emits],
 | 
					  emits: [...useDialogPluginComponent.emits],
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    agent: !Object,
 | 
					    agent: !Object,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <q-dialog ref="dialog" @hide="onHide">
 | 
					  <q-dialog ref="dialogRef" @hide="onDialogHide">
 | 
				
			||||||
    <q-card style="width: 90vw; max-width: 90vw">
 | 
					    <q-card style="width: 90vw; max-width: 90vw">
 | 
				
			||||||
      <q-bar>
 | 
					      <q-bar>
 | 
				
			||||||
        {{ title }}
 | 
					        {{ alertTemplate ? "Edit Alert Template" : "Add Alert Template" }}
 | 
				
			||||||
        <q-space />
 | 
					        <q-space />
 | 
				
			||||||
        <q-btn dense flat icon="close" v-close-popup>
 | 
					        <q-btn dense flat icon="close" v-close-popup>
 | 
				
			||||||
          <q-tooltip class="bg-white text-primary">Close</q-tooltip>
 | 
					          <q-tooltip class="bg-white text-primary">Close</q-tooltip>
 | 
				
			||||||
@@ -150,50 +150,62 @@
 | 
				
			|||||||
              <span style="text-decoration: underline; cursor: help"
 | 
					              <span style="text-decoration: underline; cursor: help"
 | 
				
			||||||
                >Alert Failure Settings
 | 
					                >Alert Failure Settings
 | 
				
			||||||
                <q-tooltip>
 | 
					                <q-tooltip>
 | 
				
			||||||
                  The selected script will run when an alert is triggered. This
 | 
					                  The selected action will run when an alert is triggered.
 | 
				
			||||||
                  script will run on any online agent.
 | 
					 | 
				
			||||||
                </q-tooltip>
 | 
					                </q-tooltip>
 | 
				
			||||||
              </span>
 | 
					              </span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <q-card-section>
 | 
					            <q-card-section>
 | 
				
			||||||
              <q-select
 | 
					              <q-option-group
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                v-model="template.action_type"
 | 
				
			||||||
                label="Failure action"
 | 
					                class="q-pb-sm"
 | 
				
			||||||
 | 
					                :options="actionTypeOptions"
 | 
				
			||||||
                dense
 | 
					                dense
 | 
				
			||||||
                options-dense
 | 
					                inline
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <tactical-dropdown
 | 
				
			||||||
 | 
					                v-if="template.action_type == 'script'"
 | 
				
			||||||
 | 
					                class="q-mb-sm"
 | 
				
			||||||
 | 
					                label="Failure script"
 | 
				
			||||||
                outlined
 | 
					                outlined
 | 
				
			||||||
                clearable
 | 
					                clearable
 | 
				
			||||||
                v-model="template.action"
 | 
					                v-model="template.action"
 | 
				
			||||||
                :options="scriptOptions"
 | 
					                :options="scriptOptions"
 | 
				
			||||||
                map-options
 | 
					                mapOptions
 | 
				
			||||||
                emit-value
 | 
					                filterable
 | 
				
			||||||
                @update:model-value="setScriptDefaults('failure')"
 | 
					                :rules="[(val) => !!val || '*Required']"
 | 
				
			||||||
              >
 | 
					              />
 | 
				
			||||||
                <template v-slot:option="scope">
 | 
					
 | 
				
			||||||
                  <q-item
 | 
					              <tactical-dropdown
 | 
				
			||||||
                    v-if="!scope.opt.category"
 | 
					                v-else-if="template.action_type == 'server'"
 | 
				
			||||||
                    v-bind="scope.itemProps"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                    class="q-pl-lg"
 | 
					                label="Failure script"
 | 
				
			||||||
                  >
 | 
					                outlined
 | 
				
			||||||
                    <q-item-section>
 | 
					                clearable
 | 
				
			||||||
                      <q-item-label v-html="scope.opt.label"></q-item-label>
 | 
					                v-model="template.action"
 | 
				
			||||||
                    </q-item-section>
 | 
					                :options="serverScriptOptions"
 | 
				
			||||||
                  </q-item>
 | 
					                mapOptions
 | 
				
			||||||
                  <q-item-label
 | 
					                filterable
 | 
				
			||||||
                    v-if="scope.opt.category"
 | 
					              />
 | 
				
			||||||
                    v-bind="scope.itemProps"
 | 
					
 | 
				
			||||||
                    header
 | 
					              <tactical-dropdown
 | 
				
			||||||
                    class="q-pa-sm"
 | 
					                v-else
 | 
				
			||||||
                    >{{ scope.opt.category }}</q-item-label
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                  >
 | 
					                label="Failure Web Hook"
 | 
				
			||||||
                </template>
 | 
					                outlined
 | 
				
			||||||
              </q-select>
 | 
					                clearable
 | 
				
			||||||
 | 
					                v-model="template.action_rest"
 | 
				
			||||||
 | 
					                :options="restActionOptions"
 | 
				
			||||||
 | 
					                mapOptions
 | 
				
			||||||
 | 
					                filterable
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <q-select
 | 
					              <q-select
 | 
				
			||||||
 | 
					                v-if="template.action_type !== 'rest'"
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                dense
 | 
					                dense
 | 
				
			||||||
                label="Failure action arguments (press Enter after typing each argument)"
 | 
					                label="Failure script arguments (press Enter after typing each argument)"
 | 
				
			||||||
                filled
 | 
					                filled
 | 
				
			||||||
                v-model="template.action_args"
 | 
					                v-model="template.action_args"
 | 
				
			||||||
                use-input
 | 
					                use-input
 | 
				
			||||||
@@ -205,9 +217,10 @@
 | 
				
			|||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <q-select
 | 
					              <q-select
 | 
				
			||||||
 | 
					                v-if="template.action_type !== 'rest'"
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                dense
 | 
					                dense
 | 
				
			||||||
                label="Failure action environment vars (press Enter after typing each key=value pair)"
 | 
					                label="Failure script environment vars (press Enter after typing each key=value pair)"
 | 
				
			||||||
                filled
 | 
					                filled
 | 
				
			||||||
                v-model="template.action_env_vars"
 | 
					                v-model="template.action_env_vars"
 | 
				
			||||||
                use-input
 | 
					                use-input
 | 
				
			||||||
@@ -219,16 +232,15 @@
 | 
				
			|||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <q-input
 | 
					              <q-input
 | 
				
			||||||
 | 
					                v-if="template.action_type !== 'rest'"
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                label="Failure action timeout (seconds)"
 | 
					                label="Failure script timeout (seconds)"
 | 
				
			||||||
                outlined
 | 
					                outlined
 | 
				
			||||||
                type="number"
 | 
					                type="number"
 | 
				
			||||||
                v-model.number="template.action_timeout"
 | 
					                v-model.number="template.action_timeout"
 | 
				
			||||||
                dense
 | 
					                dense
 | 
				
			||||||
                :rules="[
 | 
					                :rules="[
 | 
				
			||||||
                  (val) => !!val || 'Failure action timeout is required',
 | 
					                  (val) => !!val || 'Failure script timeout is required',
 | 
				
			||||||
                  (val) => val > 0 || 'Timeout must be greater than 0',
 | 
					 | 
				
			||||||
                  (val) => val <= 60 || 'Timeout must be 60 or less',
 | 
					 | 
				
			||||||
                ]"
 | 
					                ]"
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            </q-card-section>
 | 
					            </q-card-section>
 | 
				
			||||||
@@ -237,50 +249,61 @@
 | 
				
			|||||||
              <span style="text-decoration: underline; cursor: help"
 | 
					              <span style="text-decoration: underline; cursor: help"
 | 
				
			||||||
                >Alert Resolved Settings
 | 
					                >Alert Resolved Settings
 | 
				
			||||||
                <q-tooltip>
 | 
					                <q-tooltip>
 | 
				
			||||||
                  The selected script will run when an alert is resolved. This
 | 
					                  The selected action will run when an alert is resolved.
 | 
				
			||||||
                  script will run on any online agent.
 | 
					 | 
				
			||||||
                </q-tooltip>
 | 
					                </q-tooltip>
 | 
				
			||||||
              </span>
 | 
					              </span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <q-card-section>
 | 
					            <q-card-section>
 | 
				
			||||||
              <q-select
 | 
					              <q-option-group
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                v-model="template.resolved_action_type"
 | 
				
			||||||
                label="Resolved Action"
 | 
					                class="q-pb-sm"
 | 
				
			||||||
 | 
					                :options="actionTypeOptions"
 | 
				
			||||||
                dense
 | 
					                dense
 | 
				
			||||||
                options-dense
 | 
					                inline
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <tactical-dropdown
 | 
				
			||||||
 | 
					                v-if="template.resolved_action_type === 'script'"
 | 
				
			||||||
 | 
					                class="q-mb-sm"
 | 
				
			||||||
 | 
					                label="Resolved Script"
 | 
				
			||||||
                outlined
 | 
					                outlined
 | 
				
			||||||
                clearable
 | 
					                clearable
 | 
				
			||||||
                v-model="template.resolved_action"
 | 
					                v-model="template.resolved_action"
 | 
				
			||||||
                :options="scriptOptions"
 | 
					                :options="scriptOptions"
 | 
				
			||||||
                map-options
 | 
					                mapOptions
 | 
				
			||||||
                emit-value
 | 
					                filterable
 | 
				
			||||||
                @update:model-value="setScriptDefaults('resolved')"
 | 
					              />
 | 
				
			||||||
              >
 | 
					
 | 
				
			||||||
                <template v-slot:option="scope">
 | 
					              <tactical-dropdown
 | 
				
			||||||
                  <q-item
 | 
					                v-else-if="template.resolved_action_type === 'server'"
 | 
				
			||||||
                    v-if="!scope.opt.category"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                    v-bind="scope.itemProps"
 | 
					                label="Resolved Script"
 | 
				
			||||||
                    class="q-pl-lg"
 | 
					                outlined
 | 
				
			||||||
                  >
 | 
					                clearable
 | 
				
			||||||
                    <q-item-section>
 | 
					                v-model="template.resolved_action"
 | 
				
			||||||
                      <q-item-label v-html="scope.opt.label"></q-item-label>
 | 
					                :options="serverScriptOptions"
 | 
				
			||||||
                    </q-item-section>
 | 
					                mapOptions
 | 
				
			||||||
                  </q-item>
 | 
					                filterable
 | 
				
			||||||
                  <q-item-label
 | 
					              />
 | 
				
			||||||
                    v-if="scope.opt.category"
 | 
					
 | 
				
			||||||
                    v-bind="scope.itemProps"
 | 
					              <tactical-dropdown
 | 
				
			||||||
                    header
 | 
					                v-else
 | 
				
			||||||
                    class="q-pa-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                    >{{ scope.opt.category }}</q-item-label
 | 
					                label="Resolved Web Hook"
 | 
				
			||||||
                  >
 | 
					                outlined
 | 
				
			||||||
                </template>
 | 
					                clearable
 | 
				
			||||||
              </q-select>
 | 
					                v-model="template.resolved_action_rest"
 | 
				
			||||||
 | 
					                :options="restActionOptions"
 | 
				
			||||||
 | 
					                mapOptions
 | 
				
			||||||
 | 
					                filterable
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <q-select
 | 
					              <q-select
 | 
				
			||||||
 | 
					                v-if="template.resolved_action_type !== 'rest'"
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                dense
 | 
					                dense
 | 
				
			||||||
                label="Resolved action arguments (press Enter after typing each argument)"
 | 
					                label="Resolved script arguments (press Enter after typing each argument)"
 | 
				
			||||||
                filled
 | 
					                filled
 | 
				
			||||||
                v-model="template.resolved_action_args"
 | 
					                v-model="template.resolved_action_args"
 | 
				
			||||||
                use-input
 | 
					                use-input
 | 
				
			||||||
@@ -292,6 +315,7 @@
 | 
				
			|||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <q-select
 | 
					              <q-select
 | 
				
			||||||
 | 
					                v-if="template.resolved_action_type !== 'rest'"
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                dense
 | 
					                dense
 | 
				
			||||||
                label="Resolved action environment vars (press Enter after typing each key=value pair)"
 | 
					                label="Resolved action environment vars (press Enter after typing each key=value pair)"
 | 
				
			||||||
@@ -306,16 +330,15 @@
 | 
				
			|||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <q-input
 | 
					              <q-input
 | 
				
			||||||
 | 
					                v-if="template.resolved_action_type !== 'rest'"
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                label="Resolved action timeout (seconds)"
 | 
					                label="Resolved script timeout (seconds)"
 | 
				
			||||||
                outlined
 | 
					                outlined
 | 
				
			||||||
                type="number"
 | 
					                type="number"
 | 
				
			||||||
                v-model.number="template.resolved_action_timeout"
 | 
					                v-model.number="template.resolved_action_timeout"
 | 
				
			||||||
                dense
 | 
					                dense
 | 
				
			||||||
                :rules="[
 | 
					                :rules="[
 | 
				
			||||||
                  (val) => !!val || 'Resolved action timeout is required',
 | 
					                  (val) => !!val || 'Resolved script timeout is required',
 | 
				
			||||||
                  (val) => val > 0 || 'Timeout must be greater than 0',
 | 
					 | 
				
			||||||
                  (val) => val <= 60 || 'Timeout must be 60 or less',
 | 
					 | 
				
			||||||
                ]"
 | 
					                ]"
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            </q-card-section>
 | 
					            </q-card-section>
 | 
				
			||||||
@@ -324,7 +347,7 @@
 | 
				
			|||||||
              <span style="text-decoration: underline; cursor: help"
 | 
					              <span style="text-decoration: underline; cursor: help"
 | 
				
			||||||
                >Run actions only on
 | 
					                >Run actions only on
 | 
				
			||||||
                <q-tooltip>
 | 
					                <q-tooltip>
 | 
				
			||||||
                  The selected script will only run on the following types of
 | 
					                  The selected action will only run on the following types of
 | 
				
			||||||
                  alerts
 | 
					                  alerts
 | 
				
			||||||
                </q-tooltip>
 | 
					                </q-tooltip>
 | 
				
			||||||
              </span>
 | 
					              </span>
 | 
				
			||||||
@@ -674,7 +697,7 @@
 | 
				
			|||||||
                left-label
 | 
					                left-label
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
              <q-toggle
 | 
					              <q-toggle
 | 
				
			||||||
                v-model="template.check_text_on_resolved"
 | 
					                v-model="template.task_text_on_resolved"
 | 
				
			||||||
                label="Text"
 | 
					                label="Text"
 | 
				
			||||||
                color="green"
 | 
					                color="green"
 | 
				
			||||||
                left-label
 | 
					                left-label
 | 
				
			||||||
@@ -688,18 +711,23 @@
 | 
				
			|||||||
              v-if="step > 1"
 | 
					              v-if="step > 1"
 | 
				
			||||||
              flat
 | 
					              flat
 | 
				
			||||||
              color="primary"
 | 
					              color="primary"
 | 
				
			||||||
              @click="$refs.stepper.previous()"
 | 
					              @click="stepper?.previous()"
 | 
				
			||||||
              label="Back"
 | 
					              label="Back"
 | 
				
			||||||
              class="q-mr-xs"
 | 
					              class="q-mr-xs"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <q-btn
 | 
					            <q-btn
 | 
				
			||||||
              v-if="step < 5"
 | 
					              v-if="step < 5"
 | 
				
			||||||
              @click="$refs.stepper.next()"
 | 
					              @click="stepper?.next()"
 | 
				
			||||||
              color="primary"
 | 
					              color="primary"
 | 
				
			||||||
              label="Next"
 | 
					              label="Next"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <q-space />
 | 
					            <q-space />
 | 
				
			||||||
            <q-btn @click="onSubmit" color="primary" label="Submit" />
 | 
					            <q-btn
 | 
				
			||||||
 | 
					              @click="onSubmit"
 | 
				
			||||||
 | 
					              color="primary"
 | 
				
			||||||
 | 
					              label="Submit"
 | 
				
			||||||
 | 
					              :loading="loading"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
          </q-stepper-navigation>
 | 
					          </q-stepper-navigation>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
      </q-stepper>
 | 
					      </q-stepper>
 | 
				
			||||||
@@ -707,195 +735,279 @@
 | 
				
			|||||||
  </q-dialog>
 | 
					  </q-dialog>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script setup lang="ts">
 | 
				
			||||||
import mixins from "@/mixins/mixins";
 | 
					import { computed, ref, reactive, watch, nextTick } from "vue";
 | 
				
			||||||
import { mapGetters } from "vuex";
 | 
					import { useStore } from "vuex";
 | 
				
			||||||
 | 
					import { useQuasar, useDialogPluginComponent, type QStepper } from "quasar";
 | 
				
			||||||
 | 
					import { useScriptDropdown } from "@/composables/scripts";
 | 
				
			||||||
 | 
					import { useURLActionDropdown } from "@/composables/core";
 | 
				
			||||||
 | 
					import { notifyError, notifySuccess } from "@/utils/notify";
 | 
				
			||||||
 | 
					import { addAlertTemplate, saveAlertTemplate } from "@/api/alerts";
 | 
				
			||||||
 | 
					import { isValidEmail } from "@/utils/validation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					// components
 | 
				
			||||||
  name: "AlertTemplateForm",
 | 
					import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
 | 
				
			||||||
  emits: ["hide", "ok", "cancel"],
 | 
					
 | 
				
			||||||
  mixins: [mixins],
 | 
					// types
 | 
				
			||||||
  props: { alertTemplate: Object },
 | 
					import type { AlertTemplate, AlertSeverity } from "@/types/alerts";
 | 
				
			||||||
  data() {
 | 
					
 | 
				
			||||||
    return {
 | 
					// store
 | 
				
			||||||
      step: 1,
 | 
					const store = useStore();
 | 
				
			||||||
      template: {
 | 
					const hosted = computed(() => store.state.hosted);
 | 
				
			||||||
        name: "",
 | 
					const server_scripts_enabled = computed(
 | 
				
			||||||
        is_active: true,
 | 
					  () => store.state.server_scripts_enabled,
 | 
				
			||||||
        action: null,
 | 
					);
 | 
				
			||||||
        action_args: [],
 | 
					
 | 
				
			||||||
        action_env_vars: [],
 | 
					// props
 | 
				
			||||||
        action_timeout: 15,
 | 
					const props = defineProps<{
 | 
				
			||||||
        resolved_action: null,
 | 
					  alertTemplate?: AlertTemplate;
 | 
				
			||||||
        resolved_action_args: [],
 | 
					}>();
 | 
				
			||||||
        resolved_action_env_vars: [],
 | 
					
 | 
				
			||||||
        resolved_action_timeout: 15,
 | 
					// emits
 | 
				
			||||||
        email_recipients: [],
 | 
					defineEmits([...useDialogPluginComponent.emits]);
 | 
				
			||||||
        email_from: "",
 | 
					
 | 
				
			||||||
        text_recipients: [],
 | 
					// setup quasar plugins
 | 
				
			||||||
        agent_email_on_resolved: false,
 | 
					const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
 | 
				
			||||||
        agent_text_on_resolved: false,
 | 
					const $q = useQuasar();
 | 
				
			||||||
        agent_always_email: null,
 | 
					
 | 
				
			||||||
        agent_always_text: null,
 | 
					const step = ref(1);
 | 
				
			||||||
        agent_always_alert: null,
 | 
					
 | 
				
			||||||
        agent_periodic_alert_days: 0,
 | 
					// setup script dropdowns
 | 
				
			||||||
        agent_script_actions: true,
 | 
					const {
 | 
				
			||||||
        check_email_alert_severity: [],
 | 
					  script: failureAction,
 | 
				
			||||||
        check_text_alert_severity: [],
 | 
					  defaultArgs: failureArgs,
 | 
				
			||||||
        check_dashboard_alert_severity: [],
 | 
					  defaultEnvVars: failureEnvVars,
 | 
				
			||||||
        check_email_on_resolved: false,
 | 
					  defaultTimeout: failureTimeout,
 | 
				
			||||||
        check_text_on_resolved: false,
 | 
					  serverScriptOptions,
 | 
				
			||||||
        check_always_email: null,
 | 
					  scriptOptions,
 | 
				
			||||||
        check_always_text: null,
 | 
					} = useScriptDropdown({ script: props.alertTemplate?.action, onMount: true });
 | 
				
			||||||
        check_always_alert: null,
 | 
					
 | 
				
			||||||
        check_periodic_alert_days: 0,
 | 
					const {
 | 
				
			||||||
        check_script_actions: true,
 | 
					  script: resolvedAction,
 | 
				
			||||||
        task_email_alert_severity: [],
 | 
					  defaultArgs: resolvedArgs,
 | 
				
			||||||
        task_text_alert_severity: [],
 | 
					  defaultEnvVars: resolvedEnvVars,
 | 
				
			||||||
        task_dashboard_alert_severity: [],
 | 
					  defaultTimeout: resolvedTimeout,
 | 
				
			||||||
        task_email_on_resolved: false,
 | 
					} = useScriptDropdown({
 | 
				
			||||||
        task_text_on_resolved: false,
 | 
					  script: props.alertTemplate?.resolved_action,
 | 
				
			||||||
        task_always_email: null,
 | 
					  onMount: true,
 | 
				
			||||||
        task_always_text: null,
 | 
					});
 | 
				
			||||||
        task_always_alert: null,
 | 
					
 | 
				
			||||||
        task_periodic_alert_days: 0,
 | 
					// setup custom field dropdown
 | 
				
			||||||
        task_script_actions: true,
 | 
					const { restActionOptions } = useURLActionDropdown({ onMount: true });
 | 
				
			||||||
      },
 | 
					
 | 
				
			||||||
      scriptOptions: [],
 | 
					// alert template form logic
 | 
				
			||||||
      severityOptions: [
 | 
					const template: AlertTemplate = props.alertTemplate
 | 
				
			||||||
        { label: "Error", value: "error" },
 | 
					  ? reactive(Object.assign({}, { ...props.alertTemplate }))
 | 
				
			||||||
        { label: "Warning", value: "warning" },
 | 
					  : reactive({
 | 
				
			||||||
        { label: "Informational", value: "info" },
 | 
					      id: 0,
 | 
				
			||||||
      ],
 | 
					      name: "",
 | 
				
			||||||
      thumbStyle: {
 | 
					      is_active: true,
 | 
				
			||||||
        right: "2px",
 | 
					      action_type: "script",
 | 
				
			||||||
        borderRadius: "5px",
 | 
					      action: failureAction,
 | 
				
			||||||
        backgroundColor: "#027be3",
 | 
					      action_rest: undefined,
 | 
				
			||||||
        width: "5px",
 | 
					      action_args: failureArgs,
 | 
				
			||||||
        opacity: 0.75,
 | 
					      action_env_vars: failureEnvVars,
 | 
				
			||||||
      },
 | 
					      action_timeout: failureTimeout,
 | 
				
			||||||
    };
 | 
					      resolved_action_type: "script",
 | 
				
			||||||
 | 
					      resolved_action: resolvedAction,
 | 
				
			||||||
 | 
					      resolved_action_rest: undefined,
 | 
				
			||||||
 | 
					      resolved_action_args: resolvedArgs,
 | 
				
			||||||
 | 
					      resolved_action_env_vars: resolvedEnvVars,
 | 
				
			||||||
 | 
					      resolved_action_timeout: resolvedTimeout,
 | 
				
			||||||
 | 
					      email_recipients: [] as string[],
 | 
				
			||||||
 | 
					      email_from: "",
 | 
				
			||||||
 | 
					      text_recipients: [] as string[],
 | 
				
			||||||
 | 
					      agent_email_on_resolved: false,
 | 
				
			||||||
 | 
					      agent_text_on_resolved: false,
 | 
				
			||||||
 | 
					      agent_always_email: null,
 | 
				
			||||||
 | 
					      agent_always_text: null,
 | 
				
			||||||
 | 
					      agent_always_alert: null,
 | 
				
			||||||
 | 
					      agent_periodic_alert_days: 0,
 | 
				
			||||||
 | 
					      agent_script_actions: true,
 | 
				
			||||||
 | 
					      check_email_alert_severity: [] as AlertSeverity[],
 | 
				
			||||||
 | 
					      check_text_alert_severity: [] as AlertSeverity[],
 | 
				
			||||||
 | 
					      check_dashboard_alert_severity: [] as AlertSeverity[],
 | 
				
			||||||
 | 
					      check_email_on_resolved: false,
 | 
				
			||||||
 | 
					      check_text_on_resolved: false,
 | 
				
			||||||
 | 
					      check_always_email: null,
 | 
				
			||||||
 | 
					      check_always_text: null,
 | 
				
			||||||
 | 
					      check_always_alert: null,
 | 
				
			||||||
 | 
					      check_periodic_alert_days: 0,
 | 
				
			||||||
 | 
					      check_script_actions: true,
 | 
				
			||||||
 | 
					      task_email_alert_severity: [] as AlertSeverity[],
 | 
				
			||||||
 | 
					      task_text_alert_severity: [] as AlertSeverity[],
 | 
				
			||||||
 | 
					      task_dashboard_alert_severity: [] as AlertSeverity[],
 | 
				
			||||||
 | 
					      task_email_on_resolved: false,
 | 
				
			||||||
 | 
					      task_text_on_resolved: false,
 | 
				
			||||||
 | 
					      task_always_email: null,
 | 
				
			||||||
 | 
					      task_always_text: null,
 | 
				
			||||||
 | 
					      task_always_alert: null,
 | 
				
			||||||
 | 
					      task_periodic_alert_days: 0,
 | 
				
			||||||
 | 
					      task_script_actions: true,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reset selected script if action type is changed
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					  () => template.action_type,
 | 
				
			||||||
 | 
					  () => {
 | 
				
			||||||
 | 
					    template.action_rest = undefined;
 | 
				
			||||||
 | 
					    template.action = undefined;
 | 
				
			||||||
 | 
					    template.action_args = [];
 | 
				
			||||||
 | 
					    template.action_env_vars = [];
 | 
				
			||||||
 | 
					    template.action_timeout = 30;
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  computed: {
 | 
					);
 | 
				
			||||||
    ...mapGetters(["showCommunityScripts"]),
 | 
					
 | 
				
			||||||
    title() {
 | 
					watch(
 | 
				
			||||||
      return this.editing ? "Edit Alert Template" : "Add Alert Template";
 | 
					  () => template.resolved_action_type,
 | 
				
			||||||
    },
 | 
					  () => {
 | 
				
			||||||
    editing() {
 | 
					    template.resolved_action_rest = undefined;
 | 
				
			||||||
      return !!this.alertTemplate;
 | 
					    template.resolved_action = undefined;
 | 
				
			||||||
    },
 | 
					    template.resolved_action_args = [];
 | 
				
			||||||
 | 
					    template.resolved_action_env_vars = [];
 | 
				
			||||||
 | 
					    template.resolved_action_timeout = 30;
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					);
 | 
				
			||||||
    setScriptDefaults(type) {
 | 
					
 | 
				
			||||||
      if (type === "failure") {
 | 
					// sync selected script to scriptdropdown
 | 
				
			||||||
        const script = this.scriptOptions.find(
 | 
					// only add watchers if editting template
 | 
				
			||||||
          (i) => i.value === this.template.action
 | 
					if (props.alertTemplate) {
 | 
				
			||||||
        );
 | 
					  watch(
 | 
				
			||||||
        this.template.action_args = script.args;
 | 
					    () => template.action,
 | 
				
			||||||
        this.template.action_env_vars = script.env_vars;
 | 
					    (newValue) => {
 | 
				
			||||||
      } else if (type === "resolved") {
 | 
					      if (newValue) {
 | 
				
			||||||
        const script = this.scriptOptions.find(
 | 
					        failureAction.value = newValue;
 | 
				
			||||||
          (i) => i.value === this.template.resolved_action
 | 
					
 | 
				
			||||||
        );
 | 
					        // wait for the script change to happen
 | 
				
			||||||
        this.template.resolved_action_args = script.args;
 | 
					        nextTick(() => {
 | 
				
			||||||
        this.template.resolved_action_env_vars = script.env_vars;
 | 
					          template.action_args = failureArgs.value;
 | 
				
			||||||
      }
 | 
					          template.action_env_vars = failureEnvVars.value;
 | 
				
			||||||
    },
 | 
					          template.action_timeout = failureTimeout.value;
 | 
				
			||||||
    toggleAddEmail() {
 | 
					 | 
				
			||||||
      this.$q
 | 
					 | 
				
			||||||
        .dialog({
 | 
					 | 
				
			||||||
          title: "Add email",
 | 
					 | 
				
			||||||
          prompt: {
 | 
					 | 
				
			||||||
            model: "",
 | 
					 | 
				
			||||||
            isValid: (val) => this.isValidEmail(val),
 | 
					 | 
				
			||||||
            type: "email",
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          cancel: true,
 | 
					 | 
				
			||||||
          ok: { label: "Add", color: "primary" },
 | 
					 | 
				
			||||||
          persistent: false,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .onOk((data) => {
 | 
					 | 
				
			||||||
          this.template.email_recipients.push(data);
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    toggleAddSMSNumber() {
 | 
					  );
 | 
				
			||||||
      this.$q
 | 
					
 | 
				
			||||||
        .dialog({
 | 
					  watch(
 | 
				
			||||||
          title: "Add number",
 | 
					    () => template.resolved_action,
 | 
				
			||||||
          message:
 | 
					    (newValue) => {
 | 
				
			||||||
            "Use E.164 format: must have the <b>+</b> symbol and <span class='text-red'>country code</span>, followed by the <span class='text-green'>phone number</span> e.g. <b>+<span class='text-red'>1</span><span class='text-green'>2131231234</span></b>",
 | 
					      if (newValue) {
 | 
				
			||||||
          prompt: {
 | 
					        resolvedAction.value = newValue;
 | 
				
			||||||
            model: "",
 | 
					
 | 
				
			||||||
          },
 | 
					        // wait for the script change to happen
 | 
				
			||||||
          html: true,
 | 
					        nextTick(() => {
 | 
				
			||||||
          cancel: true,
 | 
					          template.resolved_action_args = resolvedArgs.value;
 | 
				
			||||||
          ok: { label: "Add", color: "primary" },
 | 
					          template.resolved_action_env_vars = resolvedEnvVars.value;
 | 
				
			||||||
          persistent: false,
 | 
					          template.resolved_action_timeout = resolvedTimeout.value;
 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .onOk((data) => {
 | 
					 | 
				
			||||||
          this.template.text_recipients.push(data);
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    removeEmail(email) {
 | 
					 | 
				
			||||||
      const removed = this.template.email_recipients.filter((k) => k !== email);
 | 
					 | 
				
			||||||
      this.template.email_recipients = removed;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    removeSMSNumber(num) {
 | 
					 | 
				
			||||||
      const removed = this.template.text_recipients.filter((k) => k !== num);
 | 
					 | 
				
			||||||
      this.template.text_recipients = removed;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    onSubmit() {
 | 
					 | 
				
			||||||
      if (!this.template.name) {
 | 
					 | 
				
			||||||
        this.notifyError("Name needs to be set");
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      this.$q.loading.show();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (this.editing) {
 | 
					 | 
				
			||||||
        this.$axios
 | 
					 | 
				
			||||||
          .put(`alerts/templates/${this.template.id}/`, this.template)
 | 
					 | 
				
			||||||
          .then(() => {
 | 
					 | 
				
			||||||
            this.$q.loading.hide();
 | 
					 | 
				
			||||||
            this.onOk();
 | 
					 | 
				
			||||||
            this.notifySuccess("Alert Template edited!");
 | 
					 | 
				
			||||||
          })
 | 
					 | 
				
			||||||
          .catch(() => {
 | 
					 | 
				
			||||||
            this.$q.loading.hide();
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        this.$axios
 | 
					 | 
				
			||||||
          .post("alerts/templates/", this.template)
 | 
					 | 
				
			||||||
          .then(() => {
 | 
					 | 
				
			||||||
            this.$q.loading.hide();
 | 
					 | 
				
			||||||
            this.onOk();
 | 
					 | 
				
			||||||
            this.notifySuccess("Alert Template was added!");
 | 
					 | 
				
			||||||
          })
 | 
					 | 
				
			||||||
          .catch(() => {
 | 
					 | 
				
			||||||
            this.$q.loading.hide();
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    show() {
 | 
					  );
 | 
				
			||||||
      this.$refs.dialog.show();
 | 
					}
 | 
				
			||||||
    },
 | 
					
 | 
				
			||||||
    hide() {
 | 
					const severityOptions = [
 | 
				
			||||||
      this.$refs.dialog.hide();
 | 
					  { label: "Error", value: "error" },
 | 
				
			||||||
    },
 | 
					  { label: "Warning", value: "warning" },
 | 
				
			||||||
    onHide() {
 | 
					  { label: "Informational", value: "info" },
 | 
				
			||||||
      this.$emit("hide");
 | 
					];
 | 
				
			||||||
    },
 | 
					
 | 
				
			||||||
    onOk() {
 | 
					const staticActionTypeOptions = [
 | 
				
			||||||
      this.$emit("ok");
 | 
					  { label: "Send a Web Hook", value: "rest" },
 | 
				
			||||||
      this.hide();
 | 
					  { label: "Run script on Agent", value: "script" },
 | 
				
			||||||
    },
 | 
					  { label: "Run script on TRMM Server", value: "server" },
 | 
				
			||||||
  },
 | 
					];
 | 
				
			||||||
  mounted() {
 | 
					
 | 
				
			||||||
    this.getScriptOptions(this.showCommunityScripts).then(
 | 
					const actionTypeOptions = computed(() => {
 | 
				
			||||||
      (options) => (this.scriptOptions = Object.freeze(options))
 | 
					  // don't show for hosted at all
 | 
				
			||||||
 | 
					  if (hosted.value) {
 | 
				
			||||||
 | 
					    return staticActionTypeOptions.filter(
 | 
				
			||||||
 | 
					      (option) => option.value !== "server",
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    // Copy alertTemplate prop locally
 | 
					  }
 | 
				
			||||||
    if (this.editing) Object.assign(this.template, this.alertTemplate);
 | 
					  // disable the server script radio button if feature is disabled globally
 | 
				
			||||||
  },
 | 
					  const modifiedOptions = staticActionTypeOptions.map((option) => {
 | 
				
			||||||
};
 | 
					    if (!server_scripts_enabled.value && option.value === "server") {
 | 
				
			||||||
 | 
					      return { ...option, disable: true };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return option;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return modifiedOptions;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const stepper = ref<QStepper | null>(null);
 | 
				
			||||||
 | 
					function toggleAddEmail() {
 | 
				
			||||||
 | 
					  $q.dialog({
 | 
				
			||||||
 | 
					    title: "Add email",
 | 
				
			||||||
 | 
					    prompt: {
 | 
				
			||||||
 | 
					      model: "",
 | 
				
			||||||
 | 
					      isValid: (val) => isValidEmail(val),
 | 
				
			||||||
 | 
					      type: "email",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    cancel: true,
 | 
				
			||||||
 | 
					    ok: { label: "Add", color: "primary" },
 | 
				
			||||||
 | 
					    persistent: false,
 | 
				
			||||||
 | 
					  }).onOk((data) => {
 | 
				
			||||||
 | 
					    template.email_recipients.push(data);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function toggleAddSMSNumber() {
 | 
				
			||||||
 | 
					  $q.dialog({
 | 
				
			||||||
 | 
					    title: "Add number",
 | 
				
			||||||
 | 
					    message:
 | 
				
			||||||
 | 
					      "Use E.164 format: must have the <b>+</b> symbol and <span class='text-red'>country code</span>, followed by the <span class='text-green'>phone number</span> e.g. <b>+<span class='text-red'>1</span><span class='text-green'>2131231234</span></b>",
 | 
				
			||||||
 | 
					    prompt: {
 | 
				
			||||||
 | 
					      model: "",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    html: true,
 | 
				
			||||||
 | 
					    cancel: true,
 | 
				
			||||||
 | 
					    ok: { label: "Add", color: "primary" },
 | 
				
			||||||
 | 
					    persistent: false,
 | 
				
			||||||
 | 
					  }).onOk((data: string) => {
 | 
				
			||||||
 | 
					    template.text_recipients.push(data);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function removeEmail(email: string) {
 | 
				
			||||||
 | 
					  const removed = template.email_recipients.filter((k) => k !== email);
 | 
				
			||||||
 | 
					  template.email_recipients = removed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function removeSMSNumber(num: string) {
 | 
				
			||||||
 | 
					  const removed = template.text_recipients.filter((k) => k !== num);
 | 
				
			||||||
 | 
					  template.text_recipients = removed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const loading = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function onSubmit() {
 | 
				
			||||||
 | 
					  // TODO rework this ghetto form validation
 | 
				
			||||||
 | 
					  if (!template.name) {
 | 
				
			||||||
 | 
					    notifyError("Name needs to be set");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  loading.value = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (props.alertTemplate) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await saveAlertTemplate(template.id, template);
 | 
				
			||||||
 | 
					      notifySuccess("Alert Template edited!");
 | 
				
			||||||
 | 
					      onDialogOK();
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      loading.value = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await addAlertTemplate(template);
 | 
				
			||||||
 | 
					      notifySuccess("Alert Template edited!");
 | 
				
			||||||
 | 
					      onDialogOK();
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      loading.value = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -191,24 +191,6 @@
 | 
				
			|||||||
              }}</q-badge>
 | 
					              }}</q-badge>
 | 
				
			||||||
            </q-td>
 | 
					            </q-td>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
 | 
					 | 
				
			||||||
          <template v-slot:body-cell-alert_time="props">
 | 
					 | 
				
			||||||
            <q-td :props="props">
 | 
					 | 
				
			||||||
              {{ formatDate(props.value) }}
 | 
					 | 
				
			||||||
            </q-td>
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <template v-slot:body-cell-resolve_on="props">
 | 
					 | 
				
			||||||
            <q-td :props="props">
 | 
					 | 
				
			||||||
              {{ formatDate(props.value) }}
 | 
					 | 
				
			||||||
            </q-td>
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <template v-slot:body-cell-snoozed_until="props">
 | 
					 | 
				
			||||||
            <q-td :props="props">
 | 
					 | 
				
			||||||
              {{ formatDate(props.value) }}
 | 
					 | 
				
			||||||
            </q-td>
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
        </q-table>
 | 
					        </q-table>
 | 
				
			||||||
      </q-card-section>
 | 
					      </q-card-section>
 | 
				
			||||||
    </q-card>
 | 
					    </q-card>
 | 
				
			||||||
@@ -265,6 +247,7 @@ export default {
 | 
				
			|||||||
          field: "alert_time",
 | 
					          field: "alert_time",
 | 
				
			||||||
          align: "left",
 | 
					          align: "left",
 | 
				
			||||||
          sortable: true,
 | 
					          sortable: true,
 | 
				
			||||||
 | 
					          format: (a) => this.formatDate(a),
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          name: "hostname",
 | 
					          name: "hostname",
 | 
				
			||||||
@@ -296,11 +279,12 @@ export default {
 | 
				
			|||||||
          sortable: true,
 | 
					          sortable: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          name: "resolve_on",
 | 
					          name: "resolved_on",
 | 
				
			||||||
          label: "Resolved On",
 | 
					          label: "Resolved On",
 | 
				
			||||||
          field: "resolve_on",
 | 
					          field: "resolved_on",
 | 
				
			||||||
          align: "left",
 | 
					          align: "left",
 | 
				
			||||||
          sortable: true,
 | 
					          sortable: true,
 | 
				
			||||||
 | 
					          format: (a) => this.formatDate(a),
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          name: "snoozed_until",
 | 
					          name: "snoozed_until",
 | 
				
			||||||
@@ -308,6 +292,7 @@ export default {
 | 
				
			|||||||
          field: "snoozed_until",
 | 
					          field: "snoozed_until",
 | 
				
			||||||
          align: "left",
 | 
					          align: "left",
 | 
				
			||||||
          sortable: true,
 | 
					          sortable: true,
 | 
				
			||||||
 | 
					          format: (a) => this.formatDate(a),
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        { name: "actions", label: "Actions", align: "left" },
 | 
					        { name: "actions", label: "Actions", align: "left" },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
@@ -328,7 +313,7 @@ export default {
 | 
				
			|||||||
      return this.columns.map((column) => {
 | 
					      return this.columns.map((column) => {
 | 
				
			||||||
        if (column.name === "snoozed_until") {
 | 
					        if (column.name === "snoozed_until") {
 | 
				
			||||||
          if (this.includeSnoozed) return column.name;
 | 
					          if (this.includeSnoozed) return column.name;
 | 
				
			||||||
        } else if (column.name === "resolve_on") {
 | 
					        } else if (column.name === "resolved_on") {
 | 
				
			||||||
          if (this.includeResolved) return column.name;
 | 
					          if (this.includeResolved) return column.name;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return column.name;
 | 
					          return column.name;
 | 
				
			||||||
@@ -340,7 +325,7 @@ export default {
 | 
				
			|||||||
    getClients() {
 | 
					    getClients() {
 | 
				
			||||||
      this.$axios.get("/clients/").then((r) => {
 | 
					      this.$axios.get("/clients/").then((r) => {
 | 
				
			||||||
        this.clientsOptions = Object.freeze(
 | 
					        this.clientsOptions = Object.freeze(
 | 
				
			||||||
          r.data.map((client) => ({ label: client.name, value: client.id }))
 | 
					          r.data.map((client) => ({ label: client.name, value: client.id })),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@
 | 
				
			|||||||
          <q-tab name="customfields" label="Custom Fields" />
 | 
					          <q-tab name="customfields" label="Custom Fields" />
 | 
				
			||||||
          <q-tab name="keystore" label="Key Store" />
 | 
					          <q-tab name="keystore" label="Key Store" />
 | 
				
			||||||
          <q-tab name="urlactions" label="URL Actions" />
 | 
					          <q-tab name="urlactions" label="URL Actions" />
 | 
				
			||||||
 | 
					          <q-tab name="webhooks" label="Web Hooks" />
 | 
				
			||||||
          <q-tab name="retention" label="Retention" />
 | 
					          <q-tab name="retention" label="Retention" />
 | 
				
			||||||
          <q-tab name="apikeys" label="API Keys" />
 | 
					          <q-tab name="apikeys" label="API Keys" />
 | 
				
			||||||
          <!-- <q-tab name="openai" label="Open AI" /> -->
 | 
					          <!-- <q-tab name="openai" label="Open AI" /> -->
 | 
				
			||||||
@@ -41,6 +42,51 @@
 | 
				
			|||||||
                    <q-tooltip> Runs at 35mins past every hour </q-tooltip>
 | 
					                    <q-tooltip> Runs at 35mins past every hour </q-tooltip>
 | 
				
			||||||
                  </q-checkbox>
 | 
					                  </q-checkbox>
 | 
				
			||||||
                </q-card-section>
 | 
					                </q-card-section>
 | 
				
			||||||
 | 
					                <q-card-section v-if="!hosted" class="row">
 | 
				
			||||||
 | 
					                  <q-checkbox
 | 
				
			||||||
 | 
					                    v-model="settings.enable_server_scripts"
 | 
				
			||||||
 | 
					                    label="Enable server side scripts"
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <q-tooltip
 | 
				
			||||||
 | 
					                      >Allow running scripts on TRMM server for alert
 | 
				
			||||||
 | 
					                      failure/resolve actions</q-tooltip
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                  </q-checkbox>
 | 
				
			||||||
 | 
					                  <q-btn
 | 
				
			||||||
 | 
					                    size="sm"
 | 
				
			||||||
 | 
					                    round
 | 
				
			||||||
 | 
					                    dense
 | 
				
			||||||
 | 
					                    flat
 | 
				
			||||||
 | 
					                    icon="warning"
 | 
				
			||||||
 | 
					                    @click="
 | 
				
			||||||
 | 
					                      openURL(
 | 
				
			||||||
 | 
					                        'https://docs.tacticalrmm.com/functions/permissions/#permissions-with-extra-security-implications',
 | 
				
			||||||
 | 
					                      )
 | 
				
			||||||
 | 
					                    "
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                  </q-btn>
 | 
				
			||||||
 | 
					                </q-card-section>
 | 
				
			||||||
 | 
					                <q-card-section v-if="!hosted" class="row">
 | 
				
			||||||
 | 
					                  <q-checkbox
 | 
				
			||||||
 | 
					                    v-model="settings.enable_server_webterminal"
 | 
				
			||||||
 | 
					                    label="Enable web terminal"
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <q-tooltip>Enable the web terminal</q-tooltip>
 | 
				
			||||||
 | 
					                  </q-checkbox>
 | 
				
			||||||
 | 
					                  <q-btn
 | 
				
			||||||
 | 
					                    size="sm"
 | 
				
			||||||
 | 
					                    roundenable_server_webterminal
 | 
				
			||||||
 | 
					                    dense
 | 
				
			||||||
 | 
					                    flat
 | 
				
			||||||
 | 
					                    icon="warning"
 | 
				
			||||||
 | 
					                    @click="
 | 
				
			||||||
 | 
					                      openURL(
 | 
				
			||||||
 | 
					                        'https://docs.tacticalrmm.com/functions/permissions/#permissions-with-extra-security-implications',
 | 
				
			||||||
 | 
					                      )
 | 
				
			||||||
 | 
					                    "
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                  </q-btn>
 | 
				
			||||||
 | 
					                </q-card-section>
 | 
				
			||||||
                <q-card-section class="row">
 | 
					                <q-card-section class="row">
 | 
				
			||||||
                  <div class="col-4">Default agent timezone:</div>
 | 
					                  <div class="col-4">Default agent timezone:</div>
 | 
				
			||||||
                  <div class="col-2"></div>
 | 
					                  <div class="col-2"></div>
 | 
				
			||||||
@@ -125,6 +171,24 @@
 | 
				
			|||||||
                    class="col-6"
 | 
					                    class="col-6"
 | 
				
			||||||
                  />
 | 
					                  />
 | 
				
			||||||
                </q-card-section>
 | 
					                </q-card-section>
 | 
				
			||||||
 | 
					                <q-card-section class="row">
 | 
				
			||||||
 | 
					                  <div class="col-4 flex items-center">
 | 
				
			||||||
 | 
					                    Receive notifications on:
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  <div class="col-2"></div>
 | 
				
			||||||
 | 
					                  <q-checkbox
 | 
				
			||||||
 | 
					                    dense
 | 
				
			||||||
 | 
					                    v-model="settings.notify_on_info_alerts"
 | 
				
			||||||
 | 
					                    class="col-3"
 | 
				
			||||||
 | 
					                    label="Informational Alerts"
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                  <q-checkbox
 | 
				
			||||||
 | 
					                    dense
 | 
				
			||||||
 | 
					                    v-model="settings.notify_on_warning_alerts"
 | 
				
			||||||
 | 
					                    class="col-3"
 | 
				
			||||||
 | 
					                    label="Warning Alerts"
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </q-card-section>
 | 
				
			||||||
                <q-card-section class="row">
 | 
					                <q-card-section class="row">
 | 
				
			||||||
                  <div class="col-4">Agent Debug Level:</div>
 | 
					                  <div class="col-4">Agent Debug Level:</div>
 | 
				
			||||||
                  <div class="col-2"></div>
 | 
					                  <div class="col-2"></div>
 | 
				
			||||||
@@ -488,17 +552,28 @@
 | 
				
			|||||||
                  </q-input>
 | 
					                  </q-input>
 | 
				
			||||||
                </q-card-section>
 | 
					                </q-card-section>
 | 
				
			||||||
              </q-tab-panel>
 | 
					              </q-tab-panel>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <!-- custom fields -->
 | 
				
			||||||
              <q-tab-panel name="customfields">
 | 
					              <q-tab-panel name="customfields">
 | 
				
			||||||
                <CustomFields />
 | 
					                <CustomFields />
 | 
				
			||||||
              </q-tab-panel>
 | 
					              </q-tab-panel>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <!-- key store -->
 | 
				
			||||||
              <q-tab-panel name="keystore">
 | 
					              <q-tab-panel name="keystore">
 | 
				
			||||||
                <KeyStoreTable />
 | 
					                <KeyStoreTable />
 | 
				
			||||||
              </q-tab-panel>
 | 
					              </q-tab-panel>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <!-- url actions -->
 | 
				
			||||||
              <q-tab-panel name="urlactions">
 | 
					              <q-tab-panel name="urlactions">
 | 
				
			||||||
                <URLActionsTable />
 | 
					                <URLActionsTable type="web" />
 | 
				
			||||||
              </q-tab-panel>
 | 
					              </q-tab-panel>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <!-- web hooks -->
 | 
				
			||||||
 | 
					              <q-tab-panel name="webhooks">
 | 
				
			||||||
 | 
					                <URLActionsTable type="rest" />
 | 
				
			||||||
 | 
					              </q-tab-panel>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <!-- retention -->
 | 
				
			||||||
              <q-tab-panel name="retention">
 | 
					              <q-tab-panel name="retention">
 | 
				
			||||||
                <q-card-section class="row">
 | 
					                <q-card-section class="row">
 | 
				
			||||||
                  <div class="col-4">Check History (days):</div>
 | 
					                  <div class="col-4">Check History (days):</div>
 | 
				
			||||||
@@ -656,6 +731,7 @@ export default {
 | 
				
			|||||||
    KeyStoreTable,
 | 
					    KeyStoreTable,
 | 
				
			||||||
    URLActionsTable,
 | 
					    URLActionsTable,
 | 
				
			||||||
    APIKeysTable,
 | 
					    APIKeysTable,
 | 
				
			||||||
 | 
					    // ServerTasksTable,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mixins: [mixins],
 | 
					  mixins: [mixins],
 | 
				
			||||||
  data() {
 | 
					  data() {
 | 
				
			||||||
@@ -827,6 +903,7 @@ export default {
 | 
				
			|||||||
              });
 | 
					              });
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            this.$emit("close");
 | 
					            this.$emit("close");
 | 
				
			||||||
 | 
					            this.$store.dispatch("getDashInfo", false);
 | 
				
			||||||
            this.notifySuccess("Settings were edited!");
 | 
					            this.notifySuccess("Settings were edited!");
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										160
									
								
								src/components/modals/coresettings/TestURLAction.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/components/modals/coresettings/TestURLAction.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <q-dialog ref="dialogRef" @hide="onDialogHide">
 | 
				
			||||||
 | 
					    <q-card class="q-dialog-plugin" style="width: 80vw">
 | 
				
			||||||
 | 
					      <q-bar>
 | 
				
			||||||
 | 
					        Testing {{ urlAction.name }}
 | 
				
			||||||
 | 
					        <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-card-section>
 | 
				
			||||||
 | 
					        <q-option-group
 | 
				
			||||||
 | 
					          v-model="runAgainst"
 | 
				
			||||||
 | 
					          :options="runAgainstOptions"
 | 
				
			||||||
 | 
					          inline
 | 
				
			||||||
 | 
					          dense
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <q-card-section v-if="runAgainst === 'agent'">
 | 
				
			||||||
 | 
					        <tactical-dropdown
 | 
				
			||||||
 | 
					          v-model="agent"
 | 
				
			||||||
 | 
					          :options="agentOptions"
 | 
				
			||||||
 | 
					          label="Agents"
 | 
				
			||||||
 | 
					          mapOptions
 | 
				
			||||||
 | 
					          filterable
 | 
				
			||||||
 | 
					          dense
 | 
				
			||||||
 | 
					          filled
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <q-card-section v-else-if="runAgainst === 'site'">
 | 
				
			||||||
 | 
					        <tactical-dropdown
 | 
				
			||||||
 | 
					          v-model="site"
 | 
				
			||||||
 | 
					          :options="siteOptions"
 | 
				
			||||||
 | 
					          label="Sites"
 | 
				
			||||||
 | 
					          mapOptions
 | 
				
			||||||
 | 
					          filterable
 | 
				
			||||||
 | 
					          dense
 | 
				
			||||||
 | 
					          filled
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <q-card-section v-else-if="runAgainst === 'client'">
 | 
				
			||||||
 | 
					        <tactical-dropdown
 | 
				
			||||||
 | 
					          v-model="client"
 | 
				
			||||||
 | 
					          :options="clientOptions"
 | 
				
			||||||
 | 
					          label="Client"
 | 
				
			||||||
 | 
					          mapOptions
 | 
				
			||||||
 | 
					          filterable
 | 
				
			||||||
 | 
					          dense
 | 
				
			||||||
 | 
					          filled
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <q-card-section style="height: 60vh" class="scroll">
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          URL:
 | 
				
			||||||
 | 
					          <code>{{ return_url }}</code>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <br />
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          Body
 | 
				
			||||||
 | 
					          <q-separator />
 | 
				
			||||||
 | 
					          <code>{{ return_request }}</code>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <br />
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          Response
 | 
				
			||||||
 | 
					          <q-separator />
 | 
				
			||||||
 | 
					          <code>{{ return_result }}</code>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <q-card-actions align="right">
 | 
				
			||||||
 | 
					        <q-btn flat label="Close" v-close-popup />
 | 
				
			||||||
 | 
					        <q-btn
 | 
				
			||||||
 | 
					          :loading="loading"
 | 
				
			||||||
 | 
					          flat
 | 
				
			||||||
 | 
					          label="Run"
 | 
				
			||||||
 | 
					          color="primary"
 | 
				
			||||||
 | 
					          @click="submit"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </q-card-actions>
 | 
				
			||||||
 | 
					    </q-card>
 | 
				
			||||||
 | 
					  </q-dialog>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					// composition imports
 | 
				
			||||||
 | 
					import { ref, reactive, computed } from "vue";
 | 
				
			||||||
 | 
					import { useDialogPluginComponent } from "quasar";
 | 
				
			||||||
 | 
					import { useAgentDropdown } from "@/composables/agents";
 | 
				
			||||||
 | 
					import { useSiteDropdown, useClientDropdown } from "@/composables/clients";
 | 
				
			||||||
 | 
					import { runTestURLAction } from "@/api/core";
 | 
				
			||||||
 | 
					import { URLAction } from "@/types/core/urlactions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ui imports
 | 
				
			||||||
 | 
					import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// define emits
 | 
				
			||||||
 | 
					defineEmits([...useDialogPluginComponent.emits]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// define props
 | 
				
			||||||
 | 
					const props = defineProps<{ urlAction: URLAction }>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// setup quasar
 | 
				
			||||||
 | 
					const { dialogRef, onDialogHide } = useDialogPluginComponent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// setup dropdowns
 | 
				
			||||||
 | 
					const { agent, agentOptions } = useAgentDropdown({ onMount: true });
 | 
				
			||||||
 | 
					const { client, clientOptions } = useClientDropdown(true);
 | 
				
			||||||
 | 
					const { site, siteOptions } = useSiteDropdown(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const runAgainst = ref<"agent" | "site" | "client" | "none">("none");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const runAgainstOptions = [
 | 
				
			||||||
 | 
					  { label: "Agent", value: "agent" },
 | 
				
			||||||
 | 
					  { label: "Site", value: "site" },
 | 
				
			||||||
 | 
					  { label: "Client", value: "client" },
 | 
				
			||||||
 | 
					  { label: "None", value: "none" },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					const loading = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const runAgainstID = computed(() => {
 | 
				
			||||||
 | 
					  if (runAgainst.value === "agent") return agent.value;
 | 
				
			||||||
 | 
					  else if (runAgainst.value === "site") return site.value;
 | 
				
			||||||
 | 
					  else if (runAgainst.value === "client") return client.value;
 | 
				
			||||||
 | 
					  else return 0;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					const state = reactive({
 | 
				
			||||||
 | 
					  pattern: props.urlAction.pattern,
 | 
				
			||||||
 | 
					  rest_body: props.urlAction.rest_body,
 | 
				
			||||||
 | 
					  rest_headers: props.urlAction.rest_headers,
 | 
				
			||||||
 | 
					  rest_method: props.urlAction.rest_method,
 | 
				
			||||||
 | 
					  run_instance_type: runAgainst,
 | 
				
			||||||
 | 
					  run_instance_id: runAgainstID,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const return_url = ref("");
 | 
				
			||||||
 | 
					const return_result = ref("");
 | 
				
			||||||
 | 
					const return_request = ref("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function submit() {
 | 
				
			||||||
 | 
					  loading.value = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const { url, result, body } = await runTestURLAction(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return_result.value = result;
 | 
				
			||||||
 | 
					    return_url.value = url;
 | 
				
			||||||
 | 
					    return_request.value = body;
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    console.error(e);
 | 
				
			||||||
 | 
					  } finally {
 | 
				
			||||||
 | 
					    loading.value = false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
@@ -1,14 +1,31 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <q-dialog ref="dialog" @hide="onHide">
 | 
					  <q-dialog
 | 
				
			||||||
    <q-card class="q-dialog-plugin" style="width: 60vw">
 | 
					    ref="dialogRef"
 | 
				
			||||||
 | 
					    @hide="onDialogHide"
 | 
				
			||||||
 | 
					    @show="loadEditor"
 | 
				
			||||||
 | 
					    @before-hide="cleanupEditors"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <q-card
 | 
				
			||||||
 | 
					      class="q-dialog-plugin"
 | 
				
			||||||
 | 
					      :style="`width: ${props.type === 'web' ? 50 : 60}vw; max-width: ${props.type === 'web' ? 60 : 70}vw`"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
      <q-bar>
 | 
					      <q-bar>
 | 
				
			||||||
        {{ title }}
 | 
					        {{
 | 
				
			||||||
 | 
					          props.action
 | 
				
			||||||
 | 
					            ? props.type === "web"
 | 
				
			||||||
 | 
					              ? "Edit URL Action"
 | 
				
			||||||
 | 
					              : "Edit Web Hook"
 | 
				
			||||||
 | 
					            : props.type === "web"
 | 
				
			||||||
 | 
					              ? "Add URL Action"
 | 
				
			||||||
 | 
					              : "Add Web Hook"
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
        <q-space />
 | 
					        <q-space />
 | 
				
			||||||
        <q-btn dense flat icon="close" v-close-popup>
 | 
					        <q-btn dense flat icon="close" v-close-popup>
 | 
				
			||||||
          <q-tooltip class="bg-white text-primary">Close</q-tooltip>
 | 
					          <q-tooltip class="bg-white text-primary">Close</q-tooltip>
 | 
				
			||||||
        </q-btn>
 | 
					        </q-btn>
 | 
				
			||||||
      </q-bar>
 | 
					      </q-bar>
 | 
				
			||||||
      <q-form @submit="submit">
 | 
					
 | 
				
			||||||
 | 
					      <div style="max-height: 80vh" class="scroll">
 | 
				
			||||||
        <!-- name -->
 | 
					        <!-- name -->
 | 
				
			||||||
        <q-card-section>
 | 
					        <q-card-section>
 | 
				
			||||||
          <q-input
 | 
					          <q-input
 | 
				
			||||||
@@ -26,6 +43,8 @@
 | 
				
			|||||||
            label="Description"
 | 
					            label="Description"
 | 
				
			||||||
            outlined
 | 
					            outlined
 | 
				
			||||||
            dense
 | 
					            dense
 | 
				
			||||||
 | 
					            type="textarea"
 | 
				
			||||||
 | 
					            rows="2"
 | 
				
			||||||
            v-model="localAction.desc"
 | 
					            v-model="localAction.desc"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </q-card-section>
 | 
					        </q-card-section>
 | 
				
			||||||
@@ -41,89 +60,186 @@
 | 
				
			|||||||
          />
 | 
					          />
 | 
				
			||||||
        </q-card-section>
 | 
					        </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <q-card-actions align="right">
 | 
					        <q-card-section v-if="type === 'rest'">
 | 
				
			||||||
          <q-btn flat label="Cancel" v-close-popup />
 | 
					          <q-select
 | 
				
			||||||
          <q-btn flat label="Submit" color="primary" type="submit" />
 | 
					            v-model="localAction.rest_method"
 | 
				
			||||||
        </q-card-actions>
 | 
					            label="Method"
 | 
				
			||||||
      </q-form>
 | 
					            :options="URLActionMethods"
 | 
				
			||||||
 | 
					            outlined
 | 
				
			||||||
 | 
					            dense
 | 
				
			||||||
 | 
					            map-options
 | 
				
			||||||
 | 
					            emit-value
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <q-card-section v-show="type === 'rest'">
 | 
				
			||||||
 | 
					          <q-toolbar>
 | 
				
			||||||
 | 
					            <q-tabs v-model="tab" dense shrink>
 | 
				
			||||||
 | 
					              <q-tab
 | 
				
			||||||
 | 
					                name="body"
 | 
				
			||||||
 | 
					                label="Request Body"
 | 
				
			||||||
 | 
					                :ripple="false"
 | 
				
			||||||
 | 
					                :disable="disableBodyTab"
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <q-tab name="headers" label="Request Headers" :ripple="false" />
 | 
				
			||||||
 | 
					            </q-tabs>
 | 
				
			||||||
 | 
					          </q-toolbar>
 | 
				
			||||||
 | 
					          <div ref="editorDiv" :style="{ height: '30vh' }"></div>
 | 
				
			||||||
 | 
					        </q-card-section>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <q-card-actions align="right">
 | 
				
			||||||
 | 
					        <q-btn
 | 
				
			||||||
 | 
					          v-if="type === 'rest'"
 | 
				
			||||||
 | 
					          flat
 | 
				
			||||||
 | 
					          label="Test"
 | 
				
			||||||
 | 
					          color="primary"
 | 
				
			||||||
 | 
					          @click="testWebHook"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <q-btn flat label="Cancel" v-close-popup />
 | 
				
			||||||
 | 
					        <q-btn flat label="Submit" color="primary" @click="submit" />
 | 
				
			||||||
 | 
					      </q-card-actions>
 | 
				
			||||||
    </q-card>
 | 
					    </q-card>
 | 
				
			||||||
  </q-dialog>
 | 
					  </q-dialog>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script setup lang="ts">
 | 
				
			||||||
import mixins from "@/mixins/mixins";
 | 
					// composition imports
 | 
				
			||||||
 | 
					import { ref, computed, reactive, watch } from "vue";
 | 
				
			||||||
 | 
					import { useDialogPluginComponent, useQuasar, extend } from "quasar";
 | 
				
			||||||
 | 
					import { editURLAction, saveURLAction } from "@/api/core";
 | 
				
			||||||
 | 
					import { notifySuccess } from "@/utils/notify";
 | 
				
			||||||
 | 
					import { URLAction, URLActionType } from "@/types/core/urlactions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					// ui imports
 | 
				
			||||||
  name: "URLActionsForm",
 | 
					import TestURLAction from "@/components/modals/coresettings/TestURLAction.vue";
 | 
				
			||||||
  emits: ["hide", "ok", "cancel"],
 | 
					 | 
				
			||||||
  mixins: [mixins],
 | 
					 | 
				
			||||||
  props: { action: Object },
 | 
					 | 
				
			||||||
  data() {
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      localAction: {
 | 
					 | 
				
			||||||
        name: "",
 | 
					 | 
				
			||||||
        desc: "",
 | 
					 | 
				
			||||||
        pattern: "",
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  computed: {
 | 
					 | 
				
			||||||
    title() {
 | 
					 | 
				
			||||||
      return this.editing ? "Edit URL Action" : "Add URL Action";
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    editing() {
 | 
					 | 
				
			||||||
      return !!this.action;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  methods: {
 | 
					 | 
				
			||||||
    submit() {
 | 
					 | 
				
			||||||
      this.$q.loading.show();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      let data = {
 | 
					import * as monaco from "monaco-editor";
 | 
				
			||||||
        ...this.localAction,
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.editing) {
 | 
					// define emits
 | 
				
			||||||
        this.$axios
 | 
					defineEmits([...useDialogPluginComponent.emits]);
 | 
				
			||||||
          .put(`/core/urlaction/${data.id}/`, data)
 | 
					
 | 
				
			||||||
          .then(() => {
 | 
					// define props
 | 
				
			||||||
            this.$q.loading.hide();
 | 
					const props = defineProps<{ type: URLActionType; action?: URLAction }>();
 | 
				
			||||||
            this.onOk();
 | 
					
 | 
				
			||||||
            this.notifySuccess("Url Action was edited!");
 | 
					// setup quasar
 | 
				
			||||||
          })
 | 
					const $q = useQuasar();
 | 
				
			||||||
          .catch(() => {
 | 
					const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
 | 
				
			||||||
            this.$q.loading.hide();
 | 
					
 | 
				
			||||||
          });
 | 
					// static data
 | 
				
			||||||
      } else {
 | 
					const URLActionMethods = [
 | 
				
			||||||
        this.$axios
 | 
					  { value: "get", label: "GET" },
 | 
				
			||||||
          .post("/core/urlaction/", data)
 | 
					  { value: "post", label: "POST" },
 | 
				
			||||||
          .then(() => {
 | 
					  { value: "put", label: "PUT" },
 | 
				
			||||||
            this.$q.loading.hide();
 | 
					  { value: "delete", label: "DELETE" },
 | 
				
			||||||
            this.onOk();
 | 
					  { value: "patch", label: "PATCH" },
 | 
				
			||||||
            this.notifySuccess("URL Action was added!");
 | 
					];
 | 
				
			||||||
          })
 | 
					
 | 
				
			||||||
          .catch(() => {
 | 
					const localAction: URLAction = props.action
 | 
				
			||||||
            this.$q.loading.hide();
 | 
					  ? reactive(extend({}, props.action))
 | 
				
			||||||
          });
 | 
					  : reactive({
 | 
				
			||||||
      }
 | 
					      name: "",
 | 
				
			||||||
    },
 | 
					      desc: "",
 | 
				
			||||||
    show() {
 | 
					      pattern: "",
 | 
				
			||||||
      this.$refs.dialog.show();
 | 
					      action_type: props.type,
 | 
				
			||||||
    },
 | 
					      rest_body: "{\n    \n}",
 | 
				
			||||||
    hide() {
 | 
					      rest_method: "post",
 | 
				
			||||||
      this.$refs.dialog.hide();
 | 
					      rest_headers: `{\n  "Content-Type": "application/json"\n}`, // eslint-disable-line
 | 
				
			||||||
    },
 | 
					    } as URLAction);
 | 
				
			||||||
    onHide() {
 | 
					
 | 
				
			||||||
      this.$emit("hide");
 | 
					const disableBodyTab = computed(() =>
 | 
				
			||||||
    },
 | 
					  ["get", "delete"].includes(localAction.rest_method),
 | 
				
			||||||
    onOk() {
 | 
					);
 | 
				
			||||||
      this.$emit("ok");
 | 
					const tab = ref(disableBodyTab.value ? "headers" : "body");
 | 
				
			||||||
      this.hide();
 | 
					
 | 
				
			||||||
    },
 | 
					watch(
 | 
				
			||||||
 | 
					  () => localAction.rest_method,
 | 
				
			||||||
 | 
					  () => {
 | 
				
			||||||
 | 
					    disableBodyTab.value ? (tab.value = "headers") : undefined;
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mounted() {
 | 
					);
 | 
				
			||||||
    // If pk prop is set that means we are editing
 | 
					
 | 
				
			||||||
    if (this.action) Object.assign(this.localAction, this.action);
 | 
					async function submit() {
 | 
				
			||||||
  },
 | 
					  $q.loading.show();
 | 
				
			||||||
};
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    props.action
 | 
				
			||||||
 | 
					      ? await editURLAction(localAction.id, localAction)
 | 
				
			||||||
 | 
					      : await saveURLAction(localAction);
 | 
				
			||||||
 | 
					    onDialogOK();
 | 
				
			||||||
 | 
					    notifySuccess("Url Action was edited!");
 | 
				
			||||||
 | 
					  } catch (e) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $q.loading.hide();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const editorDiv = ref<HTMLElement | null>(null);
 | 
				
			||||||
 | 
					let editor: monaco.editor.IStandaloneCodeEditor;
 | 
				
			||||||
 | 
					var modelBodyUri = monaco.Uri.parse("model://body"); // a made up unique URI for our model
 | 
				
			||||||
 | 
					var modelHeadersUri = monaco.Uri.parse("model://headers"); // a made up unique URI for our model
 | 
				
			||||||
 | 
					var modelBody = monaco.editor.createModel(
 | 
				
			||||||
 | 
					  localAction.rest_body,
 | 
				
			||||||
 | 
					  "json",
 | 
				
			||||||
 | 
					  modelBodyUri,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var modelHeaders = monaco.editor.createModel(
 | 
				
			||||||
 | 
					  localAction.rest_headers,
 | 
				
			||||||
 | 
					  "json",
 | 
				
			||||||
 | 
					  modelHeadersUri,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function testWebHook() {
 | 
				
			||||||
 | 
					  $q.dialog({
 | 
				
			||||||
 | 
					    component: TestURLAction,
 | 
				
			||||||
 | 
					    componentProps: {
 | 
				
			||||||
 | 
					      urlAction: localAction,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// watch tab change and change model
 | 
				
			||||||
 | 
					watch(tab, (newValue, oldValue) => {
 | 
				
			||||||
 | 
					  if (oldValue === "body") {
 | 
				
			||||||
 | 
					    localAction.rest_body = editor.getValue();
 | 
				
			||||||
 | 
					  } else if (oldValue === "headers") {
 | 
				
			||||||
 | 
					    localAction.rest_headers = editor.getValue();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (newValue === "body") {
 | 
				
			||||||
 | 
					    editor.setModel(modelBody);
 | 
				
			||||||
 | 
					    editor.setValue(localAction.rest_body);
 | 
				
			||||||
 | 
					  } else if (newValue === "headers") {
 | 
				
			||||||
 | 
					    editor.setModel(modelHeaders);
 | 
				
			||||||
 | 
					    editor.setValue(localAction.rest_headers);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function loadEditor() {
 | 
				
			||||||
 | 
					  const theme = $q.dark.isActive ? "vs-dark" : "vs-light";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!editorDiv.value) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  editor = monaco.editor.create(editorDiv.value, {
 | 
				
			||||||
 | 
					    model: tab.value === "body" ? modelBody : modelHeaders,
 | 
				
			||||||
 | 
					    theme: theme,
 | 
				
			||||||
 | 
					    automaticLayout: true,
 | 
				
			||||||
 | 
					    minimap: { enabled: false },
 | 
				
			||||||
 | 
					    quickSuggestions: false,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  editor.onDidChangeModelContent(() => {
 | 
				
			||||||
 | 
					    if (tab.value === "body") {
 | 
				
			||||||
 | 
					      localAction.rest_body = editor.getValue();
 | 
				
			||||||
 | 
					    } else if (tab.value === "headers") {
 | 
				
			||||||
 | 
					      localAction.rest_headers = editor.getValue();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function cleanupEditors() {
 | 
				
			||||||
 | 
					  modelBody.dispose();
 | 
				
			||||||
 | 
					  modelHeaders.dispose();
 | 
				
			||||||
 | 
					  editor.dispose();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,21 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <div class="row">
 | 
					    <div class="row">
 | 
				
			||||||
      <div class="text-subtitle2">URL Actions</div>
 | 
					      <div class="text-subtitle2">
 | 
				
			||||||
 | 
					        {{
 | 
				
			||||||
 | 
					          props.type === "web"
 | 
				
			||||||
 | 
					            ? "URL Actions"
 | 
				
			||||||
 | 
					            : "Web Hooks for Alert Failure/Resolved Actions"
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
      <q-space />
 | 
					      <q-space />
 | 
				
			||||||
      <q-btn
 | 
					      <q-btn
 | 
				
			||||||
        size="sm"
 | 
					        size="sm"
 | 
				
			||||||
        color="grey-5"
 | 
					        color="grey-5"
 | 
				
			||||||
        icon="fas fa-plus"
 | 
					        icon="fas fa-plus"
 | 
				
			||||||
        text-color="black"
 | 
					        text-color="black"
 | 
				
			||||||
        label="Add URL Action"
 | 
					        :label="`Add ${props.type === 'web' ? 'URL Action' : 'Web Hook'}`"
 | 
				
			||||||
        @click="addAction"
 | 
					        @click="addURLAction"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <q-separator />
 | 
					    <q-separator />
 | 
				
			||||||
@@ -17,31 +23,36 @@
 | 
				
			|||||||
      dense
 | 
					      dense
 | 
				
			||||||
      :rows="actions"
 | 
					      :rows="actions"
 | 
				
			||||||
      :columns="columns"
 | 
					      :columns="columns"
 | 
				
			||||||
      v-model:pagination="pagination"
 | 
					      :pagination="{ rowsPerPage: 0, sortBy: 'name', descending: true }"
 | 
				
			||||||
      row-key="id"
 | 
					      row-key="id"
 | 
				
			||||||
      binary-state-sort
 | 
					      binary-state-sort
 | 
				
			||||||
      hide-pagination
 | 
					      hide-pagination
 | 
				
			||||||
      virtual-scroll
 | 
					      virtual-scroll
 | 
				
			||||||
      :rows-per-page-options="[0]"
 | 
					      :rows-per-page-options="[0]"
 | 
				
			||||||
      no-data-label="No URL Actions added yet"
 | 
					      :no-data-label="`No ${props.type === 'web' ? 'URL Actions' : 'Web Hooks'} added yet`"
 | 
				
			||||||
 | 
					      :loading="loading"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <!-- body slots -->
 | 
					      <!-- body slots -->
 | 
				
			||||||
      <template v-slot:body="props">
 | 
					      <template v-slot:body="props">
 | 
				
			||||||
        <q-tr
 | 
					        <q-tr
 | 
				
			||||||
          :props="props"
 | 
					          :props="props"
 | 
				
			||||||
          class="cursor-pointer"
 | 
					          class="cursor-pointer"
 | 
				
			||||||
          @dblclick="editAction(props.row)"
 | 
					          @dblclick="editURLAction(props.row)"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <!-- context menu -->
 | 
					          <!-- context menu -->
 | 
				
			||||||
          <q-menu context-menu>
 | 
					          <q-menu context-menu>
 | 
				
			||||||
            <q-list dense style="min-width: 200px">
 | 
					            <q-list dense style="min-width: 200px">
 | 
				
			||||||
              <q-item clickable v-close-popup @click="editAction(props.row)">
 | 
					              <q-item clickable v-close-popup @click="editURLAction(props.row)">
 | 
				
			||||||
                <q-item-section side>
 | 
					                <q-item-section side>
 | 
				
			||||||
                  <q-icon name="edit" />
 | 
					                  <q-icon name="edit" />
 | 
				
			||||||
                </q-item-section>
 | 
					                </q-item-section>
 | 
				
			||||||
                <q-item-section>Edit</q-item-section>
 | 
					                <q-item-section>Edit</q-item-section>
 | 
				
			||||||
              </q-item>
 | 
					              </q-item>
 | 
				
			||||||
              <q-item clickable v-close-popup @click="deleteAction(props.row)">
 | 
					              <q-item
 | 
				
			||||||
 | 
					                clickable
 | 
				
			||||||
 | 
					                v-close-popup
 | 
				
			||||||
 | 
					                @click="deleteURLAction(props.row)"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
                <q-item-section side>
 | 
					                <q-item-section side>
 | 
				
			||||||
                  <q-icon name="delete" />
 | 
					                  <q-icon name="delete" />
 | 
				
			||||||
                </q-item-section>
 | 
					                </q-item-section>
 | 
				
			||||||
@@ -57,15 +68,15 @@
 | 
				
			|||||||
          </q-menu>
 | 
					          </q-menu>
 | 
				
			||||||
          <!-- name -->
 | 
					          <!-- name -->
 | 
				
			||||||
          <q-td>
 | 
					          <q-td>
 | 
				
			||||||
            {{ props.row.name }}
 | 
					            {{ truncateText(props.row.name, 30) }}
 | 
				
			||||||
          </q-td>
 | 
					          </q-td>
 | 
				
			||||||
          <!-- desc -->
 | 
					          <!-- desc -->
 | 
				
			||||||
          <q-td>
 | 
					          <q-td>
 | 
				
			||||||
            {{ props.row.desc }}
 | 
					            {{ truncateText(props.row.desc, 20) }}
 | 
				
			||||||
          </q-td>
 | 
					          </q-td>
 | 
				
			||||||
          <!-- pattern -->
 | 
					          <!-- pattern -->
 | 
				
			||||||
          <q-td>
 | 
					          <q-td>
 | 
				
			||||||
            {{ props.row.pattern }}
 | 
					            {{ truncateText(props.row.pattern, 20) }}
 | 
				
			||||||
          </q-td>
 | 
					          </q-td>
 | 
				
			||||||
        </q-tr>
 | 
					        </q-tr>
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
@@ -73,105 +84,103 @@
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					// composition imports
 | 
				
			||||||
 | 
					import { ref, onMounted } from "vue";
 | 
				
			||||||
 | 
					import { QTableColumn, useQuasar } from "quasar";
 | 
				
			||||||
 | 
					import { fetchURLActions, removeURLAction } from "@/api/core";
 | 
				
			||||||
 | 
					import { notifySuccess } from "@/utils/notify";
 | 
				
			||||||
 | 
					import { truncateText } from "@/utils/format";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ui imports
 | 
				
			||||||
import URLActionsForm from "@/components/modals/coresettings/URLActionsForm.vue";
 | 
					import URLActionsForm from "@/components/modals/coresettings/URLActionsForm.vue";
 | 
				
			||||||
import mixins from "@/mixins/mixins";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					// types
 | 
				
			||||||
  name: "URLActionTable",
 | 
					import { type URLActionType, type URLAction } from "@/types/core/urlactions";
 | 
				
			||||||
  mixins: [mixins],
 | 
					 | 
				
			||||||
  data() {
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      actions: [],
 | 
					 | 
				
			||||||
      pagination: {
 | 
					 | 
				
			||||||
        rowsPerPage: 0,
 | 
					 | 
				
			||||||
        sortBy: "name",
 | 
					 | 
				
			||||||
        descending: true,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      columns: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: "name",
 | 
					 | 
				
			||||||
          label: "Name",
 | 
					 | 
				
			||||||
          field: "name",
 | 
					 | 
				
			||||||
          align: "left",
 | 
					 | 
				
			||||||
          sortable: true,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: "desc",
 | 
					 | 
				
			||||||
          label: "Description",
 | 
					 | 
				
			||||||
          field: "desc",
 | 
					 | 
				
			||||||
          align: "left",
 | 
					 | 
				
			||||||
          sortable: true,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: "pattern",
 | 
					 | 
				
			||||||
          label: "Pattern",
 | 
					 | 
				
			||||||
          field: "pattern",
 | 
					 | 
				
			||||||
          align: "left",
 | 
					 | 
				
			||||||
          sortable: true,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  methods: {
 | 
					 | 
				
			||||||
    getURLActions() {
 | 
					 | 
				
			||||||
      this.$q.loading.show();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.$axios
 | 
					// define props
 | 
				
			||||||
        .get("/core/urlaction/")
 | 
					const props = defineProps<{ type: URLActionType }>();
 | 
				
			||||||
        .then((r) => {
 | 
					
 | 
				
			||||||
          this.$q.loading.hide();
 | 
					// setup quasar
 | 
				
			||||||
          this.actions = r.data;
 | 
					const $q = useQuasar();
 | 
				
			||||||
        })
 | 
					
 | 
				
			||||||
        .catch(() => {
 | 
					const loading = ref(false);
 | 
				
			||||||
          this.$q.loading.hide();
 | 
					
 | 
				
			||||||
        });
 | 
					const actions = ref([] as URLAction[]);
 | 
				
			||||||
    },
 | 
					
 | 
				
			||||||
    addAction() {
 | 
					const columns: QTableColumn[] = [
 | 
				
			||||||
      this.$q
 | 
					  {
 | 
				
			||||||
        .dialog({
 | 
					    name: "name",
 | 
				
			||||||
          component: URLActionsForm,
 | 
					    label: "Name",
 | 
				
			||||||
        })
 | 
					    field: "name",
 | 
				
			||||||
        .onOk(() => {
 | 
					    align: "left",
 | 
				
			||||||
          this.getURLActions();
 | 
					    sortable: true,
 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    editAction(action) {
 | 
					 | 
				
			||||||
      this.$q
 | 
					 | 
				
			||||||
        .dialog({
 | 
					 | 
				
			||||||
          component: URLActionsForm,
 | 
					 | 
				
			||||||
          componentProps: {
 | 
					 | 
				
			||||||
            action: action,
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .onOk(() => {
 | 
					 | 
				
			||||||
          this.getURLActions();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    deleteAction(action) {
 | 
					 | 
				
			||||||
      this.$q
 | 
					 | 
				
			||||||
        .dialog({
 | 
					 | 
				
			||||||
          title: `Delete URL Action: ${action.name}?`,
 | 
					 | 
				
			||||||
          cancel: true,
 | 
					 | 
				
			||||||
          ok: { label: "Delete", color: "negative" },
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .onOk(() => {
 | 
					 | 
				
			||||||
          this.$q.loading.show();
 | 
					 | 
				
			||||||
          this.$axios
 | 
					 | 
				
			||||||
            .delete(`/core/urlaction/${action.id}/`)
 | 
					 | 
				
			||||||
            .then(() => {
 | 
					 | 
				
			||||||
              this.getURLActions();
 | 
					 | 
				
			||||||
              this.$q.loading.hide();
 | 
					 | 
				
			||||||
              this.notifySuccess(`URL Action: ${action.name} was deleted!`);
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .catch(() => {
 | 
					 | 
				
			||||||
              this.$q.loading.hide();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mounted() {
 | 
					  {
 | 
				
			||||||
    this.getURLActions();
 | 
					    name: "desc",
 | 
				
			||||||
 | 
					    label: "Description",
 | 
				
			||||||
 | 
					    field: "desc",
 | 
				
			||||||
 | 
					    align: "left",
 | 
				
			||||||
 | 
					    sortable: true,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					  {
 | 
				
			||||||
 | 
					    name: "pattern",
 | 
				
			||||||
 | 
					    label: "URL Pattern",
 | 
				
			||||||
 | 
					    field: "pattern",
 | 
				
			||||||
 | 
					    align: "left",
 | 
				
			||||||
 | 
					    sortable: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function getURLActions() {
 | 
				
			||||||
 | 
					  $q.loading.show();
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const result = await fetchURLActions();
 | 
				
			||||||
 | 
					    actions.value = result.filter(
 | 
				
			||||||
 | 
					      (action) => action.action_type === props.type,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    console.error(e);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $q.loading.hide();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function addURLAction() {
 | 
				
			||||||
 | 
					  $q.dialog({
 | 
				
			||||||
 | 
					    component: URLActionsForm,
 | 
				
			||||||
 | 
					    componentProps: {
 | 
				
			||||||
 | 
					      type: props.type,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }).onOk(getURLActions);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function editURLAction(action: URLAction) {
 | 
				
			||||||
 | 
					  $q.dialog({
 | 
				
			||||||
 | 
					    component: URLActionsForm,
 | 
				
			||||||
 | 
					    componentProps: {
 | 
				
			||||||
 | 
					      type: props.type,
 | 
				
			||||||
 | 
					      action: action,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }).onOk(getURLActions);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function deleteURLAction(action: URLAction) {
 | 
				
			||||||
 | 
					  $q.dialog({
 | 
				
			||||||
 | 
					    title: `Delete URL Action: ${action.name}?`,
 | 
				
			||||||
 | 
					    cancel: true,
 | 
				
			||||||
 | 
					    ok: { label: "Delete", color: "negative" },
 | 
				
			||||||
 | 
					  }).onOk(async () => {
 | 
				
			||||||
 | 
					    loading.value = true;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await removeURLAction(action.id);
 | 
				
			||||||
 | 
					      await getURLActions();
 | 
				
			||||||
 | 
					      notifySuccess(`URL Action: ${action.name} was deleted!`);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.error(e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    loading.value = false;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					onMounted(getURLActions);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -319,10 +319,12 @@ export default {
 | 
				
			|||||||
          );
 | 
					          );
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.urlActions = r.data.map((action) => ({
 | 
					        this.urlActions = r.data
 | 
				
			||||||
          label: action.name,
 | 
					          .filter((action) => action.action_type === "web")
 | 
				
			||||||
          value: action.id,
 | 
					          .map((action) => ({
 | 
				
			||||||
        }));
 | 
					            label: action.name,
 | 
				
			||||||
 | 
					            value: action.id,
 | 
				
			||||||
 | 
					          }));
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getUserPrefs() {
 | 
					    getUserPrefs() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,6 +71,8 @@
 | 
				
			|||||||
              :readonly="readonly"
 | 
					              :readonly="readonly"
 | 
				
			||||||
              v-model="script.description"
 | 
					              v-model="script.description"
 | 
				
			||||||
              label="Description"
 | 
					              label="Description"
 | 
				
			||||||
 | 
					              type="textarea"
 | 
				
			||||||
 | 
					              rows="2"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <q-select
 | 
					            <q-select
 | 
				
			||||||
              :readonly="readonly"
 | 
					              :readonly="readonly"
 | 
				
			||||||
@@ -167,7 +169,7 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <q-card-actions>
 | 
					      <q-card-actions>
 | 
				
			||||||
        <tactical-dropdown
 | 
					        <tactical-dropdown
 | 
				
			||||||
          style="width: 350px"
 | 
					          style="width: 450px"
 | 
				
			||||||
          dense
 | 
					          dense
 | 
				
			||||||
          :loading="agentLoading"
 | 
					          :loading="agentLoading"
 | 
				
			||||||
          filled
 | 
					          filled
 | 
				
			||||||
@@ -187,7 +189,21 @@
 | 
				
			|||||||
              :disable="
 | 
					              :disable="
 | 
				
			||||||
                !agent || !script.script_body || !script.default_timeout
 | 
					                !agent || !script.script_body || !script.default_timeout
 | 
				
			||||||
              "
 | 
					              "
 | 
				
			||||||
              @click="openTestScriptModal"
 | 
					              @click="openTestScriptModal('agent')"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <q-btn
 | 
				
			||||||
 | 
					              v-if="!hosted"
 | 
				
			||||||
 | 
					              size="md"
 | 
				
			||||||
 | 
					              color="secondary"
 | 
				
			||||||
 | 
					              dense
 | 
				
			||||||
 | 
					              flat
 | 
				
			||||||
 | 
					              label="Test on Server"
 | 
				
			||||||
 | 
					              :disable="
 | 
				
			||||||
 | 
					                !script.script_body ||
 | 
				
			||||||
 | 
					                !script.default_timeout ||
 | 
				
			||||||
 | 
					                !server_scripts_enabled
 | 
				
			||||||
 | 
					              "
 | 
				
			||||||
 | 
					              @click="openTestScriptModal('server')"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
        </tactical-dropdown>
 | 
					        </tactical-dropdown>
 | 
				
			||||||
@@ -215,7 +231,7 @@ import { useQuasar, useDialogPluginComponent } from "quasar";
 | 
				
			|||||||
import { saveScript, editScript, downloadScript } from "@/api/scripts";
 | 
					import { saveScript, editScript, downloadScript } from "@/api/scripts";
 | 
				
			||||||
import { useAgentDropdown, agentPlatformOptions } from "@/composables/agents";
 | 
					import { useAgentDropdown, agentPlatformOptions } from "@/composables/agents";
 | 
				
			||||||
import { generateScript } from "@/api/core";
 | 
					import { generateScript } from "@/api/core";
 | 
				
			||||||
import { notifySuccess } from "@/utils/notify";
 | 
					import { notifyError, notifySuccess } from "@/utils/notify";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ui imports
 | 
					// ui imports
 | 
				
			||||||
import TestScriptModal from "@/components/scripts/TestScriptModal.vue";
 | 
					import TestScriptModal from "@/components/scripts/TestScriptModal.vue";
 | 
				
			||||||
@@ -285,6 +301,10 @@ const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// setup agent dropdown
 | 
					// setup agent dropdown
 | 
				
			||||||
const { agent, agentOptions, getAgentOptions } = useAgentDropdown();
 | 
					const { agent, agentOptions, getAgentOptions } = useAgentDropdown();
 | 
				
			||||||
 | 
					const hosted = computed(() => store.state.hosted);
 | 
				
			||||||
 | 
					const server_scripts_enabled = computed(
 | 
				
			||||||
 | 
					  () => store.state.server_scripts_enabled,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// script form logic
 | 
					// script form logic
 | 
				
			||||||
const script: Script = props.script
 | 
					const script: Script = props.script
 | 
				
			||||||
@@ -305,7 +325,7 @@ const agentLoading = ref(false);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const missingShebang = computed(() => {
 | 
					const missingShebang = computed(() => {
 | 
				
			||||||
  if (script.shell === "shell" || script.shell === "python") {
 | 
					  if (script.shell === "shell" || script.shell === "python") {
 | 
				
			||||||
    return !script.script_body.includes("#!");
 | 
					    return !script.script_body.startsWith("#!");
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -364,12 +384,20 @@ async function submit() {
 | 
				
			|||||||
  loading.value = false;
 | 
					  loading.value = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function openTestScriptModal() {
 | 
					function openTestScriptModal(ctx: string) {
 | 
				
			||||||
 | 
					  if (ctx === "server" && !script.script_body.startsWith("#!")) {
 | 
				
			||||||
 | 
					    notifyError(
 | 
				
			||||||
 | 
					      "A shebang is required at the top of the script to specify the interpreter's path. Please ensure your script begins with a shebang line.",
 | 
				
			||||||
 | 
					      7000,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  $q.dialog({
 | 
					  $q.dialog({
 | 
				
			||||||
    component: TestScriptModal,
 | 
					    component: TestScriptModal,
 | 
				
			||||||
    componentProps: {
 | 
					    componentProps: {
 | 
				
			||||||
      script: { ...script },
 | 
					      script: { ...script },
 | 
				
			||||||
      agent: agent.value,
 | 
					      agent: agent.value,
 | 
				
			||||||
 | 
					      ctx: ctx,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								src/components/scripts/ScriptOutputCopyClip.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/components/scripts/ScriptOutputCopyClip.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="row q-gutter-sm items-center">
 | 
				
			||||||
 | 
					    <div class="col-auto">{{ label }}</div>
 | 
				
			||||||
 | 
					    <div class="col-auto">
 | 
				
			||||||
 | 
					      <q-btn dense flat size="md" icon="content_copy" @click="copyText">
 | 
				
			||||||
 | 
					        <q-tooltip>Copy to Clipboard</q-tooltip>
 | 
				
			||||||
 | 
					      </q-btn>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { copyOutput } from "@/utils/helpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps({
 | 
				
			||||||
 | 
					  label: String,
 | 
				
			||||||
 | 
					  data: {
 | 
				
			||||||
 | 
					    type: String,
 | 
				
			||||||
 | 
					    required: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const copyText = () => {
 | 
				
			||||||
 | 
					  copyOutput(props.data);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
@@ -18,12 +18,12 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <br />
 | 
					        <br />
 | 
				
			||||||
        <div v-if="ret.stdout">
 | 
					        <div v-if="ret.stdout">
 | 
				
			||||||
          Standard Output
 | 
					          <script-output-copy-clip label="Standard Output" :data="ret.stdout" />
 | 
				
			||||||
          <q-separator />
 | 
					          <q-separator />
 | 
				
			||||||
          <pre>{{ ret.stdout }}</pre>
 | 
					          <pre>{{ ret.stdout }}</pre>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div v-if="ret.stderr">
 | 
					        <div v-if="ret.stderr">
 | 
				
			||||||
          Standard Error
 | 
					          <script-output-copy-clip label="Standard Error" :data="ret.stderr" />
 | 
				
			||||||
          <q-separator />
 | 
					          <q-separator />
 | 
				
			||||||
          <pre>{{ ret.stderr }}</pre>
 | 
					          <pre>{{ ret.stderr }}</pre>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@@ -36,15 +36,20 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { ref, onMounted } from "vue";
 | 
					import { ref, onMounted } from "vue";
 | 
				
			||||||
import { testScript } from "@/api/scripts";
 | 
					import { testScript, testScriptOnServer } from "@/api/scripts";
 | 
				
			||||||
import { useDialogPluginComponent } from "quasar";
 | 
					import { useDialogPluginComponent } from "quasar";
 | 
				
			||||||
 | 
					import ScriptOutputCopyClip from "@/components/scripts/ScriptOutputCopyClip.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  name: "TestScriptModal",
 | 
					  name: "TestScriptModal",
 | 
				
			||||||
 | 
					  components: {
 | 
				
			||||||
 | 
					    ScriptOutputCopyClip,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  emits: [...useDialogPluginComponent.emits],
 | 
					  emits: [...useDialogPluginComponent.emits],
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    script: !Object,
 | 
					    script: !Object,
 | 
				
			||||||
    agent: !String,
 | 
					    agent: !String,
 | 
				
			||||||
 | 
					    ctx: !String,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  setup(props) {
 | 
					  setup(props) {
 | 
				
			||||||
    // setup quasar dialog plugin
 | 
					    // setup quasar dialog plugin
 | 
				
			||||||
@@ -70,7 +75,11 @@ export default {
 | 
				
			|||||||
        env_vars: props.script.env_vars,
 | 
					        env_vars: props.script.env_vars,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        ret.value = await testScript(props.agent, data);
 | 
					        if (props.ctx === "server") {
 | 
				
			||||||
 | 
					          ret.value = await testScriptOnServer(data);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          ret.value = await testScript(props.agent, data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      } catch (e) {
 | 
					      } catch (e) {
 | 
				
			||||||
        console.error(e);
 | 
					        console.error(e);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -755,7 +755,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { ref, watch, onMounted } from "vue";
 | 
					import { ref, watch, onMounted, defineComponent } from "vue";
 | 
				
			||||||
import { useDialogPluginComponent } from "quasar";
 | 
					import { useDialogPluginComponent } from "quasar";
 | 
				
			||||||
import draggable from "vuedraggable";
 | 
					import draggable from "vuedraggable";
 | 
				
			||||||
import { saveTask, updateTask } from "@/api/tasks";
 | 
					import { saveTask, updateTask } from "@/api/tasks";
 | 
				
			||||||
@@ -843,7 +843,7 @@ const taskInstancePolicyOptions = [
 | 
				
			|||||||
  { label: "Stop Existing", value: 3 },
 | 
					  { label: "Stop Existing", value: 3 },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default defineComponent({
 | 
				
			||||||
  components: { TacticalDropdown, draggable },
 | 
					  components: { TacticalDropdown, draggable },
 | 
				
			||||||
  name: "AddAutomatedTask",
 | 
					  name: "AddAutomatedTask",
 | 
				
			||||||
  emits: [...useDialogPluginComponent.emits],
 | 
					  emits: [...useDialogPluginComponent.emits],
 | 
				
			||||||
@@ -858,18 +858,19 @@ export default {
 | 
				
			|||||||
    // setup dropdowns
 | 
					    // setup dropdowns
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
      script,
 | 
					      script,
 | 
				
			||||||
 | 
					      scriptName,
 | 
				
			||||||
      scriptOptions,
 | 
					      scriptOptions,
 | 
				
			||||||
      defaultTimeout,
 | 
					      defaultTimeout,
 | 
				
			||||||
      defaultArgs,
 | 
					      defaultArgs,
 | 
				
			||||||
      defaultEnvVars,
 | 
					      defaultEnvVars,
 | 
				
			||||||
    } = useScriptDropdown(undefined, {
 | 
					    } = useScriptDropdown({
 | 
				
			||||||
      onMount: true,
 | 
					      onMount: true,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // set defaultTimeout to 30
 | 
					    // set defaultTimeout to 30
 | 
				
			||||||
    defaultTimeout.value = 30;
 | 
					    defaultTimeout.value = 30;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { checkOptions, getCheckOptions } = useCheckDropdown();
 | 
					    const { checkOptions, getCheckOptions } = useCheckDropdown(props.parent);
 | 
				
			||||||
    const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
 | 
					    const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // add task logic
 | 
					    // add task logic
 | 
				
			||||||
@@ -952,9 +953,7 @@ export default {
 | 
				
			|||||||
      if (actionType.value === "script") {
 | 
					      if (actionType.value === "script") {
 | 
				
			||||||
        task.value.actions.push({
 | 
					        task.value.actions.push({
 | 
				
			||||||
          type: "script",
 | 
					          type: "script",
 | 
				
			||||||
          name: scriptOptions.value.find(
 | 
					          name: scriptName.value,
 | 
				
			||||||
            (option) => option.value === script.value,
 | 
					 | 
				
			||||||
          ).label,
 | 
					 | 
				
			||||||
          script: script.value,
 | 
					          script: script.value,
 | 
				
			||||||
          timeout: defaultTimeout.value,
 | 
					          timeout: defaultTimeout.value,
 | 
				
			||||||
          script_args: defaultArgs.value,
 | 
					          script_args: defaultArgs.value,
 | 
				
			||||||
@@ -1179,7 +1178,7 @@ export default {
 | 
				
			|||||||
      onDialogHide,
 | 
					      onDialogHide,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped>
 | 
					<style scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
import { computed, ref } from "vue";
 | 
					import { ref, computed, onMounted } from "vue";
 | 
				
			||||||
import { useStore } from "vuex";
 | 
					import { useStore } from "vuex";
 | 
				
			||||||
import { fetchAgents } from "@/api/agents";
 | 
					import { fetchAgents } from "@/api/agents";
 | 
				
			||||||
import { formatAgentOptions } from "@/utils/format";
 | 
					import { formatAgentOptions } from "@/utils/format";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// agent dropdown
 | 
					// agent dropdown
 | 
				
			||||||
export function useAgentDropdown() {
 | 
					export function useAgentDropdown(opts = {}) {
 | 
				
			||||||
  const agent = ref(null);
 | 
					  const agent = ref(null);
 | 
				
			||||||
  const agents = ref([]);
 | 
					  const agents = ref([]);
 | 
				
			||||||
  const agentOptions = ref([]);
 | 
					  const agentOptions = ref([]);
 | 
				
			||||||
@@ -13,10 +13,14 @@ export function useAgentDropdown() {
 | 
				
			|||||||
  async function getAgentOptions(flat = false) {
 | 
					  async function getAgentOptions(flat = false) {
 | 
				
			||||||
    agentOptions.value = formatAgentOptions(
 | 
					    agentOptions.value = formatAgentOptions(
 | 
				
			||||||
      await fetchAgents({ detail: false }),
 | 
					      await fetchAgents({ detail: false }),
 | 
				
			||||||
      flat
 | 
					      flat,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (opts.onMount) {
 | 
				
			||||||
 | 
					    onMounted(getAgentOptions);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    //data
 | 
					    //data
 | 
				
			||||||
    agent,
 | 
					    agent,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,28 +0,0 @@
 | 
				
			|||||||
import { ref, onMounted } from "vue";
 | 
					 | 
				
			||||||
import { fetchCustomFields } from "@/api/core";
 | 
					 | 
				
			||||||
import { formatCustomFieldOptions } from "@/utils/format";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function useCustomFieldDropdown({ onMount = false }) {
 | 
					 | 
				
			||||||
  const customFieldOptions = ref([]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // type can be "client", "site", or "agent"
 | 
					 | 
				
			||||||
  async function getCustomFieldOptions(model = null, flat = false) {
 | 
					 | 
				
			||||||
    const params = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (model) params[model] = model;
 | 
					 | 
				
			||||||
    customFieldOptions.value = formatCustomFieldOptions(
 | 
					 | 
				
			||||||
      await fetchCustomFields(params),
 | 
					 | 
				
			||||||
      flat
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (onMount) onMounted(getCustomFieldOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    //data
 | 
					 | 
				
			||||||
    customFieldOptions,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //methods
 | 
					 | 
				
			||||||
    getCustomFieldOptions,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										88
									
								
								src/composables/core.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/composables/core.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					import { ref, computed, onMounted } from "vue";
 | 
				
			||||||
 | 
					import { fetchCustomFields, fetchURLActions } from "@/api/core";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  formatCustomFieldOptions,
 | 
				
			||||||
 | 
					  formatURLActionOptions,
 | 
				
			||||||
 | 
					} from "@/utils/format";
 | 
				
			||||||
 | 
					import type { CustomField } from "@/types/core/customfields";
 | 
				
			||||||
 | 
					import type { URLAction } from "@/types/core/urlactions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface URLActionOption extends URLAction {
 | 
				
			||||||
 | 
					  value: number;
 | 
				
			||||||
 | 
					  label: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface CustomFieldOption extends CustomField {
 | 
				
			||||||
 | 
					  value: number;
 | 
				
			||||||
 | 
					  label: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UseCustomFieldDropdownParams {
 | 
				
			||||||
 | 
					  onMount?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useCustomFieldDropdown(opts: UseCustomFieldDropdownParams) {
 | 
				
			||||||
 | 
					  const customFieldOptions = ref([] as CustomFieldOption[]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // type can be "client", "site", or "agent"
 | 
				
			||||||
 | 
					  async function getCustomFieldOptions(model = null, flat = false) {
 | 
				
			||||||
 | 
					    const params = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (model) params[model] = model;
 | 
				
			||||||
 | 
					    customFieldOptions.value = formatCustomFieldOptions(
 | 
				
			||||||
 | 
					      await fetchCustomFields(params),
 | 
				
			||||||
 | 
					      flat,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const restActionOptions = computed(() =>
 | 
				
			||||||
 | 
					    customFieldOptions.value.filter((option) => option.type === "rest"),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (opts.onMount) onMounted(getCustomFieldOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    customFieldOptions,
 | 
				
			||||||
 | 
					    restActionOptions,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //methods
 | 
				
			||||||
 | 
					    getCustomFieldOptions,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UseURLActionDropdownParams {
 | 
				
			||||||
 | 
					  onMount?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useURLActionDropdown(opts: UseURLActionDropdownParams) {
 | 
				
			||||||
 | 
					  const urlActionOptions = ref([] as URLActionOption[]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // type can be "client", "site", or "agent"
 | 
				
			||||||
 | 
					  async function getURLActionOptions(flat = false) {
 | 
				
			||||||
 | 
					    const params = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    urlActionOptions.value = formatURLActionOptions(
 | 
				
			||||||
 | 
					      await fetchURLActions(params),
 | 
				
			||||||
 | 
					      flat,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const webActionOptions = computed(() =>
 | 
				
			||||||
 | 
					    urlActionOptions.value.filter((action) => action.action_type === "web"),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const restActionOptions = computed(() =>
 | 
				
			||||||
 | 
					    urlActionOptions.value.filter((action) => action.action_type === "rest"),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (opts?.onMount) onMounted(getURLActionOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    urlActionOptions,
 | 
				
			||||||
 | 
					    restActionOptions,
 | 
				
			||||||
 | 
					    webActionOptions,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //methods
 | 
				
			||||||
 | 
					    getURLActionOptions,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,70 +0,0 @@
 | 
				
			|||||||
import { ref, watch, computed, onMounted } from "vue";
 | 
					 | 
				
			||||||
import { useStore } from "vuex";
 | 
					 | 
				
			||||||
import { fetchScripts } from "@/api/scripts";
 | 
					 | 
				
			||||||
import { formatScriptOptions } from "@/utils/format";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// script dropdown
 | 
					 | 
				
			||||||
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("");
 | 
					 | 
				
			||||||
  const baseUrl =
 | 
					 | 
				
			||||||
    "https://github.com/amidaware/community-scripts/blob/main/scripts/";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // specify parameters to filter out community scripts
 | 
					 | 
				
			||||||
  async function getScriptOptions(showCommunityScripts = false) {
 | 
					 | 
				
			||||||
    scriptOptions.value = Object.freeze(
 | 
					 | 
				
			||||||
      formatScriptOptions(await fetchScripts({ showCommunityScripts })),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // watch scriptPk for changes and update the default timeout and args
 | 
					 | 
				
			||||||
  watch([script, scriptOptions], () => {
 | 
					 | 
				
			||||||
    if (script.value && scriptOptions.value.length > 0) {
 | 
					 | 
				
			||||||
      const tmpScript = scriptOptions.value.find(
 | 
					 | 
				
			||||||
        (i) => i.value === script.value,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      defaultTimeout.value = tmpScript.timeout;
 | 
					 | 
				
			||||||
      defaultArgs.value = tmpScript.args;
 | 
					 | 
				
			||||||
      defaultEnvVars.value = tmpScript.env_vars;
 | 
					 | 
				
			||||||
      syntax.value = tmpScript.syntax;
 | 
					 | 
				
			||||||
      link.value =
 | 
					 | 
				
			||||||
        tmpScript.script_type === "builtin"
 | 
					 | 
				
			||||||
          ? `${baseUrl}${tmpScript.filename}`
 | 
					 | 
				
			||||||
          : null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // vuex show community scripts
 | 
					 | 
				
			||||||
  const store = useStore();
 | 
					 | 
				
			||||||
  const showCommunityScripts = computed(() => store.state.showCommunityScripts);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (onMount) onMounted(() => getScriptOptions(showCommunityScripts.value));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    //data
 | 
					 | 
				
			||||||
    script,
 | 
					 | 
				
			||||||
    scriptOptions,
 | 
					 | 
				
			||||||
    defaultTimeout,
 | 
					 | 
				
			||||||
    defaultArgs,
 | 
					 | 
				
			||||||
    defaultEnvVars,
 | 
					 | 
				
			||||||
    syntax,
 | 
					 | 
				
			||||||
    link,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //methods
 | 
					 | 
				
			||||||
    getScriptOptions,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const shellOptions = [
 | 
					 | 
				
			||||||
  { label: "Powershell", value: "powershell" },
 | 
					 | 
				
			||||||
  { label: "Batch", value: "cmd" },
 | 
					 | 
				
			||||||
  { label: "Python", value: "python" },
 | 
					 | 
				
			||||||
  { label: "Shell", value: "shell" },
 | 
					 | 
				
			||||||
  { label: "Nushell", value: "nushell" },
 | 
					 | 
				
			||||||
  { label: "Deno", value: "deno" },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
							
								
								
									
										141
									
								
								src/composables/scripts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/composables/scripts.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					import { ref, watch, computed, onMounted } from "vue";
 | 
				
			||||||
 | 
					import { useStore } from "vuex";
 | 
				
			||||||
 | 
					import { fetchScripts } from "@/api/scripts";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  formatScriptOptions,
 | 
				
			||||||
 | 
					  removeExtraOptionCategories,
 | 
				
			||||||
 | 
					} from "@/utils/format";
 | 
				
			||||||
 | 
					import type { Script } from "@/types/scripts";
 | 
				
			||||||
 | 
					import { AgentPlatformType } from "@/types/agents";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ScriptOption extends Script {
 | 
				
			||||||
 | 
					  label: string;
 | 
				
			||||||
 | 
					  value: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface useScriptDropdownParams {
 | 
				
			||||||
 | 
					  script?: number; // set a selected script on init
 | 
				
			||||||
 | 
					  plat?: AgentPlatformType; // set a platform for filterByPlatform
 | 
				
			||||||
 | 
					  onMount?: boolean; // loads script options on mount
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// script dropdown
 | 
				
			||||||
 | 
					export function useScriptDropdown(opts?: useScriptDropdownParams) {
 | 
				
			||||||
 | 
					  const scriptOptions = ref([] as ScriptOption[]);
 | 
				
			||||||
 | 
					  const defaultTimeout = ref(30);
 | 
				
			||||||
 | 
					  const defaultArgs = ref([] as string[]);
 | 
				
			||||||
 | 
					  const defaultEnvVars = ref([] as string[]);
 | 
				
			||||||
 | 
					  const script = ref(opts?.script);
 | 
				
			||||||
 | 
					  const scriptName = ref("");
 | 
				
			||||||
 | 
					  const syntax = ref<string | undefined>("");
 | 
				
			||||||
 | 
					  const link = ref<string | undefined>("");
 | 
				
			||||||
 | 
					  const plat = ref<AgentPlatformType | undefined>(opts?.plat);
 | 
				
			||||||
 | 
					  const baseUrl =
 | 
				
			||||||
 | 
					    "https://github.com/amidaware/community-scripts/blob/main/scripts/";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // specify parameters to filter out community scripts
 | 
				
			||||||
 | 
					  async function getScriptOptions() {
 | 
				
			||||||
 | 
					    scriptOptions.value = Object.freeze(
 | 
				
			||||||
 | 
					      formatScriptOptions(
 | 
				
			||||||
 | 
					        await fetchScripts({
 | 
				
			||||||
 | 
					          showCommunityScripts: showCommunityScripts.value,
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    ) as ScriptOption[];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // watch scriptPk for changes and update the default timeout and args
 | 
				
			||||||
 | 
					  watch([script, scriptOptions], () => {
 | 
				
			||||||
 | 
					    if (script.value && scriptOptions.value.length > 0) {
 | 
				
			||||||
 | 
					      const tmpScript = scriptOptions.value.find(
 | 
				
			||||||
 | 
					        (i) => i.value === script.value,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (tmpScript) {
 | 
				
			||||||
 | 
					        defaultTimeout.value = tmpScript.default_timeout;
 | 
				
			||||||
 | 
					        defaultArgs.value = tmpScript.args;
 | 
				
			||||||
 | 
					        defaultEnvVars.value = tmpScript.env_vars;
 | 
				
			||||||
 | 
					        syntax.value = tmpScript.syntax;
 | 
				
			||||||
 | 
					        scriptName.value = tmpScript.label;
 | 
				
			||||||
 | 
					        link.value =
 | 
				
			||||||
 | 
					          tmpScript.script_type === "builtin"
 | 
				
			||||||
 | 
					            ? `${baseUrl}${tmpScript.filename}`
 | 
				
			||||||
 | 
					            : undefined;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // vuex show community scripts
 | 
				
			||||||
 | 
					  const store = useStore();
 | 
				
			||||||
 | 
					  const showCommunityScripts = computed(() => store.state.showCommunityScripts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // filter for only getting server tasks
 | 
				
			||||||
 | 
					  const serverScriptOptions = computed(
 | 
				
			||||||
 | 
					    () =>
 | 
				
			||||||
 | 
					      removeExtraOptionCategories(
 | 
				
			||||||
 | 
					        scriptOptions.value.filter(
 | 
				
			||||||
 | 
					          (script) =>
 | 
				
			||||||
 | 
					            script.category ||
 | 
				
			||||||
 | 
					            !script.supported_platforms ||
 | 
				
			||||||
 | 
					            script.supported_platforms.length === 0 ||
 | 
				
			||||||
 | 
					            script.supported_platforms.includes("linux"),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ) as ScriptOption[],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const filterByPlatformOptions = computed(() => {
 | 
				
			||||||
 | 
					    if (!plat.value) {
 | 
				
			||||||
 | 
					      return scriptOptions.value;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return removeExtraOptionCategories(
 | 
				
			||||||
 | 
					        scriptOptions.value.filter(
 | 
				
			||||||
 | 
					          (script) =>
 | 
				
			||||||
 | 
					            script.category ||
 | 
				
			||||||
 | 
					            !script.supported_platforms ||
 | 
				
			||||||
 | 
					            script.supported_platforms.length === 0 ||
 | 
				
			||||||
 | 
					            script.supported_platforms.includes(plat.value!),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ) as ScriptOption[];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function reset() {
 | 
				
			||||||
 | 
					    defaultTimeout.value = 30;
 | 
				
			||||||
 | 
					    defaultArgs.value = [];
 | 
				
			||||||
 | 
					    defaultEnvVars.value = [];
 | 
				
			||||||
 | 
					    script.value = undefined;
 | 
				
			||||||
 | 
					    syntax.value = "";
 | 
				
			||||||
 | 
					    link.value = "";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (opts?.onMount) onMounted(() => getScriptOptions());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    //data
 | 
				
			||||||
 | 
					    script,
 | 
				
			||||||
 | 
					    defaultTimeout,
 | 
				
			||||||
 | 
					    defaultArgs,
 | 
				
			||||||
 | 
					    defaultEnvVars,
 | 
				
			||||||
 | 
					    scriptName,
 | 
				
			||||||
 | 
					    syntax,
 | 
				
			||||||
 | 
					    link,
 | 
				
			||||||
 | 
					    plat,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    scriptOptions, // unfiltered options
 | 
				
			||||||
 | 
					    serverScriptOptions, // only scripts that can run on server
 | 
				
			||||||
 | 
					    filterByPlatformOptions, // use the returned plat to change options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //methods
 | 
				
			||||||
 | 
					    getScriptOptions,
 | 
				
			||||||
 | 
					    reset, // resets dropdown selection state
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const shellOptions = [
 | 
				
			||||||
 | 
					  { label: "Powershell", value: "powershell" },
 | 
				
			||||||
 | 
					  { label: "Batch", value: "cmd" },
 | 
				
			||||||
 | 
					  { label: "Python", value: "python" },
 | 
				
			||||||
 | 
					  { label: "Shell", value: "shell" },
 | 
				
			||||||
 | 
					  { label: "Nushell", value: "nushell" },
 | 
				
			||||||
 | 
					  { label: "Deno", value: "deno" },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
@@ -84,7 +84,16 @@
 | 
				
			|||||||
          checked-icon="nights_stay"
 | 
					          checked-icon="nights_stay"
 | 
				
			||||||
          unchecked-icon="wb_sunny"
 | 
					          unchecked-icon="wb_sunny"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
 | 
					        <!-- web terminal button -->
 | 
				
			||||||
 | 
					        <q-btn
 | 
				
			||||||
 | 
					          v-if="!hosted"
 | 
				
			||||||
 | 
					          label=">_"
 | 
				
			||||||
 | 
					          dense
 | 
				
			||||||
 | 
					          flat
 | 
				
			||||||
 | 
					          @click="openWebTerm"
 | 
				
			||||||
 | 
					          class="q-mr-sm"
 | 
				
			||||||
 | 
					          style="font-size: 16px"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
        <!-- Devices Chip -->
 | 
					        <!-- Devices Chip -->
 | 
				
			||||||
        <q-chip class="cursor-pointer">
 | 
					        <q-chip class="cursor-pointer">
 | 
				
			||||||
          <q-avatar size="md" icon="devices" color="primary" />
 | 
					          <q-avatar size="md" icon="devices" color="primary" />
 | 
				
			||||||
@@ -148,7 +157,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        <AlertsIcon />
 | 
					        <AlertsIcon />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <q-btn-dropdown flat no-caps stretch :label="user">
 | 
					        <q-btn-dropdown flat no-caps stretch :label="username || ''">
 | 
				
			||||||
          <q-list>
 | 
					          <q-list>
 | 
				
			||||||
            <q-item
 | 
					            <q-item
 | 
				
			||||||
              clickable
 | 
					              clickable
 | 
				
			||||||
@@ -200,187 +209,114 @@
 | 
				
			|||||||
    </q-page-container>
 | 
					    </q-page-container>
 | 
				
			||||||
  </q-layout>
 | 
					  </q-layout>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
<script>
 | 
					<script setup lang="ts">
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
 | 
					import { computed, onMounted } from "vue";
 | 
				
			||||||
import { useQuasar } from "quasar";
 | 
					import { useQuasar } from "quasar";
 | 
				
			||||||
import { useStore } from "vuex";
 | 
					import { useStore } from "vuex";
 | 
				
			||||||
import axios from "axios";
 | 
					import { useDashboardStore } from "@/stores/dashboard";
 | 
				
			||||||
import { getWSUrl } from "@/websocket/channels";
 | 
					import { useAuthStore } from "@/stores/auth";
 | 
				
			||||||
 | 
					import { storeToRefs } from "pinia";
 | 
				
			||||||
import { resetTwoFactor } from "@/api/accounts";
 | 
					import { resetTwoFactor } from "@/api/accounts";
 | 
				
			||||||
import { notifySuccess } from "@/utils/notify";
 | 
					import { notifyError, notifySuccess } from "@/utils/notify";
 | 
				
			||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// webtermn
 | 
				
			||||||
 | 
					import { checkWebTermPerms, openWebTerminal } from "@/api/core";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ui imports
 | 
					// ui imports
 | 
				
			||||||
import AlertsIcon from "@/components/AlertsIcon.vue";
 | 
					import AlertsIcon from "@/components/AlertsIcon.vue";
 | 
				
			||||||
import UserPreferences from "@/components/modals/coresettings/UserPreferences.vue";
 | 
					import UserPreferences from "@/components/modals/coresettings/UserPreferences.vue";
 | 
				
			||||||
import ResetPass from "@/components/accounts/ResetPass.vue";
 | 
					import ResetPass from "@/components/accounts/ResetPass.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					const store = useStore();
 | 
				
			||||||
  name: "MainLayout",
 | 
					const $q = useQuasar();
 | 
				
			||||||
  components: { AlertsIcon },
 | 
					 | 
				
			||||||
  setup() {
 | 
					 | 
				
			||||||
    const store = useStore();
 | 
					 | 
				
			||||||
    const $q = useQuasar();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const darkMode = computed({
 | 
					const {
 | 
				
			||||||
      get: () => {
 | 
					  serverCount,
 | 
				
			||||||
        return $q.dark.isActive;
 | 
					  serverOfflineCount,
 | 
				
			||||||
      },
 | 
					  workstationCount,
 | 
				
			||||||
      set: (value) => {
 | 
					  workstationOfflineCount,
 | 
				
			||||||
        axios.patch("/accounts/users/ui/", { dark_mode: value });
 | 
					  daysUntilCertExpires,
 | 
				
			||||||
        $q.dark.set(value);
 | 
					} = storeToRefs(useDashboardStore());
 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const currentTRMMVersion = computed(() => store.state.currentTRMMVersion);
 | 
					const { username } = storeToRefs(useAuthStore());
 | 
				
			||||||
    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 dash_warning_color = computed(() => store.state.dash_warning_color);
 | 
					 | 
				
			||||||
    const dash_negative_color = computed(() => store.state.dash_negative_color);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const latestReleaseURL = computed(() => {
 | 
					const darkMode = computed({
 | 
				
			||||||
      return latestTRMMVersion.value
 | 
					  get: () => {
 | 
				
			||||||
        ? `https://github.com/amidaware/tacticalrmm/releases/tag/v${latestTRMMVersion.value}`
 | 
					    return $q.dark.isActive;
 | 
				
			||||||
        : "";
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function showUserPreferences() {
 | 
					 | 
				
			||||||
      $q.dialog({
 | 
					 | 
				
			||||||
        component: UserPreferences,
 | 
					 | 
				
			||||||
      }).onOk(() => store.dispatch("getDashInfo"));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function resetPassword() {
 | 
					 | 
				
			||||||
      $q.dialog({
 | 
					 | 
				
			||||||
        component: ResetPass,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function reset2FA() {
 | 
					 | 
				
			||||||
      $q.dialog({
 | 
					 | 
				
			||||||
        title: "Reset 2FA",
 | 
					 | 
				
			||||||
        message: "Are you sure you would like to reset your 2FA token?",
 | 
					 | 
				
			||||||
        cancel: true,
 | 
					 | 
				
			||||||
        persistent: true,
 | 
					 | 
				
			||||||
      }).onOk(async () => {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          const ret = await resetTwoFactor();
 | 
					 | 
				
			||||||
          notifySuccess(ret, 3000);
 | 
					 | 
				
			||||||
        } catch {}
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const serverCount = ref(0);
 | 
					 | 
				
			||||||
    const serverOfflineCount = ref(0);
 | 
					 | 
				
			||||||
    const workstationCount = ref(0);
 | 
					 | 
				
			||||||
    const workstationOfflineCount = ref(0);
 | 
					 | 
				
			||||||
    const daysUntilCertExpires = ref(100);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const ws = ref(null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function setupWS() {
 | 
					 | 
				
			||||||
      // moved computed token inside the function since it is not refreshing
 | 
					 | 
				
			||||||
      // when ws is closed causing ws to connect with expired token
 | 
					 | 
				
			||||||
      const token = computed(() => store.state.token);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!token.value) {
 | 
					 | 
				
			||||||
        console.log(
 | 
					 | 
				
			||||||
          "Access token is null or invalid, not setting up WebSocket",
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      console.log("Starting websocket");
 | 
					 | 
				
			||||||
      let url = getWSUrl("dashinfo", token.value);
 | 
					 | 
				
			||||||
      ws.value = new WebSocket(url);
 | 
					 | 
				
			||||||
      ws.value.onopen = () => {
 | 
					 | 
				
			||||||
        console.log("Connected to ws");
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      ws.value.onmessage = (e) => {
 | 
					 | 
				
			||||||
        const data = JSON.parse(e.data);
 | 
					 | 
				
			||||||
        serverCount.value = data.total_server_count;
 | 
					 | 
				
			||||||
        serverOfflineCount.value = data.total_server_offline_count;
 | 
					 | 
				
			||||||
        workstationCount.value = data.total_workstation_count;
 | 
					 | 
				
			||||||
        workstationOfflineCount.value = data.total_workstation_offline_count;
 | 
					 | 
				
			||||||
        daysUntilCertExpires.value = data.days_until_cert_expires;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      ws.value.onclose = (e) => {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          console.log(`Closed code: ${e.code}`);
 | 
					 | 
				
			||||||
          console.log("Retrying websocket connection...");
 | 
					 | 
				
			||||||
          setTimeout(() => {
 | 
					 | 
				
			||||||
            setupWS();
 | 
					 | 
				
			||||||
          }, 3 * 1000);
 | 
					 | 
				
			||||||
        } catch (e) {
 | 
					 | 
				
			||||||
          console.log("Websocket connection closed");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      ws.value.onerror = () => {
 | 
					 | 
				
			||||||
        console.log("There was an error");
 | 
					 | 
				
			||||||
        ws.value.onclose();
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const poll = ref(null);
 | 
					 | 
				
			||||||
    function livePoll() {
 | 
					 | 
				
			||||||
      poll.value = setInterval(
 | 
					 | 
				
			||||||
        () => {
 | 
					 | 
				
			||||||
          store.dispatch("checkVer");
 | 
					 | 
				
			||||||
          store.dispatch("getDashInfo", false);
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        60 * 4 * 1000,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const updateAvailable = computed(() => {
 | 
					 | 
				
			||||||
      if (
 | 
					 | 
				
			||||||
        latestTRMMVersion.value === "error" ||
 | 
					 | 
				
			||||||
        hosted.value ||
 | 
					 | 
				
			||||||
        currentTRMMVersion.value?.includes("-dev")
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
      return currentTRMMVersion.value !== latestTRMMVersion.value;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onMounted(() => {
 | 
					 | 
				
			||||||
      setupWS();
 | 
					 | 
				
			||||||
      store.dispatch("getDashInfo");
 | 
					 | 
				
			||||||
      store.dispatch("checkVer");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      livePoll();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onBeforeUnmount(() => {
 | 
					 | 
				
			||||||
      ws.value.close();
 | 
					 | 
				
			||||||
      clearInterval(poll.value);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      // reactive data
 | 
					 | 
				
			||||||
      serverCount,
 | 
					 | 
				
			||||||
      serverOfflineCount,
 | 
					 | 
				
			||||||
      workstationCount,
 | 
					 | 
				
			||||||
      workstationOfflineCount,
 | 
					 | 
				
			||||||
      daysUntilCertExpires,
 | 
					 | 
				
			||||||
      latestReleaseURL,
 | 
					 | 
				
			||||||
      currentTRMMVersion,
 | 
					 | 
				
			||||||
      latestTRMMVersion,
 | 
					 | 
				
			||||||
      user,
 | 
					 | 
				
			||||||
      needRefresh,
 | 
					 | 
				
			||||||
      darkMode,
 | 
					 | 
				
			||||||
      hosted,
 | 
					 | 
				
			||||||
      tokenExpired,
 | 
					 | 
				
			||||||
      dash_warning_color,
 | 
					 | 
				
			||||||
      dash_negative_color,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // methods
 | 
					 | 
				
			||||||
      showUserPreferences,
 | 
					 | 
				
			||||||
      resetPassword,
 | 
					 | 
				
			||||||
      reset2FA,
 | 
					 | 
				
			||||||
      updateAvailable,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					  set: (value) => {
 | 
				
			||||||
 | 
					    axios.patch("/accounts/users/ui/", { dark_mode: value });
 | 
				
			||||||
 | 
					    $q.dark.set(value);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const currentTRMMVersion = computed(() => store.state.currentTRMMVersion);
 | 
				
			||||||
 | 
					const latestTRMMVersion = computed(() => store.state.latestTRMMVersion);
 | 
				
			||||||
 | 
					const needRefresh = computed(() => store.state.needrefresh);
 | 
				
			||||||
 | 
					const hosted = computed(() => store.state.hosted);
 | 
				
			||||||
 | 
					const tokenExpired = computed(() => store.state.tokenExpired);
 | 
				
			||||||
 | 
					const dash_warning_color = computed(() => store.state.dash_warning_color);
 | 
				
			||||||
 | 
					const dash_negative_color = computed(() => store.state.dash_negative_color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const latestReleaseURL = computed(() => {
 | 
				
			||||||
 | 
					  return latestTRMMVersion.value
 | 
				
			||||||
 | 
					    ? `https://github.com/amidaware/tacticalrmm/releases/tag/v${latestTRMMVersion.value}`
 | 
				
			||||||
 | 
					    : "";
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function showUserPreferences() {
 | 
				
			||||||
 | 
					  $q.dialog({
 | 
				
			||||||
 | 
					    component: UserPreferences,
 | 
				
			||||||
 | 
					  }).onOk(() => store.dispatch("getDashInfo"));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function resetPassword() {
 | 
				
			||||||
 | 
					  $q.dialog({
 | 
				
			||||||
 | 
					    component: ResetPass,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function reset2FA() {
 | 
				
			||||||
 | 
					  $q.dialog({
 | 
				
			||||||
 | 
					    title: "Reset 2FA",
 | 
				
			||||||
 | 
					    message: "Are you sure you would like to reset your 2FA token?",
 | 
				
			||||||
 | 
					    cancel: true,
 | 
				
			||||||
 | 
					    persistent: true,
 | 
				
			||||||
 | 
					  }).onOk(async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const ret = await resetTwoFactor();
 | 
				
			||||||
 | 
					      notifySuccess(ret, 3000);
 | 
				
			||||||
 | 
					    } catch {}
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function openWebTerm() {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const { message, status } = await checkWebTermPerms();
 | 
				
			||||||
 | 
					    if (status === 412) {
 | 
				
			||||||
 | 
					      notifyError(message);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      openWebTerminal();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    console.error(e);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateAvailable = computed(() => {
 | 
				
			||||||
 | 
					  if (
 | 
				
			||||||
 | 
					    latestTRMMVersion.value === "error" ||
 | 
				
			||||||
 | 
					    hosted.value ||
 | 
				
			||||||
 | 
					    currentTRMMVersion.value?.includes("-dev")
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  return currentTRMMVersion.value !== latestTRMMVersion.value;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					  store.dispatch("getDashInfo");
 | 
				
			||||||
 | 
					  store.dispatch("checkVer");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,8 @@ import {
 | 
				
			|||||||
  createWebHistory,
 | 
					  createWebHistory,
 | 
				
			||||||
  createWebHashHistory,
 | 
					  createWebHashHistory,
 | 
				
			||||||
} from "vue-router";
 | 
					} from "vue-router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useAuthStore } from "@/stores/auth";
 | 
				
			||||||
import routes from "./routes";
 | 
					import routes from "./routes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// useful for importing router outside of vue components
 | 
					// useful for importing router outside of vue components
 | 
				
			||||||
@@ -13,7 +15,7 @@ export const router = new createRouter({
 | 
				
			|||||||
  history: createWebHistory(process.env.VUE_ROUTER_BASE),
 | 
					  history: createWebHistory(process.env.VUE_ROUTER_BASE),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function ({ store }) {
 | 
					export default function (/* { store } */) {
 | 
				
			||||||
  const createHistory = process.env.SERVER
 | 
					  const createHistory = process.env.SERVER
 | 
				
			||||||
    ? createMemoryHistory
 | 
					    ? createMemoryHistory
 | 
				
			||||||
    : process.env.VUE_ROUTER_MODE === "history"
 | 
					    : process.env.VUE_ROUTER_MODE === "history"
 | 
				
			||||||
@@ -24,13 +26,15 @@ export default function ({ store }) {
 | 
				
			|||||||
    scrollBehavior: () => ({ left: 0, top: 0 }),
 | 
					    scrollBehavior: () => ({ left: 0, top: 0 }),
 | 
				
			||||||
    routes,
 | 
					    routes,
 | 
				
			||||||
    history: createHistory(
 | 
					    history: createHistory(
 | 
				
			||||||
      process.env.MODE === "ssr" ? void 0 : process.env.VUE_ROUTER_BASE
 | 
					      process.env.MODE === "ssr" ? void 0 : process.env.VUE_ROUTER_BASE,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Router.beforeEach((to, from, next) => {
 | 
					  Router.beforeEach((to, from, next) => {
 | 
				
			||||||
 | 
					    const auth = useAuthStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (to.meta.requireAuth) {
 | 
					    if (to.meta.requireAuth) {
 | 
				
			||||||
      if (!store.getters.loggedIn) {
 | 
					      if (!auth.loggedIn) {
 | 
				
			||||||
        next({
 | 
					        next({
 | 
				
			||||||
          name: "Login",
 | 
					          name: "Login",
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -38,7 +42,7 @@ export default function ({ store }) {
 | 
				
			|||||||
        next();
 | 
					        next();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else if (to.meta.requiresVisitor) {
 | 
					    } else if (to.meta.requiresVisitor) {
 | 
				
			||||||
      if (store.getters.loggedIn) {
 | 
					      if (auth.loggedIn) {
 | 
				
			||||||
        next({
 | 
					        next({
 | 
				
			||||||
          name: "Dashboard",
 | 
					          name: "Dashboard",
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,6 +46,14 @@ const routes = [
 | 
				
			|||||||
      requireAuth: true,
 | 
					      requireAuth: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    path: "/webterm",
 | 
				
			||||||
 | 
					    name: "WebTerm",
 | 
				
			||||||
 | 
					    component: () => import("@/views/WebTerminal.vue"),
 | 
				
			||||||
 | 
					    meta: {
 | 
				
			||||||
 | 
					      requireAuth: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    path: "/remotebackground/:agent_id",
 | 
					    path: "/remotebackground/:agent_id",
 | 
				
			||||||
    name: "RemoteBackground",
 | 
					    name: "RemoteBackground",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,6 @@ export default function () {
 | 
				
			|||||||
  const Store = new createStore({
 | 
					  const Store = new createStore({
 | 
				
			||||||
    state() {
 | 
					    state() {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        username: localStorage.getItem("user_name") || null,
 | 
					 | 
				
			||||||
        token: localStorage.getItem("access_token") || null,
 | 
					 | 
				
			||||||
        tree: [],
 | 
					        tree: [],
 | 
				
			||||||
        agents: [],
 | 
					        agents: [],
 | 
				
			||||||
        treeReady: false,
 | 
					        treeReady: false,
 | 
				
			||||||
@@ -43,15 +41,14 @@ export default function () {
 | 
				
			|||||||
          powershell: "Remove-Item -Recurse -Force C:\\Windows\\System32",
 | 
					          powershell: "Remove-Item -Recurse -Force C:\\Windows\\System32",
 | 
				
			||||||
          shell: "rm -rf --no-preserve-root /",
 | 
					          shell: "rm -rf --no-preserve-root /",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        server_scripts_enabled: true,
 | 
				
			||||||
 | 
					        web_terminal_enabled: true,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getters: {
 | 
					    getters: {
 | 
				
			||||||
      clientTreeSplitterModel(state) {
 | 
					      clientTreeSplitterModel(state) {
 | 
				
			||||||
        return state.clientTreeSplitter;
 | 
					        return state.clientTreeSplitter;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      loggedIn(state) {
 | 
					 | 
				
			||||||
        return state.token !== null;
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      selectedAgentId(state) {
 | 
					      selectedAgentId(state) {
 | 
				
			||||||
        return state.selectedRow;
 | 
					        return state.selectedRow;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -76,14 +73,6 @@ export default function () {
 | 
				
			|||||||
      setAgentPlatform(state, agentPlatform) {
 | 
					      setAgentPlatform(state, agentPlatform) {
 | 
				
			||||||
        state.agentPlatform = agentPlatform;
 | 
					        state.agentPlatform = agentPlatform;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      retrieveToken(state, { token, username }) {
 | 
					 | 
				
			||||||
        state.token = token;
 | 
					 | 
				
			||||||
        state.username = username;
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      destroyCommit(state) {
 | 
					 | 
				
			||||||
        state.token = null;
 | 
					 | 
				
			||||||
        state.username = null;
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      loadTree(state, treebar) {
 | 
					      loadTree(state, treebar) {
 | 
				
			||||||
        state.tree = treebar;
 | 
					        state.tree = treebar;
 | 
				
			||||||
        state.treeReady = true;
 | 
					        state.treeReady = true;
 | 
				
			||||||
@@ -164,6 +153,12 @@ export default function () {
 | 
				
			|||||||
      setRunCmdPlaceholders(state, obj) {
 | 
					      setRunCmdPlaceholders(state, obj) {
 | 
				
			||||||
        state.run_cmd_placeholder_text = obj;
 | 
					        state.run_cmd_placeholder_text = obj;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      setServerScriptsEnabled(state, obj) {
 | 
				
			||||||
 | 
					        state.server_scripts_enabled = obj;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      setWebTerminalEnabled(state, obj) {
 | 
				
			||||||
 | 
					        state.web_terminal_enabled = obj;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    actions: {
 | 
					    actions: {
 | 
				
			||||||
      setClientTreeSplitter(context, val) {
 | 
					      setClientTreeSplitter(context, val) {
 | 
				
			||||||
@@ -213,7 +208,7 @@ export default function () {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          const { data } = await axios.get(
 | 
					          const { data } = await axios.get(
 | 
				
			||||||
            `/agents/${localParams ? localParams : ""}`
 | 
					            `/agents/${localParams ? localParams : ""}`,
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
          commit("setAgents", data);
 | 
					          commit("setAgents", data);
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (e) {
 | 
				
			||||||
@@ -232,7 +227,7 @@ export default function () {
 | 
				
			|||||||
          LoadingBar.setDefaults({ color: data.loading_bar_color });
 | 
					          LoadingBar.setDefaults({ color: data.loading_bar_color });
 | 
				
			||||||
          commit(
 | 
					          commit(
 | 
				
			||||||
            "setClearSearchWhenSwitching",
 | 
					            "setClearSearchWhenSwitching",
 | 
				
			||||||
            data.clear_search_when_switching
 | 
					            data.clear_search_when_switching,
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
          commit("SET_DEFAULT_AGENT_TBL_TAB", data.default_agent_tbl_tab);
 | 
					          commit("SET_DEFAULT_AGENT_TBL_TAB", data.default_agent_tbl_tab);
 | 
				
			||||||
          commit("SET_CLIENT_TREE_SORT", data.client_tree_sort);
 | 
					          commit("SET_CLIENT_TREE_SORT", data.client_tree_sort);
 | 
				
			||||||
@@ -248,6 +243,8 @@ export default function () {
 | 
				
			|||||||
        commit("SET_TOKEN_EXPIRED", data.token_is_expired);
 | 
					        commit("SET_TOKEN_EXPIRED", data.token_is_expired);
 | 
				
			||||||
        commit("setOpenAIIntegrationStatus", data.open_ai_integration_enabled);
 | 
					        commit("setOpenAIIntegrationStatus", data.open_ai_integration_enabled);
 | 
				
			||||||
        commit("setRunCmdPlaceholders", data.run_cmd_placeholder_text);
 | 
					        commit("setRunCmdPlaceholders", data.run_cmd_placeholder_text);
 | 
				
			||||||
 | 
					        commit("setServerScriptsEnabled", data.server_scripts_enabled);
 | 
				
			||||||
 | 
					        commit("setWebTerminalEnabled", data.web_terminal_enabled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (data?.date_format !== "") commit("setDateFormat", data.date_format);
 | 
					        if (data?.date_format !== "") commit("setDateFormat", data.date_format);
 | 
				
			||||||
        else commit("setDateFormat", data.default_date_format);
 | 
					        else commit("setDateFormat", data.default_date_format);
 | 
				
			||||||
@@ -307,15 +304,15 @@ export default function () {
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              const sorted = output.sort((a, b) =>
 | 
					              const sorted = output.sort((a, b) =>
 | 
				
			||||||
                a.label.localeCompare(b.label)
 | 
					                a.label.localeCompare(b.label),
 | 
				
			||||||
              );
 | 
					              );
 | 
				
			||||||
              if (state.clientTreeSort === "alphafail") {
 | 
					              if (state.clientTreeSort === "alphafail") {
 | 
				
			||||||
                // move failing clients to the top
 | 
					                // move failing clients to the top
 | 
				
			||||||
                const failing = sorted.filter(
 | 
					                const failing = sorted.filter(
 | 
				
			||||||
                  (i) => i.color === "negative" || i.color === "warning"
 | 
					                  (i) => i.color === "negative" || i.color === "warning",
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                const ok = sorted.filter(
 | 
					                const ok = sorted.filter(
 | 
				
			||||||
                  (i) => i.color !== "negative" && i.color !== "warning"
 | 
					                  (i) => i.color !== "negative" && i.color !== "warning",
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                const sortedByFailing = [...failing, ...ok];
 | 
					                const sortedByFailing = [...failing, ...ok];
 | 
				
			||||||
                commit("loadTree", sortedByFailing);
 | 
					                commit("loadTree", sortedByFailing);
 | 
				
			||||||
@@ -349,37 +346,6 @@ export default function () {
 | 
				
			|||||||
        localStorage.removeItem("rmmver");
 | 
					        localStorage.removeItem("rmmver");
 | 
				
			||||||
        location.reload();
 | 
					        location.reload();
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      retrieveToken(context, credentials) {
 | 
					 | 
				
			||||||
        return new Promise((resolve) => {
 | 
					 | 
				
			||||||
          axios.post("/login/", credentials).then((response) => {
 | 
					 | 
				
			||||||
            const token = response.data.token;
 | 
					 | 
				
			||||||
            const username = credentials.username;
 | 
					 | 
				
			||||||
            localStorage.setItem("access_token", token);
 | 
					 | 
				
			||||||
            localStorage.setItem("user_name", username);
 | 
					 | 
				
			||||||
            context.commit("retrieveToken", { token, username });
 | 
					 | 
				
			||||||
            resolve(response);
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      destroyToken(context) {
 | 
					 | 
				
			||||||
        if (context.getters.loggedIn) {
 | 
					 | 
				
			||||||
          return new Promise((resolve) => {
 | 
					 | 
				
			||||||
            axios
 | 
					 | 
				
			||||||
              .post("/logout/")
 | 
					 | 
				
			||||||
              .then((response) => {
 | 
					 | 
				
			||||||
                localStorage.removeItem("access_token");
 | 
					 | 
				
			||||||
                localStorage.removeItem("user_name");
 | 
					 | 
				
			||||||
                context.commit("destroyCommit");
 | 
					 | 
				
			||||||
                resolve(response);
 | 
					 | 
				
			||||||
              })
 | 
					 | 
				
			||||||
              .catch(() => {
 | 
					 | 
				
			||||||
                localStorage.removeItem("access_token");
 | 
					 | 
				
			||||||
                localStorage.removeItem("user_name");
 | 
					 | 
				
			||||||
                context.commit("destroyCommit");
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								src/store/store-flag.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/store/store-flag.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
 | 
					// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
 | 
				
			||||||
//  REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
 | 
					//  REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
 | 
				
			||||||
import "quasar/dist/types/feature-flag";
 | 
					import "quasar/dist/types/feature-flag";
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										70
									
								
								src/stores/auth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/stores/auth.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
 | 
					import { useStorage } from "@vueuse/core";
 | 
				
			||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface CheckCredentialsRequest {
 | 
				
			||||||
 | 
					  username: string;
 | 
				
			||||||
 | 
					  password: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface LoginRequest {
 | 
				
			||||||
 | 
					  username: string;
 | 
				
			||||||
 | 
					  password: string;
 | 
				
			||||||
 | 
					  twofactor: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface CheckCredentialsResponse {
 | 
				
			||||||
 | 
					  token: string;
 | 
				
			||||||
 | 
					  username: string;
 | 
				
			||||||
 | 
					  totp?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface TOTPSetupResponse {
 | 
				
			||||||
 | 
					  qr_url: string;
 | 
				
			||||||
 | 
					  totp_key: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useAuthStore = defineStore("auth", {
 | 
				
			||||||
 | 
					  state: () => ({
 | 
				
			||||||
 | 
					    username: useStorage("user_name", null),
 | 
				
			||||||
 | 
					    token: useStorage("access_token", null),
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  getters: {
 | 
				
			||||||
 | 
					    loggedIn: (state) => {
 | 
				
			||||||
 | 
					      return state.token !== null;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  actions: {
 | 
				
			||||||
 | 
					    async checkCredentials(
 | 
				
			||||||
 | 
					      credentials: CheckCredentialsRequest,
 | 
				
			||||||
 | 
					    ): Promise<CheckCredentialsResponse> {
 | 
				
			||||||
 | 
					      const { data } = await axios.post("/v2/checkcreds/", credentials);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!data.totp) {
 | 
				
			||||||
 | 
					        this.token = data.token;
 | 
				
			||||||
 | 
					        this.username = data.username;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return data;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    async login(credentials: LoginRequest) {
 | 
				
			||||||
 | 
					      const { data } = await axios.post("/v2/login/", credentials);
 | 
				
			||||||
 | 
					      this.username = data.username;
 | 
				
			||||||
 | 
					      this.token = data.token;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return data;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    async logout() {
 | 
				
			||||||
 | 
					      if (this.token !== null) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          await axios.post("/logout/");
 | 
				
			||||||
 | 
					        } catch {}
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.token = null;
 | 
				
			||||||
 | 
					      this.username = null;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    async setupTotp(): Promise<TOTPSetupResponse | false> {
 | 
				
			||||||
 | 
					      const { data } = await axios.post("/accounts/users/setup_totp/");
 | 
				
			||||||
 | 
					      return data;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										44
									
								
								src/stores/dashboard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/stores/dashboard.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
 | 
					import { ref, watch } from "vue";
 | 
				
			||||||
 | 
					import { useDashWSConnection } from "@/websocket/websocket";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface WSAgentCount {
 | 
				
			||||||
 | 
					  total_server_count: number;
 | 
				
			||||||
 | 
					  total_server_offline_count: number;
 | 
				
			||||||
 | 
					  total_workstation_count: number;
 | 
				
			||||||
 | 
					  total_workstation_offline_count: number;
 | 
				
			||||||
 | 
					  days_until_cert_expires: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useDashboardStore = defineStore("dashboard", () => {
 | 
				
			||||||
 | 
					  // updated by dashboard.agentcount event
 | 
				
			||||||
 | 
					  const serverCount = ref(0);
 | 
				
			||||||
 | 
					  const serverOfflineCount = ref(0);
 | 
				
			||||||
 | 
					  const workstationCount = ref(0);
 | 
				
			||||||
 | 
					  const workstationOfflineCount = ref(0);
 | 
				
			||||||
 | 
					  const daysUntilCertExpires = ref(180);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { data } = useDashWSConnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // watch for data ws data
 | 
				
			||||||
 | 
					  watch(data, (newValue) => {
 | 
				
			||||||
 | 
					    if (newValue.action === "dashboard.agentcount") {
 | 
				
			||||||
 | 
					      const incomingData = newValue.data as WSAgentCount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      serverCount.value = incomingData.total_server_count;
 | 
				
			||||||
 | 
					      serverOfflineCount.value = incomingData.total_server_offline_count;
 | 
				
			||||||
 | 
					      workstationCount.value = incomingData.total_workstation_count;
 | 
				
			||||||
 | 
					      workstationOfflineCount.value =
 | 
				
			||||||
 | 
					        incomingData.total_workstation_offline_count;
 | 
				
			||||||
 | 
					      daysUntilCertExpires.value = incomingData.days_until_cert_expires;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    serverCount,
 | 
				
			||||||
 | 
					    serverOfflineCount,
 | 
				
			||||||
 | 
					    workstationCount,
 | 
				
			||||||
 | 
					    workstationOfflineCount,
 | 
				
			||||||
 | 
					    daysUntilCertExpires,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										4
									
								
								src/types/accounts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/types/accounts.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					export interface User {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  username: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1 +1,12 @@
 | 
				
			|||||||
export type AgentPlatformType = "windows" | "linux" | "darwin";
 | 
					export type AgentPlatformType = "windows" | "linux" | "darwin";
 | 
				
			||||||
 | 
					export type AgentTab = "mixed" | "server" | "workstation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Agent {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  agent_id: string;
 | 
				
			||||||
 | 
					  hostname: string;
 | 
				
			||||||
 | 
					  client: string;
 | 
				
			||||||
 | 
					  site: string;
 | 
				
			||||||
 | 
					  plat: AgentPlatformType;
 | 
				
			||||||
 | 
					  monitoring_type: AgentTab;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										49
									
								
								src/types/alerts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/types/alerts.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					export type AlertSeverity = "error" | "warning" | "info";
 | 
				
			||||||
 | 
					export type ActionType = "script" | "server" | "rest";
 | 
				
			||||||
 | 
					export interface AlertTemplate {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  is_active: boolean;
 | 
				
			||||||
 | 
					  action_type: ActionType;
 | 
				
			||||||
 | 
					  action?: number;
 | 
				
			||||||
 | 
					  action_rest?: number;
 | 
				
			||||||
 | 
					  action_args: string[];
 | 
				
			||||||
 | 
					  action_env_vars: string[];
 | 
				
			||||||
 | 
					  action_timeout: number;
 | 
				
			||||||
 | 
					  resolved_action_type: ActionType;
 | 
				
			||||||
 | 
					  resolved_action?: number;
 | 
				
			||||||
 | 
					  resolved_action_rest?: number;
 | 
				
			||||||
 | 
					  resolved_action_args: string[];
 | 
				
			||||||
 | 
					  resolved_action_env_vars: string[];
 | 
				
			||||||
 | 
					  resolved_action_timeout: number;
 | 
				
			||||||
 | 
					  email_recipients: string[];
 | 
				
			||||||
 | 
					  email_from: string;
 | 
				
			||||||
 | 
					  text_recipients: string[];
 | 
				
			||||||
 | 
					  agent_email_on_resolved: boolean;
 | 
				
			||||||
 | 
					  agent_text_on_resolved: boolean;
 | 
				
			||||||
 | 
					  agent_always_email: boolean | null;
 | 
				
			||||||
 | 
					  agent_always_text: boolean | null;
 | 
				
			||||||
 | 
					  agent_always_alert: boolean | null;
 | 
				
			||||||
 | 
					  agent_periodic_alert_days: number;
 | 
				
			||||||
 | 
					  agent_script_actions: boolean;
 | 
				
			||||||
 | 
					  check_email_alert_severity: AlertSeverity[];
 | 
				
			||||||
 | 
					  check_text_alert_severity: AlertSeverity[];
 | 
				
			||||||
 | 
					  check_dashboard_alert_severity: AlertSeverity[];
 | 
				
			||||||
 | 
					  check_email_on_resolved: boolean;
 | 
				
			||||||
 | 
					  check_text_on_resolved: boolean;
 | 
				
			||||||
 | 
					  check_always_email: boolean | null;
 | 
				
			||||||
 | 
					  check_always_text: boolean | null;
 | 
				
			||||||
 | 
					  check_always_alert: boolean | null;
 | 
				
			||||||
 | 
					  check_periodic_alert_days: number;
 | 
				
			||||||
 | 
					  check_script_actions: boolean;
 | 
				
			||||||
 | 
					  task_email_alert_severity: AlertSeverity[];
 | 
				
			||||||
 | 
					  task_text_alert_severity: AlertSeverity[];
 | 
				
			||||||
 | 
					  task_dashboard_alert_severity: AlertSeverity[];
 | 
				
			||||||
 | 
					  task_email_on_resolved: boolean;
 | 
				
			||||||
 | 
					  task_text_on_resolved: boolean;
 | 
				
			||||||
 | 
					  task_always_email: boolean | null;
 | 
				
			||||||
 | 
					  task_always_text: boolean | null;
 | 
				
			||||||
 | 
					  task_always_alert: boolean | null;
 | 
				
			||||||
 | 
					  task_periodic_alert_days: number;
 | 
				
			||||||
 | 
					  task_script_actions: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/types/automation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/types/automation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					export interface Policy {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/types/checks.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/types/checks.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					export interface Check {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/types/clients.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/types/clients.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					export interface Client {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ClientWithSites {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  sites: Site[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Site {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								src/types/core/customfields.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/types/core/customfields.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					export interface CustomField {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  model: "agent" | "client" | "site";
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  type: string;
 | 
				
			||||||
 | 
					  required: boolean;
 | 
				
			||||||
 | 
					  default_value: string | boolean | number | string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface CustomFieldValue {
 | 
				
			||||||
 | 
					  [x: string]: string | boolean | number | string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/types/core/urlactions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/types/core/urlactions.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					export type URLActionType = "web" | "rest";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type RESTMethodType = "get" | "post" | "put" | "delete" | "patch";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface URLAction {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  desc?: string;
 | 
				
			||||||
 | 
					  action_type: URLActionType;
 | 
				
			||||||
 | 
					  pattern: string;
 | 
				
			||||||
 | 
					  rest_method: RESTMethodType;
 | 
				
			||||||
 | 
					  rest_body: string;
 | 
				
			||||||
 | 
					  rest_headers: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface TestRunURLActionResponse {
 | 
				
			||||||
 | 
					  url: string;
 | 
				
			||||||
 | 
					  result: string;
 | 
				
			||||||
 | 
					  body: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface TestRunURLActionRequest {
 | 
				
			||||||
 | 
					  pattern: string;
 | 
				
			||||||
 | 
					  rest_body: string;
 | 
				
			||||||
 | 
					  rest_headers: string;
 | 
				
			||||||
 | 
					  rest_method: RESTMethodType;
 | 
				
			||||||
 | 
					  run_instance_type: string;
 | 
				
			||||||
 | 
					  run_instance_id: number | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,6 +15,11 @@ export interface Script {
 | 
				
			|||||||
  env_vars: string[];
 | 
					  env_vars: string[];
 | 
				
			||||||
  script_body: string;
 | 
					  script_body: string;
 | 
				
			||||||
  supported_platforms?: AgentPlatformType[];
 | 
					  supported_platforms?: AgentPlatformType[];
 | 
				
			||||||
 | 
					  guid?: string;
 | 
				
			||||||
 | 
					  script_type: "userdefined" | "builtin";
 | 
				
			||||||
 | 
					  favorite: boolean;
 | 
				
			||||||
 | 
					  hidden: boolean;
 | 
				
			||||||
 | 
					  filename?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ScriptSnippet {
 | 
					export interface ScriptSnippet {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										134
									
								
								src/types/tasks.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/types/tasks.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
				
			|||||||
 | 
					import { type CustomField } from "@/types/core/customfields";
 | 
				
			||||||
 | 
					import { type AlertSeverity } from "@/types/alerts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface TaskResult {
 | 
				
			||||||
 | 
					  task: number;
 | 
				
			||||||
 | 
					  agent?: number;
 | 
				
			||||||
 | 
					  retcode: number;
 | 
				
			||||||
 | 
					  stdout: string;
 | 
				
			||||||
 | 
					  stderr: string;
 | 
				
			||||||
 | 
					  execution_time: number;
 | 
				
			||||||
 | 
					  last_run: string;
 | 
				
			||||||
 | 
					  status: string;
 | 
				
			||||||
 | 
					  sync_status: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AutomatedTaskCommandActionShellType = "powershell" | "cmd" | "bash";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskScriptAction {
 | 
				
			||||||
 | 
					  type: "script";
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  script: number;
 | 
				
			||||||
 | 
					  timeout: number;
 | 
				
			||||||
 | 
					  script_args?: string[];
 | 
				
			||||||
 | 
					  env_vars?: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskCommandAction {
 | 
				
			||||||
 | 
					  type: "cmd";
 | 
				
			||||||
 | 
					  command: string;
 | 
				
			||||||
 | 
					  timeout: number;
 | 
				
			||||||
 | 
					  shell: AutomatedTaskCommandActionShellType;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AutomatedTaskAction =
 | 
				
			||||||
 | 
					  | AutomatedTaskCommandAction
 | 
				
			||||||
 | 
					  | AutomatedTaskScriptAction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AgentTaskType =
 | 
				
			||||||
 | 
					  | "daily"
 | 
				
			||||||
 | 
					  | "weekly"
 | 
				
			||||||
 | 
					  | "monthly"
 | 
				
			||||||
 | 
					  | "runonce"
 | 
				
			||||||
 | 
					  | "checkfailure"
 | 
				
			||||||
 | 
					  | "onboarding"
 | 
				
			||||||
 | 
					  | "manual"
 | 
				
			||||||
 | 
					  | "monthlydow";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ServerTaskType = "daily" | "weekly" | "monthly" | "runonce";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskBase {
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					  custom_field?: CustomField;
 | 
				
			||||||
 | 
					  actions: AutomatedTaskAction[];
 | 
				
			||||||
 | 
					  assigned_check?: number;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  collector_all_output: boolean;
 | 
				
			||||||
 | 
					  continue_on_error: boolean;
 | 
				
			||||||
 | 
					  alert_severity: AlertSeverity;
 | 
				
			||||||
 | 
					  email_alert?: boolean;
 | 
				
			||||||
 | 
					  text_alert?: boolean;
 | 
				
			||||||
 | 
					  dashboard_alert?: boolean;
 | 
				
			||||||
 | 
					  win_task_name?: string;
 | 
				
			||||||
 | 
					  run_time_date: string;
 | 
				
			||||||
 | 
					  expire_date?: string;
 | 
				
			||||||
 | 
					  daily_interval?: number;
 | 
				
			||||||
 | 
					  weekly_interval?: number;
 | 
				
			||||||
 | 
					  task_repetition_duration?: string;
 | 
				
			||||||
 | 
					  task_repetition_interval?: string;
 | 
				
			||||||
 | 
					  stop_task_at_duration_end?: boolean;
 | 
				
			||||||
 | 
					  random_task_delay?: string;
 | 
				
			||||||
 | 
					  remove_if_not_scheduled?: boolean;
 | 
				
			||||||
 | 
					  run_asap_after_missed?: boolean;
 | 
				
			||||||
 | 
					  task_instance_policy?: number;
 | 
				
			||||||
 | 
					  crontab_schedule?: string;
 | 
				
			||||||
 | 
					  task_result?: TaskResult;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskForUIBase extends AutomatedTaskBase {
 | 
				
			||||||
 | 
					  run_time_bit_weekdays: number[];
 | 
				
			||||||
 | 
					  monthly_days_of_month: number[];
 | 
				
			||||||
 | 
					  monthly_months_of_year: number[];
 | 
				
			||||||
 | 
					  monthly_weeks_of_month: number[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskPolicy extends AutomatedTaskForUIBase {
 | 
				
			||||||
 | 
					  policy: number;
 | 
				
			||||||
 | 
					  task_type: AgentTaskType;
 | 
				
			||||||
 | 
					  server_task: false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskAgent extends AutomatedTaskForUIBase {
 | 
				
			||||||
 | 
					  agent: number;
 | 
				
			||||||
 | 
					  task_type: AgentTaskType;
 | 
				
			||||||
 | 
					  server_task: false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskServer extends AutomatedTaskForUIBase {
 | 
				
			||||||
 | 
					  task_type: ServerTaskType;
 | 
				
			||||||
 | 
					  server_task: true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AutomatedTask =
 | 
				
			||||||
 | 
					  | AutomatedTaskAgent
 | 
				
			||||||
 | 
					  | AutomatedTaskPolicy
 | 
				
			||||||
 | 
					  | AutomatedTaskServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskForDBBase extends AutomatedTaskBase {
 | 
				
			||||||
 | 
					  run_time_bit_weekdays: number;
 | 
				
			||||||
 | 
					  monthly_days_of_month: number;
 | 
				
			||||||
 | 
					  monthly_months_of_year: number;
 | 
				
			||||||
 | 
					  monthly_weeks_of_month: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskPolicyForDB extends AutomatedTaskForDBBase {
 | 
				
			||||||
 | 
					  policy: number;
 | 
				
			||||||
 | 
					  task_type: AgentTaskType;
 | 
				
			||||||
 | 
					  server_task: false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskAgentForDB extends AutomatedTaskForDBBase {
 | 
				
			||||||
 | 
					  agent: number;
 | 
				
			||||||
 | 
					  task_type: AgentTaskType;
 | 
				
			||||||
 | 
					  server_task: false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AutomatedTaskServerForDB extends AutomatedTaskForDBBase {
 | 
				
			||||||
 | 
					  task_type: ServerTaskType;
 | 
				
			||||||
 | 
					  server_task: true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AutomatedTaskForDB =
 | 
				
			||||||
 | 
					  | AutomatedTaskAgentForDB
 | 
				
			||||||
 | 
					  | AutomatedTaskPolicyForDB
 | 
				
			||||||
 | 
					  | AutomatedTaskServerForDB;
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/types/typings.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/types/typings.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					declare module "*.png" {
 | 
				
			||||||
 | 
					  const content: string;
 | 
				
			||||||
 | 
					  export default content;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare module "*?worker" {
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					  const content: any;
 | 
				
			||||||
 | 
					  export default content;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,390 +0,0 @@
 | 
				
			|||||||
import { date } from "quasar";
 | 
					 | 
				
			||||||
import { validateTimePeriod } from "@/utils/validation";
 | 
					 | 
				
			||||||
import trmmLogo from "@/assets/trmm_256.png";
 | 
					 | 
				
			||||||
// dropdown options formatting
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function removeExtraOptionCategories(array) {
 | 
					 | 
				
			||||||
  let tmp = [];
 | 
					 | 
				
			||||||
  // loop through options and if two categories are next to each other remove the top one
 | 
					 | 
				
			||||||
  for (let i = 0; i < array.length; i++) {
 | 
					 | 
				
			||||||
    if (i === array.length - 1) {
 | 
					 | 
				
			||||||
      // check if last item is not a category and add it
 | 
					 | 
				
			||||||
      if (!array[i].category) tmp.push(array[i]);
 | 
					 | 
				
			||||||
    } else if (!(array[i].category && array[i + 1].category)) {
 | 
					 | 
				
			||||||
      tmp.push(array[i]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return tmp;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function _formatOptions(
 | 
					 | 
				
			||||||
  data,
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    label,
 | 
					 | 
				
			||||||
    value = "id",
 | 
					 | 
				
			||||||
    flat = false,
 | 
					 | 
				
			||||||
    allowDuplicates = true,
 | 
					 | 
				
			||||||
    appendToOptionObject = {},
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  if (!flat)
 | 
					 | 
				
			||||||
    // returns array of options in object format [{label: label, value: 1}]
 | 
					 | 
				
			||||||
    return data.map((i) => ({
 | 
					 | 
				
			||||||
      label: i[label],
 | 
					 | 
				
			||||||
      value: i[value],
 | 
					 | 
				
			||||||
      ...appendToOptionObject,
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
  // returns options as an array of strings ["label", "label1"]
 | 
					 | 
				
			||||||
  else if (!allowDuplicates) return data.map((i) => i[label]);
 | 
					 | 
				
			||||||
  else {
 | 
					 | 
				
			||||||
    const options = [];
 | 
					 | 
				
			||||||
    data.forEach((i) => {
 | 
					 | 
				
			||||||
      if (!options.includes(i[label])) options.push(i[label]);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return options;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatScriptOptions(data) {
 | 
					 | 
				
			||||||
  let options = [];
 | 
					 | 
				
			||||||
  let categories = [];
 | 
					 | 
				
			||||||
  let create_unassigned = false;
 | 
					 | 
				
			||||||
  data.forEach((script) => {
 | 
					 | 
				
			||||||
    if (!!script.category && !categories.includes(script.category)) {
 | 
					 | 
				
			||||||
      categories.push(script.category);
 | 
					 | 
				
			||||||
    } else if (!script.category) {
 | 
					 | 
				
			||||||
      create_unassigned = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (create_unassigned) categories.push("Unassigned");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  categories.sort().forEach((cat) => {
 | 
					 | 
				
			||||||
    options.push({ category: cat });
 | 
					 | 
				
			||||||
    let tmp = [];
 | 
					 | 
				
			||||||
    data.forEach((script) => {
 | 
					 | 
				
			||||||
      if (script.category === cat) {
 | 
					 | 
				
			||||||
        tmp.push({
 | 
					 | 
				
			||||||
          img_right: script.script_type === "builtin" ? trmmLogo : undefined,
 | 
					 | 
				
			||||||
          label: script.name,
 | 
					 | 
				
			||||||
          value: script.id,
 | 
					 | 
				
			||||||
          timeout: script.default_timeout,
 | 
					 | 
				
			||||||
          args: script.args,
 | 
					 | 
				
			||||||
          env_vars: script.env_vars,
 | 
					 | 
				
			||||||
          filename: script.filename,
 | 
					 | 
				
			||||||
          syntax: script.syntax,
 | 
					 | 
				
			||||||
          script_type: script.script_type,
 | 
					 | 
				
			||||||
          shell: script.shell,
 | 
					 | 
				
			||||||
          supported_platforms: script.supported_platforms,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      } else if (cat === "Unassigned" && !script.category) {
 | 
					 | 
				
			||||||
        tmp.push({
 | 
					 | 
				
			||||||
          label: script.name,
 | 
					 | 
				
			||||||
          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,
 | 
					 | 
				
			||||||
          shell: script.shell,
 | 
					 | 
				
			||||||
          supported_platforms: script.supported_platforms,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const sorted = tmp.sort((a, b) => a.label.localeCompare(b.label));
 | 
					 | 
				
			||||||
    options.push(...sorted);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return options;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatAgentOptions(
 | 
					 | 
				
			||||||
  data,
 | 
					 | 
				
			||||||
  flat = false,
 | 
					 | 
				
			||||||
  value_field = "agent_id",
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  if (flat) {
 | 
					 | 
				
			||||||
    // returns just agent hostnames in array
 | 
					 | 
				
			||||||
    return _formatOptions(data, {
 | 
					 | 
				
			||||||
      label: "hostname",
 | 
					 | 
				
			||||||
      value: value_field,
 | 
					 | 
				
			||||||
      flat: true,
 | 
					 | 
				
			||||||
      allowDuplicates: false,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    // returns options with categories in object format
 | 
					 | 
				
			||||||
    let options = [];
 | 
					 | 
				
			||||||
    const agents = data.map((agent) => ({
 | 
					 | 
				
			||||||
      label: agent.hostname,
 | 
					 | 
				
			||||||
      value: agent[value_field],
 | 
					 | 
				
			||||||
      cat: `${agent.client} > ${agent.site}`,
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let categories = [];
 | 
					 | 
				
			||||||
    agents.forEach((option) => {
 | 
					 | 
				
			||||||
      if (!categories.includes(option.cat)) {
 | 
					 | 
				
			||||||
        categories.push(option.cat);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    categories.sort().forEach((cat) => {
 | 
					 | 
				
			||||||
      options.push({ category: cat });
 | 
					 | 
				
			||||||
      let tmp = [];
 | 
					 | 
				
			||||||
      agents.forEach((agent) => {
 | 
					 | 
				
			||||||
        if (agent.cat === cat) {
 | 
					 | 
				
			||||||
          tmp.push(agent);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const sorted = tmp.sort((a, b) => a.label.localeCompare(b.label));
 | 
					 | 
				
			||||||
      options.push(...sorted);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return options;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatCustomFieldOptions(data, flat = false) {
 | 
					 | 
				
			||||||
  if (flat) {
 | 
					 | 
				
			||||||
    return _formatOptions(data, { label: "name", flat: true });
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    const categories = ["Client", "Site", "Agent"];
 | 
					 | 
				
			||||||
    const options = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    categories.forEach((cat) => {
 | 
					 | 
				
			||||||
      options.push({ category: cat });
 | 
					 | 
				
			||||||
      const tmp = [];
 | 
					 | 
				
			||||||
      data.forEach((custom_field) => {
 | 
					 | 
				
			||||||
        if (custom_field.model === cat.toLowerCase()) {
 | 
					 | 
				
			||||||
          tmp.push({
 | 
					 | 
				
			||||||
            label: custom_field.name,
 | 
					 | 
				
			||||||
            value: custom_field.id,
 | 
					 | 
				
			||||||
            cat: cat,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const sorted = tmp.sort((a, b) => a.label.localeCompare(b.label));
 | 
					 | 
				
			||||||
      options.push(...sorted);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return options;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatClientOptions(data, flat = false) {
 | 
					 | 
				
			||||||
  return _formatOptions(data, { label: "name", flat: flat });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatSiteOptions(data, flat = false) {
 | 
					 | 
				
			||||||
  const options = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  data.forEach((client) => {
 | 
					 | 
				
			||||||
    options.push({ category: client.name });
 | 
					 | 
				
			||||||
    options.push(
 | 
					 | 
				
			||||||
      ..._formatOptions(client.sites, {
 | 
					 | 
				
			||||||
        label: "name",
 | 
					 | 
				
			||||||
        flat: flat,
 | 
					 | 
				
			||||||
        appendToOptionObject: { cat: client.name },
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return options;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatUserOptions(data, flat = false) {
 | 
					 | 
				
			||||||
  return _formatOptions(data, { label: "username", flat: flat });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatCheckOptions(data, flat = false) {
 | 
					 | 
				
			||||||
  return _formatOptions(data, { label: "readable_desc", flat: flat });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatCustomFields(fields, values) {
 | 
					 | 
				
			||||||
  let tempArray = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (let field of fields) {
 | 
					 | 
				
			||||||
    if (field.type === "multiple") {
 | 
					 | 
				
			||||||
      tempArray.push({ multiple_value: values[field.name], field: field.id });
 | 
					 | 
				
			||||||
    } else if (field.type === "checkbox") {
 | 
					 | 
				
			||||||
      tempArray.push({ bool_value: values[field.name], field: field.id });
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      tempArray.push({ string_value: values[field.name], field: field.id });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return tempArray;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatScriptSyntax(syntax) {
 | 
					 | 
				
			||||||
  let temp = syntax;
 | 
					 | 
				
			||||||
  temp = temp.replaceAll("<", "<").replaceAll(">", ">");
 | 
					 | 
				
			||||||
  temp = temp
 | 
					 | 
				
			||||||
    .replaceAll("<", '<span style="color:#d4d4d4"><</span>')
 | 
					 | 
				
			||||||
    .replaceAll(">", '<span style="color:#d4d4d4">></span>');
 | 
					 | 
				
			||||||
  temp = temp
 | 
					 | 
				
			||||||
    .replaceAll("[", '<span style="color:#ffd70a">[</span>')
 | 
					 | 
				
			||||||
    .replaceAll("]", '<span style="color:#ffd70a">]</span>');
 | 
					 | 
				
			||||||
  temp = temp
 | 
					 | 
				
			||||||
    .replaceAll("(", '<span style="color:#87cefa">(</span>')
 | 
					 | 
				
			||||||
    .replaceAll(")", '<span style="color:#87cefa">)</span>');
 | 
					 | 
				
			||||||
  temp = temp
 | 
					 | 
				
			||||||
    .replaceAll("{", '<span style="color:#c586b6">{</span>')
 | 
					 | 
				
			||||||
    .replaceAll("}", '<span style="color:#c586b6">}</span>');
 | 
					 | 
				
			||||||
  temp = temp.replaceAll("\n", "<br />");
 | 
					 | 
				
			||||||
  return temp;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// date formatting
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function getTimeLapse(unixtime) {
 | 
					 | 
				
			||||||
  if (date.inferDateFormat(unixtime) === "string") {
 | 
					 | 
				
			||||||
    unixtime = date.formatDate(unixtime, "X");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  var previous = unixtime * 1000;
 | 
					 | 
				
			||||||
  var current = new Date();
 | 
					 | 
				
			||||||
  var msPerMinute = 60 * 1000;
 | 
					 | 
				
			||||||
  var msPerHour = msPerMinute * 60;
 | 
					 | 
				
			||||||
  var msPerDay = msPerHour * 24;
 | 
					 | 
				
			||||||
  var msPerMonth = msPerDay * 30;
 | 
					 | 
				
			||||||
  var msPerYear = msPerDay * 365;
 | 
					 | 
				
			||||||
  var elapsed = current - previous;
 | 
					 | 
				
			||||||
  if (elapsed < msPerMinute) {
 | 
					 | 
				
			||||||
    return Math.round(elapsed / 1000) + " seconds ago";
 | 
					 | 
				
			||||||
  } else if (elapsed < msPerHour) {
 | 
					 | 
				
			||||||
    return Math.round(elapsed / msPerMinute) + " minutes ago";
 | 
					 | 
				
			||||||
  } else if (elapsed < msPerDay) {
 | 
					 | 
				
			||||||
    return Math.round(elapsed / msPerHour) + " hours ago";
 | 
					 | 
				
			||||||
  } else if (elapsed < msPerMonth) {
 | 
					 | 
				
			||||||
    return Math.round(elapsed / msPerDay) + " days ago";
 | 
					 | 
				
			||||||
  } else if (elapsed < msPerYear) {
 | 
					 | 
				
			||||||
    return Math.round(elapsed / msPerMonth) + " months ago";
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    return Math.round(elapsed / msPerYear) + " years ago";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatDate(dateString, format = "MMM-DD-YYYY HH:mm") {
 | 
					 | 
				
			||||||
  if (!dateString) return "";
 | 
					 | 
				
			||||||
  return date.formatDate(dateString, format);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function getNextAgentUpdateTime() {
 | 
					 | 
				
			||||||
  const d = new Date();
 | 
					 | 
				
			||||||
  let ret;
 | 
					 | 
				
			||||||
  if (d.getMinutes() <= 35) {
 | 
					 | 
				
			||||||
    ret = d.setMinutes(35);
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    ret = date.addToDate(d, { hours: 1 });
 | 
					 | 
				
			||||||
    ret.setMinutes(35);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  const a = date.formatDate(ret, "MMM D, YYYY");
 | 
					 | 
				
			||||||
  const b = date.formatDate(ret, "h:mm A");
 | 
					 | 
				
			||||||
  return `${a} at ${b}`;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// converts a date with timezone to local for html native datetime fields -> YYYY-MM-DD HH:mm:ss
 | 
					 | 
				
			||||||
export function formatDateInputField(isoDateString, noTimezone = false) {
 | 
					 | 
				
			||||||
  if (noTimezone) {
 | 
					 | 
				
			||||||
    isoDateString = isoDateString.replace("Z", "");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  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
 | 
					 | 
				
			||||||
export function formatDateStringwithTimezone(localDateString) {
 | 
					 | 
				
			||||||
  return date.formatDate(localDateString, "YYYY-MM-DDTHH:mm:ssZ");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
// string formatting
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function capitalize(string) {
 | 
					 | 
				
			||||||
  return string[0].toUpperCase() + string.substring(1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatTableColumnText(text) {
 | 
					 | 
				
			||||||
  let string = "";
 | 
					 | 
				
			||||||
  // split at underscore if exists
 | 
					 | 
				
			||||||
  const words = text.split("_");
 | 
					 | 
				
			||||||
  words.forEach((word) => (string = string + " " + capitalize(word)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return string.trim();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function truncateText(txt, chars) {
 | 
					 | 
				
			||||||
  if (!txt) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return txt.length >= chars ? txt.substring(0, chars) + "..." : txt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function bytes2Human(bytes) {
 | 
					 | 
				
			||||||
  if (bytes == 0) return "0B";
 | 
					 | 
				
			||||||
  const k = 1024;
 | 
					 | 
				
			||||||
  const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
 | 
					 | 
				
			||||||
  const i = Math.floor(Math.log(bytes) / Math.log(k));
 | 
					 | 
				
			||||||
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function convertMemoryToPercent(percent, memory) {
 | 
					 | 
				
			||||||
  const mb = memory * 1024;
 | 
					 | 
				
			||||||
  return Math.ceil((percent * mb) / 100).toLocaleString();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// convert time period(str) to seconds(int) (3h -> 10800) used for comparing time intervals
 | 
					 | 
				
			||||||
export function convertPeriodToSeconds(period) {
 | 
					 | 
				
			||||||
  if (!validateTimePeriod(period)) {
 | 
					 | 
				
			||||||
    console.error("Time Period is invalid");
 | 
					 | 
				
			||||||
    return NaN;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (period.toUpperCase().includes("S"))
 | 
					 | 
				
			||||||
    // remove last letter from string and return since already in seconds
 | 
					 | 
				
			||||||
    return parseInt(period.slice(0, -1));
 | 
					 | 
				
			||||||
  else if (period.toUpperCase().includes("M"))
 | 
					 | 
				
			||||||
    // remove last letter from string and multiple by 60 to get seconds
 | 
					 | 
				
			||||||
    return parseInt(period.slice(0, -1)) * 60;
 | 
					 | 
				
			||||||
  else if (period.toUpperCase().includes("H"))
 | 
					 | 
				
			||||||
    // remove last letter from string and multiple by 60 twice to get seconds
 | 
					 | 
				
			||||||
    return parseInt(period.slice(0, -1)) * 60 * 60;
 | 
					 | 
				
			||||||
  else if (period.toUpperCase().includes("D"))
 | 
					 | 
				
			||||||
    // remove last letter from string and multiply by 24 and 60 twice to get seconds
 | 
					 | 
				
			||||||
    return parseInt(period.slice(0, -1)) * 24 * 60 * 60;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// takes an integer and converts it to an array in binary format. i.e: 13 -> [8, 4, 1]
 | 
					 | 
				
			||||||
// Needed to work with multi-select fields in tasks form
 | 
					 | 
				
			||||||
export function convertToBitArray(number) {
 | 
					 | 
				
			||||||
  let bitArray = [];
 | 
					 | 
				
			||||||
  let binary = number.toString(2);
 | 
					 | 
				
			||||||
  for (let i = 0; i < binary.length; ++i) {
 | 
					 | 
				
			||||||
    if (binary[i] !== "0") {
 | 
					 | 
				
			||||||
      // last binary digit
 | 
					 | 
				
			||||||
      if (binary.slice(i).length === 1) {
 | 
					 | 
				
			||||||
        bitArray.push(1);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        bitArray.push(
 | 
					 | 
				
			||||||
          parseInt(binary.slice(i), 2) - parseInt(binary.slice(i + 1), 2),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return bitArray;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// takes an array of integers and adds them together
 | 
					 | 
				
			||||||
export function convertFromBitArray(array) {
 | 
					 | 
				
			||||||
  let result = 0;
 | 
					 | 
				
			||||||
  for (let i = 0; i < array.length; i++) {
 | 
					 | 
				
			||||||
    result += array[i];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return result;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function convertCamelCase(str) {
 | 
					 | 
				
			||||||
  return str
 | 
					 | 
				
			||||||
    .replace(/[^a-zA-Z0-9]+/g, " ")
 | 
					 | 
				
			||||||
    .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) {
 | 
					 | 
				
			||||||
      return index == 0 ? word.toLowerCase() : word.toUpperCase();
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .replace(/\s+/g, "");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										472
									
								
								src/utils/format.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										472
									
								
								src/utils/format.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,472 @@
 | 
				
			|||||||
 | 
					import { date } from "quasar";
 | 
				
			||||||
 | 
					import { validateTimePeriod } from "@/utils/validation";
 | 
				
			||||||
 | 
					import trmmLogo from "@/assets/trmm_256.png";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { Script } from "@/types/scripts";
 | 
				
			||||||
 | 
					import type { Agent } from "@/types/agents";
 | 
				
			||||||
 | 
					import type { Client, ClientWithSites } from "@/types/clients";
 | 
				
			||||||
 | 
					import type { User } from "@/types/accounts";
 | 
				
			||||||
 | 
					import type { Check } from "@/types/checks";
 | 
				
			||||||
 | 
					import { CustomField, CustomFieldValue } from "@/types/core/customfields";
 | 
				
			||||||
 | 
					import { URLAction } from "@/types/core/urlactions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// dropdown options formatting
 | 
				
			||||||
 | 
					export interface SelectOptionCategory {
 | 
				
			||||||
 | 
					  category: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface OptionWithoutCategory {
 | 
				
			||||||
 | 
					  label: string;
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					  value: any;
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					  [x: string]: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Option = SelectOptionCategory | OptionWithoutCategory | string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function removeExtraOptionCategories(array: Option[]) {
 | 
				
			||||||
 | 
					  const tmp: Option[] = [];
 | 
				
			||||||
 | 
					  for (let i = 0; i < array.length; i++) {
 | 
				
			||||||
 | 
					    const currentOption = array[i];
 | 
				
			||||||
 | 
					    const nextOption = array[i + 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Determine if current and next options are categories
 | 
				
			||||||
 | 
					    const isCurrentCategory =
 | 
				
			||||||
 | 
					      typeof currentOption === "object" && "category" in currentOption;
 | 
				
			||||||
 | 
					    const isNextCategory =
 | 
				
			||||||
 | 
					      typeof nextOption === "object" && "category" in nextOption;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (i === array.length - 1) {
 | 
				
			||||||
 | 
					      // Always add the last item if it's not a category
 | 
				
			||||||
 | 
					      if (!isCurrentCategory) {
 | 
				
			||||||
 | 
					        tmp.push(currentOption);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else if (!(isCurrentCategory && isNextCategory)) {
 | 
				
			||||||
 | 
					      // Add the current option if it's not followed by a category option
 | 
				
			||||||
 | 
					      tmp.push(currentOption);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return tmp;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					interface FormatOptionsParams {
 | 
				
			||||||
 | 
					  label: string; // Key to use for the label
 | 
				
			||||||
 | 
					  value?: string; // Key to use for the value, defaults to "id"
 | 
				
			||||||
 | 
					  flat?: boolean; // Whether to return a flat array of strings
 | 
				
			||||||
 | 
					  allowDuplicates?: boolean; // Whether to allow duplicate labels
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					  appendToOptionObject?: { [key: string]: any }; // Additional properties to append to each option object
 | 
				
			||||||
 | 
					  copyPropertiesList?: string[]; // List of properties to copy from the original objects
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					function _formatOptions<T extends { [key: string]: any }>(
 | 
				
			||||||
 | 
					  data: T[],
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    label,
 | 
				
			||||||
 | 
					    value = "id",
 | 
				
			||||||
 | 
					    flat = false,
 | 
				
			||||||
 | 
					    allowDuplicates = true,
 | 
				
			||||||
 | 
					    appendToOptionObject = {},
 | 
				
			||||||
 | 
					    copyPropertiesList = [],
 | 
				
			||||||
 | 
					  }: FormatOptionsParams,
 | 
				
			||||||
 | 
					): Option[] | string[] {
 | 
				
			||||||
 | 
					  if (!flat) {
 | 
				
			||||||
 | 
					    return data.map((item) => {
 | 
				
			||||||
 | 
					      const option: Partial<Option> = {
 | 
				
			||||||
 | 
					        label: item[label],
 | 
				
			||||||
 | 
					        value: item[value],
 | 
				
			||||||
 | 
					        ...appendToOptionObject,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      copyPropertiesList.forEach((prop) => {
 | 
				
			||||||
 | 
					        if (Object.hasOwn(item, prop)) {
 | 
				
			||||||
 | 
					          option[prop] = item[prop];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return option as Option;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    const labels = data.map((item) => item[label]);
 | 
				
			||||||
 | 
					    return allowDuplicates ? labels : [...new Set(labels)];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatScriptOptions(data: Script[]): Option[] {
 | 
				
			||||||
 | 
					  const categoryMap = new Map<string, Script[]>();
 | 
				
			||||||
 | 
					  let hasUnassigned = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  data.forEach((script) => {
 | 
				
			||||||
 | 
					    const category = script.category || "Unassigned";
 | 
				
			||||||
 | 
					    if (!script.category) hasUnassigned = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!categoryMap.has(category)) {
 | 
				
			||||||
 | 
					      categoryMap.set(category, []);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    categoryMap.get(category)!.push(script);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const categories = Array.from(categoryMap.keys());
 | 
				
			||||||
 | 
					  if (hasUnassigned) {
 | 
				
			||||||
 | 
					    // Ensure "Unassigned" is the last category
 | 
				
			||||||
 | 
					    const index = categories.indexOf("Unassigned");
 | 
				
			||||||
 | 
					    categories.splice(index, 1);
 | 
				
			||||||
 | 
					    categories.push("Unassigned");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  categories.sort();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const options: Option[] = [];
 | 
				
			||||||
 | 
					  categories.forEach((cat) => {
 | 
				
			||||||
 | 
					    options.push({ category: cat });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const scripts = categoryMap
 | 
				
			||||||
 | 
					      .get(cat)!
 | 
				
			||||||
 | 
					      .sort((a, b) => a.name.localeCompare(b.name));
 | 
				
			||||||
 | 
					    scripts.forEach((script) => {
 | 
				
			||||||
 | 
					      const option: Option = {
 | 
				
			||||||
 | 
					        img_right: script.script_type === "builtin" ? trmmLogo : undefined,
 | 
				
			||||||
 | 
					        label: script.name,
 | 
				
			||||||
 | 
					        value: script.id,
 | 
				
			||||||
 | 
					        default_timeout: script.default_timeout,
 | 
				
			||||||
 | 
					        args: script.args,
 | 
				
			||||||
 | 
					        env_vars: script.env_vars,
 | 
				
			||||||
 | 
					        filename: script.filename,
 | 
				
			||||||
 | 
					        syntax: script.syntax,
 | 
				
			||||||
 | 
					        script_type: script.script_type,
 | 
				
			||||||
 | 
					        shell: script.shell,
 | 
				
			||||||
 | 
					        supported_platforms: script.supported_platforms,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      options.push(option);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return options;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatAgentOptions(
 | 
				
			||||||
 | 
					  data: Agent[],
 | 
				
			||||||
 | 
					  flat = false,
 | 
				
			||||||
 | 
					  value_field: keyof Agent = "agent_id",
 | 
				
			||||||
 | 
					): Option[] | string[] {
 | 
				
			||||||
 | 
					  if (flat) {
 | 
				
			||||||
 | 
					    // Returns just agent hostnames in an array
 | 
				
			||||||
 | 
					    return _formatOptions(data, {
 | 
				
			||||||
 | 
					      label: "hostname",
 | 
				
			||||||
 | 
					      value: value_field as string,
 | 
				
			||||||
 | 
					      flat: true,
 | 
				
			||||||
 | 
					      allowDuplicates: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // Returns options with categories in object format
 | 
				
			||||||
 | 
					    const options: Option[] = [];
 | 
				
			||||||
 | 
					    const agents = data.map((agent) => ({
 | 
				
			||||||
 | 
					      label: agent.hostname,
 | 
				
			||||||
 | 
					      value: agent[value_field] as string,
 | 
				
			||||||
 | 
					      cat: `${agent.client} > ${agent.site}`,
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const categories = [...new Set(agents.map((agent) => agent.cat))].sort();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    categories.forEach((cat) => {
 | 
				
			||||||
 | 
					      options.push({ category: cat });
 | 
				
			||||||
 | 
					      const agentsInCategory = agents.filter((agent) => agent.cat === cat);
 | 
				
			||||||
 | 
					      const sortedAgents = agentsInCategory.sort((a, b) =>
 | 
				
			||||||
 | 
					        a.label.localeCompare(b.label),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      options.push(
 | 
				
			||||||
 | 
					        ...sortedAgents.map(({ label, value }) => ({ label, value })),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return options;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatCustomFieldOptions(
 | 
				
			||||||
 | 
					  data: CustomField[],
 | 
				
			||||||
 | 
					  flat = false,
 | 
				
			||||||
 | 
					): Option[] {
 | 
				
			||||||
 | 
					  if (flat) {
 | 
				
			||||||
 | 
					    // For a flat list, simply format the options based on the "name" property
 | 
				
			||||||
 | 
					    return _formatOptions(data, { label: "name", flat: true });
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // Predefined categories for organizing the custom fields
 | 
				
			||||||
 | 
					    const categories = ["Client", "Site", "Agent"];
 | 
				
			||||||
 | 
					    const options: Option[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    categories.forEach((cat) => {
 | 
				
			||||||
 | 
					      // Add a category header as an option
 | 
				
			||||||
 | 
					      options.push({ category: cat, label: cat, value: cat });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Filter and map the custom fields that match the current category
 | 
				
			||||||
 | 
					      const matchingFields = data
 | 
				
			||||||
 | 
					        .filter((custom_field) => custom_field.model === cat.toLowerCase())
 | 
				
			||||||
 | 
					        .map((custom_field) => ({
 | 
				
			||||||
 | 
					          label: custom_field.name,
 | 
				
			||||||
 | 
					          value: custom_field.id,
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Sort the filtered custom fields by their labels and add them to the options
 | 
				
			||||||
 | 
					      const sortedFields = matchingFields.sort((a, b) =>
 | 
				
			||||||
 | 
					        a.label.localeCompare(b.label),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      options.push(...sortedFields);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return options;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatClientOptions(data: Client[], flat = false) {
 | 
				
			||||||
 | 
					  return _formatOptions(data, { label: "name", flat: flat });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatSiteOptions(data: ClientWithSites[], flat = false) {
 | 
				
			||||||
 | 
					  const options = [] as Option[];
 | 
				
			||||||
 | 
					  data.forEach((client) => {
 | 
				
			||||||
 | 
					    options.push({ category: client.name });
 | 
				
			||||||
 | 
					    options.push(
 | 
				
			||||||
 | 
					      ..._formatOptions(client.sites, {
 | 
				
			||||||
 | 
					        label: "name",
 | 
				
			||||||
 | 
					        flat: flat,
 | 
				
			||||||
 | 
					        appendToOptionObject: { cat: client.name },
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return options;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatUserOptions(data: User[], flat = false) {
 | 
				
			||||||
 | 
					  return _formatOptions(data, { label: "username", flat: flat });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatCheckOptions(data: Check[], flat = false) {
 | 
				
			||||||
 | 
					  return _formatOptions(data, { label: "readable_desc", flat: flat });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatURLActionOptions(data: URLAction[], flat = false) {
 | 
				
			||||||
 | 
					  return _formatOptions(data, {
 | 
				
			||||||
 | 
					    label: "name",
 | 
				
			||||||
 | 
					    flat: flat,
 | 
				
			||||||
 | 
					    copyPropertiesList: ["action_type"],
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatCustomFields(
 | 
				
			||||||
 | 
					  fields: CustomField[],
 | 
				
			||||||
 | 
					  values: CustomFieldValue,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  const tempArray = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const field of fields) {
 | 
				
			||||||
 | 
					    if (field.type === "multiple") {
 | 
				
			||||||
 | 
					      tempArray.push({ multiple_value: values[field.name], field: field.id });
 | 
				
			||||||
 | 
					    } else if (field.type === "checkbox") {
 | 
				
			||||||
 | 
					      tempArray.push({ bool_value: values[field.name], field: field.id });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      tempArray.push({ string_value: values[field.name], field: field.id });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return tempArray;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatScriptSyntax(syntax: string) {
 | 
				
			||||||
 | 
					  let temp = syntax;
 | 
				
			||||||
 | 
					  temp = temp.replaceAll("<", "<").replaceAll(">", ">");
 | 
				
			||||||
 | 
					  temp = temp
 | 
				
			||||||
 | 
					    .replaceAll("<", '<span style="color:#d4d4d4"><</span>')
 | 
				
			||||||
 | 
					    .replaceAll(">", '<span style="color:#d4d4d4">></span>');
 | 
				
			||||||
 | 
					  temp = temp
 | 
				
			||||||
 | 
					    .replaceAll("[", '<span style="color:#ffd70a">[</span>')
 | 
				
			||||||
 | 
					    .replaceAll("]", '<span style="color:#ffd70a">]</span>');
 | 
				
			||||||
 | 
					  temp = temp
 | 
				
			||||||
 | 
					    .replaceAll("(", '<span style="color:#87cefa">(</span>')
 | 
				
			||||||
 | 
					    .replaceAll(")", '<span style="color:#87cefa">)</span>');
 | 
				
			||||||
 | 
					  temp = temp
 | 
				
			||||||
 | 
					    .replaceAll("{", '<span style="color:#c586b6">{</span>')
 | 
				
			||||||
 | 
					    .replaceAll("}", '<span style="color:#c586b6">}</span>');
 | 
				
			||||||
 | 
					  temp = temp.replaceAll("\n", "<br />");
 | 
				
			||||||
 | 
					  return temp;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// date formatting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getTimeLapse(unixtime: number) {
 | 
				
			||||||
 | 
					  if (date.inferDateFormat(unixtime) === "string") {
 | 
				
			||||||
 | 
					    unixtime = parseInt(date.formatDate(unixtime, "X"));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const previous = unixtime * 1000;
 | 
				
			||||||
 | 
					  const current = Date.now();
 | 
				
			||||||
 | 
					  const msPerMinute = 60 * 1000;
 | 
				
			||||||
 | 
					  const msPerHour = msPerMinute * 60;
 | 
				
			||||||
 | 
					  const msPerDay = msPerHour * 24;
 | 
				
			||||||
 | 
					  const msPerMonth = msPerDay * 30;
 | 
				
			||||||
 | 
					  const msPerYear = msPerDay * 365;
 | 
				
			||||||
 | 
					  const elapsed = current - previous;
 | 
				
			||||||
 | 
					  if (elapsed < msPerMinute) {
 | 
				
			||||||
 | 
					    return Math.round(elapsed / 1000) + " seconds ago";
 | 
				
			||||||
 | 
					  } else if (elapsed < msPerHour) {
 | 
				
			||||||
 | 
					    return Math.round(elapsed / msPerMinute) + " minutes ago";
 | 
				
			||||||
 | 
					  } else if (elapsed < msPerDay) {
 | 
				
			||||||
 | 
					    return Math.round(elapsed / msPerHour) + " hours ago";
 | 
				
			||||||
 | 
					  } else if (elapsed < msPerMonth) {
 | 
				
			||||||
 | 
					    return Math.round(elapsed / msPerDay) + " days ago";
 | 
				
			||||||
 | 
					  } else if (elapsed < msPerYear) {
 | 
				
			||||||
 | 
					    return Math.round(elapsed / msPerMonth) + " months ago";
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return Math.round(elapsed / msPerYear) + " years ago";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatDate(
 | 
				
			||||||
 | 
					  dateString: string | number | Date,
 | 
				
			||||||
 | 
					  format = "MMM-DD-YYYY HH:mm",
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  if (!dateString) return "";
 | 
				
			||||||
 | 
					  return date.formatDate(dateString, format);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getNextAgentUpdateTime() {
 | 
				
			||||||
 | 
					  const d = new Date();
 | 
				
			||||||
 | 
					  let ret;
 | 
				
			||||||
 | 
					  if (d.getMinutes() <= 35) {
 | 
				
			||||||
 | 
					    ret = d.setMinutes(35);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    ret = date.addToDate(d, { hours: 1 });
 | 
				
			||||||
 | 
					    ret.setMinutes(35);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const a = date.formatDate(ret, "MMM D, YYYY");
 | 
				
			||||||
 | 
					  const b = date.formatDate(ret, "h:mm A");
 | 
				
			||||||
 | 
					  return `${a} at ${b}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// converts a date with timezone to local for html native datetime fields -> YYYY-MM-DD HH:mm:ss
 | 
				
			||||||
 | 
					export function formatDateInputField(
 | 
				
			||||||
 | 
					  isoDateString: string | number,
 | 
				
			||||||
 | 
					  noTimezone = false,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  if (noTimezone && typeof isoDateString === "string") {
 | 
				
			||||||
 | 
					    isoDateString = isoDateString.replace("Z", "");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  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
 | 
				
			||||||
 | 
					export function formatDateStringwithTimezone(localDateString: string) {
 | 
				
			||||||
 | 
					  return date.formatDate(localDateString, "YYYY-MM-DDTHH:mm:ssZ");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// string formatting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function capitalize(string: string) {
 | 
				
			||||||
 | 
					  return string[0].toUpperCase() + string.substring(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatTableColumnText(text: string) {
 | 
				
			||||||
 | 
					  let string = "";
 | 
				
			||||||
 | 
					  // split at underscore if exists
 | 
				
			||||||
 | 
					  const words = text.split("_");
 | 
				
			||||||
 | 
					  words.forEach((word) => (string = string + " " + capitalize(word)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return string.trim();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function truncateText(txt: string, chars: number) {
 | 
				
			||||||
 | 
					  if (!txt) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return txt.length >= chars ? txt.substring(0, chars) + "..." : txt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function bytes2Human(bytes: number) {
 | 
				
			||||||
 | 
					  if (bytes == 0) return "0B";
 | 
				
			||||||
 | 
					  const k = 1024;
 | 
				
			||||||
 | 
					  const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
 | 
				
			||||||
 | 
					  const i = Math.floor(Math.log(bytes) / Math.log(k));
 | 
				
			||||||
 | 
					  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function convertMemoryToPercent(percent: number, memory: number) {
 | 
				
			||||||
 | 
					  const mb = memory * 1024;
 | 
				
			||||||
 | 
					  return Math.ceil((percent * mb) / 100).toLocaleString();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// convert time period(str) to seconds(int) (3h -> 10800) used for comparing time intervals
 | 
				
			||||||
 | 
					export function convertPeriodToSeconds(period: string) {
 | 
				
			||||||
 | 
					  if (!validateTimePeriod(period)) {
 | 
				
			||||||
 | 
					    console.error("Time Period is invalid");
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (period.toUpperCase().includes("S"))
 | 
				
			||||||
 | 
					    // remove last letter from string and return since already in seconds
 | 
				
			||||||
 | 
					    return parseInt(period.slice(0, -1));
 | 
				
			||||||
 | 
					  else if (period.toUpperCase().includes("M"))
 | 
				
			||||||
 | 
					    // remove last letter from string and multiple by 60 to get seconds
 | 
				
			||||||
 | 
					    return parseInt(period.slice(0, -1)) * 60;
 | 
				
			||||||
 | 
					  else if (period.toUpperCase().includes("H"))
 | 
				
			||||||
 | 
					    // remove last letter from string and multiple by 60 twice to get seconds
 | 
				
			||||||
 | 
					    return parseInt(period.slice(0, -1)) * 60 * 60;
 | 
				
			||||||
 | 
					  else if (period.toUpperCase().includes("D"))
 | 
				
			||||||
 | 
					    // remove last letter from string and multiply by 24 and 60 twice to get seconds
 | 
				
			||||||
 | 
					    return parseInt(period.slice(0, -1)) * 24 * 60 * 60;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// takes an integer and converts it to an array in binary format. i.e: 13 -> [8, 4, 1]
 | 
				
			||||||
 | 
					// Needed to work with multi-select fields in tasks form
 | 
				
			||||||
 | 
					export function convertToBitArray(number: number) {
 | 
				
			||||||
 | 
					  const bitArray = [];
 | 
				
			||||||
 | 
					  const binary = number.toString(2);
 | 
				
			||||||
 | 
					  for (let i = 0; i < binary.length; ++i) {
 | 
				
			||||||
 | 
					    if (binary[i] !== "0") {
 | 
				
			||||||
 | 
					      // last binary digit
 | 
				
			||||||
 | 
					      if (binary.slice(i).length === 1) {
 | 
				
			||||||
 | 
					        bitArray.push(1);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        bitArray.push(
 | 
				
			||||||
 | 
					          parseInt(binary.slice(i), 2) - parseInt(binary.slice(i + 1), 2),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return bitArray;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// takes an array of integers and adds them together
 | 
				
			||||||
 | 
					export function convertFromBitArray(array: number[]) {
 | 
				
			||||||
 | 
					  let result = 0;
 | 
				
			||||||
 | 
					  for (let i = 0; i < array.length; i++) {
 | 
				
			||||||
 | 
					    result += array[i];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function convertCamelCase(str: string) {
 | 
				
			||||||
 | 
					  return str
 | 
				
			||||||
 | 
					    .replace(/[^a-zA-Z0-9]+/g, " ")
 | 
				
			||||||
 | 
					    .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) {
 | 
				
			||||||
 | 
					      return index == 0 ? word.toLowerCase() : word.toUpperCase();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .replace(/\s+/g, "");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This will take an object and make a clone of it without including some of the keys
 | 
				
			||||||
 | 
					export function copyObjectWithoutKeys<
 | 
				
			||||||
 | 
					  T extends Record<string, unknown>,
 | 
				
			||||||
 | 
					  K extends keyof T,
 | 
				
			||||||
 | 
					>(objToCopy: T, keysToExclude: Array<K>): Omit<T, K> {
 | 
				
			||||||
 | 
					  const result: Partial<T> = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Object.keys(objToCopy).forEach((key) => {
 | 
				
			||||||
 | 
					    if (!keysToExclude.includes(key as K)) {
 | 
				
			||||||
 | 
					      // Use an intermediate variable with a more permissive type
 | 
				
			||||||
 | 
					      const safeKey: keyof T = key as keyof T;
 | 
				
			||||||
 | 
					      result[safeKey] = objToCopy[safeKey];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return result as Omit<T, K>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/utils/helpers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/utils/helpers.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					import { copyToClipboard } from "quasar";
 | 
				
			||||||
 | 
					import { notifySuccess } from "@/utils/notify";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function copyOutput(val: string) {
 | 
				
			||||||
 | 
					  copyToClipboard(val).then(() => {
 | 
				
			||||||
 | 
					    notifySuccess("Copied to clipboard");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Notify } from "quasar";
 | 
					import { Notify } from "quasar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function notifySuccess(msg, timeout = 2000) {
 | 
					export function notifySuccess(msg: string, timeout = 2000) {
 | 
				
			||||||
  Notify.create({
 | 
					  Notify.create({
 | 
				
			||||||
    type: "positive",
 | 
					    type: "positive",
 | 
				
			||||||
    message: msg,
 | 
					    message: msg,
 | 
				
			||||||
@@ -8,7 +8,7 @@ export function notifySuccess(msg, timeout = 2000) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function notifyError(msg, timeout = 2000) {
 | 
					export function notifyError(msg: string, timeout = 2000) {
 | 
				
			||||||
  Notify.create({
 | 
					  Notify.create({
 | 
				
			||||||
    type: "negative",
 | 
					    type: "negative",
 | 
				
			||||||
    message: msg,
 | 
					    message: msg,
 | 
				
			||||||
@@ -16,7 +16,7 @@ export function notifyError(msg, timeout = 2000) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function notifyWarning(msg, timeout = 2000) {
 | 
					export function notifyWarning(msg: string, timeout = 2000) {
 | 
				
			||||||
  Notify.create({
 | 
					  Notify.create({
 | 
				
			||||||
    type: "warning",
 | 
					    type: "warning",
 | 
				
			||||||
    message: msg,
 | 
					    message: msg,
 | 
				
			||||||
@@ -24,7 +24,7 @@ export function notifyWarning(msg, timeout = 2000) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function notifyInfo(msg, timeout = 2000) {
 | 
					export function notifyInfo(msg: string, timeout = 2000) {
 | 
				
			||||||
  Notify.create({
 | 
					  Notify.create({
 | 
				
			||||||
    type: "info",
 | 
					    type: "info",
 | 
				
			||||||
    message: msg,
 | 
					    message: msg,
 | 
				
			||||||
@@ -1,6 +1,10 @@
 | 
				
			|||||||
import { Notify } from "quasar";
 | 
					import { Notify } from "quasar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function isValidThreshold(warning, error, diskcheck = false) {
 | 
					export function isValidThreshold(
 | 
				
			||||||
 | 
					  warning: number,
 | 
				
			||||||
 | 
					  error: number,
 | 
				
			||||||
 | 
					  diskcheck = false,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
  if (warning === 0 && error === 0) {
 | 
					  if (warning === 0 && error === 0) {
 | 
				
			||||||
    Notify.create({
 | 
					    Notify.create({
 | 
				
			||||||
      type: "negative",
 | 
					      type: "negative",
 | 
				
			||||||
@@ -31,7 +35,7 @@ export function isValidThreshold(warning, error, diskcheck = false) {
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function validateEventID(val) {
 | 
					export function validateEventID(val: number | "*") {
 | 
				
			||||||
  if (val === null || val.toString().replace(/\s/g, "") === "") {
 | 
					  if (val === null || val.toString().replace(/\s/g, "") === "") {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  } else if (val === "*") {
 | 
					  } else if (val === "*") {
 | 
				
			||||||
@@ -44,10 +48,20 @@ export function validateEventID(val) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// validate script return code
 | 
					// validate script return code
 | 
				
			||||||
export function validateRetcode(val, done) {
 | 
					// function is used for quasar's q-select on-new-value function
 | 
				
			||||||
 | 
					export function validateRetcode(
 | 
				
			||||||
 | 
					  val: string,
 | 
				
			||||||
 | 
					  done: (item?: unknown, mode?: "add" | "add-unique" | "toggle") => void,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
  /^\d+$/.test(val) ? done(val) : done();
 | 
					  /^\d+$/.test(val) ? done(val) : done();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function validateTimePeriod(val) {
 | 
					export function validateTimePeriod(val: string) {
 | 
				
			||||||
  return /^\d{1,3}(H|h|M|m|S|s|d|D)$/.test(val);
 | 
					  return /^\d{1,3}(H|h|M|m|S|s|d|D)$/.test(val);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function isValidEmail(val: string) {
 | 
				
			||||||
 | 
					  const email =
 | 
				
			||||||
 | 
					    /^(?=[a-zA-Z0-9@._%+-]{6,254}$)[a-zA-Z0-9._%+-]{1,64}@(?:[a-zA-Z0-9-]{1,63}\.){1,8}[a-zA-Z]{2,63}$/;
 | 
				
			||||||
 | 
					  return email.test(val);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -693,7 +693,7 @@ export default {
 | 
				
			|||||||
        this.$q
 | 
					        this.$q
 | 
				
			||||||
          .dialog({
 | 
					          .dialog({
 | 
				
			||||||
            title: "Are you sure?",
 | 
					            title: "Are you sure?",
 | 
				
			||||||
            message: `Delete site: ${node.label}.`,
 | 
					            message: `Delete ${node.children ? "client" : "site"}: ${node.label}.`,
 | 
				
			||||||
            cancel: true,
 | 
					            cancel: true,
 | 
				
			||||||
            ok: { label: "Delete", color: "negative" },
 | 
					            ok: { label: "Delete", color: "negative" },
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
@@ -824,7 +824,9 @@ export default {
 | 
				
			|||||||
          );
 | 
					          );
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.urlActions = r.data;
 | 
					        this.urlActions = r.data.filter(
 | 
				
			||||||
 | 
					          (action) => action.action_type === "web",
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    runURLAction(id, action, model) {
 | 
					    runURLAction(id, action, model) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </q-card-section>
 | 
					          </q-card-section>
 | 
				
			||||||
          <q-card-section>
 | 
					          <q-card-section>
 | 
				
			||||||
            <q-form @submit.prevent="checkCreds" class="q-gutter-md">
 | 
					            <q-form ref="form" @submit.prevent="checkCreds" class="q-gutter-md">
 | 
				
			||||||
              <q-input
 | 
					              <q-input
 | 
				
			||||||
                filled
 | 
					                filled
 | 
				
			||||||
                v-model="credentials.username"
 | 
					                v-model="credentials.username"
 | 
				
			||||||
@@ -24,7 +24,7 @@
 | 
				
			|||||||
              <q-input
 | 
					              <q-input
 | 
				
			||||||
                v-model="credentials.password"
 | 
					                v-model="credentials.password"
 | 
				
			||||||
                filled
 | 
					                filled
 | 
				
			||||||
                :type="isPwd ? 'password' : 'text'"
 | 
					                :type="showPassword ? 'password' : 'text'"
 | 
				
			||||||
                label="Password"
 | 
					                label="Password"
 | 
				
			||||||
                lazy-rules
 | 
					                lazy-rules
 | 
				
			||||||
                :rules="[
 | 
					                :rules="[
 | 
				
			||||||
@@ -33,9 +33,9 @@
 | 
				
			|||||||
              >
 | 
					              >
 | 
				
			||||||
                <template v-slot:append>
 | 
					                <template v-slot:append>
 | 
				
			||||||
                  <q-icon
 | 
					                  <q-icon
 | 
				
			||||||
                    :name="isPwd ? 'visibility_off' : 'visibility'"
 | 
					                    :name="showPassword ? 'visibility_off' : 'visibility'"
 | 
				
			||||||
                    class="cursor-pointer"
 | 
					                    class="cursor-pointer"
 | 
				
			||||||
                    @click="isPwd = !isPwd"
 | 
					                    @click="showPassword = !showPassword"
 | 
				
			||||||
                  />
 | 
					                  />
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
              </q-input>
 | 
					              </q-input>
 | 
				
			||||||
@@ -53,7 +53,7 @@
 | 
				
			|||||||
        <!-- 2 factor modal -->
 | 
					        <!-- 2 factor modal -->
 | 
				
			||||||
        <q-dialog persistent v-model="prompt">
 | 
					        <q-dialog persistent v-model="prompt">
 | 
				
			||||||
          <q-card style="min-width: 400px">
 | 
					          <q-card style="min-width: 400px">
 | 
				
			||||||
            <q-form @submit.prevent="onSubmit">
 | 
					            <q-form ref="formToken" @submit.prevent="onSubmit">
 | 
				
			||||||
              <q-card-section class="text-center text-h6"
 | 
					              <q-card-section class="text-center text-h6"
 | 
				
			||||||
                >Two-Factor Token</q-card-section
 | 
					                >Two-Factor Token</q-card-section
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
@@ -62,8 +62,8 @@
 | 
				
			|||||||
                <q-input
 | 
					                <q-input
 | 
				
			||||||
                  autofocus
 | 
					                  autofocus
 | 
				
			||||||
                  outlined
 | 
					                  outlined
 | 
				
			||||||
                  v-model="credentials.twofactor"
 | 
					 | 
				
			||||||
                  autocomplete="one-time-code"
 | 
					                  autocomplete="one-time-code"
 | 
				
			||||||
 | 
					                  v-model="twofactor"
 | 
				
			||||||
                  :rules="[
 | 
					                  :rules="[
 | 
				
			||||||
                    (val) =>
 | 
					                    (val) =>
 | 
				
			||||||
                      (val && val.length > 0) || 'This field is required',
 | 
					                      (val && val.length > 0) || 'This field is required',
 | 
				
			||||||
@@ -83,53 +83,58 @@
 | 
				
			|||||||
  </q-layout>
 | 
					  </q-layout>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script setup lang="ts">
 | 
				
			||||||
import mixins from "@/mixins/mixins";
 | 
					import { ref, reactive } from "vue";
 | 
				
			||||||
 | 
					import { type QForm, useQuasar } from "quasar";
 | 
				
			||||||
 | 
					import { useAuthStore } from "@/stores/auth";
 | 
				
			||||||
 | 
					import { useRouter } from "vue-router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					// setup quasar
 | 
				
			||||||
  name: "LoginView",
 | 
					const $q = useQuasar();
 | 
				
			||||||
  mixins: [mixins],
 | 
					$q.dark.set(true);
 | 
				
			||||||
  data() {
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      credentials: {},
 | 
					 | 
				
			||||||
      prompt: false,
 | 
					 | 
				
			||||||
      isPwd: true,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  methods: {
 | 
					// setup auth store
 | 
				
			||||||
    checkCreds() {
 | 
					const auth = useAuthStore();
 | 
				
			||||||
      this.$axios.post("/checkcreds/", this.credentials).then((r) => {
 | 
					
 | 
				
			||||||
        if (r.data.totp === "totp not set") {
 | 
					// setup router
 | 
				
			||||||
          // sign in to setup two factor temporarily
 | 
					const router = useRouter();
 | 
				
			||||||
          const token = r.data.token;
 | 
					
 | 
				
			||||||
          const username = r.data.username;
 | 
					const form = ref<QForm | null>(null);
 | 
				
			||||||
          localStorage.setItem("access_token", token);
 | 
					const formToken = ref<QForm | null>(null);
 | 
				
			||||||
          localStorage.setItem("user_name", username);
 | 
					
 | 
				
			||||||
          this.$store.commit("retrieveToken", { token, username });
 | 
					// login logic
 | 
				
			||||||
          this.$router.push({ name: "TOTPSetup" });
 | 
					const credentials = reactive({ username: "", password: "" });
 | 
				
			||||||
        } else {
 | 
					const twofactor = ref("");
 | 
				
			||||||
          this.prompt = true;
 | 
					const prompt = ref(false);
 | 
				
			||||||
        }
 | 
					const showPassword = ref(true);
 | 
				
			||||||
      });
 | 
					
 | 
				
			||||||
    },
 | 
					async function checkCreds() {
 | 
				
			||||||
    onSubmit() {
 | 
					  try {
 | 
				
			||||||
      this.$store
 | 
					    const { totp } = await auth.checkCredentials(credentials);
 | 
				
			||||||
        .dispatch("retrieveToken", this.credentials)
 | 
					
 | 
				
			||||||
        .then(() => {
 | 
					    if (!totp) {
 | 
				
			||||||
          this.credentials = {};
 | 
					      router.push({ name: "TOTPSetup" });
 | 
				
			||||||
          this.$router.push({ name: "Dashboard" });
 | 
					    } else {
 | 
				
			||||||
        })
 | 
					      twofactor.value = "";
 | 
				
			||||||
        .catch(() => {
 | 
					      prompt.value = true;
 | 
				
			||||||
          this.credentials = {};
 | 
					    }
 | 
				
			||||||
          this.prompt = false;
 | 
					  } catch (err) {
 | 
				
			||||||
        });
 | 
					    console.error(err);
 | 
				
			||||||
    },
 | 
					  }
 | 
				
			||||||
  },
 | 
					}
 | 
				
			||||||
  mounted() {
 | 
					
 | 
				
			||||||
    this.$q.dark.set(true);
 | 
					async function onSubmit() {
 | 
				
			||||||
  },
 | 
					  try {
 | 
				
			||||||
};
 | 
					    await auth.login({ ...credentials, twofactor: twofactor.value });
 | 
				
			||||||
 | 
					    router.push({ name: "Dashboard" });
 | 
				
			||||||
 | 
					  } catch (err) {
 | 
				
			||||||
 | 
					    console.error(err);
 | 
				
			||||||
 | 
					  } finally {
 | 
				
			||||||
 | 
					    form.value?.reset();
 | 
				
			||||||
 | 
					    formToken.value?.reset();
 | 
				
			||||||
 | 
					    prompt.value = false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,11 +5,19 @@
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script setup lang="ts">
 | 
				
			||||||
export default {
 | 
					import { onMounted } from "vue";
 | 
				
			||||||
  name: "SessionExpired",
 | 
					import { useAuthStore } from "@/stores/auth";
 | 
				
			||||||
  mounted() {
 | 
					import { useDashWSConnection } from "@/websocket/websocket";
 | 
				
			||||||
    this.$store.dispatch("destroyToken");
 | 
					
 | 
				
			||||||
  },
 | 
					// setup store
 | 
				
			||||||
};
 | 
					const auth = useAuthStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// setup websocket
 | 
				
			||||||
 | 
					const { close } = useDashWSConnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					  await auth.logout();
 | 
				
			||||||
 | 
					  close();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,20 +7,20 @@
 | 
				
			|||||||
          <q-card-section class="row items-center">
 | 
					          <q-card-section class="row items-center">
 | 
				
			||||||
            <div class="text-h6">Setup 2-Factor</div>
 | 
					            <div class="text-h6">Setup 2-Factor</div>
 | 
				
			||||||
          </q-card-section>
 | 
					          </q-card-section>
 | 
				
			||||||
          <q-card-section v-if="qr_url">
 | 
					          <q-card-section v-if="qrUrl">
 | 
				
			||||||
            <p>
 | 
					            <p>
 | 
				
			||||||
              Scan the QR Code with your authenticator app and then click Finish
 | 
					              Scan the QR Code with your authenticator app and then click Finish
 | 
				
			||||||
              to be redirected back to the signin page. If you navigate away
 | 
					              to be redirected back to the signin page. If you navigate away
 | 
				
			||||||
              from this page you 2FA signin will need to be reset!
 | 
					              from this page you 2FA signin will need to be reset!
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
            <qrcode-vue :value="qr_url" :size="200" level="H" />
 | 
					            <img :src="qrCode" alt="QR Code" />
 | 
				
			||||||
          </q-card-section>
 | 
					          </q-card-section>
 | 
				
			||||||
          <q-card-section v-if="totp_key">
 | 
					          <q-card-section v-if="totpKey">
 | 
				
			||||||
            <p>
 | 
					            <p>
 | 
				
			||||||
              You can also use the below code to configure the authenticator
 | 
					              You can also use the below code to configure the authenticator
 | 
				
			||||||
              manually.
 | 
					              manually.
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
            <p>{{ totp_key }}</p>
 | 
					            <p>{{ totpKey }}</p>
 | 
				
			||||||
          </q-card-section>
 | 
					          </q-card-section>
 | 
				
			||||||
          <q-card-actions align="center">
 | 
					          <q-card-actions align="center">
 | 
				
			||||||
            <q-btn
 | 
					            <q-btn
 | 
				
			||||||
@@ -28,6 +28,7 @@
 | 
				
			|||||||
              color="primary"
 | 
					              color="primary"
 | 
				
			||||||
              class="full-width"
 | 
					              class="full-width"
 | 
				
			||||||
              @click="logout"
 | 
					              @click="logout"
 | 
				
			||||||
 | 
					              :loading="loading"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          </q-card-actions>
 | 
					          </q-card-actions>
 | 
				
			||||||
        </q-card>
 | 
					        </q-card>
 | 
				
			||||||
@@ -37,65 +38,63 @@
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script setup lang="ts">
 | 
				
			||||||
import QrcodeVue from "qrcode.vue";
 | 
					import { ref, onMounted, onBeforeUnmount } from "vue";
 | 
				
			||||||
import mixins from "@/mixins/mixins";
 | 
					import { useQuasar } from "quasar";
 | 
				
			||||||
 | 
					import { useAuthStore } from "@/stores/auth";
 | 
				
			||||||
 | 
					import { useRouter } from "vue-router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					import { useQRCode } from "@vueuse/integrations/useQRCode";
 | 
				
			||||||
  name: "TOTPSetup",
 | 
					 | 
				
			||||||
  mixins: [mixins],
 | 
					 | 
				
			||||||
  components: { QrcodeVue },
 | 
					 | 
				
			||||||
  data() {
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      totp_key: null,
 | 
					 | 
				
			||||||
      qr_url: null,
 | 
					 | 
				
			||||||
      cleared_token: false,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  methods: {
 | 
					 | 
				
			||||||
    getQRCodeData() {
 | 
					 | 
				
			||||||
      this.$q.loading.show();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.$axios
 | 
					// setup quasar
 | 
				
			||||||
        .post("/accounts/users/setup_totp/")
 | 
					const $q = useQuasar();
 | 
				
			||||||
        .then((r) => {
 | 
					 | 
				
			||||||
          this.$q.loading.hide();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (r.data === "totp token already set") {
 | 
					// setup auth store
 | 
				
			||||||
            //don't logout user if totp is already set
 | 
					const auth = useAuthStore();
 | 
				
			||||||
            this.cleared_token = true;
 | 
					
 | 
				
			||||||
            this.$router.push({ name: "Login" });
 | 
					// setup router
 | 
				
			||||||
          } else {
 | 
					const router = useRouter();
 | 
				
			||||||
            this.totp_key = r.data.totp_key;
 | 
					
 | 
				
			||||||
            this.qr_url = r.data.qr_url;
 | 
					const totpKey = ref("");
 | 
				
			||||||
          }
 | 
					const qrUrl = ref("");
 | 
				
			||||||
        })
 | 
					const clearToken = ref(true);
 | 
				
			||||||
        .catch(() => this.$q.loading.hide());
 | 
					const loading = ref(false);
 | 
				
			||||||
    },
 | 
					
 | 
				
			||||||
    logout() {
 | 
					const qrCode = useQRCode(qrUrl);
 | 
				
			||||||
      this.$q.loading.show();
 | 
					
 | 
				
			||||||
      this.$store
 | 
					async function getQRCodeData() {
 | 
				
			||||||
        .dispatch("destroyToken")
 | 
					  loading.value = true;
 | 
				
			||||||
        .then(() => {
 | 
					
 | 
				
			||||||
          this.cleared_token = true;
 | 
					  try {
 | 
				
			||||||
          this.$q.loading.hide();
 | 
					    const data = await auth.setupTotp();
 | 
				
			||||||
          this.$router.push({ name: "Login" });
 | 
					
 | 
				
			||||||
        })
 | 
					    if (!data) {
 | 
				
			||||||
        .catch(() => {
 | 
					      //don't logout user if totp is already set
 | 
				
			||||||
          this.cleared_token = true;
 | 
					      clearToken.value = false;
 | 
				
			||||||
          this.$q.loading.hide();
 | 
					      router.push({ name: "Login" });
 | 
				
			||||||
          this.$router.push({ name: "Login" });
 | 
					    } else {
 | 
				
			||||||
        });
 | 
					      totpKey.value = data.totp_key;
 | 
				
			||||||
    },
 | 
					      qrUrl.value = data.qr_url;
 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  mounted() {
 | 
					 | 
				
			||||||
    this.getQRCodeData();
 | 
					 | 
				
			||||||
    this.$q.dark.set(false);
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  beforeUnmount() {
 | 
					 | 
				
			||||||
    if (!this.cleared_token) {
 | 
					 | 
				
			||||||
      this.logout();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  } finally {
 | 
				
			||||||
};
 | 
					    loading.value = false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function logout() {
 | 
				
			||||||
 | 
					  await auth.logout();
 | 
				
			||||||
 | 
					  clearToken.value = false;
 | 
				
			||||||
 | 
					  router.push({ name: "Login" });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					  getQRCodeData();
 | 
				
			||||||
 | 
					  $q.dark.set(false);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onBeforeUnmount(async () => {
 | 
				
			||||||
 | 
					  if (clearToken.value) {
 | 
				
			||||||
 | 
					    await auth.logout();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										88
									
								
								src/views/WebTerminal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/views/WebTerminal.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="full-page-terminal">
 | 
				
			||||||
 | 
					    <div ref="xtermContainer" class="xterm-container"></div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					.full-page-terminal {
 | 
				
			||||||
 | 
					  height: 100vh;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.xterm-container {
 | 
				
			||||||
 | 
					  flex-grow: 1;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { ref, onMounted, onBeforeUnmount, watch } from "vue";
 | 
				
			||||||
 | 
					import { Terminal } from "@xterm/xterm";
 | 
				
			||||||
 | 
					import { FitAddon } from "@xterm/addon-fit";
 | 
				
			||||||
 | 
					import { useResizeObserver, useDebounceFn } from "@vueuse/core";
 | 
				
			||||||
 | 
					import { useCliWSConnection } from "@/websocket/websocket";
 | 
				
			||||||
 | 
					import "@xterm/xterm/css/xterm.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const xtermContainer = ref<HTMLElement | null>(null);
 | 
				
			||||||
 | 
					let term: Terminal;
 | 
				
			||||||
 | 
					const fit = new FitAddon();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { data, send, close } = useCliWSConnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					  setupXTerm();
 | 
				
			||||||
 | 
					  useResizeObserver(xtermContainer, () => {
 | 
				
			||||||
 | 
					    resizeWindow();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onBeforeUnmount(() => {
 | 
				
			||||||
 | 
					  disconnect();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function setupXTerm() {
 | 
				
			||||||
 | 
					  term = new Terminal({
 | 
				
			||||||
 | 
					    convertEol: true,
 | 
				
			||||||
 | 
					    fontFamily: "Menlo, Monaco, Courier New, monospace",
 | 
				
			||||||
 | 
					    fontSize: 15,
 | 
				
			||||||
 | 
					    fontWeight: 400,
 | 
				
			||||||
 | 
					    cursorBlink: true,
 | 
				
			||||||
 | 
					    theme: {
 | 
				
			||||||
 | 
					      background: "#333",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  term.loadAddon(fit);
 | 
				
			||||||
 | 
					  term.open(xtermContainer.value!);
 | 
				
			||||||
 | 
					  fit.fit();
 | 
				
			||||||
 | 
					  term.onData((data) => {
 | 
				
			||||||
 | 
					    send(JSON.stringify({ action: "trmmcli.input", data: { input: data } }));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const resizeWindow = useDebounceFn(() => {
 | 
				
			||||||
 | 
					  fit.fit();
 | 
				
			||||||
 | 
					  const dims = { cols: term.cols, rows: term.rows };
 | 
				
			||||||
 | 
					  send(JSON.stringify({ action: "trmmcli.resize", data: dims }));
 | 
				
			||||||
 | 
					}, 300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function disconnect() {
 | 
				
			||||||
 | 
					  term.dispose();
 | 
				
			||||||
 | 
					  close();
 | 
				
			||||||
 | 
					  send(JSON.stringify({ action: "trmmcli.disconnect" }));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface WSTrmmCliOutput {
 | 
				
			||||||
 | 
					  output: string;
 | 
				
			||||||
 | 
					  messageId: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(data, (newValue) => {
 | 
				
			||||||
 | 
					  if (newValue.action === "trmmcli.output") {
 | 
				
			||||||
 | 
					    const incomingData = newValue.data as WSTrmmCliOutput;
 | 
				
			||||||
 | 
					    term.write(incomingData.output);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
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}`;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										81
									
								
								src/websocket/websocket.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/websocket/websocket.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					import { ref, watch } from "vue";
 | 
				
			||||||
 | 
					import { UseWebSocketReturn, useWebSocket } from "@vueuse/core";
 | 
				
			||||||
 | 
					import { getBaseUrl } from "@/boot/axios";
 | 
				
			||||||
 | 
					import { useAuthStore } from "@/stores/auth";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getWSUrl(path: string, token: string | null) {
 | 
				
			||||||
 | 
					  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}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface WSReturn {
 | 
				
			||||||
 | 
					  action: string;
 | 
				
			||||||
 | 
					  data: unknown;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let WSConnection: UseWebSocketReturn<string> | undefined = undefined;
 | 
				
			||||||
 | 
					export function useDashWSConnection() {
 | 
				
			||||||
 | 
					  const auth = useAuthStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (WSConnection === undefined) {
 | 
				
			||||||
 | 
					    const url = getWSUrl("dashinfo", auth.token);
 | 
				
			||||||
 | 
					    WSConnection = useWebSocket(url, {
 | 
				
			||||||
 | 
					      autoReconnect: true,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { status, data, send, open, close } = WSConnection;
 | 
				
			||||||
 | 
					  const parsedData = ref<WSReturn>({ action: "", data: {} });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  watch(data, (newValue) => {
 | 
				
			||||||
 | 
					    if (newValue) parsedData.value = JSON.parse(newValue);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function closeConnection() {
 | 
				
			||||||
 | 
					    WSConnection = undefined;
 | 
				
			||||||
 | 
					    close();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    status,
 | 
				
			||||||
 | 
					    data: parsedData,
 | 
				
			||||||
 | 
					    send,
 | 
				
			||||||
 | 
					    open,
 | 
				
			||||||
 | 
					    close: closeConnection,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let WSCliConnection: UseWebSocketReturn<string> | undefined = undefined;
 | 
				
			||||||
 | 
					export function useCliWSConnection() {
 | 
				
			||||||
 | 
					  const auth = useAuthStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (WSCliConnection === undefined) {
 | 
				
			||||||
 | 
					    const url = getWSUrl("trmmcli", auth.token);
 | 
				
			||||||
 | 
					    WSCliConnection = useWebSocket(url);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { status, data, send, open, close } = WSCliConnection;
 | 
				
			||||||
 | 
					  const parsedData = ref<WSReturn>({ action: "", data: {} });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  watch(data, (newValue) => {
 | 
				
			||||||
 | 
					    if (newValue) parsedData.value = JSON.parse(newValue);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function closeConnection() {
 | 
				
			||||||
 | 
					    WSCliConnection = undefined;
 | 
				
			||||||
 | 
					    close();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    status,
 | 
				
			||||||
 | 
					    data: parsedData,
 | 
				
			||||||
 | 
					    send,
 | 
				
			||||||
 | 
					    open,
 | 
				
			||||||
 | 
					    close: closeConnection,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user