Compare commits
	
		
			42 Commits
		
	
	
		
			v0.100.1-d
			...
			v0.101.8-d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 5f56e7017b | ||
|  | 9c033c1c90 | ||
|  | ba14ed348e | ||
|  | 7e25db6622 | ||
|  | 78636c436f | ||
|  | d37122386f | ||
|  | 17d960fca9 | ||
|  | d2e0b8ad9b | ||
|  | 776c27ec26 | ||
|  | 41c61ce152 | ||
|  | 8e9de8b6b6 | ||
|  | 4cf5f7a3cb | ||
|  | 9729492d1c | ||
|  | d6da8b4a96 | ||
|  | 9264cf4044 | ||
|  | 3a45c2a309 | ||
|  | 59de35c698 | ||
|  | 5b8ac2c809 | ||
|  | 83d0ff1c0a | ||
|  | 8a6ec6ceab | ||
|  | 93dbc74e33 | ||
|  | 5f2add48a9 | ||
|  | b7369875af | ||
|  | 2eb6580fed | ||
|  | 9f85fbb330 | ||
|  | ee9715a4cf | ||
|  | 76f330fb9c | ||
|  | e67c1ff331 | ||
|  | 137a5648ce | ||
|  | a944bc50d1 | ||
|  | 0a4b00298d | ||
|  | 1eaed284a3 | ||
|  | b278e0bed4 | ||
|  | 6ee3df7e4e | ||
|  | 7ee87da3b6 | ||
|  | 7bce958633 | ||
|  | 57963f6d1a | ||
|  | c9d76bdddc | ||
|  | c279a44679 | ||
|  | 974ba53926 | ||
|  | 021fbbe14f | ||
|  | bbd74c34b7 | 
							
								
								
									
										7
									
								
								.devcontainer/.env.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.devcontainer/.env.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| COMPOSE_PROJECT_NAME=trmm | ||||
| IMAGE_REPO=tacticalrmm/ | ||||
| VERSION=latest | ||||
|  | ||||
| # DEV SETTINGS | ||||
| APP_PORT=443 | ||||
| DOCKER_NETWORK=172.21.0.0/24 | ||||
							
								
								
									
										26
									
								
								.devcontainer/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								.devcontainer/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| version: '3.4' | ||||
