Compare commits

..

30 Commits

Author SHA1 Message Date
wh1te909
8c6ac164ba bump dev ver 2023-04-07 22:46:24 +00:00
wh1te909
dc68b16ff2 format and align the icon 2023-04-07 21:52:53 +00:00
Dan
a4f15fd05a Merge pull request #6 from jpros/custom-fields-in-summary
Add custom fields to summary view
2023-04-07 13:51:50 -07:00
wh1te909
176675abd8 update reqs 2023-04-07 20:50:21 +00:00
João Paulo Ros
73dc278ac4 Hide custom fields with no value 2023-04-04 20:36:53 -07:00
wh1te909
d6b443296b update reqs 2023-04-04 06:34:11 +00:00
Dan
f3c718d29c Merge pull request #7 from jpros/custom-fields-search
Add custom fields to search in the dashboard
2023-04-03 22:46:20 -07:00
João Paulo Ros
5955af08c7 Hide custom fields that are not supposed to appear in the UI. 2023-03-31 15:22:10 -07:00
João Paulo Ros
dec1ccc98a Add Search from query parameter 2023-03-30 15:40:47 -07:00
João Paulo Ros
a78780b837 Add custom fields to summary view 2023-03-30 14:38:55 -07:00
João Paulo Ros
beff8eb10e Add custom fields to search in dashboard 2023-03-30 12:37:26 -07:00
wh1te909
c2f21b70dd bump version 2023-03-22 16:59:35 +00:00
wh1te909
520145e0e3 bump dev ver 2023-03-21 18:49:28 +00:00
wh1te909
6a132187a2 fix phantom column fixes amidaware/tacticalrmm#1264 2023-03-20 14:08:58 +00:00
wh1te909
a63a9ccd76 update reqs 2023-03-20 01:28:54 +00:00
wh1te909
ff1eb791db feat: increase size of notes text box closes amidaware/tacticalrmm#1407 2023-03-10 05:31:50 +00:00
wh1te909
13bd88b979 feat: bulk run checks by client or site amidaware/tacticalrmm@7d017f9494 2023-03-10 00:22:12 +00:00
wh1te909
5b0c244920 update reqs 2023-03-10 00:20:51 +00:00
wh1te909
0318a17cac update reqs 2023-03-05 20:49:03 +00:00
wh1te909
75296ed8ee bump version 2023-01-18 20:04:26 +00:00
wh1te909
09bee45b2f update reqs and dev version 2023-01-16 23:03:30 +00:00
wh1te909
3573c48872 formatting 2023-01-16 08:38:34 +00:00
wh1te909
784841c221 syntax typo 2023-01-16 08:37:15 +00:00
wh1te909
ed788a1861 update reqs 2023-01-16 08:35:07 +00:00
wh1te909
bd6b08505a bump version 2022-12-21 18:44:21 +00:00
wh1te909
acd64f25f2 add ui for self reset amidaware/tacticalrmm#1378 2022-12-20 23:38:19 +00:00
wh1te909
087be2c232 update reqs 2022-12-20 23:35:44 +00:00
wh1te909
91a3272843 format 2022-12-20 23:35:32 +00:00
wh1te909
6e64f0a11b formatting 2022-12-20 21:02:55 +00:00
wh1te909
8f34f76a1d fix edit apikey amidaware/tacticalrmm#1369 2022-12-08 23:27:26 +00:00
21 changed files with 2648 additions and 4112 deletions

View File

@@ -5,7 +5,7 @@
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"editorconfig.editorconfig", "editorconfig.editorconfig",
"vue.volar", "vue.volar",
"wayou.vscode-todo-highlight", "wayou.vscode-todo-highlight"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": [
"octref.vetur", "octref.vetur",

View File

@@ -4,7 +4,7 @@
"editor.formatOnSave": true, "editor.formatOnSave": true,
"[vue][javascript][typescript][javascriptreact]": { "[vue][javascript][typescript][javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"], "editor.codeActionsOnSave": ["source.fixAll.eslint"]
}, },
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"], "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
@@ -15,7 +15,7 @@
"**/node_modules/": true, "**/node_modules/": true,
"/node_modules/**": true, "/node_modules/**": true,
"**/env/": true, "**/env/": true,
"/env/**": true, "/env/**": true
} }
} }
} }

