Compare commits
	
		
			32 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8f1c694071 | ||
| 
						 | 
					e837c494cb | ||
| 
						 | 
					13f0f117da | ||
| 
						 | 
					0b6ae80777 | ||
| 
						 | 
					cfe1cb2dbf | ||
| 
						 | 
					e1dc8050e3 | ||
| 
						 | 
					3e6365574e | ||
| 
						 | 
					5114ff40aa | ||
| 
						 | 
					6ea7c92b20 | ||
| 
						 | 
					20d534eab0 | ||
| 
						 | 
					1b2286c4f8 | ||
| 
						 | 
					8207f30234 | ||
| 
						 | 
					68036f6837 | ||
| 
						 | 
					03fae45ac5 | ||
| 
						 | 
					c2591c9e7d | ||
| 
						 | 
					7fcbe6fbd8 | ||
| 
						 | 
					a2f472ef9c | ||
| 
						 | 
					8403ac0e93 | ||
| 
						 | 
					b7a91563b0 | ||
| 
						 | 
					ab19afca16 | ||
| 
						 | 
					f24c6a7a80 | ||
| 
						 | 
					99490bf859 | ||
| 
						 | 
					72cdeeaa6a | ||
| 
						 | 
					1eca4d605b | ||
| 
						 | 
					52ee98f6f8 | ||
| 
						 | 
					d270b877c9 | ||
| 
						 | 
					fd8b2a1d98 | ||
| 
						 | 
					f518043d8d | ||
| 
						 | 
					cc2335558d | ||
| 
						 | 
					a8a171ba2c | ||
| 
						 | 
					24a63f477e | ||
| 
						 | 
					ddeb6293a1 | 
@@ -3,9 +3,10 @@ version: '3.7'
 | 
				
			|||||||
services:
 | 
					services:
 | 
				
			||||||
  app-dev:
 | 
					  app-dev:
 | 
				
			||||||
    container_name: trmm-app-dev
 | 
					    container_name: trmm-app-dev
 | 
				
			||||||
    image: node:20-alpine
 | 
					    image: node:18-alpine
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    command: /bin/sh -c "npm install --cache ~/.npm && npm i -g @quasar/cli && npm run serve"
 | 
					    command: /bin/sh -c "npm install --cache ~/.npm && npm run serve"
 | 
				
			||||||
 | 
					    user: 1000:1000
 | 
				
			||||||
    working_dir: /workspace/web
 | 
					    working_dir: /workspace/web
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ..:/workspace:cached
 | 
					      - ..:/workspace:cached
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2795
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2795
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										45
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								package.json
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "web",
 | 
					  "name": "web",
 | 
				
			||||||
  "version": "0.101.46",
 | 
					  "version": "0.101.44",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "productName": "Tactical RMM",
 | 
					  "productName": "Tactical RMM",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
