Compare commits
	
		
			21 Commits
		
	
	
		
			v0.101.17-
			...
			v0.101.21-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3fdd8272f6 | ||
|  | 339227bedc | ||
|  | 17c7c95cc1 | ||
|  | a3ceb5e81b | ||
|  | 679d8cab77 | ||
|  | c4c1474e09 | ||
|  | 82677b0b82 | ||
|  | b78af07f11 | ||
|  | 24acef19c5 | ||
|  | fee6edb39e | ||
|  | 89e7db905d | ||
|  | 827e81dcda | ||
|  | 6ea3a053f2 | ||
|  | 88d297f7c6 | ||
|  | 6c57d3e6b1 | ||
|  | 0113fbc761 | ||
|  | 95df8c1889 | ||
|  | 819a364207 | ||
|  | ed2b07fb0b | ||
|  | 64ed5e8740 | ||
|  | cdeaa3d9c4 | 
							
								
								
									
										1381
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1381
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										26
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "web", | ||||
|   "version": "0.101.17-dev", | ||||
|   "version": "0.101.21-dev", | ||||
|   "private": true, | ||||
|   "productName": "Tactical RMM", | ||||
|   "scripts": { | ||||
| @@ -10,12 +10,12 @@ | ||||
|     "format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@quasar/extras": "1.16.2", | ||||
|     "apexcharts": "3.37.3", | ||||
|     "axios": "1.3.5", | ||||
|     "@quasar/extras": "1.16.4", | ||||
|     "apexcharts": "3.40.0", | ||||
|     "axios": "1.4.0", | ||||
|     "dotenv": "16.0.3", | ||||
|     "qrcode.vue": "3.3.4", | ||||
|     "quasar": "2.11.10", | ||||
|     "qrcode.vue": "3.4.0", | ||||
|     "quasar": "2.12.0", | ||||
|     "vue": "3.2.47", | ||||
|     "vue3-ace-editor": "2.2.2", | ||||
|     "vue3-apexcharts": "1.4.1", | ||||
| @@ -24,17 +24,17 @@ | ||||
|     "vuex": "4.1.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@quasar/cli": "^2.0.0", | ||||
|     "@quasar/cli": "^2.2.1", | ||||
|     "@intlify/unplugin-vue-i18n": "^0.10.0", | ||||
|     "@quasar/app-vite": "^1.2.1", | ||||
|     "@types/node": "^18.15.11", | ||||
|     "@typescript-eslint/eslint-plugin": "^5.57.1", | ||||
|     "@typescript-eslint/parser": "^5.57.1", | ||||
|     "@quasar/app-vite": "^1.4.3", | ||||
|     "@types/node": "^20.2.4", | ||||
|     "@typescript-eslint/eslint-plugin": "^5.59.7", | ||||
|     "@typescript-eslint/parser": "^5.59.7", | ||||
|     "autoprefixer": "10.4.14", | ||||
|     "eslint": "8.37.0", | ||||
|     "eslint": "8.41.0", | ||||
|     "eslint-config-prettier": "8.8.0", | ||||
|     "eslint-plugin-vue": "8.7.1", | ||||
|     "prettier": "2.8.7", | ||||
|     "prettier": "2.8.8", | ||||
|     "typescript": "5.0.4" | ||||
|   } | ||||
| } | ||||
| @@ -12,6 +12,9 @@ export default { | ||||
| body | ||||
|   overflow-y: hidden | ||||
|  | ||||
| a | ||||
|   color: #1976D2 | ||||
|  | ||||
| .tbl-sticky | ||||
|   thead tr th | ||||
|     position: sticky | ||||
|   | ||||
| @@ -232,3 +232,8 @@ export async function removeAgentNote(pk) { | ||||
|   const { data } = await axios.delete(`${baseUrl}/notes/${pk}/`); | ||||
|   return data; | ||||
| } | ||||
|  | ||||
| export async function wakeUpWOL(agent_id) { | ||||
|   const { data } = await axios.post(`${baseUrl}/${agent_id}/wol/`); | ||||
|   return data; | ||||
| } | ||||
|   | ||||
| @@ -38,3 +38,8 @@ export async function runURLAction(payload) { | ||||
|     console.error(e); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export async function generateScript(payload) { | ||||
|   const { data } = await axios.post(`${baseUrl}/openai/generate/`, payload); | ||||
|   return data; | ||||
| } | ||||
|   | ||||
| @@ -211,7 +211,7 @@ | ||||
|               v-if="props.row.maintenance_mode" | ||||
|               name="construction" | ||||
|               size="1.2em" | ||||
|               color="green" | ||||
|               :color="dash_positive_color" | ||||
|             > | ||||
|               <q-tooltip>Maintenance Mode Enabled</q-tooltip> | ||||
|             </q-icon> | ||||
| @@ -219,7 +219,7 @@ | ||||
|               v-else-if="props.row.checks.failing > 0" | ||||
|               name="fas fa-check-double" | ||||
|               size="1.2em" | ||||
|               color="negative" | ||||
|               :color="dash_negative_color" | ||||
|             > | ||||
|               <q-tooltip>Checks failing</q-tooltip> | ||||
|             </q-icon> | ||||
| @@ -227,7 +227,7 @@ | ||||
|               v-else-if="props.row.checks.warning > 0" | ||||
|               name="fas fa-check-double" | ||||
|               size="1.2em" | ||||
|               color="warning" | ||||
|               :color="dash_warning_color" | ||||
|             > | ||||
|               <q-tooltip>Checks warning</q-tooltip> | ||||
|             </q-icon> | ||||
| @@ -235,7 +235,7 @@ | ||||
|               v-else-if="props.row.checks.info > 0" | ||||
|               name="fas fa-check-double" | ||||
|               size="1.2em" | ||||
|               color="info" | ||||
|               :color="dash_info_color" | ||||
|             > | ||||
|               <q-tooltip>Checks info</q-tooltip> | ||||
|             </q-icon> | ||||
| @@ -243,7 +243,7 @@ | ||||
|               v-else | ||||
|               name="fas fa-check-double" | ||||
|               size="1.2em" | ||||
|               color="positive" | ||||
|               :color="dash_positive_color" | ||||
|             > | ||||
|               <q-tooltip>Checks passing</q-tooltip> | ||||
|             </q-icon> | ||||
| @@ -279,7 +279,7 @@ | ||||
|               @click="showPendingActionsModal(props.row)" | ||||
|               name="far fa-clock" | ||||
|               size="1.4em" | ||||
|               color="warning" | ||||
|               :color="dash_warning_color" | ||||
|               class="cursor-pointer" | ||||
|             > | ||||
|               <q-tooltip | ||||
| @@ -303,7 +303,7 @@ | ||||
|               v-if="props.row.status === 'overdue'" | ||||
|               name="fas fa-signal" | ||||
|               size="1.2em" | ||||
|               color="negative" | ||||
|               :color="dash_negative_color" | ||||
|             > | ||||
|               <q-tooltip>Agent overdue</q-tooltip> | ||||
|             </q-icon> | ||||
| @@ -311,11 +311,16 @@ | ||||
|               v-else-if="props.row.status === 'offline'" | ||||
|               name="fas fa-signal" | ||||
|               size="1.2em" | ||||
|               color="warning" | ||||
|               :color="dash_warning_color" | ||||
|             > | ||||
|               <q-tooltip>Agent offline</q-tooltip> | ||||
|             </q-icon> | ||||
|             <q-icon v-else name="fas fa-signal" size="1.2em" color="positive"> | ||||
|             <q-icon | ||||
|               v-else | ||||
|               name="fas fa-signal" | ||||
|               size="1.2em" | ||||
|               :color="dash_positive_color" | ||||
|             > | ||||
|               <q-tooltip>Agent online</q-tooltip> | ||||
|             </q-icon> | ||||
|           </q-td> | ||||
| @@ -373,7 +378,8 @@ export default { | ||||
|         "local_ips", | ||||
|         "make_model", | ||||
|         "physical_disks", | ||||
|         "custom_fields" | ||||
|         "custom_fields", | ||||
|         "serial_number", | ||||
|       ]; | ||||
|       // quasar filter only does visible columns so this is a hack to add hidden columns we want to filter | ||||
|       // originally I was modifying cols directly but this led to phantom colum so doing it this way now | ||||
| @@ -435,7 +441,7 @@ export default { | ||||
|         return allColumns.some((col) => { | ||||
|           let valObj = cellValue(col, row); | ||||
|           if (Array.isArray(valObj)) { | ||||
|             valObj = valObj.map((item) => item.value ? item.value : item); | ||||
|             valObj = valObj.map((item) => (item.value ? item.value : item)); | ||||
|           } | ||||
|           const val = valObj + ""; | ||||
|           const haystack = | ||||
| @@ -488,7 +494,9 @@ export default { | ||||
|       const data = { | ||||
|         [db_field]: !alert_action, | ||||
|       }; | ||||
|       const alertColor = !alert_action ? "positive" : "info"; | ||||
|       const alertColor = !alert_action | ||||
|         ? this.dash_positive_color | ||||
|         : this.dash_info_color; | ||||
|       this.$axios.put(`/agents/${agent.agent_id}/`, data).then(() => { | ||||
|         this.$q.notify({ | ||||
|           color: alertColor, | ||||
| @@ -532,7 +540,13 @@ export default { | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState(["tableHeight"]), | ||||
|     ...mapState([ | ||||
|       "tableHeight", | ||||
|       "dash_info_color", | ||||
|       "dash_positive_color", | ||||
|       "dash_negative_color", | ||||
|       "dash_warning_color", | ||||
|     ]), | ||||
|     agentDblClickAction() { | ||||
|       return this.$store.state.agentDblClickAction; | ||||
|     }, | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
|     <q-badge v-if="alertsCount > 0" :color="badgeColor" floating transparent>{{ | ||||
|       alertsCountText() | ||||
|     }}</q-badge> | ||||
|     <q-menu style="max-height: 30vh"> | ||||
|     <q-menu :style="{ 'max-height': `${$q.screen.height - 100}px` }"> | ||||
|       <q-list separator> | ||||
|         <q-item v-if="alertsCount === 0">No New Alerts</q-item> | ||||
|         <q-item v-for="alert in topAlerts" :key="alert.id"> | ||||
| @@ -59,6 +59,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapState } from "vuex"; | ||||
| import mixins from "@/mixins/mixins"; | ||||
| import AlertsOverview from "@/components/modals/alerts/AlertsOverview.vue"; | ||||
| import { getTimeLapse } from "@/utils/format"; | ||||
| @@ -75,19 +76,21 @@ export default { | ||||
|     return { | ||||
|       alertsCount: 0, | ||||
|       topAlerts: [], | ||||
|       errorColor: "red", | ||||
|       warningColor: "orange", | ||||
|       infoColor: "blue", | ||||
|       poll: null, | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState([ | ||||
|       "dash_info_color", | ||||
|       "dash_warning_color", | ||||
|       "dash_negative_color", | ||||
|     ]), | ||||
|     badgeColor() { | ||||
|       const severities = this.topAlerts.map((alert) => alert.severity); | ||||
|  | ||||
|       if (severities.includes("error")) return this.errorColor; | ||||
|       else if (severities.includes("warning")) return this.warningColor; | ||||
|       else return this.infoColor; | ||||
|       if (severities.includes("error")) return this.dash_negative_color; | ||||
|       else if (severities.includes("warning")) return this.dash_warning_color; | ||||
|       else return this.dash_info_color; | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
| @@ -159,9 +162,9 @@ export default { | ||||
|         }); | ||||
|     }, | ||||
|     alertIconColor(severity) { | ||||
|       if (severity === "error") return this.errorColor; | ||||
|       else if (severity === "warning") return this.warningColor; | ||||
|       else return this.infoColor; | ||||
|       if (severity === "error") return this.dash_negative_color; | ||||
|       else if (severity === "warning") return this.dash_warning_color; | ||||
|       else return this.dash_info_color; | ||||
|     }, | ||||
|     alertsCountText() { | ||||
|       if (this.alertsCount > 99) return "99+"; | ||||
|   | ||||
| @@ -98,6 +98,10 @@ | ||||
|                 v-model="localRole.can_reboot_agents" | ||||
|                 label="Reboot Agents" | ||||
|               /> | ||||
|               <q-checkbox | ||||
|                 v-model="localRole.can_send_wol" | ||||
|                 label="Wake-Up (WoL) Agents" | ||||
|               /> | ||||
|               <q-checkbox | ||||
|                 v-model="localRole.can_install_agents" | ||||
|                 label="Install Agents" | ||||
| @@ -437,8 +441,8 @@ export default { | ||||
|           can_run_scripts: false, | ||||
|           can_run_bulk: false, | ||||
|           can_manage_winsvcs: false, | ||||
|           can_recover_agents: false, | ||||
|           can_list_agent_history: false, | ||||
|           can_send_wol: false, | ||||
|           // software perms | ||||
|           can_list_software: false, | ||||
|           can_manage_software: false, | ||||
|   | ||||
| @@ -146,6 +146,13 @@ | ||||
|       <q-item-section>Run Checks</q-item-section> | ||||
|     </q-item> | ||||
|  | ||||
|     <q-item clickable v-close-popup @click="wakeUp(agent)"> | ||||
|       <q-item-section side> | ||||
|         <q-icon size="xs" name="offline_bolt" /> | ||||
|       </q-item-section> | ||||
|       <q-item-section>Wake-Up (WoL)</q-item-section> | ||||
|     </q-item> | ||||
|  | ||||
|     <q-item clickable> | ||||
|       <q-item-section side> | ||||
|         <q-icon size="xs" name="power_settings_new" /> | ||||
| @@ -210,6 +217,7 @@ import { | ||||
|   removeAgent, | ||||
|   runRemoteBackground, | ||||
|   runTakeControl, | ||||
|   wakeUpWOL, | ||||
| } from "@/api/agents"; | ||||
| import { runAgentUpdateScan, runAgentUpdateInstall } from "@/api/winupdates"; | ||||
| import { runAgentChecks } from "@/api/checks"; | ||||
| @@ -370,6 +378,15 @@ export default { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     async function wakeUp(agent) { | ||||
|       try { | ||||
|         const data = await wakeUpWOL(agent.agent_id); | ||||
|         notifySuccess(data); | ||||
|       } catch (e) { | ||||
|         console.error(e); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     function showRebootLaterModal(agent) { | ||||
|       $q.dialog({ | ||||
|         component: RebootLater, | ||||
| @@ -498,6 +515,7 @@ export default { | ||||
|       showPolicyAdd, | ||||
|       showAgentRecovery, | ||||
|       pingAgent, | ||||
|       wakeUp, | ||||
|     }; | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -261,7 +261,7 @@ | ||||
|           <q-td v-else-if="props.row.task_result.status === 'passing'"> | ||||
|             <q-icon | ||||
|               style="font-size: 1.3rem" | ||||
|               color="positive" | ||||
|               :color="dash_positive_color" | ||||
|               name="check_circle" | ||||
|             > | ||||
|               <q-tooltip>Passing</q-tooltip> | ||||
| @@ -271,7 +271,7 @@ | ||||
|             <q-icon | ||||
|               v-if="props.row.alert_severity === 'info'" | ||||
|               style="font-size: 1.3rem" | ||||
|               color="info" | ||||
|               :color="dash_info_color" | ||||
|               name="info" | ||||
|             > | ||||
|               <q-tooltip>Informational</q-tooltip> | ||||
| @@ -279,7 +279,7 @@ | ||||
|             <q-icon | ||||
|               v-else-if="props.row.alert_severity === 'warning'" | ||||
|               style="font-size: 1.3rem" | ||||
|               color="warning" | ||||
|               :color="dash_warning_color" | ||||
|               name="warning" | ||||
|             > | ||||
|               <q-tooltip>Warning</q-tooltip> | ||||
| @@ -287,7 +287,7 @@ | ||||
|             <q-icon | ||||
|               v-else | ||||
|               style="font-size: 1.3rem" | ||||
|               color="negative" | ||||
|               :color="dash_negative_color" | ||||
|               name="error" | ||||
|             > | ||||
|               <q-tooltip>Error</q-tooltip> | ||||
| @@ -418,6 +418,10 @@ export default { | ||||
|     const tabHeight = computed(() => store.state.tabHeight); | ||||
|     const agentPlatform = computed(() => store.state.agentPlatform); | ||||
|     const formatDate = computed(() => store.getters.formatDate); | ||||
|     const dash_info_color = computed(() => store.state.dash_info_color); | ||||
|     const dash_positive_color = computed(() => store.state.dash_positive_color); | ||||
|     const dash_negative_color = computed(() => store.state.dash_negative_color); | ||||
|     const dash_warning_color = computed(() => store.state.dash_warning_color); | ||||
|  | ||||
|     // setup quasar | ||||
|     const $q = useQuasar(); | ||||
| @@ -552,6 +556,10 @@ export default { | ||||
|       selectedAgent, | ||||
|       tabHeight, | ||||
|       agentPlatform, | ||||
|       dash_info_color, | ||||
|       dash_positive_color, | ||||
|       dash_warning_color, | ||||
|       dash_negative_color, | ||||
|  | ||||
|       // non-reactive data | ||||
|       columns, | ||||
|   | ||||
| @@ -301,7 +301,7 @@ | ||||
|           <q-td v-else-if="props.row.check_result.status === 'passing'"> | ||||
|             <q-icon | ||||
|               style="font-size: 1.3rem" | ||||
|               color="positive" | ||||
|               :color="dash_positive_color" | ||||
|               name="check_circle" | ||||
|             > | ||||
|               <q-tooltip>Passing</q-tooltip> | ||||
| @@ -311,7 +311,7 @@ | ||||
|             <q-icon | ||||
|               v-if="getAlertSeverity(props.row) === 'info'" | ||||
|               style="font-size: 1.3rem" | ||||
|               color="info" | ||||
|               :color="dash_info_color" | ||||
|               name="info" | ||||
|             > | ||||
|               <q-tooltip>Informational</q-tooltip> | ||||
| @@ -319,7 +319,7 @@ | ||||
|             <q-icon | ||||
|               v-else-if="getAlertSeverity(props.row) === 'warning'" | ||||
|               style="font-size: 1.3rem" | ||||
|               color="warning" | ||||
|               :color="dash_warning_color" | ||||
|               name="warning" | ||||
|             > | ||||
|               <q-tooltip>Warning</q-tooltip> | ||||
| @@ -327,7 +327,7 @@ | ||||
|             <q-icon | ||||
|               v-else | ||||
|               style="font-size: 1.3rem" | ||||
|               color="negative" | ||||
|               :color="dash_negative_color" | ||||
|               name="error" | ||||
|             > | ||||
|               <q-tooltip>Error</q-tooltip> | ||||
| @@ -479,6 +479,10 @@ export default { | ||||
|     const tabHeight = computed(() => store.state.tabHeight); | ||||
|     const agentPlatform = computed(() => store.state.agentPlatform); | ||||
|     const formatDate = computed(() => store.getters.formatDate); | ||||
|     const dash_info_color = computed(() => store.state.dash_info_color); | ||||
|     const dash_positive_color = computed(() => store.state.dash_positive_color); | ||||
|     const dash_negative_color = computed(() => store.state.dash_negative_color); | ||||
|     const dash_warning_color = computed(() => store.state.dash_warning_color); | ||||
|  | ||||
|     // setup quasar | ||||
|     const $q = useQuasar(); | ||||
| @@ -653,6 +657,10 @@ export default { | ||||
|       tabHeight, | ||||
|       selectedAgent, | ||||
|       agentPlatform, | ||||
|       dash_info_color, | ||||
|       dash_positive_color, | ||||
|       dash_warning_color, | ||||
|       dash_negative_color, | ||||
|  | ||||
|       // non-reactive data | ||||
|       columns, | ||||
|   | ||||
| @@ -18,6 +18,33 @@ | ||||
|         icon="refresh" | ||||
|         @click="refreshSummary" | ||||
|       /> | ||||
|       <q-icon | ||||
|         v-if="summary.status === 'overdue'" | ||||
|         name="fas fa-signal" | ||||
|         size="1.2em" | ||||
|         :color="dash_negative_color" | ||||
|         class="q-mr-sm" | ||||
|       > | ||||
|         <q-tooltip>Agent overdue</q-tooltip> | ||||
|       </q-icon> | ||||
|       <q-icon | ||||
|         v-else-if="summary.status === 'offline'" | ||||
|         name="fas fa-signal" | ||||
|         size="1.2em" | ||||
|         :color="dash_warning_color" | ||||
|         class="q-mr-sm" | ||||
|       > | ||||
|         <q-tooltip>Agent offline</q-tooltip> | ||||
|       </q-icon> | ||||
|       <q-icon | ||||
|         v-else | ||||
|         name="fas fa-signal" | ||||
|         size="1.2em" | ||||
|         :color="dash_positive_color" | ||||
|         class="q-mr-sm" | ||||
|       > | ||||
|         <q-tooltip>Agent online</q-tooltip> | ||||
|       </q-icon> | ||||
|       <b>{{ summary.hostname }}</b> | ||||
|       <span v-if="summary.maintenance_mode"> | ||||
|         • <q-badge color="green"> Maintenance Mode </q-badge> | ||||
| @@ -60,7 +87,7 @@ | ||||
|             </q-item-section> | ||||
|             <q-item-section>{{ summary.make_model }}</q-item-section> | ||||
|           </q-item> | ||||
|           <q-item v-for="(cpu, i) in summary.cpu_model" :key="cpu + i"> | ||||
|           <q-item> | ||||
|             <q-item-section avatar> | ||||
|               <q-icon name="fas fa-microchip" /> | ||||
|             </q-item-section> | ||||
| @@ -87,6 +114,13 @@ | ||||
|             </q-item-section> | ||||
|             <q-item-section>{{ summary.graphics }}</q-item-section> | ||||
|           </q-item> | ||||
|           <!-- serial --> | ||||
|           <q-item v-if="serial_number"> | ||||
|             <q-item-section avatar> | ||||
|               <q-icon name="fa-solid fa-barcode" /> | ||||
|             </q-item-section> | ||||
|             <q-item-section>{{ serial_number }}</q-item-section> | ||||
|           </q-item> | ||||
|           <q-item> | ||||
|             <q-item-section avatar> | ||||
|               <q-icon name="fas fa-globe-americas" /> | ||||
| @@ -110,7 +144,7 @@ | ||||
|               size="lg" | ||||
|               square | ||||
|               icon="done" | ||||
|               color="green" | ||||
|               :color="dash_positive_color" | ||||
|               text-color="white" | ||||
|             /> | ||||
|             <small>{{ summary.checks.passing }} checks passing</small> | ||||
| @@ -120,7 +154,7 @@ | ||||
|               size="lg" | ||||
|               square | ||||
|               icon="cancel" | ||||
|               color="red" | ||||
|               :color="dash_negative_color" | ||||
|               text-color="white" | ||||
|             /> | ||||
|             <small>{{ summary.checks.failing }} checks failing</small> | ||||
| @@ -130,7 +164,7 @@ | ||||
|               size="lg" | ||||
|               square | ||||
|               icon="warning" | ||||
|               color="warning" | ||||
|               :color="dash_warning_color" | ||||
|               text-color="white" | ||||
|             /> | ||||
|             <small>{{ summary.checks.warning }} checks warning</small> | ||||
| @@ -140,7 +174,7 @@ | ||||
|               size="lg" | ||||
|               square | ||||
|               icon="info" | ||||
|               color="info" | ||||
|               :color="dash_info_color" | ||||
|               text-color="white" | ||||
|             /> | ||||
|             <small>{{ summary.checks.info }} checks info</small> | ||||
| @@ -222,19 +256,34 @@ export default { | ||||
|     const store = useStore(); | ||||
|     const selectedAgent = computed(() => store.state.selectedRow); | ||||
|     const refreshSummaryTab = computed(() => store.state.refreshSummaryTab); | ||||
|     const dash_info_color = computed(() => store.state.dash_info_color); | ||||
|     const dash_positive_color = computed(() => store.state.dash_positive_color); | ||||
|     const dash_negative_color = computed(() => store.state.dash_negative_color); | ||||
|     const dash_warning_color = computed(() => store.state.dash_warning_color); | ||||
|  | ||||
|     // summary tab logic | ||||
|     const summary = ref(null); | ||||
|     const customFieldsDefinitions = ref(null); | ||||
|     const loading = ref(false); | ||||
|  | ||||
|     const serial_number = computed(() => { | ||||
|       return summary.value.wmi_detail.bios?.[0]?.[0]?.SerialNumber; | ||||
|     }); | ||||
|  | ||||
|     const cpu = computed(() => { | ||||
|       if (summary.value.cpu_model?.length > 1) { | ||||
|         return `${summary.value.cpu_model.length}x ${summary.value.cpu_model[0]}`; | ||||
|       } | ||||
|       return summary.value.cpu_model[0]; | ||||
|     }); | ||||
|  | ||||
|     function diskBarColor(percent) { | ||||
|       if (percent < 80) { | ||||
|         return "positive"; | ||||
|         return dash_positive_color.value; | ||||
|       } else if (percent > 80 && percent < 95) { | ||||
|         return "warning"; | ||||
|         return dash_warning_color.value; | ||||
|       } else { | ||||
|         return "negative"; | ||||
|         return dash_negative_color.value; | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @@ -290,6 +339,7 @@ export default { | ||||
|  | ||||
|     async function refreshSummary() { | ||||
|       loading.value = true; | ||||
|       summary.value = await fetchAgent(selectedAgent.value); | ||||
|       try { | ||||
|         const result = await refreshAgentWMI(selectedAgent.value); | ||||
|         await getSummary(); | ||||
| @@ -325,6 +375,12 @@ export default { | ||||
|       loading, | ||||
|       selectedAgent, | ||||
|       disks, | ||||
|       dash_info_color, | ||||
|       dash_positive_color, | ||||
|       dash_warning_color, | ||||
|       dash_negative_color, | ||||
|       serial_number, | ||||
|       cpu, | ||||
|  | ||||
|       // methods | ||||
|       getSummary, | ||||
|   | ||||
| @@ -128,7 +128,7 @@ | ||||
|             <q-icon | ||||
|               v-else-if="props.row.action === 'ignore'" | ||||
|               name="fas fa-check" | ||||
|               color="negative" | ||||
|               :color="dash_negative_color" | ||||
|             > | ||||
|               <q-tooltip>Ignore</q-tooltip> | ||||
|             </q-icon> | ||||
| @@ -144,7 +144,7 @@ | ||||
|             <q-icon | ||||
|               v-if="props.row.installed" | ||||
|               name="fas fa-check" | ||||
|               color="positive" | ||||
|               :color="dash_positive_color" | ||||
|             > | ||||
|               <q-tooltip>Installed</q-tooltip> | ||||
|             </q-icon> | ||||
| @@ -158,11 +158,15 @@ | ||||
|             <q-icon | ||||
|               v-else-if="props.row.action == 'ignore'" | ||||
|               name="fas fa-ban" | ||||
|               color="negative" | ||||
|               :color="dash_negative_color" | ||||
|             > | ||||
|               <q-tooltip>Ignored</q-tooltip> | ||||
|             </q-icon> | ||||
|             <q-icon v-else name="fas fa-exclamation" color="warning"> | ||||
|             <q-icon | ||||
|               v-else | ||||
|               name="fas fa-exclamation" | ||||
|               :color="dash_warning_color" | ||||
|             > | ||||
|               <q-tooltip>Missing</q-tooltip> | ||||
|             </q-icon> | ||||
|           </q-td> | ||||
| @@ -251,6 +255,9 @@ export default { | ||||
|     const tabHeight = computed(() => store.state.tabHeight); | ||||
|     const agentPlatform = computed(() => store.state.agentPlatform); | ||||
|     const formatDate = computed(() => store.getters.formatDate); | ||||
|     const dash_positive_color = computed(() => store.state.dash_positive_color); | ||||
|     const dash_negative_color = computed(() => store.state.dash_negative_color); | ||||
|     const dash_warning_color = computed(() => store.state.dash_warning_color); | ||||
|  | ||||
|     // setup quasar | ||||
|     const $q = useQuasar(); | ||||
| @@ -348,6 +355,9 @@ export default { | ||||
|       selectedAgent, | ||||
|       tabHeight, | ||||
|       agentPlatform, | ||||
|       dash_positive_color, | ||||
|       dash_warning_color, | ||||
|       dash_negative_color, | ||||
|  | ||||
|       // non-reactive data | ||||
|       columns, | ||||
|   | ||||
| @@ -217,6 +217,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapState } from "vuex"; | ||||
| import mixins from "@/mixins/mixins"; | ||||
| import PolicyStatus from "@/components/automation/modals/PolicyStatus.vue"; | ||||
| import DiskSpaceCheck from "@/components/checks/DiskSpaceCheck.vue"; | ||||
| @@ -268,6 +269,9 @@ export default { | ||||
|       if (newValue !== oldValue) this.getChecks(); | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState(["dash_positive_color", "dash_warning_color"]), | ||||
|   }, | ||||
|   methods: { | ||||
|     getChecks() { | ||||
|       this.$q.loading.show(); | ||||
| @@ -295,7 +299,9 @@ export default { | ||||
|  | ||||
|       data.check_alert = true; | ||||
|       const act = !action ? "enabled" : "disabled"; | ||||
|       const color = !action ? "positive" : "warning"; | ||||
|       const color = !action | ||||
|         ? this.dash_positive_color | ||||
|         : this.dash_warning_color; | ||||
|       this.$axios | ||||
|         .put(`/checks/${id}/`, data) | ||||
|         .then(() => { | ||||
|   | ||||
| @@ -41,7 +41,7 @@ | ||||
|               <q-td v-if="props.row.status === 'passing'"> | ||||
|                 <q-icon | ||||
|                   style="font-size: 1.3rem" | ||||
|                   color="positive" | ||||
|                   :color="dash_positive_color" | ||||
|                   name="check_circle" | ||||
|                 > | ||||
|                   <q-tooltip>Passing</q-tooltip> | ||||
| @@ -51,7 +51,7 @@ | ||||
|                 <q-icon | ||||
|                   v-if="props.row.alert_severity === 'info'" | ||||
|                   style="font-size: 1.3rem" | ||||
|                   color="info" | ||||
|                   :color="dash_info_color" | ||||
|                   name="info" | ||||
|                 > | ||||
|                   <q-tooltip>Informational</q-tooltip> | ||||
| @@ -59,7 +59,7 @@ | ||||
|                 <q-icon | ||||
|                   v-else-if="props.row.alert_severity === 'warning'" | ||||
|                   style="font-size: 1.3rem" | ||||
|                   color="warning" | ||||
|                   :color="dash_warning_color" | ||||
|                   name="warning" | ||||
|                 > | ||||
|                   <q-tooltip>Warning</q-tooltip> | ||||
| @@ -67,7 +67,7 @@ | ||||
|                 <q-icon | ||||
|                   v-else | ||||
|                   style="font-size: 1.3rem" | ||||
|                   color="negative" | ||||
|                   :color="dash_negative_color" | ||||
|                   name="error" | ||||
|                 > | ||||
|                   <q-tooltip>Error</q-tooltip> | ||||
| @@ -148,7 +148,7 @@ | ||||
|  | ||||
| <script> | ||||
| import { computed } from "vue"; | ||||
| import { useStore } from "vuex"; | ||||
| import { useStore, mapState } from "vuex"; | ||||
| import ScriptOutput from "@/components/checks/ScriptOutput.vue"; | ||||
| import EventLogCheckOutput from "@/components/checks/EventLogCheckOutput.vue"; | ||||
|  | ||||
| @@ -220,6 +220,12 @@ export default { | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState([ | ||||
|       "dash_info_color", | ||||
|       "dash_positive_color", | ||||
|       "dash_negative_color", | ||||
|       "dash_warning_color", | ||||
|     ]), | ||||
|     title() { | ||||
|       return !!this.item.readable_desc | ||||
|         ? this.item.readable_desc + " Status" | ||||
|   | ||||
| @@ -304,6 +304,9 @@ export default { | ||||
|     // setup vuex | ||||
|     const store = useStore(); | ||||
|     const formatDate = computed(() => store.getters.formatDate); | ||||
|     const dash_positive_color = computed(() => store.state.dash_positive_color); | ||||
|     const dash_negative_color = computed(() => store.state.dash_negative_color); | ||||
|     const dash_warning_color = computed(() => store.state.dash_warning_color); | ||||
|  | ||||
|     // setup dropdowns | ||||
|     const { clientOptions, getClientOptions } = useClientDropdown(); | ||||
| @@ -381,12 +384,18 @@ export default { | ||||
|     } | ||||
|  | ||||
|     function formatActionColor(action) { | ||||
|       if (action === "add") return "success"; | ||||
|       else if (action === "agent_install") return "success"; | ||||
|       else if (action === "modify") return "warning"; | ||||
|       else if (action === "delete") return "negative"; | ||||
|       else if (action === "failed_login") return "negative"; | ||||
|       else return "primary"; | ||||
|       switch (action.toLowerCase()) { | ||||
|         case "modify": | ||||
|           return dash_warning_color.value; | ||||
|         case "add": | ||||
|         case "agent_install": | ||||
|           return dash_positive_color.value; | ||||
|         case "delete": | ||||
|         case "failed_login": | ||||
|           return dash_negative_color.value; | ||||
|         default: | ||||
|           return "primary"; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // watchers | ||||
|   | ||||
| @@ -68,25 +68,25 @@ | ||||
|         /> | ||||
|         <q-radio | ||||
|           v-model="logLevelFilter" | ||||
|           color="cyan" | ||||
|           :color="dash_info_color" | ||||
|           val="info" | ||||
|           label="Info" | ||||
|         /> | ||||
|         <q-radio | ||||
|           v-model="logLevelFilter" | ||||
|           color="red" | ||||
|           :color="dash_negative_color" | ||||
|           val="critical" | ||||
|           label="Critical" | ||||
|         /> | ||||
|         <q-radio | ||||
|           v-model="logLevelFilter" | ||||
|           color="red" | ||||
|           :color="dash_negative_color" | ||||
|           val="error" | ||||
|           label="Error" | ||||
|         /> | ||||
|         <q-radio | ||||
|           v-model="logLevelFilter" | ||||
|           color="yellow" | ||||
|           :color="dash_warning_color" | ||||
|           val="warning" | ||||
|           label="Warning" | ||||
|         /> | ||||
| @@ -109,7 +109,7 @@ | ||||
|       <template v-slot:top-row> | ||||
|         <q-tr v-if="Array.isArray(debugLog) && debugLog.length === 1000"> | ||||
|           <q-td colspan="100%"> | ||||
|             <q-icon name="warning" color="warning" /> | ||||
|             <q-icon name="warning" :color="dash_warning_color" /> | ||||
|             Results are limited to 1000 rows. | ||||
|           </q-td> | ||||
|         </q-tr> | ||||
| @@ -203,6 +203,10 @@ export default { | ||||
|     const store = useStore(); | ||||
|  | ||||
|     const formatDate = computed(() => store.getters.formatDate); | ||||
|     const dash_info_color = computed(() => store.state.dash_info_color); | ||||
|     const dash_positive_color = computed(() => store.state.dash_positive_color); | ||||
|     const dash_negative_color = computed(() => store.state.dash_negative_color); | ||||
|     const dash_warning_color = computed(() => store.state.dash_warning_color); | ||||
|  | ||||
|     // setup dropdowns | ||||
|     const { agentOptions, getAgentOptions } = useAgentDropdown(); | ||||
| @@ -261,6 +265,10 @@ export default { | ||||
|       agentOptions, | ||||
|       loading, | ||||
|       filter, | ||||
|       dash_info_color, | ||||
|       dash_positive_color, | ||||
|       dash_warning_color, | ||||
|       dash_negative_color, | ||||
|  | ||||
|       // non-reactive data | ||||
|       columns, | ||||
|   | ||||
| @@ -94,7 +94,7 @@ | ||||
|                         class="q-pr-sm" | ||||
|                         name="fas fa-signal" | ||||
|                         size="1.2em" | ||||
|                         color="warning" | ||||
|                         :color="dash_warning_color" | ||||
|                       /> | ||||
|                       Mark an agent as | ||||
|                       <span class="text-weight-bold">offline</span> if it has | ||||
| @@ -120,7 +120,7 @@ | ||||
|                         class="q-pr-sm" | ||||
|                         name="fas fa-signal" | ||||
|                         size="1.2em" | ||||
|                         color="negative" | ||||
|                         :color="dash_negative_color" | ||||
|                       /> | ||||
|                       Mark an agent as | ||||
|                       <span class="text-weight-bold">overdue</span> if it has | ||||
| @@ -373,6 +373,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapState } from "vuex"; | ||||
| import { useDialogPluginComponent } from "quasar"; | ||||
| import mixins from "@/mixins/mixins"; | ||||
| import PatchPolicyForm from "@/components/modals/agents/PatchPolicyForm.vue"; | ||||
| @@ -549,6 +550,9 @@ export default { | ||||
|       return result.trimEnd(","); | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState(["dash_warning_color", "dash_negative_color"]), | ||||
|   }, | ||||
|   mounted() { | ||||
|     // Get custom fields | ||||
|     this.getCustomFields("agent").then((r) => { | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
|           <q-tab name="urlactions" label="URL Actions" /> | ||||
|           <q-tab name="retention" label="Retention" /> | ||||
|           <q-tab name="apikeys" label="API Keys" /> | ||||
|           <!-- <q-tab name="openai" label="Open AI" /> --> | ||||
|         </q-tabs> | ||||
|       </template> | ||||
|       <template v-slot:after> | ||||
| @@ -508,6 +509,49 @@ | ||||
|               <q-tab-panel name="apikeys"> | ||||
|                 <APIKeysTable /> | ||||
|               </q-tab-panel> | ||||
|  | ||||
|               <!-- Open AI --> | ||||
|               <!-- <q-tab-panel name="openai"> | ||||
|                 <div class="text-subtitle2">Open AI</div> | ||||
|                 <q-separator /> | ||||
|                 <q-card-section class="row"> | ||||
|                   <div class="col-4">API Key:</div> | ||||
|                   <div class="col-2"></div> | ||||
|                   <q-input | ||||
|                     dense | ||||
|                     outlined | ||||
|                     v-model="settings.open_ai_token" | ||||
|                     class="col-6" | ||||
|                   /> | ||||
|                 </q-card-section> | ||||
|                 <q-card-section class="row"> | ||||
|                   <div class="col-4">Open AI Model:</div> | ||||
|                   <div class="col-2"></div> | ||||
|                   <q-input | ||||
|                     dense | ||||
|                     outlined | ||||
|                     v-model="settings.open_ai_model" | ||||
|                     class="col-6" | ||||
|                   > | ||||
|                     <template v-slot:after> | ||||
|                       <q-btn | ||||
|                         round | ||||
|                         dense | ||||
|                         flat | ||||
|                         icon="info" | ||||
|                         size="sm" | ||||
|                         @click=" | ||||
|                           openURL( | ||||
|                             'https://platform.openai.com/docs/models/overview' | ||||
|                           ) | ||||
|                         " | ||||
|                       > | ||||
|                         <q-tooltip>Click to see available options</q-tooltip> | ||||
|                       </q-btn> | ||||
|                     </template> | ||||
|                   </q-input> | ||||
|                 </q-card-section> | ||||
|               </q-tab-panel> --> | ||||
|             </q-tab-panels> | ||||
|           </q-scroll-area> | ||||
|           <q-card-section class="row items-center"> | ||||
|   | ||||
| @@ -82,6 +82,98 @@ | ||||
|                     class="col-4" | ||||
|                   /> | ||||
|                 </q-card-section> | ||||
|                 <q-card-section class="row"> | ||||
|                   <div class="col-2">Dashboard Info Color:</div> | ||||
|                   <div class="col-2"></div> | ||||
|                   <q-input | ||||
|                     outlined | ||||
|                     dense | ||||
|                     v-model="dash_info_color" | ||||
|                     class="col-8" | ||||
|                   > | ||||
|                     <template v-slot:after> | ||||
|                       <q-btn | ||||
|                         round | ||||
|                         dense | ||||
|                         flat | ||||
|                         size="sm" | ||||
|                         icon="info" | ||||
|                         @click="openURL(quasar_color_url)" | ||||
|                       > | ||||
|                         <q-tooltip>Click to see color options</q-tooltip> | ||||
|                       </q-btn> | ||||
|                     </template> | ||||
|                   </q-input> | ||||
|                 </q-card-section> | ||||
|                 <q-card-section class="row"> | ||||
|                   <div class="col-2">Dashboard Positive Color:</div> | ||||
|                   <div class="col-2"></div> | ||||
|                   <q-input | ||||
|                     outlined | ||||
|                     dense | ||||
|                     v-model="dash_positive_color" | ||||
|                     class="col-8" | ||||
|                   > | ||||
|                     <template v-slot:after> | ||||
|                       <q-btn | ||||
|                         round | ||||
|                         dense | ||||
|                         flat | ||||
|                         size="sm" | ||||
|                         icon="info" | ||||
|                         @click="openURL(quasar_color_url)" | ||||
|                       > | ||||
|                         <q-tooltip>Click to see color options</q-tooltip> | ||||
|                       </q-btn> | ||||
|                     </template> | ||||
|                   </q-input> | ||||
|                 </q-card-section> | ||||
|                 <q-card-section class="row"> | ||||
|                   <div class="col-2">Dashboard Negative Color:</div> | ||||
|                   <div class="col-2"></div> | ||||
|                   <q-input | ||||
|                     outlined | ||||
|                     dense | ||||
|                     v-model="dash_negative_color" | ||||
|                     class="col-8" | ||||
|                   > | ||||
|                     <template v-slot:after> | ||||
|                       <q-btn | ||||
|                         round | ||||
|                         dense | ||||
|                         flat | ||||
|                         size="sm" | ||||
|                         icon="info" | ||||
|                         @click="openURL(quasar_color_url)" | ||||
|                       > | ||||
|                         <q-tooltip>Click to see color options</q-tooltip> | ||||
|                       </q-btn> | ||||
|                     </template> | ||||
|                   </q-input> | ||||
|                 </q-card-section> | ||||
|                 <q-card-section class="row"> | ||||
|                   <div class="col-2">Dashboard Warning Color:</div> | ||||
|                   <div class="col-2"></div> | ||||
|                   <q-input | ||||
|                     outlined | ||||
|                     dense | ||||
|                     v-model="dash_warning_color" | ||||
|                     class="col-8" | ||||
|                   > | ||||
|                     <template v-slot:after> | ||||
|                       <q-btn | ||||
|                         round | ||||
|                         dense | ||||
|                         flat | ||||
|                         size="sm" | ||||
|                         icon="info" | ||||
|                         @click="openURL(quasar_color_url)" | ||||
|                       > | ||||
|                         <q-tooltip>Click to see color options</q-tooltip> | ||||
|                       </q-btn> | ||||
|                     </template> | ||||
|                   </q-input> | ||||
|                 </q-card-section> | ||||
|                 <q-card-section class="row"> | ||||
|                   <div class="col-2">Client Sort:</div> | ||||
|                   <div class="col-2"></div> | ||||
| @@ -156,9 +248,14 @@ export default { | ||||
|       tab: "ui", | ||||
|       splitterModel: 20, | ||||
|       loading_bar_color: "", | ||||
|       dash_info_color: "", | ||||
|       dash_positive_color: "", | ||||
|       dash_negative_color: "", | ||||
|       dash_warning_color: "", | ||||
|       urlActions: [], | ||||
|       clear_search_when_switching: true, | ||||
|       date_format: "", | ||||
|       quasar_color_url: "https://quasar.dev/style/color-palette", | ||||
|       clientTreeSortOptions: [ | ||||
|         { | ||||
|           label: "Sort alphabetically, moving failing clients to the top", | ||||
| @@ -235,6 +332,10 @@ export default { | ||||
|         this.defaultAgentTblTab = r.data.default_agent_tbl_tab; | ||||
|         this.clientTreeSort = r.data.client_tree_sort; | ||||
|         this.loading_bar_color = r.data.loading_bar_color; | ||||
|         this.dash_info_color = r.data.dash_info_color; | ||||
|         this.dash_positive_color = r.data.dash_positive_color; | ||||
|         this.dash_negative_color = r.data.dash_negative_color; | ||||
|         this.dash_warning_color = r.data.dash_warning_color; | ||||
|         this.clear_search_when_switching = r.data.clear_search_when_switching; | ||||
|         this.date_format = r.data.date_format; | ||||
|       }); | ||||
| @@ -253,6 +354,10 @@ export default { | ||||
|         default_agent_tbl_tab: this.defaultAgentTblTab, | ||||
|         client_tree_sort: this.clientTreeSort, | ||||
|         loading_bar_color: this.loading_bar_color, | ||||
|         dash_info_color: this.dash_info_color, | ||||
|         dash_positive_color: this.dash_positive_color, | ||||
|         dash_negative_color: this.dash_negative_color, | ||||
|         dash_warning_color: this.dash_warning_color, | ||||
|         clear_search_when_switching: this.clear_search_when_switching, | ||||
|         date_format: this.date_format, | ||||
|       }; | ||||
|   | ||||
| @@ -11,7 +11,17 @@ | ||||
|       :style="maximized ? '' : 'width: 90vw; max-width: 90vw'" | ||||
|     > | ||||
|       <q-bar> | ||||
|         {{ title }} | ||||
|         <span class="q-pr-sm">{{ title }}</span> | ||||
|         <q-btn | ||||
|           v-if="!script && openAIEnabled" | ||||
|           size="xs" | ||||
|           :disable="loading" | ||||
|           dense | ||||
|           label="Generate Script" | ||||
|           color="primary" | ||||
|           no-caps | ||||
|           @click="generateScriptOpenAI" | ||||
|         /> | ||||
|         <q-space /> | ||||
|         <q-btn | ||||
|           dense | ||||
| @@ -57,116 +67,133 @@ | ||||
|           ><br />Add one to get rid of this warning. Ignore if windows. | ||||
|         </q-banner> | ||||
|         <div class="row q-pa-sm"> | ||||
|           <div class="col-4 q-gutter-sm q-pr-sm"> | ||||
|             <q-input | ||||
|               filled | ||||
|               dense | ||||
|               :readonly="readonly" | ||||
|               v-model="formScript.name" | ||||
|               label="Name" | ||||
|               :rules="[(val) => !!val || '*Required']" | ||||
|               hide-bottom-space | ||||
|             /> | ||||
|             <q-input | ||||
|               filled | ||||
|               dense | ||||
|               :readonly="readonly" | ||||
|               v-model="formScript.description" | ||||
|               label="Description" | ||||
|             /> | ||||
|             <q-select | ||||
|               :readonly="readonly" | ||||
|               options-dense | ||||
|               filled | ||||
|               dense | ||||
|               v-model="formScript.shell" | ||||
|               :options="shellOptions" | ||||
|               emit-value | ||||
|               map-options | ||||
|               label="Shell Type" | ||||
|             /> | ||||
|             <tactical-dropdown | ||||
|               v-model="formScript.supported_platforms" | ||||
|               :options="agentPlatformOptions" | ||||
|               label="Supported Platforms (All supported if blank)" | ||||
|               clearable | ||||
|               mapOptions | ||||
|               filled | ||||
|               multiple | ||||
|               :readonly="readonly" | ||||
|             /> | ||||
|             <tactical-dropdown | ||||
|               filled | ||||
|               v-model="formScript.category" | ||||
|               :options="categories" | ||||
|               use-input | ||||
|               clearable | ||||
|               new-value-mode="add-unique" | ||||
|               filterable | ||||
|               label="Category" | ||||
|               :readonly="readonly" | ||||
|               hide-bottom-space | ||||
|             /> | ||||
|             <tactical-dropdown | ||||
|               v-model="formScript.args" | ||||
|               label="Script Arguments (press Enter after typing each argument)" | ||||
|               filled | ||||
|               use-input | ||||
|               multiple | ||||
|               hide-dropdown-icon | ||||
|               input-debounce="0" | ||||
|               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 | ||||
|               dense | ||||
|               :readonly="readonly" | ||||
|               v-model.number="formScript.default_timeout" | ||||
|               label="Timeout (seconds)" | ||||
|               :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" | ||||
|               style="height: 150px; overflow-y: auto; resize: none" | ||||
|               v-model="formScript.syntax" | ||||
|               dense | ||||
|               filled | ||||
|               :readonly="readonly" | ||||
|             /> | ||||
|           </div> | ||||
|           <q-scroll-area | ||||
|             :thumb-style="{ | ||||
|               right: '4px', | ||||
|               borderRadius: '5px', | ||||
|               width: '5px', | ||||
|               opacity: 0.75, | ||||
|             }" | ||||
|             :bar-style="{ | ||||
|               right: '2px', | ||||
|               borderRadius: '9px', | ||||
|               width: '9px', | ||||
|               opacity: 0.2, | ||||
|             }" | ||||
|             class="col-4 q-mb-none q-pb-none" | ||||
|             :style="{ height: `${maximized ? '82vh' : '64vh'}` }" | ||||
|           > | ||||
|             <div class="q-gutter-sm q-pr-sm"> | ||||
|               <q-input | ||||
|                 filled | ||||
|                 dense | ||||
|                 :readonly="readonly" | ||||
|                 v-model="formScript.name" | ||||
|                 label="Name" | ||||
|                 :rules="[(val) => !!val || '*Required']" | ||||
|                 hide-bottom-space | ||||
|               /> | ||||
|               <q-input | ||||
|                 filled | ||||
|                 dense | ||||
|                 :readonly="readonly" | ||||
|                 v-model="formScript.description" | ||||
|                 label="Description" | ||||
|               /> | ||||
|               <q-select | ||||
|                 :readonly="readonly" | ||||
|                 options-dense | ||||
|                 filled | ||||
|                 dense | ||||
|                 v-model="formScript.shell" | ||||
|                 :options="shellOptions" | ||||
|                 emit-value | ||||
|                 map-options | ||||
|                 label="Shell Type" | ||||
|               /> | ||||
|               <tactical-dropdown | ||||
|                 v-model="formScript.supported_platforms" | ||||
|                 :options="agentPlatformOptions" | ||||
|                 label="Supported Platforms (All supported if blank)" | ||||
|                 clearable | ||||
|                 mapOptions | ||||
|                 filled | ||||
|                 multiple | ||||
|                 :readonly="readonly" | ||||
|               /> | ||||
|               <tactical-dropdown | ||||
|                 filled | ||||
|                 v-model="formScript.category" | ||||
|                 :options="categories" | ||||
|                 use-input | ||||
|                 clearable | ||||
|                 new-value-mode="add-unique" | ||||
|                 filterable | ||||
|                 label="Category" | ||||
|                 :readonly="readonly" | ||||
|                 hide-bottom-space | ||||
|               /> | ||||
|               <tactical-dropdown | ||||
|                 v-model="formScript.args" | ||||
|                 label="Script Arguments (press Enter after typing each argument)" | ||||
|                 filled | ||||
|                 use-input | ||||
|                 multiple | ||||
|                 hide-dropdown-icon | ||||
|                 input-debounce="0" | ||||
|                 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 | ||||
|                 dense | ||||
|                 :readonly="readonly" | ||||
|                 v-model.number="formScript.default_timeout" | ||||
|                 label="Timeout (seconds)" | ||||
|                 :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" | ||||
|                 style="height: 150px; overflow-y: auto; resize: none" | ||||
|                 v-model="formScript.syntax" | ||||
|                 dense | ||||
|                 filled | ||||
|                 :readonly="readonly" | ||||
|               /> | ||||
|             </div> | ||||
|           </q-scroll-area> | ||||
|           <v-ace-editor | ||||
|             v-model:value="formScript.script_body" | ||||
|             class="col-8" | ||||
|             :lang="lang" | ||||
|             :theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'" | ||||
|             :style="{ height: `${maximized ? '87vh' : '64vh'}` }" | ||||
|             :style="{ height: `${maximized ? '82vh' : '64vh'}` }" | ||||
|             wrap | ||||
|             :printMargin="false" | ||||
|             :options="{ fontSize: '14px' }" | ||||
| @@ -220,9 +247,11 @@ | ||||
| <script> | ||||
| // composable imports | ||||
| import { ref, computed, onMounted } from "vue"; | ||||
| import { useStore } from "vuex"; | ||||
| import { useQuasar, useDialogPluginComponent } from "quasar"; | ||||
| import { saveScript, editScript, downloadScript } from "@/api/scripts"; | ||||
| import { useAgentDropdown, agentPlatformOptions } from "@/composables/agents"; | ||||
| import { generateScript } from "@/api/core"; | ||||
| import { notifySuccess } from "@/utils/notify"; | ||||
|  | ||||
| // ui imports | ||||
| @@ -266,6 +295,10 @@ export default { | ||||
|     const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); | ||||
|     const $q = useQuasar(); | ||||
|  | ||||
|     // setup store | ||||
|     const store = useStore(); | ||||
|     const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled); | ||||
|  | ||||
|     // setup agent dropdown | ||||
|     const { agent, agentOptions, getAgentOptions } = useAgentDropdown(); | ||||
|  | ||||
| @@ -355,6 +388,23 @@ export default { | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     function generateScriptOpenAI() { | ||||
|       $q.dialog({ | ||||
|         title: "Ask ChatGPT what you need!", | ||||
|         prompt: { | ||||
|           model: `${lang.value} code that `, | ||||
|           type: "text", | ||||
|         }, | ||||
|         cancel: true, | ||||
|         persistent: true, | ||||
|       }).onOk(async (data) => { | ||||
|         const completion = await generateScript({ | ||||
|           prompt: data, | ||||
|         }); | ||||
|         script.value.script_body = completion; | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // component life cycle hooks | ||||
|     onMounted(async () => { | ||||
|       agentLoading.value = true; | ||||
| @@ -380,10 +430,12 @@ export default { | ||||
|  | ||||
|       //computed | ||||
|       title, | ||||
|       openAIEnabled, | ||||
|  | ||||
|       //methods | ||||
|       submitForm, | ||||
|       openTestScriptModal, | ||||
|       generateScriptOpenAI, | ||||
|  | ||||
|       // quasar dialog plugin | ||||
|       dialogRef, | ||||
|   | ||||
| @@ -11,7 +11,17 @@ | ||||
|       :style="maximized ? '' : 'width: 70vw; max-width: 90vw'" | ||||
|     > | ||||
|       <q-bar> | ||||
|         {{ title }} | ||||
|         <span class="q-pr-sm">{{ title }}</span> | ||||
|         <q-btn | ||||
|           v-if="!snippet && openAIEnabled" | ||||
|           :disable="loading" | ||||
|           dense | ||||
|           size="xs" | ||||
|           label="Generate Script" | ||||
|           color="primary" | ||||
|           no-caps | ||||
|           @click="generateScriptOpenAI" | ||||
|         /> | ||||
|         <q-space /> | ||||
|         <q-btn | ||||
|           dense | ||||
| @@ -97,6 +107,9 @@ | ||||
| <script> | ||||
| // composable imports | ||||
| import { ref, computed } from "vue"; | ||||
| import { useStore } from "vuex"; | ||||
| import { useQuasar } from "quasar"; | ||||
| import { generateScript } from "@/api/core"; | ||||
| import { useDialogPluginComponent } from "quasar"; | ||||
| import { saveScriptSnippet, editScriptSnippet } from "@/api/scripts"; | ||||
| import { notifySuccess } from "@/utils/notify"; | ||||
| @@ -128,6 +141,13 @@ export default { | ||||
|     // setup quasar plugins | ||||
|     const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); | ||||
|  | ||||
|     // setup quasar | ||||
|     const $q = useQuasar(); | ||||
|  | ||||
|     // setup store | ||||
|     const store = useStore(); | ||||
|     const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled); | ||||
|  | ||||
|     // snippet form logic | ||||
|     const snippet = props.snippet | ||||
|       ? ref(Object.assign({}, props.snippet)) | ||||
| @@ -167,6 +187,23 @@ export default { | ||||
|       loading.value = false; | ||||
|     } | ||||
|  | ||||
|     function generateScriptOpenAI() { | ||||
|       $q.dialog({ | ||||
|         title: "Ask ChatGPT what you need!", | ||||
|         prompt: { | ||||
|           model: `${lang.value} code that `, | ||||
|           type: "text", | ||||
|         }, | ||||
|         cancel: true, | ||||
|         persistent: true, | ||||
|       }).onOk(async (data) => { | ||||
|         const completion = await generateScript({ | ||||
|           prompt: data, | ||||
|         }); | ||||
|         snippet.value.code = completion; | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|       // reactive data | ||||
|       formSnippet: snippet.value, | ||||
| @@ -179,9 +216,11 @@ export default { | ||||
|  | ||||
|       //computed | ||||
|       title, | ||||
|       openAIEnabled, | ||||
|  | ||||
|       //methods | ||||
|       submitForm, | ||||
|       generateScriptOpenAI, | ||||
|  | ||||
|       // quasar dialog plugin | ||||
|       dialogRef, | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { ref } from "vue"; | ||||
| import { computed, ref } from "vue"; | ||||
| import { useStore } from "vuex"; | ||||
| import { fetchAgents } from "@/api/agents"; | ||||
| import { formatAgentOptions } from "@/utils/format"; | ||||
|  | ||||
| @@ -28,10 +29,12 @@ export function useAgentDropdown() { | ||||
| } | ||||
|  | ||||
| export function cmdPlaceholder(shell) { | ||||
|   if (shell === "cmd") return "rmdir /S /Q C:\\Windows\\System32"; | ||||
|   else if (shell === "powershell") | ||||
|     return "Remove-Item -Recurse -Force C:\\Windows\\System32"; | ||||
|   else return "rm -rf --no-preserve-root /"; | ||||
|   const store = useStore(); | ||||
|   const placeholders = computed(() => store.state.run_cmd_placeholder_text); | ||||
|  | ||||
|   if (shell === "cmd") return placeholders.value.cmd; | ||||
|   else if (shell === "powershell") return placeholders.value.powershell; | ||||
|   else return placeholders.value.shell; | ||||
| } | ||||
|  | ||||
| export const agentPlatformOptions = [ | ||||
|   | ||||
| @@ -56,15 +56,27 @@ | ||||
|           Tactical RMM<span class="text-overline q-ml-sm" | ||||
|             >v{{ currentTRMMVersion }}</span | ||||
|           > | ||||
|           <span class="text-overline q-ml-md" v-if="updateAvailable()" | ||||
|             ><q-badge color="warning" | ||||
|               ><a :href="latestReleaseURL" target="_blank" | ||||
|                 >v{{ latestTRMMVersion }} available</a | ||||
|               ></q-badge | ||||
|             ></span | ||||
|           <!-- update check --> | ||||
|           <q-chip | ||||
|             v-if="updateAvailable" | ||||
|             class="text-overline q-ml-sm" | ||||
|             :color="dash_warning_color" | ||||
|             icon="update" | ||||
|             dense | ||||
|             ><a :href="latestReleaseURL" target="_blank" | ||||
|               >v{{ latestTRMMVersion }} available</a | ||||
|             ></q-chip | ||||
|           > | ||||
|           <!-- cert expiring soon check --> | ||||
|           <q-chip | ||||
|             v-if="daysUntilCertExpires <= 15" | ||||
|             dense | ||||
|             :color="dash_negative_color" | ||||
|             text-color="black" | ||||
|             icon="warning" | ||||
|             >Certificate expires in {{ daysUntilCertExpires }} days</q-chip | ||||
|           > | ||||
|         </q-toolbar-title> | ||||
|  | ||||
|         <!-- temp dark mode toggle --> | ||||
|         <q-toggle | ||||
|           v-model="darkMode" | ||||
| @@ -94,7 +106,11 @@ | ||||
|               </q-item> | ||||
|               <q-item> | ||||
|                 <q-item-section avatar> | ||||
|                   <q-icon name="power_off" size="sm" color="negative" /> | ||||
|                   <q-icon | ||||
|                     name="power_off" | ||||
|                     size="sm" | ||||
|                     :color="dash_negative_color" | ||||
|                   /> | ||||
|                 </q-item-section> | ||||
|  | ||||
|                 <q-item-section no-wrap> | ||||
| @@ -113,7 +129,11 @@ | ||||
|               </q-item> | ||||
|               <q-item> | ||||
|                 <q-item-section avatar> | ||||
|                   <q-icon name="power_off" size="sm" color="negative" /> | ||||
|                   <q-icon | ||||
|                     name="power_off" | ||||
|                     size="sm" | ||||
|                     :color="dash_negative_color" | ||||
|                   /> | ||||
|                 </q-item-section> | ||||
|  | ||||
|                 <q-item-section no-wrap> | ||||
| @@ -218,6 +238,8 @@ export default { | ||||
|     const user = computed(() => store.state.username); | ||||
|     const hosted = computed(() => store.state.hosted); | ||||
|     const tokenExpired = computed(() => store.state.tokenExpired); | ||||
|     const dash_warning_color = computed(() => store.state.dash_warning_color); | ||||
|     const dash_negative_color = computed(() => store.state.dash_negative_color); | ||||
|  | ||||
|     const latestReleaseURL = computed(() => { | ||||
|       return latestTRMMVersion.value | ||||
| @@ -255,6 +277,7 @@ export default { | ||||
|     const serverOfflineCount = ref(0); | ||||
|     const workstationCount = ref(0); | ||||
|     const workstationOfflineCount = ref(0); | ||||
|     const daysUntilCertExpires = ref(100); | ||||
|  | ||||
|     const ws = ref(null); | ||||
|  | ||||
| @@ -262,6 +285,13 @@ export default { | ||||
|       // 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); | ||||
| @@ -274,6 +304,7 @@ export default { | ||||
|         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 { | ||||
| @@ -297,13 +328,18 @@ export default { | ||||
|       poll.value = setInterval(() => { | ||||
|         store.dispatch("checkVer"); | ||||
|         store.dispatch("getDashInfo", false); | ||||
|       }, 60 * 5 * 1000); | ||||
|       }, 60 * 4 * 1000); | ||||
|     } | ||||
|  | ||||
|     function updateAvailable() { | ||||
|       if (latestTRMMVersion.value === "error" || hosted.value) return false; | ||||
|     const updateAvailable = computed(() => { | ||||
|       if ( | ||||
|         latestTRMMVersion.value === "error" || | ||||
|         hosted.value || | ||||
|         currentTRMMVersion.value?.includes("-dev") | ||||
|       ) | ||||
|         return false; | ||||
|       return currentTRMMVersion.value !== latestTRMMVersion.value; | ||||
|     } | ||||
|     }); | ||||
|  | ||||
|     onMounted(() => { | ||||
|       setupWS(); | ||||
| @@ -324,6 +360,7 @@ export default { | ||||
|       serverOfflineCount, | ||||
|       workstationCount, | ||||
|       workstationOfflineCount, | ||||
|       daysUntilCertExpires, | ||||
|       latestReleaseURL, | ||||
|       currentTRMMVersion, | ||||
|       latestTRMMVersion, | ||||
| @@ -332,6 +369,8 @@ export default { | ||||
|       darkMode, | ||||
|       hosted, | ||||
|       tokenExpired, | ||||
|       dash_warning_color, | ||||
|       dash_negative_color, | ||||
|  | ||||
|       // methods | ||||
|       showUserPreferences, | ||||
|   | ||||
| @@ -33,6 +33,16 @@ export default function () { | ||||
|         currentTRMMVersion: null, | ||||
|         latestTRMMVersion: null, | ||||
|         dateFormat: "MMM-DD-YYYY - HH:mm", | ||||
|         openAIIntegrationEnabled: false, | ||||
|         dash_info_color: "info", | ||||
|         dash_positive_color: "positive", | ||||
|         dash_negative_color: "negative", | ||||
|         dash_warning_color: "warning", | ||||
|         run_cmd_placeholder_text: { | ||||
|           cmd: "rmdir /S /Q C:\\Windows\\System32", | ||||
|           powershell: "Remove-Item -Recurse -Force C:\\Windows\\System32", | ||||
|           shell: "rm -rf --no-preserve-root /", | ||||
|         }, | ||||
|       }; | ||||
|     }, | ||||
|     getters: { | ||||
| @@ -136,6 +146,24 @@ export default function () { | ||||
|       setDateFormat(state, val) { | ||||
|         state.dateFormat = val; | ||||
|       }, | ||||
|       setOpenAIIntegrationStatus(state, val) { | ||||
|         state.openAIIntegrationEnabled = val; | ||||
|       }, | ||||
|       setDashInfoColor(state, val) { | ||||
|         state.dash_info_color = val; | ||||
|       }, | ||||
|       setDashPositiveColor(state, val) { | ||||
|         state.dash_positive_color = val; | ||||
|       }, | ||||
|       setDashNegativeColor(state, val) { | ||||
|         state.dash_negative_color = val; | ||||
|       }, | ||||
|       setDashWarningColor(state, val) { | ||||
|         state.dash_warning_color = val; | ||||
|       }, | ||||
|       setRunCmdPlaceholders(state, obj) { | ||||
|         state.run_cmd_placeholder_text = obj; | ||||
|       }, | ||||
|     }, | ||||
|     actions: { | ||||
|       setClientTreeSplitter(context, val) { | ||||
| @@ -160,9 +188,9 @@ export default function () { | ||||
|         } | ||||
|         if (clearTreeSelected) commit("destroySubTable"); | ||||
|  | ||||
|         dispatch("getDashInfo", false); | ||||
|         dispatch("loadAgents"); | ||||
|         dispatch("loadTree"); | ||||
|         dispatch("getDashInfo", false); | ||||
|       }, | ||||
|       async loadAgents({ state, commit }) { | ||||
|         commit("AGENT_TABLE_LOADING", true); | ||||
| @@ -194,107 +222,111 @@ export default function () { | ||||
|  | ||||
|         commit("AGENT_TABLE_LOADING", false); | ||||
|       }, | ||||
|       async getDashInfo(context, edited = true) { | ||||
|       async getDashInfo({ commit }, edited = true) { | ||||
|         const { data } = await axios.get("/core/dashinfo/"); | ||||
|         commit("setDashInfoColor", data.dash_info_color); | ||||
|         commit("setDashPositiveColor", data.dash_positive_color); | ||||
|         commit("setDashNegativeColor", data.dash_negative_color); | ||||
|         commit("setDashWarningColor", data.dash_warning_color); | ||||
|         if (edited) { | ||||
|           LoadingBar.setDefaults({ color: data.loading_bar_color }); | ||||
|           context.commit( | ||||
|           commit( | ||||
|             "setClearSearchWhenSwitching", | ||||
|             data.clear_search_when_switching | ||||
|           ); | ||||
|           context.commit( | ||||
|             "SET_DEFAULT_AGENT_TBL_TAB", | ||||
|             data.default_agent_tbl_tab | ||||
|           ); | ||||
|           context.commit("SET_CLIENT_TREE_SORT", data.client_tree_sort); | ||||
|           context.commit("SET_CLIENT_SPLITTER", data.client_tree_splitter); | ||||
|           commit("SET_DEFAULT_AGENT_TBL_TAB", data.default_agent_tbl_tab); | ||||
|           commit("SET_CLIENT_TREE_SORT", data.client_tree_sort); | ||||
|           commit("SET_CLIENT_SPLITTER", data.client_tree_splitter); | ||||
|         } | ||||
|         Dark.set(data.dark_mode); | ||||
|         context.commit("setCurrentTRMMVersion", data.trmm_version); | ||||
|         context.commit("setLatestTRMMVersion", data.latest_trmm_ver); | ||||
|         context.commit("SET_AGENT_DBLCLICK_ACTION", data.dbl_click_action); | ||||
|         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); | ||||
|         commit("setCurrentTRMMVersion", data.trmm_version); | ||||
|         commit("setLatestTRMMVersion", data.latest_trmm_ver); | ||||
|         commit("SET_AGENT_DBLCLICK_ACTION", data.dbl_click_action); | ||||
|         commit("SET_URL_ACTION", data.url_action); | ||||
|         commit("setShowCommunityScripts", data.show_community_scripts); | ||||
|         commit("SET_HOSTED", data.hosted); | ||||
|         commit("SET_TOKEN_EXPIRED", data.token_is_expired); | ||||
|         commit("setOpenAIIntegrationStatus", data.open_ai_integration_enabled); | ||||
|         commit("setRunCmdPlaceholders", data.run_cmd_placeholder_text); | ||||
|  | ||||
|         if (data.date_format && data.date_format !== "") | ||||
|           context.commit("setDateFormat", data.date_format); | ||||
|         else context.commit("setDateFormat", data.default_date_format); | ||||
|         if (data?.date_format !== "") commit("setDateFormat", data.date_format); | ||||
|         else commit("setDateFormat", data.default_date_format); | ||||
|       }, | ||||
|       loadTree({ commit, state }) { | ||||
|         axios | ||||
|           .get("/clients/") | ||||
|           .then((r) => { | ||||
|             if (r.data.length === 0) { | ||||
|               this.$router.push({ name: "InitialSetup" }); | ||||
|             } | ||||
|         setTimeout(() => { | ||||
|           axios | ||||
|             .get("/clients/") | ||||
|             .then((r) => { | ||||
|               if (r.data.length === 0) { | ||||
|                 this.$router.push({ name: "InitialSetup" }); | ||||
|               } | ||||
|  | ||||
|             let output = []; | ||||
|             for (let client of r.data) { | ||||
|               let childSites = []; | ||||
|               for (let site of client.sites) { | ||||
|                 let siteNode = { | ||||
|                   label: site.name, | ||||
|                   id: site.id, | ||||
|                   raw: `Site|${site.id}`, | ||||
|                   header: "generic", | ||||
|                   icon: "apartment", | ||||
|                   selectable: true, | ||||
|                   site: site, | ||||
|                 }; | ||||
|               let output = []; | ||||
|               for (let client of r.data) { | ||||
|                 let childSites = []; | ||||
|                 for (let site of client.sites) { | ||||
|                   let siteNode = { | ||||
|                     label: site.name, | ||||
|                     id: site.id, | ||||
|                     raw: `Site|${site.id}`, | ||||
|                     header: "generic", | ||||
|                     icon: "apartment", | ||||
|                     selectable: true, | ||||
|                     site: site, | ||||
|                   }; | ||||
|  | ||||
|                 if (site.maintenance_mode) { | ||||
|                   siteNode["color"] = "green"; | ||||
|                 } else if (site.failing_checks.error) { | ||||
|                   siteNode["color"] = "negative"; | ||||
|                 } else if (site.failing_checks.warning) { | ||||
|                   siteNode["color"] = "warning"; | ||||
|                   if (site.maintenance_mode) { | ||||
|                     siteNode["color"] = "green"; | ||||
|                   } else if (site.failing_checks.error) { | ||||
|                     siteNode["color"] = "negative"; | ||||
|                   } else if (site.failing_checks.warning) { | ||||
|                     siteNode["color"] = "warning"; | ||||
|                   } | ||||
|  | ||||
|                   childSites.push(siteNode); | ||||
|                 } | ||||
|  | ||||
|                 childSites.push(siteNode); | ||||
|                 let clientNode = { | ||||
|                   label: client.name, | ||||
|                   id: client.id, | ||||
|                   raw: `Client|${client.id}`, | ||||
|                   header: "root", | ||||
|                   icon: "business", | ||||
|                   children: childSites, | ||||
|                   client: client, | ||||
|                 }; | ||||
|  | ||||
|                 if (client.maintenance_mode) clientNode["color"] = "green"; | ||||
|                 else if (client.failing_checks.error) { | ||||
|                   clientNode["color"] = "negative"; | ||||
|                 } else if (client.failing_checks.warning) { | ||||
|                   clientNode["color"] = "warning"; | ||||
|                 } | ||||
|  | ||||
|                 output.push(clientNode); | ||||
|               } | ||||
|  | ||||
|               let clientNode = { | ||||
|                 label: client.name, | ||||
|                 id: client.id, | ||||
|                 raw: `Client|${client.id}`, | ||||
|                 header: "root", | ||||
|                 icon: "business", | ||||
|                 children: childSites, | ||||
|                 client: client, | ||||
|               }; | ||||
|  | ||||
|               if (client.maintenance_mode) clientNode["color"] = "green"; | ||||
|               else if (client.failing_checks.error) { | ||||
|                 clientNode["color"] = "negative"; | ||||
|               } else if (client.failing_checks.warning) { | ||||
|                 clientNode["color"] = "warning"; | ||||
|               const sorted = output.sort((a, b) => | ||||
|                 a.label.localeCompare(b.label) | ||||
|               ); | ||||
|               if (state.clientTreeSort === "alphafail") { | ||||
|                 // move failing clients to the top | ||||
|                 const failing = sorted.filter( | ||||
|                   (i) => i.color === "negative" || i.color === "warning" | ||||
|                 ); | ||||
|                 const ok = sorted.filter( | ||||
|                   (i) => i.color !== "negative" && i.color !== "warning" | ||||
|                 ); | ||||
|                 const sortedByFailing = [...failing, ...ok]; | ||||
|                 commit("loadTree", sortedByFailing); | ||||
|               } else { | ||||
|                 commit("loadTree", sorted); | ||||
|               } | ||||
|  | ||||
|               output.push(clientNode); | ||||
|             } | ||||
|  | ||||
|             const sorted = output.sort((a, b) => | ||||
|               a.label.localeCompare(b.label) | ||||
|             ); | ||||
|             if (state.clientTreeSort === "alphafail") { | ||||
|               // move failing clients to the top | ||||
|               const failing = sorted.filter( | ||||
|                 (i) => i.color === "negative" || i.color === "warning" | ||||
|               ); | ||||
|               const ok = sorted.filter( | ||||
|                 (i) => i.color !== "negative" && i.color !== "warning" | ||||
|               ); | ||||
|               const sortedByFailing = [...failing, ...ok]; | ||||
|               commit("loadTree", sortedByFailing); | ||||
|             } else { | ||||
|               commit("loadTree", sorted); | ||||
|             } | ||||
|           }) | ||||
|           .catch(() => { | ||||
|             state.treeReady = true; | ||||
|           }); | ||||
|             }) | ||||
|             .catch(() => { | ||||
|               state.treeReady = true; | ||||
|             }); | ||||
|         }, 150); | ||||
|       }, | ||||
|       checkVer(context) { | ||||
|         axios.get("/core/version/").then((r) => { | ||||
|   | ||||
| @@ -452,7 +452,7 @@ export default { | ||||
|       showInstallAgentModal: false, | ||||
|       sitePk: null, | ||||
|       innerModel: (this.$q.screen.height - 82) / 2, | ||||
|       search: (this.$route.query.search ? this.$route.query.search : ""), | ||||
|       search: this.$route.query.search ? this.$route.query.search : "", | ||||
|       filterTextLength: 0, | ||||
|       filterAvailability: "all", | ||||
|       filterPatchesPending: false, | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
|         @click="restartMeshService" | ||||
|       /> | ||||
|       <q-btn | ||||
|         color="negative" | ||||
|         :color="dash_negative_color" | ||||
|         size="sm" | ||||
|         label="Recover Connection" | ||||
|         icon="fas fa-first-aid" | ||||
| @@ -35,6 +35,7 @@ | ||||
| <script> | ||||
| // composition imports | ||||
| import { ref, computed, onMounted } from "vue"; | ||||
| import { useStore } from "vuex"; | ||||
| import { useRoute } from "vue-router"; | ||||
| import { useMeta, useQuasar } from "quasar"; | ||||
| import { fetchAgentMeshCentralURLs, sendAgentRecoverMesh } from "@/api/agents"; | ||||
| @@ -47,12 +48,17 @@ export default { | ||||
|   setup() { | ||||
|     // vue lifecycle hooks | ||||
|     onMounted(() => { | ||||
|       dashInfo(); | ||||
|       getDashInfo(); | ||||
|       getMeshURLs(); | ||||
|     }); | ||||
|  | ||||
|     // quasar setup | ||||
|     const $q = useQuasar(); | ||||
|     const store = useStore(); | ||||
|     const dash_positive_color = computed(() => store.state.dash_positive_color); | ||||
|     const dash_negative_color = computed(() => store.state.dash_negative_color); | ||||
|     const dash_warning_color = computed(() => store.state.dash_warning_color); | ||||
|  | ||||
|     // vue router | ||||
|     const { params } = useRoute(); | ||||
| @@ -64,14 +70,19 @@ export default { | ||||
|     const statusColor = computed(() => { | ||||
|       switch (status.value) { | ||||
|         case "online": | ||||
|           return "positive"; | ||||
|           return dash_positive_color.value; | ||||
|         case "offline": | ||||
|           return "warning"; | ||||
|           return dash_warning_color.value; | ||||
|         default: | ||||
|           return "negative"; | ||||
|           return dash_negative_color.value; | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // TODO refactor this so we're not calling the api twice | ||||
|     const dashInfo = () => { | ||||
|       store.dispatch("getDashInfo", false); | ||||
|     }; | ||||
|  | ||||
|     async function getMeshURLs() { | ||||
|       $q.loading.show(); | ||||
|       try { | ||||
| @@ -131,6 +142,7 @@ export default { | ||||
|       control, | ||||
|       status, | ||||
|       statusColor, | ||||
|       dash_negative_color, | ||||
|  | ||||
|       // methods | ||||
|       repairMeshCentral, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user