View File

@@ -1,24 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<head> <title><%= productName %></title>
<title>
<%= productName %>
</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<meta name="description" content="<%= productDescription %>" /> <meta name="description" content="<%= productDescription %>" />
<meta name="format-detection" content="telephone=no" /> <meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" /> <meta name="msapplication-tap-highlight" content="no" />
<meta name="viewport" <meta
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>" /> name="viewport"
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>"
/>
<link rel="icon" type="image/ico" href="favicon.ico" /> <link rel="icon" type="image/ico" href="favicon.ico" />
<script src="/env-config.js"></script> <script src="/env-config.js"></script>
</head> </head>
<body> <body>
<!-- quasar:entry-point --> <!-- quasar:entry-point -->
</body> </body>
</html> </html>

6308
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "web", "name": "web",
"version": "0.101.9", "version": "0.101.17-dev",
"private": true, "private": true,
"productName": "Tactical RMM", "productName": "Tactical RMM",
"scripts": { "scripts": {
@@ -10,13 +10,13 @@
"format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore" "format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore"
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "1.15.6", "@quasar/extras": "1.16.2",
"apexcharts": "3.35.5", "apexcharts": "3.37.3",
"axios": "0.27.2", "axios": "1.3.5",
"dotenv": "16.0.3", "dotenv": "16.0.3",
"qrcode.vue": "3.3.3", "qrcode.vue": "3.3.4",
"quasar": "2.10.2", "quasar": "2.11.10",
"vue": "3.2.45", "vue": "3.2.47",
"vue3-ace-editor": "2.2.2", "vue3-ace-editor": "2.2.2",
"vue3-apexcharts": "1.4.1", "vue3-apexcharts": "1.4.1",
"vuedraggable": "4.1.0", "vuedraggable": "4.1.0",
@@ -24,17 +24,17 @@
"vuex": "4.1.0" "vuex": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@quasar/cli": "^1.3.2", "@quasar/cli": "^2.0.0",
"@intlify/vite-plugin-vue-i18n": "^6.0.3", "@intlify/unplugin-vue-i18n": "^0.10.0",
"@quasar/app-vite": "^1.1.3", "@quasar/app-vite": "^1.2.1",
"@types/node": "^18.11.10", "@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^5.57.1",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.14",
"eslint": "8.28.0", "eslint": "8.37.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.8.0",
"eslint-plugin-vue": "8.7.1", "eslint-plugin-vue": "8.7.1",
"prettier": "2.8.0", "prettier": "2.8.7",
"typescript": "4.9.3" "typescript": "5.0.4"
} }
} }

View File