@@ -10,38 +10,37 @@
 | 
				
			|||||||
    "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.12",
 | 
					    "@quasar/extras": "1.16.11",
 | 
				
			||||||
    "@vueuse/core": "10.11.0",
 | 
					    "apexcharts": "3.48.0",
 | 
				
			||||||
    "@vueuse/integrations": "10.11.0",
 | 
					    "axios": "1.6.8",
 | 
				
			||||||
    "@vueuse/shared": "10.11.0",
 | 
					 | 
				
			||||||
    "apexcharts": "3.49.2",
 | 
					 | 
				
			||||||
    "axios": "1.7.2",
 | 
					 | 
				
			||||||
    "dotenv": "16.4.5",
 | 
					    "dotenv": "16.4.5",
 | 
				
			||||||
    "monaco-editor": "0.50.0",
 | 
					    "pinia": "^2.1.7",
 | 
				
			||||||
    "pinia": "2.1.7",
 | 
					    "qrcode.vue": "3.4.1",
 | 
				
			||||||
    "qrcode": "1.5.3",
 | 
					    "quasar": "2.15.2",
 | 
				
			||||||
    "quasar": "2.16.5",
 | 
					    "vue": "3.4.21",
 | 
				
			||||||
    "vue": "3.4.31",
 | 
					    "vue3-apexcharts": "1.5.2",
 | 
				
			||||||
    "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/xterm": "5.5.0",
 | 
					    "xterm": "^5.3.0",
 | 
				
			||||||
    "@xterm/addon-fit": "0.10.0",
 | 
					    "xterm-addon-fit": "^0.8.0",
 | 
				
			||||||
    "yaml": "2.4.5"
 | 
					    "yaml": "2.4.1"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@quasar/cli": "2.4.0",
 | 
				
			||||||
    "@intlify/unplugin-vue-i18n": "4.0.0",
 | 
					    "@intlify/unplugin-vue-i18n": "4.0.0",
 | 
				
			||||||
    "@quasar/app-vite": "1.9.3",
 | 
					    "@quasar/app-vite": "1.8.0",
 | 
				
			||||||
    "@quasar/cli": "2.4.1",
 | 
					    "@types/node": "20.12.2",
 | 
				
			||||||
    "@types/node": "20.14.10",
 | 
					    "@typescript-eslint/eslint-plugin": "7.4.0",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "7.16.0",
 | 
					    "@typescript-eslint/parser": "7.4.0",
 | 
				
			||||||
    "@typescript-eslint/parser": "7.16.0",
 | 
					 | 
				
			||||||
    "autoprefixer": "10.4.19",
 | 
					    "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.5.3"
 | 
					    "typescript": "5.4.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: ["pinia", "axios", "monaco", "integrations"],
 | 
					    boot: ["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-v7",
 | 
					      "mdi-v5",
 | 
				
			||||||
      "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;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										45
									
								
								src/api/core.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/api/core.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,97 +0,0 @@
 | 
				
			|||||||
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,11 +13,6 @@ 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;
 | 
				
			||||||
@@ -61,7 +56,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,5 +1,4 @@
 | 
				
			|||||||
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 = () => {
 | 
				
			||||||
@@ -19,22 +18,27 @@ export function setErrorMessage(data, message) {
 | 
				
			|||||||
  ];
 | 
					  ];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function ({ app, router }) {
 | 
					export default function ({ app, router, store }) {
 | 
				
			||||||
  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 = auth.token;
 | 
					      const token = store.state.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(
 | 
				
			||||||
@@ -97,6 +101,6 @@ export default function ({ app, router }) {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return Promise.reject({ ...error });
 | 
					      return Promise.reject({ ...error });
 | 
				
			||||||
    },
 | 
					    }
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
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)
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -278,7 +278,7 @@ export default {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          name: "resolved_action_name",
 | 
					          name: "resolved_action_name",
 | 
				
			||||||
          label: "Resolved Action",
 | 
					          label: "Resolve 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,11 +179,6 @@
 | 
				
			|||||||
                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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -333,11 +328,6 @@
 | 
				
			|||||||
                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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -419,8 +409,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { computed, ref, watch } from "vue";
 | 
					import { 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";
 | 
				
			||||||
@@ -438,10 +427,6 @@ 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);
 | 
				
			||||||
@@ -526,9 +511,6 @@ 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,
 | 
				
			||||||
@@ -568,7 +550,6 @@ export default {
 | 
				
			|||||||
      loading,
 | 
					      loading,
 | 
				
			||||||
      clientOptions,
 | 
					      clientOptions,
 | 
				
			||||||
      siteOptions,
 | 
					      siteOptions,
 | 
				
			||||||
      hosted,
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      onSubmit,
 | 
					      onSubmit,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -302,9 +302,7 @@ export default {
 | 
				
			|||||||
    async function getURLActions() {
 | 
					    async function getURLActions() {
 | 
				
			||||||
      menuLoading.value = true;
 | 
					      menuLoading.value = true;
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        urlActions.value = (await fetchURLActions()).filter(
 | 
					        urlActions.value = await fetchURLActions();
 | 
				
			||||||
          (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,7 +666,6 @@ 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="filterByPlatformOptions.length === 0">
 | 
					      <q-card-section v-if="scriptOptions.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="filterByPlatformOptions"
 | 
					            :options="scriptOptions"
 | 
				
			||||||
            label="Select script"
 | 
					            label="Select script"
 | 
				
			||||||
            mapOptions
 | 
					            mapOptions
 | 
				
			||||||
            :disable="!!check"
 | 
					            :disable="!!check"
 | 
				
			||||||
@@ -140,7 +140,6 @@ 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
 | 
				
			||||||
@@ -149,13 +148,11 @@ export default {
 | 
				
			|||||||
    // setup script dropdown
 | 
					    // setup script dropdown
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
      script,
 | 
					      script,
 | 
				
			||||||
      filterByPlatformOptions,
 | 
					      scriptOptions,
 | 
				
			||||||
      defaultTimeout,
 | 
					      defaultTimeout,
 | 
				
			||||||
      defaultArgs,
 | 
					      defaultArgs,
 | 
				
			||||||
      defaultEnvVars,
 | 
					      defaultEnvVars,
 | 
				
			||||||
    } = useScriptDropdown({
 | 
					    } = useScriptDropdown(props.check ? props.check.script : undefined, {
 | 
				
			||||||
      script: props.check ? props.check.script : undefined,
 | 
					 | 
				
			||||||
      plat: props.plat,
 | 
					 | 
				
			||||||
      onMount: true,
 | 
					      onMount: true,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -184,7 +181,7 @@ export default {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // non-reactive data
 | 
					      // non-reactive data
 | 
				
			||||||
      failOptions,
 | 
					      failOptions,
 | 
				
			||||||
      filterByPlatformOptions,
 | 
					      scriptOptions,
 | 
				
			||||||
      severityOptions,
 | 
					      severityOptions,
 | 
				
			||||||
      envVarsLabel,
 | 
					      envVarsLabel,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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="filterByPlatformOptions"
 | 
					            :options="filteredScriptOptions"
 | 
				
			||||||
            label="Select Script"
 | 
					            label="Select Script"
 | 
				
			||||||
            outlined
 | 
					            outlined
 | 
				
			||||||
            mapOptions
 | 
					            mapOptions
 | 
				
			||||||
@@ -210,14 +210,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import {
 | 
					import { ref, computed, watch, onMounted } from "vue";
 | 
				
			||||||
  ref,
 | 
					import { useStore } from "vuex";
 | 
				
			||||||
  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";
 | 
				
			||||||
@@ -225,6 +219,7 @@ 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
 | 
				
			||||||
@@ -256,7 +251,7 @@ const patchModeOptions = [
 | 
				
			|||||||
  { label: "Install", value: "install" },
 | 
					  { label: "Install", value: "install" },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default {
 | 
				
			||||||
  name: "BulkAction",
 | 
					  name: "BulkAction",
 | 
				
			||||||
  components: { TacticalDropdown },
 | 
					  components: { TacticalDropdown },
 | 
				
			||||||
  emits: [...useDialogPluginComponent.emits],
 | 
					  emits: [...useDialogPluginComponent.emits],
 | 
				
			||||||
@@ -264,8 +259,14 @@ export default defineComponent({
 | 
				
			|||||||
    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.osType === "windows") {
 | 
					      if (state.value.osType === "windows") {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
          { label: "CMD", value: "cmd" },
 | 
					          { label: "CMD", value: "cmd" },
 | 
				
			||||||
          { label: "Powershell", value: "powershell" },
 | 
					          { label: "Powershell", value: "powershell" },
 | 
				
			||||||
@@ -292,8 +293,7 @@ export default defineComponent({
 | 
				
			|||||||
    // dropdown setup
 | 
					    // dropdown setup
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
      script,
 | 
					      script,
 | 
				
			||||||
      plat,
 | 
					      scriptOptions,
 | 
				
			||||||
      filterByPlatformOptions,
 | 
					 | 
				
			||||||
      defaultTimeout,
 | 
					      defaultTimeout,
 | 
				
			||||||
      defaultArgs,
 | 
					      defaultArgs,
 | 
				
			||||||
      defaultEnvVars,
 | 
					      defaultEnvVars,
 | 
				
			||||||
@@ -304,7 +304,7 @@ export default defineComponent({
 | 
				
			|||||||
    const { client, clientOptions, getClientOptions } = useClientDropdown();
 | 
					    const { client, clientOptions, getClientOptions } = useClientDropdown();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // bulk action logic
 | 
					    // bulk action logic
 | 
				
			||||||
    const state = reactive({
 | 
					    const state = ref({
 | 
				
			||||||
      mode: props.mode,
 | 
					      mode: props.mode,
 | 
				
			||||||
      target: "client",
 | 
					      target: "client",
 | 
				
			||||||
      monType: "all",
 | 
					      monType: "all",
 | 
				
			||||||
@@ -326,39 +326,33 @@ export default defineComponent({
 | 
				
			|||||||
    const loading = ref(false);
 | 
					    const loading = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    watch(
 | 
					    watch(
 | 
				
			||||||
      () => state.target,
 | 
					      () => state.value.target,
 | 
				
			||||||
      () => {
 | 
					      () => {
 | 
				
			||||||
        client.value = null;
 | 
					        client.value = null;
 | 
				
			||||||
        site.value = null;
 | 
					        site.value = null;
 | 
				
			||||||
        agents.value = [];
 | 
					        agents.value = [];
 | 
				
			||||||
      },
 | 
					      }
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    plat.value = state.osType;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    watch(
 | 
					    watch(
 | 
				
			||||||
      () => state.osType,
 | 
					      () => state.value.osType,
 | 
				
			||||||
      (newValue) => {
 | 
					      (newValue) => {
 | 
				
			||||||
        state.custom_shell = null;
 | 
					        state.value.custom_shell = null;
 | 
				
			||||||
        state.run_as_user = false;
 | 
					        state.value.run_as_user = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (newValue === "windows") {
 | 
					        if (newValue === "windows") {
 | 
				
			||||||
          state.shell = "cmd";
 | 
					          state.value.shell = "cmd";
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          state.shell = "/bin/bash";
 | 
					          state.value.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);
 | 
					        const data = await runBulkAction(state.value);
 | 
				
			||||||
        notifySuccess(data);
 | 
					        notifySuccess(data);
 | 
				
			||||||
        onDialogHide();
 | 
					        onDialogHide();
 | 
				
			||||||
      } catch (e) {}
 | 
					      } catch (e) {}
 | 
				
			||||||
@@ -368,7 +362,9 @@ export default defineComponent({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const supportsRunAsUser = () => {
 | 
					    const supportsRunAsUser = () => {
 | 
				
			||||||
      const modes = ["script", "command"];
 | 
					      const modes = ["script", "command"];
 | 
				
			||||||
      return state.osType === "windows" && modes.includes(state.mode);
 | 
					      return (
 | 
				
			||||||
 | 
					        state.value.osType === "windows" && modes.includes(state.value.mode)
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // set modal title and caption
 | 
					    // set modal title and caption
 | 
				
			||||||
@@ -376,10 +372,25 @@ export default defineComponent({
 | 
				
			|||||||
      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
 | 
				
			||||||
@@ -387,7 +398,7 @@ export default defineComponent({
 | 
				
			|||||||
      getAgentOptions();
 | 
					      getAgentOptions();
 | 
				
			||||||
      getSiteOptions();
 | 
					      getSiteOptions();
 | 
				
			||||||
      getClientOptions();
 | 
					      getClientOptions();
 | 
				
			||||||
      if (props.mode === "script") getScriptOptions();
 | 
					      if (props.mode === "script") getScriptOptions(showCommunityScripts.value);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
@@ -396,7 +407,7 @@ export default defineComponent({
 | 
				
			|||||||
      agentOptions,
 | 
					      agentOptions,
 | 
				
			||||||
      clientOptions,
 | 
					      clientOptions,
 | 
				
			||||||
      siteOptions,
 | 
					      siteOptions,
 | 
				
			||||||
      filterByPlatformOptions,
 | 
					      filteredScriptOptions,
 | 
				
			||||||
      loading,
 | 
					      loading,
 | 
				
			||||||
      shellOptions,
 | 
					      shellOptions,
 | 
				
			||||||
      filteredOsTypeOptions,
 | 
					      filteredOsTypeOptions,
 | 
				
			||||||
@@ -422,5 +433,5 @@ export default defineComponent({
 | 
				
			|||||||
      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: number) => !!val || '*Required']"
 | 
					            :rules="[(val) => !!val || '*Required']"
 | 
				
			||||||
            v-model="state.script"
 | 
					            v-model="state.script"
 | 
				
			||||||
            :options="filterByPlatformOptions"
 | 
					            :options="filteredScriptOptions"
 | 
				
			||||||
            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: number) => !!val || '*Required']"
 | 
					            :rules="[(val) => !!val || '*Required']"
 | 
				
			||||||
            outlined
 | 
					            outlined
 | 
				
			||||||
            v-model="state.custom_field"
 | 
					            v-model="state.custom_field"
 | 
				
			||||||
            :options="customFieldOptions"
 | 
					            :options="customFieldOptions"
 | 
				
			||||||
@@ -182,23 +182,23 @@
 | 
				
			|||||||
  </q-dialog>
 | 
					  </q-dialog>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { ref, watch } from "vue";
 | 
					import { ref, watch, computed } 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 { formatScriptSyntax } from "@/utils/format";
 | 
					import {
 | 
				
			||||||
 | 
					  formatScriptSyntax,
 | 
				
			||||||
 | 
					  removeExtraOptionCategories,
 | 
				
			||||||
 | 
					} from "@/utils/format";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//ui imports
 | 
					//ui imports
 | 
				
			||||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
 | 
					import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// types
 | 
					 | 
				
			||||||
import type { Agent } from "@/types/agents";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// static data
 | 
					// static data
 | 
				
			||||||
const outputOptions = [
 | 
					const outputOptions = [
 | 
				
			||||||
  { label: "Wait for Output", value: "wait" },
 | 
					  { label: "Wait for Output", value: "wait" },
 | 
				
			||||||
@@ -208,71 +208,110 @@ const outputOptions = [
 | 
				
			|||||||
  { label: "Save results to Agent Notes", value: "note" },
 | 
					  { label: "Save results to Agent Notes", value: "note" },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// emits
 | 
					export default {
 | 
				
			||||||
defineEmits([...useDialogPluginComponent.emits]);
 | 
					  name: "RunScript",
 | 
				
			||||||
 | 
					  emits: [...useDialogPluginComponent.emits],
 | 
				
			||||||
 | 
					  components: { TacticalDropdown },
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    agent: !Object,
 | 
				
			||||||
 | 
					    script: Number,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  setup(props) {
 | 
				
			||||||
 | 
					    // setup quasar dialog plugin
 | 
				
			||||||
 | 
					    const { dialogRef, onDialogHide } = useDialogPluginComponent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// props
 | 
					    // setup dropdowns
 | 
				
			||||||
const props = defineProps<{
 | 
					    const {
 | 
				
			||||||
  agent: Agent;
 | 
					      script,
 | 
				
			||||||
  script?: number;
 | 
					      scriptOptions,
 | 
				
			||||||
}>();
 | 
					      defaultTimeout,
 | 
				
			||||||
 | 
					      defaultArgs,
 | 
				
			||||||
 | 
					      defaultEnvVars,
 | 
				
			||||||
 | 
					      syntax,
 | 
				
			||||||
 | 
					      link,
 | 
				
			||||||
 | 
					    } = useScriptDropdown(props.script, {
 | 
				
			||||||
 | 
					      onMount: true,
 | 
				
			||||||
 | 
					      filterByPlatform: props.agent.plat,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// setup quasar dialog plugin
 | 
					    // main run script functionaity
 | 
				
			||||||
const { dialogRef, onDialogHide } = useDialogPluginComponent();
 | 
					    const state = ref({
 | 
				
			||||||
 | 
					      output: "wait",
 | 
				
			||||||
 | 
					      emails: [],
 | 
				
			||||||
 | 
					      emailMode: "default",
 | 
				
			||||||
 | 
					      custom_field: null,
 | 
				
			||||||
 | 
					      save_all_output: false,
 | 
				
			||||||
 | 
					      script,
 | 
				
			||||||
 | 
					      args: defaultArgs,
 | 
				
			||||||
 | 
					      env_vars: defaultEnvVars,
 | 
				
			||||||
 | 
					      timeout: defaultTimeout,
 | 
				
			||||||
 | 
					      run_as_user: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// setup dropdowns
 | 
					    const ret = ref(null);
 | 
				
			||||||
const {
 | 
					    const loading = ref(false);
 | 
				
			||||||
  script,
 | 
					    const maximized = ref(false);
 | 
				
			||||||
  filterByPlatformOptions,
 | 
					 | 
				
			||||||
  defaultTimeout,
 | 
					 | 
				
			||||||
  defaultArgs,
 | 
					 | 
				
			||||||
  defaultEnvVars,
 | 
					 | 
				
			||||||
  syntax,
 | 
					 | 
				
			||||||
  link,
 | 
					 | 
				
			||||||
} = useScriptDropdown({
 | 
					 | 
				
			||||||
  script: props.script,
 | 
					 | 
				
			||||||
  plat: props.agent.plat,
 | 
					 | 
				
			||||||
  onMount: true,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// main run script functionaity
 | 
					    async function sendScript() {
 | 
				
			||||||
const state = ref({
 | 
					      ret.value = null;
 | 
				
			||||||
  output: "wait",
 | 
					      loading.value = true;
 | 
				
			||||||
  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);
 | 
					      ret.value = await runScript(props.agent.agent_id, state.value);
 | 
				
			||||||
const loading = ref(false);
 | 
					      loading.value = false;
 | 
				
			||||||
const maximized = ref(false);
 | 
					      if (state.value.output === "forget") {
 | 
				
			||||||
 | 
					        onDialogHide();
 | 
				
			||||||
 | 
					        notifySuccess(ret.value);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function sendScript() {
 | 
					    function openScriptURL() {
 | 
				
			||||||
  ret.value = null;
 | 
					      link.value ? openURL(link.value) : null;
 | 
				
			||||||
  loading.value = true;
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ret.value = await runScript(props.agent.agent_id, state.value);
 | 
					    const filteredScriptOptions = computed(() => {
 | 
				
			||||||
  loading.value = false;
 | 
					      return removeExtraOptionCategories(
 | 
				
			||||||
  if (state.value.output === "forget") {
 | 
					        scriptOptions.value.filter(
 | 
				
			||||||
    onDialogHide();
 | 
					          (script) =>
 | 
				
			||||||
    if (ret.value) notifySuccess(ret.value);
 | 
					            script.category ||
 | 
				
			||||||
  }
 | 
					            !script.supported_platforms ||
 | 
				
			||||||
}
 | 
					            script.supported_platforms.length === 0 ||
 | 
				
			||||||
 | 
					            script.supported_platforms.includes(props.agent.plat)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function openScriptURL() {
 | 
					    // watchers
 | 
				
			||||||
  link.value ? openURL(link.value) : null;
 | 
					    watch(
 | 
				
			||||||
}
 | 
					      [() => state.value.output, () => state.value.emailMode],
 | 
				
			||||||
 | 
					      () => (state.value.emails = [])
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// watchers
 | 
					    return {
 | 
				
			||||||
watch(
 | 
					      // reactive data
 | 
				
			||||||
  [() => state.value.output, () => state.value.emailMode],
 | 
					      state,
 | 
				
			||||||
  () => (state.value.emails = []),
 | 
					      loading,
 | 
				
			||||||
);
 | 
					      filteredScriptOptions,
 | 
				
			||||||
 | 
					      link,
 | 
				
			||||||
 | 
					      syntax,
 | 
				
			||||||
 | 
					      ret,
 | 
				
			||||||
 | 
					      maximized,
 | 
				
			||||||
 | 
					      customFieldOptions,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // non-reactive data
 | 
				
			||||||
 | 
					      outputOptions,
 | 
				
			||||||
 | 
					      runAsUserToolTip,
 | 
				
			||||||
 | 
					      envVarsLabel,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //methods
 | 
				
			||||||
 | 
					      formatScriptSyntax,
 | 
				
			||||||
 | 
					      sendScript,
 | 
				
			||||||
 | 
					      openScriptURL,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // quasar dialog plugin
 | 
				
			||||||
 | 
					      dialogRef,
 | 
				
			||||||
 | 
					      onDialogHide,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <q-dialog ref="dialogRef" @hide="onDialogHide">
 | 
					  <q-dialog ref="dialog" @hide="onHide">
 | 
				
			||||||
    <q-card style="width: 90vw; max-width: 90vw">
 | 
					    <q-card style="width: 90vw; max-width: 90vw">
 | 
				
			||||||
      <q-bar>
 | 
					      <q-bar>
 | 
				
			||||||
        {{ alertTemplate ? "Edit Alert Template" : "Add Alert Template" }}
 | 
					        {{ title }}
 | 
				
			||||||
        <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,62 +150,50 @@
 | 
				
			|||||||
              <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 action will run when an alert is triggered.
 | 
					                  The selected script will run when an alert is triggered. This
 | 
				
			||||||
 | 
					                  script will run on any online agent.
 | 
				
			||||||
                </q-tooltip>
 | 
					                </q-tooltip>
 | 
				
			||||||
              </span>
 | 
					              </span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <q-card-section>
 | 
					            <q-card-section>
 | 
				
			||||||
              <q-option-group
 | 
					              <q-select
 | 
				
			||||||
                v-model="template.action_type"
 | 
					 | 
				
			||||||
                class="q-pb-sm"
 | 
					 | 
				
			||||||
                :options="actionTypeOptions"
 | 
					 | 
				
			||||||
                dense
 | 
					 | 
				
			||||||
                inline
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              <tactical-dropdown
 | 
					 | 
				
			||||||
                v-if="template.action_type == 'script'"
 | 
					 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                label="Failure script"
 | 
					                label="Failure action"
 | 
				
			||||||
 | 
					                dense
 | 
				
			||||||
 | 
					                options-dense
 | 
				
			||||||
                outlined
 | 
					                outlined
 | 
				
			||||||
                clearable
 | 
					                clearable
 | 
				
			||||||
                v-model="template.action"
 | 
					                v-model="template.action"
 | 
				
			||||||
                :options="scriptOptions"
 | 
					                :options="scriptOptions"
 | 
				
			||||||
                mapOptions
 | 
					                map-options
 | 
				
			||||||
                filterable
 | 
					                emit-value
 | 
				
			||||||
                :rules="[(val) => !!val || '*Required']"
 | 
					                @update:model-value="setScriptDefaults('failure')"
 | 
				
			||||||
              />
 | 
					              >
 | 
				
			||||||
 | 
					                <template v-slot:option="scope">
 | 
				
			||||||
              <tactical-dropdown
 | 
					                  <q-item
 | 
				
			||||||
                v-else-if="template.action_type == 'server'"
 | 
					                    v-if="!scope.opt.category"
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                    v-bind="scope.itemProps"
 | 
				
			||||||
                label="Failure script"
 | 
					                    class="q-pl-lg"
 | 
				
			||||||
                outlined
 | 
					                  >
 | 
				
			||||||
                clearable
 | 
					                    <q-item-section>
 | 
				
			||||||
                v-model="template.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="Failure Web Hook"
 | 
					                  >
 | 
				
			||||||
                outlined
 | 
					                </template>
 | 
				
			||||||
                clearable
 | 
					              </q-select>
 | 
				
			||||||
                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 script arguments (press Enter after typing each argument)"
 | 
					                label="Failure action arguments (press Enter after typing each argument)"
 | 
				
			||||||
                filled
 | 
					                filled
 | 
				
			||||||
                v-model="template.action_args"
 | 
					                v-model="template.action_args"
 | 
				
			||||||
                use-input
 | 
					                use-input
 | 
				
			||||||
@@ -217,10 +205,9 @@
 | 
				
			|||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <q-select
 | 
					              <q-select
 | 
				
			||||||
                v-if="template.action_type !== 'rest'"
 | 
					 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                dense
 | 
					                dense
 | 
				
			||||||
                label="Failure script environment vars (press Enter after typing each key=value pair)"
 | 
					                label="Failure action 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
 | 
				
			||||||
@@ -232,15 +219,16 @@
 | 
				
			|||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <q-input
 | 
					              <q-input
 | 
				
			||||||
                v-if="template.action_type !== 'rest'"
 | 
					 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                label="Failure script timeout (seconds)"
 | 
					                label="Failure action 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 script timeout is required',
 | 
					                  (val) => !!val || 'Failure action 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>
 | 
				
			||||||
@@ -249,61 +237,50 @@
 | 
				
			|||||||
              <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 action will run when an alert is resolved.
 | 
					                  The selected script will run when an alert is resolved. This
 | 
				
			||||||
 | 
					                  script will run on any online agent.
 | 
				
			||||||
                </q-tooltip>
 | 
					                </q-tooltip>
 | 
				
			||||||
              </span>
 | 
					              </span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <q-card-section>
 | 
					            <q-card-section>
 | 
				
			||||||
              <q-option-group
 | 
					              <q-select
 | 
				
			||||||
                v-model="template.resolved_action_type"
 | 
					 | 
				
			||||||
                class="q-pb-sm"
 | 
					 | 
				
			||||||
                :options="actionTypeOptions"
 | 
					 | 
				
			||||||
                dense
 | 
					 | 
				
			||||||
                inline
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              <tactical-dropdown
 | 
					 | 
				
			||||||
                v-if="template.resolved_action_type === 'script'"
 | 
					 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                label="Resolved Script"
 | 
					                label="Resolved Action"
 | 
				
			||||||
 | 
					                dense
 | 
				
			||||||
 | 
					                options-dense
 | 
				
			||||||
                outlined
 | 
					                outlined
 | 
				
			||||||
                clearable
 | 
					                clearable
 | 
				
			||||||
                v-model="template.resolved_action"
 | 
					                v-model="template.resolved_action"
 | 
				
			||||||
                :options="scriptOptions"
 | 
					                :options="scriptOptions"
 | 
				
			||||||
                mapOptions
 | 
					                map-options
 | 
				
			||||||
                filterable
 | 
					                emit-value
 | 
				
			||||||
              />
 | 
					                @update:model-value="setScriptDefaults('resolved')"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
              <tactical-dropdown
 | 
					                <template v-slot:option="scope">
 | 
				
			||||||
                v-else-if="template.resolved_action_type === 'server'"
 | 
					                  <q-item
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                    v-if="!scope.opt.category"
 | 
				
			||||||
                label="Resolved Script"
 | 
					                    v-bind="scope.itemProps"
 | 
				
			||||||
                outlined
 | 
					                    class="q-pl-lg"
 | 
				
			||||||
                clearable
 | 
					                  >
 | 
				
			||||||
                v-model="template.resolved_action"
 | 
					                    <q-item-section>
 | 
				
			||||||
                :options="serverScriptOptions"
 | 
					                      <q-item-label v-html="scope.opt.label"></q-item-label>
 | 
				
			||||||
                mapOptions
 | 
					                    </q-item-section>
 | 
				
			||||||
                filterable
 | 
					                  </q-item>
 | 
				
			||||||
              />
 | 
					                  <q-item-label
 | 
				
			||||||
 | 
					                    v-if="scope.opt.category"
 | 
				
			||||||
              <tactical-dropdown
 | 
					                    v-bind="scope.itemProps"
 | 
				
			||||||
                v-else
 | 
					                    header
 | 
				
			||||||
                class="q-mb-sm"
 | 
					                    class="q-pa-sm"
 | 
				
			||||||
                label="Resolved Web Hook"
 | 
					                    >{{ scope.opt.category }}</q-item-label
 | 
				
			||||||
                outlined
 | 
					                  >
 | 
				
			||||||
                clearable
 | 
					                </template>
 | 
				
			||||||
                v-model="template.resolved_action_rest"
 | 
					              </q-select>
 | 
				
			||||||
                :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 script arguments (press Enter after typing each argument)"
 | 
					                label="Resolved action 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
 | 
				
			||||||
@@ -315,7 +292,6 @@
 | 
				
			|||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <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)"
 | 
				
			||||||
@@ -330,15 +306,16 @@
 | 
				
			|||||||
              />
 | 
					              />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <q-input
 | 
					              <q-input
 | 
				
			||||||
                v-if="template.resolved_action_type !== 'rest'"
 | 
					 | 
				
			||||||
                class="q-mb-sm"
 | 
					                class="q-mb-sm"
 | 
				
			||||||
                label="Resolved script timeout (seconds)"
 | 
					                label="Resolved action 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 script timeout is required',
 | 
					                  (val) => !!val || 'Resolved action 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>
 | 
				
			||||||
@@ -347,7 +324,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 action will only run on the following types of
 | 
					                  The selected script will only run on the following types of
 | 
				
			||||||
                  alerts
 | 
					                  alerts
 | 
				
			||||||
                </q-tooltip>
 | 
					                </q-tooltip>
 | 
				
			||||||
              </span>
 | 
					              </span>
 | 
				
			||||||
@@ -697,7 +674,7 @@
 | 
				
			|||||||
                left-label
 | 
					                left-label
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
              <q-toggle
 | 
					              <q-toggle
 | 
				
			||||||
                v-model="template.task_text_on_resolved"
 | 
					                v-model="template.check_text_on_resolved"
 | 
				
			||||||
                label="Text"
 | 
					                label="Text"
 | 
				
			||||||
                color="green"
 | 
					                color="green"
 | 
				
			||||||
                left-label
 | 
					                left-label
 | 
				
			||||||
@@ -711,23 +688,18 @@
 | 
				
			|||||||
              v-if="step > 1"
 | 
					              v-if="step > 1"
 | 
				
			||||||
              flat
 | 
					              flat
 | 
				
			||||||
              color="primary"
 | 
					              color="primary"
 | 
				
			||||||
              @click="stepper?.previous()"
 | 
					              @click="$refs.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="stepper?.next()"
 | 
					              @click="$refs.stepper.next()"
 | 
				
			||||||
              color="primary"
 | 
					              color="primary"
 | 
				
			||||||
              label="Next"
 | 
					              label="Next"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <q-space />
 | 
					            <q-space />
 | 
				
			||||||
            <q-btn
 | 
					            <q-btn @click="onSubmit" color="primary" label="Submit" />
 | 
				
			||||||
              @click="onSubmit"
 | 
					 | 
				
			||||||
              color="primary"
 | 
					 | 
				
			||||||
              label="Submit"
 | 
					 | 
				
			||||||
              :loading="loading"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </q-stepper-navigation>
 | 
					          </q-stepper-navigation>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
      </q-stepper>
 | 
					      </q-stepper>
 | 
				
			||||||
@@ -735,279 +707,195 @@
 | 
				
			|||||||
  </q-dialog>
 | 
					  </q-dialog>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script>
 | 
				
			||||||
import { computed, ref, reactive, watch, nextTick } from "vue";
 | 
					import mixins from "@/mixins/mixins";
 | 
				
			||||||
import { useStore } from "vuex";
 | 
					import { mapGetters } 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";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// components
 | 
					export default {
 | 
				
			||||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
 | 
					  name: "AlertTemplateForm",
 | 
				
			||||||
 | 
					  emits: ["hide", "ok", "cancel"],
 | 
				
			||||||
// types
 | 
					  mixins: [mixins],
 | 
				
			||||||
import type { AlertTemplate, AlertSeverity } from "@/types/alerts";
 | 
					  props: { alertTemplate: Object },
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
// store
 | 
					    return {
 | 
				
			||||||
const store = useStore();
 | 
					      step: 1,
 | 
				
			||||||
const hosted = computed(() => store.state.hosted);
 | 
					      template: {
 | 
				
			||||||
const server_scripts_enabled = computed(
 | 
					        name: "",
 | 
				
			||||||
  () => store.state.server_scripts_enabled,
 | 
					        is_active: true,
 | 
				
			||||||
);
 | 
					        action: null,
 | 
				
			||||||
 | 
					        action_args: [],
 | 
				
			||||||
// props
 | 
					        action_env_vars: [],
 | 
				
			||||||
const props = defineProps<{
 | 
					        action_timeout: 15,
 | 
				
			||||||
  alertTemplate?: AlertTemplate;
 | 
					        resolved_action: null,
 | 
				
			||||||
}>();
 | 
					        resolved_action_args: [],
 | 
				
			||||||
 | 
					        resolved_action_env_vars: [],
 | 
				
			||||||
// emits
 | 
					        resolved_action_timeout: 15,
 | 
				
			||||||
defineEmits([...useDialogPluginComponent.emits]);
 | 
					        email_recipients: [],
 | 
				
			||||||
 | 
					        email_from: "",
 | 
				
			||||||
// setup quasar plugins
 | 
					        text_recipients: [],
 | 
				
			||||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
 | 
					        agent_email_on_resolved: false,
 | 
				
			||||||
const $q = useQuasar();
 | 
					        agent_text_on_resolved: false,
 | 
				
			||||||
 | 
					        agent_always_email: null,
 | 
				
			||||||
const step = ref(1);
 | 
					        agent_always_text: null,
 | 
				
			||||||
 | 
					        agent_always_alert: null,
 | 
				
			||||||
// setup script dropdowns
 | 
					        agent_periodic_alert_days: 0,
 | 
				
			||||||
const {
 | 
					        agent_script_actions: true,
 | 
				
			||||||
  script: failureAction,
 | 
					        check_email_alert_severity: [],
 | 
				
			||||||
  defaultArgs: failureArgs,
 | 
					        check_text_alert_severity: [],
 | 
				
			||||||
  defaultEnvVars: failureEnvVars,
 | 
					        check_dashboard_alert_severity: [],
 | 
				
			||||||
  defaultTimeout: failureTimeout,
 | 
					        check_email_on_resolved: false,
 | 
				
			||||||
  serverScriptOptions,
 | 
					        check_text_on_resolved: false,
 | 
				
			||||||
  scriptOptions,
 | 
					        check_always_email: null,
 | 
				
			||||||
} = useScriptDropdown({ script: props.alertTemplate?.action, onMount: true });
 | 
					        check_always_text: null,
 | 
				
			||||||
 | 
					        check_always_alert: null,
 | 
				
			||||||
const {
 | 
					        check_periodic_alert_days: 0,
 | 
				
			||||||
  script: resolvedAction,
 | 
					        check_script_actions: true,
 | 
				
			||||||
  defaultArgs: resolvedArgs,
 | 
					        task_email_alert_severity: [],
 | 
				
			||||||
  defaultEnvVars: resolvedEnvVars,
 | 
					        task_text_alert_severity: [],
 | 
				
			||||||
  defaultTimeout: resolvedTimeout,
 | 
					        task_dashboard_alert_severity: [],
 | 
				
			||||||
} = useScriptDropdown({
 | 
					        task_email_on_resolved: false,
 | 
				
			||||||
  script: props.alertTemplate?.resolved_action,
 | 
					        task_text_on_resolved: false,
 | 
				
			||||||
  onMount: true,
 | 
					        task_always_email: null,
 | 
				
			||||||
});
 | 
					        task_always_text: null,
 | 
				
			||||||
 | 
					        task_always_alert: null,
 | 
				
			||||||
// setup custom field dropdown
 | 
					        task_periodic_alert_days: 0,
 | 
				
			||||||
const { restActionOptions } = useURLActionDropdown({ onMount: true });
 | 
					        task_script_actions: true,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
// alert template form logic
 | 
					      scriptOptions: [],
 | 
				
			||||||
const template: AlertTemplate = props.alertTemplate
 | 
					      severityOptions: [
 | 
				
			||||||
  ? reactive(Object.assign({}, { ...props.alertTemplate }))
 | 
					        { label: "Error", value: "error" },
 | 
				
			||||||
  : reactive({
 | 
					        { label: "Warning", value: "warning" },
 | 
				
			||||||
      id: 0,
 | 
					        { label: "Informational", value: "info" },
 | 
				
			||||||
      name: "",
 | 
					      ],
 | 
				
			||||||
      is_active: true,
 | 
					      thumbStyle: {
 | 
				
			||||||
      action_type: "script",
 | 
					        right: "2px",
 | 
				
			||||||
      action: failureAction,
 | 
					        borderRadius: "5px",
 | 
				
			||||||
      action_rest: undefined,
 | 
					        backgroundColor: "#027be3",
 | 
				
			||||||
      action_args: failureArgs,
 | 
					        width: "5px",
 | 
				
			||||||
      action_env_vars: failureEnvVars,
 | 
					        opacity: 0.75,
 | 
				
			||||||
      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"]),
 | 
				
			||||||
watch(
 | 
					    title() {
 | 
				
			||||||
  () => template.resolved_action_type,
 | 
					      return this.editing ? "Edit Alert Template" : "Add Alert Template";
 | 
				
			||||||
  () => {
 | 
					    },
 | 
				
			||||||
    template.resolved_action_rest = undefined;
 | 
					    editing() {
 | 
				
			||||||
    template.resolved_action = undefined;
 | 
					      return !!this.alertTemplate;
 | 
				
			||||||
    template.resolved_action_args = [];
 | 
					    },
 | 
				
			||||||
    template.resolved_action_env_vars = [];
 | 
					 | 
				
			||||||
    template.resolved_action_timeout = 30;
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
);
 | 
					  methods: {
 | 
				
			||||||
 | 
					    setScriptDefaults(type) {
 | 
				
			||||||
// sync selected script to scriptdropdown
 | 
					      if (type === "failure") {
 | 
				
			||||||
// only add watchers if editting template
 | 
					        const script = this.scriptOptions.find(
 | 
				
			||||||
if (props.alertTemplate) {
 | 
					          (i) => i.value === this.template.action
 | 
				
			||||||
  watch(
 | 
					        );
 | 
				
			||||||
    () => template.action,
 | 
					        this.template.action_args = script.args;
 | 
				
			||||||
    (newValue) => {
 | 
					        this.template.action_env_vars = script.env_vars;
 | 
				
			||||||
      if (newValue) {
 | 
					      } else if (type === "resolved") {
 | 
				
			||||||
        failureAction.value = newValue;
 | 
					        const script = this.scriptOptions.find(
 | 
				
			||||||
 | 
					          (i) => i.value === this.template.resolved_action
 | 
				
			||||||
        // wait for the script change to happen
 | 
					        );
 | 
				
			||||||
        nextTick(() => {
 | 
					        this.template.resolved_action_args = script.args;
 | 
				
			||||||
          template.action_args = failureArgs.value;
 | 
					        this.template.resolved_action_env_vars = script.env_vars;
 | 
				
			||||||
          template.action_env_vars = failureEnvVars.value;
 | 
					 | 
				
			||||||
          template.action_timeout = failureTimeout.value;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  );
 | 
					    toggleAddEmail() {
 | 
				
			||||||
 | 
					      this.$q
 | 
				
			||||||
  watch(
 | 
					        .dialog({
 | 
				
			||||||
    () => template.resolved_action,
 | 
					          title: "Add email",
 | 
				
			||||||
    (newValue) => {
 | 
					          prompt: {
 | 
				
			||||||
      if (newValue) {
 | 
					            model: "",
 | 
				
			||||||
        resolvedAction.value = newValue;
 | 
					            isValid: (val) => this.isValidEmail(val),
 | 
				
			||||||
 | 
					            type: "email",
 | 
				
			||||||
        // wait for the script change to happen
 | 
					          },
 | 
				
			||||||
        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.email_recipients.push(data);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    toggleAddSMSNumber() {
 | 
				
			||||||
 | 
					      this.$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) => {
 | 
				
			||||||
 | 
					          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();
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
const severityOptions = [
 | 
					    hide() {
 | 
				
			||||||
  { label: "Error", value: "error" },
 | 
					      this.$refs.dialog.hide();
 | 
				
			||||||
  { label: "Warning", value: "warning" },
 | 
					    },
 | 
				
			||||||
  { label: "Informational", value: "info" },
 | 
					    onHide() {
 | 
				
			||||||
];
 | 
					      this.$emit("hide");
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
const staticActionTypeOptions = [
 | 
					    onOk() {
 | 
				
			||||||
  { label: "Send a Web Hook", value: "rest" },
 | 
					      this.$emit("ok");
 | 
				
			||||||
  { label: "Run script on Agent", value: "script" },
 | 
					      this.hide();
 | 
				
			||||||
  { label: "Run script on TRMM Server", value: "server" },
 | 
					    },
 | 
				
			||||||
];
 | 
					  },
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
const actionTypeOptions = computed(() => {
 | 
					    this.getScriptOptions(this.showCommunityScripts).then(
 | 
				
			||||||
  // don't show for hosted at all
 | 
					      (options) => (this.scriptOptions = Object.freeze(options))
 | 
				
			||||||
  if (hosted.value) {
 | 
					 | 
				
			||||||
    return staticActionTypeOptions.filter(
 | 
					 | 
				
			||||||
      (option) => option.value !== "server",
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					    // Copy alertTemplate prop locally
 | 
				
			||||||
  // disable the server script radio button if feature is disabled globally
 | 
					    if (this.editing) Object.assign(this.template, this.alertTemplate);
 | 
				
			||||||
  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,6 +191,24 @@
 | 
				
			|||||||
              }}</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>
 | 
				
			||||||
@@ -247,7 +265,6 @@ 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",
 | 
				
			||||||
@@ -279,12 +296,11 @@ export default {
 | 
				
			|||||||
          sortable: true,
 | 
					          sortable: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          name: "resolved_on",
 | 
					          name: "resolve_on",
 | 
				
			||||||
          label: "Resolved On",
 | 
					          label: "Resolved On",
 | 
				
			||||||
          field: "resolved_on",
 | 
					          field: "resolve_on",
 | 
				
			||||||
          align: "left",
 | 
					          align: "left",
 | 
				
			||||||
          sortable: true,
 | 
					          sortable: true,
 | 
				
			||||||
          format: (a) => this.formatDate(a),
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          name: "snoozed_until",
 | 
					          name: "snoozed_until",
 | 
				
			||||||
@@ -292,7 +308,6 @@ 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" },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
@@ -313,7 +328,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 === "resolved_on") {
 | 
					        } else if (column.name === "resolve_on") {
 | 
				
			||||||
          if (this.includeResolved) return column.name;
 | 
					          if (this.includeResolved) return column.name;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return column.name;
 | 
					          return column.name;
 | 
				
			||||||
@@ -325,7 +340,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,7 +10,6 @@
 | 
				
			|||||||
          <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" /> -->
 | 
				
			||||||
@@ -42,51 +41,6 @@
 | 
				
			|||||||
                    <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>
 | 
				
			||||||
@@ -171,24 +125,6 @@
 | 
				
			|||||||
                    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>
 | 
				
			||||||
@@ -552,28 +488,17 @@
 | 
				
			|||||||
                  </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 type="web" />
 | 
					                <URLActionsTable />
 | 
				
			||||||
              </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>
 | 
				
			||||||
@@ -731,7 +656,6 @@ export default {
 | 
				
			|||||||
    KeyStoreTable,
 | 
					    KeyStoreTable,
 | 
				
			||||||
    URLActionsTable,
 | 
					    URLActionsTable,
 | 
				
			||||||
    APIKeysTable,
 | 
					    APIKeysTable,
 | 
				
			||||||
    // ServerTasksTable,
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mixins: [mixins],
 | 
					  mixins: [mixins],
 | 
				
			||||||
  data() {
 | 
					  data() {
 | 
				
			||||||
@@ -903,7 +827,6 @@ 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!");
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,160 +0,0 @@
 | 
				
			|||||||
<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,28 +1,14 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <q-dialog
 | 
					  <q-dialog ref="dialog" @hide="onHide">
 | 
				
			||||||
    ref="dialogRef"
 | 
					 | 
				
			||||||
    @hide="onDialogHide"
 | 
					 | 
				
			||||||
    @show="loadEditor"
 | 
					 | 
				
			||||||
    @before-hide="cleanupEditors"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <q-card class="q-dialog-plugin" style="width: 60vw">
 | 
					    <q-card class="q-dialog-plugin" style="width: 60vw">
 | 
				
			||||||
      <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
 | 
				
			||||||
@@ -40,8 +26,6 @@
 | 
				
			|||||||
            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>
 | 
				
			||||||
@@ -57,187 +41,89 @@
 | 
				
			|||||||
          />
 | 
					          />
 | 
				
			||||||
        </q-card-section>
 | 
					        </q-card-section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <q-card-section v-if="type === 'rest'">
 | 
					        <q-card-actions align="right">
 | 
				
			||||||
          <q-select
 | 
					          <q-btn flat label="Cancel" v-close-popup />
 | 
				
			||||||
            v-model="localAction.rest_method"
 | 
					          <q-btn flat label="Submit" color="primary" type="submit" />
 | 
				
			||||||
            label="Method"
 | 
					        </q-card-actions>
 | 
				
			||||||
            :options="URLActionMethods"
 | 
					      </q-form>
 | 
				
			||||||
            outlined
 | 
					 | 
				
			||||||
            dense
 | 
					 | 
				
			||||||
            map-options
 | 
					 | 
				
			||||||
            emit-value
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </q-card-section>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <q-card-section v-show="type === 'rest'">
 | 
					 | 
				
			||||||
          <q-toolbar>
 | 
					 | 
				
			||||||
            <q-space />
 | 
					 | 
				
			||||||
            <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 setup lang="ts">
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					import mixins from "@/mixins/mixins";
 | 
				
			||||||
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";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ui imports
 | 
					export default {
 | 
				
			||||||
import TestURLAction from "@/components/modals/coresettings/TestURLAction.vue";
 | 
					  name: "URLActionsForm",
 | 
				
			||||||
 | 
					  emits: ["hide", "ok", "cancel"],
 | 
				
			||||||
import * as monaco from "monaco-editor";
 | 
					  mixins: [mixins],
 | 
				
			||||||
 | 
					  props: { action: Object },
 | 
				
			||||||
// define emits
 | 
					  data() {
 | 
				
			||||||
defineEmits([...useDialogPluginComponent.emits]);
 | 
					    return {
 | 
				
			||||||
 | 
					      localAction: {
 | 
				
			||||||
// define props
 | 
					        name: "",
 | 
				
			||||||
const props = defineProps<{ type: URLActionType; action?: URLAction }>();
 | 
					        desc: "",
 | 
				
			||||||
 | 
					        pattern: "",
 | 
				
			||||||
// setup quasar
 | 
					      },
 | 
				
			||||||
const $q = useQuasar();
 | 
					    };
 | 
				
			||||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// static data
 | 
					 | 
				
			||||||
const URLActionMethods = [
 | 
					 | 
				
			||||||
  { value: "get", label: "GET" },
 | 
					 | 
				
			||||||
  { value: "post", label: "POST" },
 | 
					 | 
				
			||||||
  { value: "put", label: "PUT" },
 | 
					 | 
				
			||||||
  { value: "delete", label: "DELETE" },
 | 
					 | 
				
			||||||
  { value: "patch", label: "PATCH" },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const localAction: URLAction = props.action
 | 
					 | 
				
			||||||
  ? reactive(extend({}, props.action))
 | 
					 | 
				
			||||||
  : reactive({
 | 
					 | 
				
			||||||
      name: "",
 | 
					 | 
				
			||||||
      desc: "",
 | 
					 | 
				
			||||||
      pattern: "",
 | 
					 | 
				
			||||||
      action_type: props.type,
 | 
					 | 
				
			||||||
      rest_body: "{\n    \n}",
 | 
					 | 
				
			||||||
      rest_method: "post",
 | 
					 | 
				
			||||||
      rest_headers: `{\n  "Content-Type": "application/json"\n}`, // eslint-disable-line
 | 
					 | 
				
			||||||
    } as URLAction);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const disableBodyTab = computed(() =>
 | 
					 | 
				
			||||||
  ["get", "delete"].includes(localAction.rest_method),
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
const tab = ref(disableBodyTab.value ? "headers" : "body");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(
 | 
					 | 
				
			||||||
  () => localAction.rest_method,
 | 
					 | 
				
			||||||
  () => {
 | 
					 | 
				
			||||||
    disableBodyTab.value ? (tab.value = "headers") : undefined;
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
);
 | 
					  computed: {
 | 
				
			||||||
 | 
					    title() {
 | 
				
			||||||
async function submit() {
 | 
					      return this.editing ? "Edit URL Action" : "Add URL Action";
 | 
				
			||||||
  $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,
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					    editing() {
 | 
				
			||||||
}
 | 
					      return !!this.action;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    submit() {
 | 
				
			||||||
 | 
					      this.$q.loading.show();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// watch tab change and change model
 | 
					      let data = {
 | 
				
			||||||
watch(tab, (newValue, oldValue) => {
 | 
					        ...this.localAction,
 | 
				
			||||||
  if (oldValue === "body") {
 | 
					      };
 | 
				
			||||||
    localAction.rest_body = editor.getValue();
 | 
					 | 
				
			||||||
  } else if (oldValue === "headers") {
 | 
					 | 
				
			||||||
    localAction.rest_headers = editor.getValue();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (newValue === "body") {
 | 
					      if (this.editing) {
 | 
				
			||||||
    editor.setModel(modelBody);
 | 
					        this.$axios
 | 
				
			||||||
    editor.setValue(localAction.rest_body);
 | 
					          .put(`/core/urlaction/${data.id}/`, data)
 | 
				
			||||||
  } else if (newValue === "headers") {
 | 
					          .then(() => {
 | 
				
			||||||
    editor.setModel(modelHeaders);
 | 
					            this.$q.loading.hide();
 | 
				
			||||||
    editor.setValue(localAction.rest_headers);
 | 
					            this.onOk();
 | 
				
			||||||
  }
 | 
					            this.notifySuccess("Url Action was edited!");
 | 
				
			||||||
});
 | 
					          })
 | 
				
			||||||
 | 
					          .catch(() => {
 | 
				
			||||||
function loadEditor() {
 | 
					            this.$q.loading.hide();
 | 
				
			||||||
  const theme = $q.dark.isActive ? "vs-dark" : "vs-light";
 | 
					          });
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
  if (!editorDiv.value) return;
 | 
					        this.$axios
 | 
				
			||||||
 | 
					          .post("/core/urlaction/", data)
 | 
				
			||||||
  editor = monaco.editor.create(editorDiv.value, {
 | 
					          .then(() => {
 | 
				
			||||||
    model: tab.value === "body" ? modelBody : modelHeaders,
 | 
					            this.$q.loading.hide();
 | 
				
			||||||
    theme: theme,
 | 
					            this.onOk();
 | 
				
			||||||
    automaticLayout: true,
 | 
					            this.notifySuccess("URL Action was added!");
 | 
				
			||||||
    minimap: { enabled: false },
 | 
					          })
 | 
				
			||||||
    quickSuggestions: false,
 | 
					          .catch(() => {
 | 
				
			||||||
  });
 | 
					            this.$q.loading.hide();
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
  editor.onDidChangeModelContent(() => {
 | 
					      }
 | 
				
			||||||
    if (tab.value === "body") {
 | 
					    },
 | 
				
			||||||
      localAction.rest_body = editor.getValue();
 | 
					    show() {
 | 
				
			||||||
    } else if (tab.value === "headers") {
 | 
					      this.$refs.dialog.show();
 | 
				
			||||||
      localAction.rest_headers = editor.getValue();
 | 
					    },
 | 
				
			||||||
    }
 | 
					    hide() {
 | 
				
			||||||
  });
 | 
					      this.$refs.dialog.hide();
 | 
				
			||||||
}
 | 
					    },
 | 
				
			||||||
 | 
					    onHide() {
 | 
				
			||||||
function cleanupEditors() {
 | 
					      this.$emit("hide");
 | 
				
			||||||
  modelBody.dispose();
 | 
					    },
 | 
				
			||||||
  modelHeaders.dispose();
 | 
					    onOk() {
 | 
				
			||||||
  editor.dispose();
 | 
					      this.$emit("ok");
 | 
				
			||||||
}
 | 
					      this.hide();
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
 | 
					    // If pk prop is set that means we are editing
 | 
				
			||||||
 | 
					    if (this.action) Object.assign(this.localAction, this.action);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,21 +1,15 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <div class="row">
 | 
					    <div class="row">
 | 
				
			||||||
      <div class="text-subtitle2">
 | 
					      <div class="text-subtitle2">URL Actions</div>
 | 
				
			||||||
        {{
 | 
					 | 
				
			||||||
          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 ${props.type === 'web' ? 'URL Action' : 'Web Hook'}`"
 | 
					        label="Add URL Action"
 | 
				
			||||||
        @click="addURLAction"
 | 
					        @click="addAction"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <q-separator />
 | 
					    <q-separator />
 | 
				
			||||||
@@ -23,36 +17,31 @@
 | 
				
			|||||||
      dense
 | 
					      dense
 | 
				
			||||||
      :rows="actions"
 | 
					      :rows="actions"
 | 
				
			||||||
      :columns="columns"
 | 
					      :columns="columns"
 | 
				
			||||||
      :pagination="{ rowsPerPage: 0, sortBy: 'name', descending: true }"
 | 
					      v-model:pagination="pagination"
 | 
				
			||||||
      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 ${props.type === 'web' ? 'URL Actions' : 'Web Hooks'} added yet`"
 | 
					      no-data-label="No URL Actions 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="editURLAction(props.row)"
 | 
					          @dblclick="editAction(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="editURLAction(props.row)">
 | 
					              <q-item clickable v-close-popup @click="editAction(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
 | 
					              <q-item clickable v-close-popup @click="deleteAction(props.row)">
 | 
				
			||||||
                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>
 | 
				
			||||||
@@ -68,15 +57,15 @@
 | 
				
			|||||||
          </q-menu>
 | 
					          </q-menu>
 | 
				
			||||||
          <!-- name -->
 | 
					          <!-- name -->
 | 
				
			||||||
          <q-td>
 | 
					          <q-td>
 | 
				
			||||||
            {{ truncateText(props.row.name, 30) }}
 | 
					            {{ props.row.name }}
 | 
				
			||||||
          </q-td>
 | 
					          </q-td>
 | 
				
			||||||
          <!-- desc -->
 | 
					          <!-- desc -->
 | 
				
			||||||
          <q-td>
 | 
					          <q-td>
 | 
				
			||||||
            {{ truncateText(props.row.desc, 20) }}
 | 
					            {{ props.row.desc }}
 | 
				
			||||||
          </q-td>
 | 
					          </q-td>
 | 
				
			||||||
          <!-- pattern -->
 | 
					          <!-- pattern -->
 | 
				
			||||||
          <q-td>
 | 
					          <q-td>
 | 
				
			||||||
            {{ truncateText(props.row.pattern, 20) }}
 | 
					            {{ props.row.pattern }}
 | 
				
			||||||
          </q-td>
 | 
					          </q-td>
 | 
				
			||||||
        </q-tr>
 | 
					        </q-tr>
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
@@ -84,103 +73,105 @@
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script>
 | 
				
			||||||
// 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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// types
 | 
					export default {
 | 
				
			||||||
import { type URLActionType, type URLAction } from "@/types/core/urlactions";
 | 
					  name: "URLActionTable",
 | 
				
			||||||
 | 
					  mixins: [mixins],
 | 
				
			||||||
// define props
 | 
					  data() {
 | 
				
			||||||
const props = defineProps<{ type: URLActionType }>();
 | 
					    return {
 | 
				
			||||||
 | 
					      actions: [],
 | 
				
			||||||
// setup quasar
 | 
					      pagination: {
 | 
				
			||||||
const $q = useQuasar();
 | 
					        rowsPerPage: 0,
 | 
				
			||||||
 | 
					        sortBy: "name",
 | 
				
			||||||
const loading = ref(false);
 | 
					        descending: true,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
const actions = ref([] as URLAction[]);
 | 
					      columns: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
const columns: QTableColumn[] = [
 | 
					          name: "name",
 | 
				
			||||||
  {
 | 
					          label: "Name",
 | 
				
			||||||
    name: "name",
 | 
					          field: "name",
 | 
				
			||||||
    label: "Name",
 | 
					          align: "left",
 | 
				
			||||||
    field: "name",
 | 
					          sortable: true,
 | 
				
			||||||
    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: {
 | 
				
			||||||
    name: "desc",
 | 
					    getURLActions() {
 | 
				
			||||||
    label: "Description",
 | 
					      this.$q.loading.show();
 | 
				
			||||||
    field: "desc",
 | 
					 | 
				
			||||||
    align: "left",
 | 
					 | 
				
			||||||
    sortable: true,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    name: "pattern",
 | 
					 | 
				
			||||||
    label: "URL Pattern",
 | 
					 | 
				
			||||||
    field: "pattern",
 | 
					 | 
				
			||||||
    align: "left",
 | 
					 | 
				
			||||||
    sortable: true,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function getURLActions() {
 | 
					      this.$axios
 | 
				
			||||||
  $q.loading.show();
 | 
					        .get("/core/urlaction/")
 | 
				
			||||||
  try {
 | 
					        .then((r) => {
 | 
				
			||||||
    const result = await fetchURLActions();
 | 
					          this.$q.loading.hide();
 | 
				
			||||||
    actions.value = result.filter(
 | 
					          this.actions = r.data;
 | 
				
			||||||
      (action) => action.action_type === props.type,
 | 
					        })
 | 
				
			||||||
    );
 | 
					        .catch(() => {
 | 
				
			||||||
  } catch (e) {
 | 
					          this.$q.loading.hide();
 | 
				
			||||||
    console.error(e);
 | 
					        });
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  $q.loading.hide();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function addURLAction() {
 | 
					 | 
				
			||||||
  $q.dialog({
 | 
					 | 
				
			||||||
    component: URLActionsForm,
 | 
					 | 
				
			||||||
    componentProps: {
 | 
					 | 
				
			||||||
      type: props.type,
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  }).onOk(getURLActions);
 | 
					    addAction() {
 | 
				
			||||||
}
 | 
					      this.$q
 | 
				
			||||||
 | 
					        .dialog({
 | 
				
			||||||
function editURLAction(action: URLAction) {
 | 
					          component: URLActionsForm,
 | 
				
			||||||
  $q.dialog({
 | 
					        })
 | 
				
			||||||
    component: URLActionsForm,
 | 
					        .onOk(() => {
 | 
				
			||||||
    componentProps: {
 | 
					          this.getURLActions();
 | 
				
			||||||
      type: props.type,
 | 
					        });
 | 
				
			||||||
      action: action,
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  }).onOk(getURLActions);
 | 
					    editAction(action) {
 | 
				
			||||||
}
 | 
					      this.$q
 | 
				
			||||||
 | 
					        .dialog({
 | 
				
			||||||
function deleteURLAction(action: URLAction) {
 | 
					          component: URLActionsForm,
 | 
				
			||||||
  $q.dialog({
 | 
					          componentProps: {
 | 
				
			||||||
    title: `Delete URL Action: ${action.name}?`,
 | 
					            action: action,
 | 
				
			||||||
    cancel: true,
 | 
					          },
 | 
				
			||||||
    ok: { label: "Delete", color: "negative" },
 | 
					        })
 | 
				
			||||||
  }).onOk(async () => {
 | 
					        .onOk(() => {
 | 
				
			||||||
    loading.value = true;
 | 
					          this.getURLActions();
 | 
				
			||||||
    try {
 | 
					        });
 | 
				
			||||||
      await removeURLAction(action.id);
 | 
					    },
 | 
				
			||||||
      await getURLActions();
 | 
					    deleteAction(action) {
 | 
				
			||||||
      notifySuccess(`URL Action: ${action.name} was deleted!`);
 | 
					      this.$q
 | 
				
			||||||
    } catch (e) {
 | 
					        .dialog({
 | 
				
			||||||
      console.error(e);
 | 
					          title: `Delete URL Action: ${action.name}?`,
 | 
				
			||||||
    }
 | 
					          cancel: true,
 | 
				
			||||||
    loading.value = false;
 | 
					          ok: { label: "Delete", color: "negative" },
 | 
				
			||||||
  });
 | 
					        })
 | 
				
			||||||
}
 | 
					        .onOk(() => {
 | 
				
			||||||
onMounted(getURLActions);
 | 
					          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();
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -319,12 +319,10 @@ export default {
 | 
				
			|||||||
          );
 | 
					          );
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.urlActions = r.data
 | 
					        this.urlActions = r.data.map((action) => ({
 | 
				
			||||||
          .filter((action) => action.action_type === "web")
 | 
					          label: action.name,
 | 
				
			||||||
          .map((action) => ({
 | 
					          value: action.id,
 | 
				
			||||||
            label: action.name,
 | 
					        }));
 | 
				
			||||||
            value: action.id,
 | 
					 | 
				
			||||||
          }));
 | 
					 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getUserPrefs() {
 | 
					    getUserPrefs() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,8 +71,6 @@
 | 
				
			|||||||
              :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"
 | 
				
			||||||
@@ -169,7 +167,7 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <q-card-actions>
 | 
					      <q-card-actions>
 | 
				
			||||||
        <tactical-dropdown
 | 
					        <tactical-dropdown
 | 
				
			||||||
          style="width: 450px"
 | 
					          style="width: 350px"
 | 
				
			||||||
          dense
 | 
					          dense
 | 
				
			||||||
          :loading="agentLoading"
 | 
					          :loading="agentLoading"
 | 
				
			||||||
          filled
 | 
					          filled
 | 
				
			||||||
@@ -189,21 +187,7 @@
 | 
				
			|||||||
              :disable="
 | 
					              :disable="
 | 
				
			||||||
                !agent || !script.script_body || !script.default_timeout
 | 
					                !agent || !script.script_body || !script.default_timeout
 | 
				
			||||||
              "
 | 
					              "
 | 
				
			||||||
              @click="openTestScriptModal('agent')"
 | 
					              @click="openTestScriptModal"
 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <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>
 | 
				
			||||||
@@ -231,7 +215,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 { notifyError, notifySuccess } from "@/utils/notify";
 | 
					import { notifySuccess } from "@/utils/notify";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ui imports
 | 
					// ui imports
 | 
				
			||||||
import TestScriptModal from "@/components/scripts/TestScriptModal.vue";
 | 
					import TestScriptModal from "@/components/scripts/TestScriptModal.vue";
 | 
				
			||||||
@@ -301,10 +285,6 @@ 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
 | 
				
			||||||
@@ -325,7 +305,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.startsWith("#!");
 | 
					    return !script.script_body.includes("#!");
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -384,20 +364,12 @@ async function submit() {
 | 
				
			|||||||
  loading.value = false;
 | 
					  loading.value = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function openTestScriptModal(ctx: string) {
 | 
					function openTestScriptModal() {
 | 
				
			||||||
  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,
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,7 +36,7 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { ref, onMounted } from "vue";
 | 
					import { ref, onMounted } from "vue";
 | 
				
			||||||
import { testScript, testScriptOnServer } from "@/api/scripts";
 | 
					import { testScript } from "@/api/scripts";
 | 
				
			||||||
import { useDialogPluginComponent } from "quasar";
 | 
					import { useDialogPluginComponent } from "quasar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
@@ -45,7 +45,6 @@ export default {
 | 
				
			|||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    script: !Object,
 | 
					    script: !Object,
 | 
				
			||||||
    agent: !String,
 | 
					    agent: !String,
 | 
				
			||||||
    ctx: !String,
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  setup(props) {
 | 
					  setup(props) {
 | 
				
			||||||
    // setup quasar dialog plugin
 | 
					    // setup quasar dialog plugin
 | 
				
			||||||
@@ -71,11 +70,7 @@ export default {
 | 
				
			|||||||
        env_vars: props.script.env_vars,
 | 
					        env_vars: props.script.env_vars,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        if (props.ctx === "server") {
 | 
					        ret.value = await testScript(props.agent, data);
 | 
				
			||||||
          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, defineComponent } from "vue";
 | 
					import { ref, watch, onMounted } 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 defineComponent({
 | 
					export default {
 | 
				
			||||||
  components: { TacticalDropdown, draggable },
 | 
					  components: { TacticalDropdown, draggable },
 | 
				
			||||||
  name: "AddAutomatedTask",
 | 
					  name: "AddAutomatedTask",
 | 
				
			||||||
  emits: [...useDialogPluginComponent.emits],
 | 
					  emits: [...useDialogPluginComponent.emits],
 | 
				
			||||||
@@ -858,19 +858,18 @@ export default defineComponent({
 | 
				
			|||||||
    // setup dropdowns
 | 
					    // setup dropdowns
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
      script,
 | 
					      script,
 | 
				
			||||||
      scriptName,
 | 
					 | 
				
			||||||
      scriptOptions,
 | 
					      scriptOptions,
 | 
				
			||||||
      defaultTimeout,
 | 
					      defaultTimeout,
 | 
				
			||||||
      defaultArgs,
 | 
					      defaultArgs,
 | 
				
			||||||
      defaultEnvVars,
 | 
					      defaultEnvVars,
 | 
				
			||||||
    } = useScriptDropdown({
 | 
					    } = useScriptDropdown(undefined, {
 | 
				
			||||||
      onMount: true,
 | 
					      onMount: true,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // set defaultTimeout to 30
 | 
					    // set defaultTimeout to 30
 | 
				
			||||||
    defaultTimeout.value = 30;
 | 
					    defaultTimeout.value = 30;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { checkOptions, getCheckOptions } = useCheckDropdown(props.parent);
 | 
					    const { checkOptions, getCheckOptions } = useCheckDropdown();
 | 
				
			||||||
    const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
 | 
					    const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // add task logic
 | 
					    // add task logic
 | 
				
			||||||
@@ -953,7 +952,9 @@ export default defineComponent({
 | 
				
			|||||||
      if (actionType.value === "script") {
 | 
					      if (actionType.value === "script") {
 | 
				
			||||||
        task.value.actions.push({
 | 
					        task.value.actions.push({
 | 
				
			||||||
          type: "script",
 | 
					          type: "script",
 | 
				
			||||||
          name: scriptName.value,
 | 
					          name: scriptOptions.value.find(
 | 
				
			||||||
 | 
					            (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,
 | 
				
			||||||
@@ -1178,7 +1179,7 @@ export default defineComponent({
 | 
				
			|||||||
      onDialogHide,
 | 
					      onDialogHide,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped>
 | 
					<style scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
import { ref, computed, onMounted } from "vue";
 | 
					import { computed, ref } 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(opts = {}) {
 | 
					export function useAgentDropdown() {
 | 
				
			||||||
  const agent = ref(null);
 | 
					  const agent = ref(null);
 | 
				
			||||||
  const agents = ref([]);
 | 
					  const agents = ref([]);
 | 
				
			||||||
  const agentOptions = ref([]);
 | 
					  const agentOptions = ref([]);
 | 
				
			||||||
@@ -13,14 +13,10 @@ export function useAgentDropdown(opts = {}) {
 | 
				
			|||||||
  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,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								src/composables/core.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/composables/core.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					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,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,88 +0,0 @@
 | 
				
			|||||||
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,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										70
									
								
								src/composables/scripts.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/composables/scripts.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					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" },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
@@ -1,141 +0,0 @@
 | 
				
			|||||||
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,16 +84,7 @@
 | 
				
			|||||||
          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" />
 | 
				
			||||||
@@ -157,7 +148,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        <AlertsIcon />
 | 
					        <AlertsIcon />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <q-btn-dropdown flat no-caps stretch :label="username || ''">
 | 
					        <q-btn-dropdown flat no-caps stretch :label="user">
 | 
				
			||||||
          <q-list>
 | 
					          <q-list>
 | 
				
			||||||
            <q-item
 | 
					            <q-item
 | 
				
			||||||
              clickable
 | 
					              clickable
 | 
				
			||||||
@@ -209,114 +200,187 @@
 | 
				
			|||||||
    </q-page-container>
 | 
					    </q-page-container>
 | 
				
			||||||
  </q-layout>
 | 
					  </q-layout>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script>
 | 
				
			||||||
// composition imports
 | 
					// composition imports
 | 
				
			||||||
import { computed, onMounted } from "vue";
 | 
					import { ref, computed, onMounted, onBeforeUnmount } from "vue";
 | 
				
			||||||
import { useQuasar } from "quasar";
 | 
					import { useQuasar } from "quasar";
 | 
				
			||||||
import { useStore } from "vuex";
 | 
					import { useStore } from "vuex";
 | 
				
			||||||
import { useDashboardStore } from "@/stores/dashboard";
 | 
					 | 
				
			||||||
import { useAuthStore } from "@/stores/auth";
 | 
					 | 
				
			||||||
import { storeToRefs } from "pinia";
 | 
					 | 
				
			||||||
import { resetTwoFactor } from "@/api/accounts";
 | 
					 | 
				
			||||||
import { notifyError, notifySuccess } from "@/utils/notify";
 | 
					 | 
				
			||||||
import axios from "axios";
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import { getWSUrl } from "@/websocket/channels";
 | 
				
			||||||
// webtermn
 | 
					import { resetTwoFactor } from "@/api/accounts";
 | 
				
			||||||
import { checkWebTermPerms, openWebTerminal } from "@/api/core";
 | 
					import { notifySuccess } from "@/utils/notify";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const store = useStore();
 | 
					export default {
 | 
				
			||||||
const $q = useQuasar();
 | 
					  name: "MainLayout",
 | 
				
			||||||
 | 
					  components: { AlertsIcon },
 | 
				
			||||||
 | 
					  setup() {
 | 
				
			||||||
 | 
					    const store = useStore();
 | 
				
			||||||
 | 
					    const $q = useQuasar();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					    const darkMode = computed({
 | 
				
			||||||
  serverCount,
 | 
					      get: () => {
 | 
				
			||||||
  serverOfflineCount,
 | 
					        return $q.dark.isActive;
 | 
				
			||||||
  workstationCount,
 | 
					      },
 | 
				
			||||||
  workstationOfflineCount,
 | 
					      set: (value) => {
 | 
				
			||||||
  daysUntilCertExpires,
 | 
					        axios.patch("/accounts/users/ui/", { dark_mode: value });
 | 
				
			||||||
} = storeToRefs(useDashboardStore());
 | 
					        $q.dark.set(value);
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { username } = storeToRefs(useAuthStore());
 | 
					    const currentTRMMVersion = computed(() => store.state.currentTRMMVersion);
 | 
				
			||||||
 | 
					    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 darkMode = computed({
 | 
					    const latestReleaseURL = computed(() => {
 | 
				
			||||||
  get: () => {
 | 
					      return latestTRMMVersion.value
 | 
				
			||||||
    return $q.dark.isActive;
 | 
					        ? `https://github.com/amidaware/tacticalrmm/releases/tag/v${latestTRMMVersion.value}`
 | 
				
			||||||
  },
 | 
					        : "";
 | 
				
			||||||
  set: (value) => {
 | 
					    });
 | 
				
			||||||
    axios.patch("/accounts/users/ui/", { dark_mode: value });
 | 
					 | 
				
			||||||
    $q.dark.set(value);
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const currentTRMMVersion = computed(() => store.state.currentTRMMVersion);
 | 
					    function showUserPreferences() {
 | 
				
			||||||
const latestTRMMVersion = computed(() => store.state.latestTRMMVersion);
 | 
					      $q.dialog({
 | 
				
			||||||
const needRefresh = computed(() => store.state.needrefresh);
 | 
					        component: UserPreferences,
 | 
				
			||||||
const hosted = computed(() => store.state.hosted);
 | 
					      }).onOk(() => store.dispatch("getDashInfo"));
 | 
				
			||||||
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(() => {
 | 
					    function resetPassword() {
 | 
				
			||||||
  if (
 | 
					      $q.dialog({
 | 
				
			||||||
    latestTRMMVersion.value === "error" ||
 | 
					        component: ResetPass,
 | 
				
			||||||
    hosted.value ||
 | 
					      });
 | 
				
			||||||
    currentTRMMVersion.value?.includes("-dev")
 | 
					    }
 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  return currentTRMMVersion.value !== latestTRMMVersion.value;
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					    function reset2FA() {
 | 
				
			||||||
  store.dispatch("getDashInfo");
 | 
					      $q.dialog({
 | 
				
			||||||
  store.dispatch("checkVer");
 | 
					        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,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,6 @@ 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
 | 
				
			||||||
@@ -15,7 +13,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"
 | 
				
			||||||
@@ -26,15 +24,13 @@ 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 (!auth.loggedIn) {
 | 
					      if (!store.getters.loggedIn) {
 | 
				
			||||||
        next({
 | 
					        next({
 | 
				
			||||||
          name: "Login",
 | 
					          name: "Login",
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -42,7 +38,7 @@ export default function (/* { store } */) {
 | 
				
			|||||||
        next();
 | 
					        next();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else if (to.meta.requiresVisitor) {
 | 
					    } else if (to.meta.requiresVisitor) {
 | 
				
			||||||
      if (auth.loggedIn) {
 | 
					      if (store.getters.loggedIn) {
 | 
				
			||||||
        next({
 | 
					        next({
 | 
				
			||||||
          name: "Dashboard",
 | 
					          name: "Dashboard",
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,14 +46,6 @@ 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,6 +7,8 @@ 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,
 | 
				
			||||||
@@ -41,14 +43,15 @@ 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;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -73,6 +76,14 @@ 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;
 | 
				
			||||||
@@ -153,12 +164,6 @@ 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) {
 | 
				
			||||||
@@ -208,7 +213,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) {
 | 
				
			||||||
@@ -227,7 +232,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);
 | 
				
			||||||
@@ -243,8 +248,6 @@ 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);
 | 
				
			||||||
@@ -304,15 +307,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);
 | 
				
			||||||
@@ -346,6 +349,37 @@ 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,4 +1,3 @@
 | 
				
			|||||||
/* 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";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,70 +0,0 @@
 | 
				
			|||||||
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;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,44 +0,0 @@
 | 
				
			|||||||
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,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,4 +0,0 @@
 | 
				
			|||||||
export interface User {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  username: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,12 +1 @@
 | 
				
			|||||||
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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,49 +0,0 @@
 | 
				
			|||||||
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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
export interface Policy {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
export interface Check {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,15 +0,0 @@
 | 
				
			|||||||
export interface Client {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface ClientWithSites {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  sites: Site[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface Site {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
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[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,29 +0,0 @@
 | 
				
			|||||||
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,11 +15,6 @@ 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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,134 +0,0 @@
 | 
				
			|||||||
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
									
									
								
							
							
						
						
									
										10
									
								
								src/types/typings.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,10 +0,0 @@
 | 
				
			|||||||
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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										390
									
								
								src/utils/format.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										390
									
								
								src/utils/format.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,390 @@
 | 
				
			|||||||
 | 
					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, "");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,472 +0,0 @@
 | 
				
			|||||||
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>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Notify } from "quasar";
 | 
					import { Notify } from "quasar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function notifySuccess(msg: string, timeout = 2000) {
 | 
					export function notifySuccess(msg, timeout = 2000) {
 | 
				
			||||||
  Notify.create({
 | 
					  Notify.create({
 | 
				
			||||||
    type: "positive",
 | 
					    type: "positive",
 | 
				
			||||||
    message: msg,
 | 
					    message: msg,
 | 
				
			||||||
@@ -8,7 +8,7 @@ export function notifySuccess(msg: string, timeout = 2000) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function notifyError(msg: string, timeout = 2000) {
 | 
					export function notifyError(msg, timeout = 2000) {
 | 
				
			||||||
  Notify.create({
 | 
					  Notify.create({
 | 
				
			||||||
    type: "negative",
 | 
					    type: "negative",
 | 
				
			||||||
    message: msg,
 | 
					    message: msg,
 | 
				
			||||||
@@ -16,7 +16,7 @@ export function notifyError(msg: string, timeout = 2000) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function notifyWarning(msg: string, timeout = 2000) {
 | 
					export function notifyWarning(msg, timeout = 2000) {
 | 
				
			||||||
  Notify.create({
 | 
					  Notify.create({
 | 
				
			||||||
    type: "warning",
 | 
					    type: "warning",
 | 
				
			||||||
    message: msg,
 | 
					    message: msg,
 | 
				
			||||||
@@ -24,7 +24,7 @@ export function notifyWarning(msg: string, timeout = 2000) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function notifyInfo(msg: string, timeout = 2000) {
 | 
					export function notifyInfo(msg, timeout = 2000) {
 | 
				
			||||||
  Notify.create({
 | 
					  Notify.create({
 | 
				
			||||||
    type: "info",
 | 
					    type: "info",
 | 
				
			||||||
    message: msg,
 | 
					    message: msg,
 | 
				
			||||||
@@ -1,10 +1,6 @@
 | 
				
			|||||||
import { Notify } from "quasar";
 | 
					import { Notify } from "quasar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function isValidThreshold(
 | 
					export function isValidThreshold(warning, error, diskcheck = false) {
 | 
				
			||||||
  warning: number,
 | 
					 | 
				
			||||||
  error: number,
 | 
					 | 
				
			||||||
  diskcheck = false,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  if (warning === 0 && error === 0) {
 | 
					  if (warning === 0 && error === 0) {
 | 
				
			||||||
    Notify.create({
 | 
					    Notify.create({
 | 
				
			||||||
      type: "negative",
 | 
					      type: "negative",
 | 
				
			||||||
@@ -35,7 +31,7 @@ export function isValidThreshold(
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function validateEventID(val: number | "*") {
 | 
					export function validateEventID(val) {
 | 
				
			||||||
  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 === "*") {
 | 
				
			||||||
@@ -48,20 +44,10 @@ export function validateEventID(val: number | "*") {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// validate script return code
 | 
					// validate script return code
 | 
				
			||||||
// function is used for quasar's q-select on-new-value function
 | 
					export function validateRetcode(val, done) {
 | 
				
			||||||
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: string) {
 | 
					export function validateTimePeriod(val) {
 | 
				
			||||||
  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 ${node.children ? "client" : "site"}: ${node.label}.`,
 | 
					            message: `Delete site: ${node.label}.`,
 | 
				
			||||||
            cancel: true,
 | 
					            cancel: true,
 | 
				
			||||||
            ok: { label: "Delete", color: "negative" },
 | 
					            ok: { label: "Delete", color: "negative" },
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
@@ -824,9 +824,7 @@ export default {
 | 
				
			|||||||
          );
 | 
					          );
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.urlActions = r.data.filter(
 | 
					        this.urlActions = r.data;
 | 
				
			||||||
          (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 ref="form" @submit.prevent="checkCreds" class="q-gutter-md">
 | 
					            <q-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="showPassword ? 'password' : 'text'"
 | 
					                :type="isPwd ? '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="showPassword ? 'visibility_off' : 'visibility'"
 | 
					                    :name="isPwd ? 'visibility_off' : 'visibility'"
 | 
				
			||||||
                    class="cursor-pointer"
 | 
					                    class="cursor-pointer"
 | 
				
			||||||
                    @click="showPassword = !showPassword"
 | 
					                    @click="isPwd = !isPwd"
 | 
				
			||||||
                  />
 | 
					                  />
 | 
				
			||||||
                </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 ref="formToken" @submit.prevent="onSubmit">
 | 
					            <q-form @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,58 +83,53 @@
 | 
				
			|||||||
  </q-layout>
 | 
					  </q-layout>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script>
 | 
				
			||||||
import { ref, reactive } from "vue";
 | 
					import mixins from "@/mixins/mixins";
 | 
				
			||||||
import { type QForm, useQuasar } from "quasar";
 | 
					 | 
				
			||||||
import { useAuthStore } from "@/stores/auth";
 | 
					 | 
				
			||||||
import { useRouter } from "vue-router";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// setup quasar
 | 
					export default {
 | 
				
			||||||
const $q = useQuasar();
 | 
					  name: "LoginView",
 | 
				
			||||||
$q.dark.set(true);
 | 
					  mixins: [mixins],
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      credentials: {},
 | 
				
			||||||
 | 
					      prompt: false,
 | 
				
			||||||
 | 
					      isPwd: true,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// setup auth store
 | 
					  methods: {
 | 
				
			||||||
const auth = useAuthStore();
 | 
					    checkCreds() {
 | 
				
			||||||
 | 
					      this.$axios.post("/checkcreds/", this.credentials).then((r) => {
 | 
				
			||||||
// setup router
 | 
					        if (r.data.totp === "totp not set") {
 | 
				
			||||||
const router = useRouter();
 | 
					          // sign in to setup two factor temporarily
 | 
				
			||||||
 | 
					          const token = r.data.token;
 | 
				
			||||||
const form = ref<QForm | null>(null);
 | 
					          const username = r.data.username;
 | 
				
			||||||
const formToken = ref<QForm | null>(null);
 | 
					          localStorage.setItem("access_token", token);
 | 
				
			||||||
 | 
					          localStorage.setItem("user_name", username);
 | 
				
			||||||
// login logic
 | 
					          this.$store.commit("retrieveToken", { token, username });
 | 
				
			||||||
const credentials = reactive({ username: "", password: "" });
 | 
					          this.$router.push({ name: "TOTPSetup" });
 | 
				
			||||||
const twofactor = ref("");
 | 
					        } else {
 | 
				
			||||||
const prompt = ref(false);
 | 
					          this.prompt = true;
 | 
				
			||||||
const showPassword = ref(true);
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
async function checkCreds() {
 | 
					    },
 | 
				
			||||||
  try {
 | 
					    onSubmit() {
 | 
				
			||||||
    const { totp } = await auth.checkCredentials(credentials);
 | 
					      this.$store
 | 
				
			||||||
 | 
					        .dispatch("retrieveToken", this.credentials)
 | 
				
			||||||
    if (!totp) {
 | 
					        .then(() => {
 | 
				
			||||||
      router.push({ name: "TOTPSetup" });
 | 
					          this.credentials = {};
 | 
				
			||||||
    } else {
 | 
					          this.$router.push({ name: "Dashboard" });
 | 
				
			||||||
      twofactor.value = "";
 | 
					        })
 | 
				
			||||||
      prompt.value = true;
 | 
					        .catch(() => {
 | 
				
			||||||
    }
 | 
					          this.credentials = {};
 | 
				
			||||||
  } catch (err) {
 | 
					          this.prompt = false;
 | 
				
			||||||
    console.error(err);
 | 
					        });
 | 
				
			||||||
  }
 | 
					    },
 | 
				
			||||||
}
 | 
					  },
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
async function onSubmit() {
 | 
					    this.$q.dark.set(true);
 | 
				
			||||||
  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,19 +5,11 @@
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script>
 | 
				
			||||||
import { onMounted } from "vue";
 | 
					export default {
 | 
				
			||||||
import { useAuthStore } from "@/stores/auth";
 | 
					  name: "SessionExpired",
 | 
				
			||||||
import { useDashWSConnection } from "@/websocket/websocket";
 | 
					  mounted() {
 | 
				
			||||||
 | 
					    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="qrUrl">
 | 
					          <q-card-section v-if="qr_url">
 | 
				
			||||||
            <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>
 | 
				
			||||||
            <img :src="qrCode" alt="QR Code" />
 | 
					            <qrcode-vue :value="qr_url" :size="200" level="H" />
 | 
				
			||||||
          </q-card-section>
 | 
					          </q-card-section>
 | 
				
			||||||
          <q-card-section v-if="totpKey">
 | 
					          <q-card-section v-if="totp_key">
 | 
				
			||||||
            <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>{{ totpKey }}</p>
 | 
					            <p>{{ totp_key }}</p>
 | 
				
			||||||
          </q-card-section>
 | 
					          </q-card-section>
 | 
				
			||||||
          <q-card-actions align="center">
 | 
					          <q-card-actions align="center">
 | 
				
			||||||
            <q-btn
 | 
					            <q-btn
 | 
				
			||||||
@@ -28,7 +28,6 @@
 | 
				
			|||||||
              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>
 | 
				
			||||||
@@ -38,63 +37,65 @@
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script>
 | 
				
			||||||
import { ref, onMounted, onBeforeUnmount } from "vue";
 | 
					import QrcodeVue from "qrcode.vue";
 | 
				
			||||||
import { useQuasar } from "quasar";
 | 
					import mixins from "@/mixins/mixins";
 | 
				
			||||||
import { useAuthStore } from "@/stores/auth";
 | 
					 | 
				
			||||||
import { useRouter } from "vue-router";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useQRCode } from "@vueuse/integrations/useQRCode";
 | 
					export default {
 | 
				
			||||||
 | 
					  name: "TOTPSetup",
 | 
				
			||||||
 | 
					  mixins: [mixins],
 | 
				
			||||||
 | 
					  components: { QrcodeVue },
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      totp_key: null,
 | 
				
			||||||
 | 
					      qr_url: null,
 | 
				
			||||||
 | 
					      cleared_token: false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    getQRCodeData() {
 | 
				
			||||||
 | 
					      this.$q.loading.show();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// setup quasar
 | 
					      this.$axios
 | 
				
			||||||
const $q = useQuasar();
 | 
					        .post("/accounts/users/setup_totp/")
 | 
				
			||||||
 | 
					        .then((r) => {
 | 
				
			||||||
 | 
					          this.$q.loading.hide();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// setup auth store
 | 
					          if (r.data === "totp token already set") {
 | 
				
			||||||
const auth = useAuthStore();
 | 
					            //don't logout user if totp is already set
 | 
				
			||||||
 | 
					            this.cleared_token = true;
 | 
				
			||||||
// setup router
 | 
					            this.$router.push({ name: "Login" });
 | 
				
			||||||
const router = useRouter();
 | 
					          } else {
 | 
				
			||||||
 | 
					            this.totp_key = r.data.totp_key;
 | 
				
			||||||
const totpKey = ref("");
 | 
					            this.qr_url = r.data.qr_url;
 | 
				
			||||||
const qrUrl = ref("");
 | 
					          }
 | 
				
			||||||
const clearToken = ref(true);
 | 
					        })
 | 
				
			||||||
const loading = ref(false);
 | 
					        .catch(() => this.$q.loading.hide());
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
const qrCode = useQRCode(qrUrl);
 | 
					    logout() {
 | 
				
			||||||
 | 
					      this.$q.loading.show();
 | 
				
			||||||
async function getQRCodeData() {
 | 
					      this.$store
 | 
				
			||||||
  loading.value = true;
 | 
					        .dispatch("destroyToken")
 | 
				
			||||||
 | 
					        .then(() => {
 | 
				
			||||||
  try {
 | 
					          this.cleared_token = true;
 | 
				
			||||||
    const data = await auth.setupTotp();
 | 
					          this.$q.loading.hide();
 | 
				
			||||||
 | 
					          this.$router.push({ name: "Login" });
 | 
				
			||||||
    if (!data) {
 | 
					        })
 | 
				
			||||||
      //don't logout user if totp is already set
 | 
					        .catch(() => {
 | 
				
			||||||
      clearToken.value = false;
 | 
					          this.cleared_token = true;
 | 
				
			||||||
      router.push({ name: "Login" });
 | 
					          this.$q.loading.hide();
 | 
				
			||||||
    } else {
 | 
					          this.$router.push({ name: "Login" });
 | 
				
			||||||
      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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,88 +0,0 @@
 | 
				
			|||||||
<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>
 | 
					 | 
				
			||||||
							
								
								
									
										11
									
								
								src/websocket/channels.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/websocket/channels.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { getBaseUrl } from "@/boot/axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getWSUrl(path, token) {
 | 
				
			||||||
 | 
					  const url = getBaseUrl().split("://")[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const proto =
 | 
				
			||||||
 | 
					    process.env.NODE_ENV === "production" || process.env.DOCKER_BUILD
 | 
				
			||||||
 | 
					      ? "wss"
 | 
				
			||||||
 | 
					      : "ws";
 | 
				
			||||||
 | 
					  return `${proto}://${url}/ws/${path}/?access_token=${token}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,81 +0,0 @@
 | 
				
			|||||||
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