|  | ||||
| services: | ||||
|   app-dev: | ||||
|     container_name: trmm-app-dev | ||||
|     image: node:16-alpine | ||||
|     restart: always | ||||
|     command: /bin/sh -c "npm install --cache ~/.npm && npm run serve" | ||||
|     user: 1000:1000 | ||||
|     working_dir: /workspace/web | ||||
|     volumes: | ||||
|       - ..:/workspace:cached | ||||
|     ports: | ||||
|       - "8080:443" | ||||
|     networks: | ||||
|       dev: | ||||
|         aliases: | ||||
|           - tactical-frontend | ||||
|  | ||||
| networks: | ||||
|   dev: | ||||
|     driver: bridge | ||||
|     ipam: | ||||
|       driver: default | ||||
|       config: | ||||
|         - subnet: ${DOCKER_NETWORK} | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -33,3 +33,4 @@ yarn-error.log* | ||||
| *.sln | ||||
|  | ||||
| .env | ||||
| /public/env-config.js | ||||
|   | ||||
							
								
								
									
										1743
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1743
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										40
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "web", | ||||
|   "version": "0.100.1-dev", | ||||
|   "version": "0.101.8-dev", | ||||
|   "private": true, | ||||
|   "productName": "Tactical RMM", | ||||
|   "scripts": { | ||||
| @@ -10,31 +10,31 @@ | ||||
|     "format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@quasar/extras": "1.14.1", | ||||
|     "apexcharts": "3.35.3", | ||||
|     "@quasar/extras": "1.15.6", | ||||
|     "apexcharts": "3.35.5", | ||||
|     "axios": "0.27.2", | ||||
|     "dotenv": "16.0.1", | ||||
|     "dotenv": "16.0.3", | ||||
|     "qrcode.vue": "3.3.3", | ||||
|     "quasar": "2.7.3", | ||||
|     "vue": "3.2.37", | ||||
|     "quasar": "2.10.2", | ||||
|     "vue": "3.2.45", | ||||
|     "vue3-ace-editor": "2.2.2", | ||||
|     "vue3-apexcharts": "1.4.1", | ||||
|     "vuedraggable": "4.1.0", | ||||
|     "vue-router": "4.0.16", | ||||
|     "vuex": "4.0.2" | ||||
|     "vue-router": "4.1.6", | ||||
|     "vuex": "4.1.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@quasar/cli": "^1.3.2", | ||||
|     "@intlify/vite-plugin-vue-i18n": "^3.4.0", | ||||
|     "@quasar/app-vite": "^1.0.2", | ||||
|     "@types/node": "^18.0.0", | ||||
|     "@typescript-eslint/eslint-plugin": "^5.29.0", | ||||
|     "@typescript-eslint/parser": "^5.29.0", | ||||
|     "autoprefixer": "^10.4.7", | ||||
|     "eslint": "^8.18.0", | ||||
|     "eslint-config-prettier": "^8.5.0", | ||||
|     "eslint-plugin-vue": "^8.5.0", | ||||
|     "prettier": "^2.7.1", | ||||
|     "typescript": "^4.7.4" | ||||
|     "@intlify/vite-plugin-vue-i18n": "^6.0.3", | ||||
|     "@quasar/app-vite": "^1.1.3", | ||||
|     "@types/node": "^18.11.10", | ||||
|     "@typescript-eslint/eslint-plugin": "^5.45.0", | ||||
|     "@typescript-eslint/parser": "^5.45.0", | ||||
|     "autoprefixer": "10.4.13", | ||||
|     "eslint": "8.28.0", | ||||
|     "eslint-config-prettier": "8.5.0", | ||||
|     "eslint-plugin-vue": "8.7.1", | ||||
|     "prettier": "2.8.0", | ||||
|     "typescript": "4.9.3" | ||||
|   } | ||||
| } | ||||
| } | ||||
| @@ -31,6 +31,17 @@ export default function ({ app, router, store }) { | ||||
|       return response; | ||||
|     }, | ||||
|     async function (error) { | ||||
|       if (error.code && error.code === "ERR_NETWORK") { | ||||
|         Notify.create({ | ||||
|           color: "negative", | ||||
|           message: "Backend is offline (network error)", | ||||
|           caption: | ||||
|             "Open your browser's dev tools and check the console tab for more detailed error messages", | ||||
|           timeout: 5000, | ||||
|         }); | ||||
|         return Promise.reject({ ...error }); | ||||
|       } | ||||
|  | ||||
|       let text; | ||||
|  | ||||
|       if (!error.response) { | ||||
|   | ||||
| @@ -196,6 +196,14 @@ | ||||
|             > | ||||
|               <q-tooltip>Linux</q-tooltip> | ||||
|             </q-icon> | ||||
|             <q-icon | ||||
|               v-else-if="props.row.plat === 'darwin'" | ||||
|               name="mdi-apple" | ||||
|               size="sm" | ||||
|               color="primary" | ||||
|             > | ||||
|               <q-tooltip>macOS</q-tooltip> | ||||
|             </q-icon> | ||||
|           </q-td> | ||||
|  | ||||
|           <q-td key="checks-status" :props="props"> | ||||
| @@ -356,6 +364,27 @@ export default { | ||||
|   }, | ||||
|   methods: { | ||||
|     filterTable(rows, terms, cols, cellValue) { | ||||
|       const hiddenFields = [ | ||||
|         "version", | ||||
|         "operating_system", | ||||
|         "public_ip", | ||||
|         "cpu_model", | ||||
|         "graphics", | ||||
|         "local_ips", | ||||
|         "make_model", | ||||
|         "physical_disks", | ||||
|       ]; | ||||
|  | ||||
|       // quasar filter only does visible columns so this is a hack to add hidden columns we want to filter | ||||
|       for (const elem of hiddenFields) { | ||||
|         if (!cols.find((o) => o.name === elem)) { | ||||
|           cols.push({ | ||||
|             name: elem, | ||||
|             field: elem, | ||||
|           }); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       const lowerTerms = terms ? terms.toLowerCase() : ""; | ||||
|       let advancedFilter = false; | ||||
|       let availability = null; | ||||
|   | ||||
| @@ -310,9 +310,10 @@ export default { | ||||
|     } | ||||
|  | ||||
|     function showUpdateDetails(update) { | ||||
|       const color = $q.dark.isActive ? "white" : ""; | ||||
|       let support_urls = ""; | ||||
|       update.more_info_urls.forEach((u) => { | ||||
|         support_urls += `<a href='${u}' target='_blank'>${u}</a><br/>`; | ||||
|         support_urls += `<a style='color: ${color}' href='${u}' target='_blank'>${u}</a><br/>`; | ||||
|       }); | ||||
|       let cats = update.categories.join(", "); | ||||
|       $q.dialog({ | ||||
|   | ||||
| @@ -7,6 +7,17 @@ | ||||
|           <q-badge color="primary" class="q-ml-sm text-caption">{{ | ||||
|             v | ||||
|           }}</q-badge> | ||||
|           <q-btn | ||||
|               v-if="!!v" | ||||
|               size="sm" | ||||
|               class="q-ml-xs" | ||||
|               flat | ||||
|               round | ||||
|               icon="content_copy" | ||||
|               @click="copyValueToClip(v)" | ||||
|             > | ||||
|               <q-tooltip>Copy to Clipboard</q-tooltip> | ||||
|             </q-btn> | ||||
|         </div> | ||||
|       </div> | ||||
|       <q-separator v-if="info.length > 1" /> | ||||
| @@ -15,6 +26,8 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { copyToClipboard } from "quasar"; | ||||
| import { notifySuccess } from "@/utils/notify"; | ||||
| // composition imports | ||||
| import { computed } from "vue"; | ||||
| import { useStore } from "vuex"; | ||||
| @@ -28,9 +41,17 @@ export default { | ||||
|     const store = useStore(); | ||||
|     const tabHeight = computed(() => store.state.tabHeight); | ||||
|  | ||||
|     function copyValueToClip(val) { | ||||
|       copyToClipboard(val) | ||||
|         .then(() => { | ||||
|           notifySuccess("Copied to clipboard"); | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|       tabHeight, | ||||
|       uid, | ||||
|       copyValueToClip, | ||||
|     }; | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -39,6 +39,19 @@ | ||||
|             new-value-mode="add" | ||||
|           /> | ||||
|         </q-card-section> | ||||
|         <q-card-section> | ||||
|           <q-select | ||||
|             dense | ||||
|             :label="envVarsLabel" | ||||
|             filled | ||||
|             v-model="state.env_vars" | ||||
|             use-input | ||||
|             use-chips | ||||
|             multiple | ||||
|             hide-dropdown-icon | ||||
|             new-value-mode="add" | ||||
|           /> | ||||
|         </q-card-section> | ||||
|         <q-card-section> | ||||
|           <tactical-dropdown | ||||
|             label="Informational return codes (press Enter after typing each code)" | ||||
| @@ -115,6 +128,7 @@ import { useDialogPluginComponent } from "quasar"; | ||||
| import { useCheckModal } from "@/composables/checks"; | ||||
| import { useScriptDropdown } from "@/composables/scripts"; | ||||
| import { validateRetcode } from "@/utils/validation"; | ||||
| import { envVarsLabel } from "@/constants/constants" | ||||
|  | ||||
| // ui imports | ||||
| import TacticalDropdown from "@/components/ui/TacticalDropdown.vue"; | ||||
| @@ -132,7 +146,7 @@ export default { | ||||
|     const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); | ||||
|  | ||||
|     // setup script dropdown | ||||
|     const { script, scriptOptions, defaultTimeout, defaultArgs } = | ||||
|     const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars } = | ||||
|       useScriptDropdown(props.check ? props.check.script : undefined, { | ||||
|         onMount: true, | ||||
|       }); | ||||
| @@ -145,6 +159,7 @@ export default { | ||||
|           ...props.parent, | ||||
|           script, | ||||
|           script_args: defaultArgs, | ||||
|           env_vars: defaultEnvVars, | ||||
|           timeout: defaultTimeout, | ||||
|           check_type: "script", | ||||
|           fails_b4_alert: 1, | ||||
| @@ -163,6 +178,7 @@ export default { | ||||
|       failOptions, | ||||
|       scriptOptions, | ||||
|       severityOptions, | ||||
|       envVarsLabel, | ||||
|  | ||||
|       // methods | ||||
|       submit, | ||||
|   | ||||
| @@ -208,7 +208,7 @@ export default { | ||||
|     } | ||||
|  | ||||
|     // component lifecycle hooks | ||||
|     onMounted(getAPIKeys()); | ||||
|     onMounted(getAPIKeys); | ||||
|     return { | ||||
|       // reactive data | ||||
|       keys, | ||||
|   | ||||
| @@ -10,10 +10,13 @@ | ||||
|       </q-card-actions> | ||||
|     </q-card-section> | ||||
|     <q-card-section> | ||||
|       <p class="text-subtitle1"> | ||||
|       <p v-if="info.plat === 'windows'" class="text-subtitle1"> | ||||
|         Download the agent then run the following command from an elevated | ||||
|         command prompt on the device you want to add. | ||||
|       </p> | ||||
|       <p v-else-if="info.plat === 'darwin'" class="text-subtitle1"> | ||||
|         Run the following command from a terminal | ||||
|       </p> | ||||
|       <p> | ||||
|         <q-field outlined :color="$q.dark.isActive ? 'white' : 'black'"> | ||||
|           <code>{{ info.data.cmd }}</code> | ||||
| @@ -37,7 +40,7 @@ | ||||
|           </q-badge> | ||||
|           <span>Do not popup any message boxes during install</span> | ||||
|         </div> | ||||
|         <div class="q-pa-xs q-gutter-xs"> | ||||
|         <div v-if="info.plat === 'windows'" class="q-pa-xs q-gutter-xs"> | ||||
|           <q-badge class="text-caption q-mr-xs" color="grey" text-color="black"> | ||||
|             <code | ||||
|               >-local-mesh "C:\\<some folder or | ||||
| @@ -46,7 +49,7 @@ | ||||
|           </q-badge> | ||||
|           <span> To skip downloading the Mesh Agent during the install.</span> | ||||
|         </div> | ||||
|         <div class="q-pa-xs q-gutter-xs"> | ||||
|         <div v-if="info.plat === 'windows'" class="q-pa-xs q-gutter-xs"> | ||||
|           <q-badge class="text-caption q-mr-xs" color="grey" text-color="black"> | ||||
|             <code | ||||
|               >-meshdir "C:\Program Files\Your Company Name\Mesh Agent"</code | ||||
| @@ -63,7 +66,7 @@ | ||||
|           </q-badge> | ||||
|           <span>Don't install the mesh agent</span> | ||||
|         </div> | ||||
|         <div class="q-pa-xs q-gutter-xs"> | ||||
|         <div v-if="info.plat === 'windows'" class="q-pa-xs q-gutter-xs"> | ||||
|           <q-badge class="text-caption q-mr-xs" color="grey" text-color="black"> | ||||
|             <code>-cert "C:\\<some folder or path>\\ca.pem"</code> | ||||
|           </q-badge> | ||||
| @@ -86,12 +89,12 @@ | ||||
|       <p class="text-italic"> | ||||
|         Note: the auth token above will be valid for {{ info.expires }} hours. | ||||
|       </p> | ||||
|       <q-btn | ||||
|       <q-btn v-if="info.plat === 'windows'" | ||||
|         type="a" | ||||
|         :href="info.data.url" | ||||
|         color="primary" | ||||
|         label="Download Agent" | ||||
|       /> | ||||
|       ></q-btn> | ||||
|     </q-card-section> | ||||
|   </q-card> | ||||
| </template> | ||||
|   | ||||
| @@ -102,6 +102,18 @@ | ||||
|             new-value-mode="add" | ||||
|           /> | ||||
|         </q-card-section> | ||||
|         <q-card-section v-if="mode === 'script'" class="q-pt-none"> | ||||
|           <tactical-dropdown | ||||
|             v-model="state.env_vars" | ||||
|             :label="envVarsLabel" | ||||
|             filled | ||||
|             use-input | ||||
|             multiple | ||||
|             hide-dropdown-icon | ||||
|             input-debounce="0" | ||||
|             new-value-mode="add" | ||||
|           /> | ||||
|         </q-card-section> | ||||
|  | ||||
|         <q-card-section v-if="mode === 'command'"> | ||||
|           <p>Shell</p> | ||||
| @@ -135,6 +147,11 @@ | ||||
|             :rules="[(val) => !!val || '*Required']" | ||||
|           /> | ||||
|         </q-card-section> | ||||
|         <q-card-section v-if="supportsRunAsUser()" class="q-pt-none"> | ||||
|           <q-checkbox v-model="state.run_as_user" label="Run As User"> | ||||
|             <q-tooltip>{{ runAsUserToolTip }}</q-tooltip> | ||||
|           </q-checkbox> | ||||
|         </q-card-section> | ||||
|  | ||||
|         <q-card-section v-if="mode === 'script' || mode === 'command'"> | ||||
|           <q-input | ||||
| @@ -203,6 +220,7 @@ import { runBulkAction } from "@/api/agents"; | ||||
| import { notifySuccess } from "@/utils/notify"; | ||||
| import { cmdPlaceholder } from "@/composables/agents"; | ||||
| import { removeExtraOptionCategories } from "@/utils/format"; | ||||
| import { envVarsLabel, runAsUserToolTip } from "@/constants/constants"; | ||||
|  | ||||
| // ui imports | ||||
| import TacticalDropdown from "@/components/ui/TacticalDropdown.vue"; | ||||
| @@ -217,6 +235,7 @@ const monTypeOptions = [ | ||||
| const osTypeOptions = [ | ||||
|   { label: "Windows", value: "windows" }, | ||||
|   { label: "Linux", value: "linux" }, | ||||
|   { label: "macOS", value: "darwin" }, | ||||
|   { label: "All", value: "all" }, | ||||
| ]; | ||||
|  | ||||
| @@ -277,6 +296,7 @@ export default { | ||||
|       scriptOptions, | ||||
|       defaultTimeout, | ||||
|       defaultArgs, | ||||
|       defaultEnvVars, | ||||
|       getScriptOptions, | ||||
|     } = useScriptDropdown(); | ||||
|     const { agents, agentOptions, getAgentOptions } = useAgentDropdown(); | ||||
| @@ -300,6 +320,8 @@ export default { | ||||
|       script, | ||||
|       timeout: defaultTimeout, | ||||
|       args: defaultArgs, | ||||
|       env_vars: defaultEnvVars, | ||||
|       run_as_user: false, | ||||
|     }); | ||||
|     const loading = ref(false); | ||||
|  | ||||
| @@ -316,6 +338,7 @@ export default { | ||||
|       () => state.value.osType, | ||||
|       (newValue) => { | ||||
|         state.value.custom_shell = null; | ||||
|         state.value.run_as_user = false; | ||||
|  | ||||
|         if (newValue === "windows") { | ||||
|           state.value.shell = "cmd"; | ||||
| @@ -337,6 +360,13 @@ export default { | ||||
|       loading.value = false; | ||||
|     } | ||||
|  | ||||
|     const supportsRunAsUser = () => { | ||||
|       const modes = ["script", "command"]; | ||||
|       return ( | ||||
|         state.value.osType === "windows" && modes.includes(state.value.mode) | ||||
|       ); | ||||
|     }; | ||||
|  | ||||
|     // set modal title and caption | ||||
|     const modalTitle = computed(() => { | ||||
|       return props.mode === "command" | ||||
| @@ -387,6 +417,8 @@ export default { | ||||
|       osTypeOptions, | ||||
|       targetOptions, | ||||
|       patchModeOptions, | ||||
|       runAsUserToolTip, | ||||
|       envVarsLabel, | ||||
|  | ||||
|       //computed | ||||
|       modalTitle, | ||||
| @@ -394,6 +426,7 @@ export default { | ||||
|       //methods | ||||
|       submit, | ||||
|       cmdPlaceholder, | ||||
|       supportsRunAsUser, | ||||
|  | ||||
|       // quasar dialog plugin | ||||
|       dialogRef, | ||||
|   | ||||
| @@ -465,8 +465,51 @@ export default { | ||||
|       }); | ||||
|     }, | ||||
|     editAgent() { | ||||
|       delete this.agent.all_timezones; | ||||
|       delete this.agent.timezone; | ||||
|       // TODO we need to fix the serializer to not send this stuff | ||||
|       const toRemove = [ | ||||
|         "created_by", | ||||
|         "created_time", | ||||
|         "modified_by", | ||||
|         "modified_time", | ||||
|         "all_timezones", | ||||
|         "timezone", | ||||
|         "wmi_detail", | ||||
|         "services", | ||||
|         "status", | ||||
|         "cpu_model", | ||||
|         "local_ips", | ||||
|         "make_model", | ||||
|         "physical_disks", | ||||
|         "graphics", | ||||
|         "checks", | ||||
|         "patches_last_installed", | ||||
|         "last_seen", | ||||
|         "applied_policies", | ||||
|         "effective_patch_policy", | ||||
|         "version", | ||||
|         "operating_system", | ||||
|         "plat", | ||||
|         "goarch", | ||||
|         "hostname", | ||||
|         "public_ip", | ||||
|         "total_ram", | ||||
|         "disks", | ||||
|         "boot_time", | ||||
|         "logged_in_username", | ||||
|         "last_logged_in_user", | ||||
|         "needs_reboot", | ||||
|         "choco_installed", | ||||
|         "policy", | ||||
|         "mesh_node_id", | ||||
|         "block_policy_inheritance", | ||||
|         "maintenance_mode", | ||||
|         "alert_template", | ||||
|         "client", | ||||
|         "site_name", | ||||
|       ]; | ||||
|       for (const elem of toRemove) { | ||||
|         delete this.agent[elem]; | ||||
|       } | ||||
|  | ||||
|       // only send the timezone data if it has changed | ||||
|       // this way django will keep the db column as null and inherit from the global setting | ||||
| @@ -503,7 +546,7 @@ export default { | ||||
|         else if (day === 0) result += "Sun, "; | ||||
|       } | ||||
|  | ||||
|       return result.trimRight(","); | ||||
|       return result.trimEnd(","); | ||||
|     }, | ||||
|   }, | ||||
|   mounted() { | ||||
|   | ||||
| @@ -52,6 +52,15 @@ | ||||
|                 goarch = GOARCH_AMD64; | ||||
|               " | ||||
|             /> | ||||
|             <q-radio | ||||
|               v-model="agentOS" | ||||
|               val="darwin" | ||||
|               label="macOS" | ||||
|               @update:model-value=" | ||||
|                 installMethod = 'mac'; | ||||
|                 goarch = GOARCH_AMD64; | ||||
|               " | ||||
|             /> | ||||
|           </div> | ||||
|         </q-card-section> | ||||
|         <q-card-section> | ||||
| @@ -105,37 +114,37 @@ | ||||
|               v-model="goarch" | ||||
|               :val="GOARCH_AMD64" | ||||
|               label="64 bit" | ||||
|               v-show="agentOS === 'windows'" | ||||
|             /> | ||||
|             <q-radio | ||||
|               v-model="goarch" | ||||
|               :val="GOARCH_i386" | ||||
|               label="32 bit" | ||||
|               v-show="agentOS === 'windows'" | ||||
|               v-show="agentOS === 'windows' || agentOS === 'linux'" | ||||
|             /> | ||||
|             <q-radio | ||||
|               v-model="goarch" | ||||
|               :val="GOARCH_AMD64" | ||||
|               label="64 bit" | ||||
|               v-show="agentOS !== 'windows'" | ||||
|               label="Intel 64 bit" | ||||
|               v-show="agentOS === 'darwin'" | ||||
|             /> | ||||
|             <q-radio | ||||
|               v-model="goarch" | ||||
|               :val="GOARCH_i386" | ||||
|               label="32 bit" | ||||
|               v-show="agentOS !== 'windows'" | ||||
|               v-show="agentOS !== 'darwin'" | ||||
|             /> | ||||
|             <q-radio | ||||
|               v-model="goarch" | ||||
|               :val="GOARCH_ARM64" | ||||
|               label="ARM 64 bit" | ||||
|               v-show="agentOS !== 'windows'" | ||||
|               v-show="agentOS === 'linux'" | ||||
|             /> | ||||
|             <q-radio | ||||
|               v-model="goarch" | ||||
|               :val="GOARCH_ARM64" | ||||
|               label="Apple Silicon (M1, M2)" | ||||
|               v-show="agentOS === 'darwin'" | ||||
|             /> | ||||
|             <q-radio | ||||
|               v-model="goarch" | ||||
|               :val="GOARCH_ARM32" | ||||
|               label="ARM 32 bit (Rasp Pi)" | ||||
|               v-show="agentOS !== 'windows'" | ||||
|               v-show="agentOS === 'linux'" | ||||
|             /> | ||||
|           </div> | ||||
|         </q-card-section> | ||||
| @@ -249,10 +258,7 @@ export default { | ||||
|         .toLowerCase() | ||||
|         .replace(/([^a-zA-Z0-9]+)/g, ""); | ||||
|  | ||||
|       const fileName = | ||||
|         this.goarch === GOARCH_AMD64 | ||||
|           ? `rmm-${clientStripped}-${siteStripped}-${this.agenttype}.exe` | ||||
|           : `rmm-${clientStripped}-${siteStripped}-${this.agenttype}-x86.exe`; | ||||
|       const fileName = `trmm-${clientStripped}-${siteStripped}-${this.agenttype}-${this.goarch}.exe`; | ||||
|  | ||||
|       const data = { | ||||
|         installMethod: this.installMethod, | ||||
| @@ -269,12 +275,13 @@ export default { | ||||
|         plat: this.agentOS, | ||||
|       }; | ||||
|  | ||||
|       if (this.installMethod === "manual") { | ||||
|       if (this.installMethod === "manual" || this.installMethod === "mac") { | ||||
|         this.$axios.post("/agents/installer/", data).then((r) => { | ||||
|           this.info = { | ||||
|             expires: this.expires, | ||||
|             data: r.data, | ||||
|             goarch: this.goarch, | ||||
|             plat: this.agentOS, | ||||
|           }; | ||||
|           this.showAgentDownload = true; | ||||
|         }); | ||||
| @@ -346,6 +353,9 @@ export default { | ||||
|         case "bash": | ||||
|           text = "Download linux install script"; | ||||
|           break; | ||||
|         case "mac": | ||||
|           text = "Show installation instructions"; | ||||
|           break; | ||||
|       } | ||||
|  | ||||
|       return text; | ||||
|   | ||||
| @@ -129,37 +129,37 @@ | ||||
|       <div class="q-gutter-sm"> | ||||
|         <q-checkbox | ||||
|           v-model="winupdatepolicy.run_time_days" | ||||
|           :val="1" | ||||
|           :val="0" | ||||
|           label="Monday" | ||||
|         /> | ||||
|         <q-checkbox | ||||
|           v-model="winupdatepolicy.run_time_days" | ||||
|           :val="2" | ||||
|           :val="1" | ||||
|           label="Tuesday" | ||||
|         /> | ||||
|         <q-checkbox | ||||
|           v-model="winupdatepolicy.run_time_days" | ||||
|           :val="3" | ||||
|           :val="2" | ||||
|           label="Wednesday" | ||||
|         /> | ||||
|         <q-checkbox | ||||
|           v-model="winupdatepolicy.run_time_days" | ||||
|           :val="4" | ||||
|           :val="3" | ||||
|           label="Thursday" | ||||
|         /> | ||||
|         <q-checkbox | ||||
|           v-model="winupdatepolicy.run_time_days" | ||||
|           :val="5" | ||||
|           :val="4" | ||||
|           label="Friday" | ||||
|         /> | ||||
|         <q-checkbox | ||||
|           v-model="winupdatepolicy.run_time_days" | ||||
|           :val="6" | ||||
|           :val="5" | ||||
|           label="Saturday" | ||||
|         /> | ||||
|         <q-checkbox | ||||
|           v-model="winupdatepolicy.run_time_days" | ||||
|           :val="0" | ||||
|           :val="6" | ||||
|           label="Sunday" | ||||
|         /> | ||||
|       </div> | ||||
|   | ||||
| @@ -63,11 +63,14 @@ export default { | ||||
|       loading.value = true; | ||||
|  | ||||
|       try { | ||||
|         await scheduleAgentReboot(props.agent.agent_id, state.value); | ||||
|         const ret = await scheduleAgentReboot( | ||||
|           props.agent.agent_id, | ||||
|           state.value | ||||
|         ); | ||||
|         $q.dialog({ | ||||
|           title: "Reboot pending", | ||||
|           style: "width: 40vw", | ||||
|           message: `A reboot has been scheduled for <strong>${state.value.datetime}</strong> on ${props.agent.hostname}. | ||||
|           message: `A reboot has been scheduled for <strong>${ret.time}</strong> on ${props.agent.hostname}. | ||||
|             <br />It can be cancelled from the Pending Actions menu until the scheduled time.`, | ||||
|           html: true, | ||||
|         }).onDismiss(onDialogOK); | ||||
|   | ||||
| @@ -77,6 +77,18 @@ | ||||
|             new-value-mode="add" | ||||
|           /> | ||||
|         </q-card-section> | ||||
|         <q-card-section> | ||||
|           <tactical-dropdown | ||||
|             v-model="state.env_vars" | ||||
|             :label="envVarsLabel" | ||||
|             filled | ||||
|             use-input | ||||
|             multiple | ||||
|             hide-dropdown-icon | ||||
|             input-debounce="0" | ||||
|             new-value-mode="add" | ||||
|           /> | ||||
|         </q-card-section> | ||||
|         <q-card-section> | ||||
|           <q-option-group | ||||
|             v-model="state.output" | ||||
| @@ -128,6 +140,11 @@ | ||||
|           /> | ||||
|           <q-checkbox v-model="state.save_all_output" label="Save all output" /> | ||||
|         </q-card-section> | ||||
|         <q-card-section v-if="agent.plat === 'windows'"> | ||||
|           <q-checkbox v-model="state.run_as_user" label="Run As User"> | ||||
|             <q-tooltip>{{ runAsUserToolTip }}</q-tooltip> | ||||
|           </q-checkbox> | ||||
|         </q-card-section> | ||||
|         <q-card-section> | ||||
|           <q-input | ||||
|             v-model.number="state.timeout" | ||||
| @@ -173,6 +190,7 @@ import { useScriptDropdown } from "@/composables/scripts"; | ||||
| import { useCustomFieldDropdown } from "@/composables/core"; | ||||
| import { runScript } from "@/api/agents"; | ||||
| import { notifySuccess } from "@/utils/notify"; | ||||
| import { envVarsLabel, runAsUserToolTip } from "@/constants/constants"; | ||||
| import { | ||||
|   formatScriptSyntax, | ||||
|   removeExtraOptionCategories, | ||||
| @@ -203,7 +221,7 @@ export default { | ||||
|     const { dialogRef, onDialogHide } = useDialogPluginComponent(); | ||||
|  | ||||
|     // setup dropdowns | ||||
|     const { script, scriptOptions, defaultTimeout, defaultArgs, syntax, link } = | ||||
|     const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars, syntax, link } = | ||||
|       useScriptDropdown(props.script, { | ||||
|         onMount: true, | ||||
|         filterByPlatform: props.agent.plat, | ||||
| @@ -219,7 +237,9 @@ export default { | ||||
|       save_all_output: false, | ||||
|       script, | ||||
|       args: defaultArgs, | ||||
|       env_vars: defaultEnvVars, | ||||
|       timeout: defaultTimeout, | ||||
|       run_as_user: false, | ||||
|     }); | ||||
|  | ||||
|     const ret = ref(null); | ||||
| @@ -273,6 +293,8 @@ export default { | ||||
|  | ||||
|       // non-reactive data | ||||
|       outputOptions, | ||||
|       runAsUserToolTip, | ||||
|       envVarsLabel, | ||||
|  | ||||
|       //methods | ||||
|       formatScriptSyntax, | ||||
|   | ||||
| @@ -51,6 +51,11 @@ | ||||
|             /> | ||||
|           </div> | ||||
|         </q-card-section> | ||||
|         <q-card-section v-if="agent.plat === 'windows'"> | ||||
|           <q-checkbox v-model="state.run_as_user" label="Run As User"> | ||||
|             <q-tooltip>{{ runAsUserToolTip }}</q-tooltip> | ||||
|           </q-checkbox> | ||||
|         </q-card-section> | ||||
|         <q-card-section v-if="state.shell === 'custom'"> | ||||
|           <q-input | ||||
|             v-model="state.custom_shell" | ||||
| @@ -117,6 +122,7 @@ import { ref } from "vue"; | ||||
| import { useDialogPluginComponent } from "quasar"; | ||||
| import { sendAgentCommand } from "@/api/agents"; | ||||
| import { cmdPlaceholder } from "@/composables/agents"; | ||||
| import { runAsUserToolTip } from "@/constants/constants"; | ||||
|  | ||||
| export default { | ||||
|   name: "SendCommand", | ||||
| @@ -134,6 +140,7 @@ export default { | ||||
|       cmd: null, | ||||
|       timeout: 30, | ||||
|       custom_shell: null, | ||||
|       run_as_user: false, | ||||
|     }); | ||||
|  | ||||
|     const loading = ref(false); | ||||
| @@ -156,6 +163,9 @@ export default { | ||||
|       loading, | ||||
|       ret, | ||||
|  | ||||
|       // non reactivete data | ||||
|       runAsUserToolTip, | ||||
|  | ||||
|       // methods | ||||
|       submit, | ||||
|       cmdPlaceholder, | ||||
|   | ||||
| @@ -204,6 +204,20 @@ | ||||
|                 new-value-mode="add" | ||||
|               /> | ||||
|  | ||||
|               <q-select | ||||
|                 class="q-mb-sm" | ||||
|                 dense | ||||
|                 label="Failure action environment vars (press Enter after typing each key=value pair)" | ||||
|                 filled | ||||
|                 v-model="template.action_env_vars" | ||||
|                 use-input | ||||
|                 use-chips | ||||
|                 multiple | ||||
|                 hide-dropdown-icon | ||||
|                 input-debounce="0" | ||||
|                 new-value-mode="add" | ||||
|               /> | ||||
|  | ||||
|               <q-input | ||||
|                 class="q-mb-sm" | ||||
|                 label="Failure action timeout (seconds)" | ||||
| @@ -277,6 +291,20 @@ | ||||
|                 new-value-mode="add" | ||||
|               /> | ||||
|  | ||||
|               <q-select | ||||
|                 class="q-mb-sm" | ||||
|                 dense | ||||
|                 label="Resolved action environment vars (press Enter after typing each key=value pair)" | ||||
|                 filled | ||||
|                 v-model="template.resolved_action_env_vars" | ||||
|                 use-input | ||||
|                 use-chips | ||||
|                 multiple | ||||
|                 hide-dropdown-icon | ||||
|                 input-debounce="0" | ||||
|                 new-value-mode="add" | ||||
|               /> | ||||
|  | ||||
|               <q-input | ||||
|                 class="q-mb-sm" | ||||
|                 label="Resolved action timeout (seconds)" | ||||
| @@ -696,9 +724,11 @@ export default { | ||||
|         is_active: true, | ||||
|         action: null, | ||||
|         action_args: [], | ||||
|         action_env_vars: [], | ||||
|         action_timeout: 15, | ||||
|         resolved_action: null, | ||||
|         resolved_action_args: [], | ||||
|         resolved_action_env_vars: [], | ||||
|         resolved_action_timeout: 15, | ||||
|         email_recipients: [], | ||||
|         email_from: "", | ||||
| @@ -762,11 +792,13 @@ export default { | ||||
|           (i) => i.value === this.template.action | ||||
|         ); | ||||
|         this.template.action_args = script.args; | ||||
|         this.template.action_env_vars = script.env_vars; | ||||
|       } else if (type === "resolved") { | ||||
|         const script = this.scriptOptions.find( | ||||
|           (i) => i.value === this.template.resolved_action | ||||
|         ); | ||||
|         this.template.resolved_action_args = script.args; | ||||
|         this.template.resolved_action_env_vars = script.env_vars; | ||||
|       } | ||||
|     }, | ||||
|     toggleAddEmail() { | ||||
|   | ||||
| @@ -12,11 +12,15 @@ | ||||
|         color="positive" | ||||
|         class="full-width" | ||||
|         @click="doCodeSign" | ||||
|         :loading="loading" | ||||
|       > | ||||
|         <q-tooltip | ||||
|           >Force all existing agents to be updated to the code-signed | ||||
|           version</q-tooltip | ||||
|         > | ||||
|         <template v-slot:loading> | ||||
|           <q-spinner-facebook /> | ||||
|         </template> | ||||
|       </q-btn> | ||||
|     </q-card-section> | ||||
|     <q-form @submit.prevent="editToken"> | ||||
| @@ -33,56 +37,92 @@ | ||||
|       </q-card-section> | ||||
|       <q-card-section class="row items-center"> | ||||
|         <q-btn label="Save" color="primary" type="submit" /> | ||||
|         <q-space /> | ||||
|         <q-btn label="Delete" color="negative" @click="confirmDelete" /> | ||||
|       </q-card-section> | ||||
|     </q-form> | ||||
|   </q-card> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import mixins from "@/mixins/mixins"; | ||||
| import { ref, onMounted } from "vue"; | ||||
| import { useQuasar } from "quasar"; | ||||
| import axios from "axios"; | ||||
| import { notifySuccess } from "@/utils/notify"; | ||||
|  | ||||
| const endpoint = "/core/codesign/"; | ||||
|  | ||||
| export default { | ||||
|   name: "CodeSign", | ||||
|   mixins: [mixins], | ||||
|   data() { | ||||
|     return { | ||||
|       settings: { | ||||
|         token: "", | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
|     getToken() { | ||||
|       this.$axios.get("/core/codesign/").then((r) => { | ||||
|         this.settings = r.data; | ||||
|   setup() { | ||||
|     const $q = useQuasar(); | ||||
|     const settings = ref({ token: "" }); | ||||
|     const loading = ref(false); | ||||
|  | ||||
|     async function getToken() { | ||||
|       try { | ||||
|         const { data } = await axios.get(endpoint); | ||||
|         settings.value = data; | ||||
|       } catch (e) { | ||||
|         console.error(e); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     async function deleteToken() { | ||||
|       try { | ||||
|         await axios.delete(endpoint); | ||||
|         notifySuccess("Token was deleted!"); | ||||
|         await getToken(); | ||||
|       } catch (e) { | ||||
|         console.error(e); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     function confirmDelete() { | ||||
|       $q.dialog({ | ||||
|         title: "Delete token?", | ||||
|         cancel: true, | ||||
|         persistent: true, | ||||
|       }).onOk(() => { | ||||
|         deleteToken(); | ||||
|       }); | ||||
|     }, | ||||
|     editToken() { | ||||
|       this.$q.loading.show(); | ||||
|       this.$axios | ||||
|         .patch("/core/codesign/", this.settings) | ||||
|         .then((r) => { | ||||
|           this.$q.loading.hide(); | ||||
|           this.notifySuccess(r.data); | ||||
|         }) | ||||
|         .catch(() => { | ||||
|           this.$q.loading.hide(); | ||||
|         }); | ||||
|     }, | ||||
|     doCodeSign() { | ||||
|       this.$q.loading.show(); | ||||
|       this.$axios | ||||
|         .post("/core/codesign/") | ||||
|         .then((r) => { | ||||
|           this.$q.loading.hide(); | ||||
|           this.notifySuccess(r.data); | ||||
|         }) | ||||
|         .catch(() => { | ||||
|           this.$q.loading.hide(); | ||||
|         }); | ||||
|     }, | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.getToken(); | ||||
|     } | ||||
|  | ||||
|     async function doCodeSign() { | ||||
|       loading.value = true; | ||||
|       try { | ||||
|         const { data } = await axios.post(endpoint); | ||||
|         loading.value = false; | ||||
|         notifySuccess(data); | ||||
|       } catch (e) { | ||||
|         loading.value = false; | ||||
|         console.error(e); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     async function editToken() { | ||||
|       $q.loading.show(); | ||||
|       try { | ||||
|         const { data } = await axios.patch(endpoint, settings.value); | ||||
|         $q.loading.hide(); | ||||
|         notifySuccess(data); | ||||
|       } catch (e) { | ||||
|         $q.loading.hide(); | ||||
|         console.error(e); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     onMounted(() => { | ||||
|       getToken(); | ||||
|     }); | ||||
|  | ||||
|     return { | ||||
|       settings, | ||||
|       loading, | ||||
|       confirmDelete, | ||||
|       doCodeSign, | ||||
|       editToken, | ||||
|     }; | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|   | ||||
| @@ -118,6 +118,17 @@ | ||||
|               new-value-mode="add" | ||||
|               :readonly="readonly" | ||||
|             /> | ||||
|             <tactical-dropdown | ||||
|               v-model="formScript.env_vars" | ||||
|               :label="envVarsLabel" | ||||
|               filled | ||||
|               use-input | ||||
|               multiple | ||||
|               hide-dropdown-icon | ||||
|               input-debounce="0" | ||||
|               new-value-mode="add" | ||||
|               :readonly="readonly" | ||||
|             /> | ||||
|             <q-input | ||||
|               type="number" | ||||
|               filled | ||||
| @@ -128,6 +139,18 @@ | ||||
|               :rules="[(val) => val >= 5 || 'Minimum is 5']" | ||||
|               hide-bottom-space | ||||
|             /> | ||||
|             <q-checkbox | ||||
|               v-model="formScript.run_as_user" | ||||
|               label="Run As User (Windows only)" | ||||
|             > | ||||
|               <q-tooltip | ||||
|                 >Setting this value on the script model will always override any | ||||
|                 'Run As User' checkboxes in the UI and force this script to | ||||
|                 always be run in the context of the logged in user. If no user | ||||
|                 is logged in, the script will not run and an error will be | ||||
|                 returned. | ||||
|               </q-tooltip> | ||||
|             </q-checkbox> | ||||
|             <q-input | ||||
|               label="Syntax" | ||||
|               type="textarea" | ||||
| @@ -217,6 +240,7 @@ import "ace-builds/src-noconflict/theme-tomorrow"; | ||||
|  | ||||
| // static data | ||||
| import { shellOptions } from "@/composables/scripts"; | ||||
| import { envVarsLabel } from "@/constants/constants"; | ||||
|  | ||||
| export default { | ||||
|   name: "ScriptFormModal", | ||||
| @@ -253,6 +277,8 @@ export default { | ||||
|           default_timeout: 90, | ||||
|           args: [], | ||||
|           script_body: "", | ||||
|           run_as_user: false, | ||||
|           env_vars: [], | ||||
|         }); | ||||
|  | ||||
|     if (props.clone) script.value.name = `(Copy) ${script.value.name}`; | ||||
| @@ -350,6 +376,7 @@ export default { | ||||
|       // non-reactive data | ||||
|       shellOptions, | ||||
|       agentPlatformOptions, | ||||
|       envVarsLabel, | ||||
|  | ||||
|       //computed | ||||
|       title, | ||||
|   | ||||
| @@ -867,7 +867,7 @@ export default { | ||||
|     } | ||||
|  | ||||
|     // component life cycle hooks | ||||
|     onMounted(getScripts()); | ||||
|     onMounted(getScripts); | ||||
|  | ||||
|     return { | ||||
|       // reactive data | ||||
|   | ||||
| @@ -93,6 +93,20 @@ | ||||
|           /> | ||||
|         </q-card-section> | ||||
|  | ||||
|         <q-card-section> | ||||
|           <tactical-dropdown | ||||
|             v-model="script.env_vars" | ||||
|             label="Environment Variables" | ||||
|             placeholder="(press Enter after typing each key=value pair)" | ||||
|             filled | ||||
|             use-input | ||||
|             multiple | ||||
|             hide-dropdown-icon | ||||
|             input-debounce="0" | ||||
|             new-value-mode="add" | ||||
|           /> | ||||
|         </q-card-section> | ||||
|  | ||||
|         <q-card-section> | ||||
|           <q-input | ||||
|             label="Default Timeout" | ||||
|   | ||||
| @@ -44,6 +44,8 @@ export default { | ||||
|         timeout: props.script.default_timeout, | ||||
|         args: props.script.args, | ||||
|         shell: props.script.shell, | ||||
|         run_as_user: props.script.run_as_user, | ||||
|         env_vars: props.script.env_vars, | ||||
|       }; | ||||
|       try { | ||||
|         ret.value = await testScript(props.agent, data); | ||||
|   | ||||
| @@ -102,7 +102,7 @@ | ||||
|  | ||||
|               <tactical-dropdown | ||||
|                 v-if="actionType === 'script'" | ||||
|                 class="col-4" | ||||
|                 class="col-3" | ||||
|                 label="Select script" | ||||
|                 v-model="script" | ||||
|                 :options="scriptOptions" | ||||
| @@ -113,7 +113,7 @@ | ||||
|  | ||||
|               <q-select | ||||
|                 v-if="actionType === 'script'" | ||||
|                 class="col-5" | ||||
|                 class="col-3" | ||||
|                 dense | ||||
|                 label="Script Arguments (press Enter after typing each argument)" | ||||
|                 filled | ||||
| @@ -126,6 +126,21 @@ | ||||
|                 new-value-mode="add" | ||||
|               /> | ||||
|  | ||||
|               <q-select | ||||
|                 v-if="actionType === 'script'" | ||||
|                 class="col-3" | ||||
|                 dense | ||||
|                 :label="envVarsLabel" | ||||
|                 filled | ||||
|                 v-model="defaultEnvVars" | ||||
|                 use-input | ||||
|                 use-chips | ||||
|                 multiple | ||||
|                 hide-dropdown-icon | ||||
|                 input-debounce="0" | ||||
|                 new-value-mode="add" | ||||
|               /> | ||||
|  | ||||
|               <q-input | ||||
|                 v-if="actionType === 'script'" | ||||
|                 class="col-2" | ||||
| @@ -210,6 +225,9 @@ | ||||
|                     <q-item-label caption> | ||||
|                       Arguments: {{ element.script_args }} | ||||
|                     </q-item-label> | ||||
|                     <q-item-label caption> | ||||
|                       Env Vars: {{ element.env_vars }} | ||||
|                     </q-item-label> | ||||
|                     <q-item-label caption> | ||||
|                       Timeout: {{ element.timeout }} | ||||
|                     </q-item-label> | ||||
| @@ -727,6 +745,7 @@ import { useCheckDropdown } from "@/composables/checks"; | ||||
| import { useCustomFieldDropdown } from "@/composables/core"; | ||||
| import { notifySuccess, notifyError } from "@/utils/notify"; | ||||
| import { validateTimePeriod } from "@/utils/validation"; | ||||
| import { envVarsLabel } from "@/constants/constants"; | ||||
| import { | ||||
|   convertPeriodToSeconds, | ||||
|   convertToBitArray, | ||||
| @@ -817,7 +836,7 @@ export default { | ||||
|     const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); | ||||
|  | ||||
|     // setup dropdowns | ||||
|     const { script, scriptOptions, defaultTimeout, defaultArgs } = | ||||
|     const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars } = | ||||
|       useScriptDropdown(undefined, { | ||||
|         onMount: true, | ||||
|       }); | ||||
| @@ -914,6 +933,7 @@ export default { | ||||
|           script: script.value, | ||||
|           timeout: defaultTimeout.value, | ||||
|           script_args: defaultArgs.value, | ||||
|           env_vars: defaultEnvVars.value, | ||||
|         }); | ||||
|       } else if (actionType.value === "cmd") { | ||||
|         task.value.actions.push({ | ||||
| @@ -927,6 +947,7 @@ export default { | ||||
|       // clear fields after add | ||||
|       script.value = null; | ||||
|       defaultArgs.value = []; | ||||
|       defaultEnvVars.value = []; | ||||
|       defaultTimeout.value = 30; | ||||
|       command.value = ""; | ||||
|     } | ||||
| @@ -991,10 +1012,16 @@ export default { | ||||
|         : []; | ||||
|  | ||||
|       // remove milliseconds and Z to work with native date input | ||||
|       task.value.run_time_date = formatDateInputField(task.value.run_time_date); | ||||
|       task.value.run_time_date = formatDateInputField( | ||||
|         task.value.run_time_date, | ||||
|         true | ||||
|       ); | ||||
|  | ||||
|       if (task.value.expire_date) | ||||
|         task.value.expire_date = formatDateInputField(task.value.expire_date); | ||||
|         task.value.expire_date = formatDateInputField( | ||||
|           task.value.expire_date, | ||||
|           true | ||||
|         ); | ||||
|  | ||||
|       // set task type if monthlydow is being used | ||||
|       if (task.value.task_type === "monthlydow") { | ||||
| @@ -1083,6 +1110,7 @@ export default { | ||||
|       script, | ||||
|       defaultTimeout, | ||||
|       defaultArgs, | ||||
|       defaultEnvVars, | ||||
|       actionType, | ||||
|       command, | ||||
|       shell, | ||||
| @@ -1110,6 +1138,7 @@ export default { | ||||
|       monthOptions, | ||||
|       taskTypeOptions, | ||||
|       taskInstancePolicyOptions, | ||||
|       envVarsLabel, | ||||
|  | ||||
|       // methods | ||||
|       submit, | ||||
|   | ||||
| @@ -31,7 +31,7 @@ export function useUserDropdown(onMount = false) { | ||||
|   } | ||||
|  | ||||
|   if (onMount) { | ||||
|     onMounted(getUserOptions()); | ||||
|     onMounted(getUserOptions); | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|   | ||||
| @@ -37,4 +37,5 @@ export function cmdPlaceholder(shell) { | ||||
| export const agentPlatformOptions = [ | ||||
|   { value: "windows", label: "Windows" }, | ||||
|   { value: "linux", label: "Linux" }, | ||||
|   { value: "darwin", label: "macOS" }, | ||||
| ]; | ||||
|   | ||||
| @@ -8,6 +8,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) { | ||||
|   const scriptOptions = ref([]); | ||||
|   const defaultTimeout = ref(30); | ||||
|   const defaultArgs = ref([]); | ||||
|   const defaultEnvVars = ref([]); | ||||
|   const script = ref(setScript); | ||||
|   const syntax = ref(""); | ||||
|   const link = ref(""); | ||||
| @@ -29,6 +30,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) { | ||||
|       ); | ||||
|       defaultTimeout.value = tmpScript.timeout; | ||||
|       defaultArgs.value = tmpScript.args; | ||||
|       defaultEnvVars.value = tmpScript.env_vars, | ||||
|       syntax.value = tmpScript.syntax; | ||||
|       link.value = | ||||
|         tmpScript.script_type === "builtin" | ||||
| @@ -49,6 +51,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) { | ||||
|     scriptOptions, | ||||
|     defaultTimeout, | ||||
|     defaultArgs, | ||||
|     defaultEnvVars, | ||||
|     syntax, | ||||
|     link, | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| const GOARCH_AMD64 = "amd64"; | ||||
| const GOARCH_i386 = "386"; | ||||
| const GOARCH_ARM64 = "arm64"; | ||||
| const GOARCH_ARM32 = "arm"; | ||||
| export const GOARCH_AMD64 = "amd64"; | ||||
| export const GOARCH_i386 = "386"; | ||||
| export const GOARCH_ARM64 = "arm64"; | ||||
| export const GOARCH_ARM32 = "arm"; | ||||
|  | ||||
| export { GOARCH_AMD64, GOARCH_i386, GOARCH_ARM64, GOARCH_ARM32 }; | ||||
| export const runAsUserToolTip = | ||||
|   "Run in the context of the logged in user. If no user is logged in, the script will not run and an error will be returned."; | ||||
|  | ||||
| export const envVarsLabel =  | ||||
|   "Environment vars (press Enter after typing each key=value pair)" | ||||
|   | ||||
| @@ -14,6 +14,22 @@ | ||||
|           @click="$store.dispatch('reload')" | ||||
|         /> | ||||
|       </q-banner> | ||||
|       <q-banner | ||||
|         v-if="!hosted && tokenExpired" | ||||
|         inline-actions | ||||
|         class="bg-yellow text-black text-center" | ||||
|       > | ||||
|       <q-icon size="xl" name="warning" /> | ||||
|         <span><br />Your code signing token is no longer valid.<br/><br/> | ||||
|         If you have downgraded or cancelled your sponsorship, please delete your token from the Code Signing modal and refresh to get rid of this banner.<br/><br/> | ||||
|         For any issues or to renew your sponsorship please email support@amidaware.com<br/><br/></span> | ||||
|         <q-btn | ||||
|           color="dark" | ||||
|           icon="refresh" | ||||
|           label="Refresh" | ||||
|           @click="$store.dispatch('reload')" | ||||
|         /> | ||||
|       </q-banner> | ||||
|       <q-toolbar> | ||||
|         <q-btn | ||||
|           dense | ||||
| @@ -35,12 +51,7 @@ | ||||
|           Tactical RMM<span class="text-overline q-ml-sm" | ||||
|             >v{{ currentTRMMVersion }}</span | ||||
|           > | ||||
|           <span | ||||
|             class="text-overline q-ml-md" | ||||
|             v-if=" | ||||
|               latestTRMMVersion !== 'error' && | ||||
|               currentTRMMVersion !== latestTRMMVersion | ||||
|             " | ||||
|           <span class="text-overline q-ml-md" v-if="updateAvailable()" | ||||
|             ><q-badge color="warning" | ||||
|               ><a :href="latestReleaseURL" target="_blank" | ||||
|                 >v{{ latestTRMMVersion }} available</a | ||||
| @@ -171,6 +182,8 @@ export default { | ||||
|     const latestTRMMVersion = computed(() => store.state.latestTRMMVersion); | ||||
|     const needRefresh = computed(() => store.state.needrefresh); | ||||
|     const user = computed(() => store.state.username); | ||||
|     const hosted = computed(() => store.state.hosted); | ||||
|     const tokenExpired = computed(() => store.state.tokenExpired); | ||||
|  | ||||
|     const latestReleaseURL = computed(() => { | ||||
|       return latestTRMMVersion.value | ||||
| @@ -233,6 +246,11 @@ export default { | ||||
|       }, 60 * 5 * 1000); | ||||
|     } | ||||
|  | ||||
|     function updateAvailable() { | ||||
|       if (latestTRMMVersion.value === "error" || hosted.value) return false; | ||||
|       return currentTRMMVersion.value !== latestTRMMVersion.value; | ||||
|     } | ||||
|  | ||||
|     onMounted(() => { | ||||
|       setupWS(); | ||||
|       store.dispatch("getDashInfo"); | ||||
| @@ -258,9 +276,12 @@ export default { | ||||
|       user, | ||||
|       needRefresh, | ||||
|       darkMode, | ||||
|       hosted, | ||||
|       tokenExpired, | ||||
|  | ||||
|       // methods | ||||
|       showUserPreferences, | ||||
|       updateAvailable, | ||||
|     }; | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -193,6 +193,7 @@ export default { | ||||
|               value: script.id, | ||||
|               timeout: script.default_timeout, | ||||
|               args: script.args, | ||||
|               env_vars: script.env_vars, | ||||
|             }); | ||||
|           } else if (cat === "Unassigned" && !script.category) { | ||||
|             tmp.push({ | ||||
| @@ -200,6 +201,7 @@ export default { | ||||
|               value: script.id, | ||||
|               timeout: script.default_timeout, | ||||
|               args: script.args, | ||||
|               env_vars: script.env_vars, | ||||
|             }); | ||||
|           } | ||||
|         }); | ||||
|   | ||||
| @@ -17,6 +17,7 @@ export default function () { | ||||
|         agentPlatform: "windows", | ||||
|         agentTableLoading: false, | ||||
|         needrefresh: false, | ||||
|         tokenExpired: false, | ||||
|         refreshSummaryTab: false, | ||||
|         tableHeight: "300px", | ||||
|         tabHeight: "300px", | ||||
| @@ -83,6 +84,9 @@ export default function () { | ||||
|       SET_REFRESH_NEEDED(state, action) { | ||||
|         state.needrefresh = action; | ||||
|       }, | ||||
|       SET_TOKEN_EXPIRED(state, action) { | ||||
|         state.tokenExpired = action; | ||||
|       }, | ||||
|       SET_SPLITTER(state, val) { | ||||
|         // top toolbar is 50px. Filebar is 40px and agent filter tabs are 44px | ||||
|         state.tableHeight = `${Screen.height - 50 - 40 - 78 - val}px`; | ||||
| @@ -212,6 +216,7 @@ export default function () { | ||||
|         context.commit("SET_URL_ACTION", data.url_action); | ||||
|         context.commit("setShowCommunityScripts", data.show_community_scripts); | ||||
|         context.commit("SET_HOSTED", data.hosted); | ||||
|         context.commit("SET_TOKEN_EXPIRED", data.token_is_expired); | ||||
|  | ||||
|         if (data.date_format && data.date_format !== "") | ||||
|           context.commit("setDateFormat", data.date_format); | ||||
|   | ||||
| @@ -68,6 +68,7 @@ export function formatScriptOptions(data) { | ||||
|           value: script.id, | ||||
|           timeout: script.default_timeout, | ||||
|           args: script.args, | ||||
|           env_vars: script.env_vars, | ||||
|           filename: script.filename, | ||||
|           syntax: script.syntax, | ||||
|           script_type: script.script_type, | ||||
| @@ -80,6 +81,7 @@ export function formatScriptOptions(data) { | ||||
|           value: script.id, | ||||
|           timeout: script.default_timeout, | ||||
|           args: script.args, | ||||
|           env_vars: script.env_vars, | ||||
|           filename: script.filename, | ||||
|           syntax: script.syntax, | ||||
|           script_type: script.script_type, | ||||
| @@ -285,7 +287,7 @@ export function formatDateInputField(isoDateString, noTimezone = false) { | ||||
|   if (noTimezone) { | ||||
|     isoDateString = isoDateString.replace("Z", ""); | ||||
|   } | ||||
|   return date.formatDate(isoDateString, "YYYY-MM-DDTHH:mm:ss"); | ||||
|   return date.formatDate(isoDateString, "YYYY-MM-DDTHH:mm"); | ||||
| } | ||||
|  | ||||
| // converts a local date string "YYYY-MM-DDTHH:mm:ss" to an iso date string with the local timezone | ||||
|   | ||||
		Reference in New Issue
	
	Block a user