@@ -4,18 +4,18 @@
module.exports = { module.exports = {
plugins: [ plugins: [
// https://github.com/postcss/autoprefixer // https://github.com/postcss/autoprefixer
require('autoprefixer')({ require("autoprefixer")({
overrideBrowserslist: [ overrideBrowserslist: [
'last 4 Chrome versions', "last 4 Chrome versions",
'last 4 Firefox versions', "last 4 Firefox versions",
'last 4 Edge versions', "last 4 Edge versions",
'last 4 Safari versions', "last 4 Safari versions",
'last 4 Android versions', "last 4 Android versions",
'last 4 ChromeAndroid versions', "last 4 ChromeAndroid versions",
'last 4 FirefoxAndroid versions', "last 4 FirefoxAndroid versions",
'last 4 iOS versions' "last 4 iOS versions",
] ],
}) }),
// https://github.com/elchininet/postcss-rtlcss // https://github.com/elchininet/postcss-rtlcss
// If you want to support RTL css, then // If you want to support RTL css, then
@@ -23,5 +23,5 @@ module.exports = {
// 2. optionally set quasar.config.js > framework > lang to an RTL language // 2. optionally set quasar.config.js > framework > lang to an RTL language
// 3. uncomment the following line: // 3. uncomment the following line:
// require('postcss-rtlcss') // require('postcss-rtlcss')
] ],
} };

View File

@@ -12,6 +12,25 @@ export async function fetchUsers(params = {}) {
} }
} }
export async function resetPass(pass) {
const payload = { password: pass };
try {
const { data } = await axios.put(`${baseUrl}/resetpw/`, payload);
return data;
} catch (e) {
console.error(e);
}
}
export async function resetTwoFactor() {
try {
const { data } = await axios.put(`${baseUrl}/reset2fa/`);
return data;
} catch (e) {
console.error(e);
}
}
// role api function // role api function
export async function fetchRoles(params = {}) { export async function fetchRoles(params = {}) {
try { try {

View File

@@ -373,17 +373,12 @@ export default {
"local_ips", "local_ips",
"make_model", "make_model",
"physical_disks", "physical_disks",
"custom_fields"
]; ];
// quasar filter only does visible columns so this is a hack to add hidden columns we want to filter // quasar filter only does visible columns so this is a hack to add hidden columns we want to filter
for (const elem of hiddenFields) { // originally I was modifying cols directly but this led to phantom colum so doing it this way now
if (!cols.find((o) => o.name === elem)) { // https://github.com/amidaware/tacticalrmm/issues/1264
cols.push({ const allColumns = [...cols, ...hiddenFields.map((field) => ({ field }))];
name: elem,
field: elem,
});
}
}
const lowerTerms = terms ? terms.toLowerCase() : ""; const lowerTerms = terms ? terms.toLowerCase() : "";
let advancedFilter = false; let advancedFilter = false;
@@ -437,8 +432,12 @@ export default {
} }
// Normal text filter // Normal text filter
return cols.some((col) => { return allColumns.some((col) => {
const val = cellValue(col, row) + ""; let valObj = cellValue(col, row);
if (Array.isArray(valObj)) {
valObj = valObj.map((item) => item.value ? item.value : item);
}
const val = valObj + "";
const haystack = const haystack =
val === "undefined" || val === "null" ? "" : val.toLowerCase(); val === "undefined" || val === "null" ? "" : val.toLowerCase();
return haystack.indexOf(search) !== -1; return haystack.indexOf(search) !== -1;

View File

@@ -0,0 +1,75 @@
<template>
<q-dialog ref="dialogRef" @hide="onDialogHide">
<q-card class="q-dialog-plugin" style="width: 60vw">
<q-card-section class="row">
<div class="col-3">New password:</div>
<div class="col-9">
<q-input
outlined
dense
v-model="pass"
:type="isPwd ? 'password' : 'text'"
:rules="[(val) => !!val || '*Required']"
>
<template v-slot:append>
<q-icon
:name="isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="isPwd = !isPwd"
/>
</template>
</q-input>
</div>
<div class="col-3">Confirm password:</div>
<div class="col-9">
<q-input
outlined
dense
v-model="pass2"
:type="isPwd ? 'password' : 'text'"
:rules="[(val) => val === pass || 'Passwords do not match']"
>
<template v-slot:append>
<q-icon
:name="isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="isPwd = !isPwd"
/>
</template>
</q-input>
</div>
</q-card-section>
<q-card-actions align="right">
<q-btn
color="primary"
label="Reset"
@click="onOKClick"
:disable="!pass || pass !== pass2"
/>
<q-btn color="negative" label="Cancel" @click="onDialogCancel" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script setup>
import { ref } from "vue";
import { useDialogPluginComponent } from "quasar";
import { resetPass } from "@/api/accounts";
import { notifySuccess } from "@/utils/notify";
const pass = ref("");
const pass2 = ref("");
const isPwd = ref(true);
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
useDialogPluginComponent();
async function onOKClick() {
const ret = await resetPass(pass.value);
notifySuccess(ret);
onDialogOK();
}
</script>

View File

@@ -166,7 +166,7 @@ export default {
type: "textarea", type: "textarea",
isValid: (val) => !!val, isValid: (val) => !!val,
}, },
style: "width: 30vw; max-width: 50vw;", style: "width: 90vw; max-width: 90vw",
ok: { label: "Add" }, ok: { label: "Add" },
cancel: true, cancel: true,
}).onOk(async () => { }).onOk(async () => {
@@ -193,7 +193,7 @@ export default {
type: "textarea", type: "textarea",
isValid: (val) => !!val, isValid: (val) => !!val,
}, },
style: "width: 30vw; max-width: 50vw;", style: "width: 90vw; max-width: 90vw",
ok: { label: "Save" }, ok: { label: "Save" },
cancel: true, cancel: true,
}).onOk(async (data) => { }).onOk(async (data) => {

View File

@@ -158,6 +158,20 @@
> >
</div> </div>
<div v-else>No checks</div> <div v-else>No checks</div>
<span
v-if="customFields.length > 0"
class="text-subtitle2 text-bold block q-mt-xl"
>Custom Fields</span
>
<q-list dense>
<q-item v-for="(field, i) in customFields" :key="field + i">
<q-item-section thumbnail>
<q-icon name="fas fa-user" size="xs" />
</q-item-section>
<q-item-section>{{ field.name }}: {{ field.value }}</q-item-section>
</q-item>
</q-list>
</div> </div>
<div class="col-1"></div> <div class="col-1"></div>
<!-- right --> <!-- right -->
@@ -193,6 +207,7 @@ import {
openAgentWindow, openAgentWindow,
} from "@/api/agents"; } from "@/api/agents";
import { notifySuccess } from "@/utils/notify"; import { notifySuccess } from "@/utils/notify";
import { fetchCustomFields } from "@/api/core";
// ui imports // ui imports
import AgentActionMenu from "@/components/agents/AgentActionMenu.vue"; import AgentActionMenu from "@/components/agents/AgentActionMenu.vue";
@@ -210,6 +225,7 @@ export default {
// summary tab logic // summary tab logic
const summary = ref(null); const summary = ref(null);
const customFieldsDefinitions = ref(null);
const loading = ref(false); const loading = ref(false);
function diskBarColor(percent) { function diskBarColor(percent) {
@@ -236,9 +252,37 @@ export default {
return ret; return ret;
}); });
const customFields = computed(() => {
if (!summary.value.custom_fields) {
return [];
}
if (!customFieldsDefinitions.value) {
return [];
}
const ret = [];
for (const customField of summary.value.custom_fields) {
const definition = customFieldsDefinitions.value.find(
(def) => def.id === customField.field
);
if (
definition &&
!definition.hide_in_ui &&
customField.value?.length > 0
) {
ret.push({
name: definition.name,
value: customField.value,
});
}
}
return ret;
});
async function getSummary() { async function getSummary() {
loading.value = true; loading.value = true;
summary.value = await fetchAgent(selectedAgent.value); summary.value = await fetchAgent(selectedAgent.value);
customFieldsDefinitions.value = await fetchCustomFields();
store.commit("setRefreshSummaryTab", false); store.commit("setRefreshSummaryTab", false);
store.commit("setAgentPlatform", summary.value.plat); store.commit("setAgentPlatform", summary.value.plat);
loading.value = false; loading.value = false;
@@ -277,6 +321,7 @@ export default {
return { return {
// reactive data // reactive data
summary, summary,
customFields,
loading, loading,
selectedAgent, selectedAgent,
disks, disks,

View File

@@ -42,10 +42,9 @@ export default {
const tabHeight = computed(() => store.state.tabHeight); const tabHeight = computed(() => store.state.tabHeight);
function copyValueToClip(val) { function copyValueToClip(val) {
copyToClipboard(val) copyToClipboard(val).then(() => {
.then(() => {
notifySuccess("Copied to clipboard"); notifySuccess("Copied to clipboard");
}) });
} }
return { return {

View File

@@ -128,7 +128,7 @@ import { useDialogPluginComponent } from "quasar";
import { useCheckModal } from "@/composables/checks"; import { useCheckModal } from "@/composables/checks";
import { useScriptDropdown } from "@/composables/scripts"; import { useScriptDropdown } from "@/composables/scripts";
import { validateRetcode } from "@/utils/validation"; import { validateRetcode } from "@/utils/validation";
import { envVarsLabel } from "@/constants/constants" import { envVarsLabel } from "@/constants/constants";
// ui imports // ui imports
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue"; import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
@@ -146,8 +146,13 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// setup script dropdown // setup script dropdown
const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars } = const {
useScriptDropdown(props.check ? props.check.script : undefined, { script,
scriptOptions,
defaultTimeout,
defaultArgs,
defaultEnvVars,
} = useScriptDropdown(props.check ? props.check.script : undefined, {
onMount: true, onMount: true,
}); });

View File

@@ -122,7 +122,7 @@ export default {
try { try {
const result = props.APIKey const result = props.APIKey
? await editAPIKey(data) ? await editAPIKey(data.id, data)
: await saveAPIKey(data); : await saveAPIKey(data);
onDialogOK(); onDialogOK();
notifySuccess(result); notifySuccess(result);

View File

@@ -89,7 +89,8 @@
<p class="text-italic"> <p class="text-italic">
Note: the auth token above will be valid for {{ info.expires }} hours. Note: the auth token above will be valid for {{ info.expires }} hours.
</p> </p>
<q-btn v-if="info.plat === 'windows'" <q-btn
v-if="info.plat === 'windows'"
type="a" type="a"
:href="info.data.url" :href="info.data.url"
color="primary" color="primary"

View File

@@ -221,8 +221,15 @@ export default {
const { dialogRef, onDialogHide } = useDialogPluginComponent(); const { dialogRef, onDialogHide } = useDialogPluginComponent();
// setup dropdowns // setup dropdowns
const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars, syntax, link } = const {
useScriptDropdown(props.script, { script,
scriptOptions,
defaultTimeout,
defaultArgs,
defaultEnvVars,
syntax,
link,
} = useScriptDropdown(props.script, {
onMount: true, onMount: true,
filterByPlatform: props.agent.plat, filterByPlatform: props.agent.plat,
}); });

View File

@@ -836,8 +836,13 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// setup dropdowns // setup dropdowns
const { script, scriptOptions, defaultTimeout, defaultArgs, defaultEnvVars } = const {
useScriptDropdown(undefined, { script,
scriptOptions,
defaultTimeout,
defaultArgs,
defaultEnvVars,
} = useScriptDropdown(undefined, {
onMount: true, onMount: true,
}); });

View File

@@ -30,7 +30,7 @@ export function useScriptDropdown(setScript = null, { onMount = false } = {}) {
); );
defaultTimeout.value = tmpScript.timeout; defaultTimeout.value = tmpScript.timeout;
defaultArgs.value = tmpScript.args; defaultArgs.value = tmpScript.args;
defaultEnvVars.value = tmpScript.env_vars, defaultEnvVars.value = tmpScript.env_vars;
syntax.value = tmpScript.syntax; syntax.value = tmpScript.syntax;
link.value = link.value =
tmpScript.script_type === "builtin" tmpScript.script_type === "builtin"

View File

@@ -7,4 +7,4 @@ 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."; "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 = export const envVarsLabel =
"Environment vars (press Enter after typing each key=value pair)" "Environment vars (press Enter after typing each key=value pair)";

View File

@@ -20,9 +20,14 @@
class="bg-yellow text-black text-center" class="bg-yellow text-black text-center"
> >
<q-icon size="xl" name="warning" /> <q-icon size="xl" name="warning" />
<span><br />Your code signing token is no longer valid.<br/><br/> <span
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/> ><br />Your code signing token is no longer valid.<br /><br />
For any issues or to renew your sponsorship please email support@amidaware.com<br/><br/></span> 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 <q-btn
color="dark" color="dark"
icon="refresh" icon="refresh"
@@ -135,6 +140,32 @@
<q-item-label>Preferences</q-item-label> <q-item-label>Preferences</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item clickable>
<q-item-section>Account</q-item-section>
<q-item-section side>
<q-icon name="keyboard_arrow_right" />
</q-item-section>
<q-menu anchor="top end" self="top start">
<q-list>
<q-item
clickable
v-ripple
@click="resetPassword"
v-close-popup
>
<q-item-section>
<q-item-label>Reset Password</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="reset2FA" v-close-popup>
<q-item-section>
<q-item-label>Reset 2FA</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-item>
<q-item to="/expired" exact> <q-item to="/expired" exact>
<q-item-section> <q-item-section>
<q-item-label>Logout</q-item-label> <q-item-label>Logout</q-item-label>
@@ -156,10 +187,13 @@ import { useQuasar } from "quasar";
import { useStore } from "vuex"; import { useStore } from "vuex";
import axios from "axios"; import axios from "axios";
import { getWSUrl } from "@/websocket/channels"; import { getWSUrl } from "@/websocket/channels";
import { resetTwoFactor } from "@/api/accounts";
import { notifySuccess } from "@/utils/notify";
// ui imports // ui imports
import AlertsIcon from "@/components/AlertsIcon.vue"; import AlertsIcon from "@/components/AlertsIcon.vue";
import UserPreferences from "@/components/modals/coresettings/UserPreferences.vue"; import UserPreferences from "@/components/modals/coresettings/UserPreferences.vue";
import ResetPass from "@/components/accounts/ResetPass.vue";
export default { export default {
name: "MainLayout", name: "MainLayout",
@@ -197,6 +231,26 @@ export default {
}).onOk(() => store.dispatch("getDashInfo")); }).onOk(() => store.dispatch("getDashInfo"));
} }
function resetPassword() {
$q.dialog({
component: ResetPass,
});
}
function reset2FA() {
$q.dialog({
title: "Reset 2FA",
message: "Are you sure you would like to reset your 2FA token?",
cancel: true,
persistent: true,
}).onOk(async () => {
try {
const ret = await resetTwoFactor();
notifySuccess(ret, 3000);
} catch {}
});
}
const serverCount = ref(0); const serverCount = ref(0);
const serverOfflineCount = ref(0); const serverOfflineCount = ref(0);
const workstationCount = ref(0); const workstationCount = ref(0);
@@ -281,6 +335,8 @@ export default {
// methods // methods
showUserPreferences, showUserPreferences,
resetPassword,
reset2FA,
updateAvailable, updateAvailable,
}; };
}, },

View File

@@ -173,6 +173,18 @@
</q-menu> </q-menu>
</q-item> </q-item>
<!-- Bulk Run Checks -->
<q-item
clickable
v-close-popup
@click="runChecks(props.node)"
>
<q-item-section side>
<q-icon name="fas fa-check-double" />
</q-item-section>
<q-item-section>Run Checks</q-item-section>
</q-item>
<q-separator></q-separator> <q-separator></q-separator>
<q-item clickable v-close-popup> <q-item clickable v-close-popup>
@@ -440,7 +452,7 @@ export default {
showInstallAgentModal: false, showInstallAgentModal: false,
sitePk: null, sitePk: null,
innerModel: (this.$q.screen.height - 82) / 2, innerModel: (this.$q.screen.height - 82) / 2,
search: "", search: (this.$route.query.search ? this.$route.query.search : ""),
filterTextLength: 0, filterTextLength: 0,
filterAvailability: "all", filterAvailability: "all",
filterPatchesPending: false, filterPatchesPending: false,
@@ -690,6 +702,17 @@ export default {
}) })
.onOk(() => this.$store.dispatch("refreshDashboard")); .onOk(() => this.$store.dispatch("refreshDashboard"));
}, },
runChecks(node) {
const target = node.children ? "client" : "site";
this.$axios
.post(`/checks/${target}/${node.id}/csbulkrun/`)
.then((r) => {
this.notifySuccess(r.data);
})
.catch((e) => {
console.error(e);
});
},
showToggleMaintenance(node) { showToggleMaintenance(node) {
let data = { let data = {
id: node.id, id: node.id,