Compare commits

..

30 Commits

Author SHA1 Message Date
wh1te909
95df8c1889 update reqs 2023-05-07 02:16:19 +00:00
sadnub
819a364207 Merge pull request #8 from sadnub/develop
open ai integration
2023-04-10 19:06:18 -04:00
sadnub
ed2b07fb0b change wording on default prompt 2023-04-10 19:04:01 -04:00
sadnub
64ed5e8740 open ai integration 2023-04-09 22:36:20 -04:00
wh1te909
cdeaa3d9c4 bump version 2023-04-09 03:28:00 +00:00
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
22 changed files with 3301 additions and 4272 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",

15
.vscode/settings.json vendored
View File

@@ -4,16 +4,9 @@
"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": [ "editor.codeActionsOnSave": ["source.fixAll.eslint"]
"source.fixAll.eslint"
],
}, },
"eslint.validate": [ "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"javascript",
"javascriptreact",
"typescript",
"vue"
],
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"files.watcherExclude": { "files.watcherExclude": {
"files.watcherExclude": { "files.watcherExclude": {
@@ -22,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,18 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title> <title><%= productName %></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>
@@ -20,5 +19,4 @@
<body> <body>
<!-- quasar:entry-point --> <!-- quasar:entry-point -->
</body> </body>
</html> </html>

6896
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.10-dev", "version": "0.101.19-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.8", "@quasar/extras": "1.16.3",
"apexcharts": "3.36.3", "apexcharts": "3.40.0",
"axios": "0.27.2", "axios": "1.4.0",
"dotenv": "16.0.3", "dotenv": "16.0.3",
"qrcode.vue": "3.3.3", "qrcode.vue": "3.3.4",
"quasar": "2.11.1", "quasar": "2.12.0",
"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.1.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.3.0",
"@types/node": "^18.11.17", "@types/node": "^18.16.5",
"@typescript-eslint/eslint-plugin": "^5.47.0", "@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.47.0", "@typescript-eslint/parser": "^5.59.2",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.14",
"eslint": "8.30.0", "eslint": "8.40.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.1", "prettier": "2.8.8",
"typescript": "4.9.4" "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

@@ -38,3 +38,8 @@ export async function runURLAction(payload) {
console.error(e); console.error(e);
} }
} }
export async function generateScript(payload) {
const { data } = await axios.post(`${baseUrl}/openai/generate/`, payload);
return data;
}

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

@@ -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

@@ -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

@@ -12,6 +12,7 @@
<q-tab name="urlactions" label="URL Actions" /> <q-tab name="urlactions" label="URL Actions" />
<q-tab name="retention" label="Retention" /> <q-tab name="retention" label="Retention" />
<q-tab name="apikeys" label="API Keys" /> <q-tab name="apikeys" label="API Keys" />
<q-tab name="openai" label="Open AI" />
</q-tabs> </q-tabs>
</template> </template>
<template v-slot:after> <template v-slot:after>
@@ -508,6 +509,49 @@
<q-tab-panel name="apikeys"> <q-tab-panel name="apikeys">
<APIKeysTable /> <APIKeysTable />
</q-tab-panel> </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-tab-panels>
</q-scroll-area> </q-scroll-area>
<q-card-section class="row items-center"> <q-card-section class="row items-center">

View File

@@ -11,7 +11,17 @@
:style="maximized ? '' : 'width: 90vw; max-width: 90vw'" :style="maximized ? '' : 'width: 90vw; max-width: 90vw'"
> >
<q-bar> <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-space />
<q-btn <q-btn
dense dense
@@ -57,7 +67,23 @@
><br />Add one to get rid of this warning. Ignore if windows. ><br />Add one to get rid of this warning. Ignore if windows.
</q-banner> </q-banner>
<div class="row q-pa-sm"> <div class="row q-pa-sm">
<div class="col-4 q-gutter-sm q-pr-sm"> <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 <q-input
filled filled
dense dense
@@ -144,11 +170,11 @@
label="Run As User (Windows only)" label="Run As User (Windows only)"
> >
<q-tooltip <q-tooltip
>Setting this value on the script model will always override any >Setting this value on the script model will always override
'Run As User' checkboxes in the UI and force this script to any 'Run As User' checkboxes in the UI and force this script
always be run in the context of the logged in user. If no user to always be run in the context of the logged in user. If no
is logged in, the script will not run and an error will be user is logged in, the script will not run and an error will
returned. be returned.
</q-tooltip> </q-tooltip>
</q-checkbox> </q-checkbox>
<q-input <q-input
@@ -161,12 +187,13 @@
:readonly="readonly" :readonly="readonly"
/> />
</div> </div>
</q-scroll-area>
<v-ace-editor <v-ace-editor
v-model:value="formScript.script_body" v-model:value="formScript.script_body"
class="col-8" class="col-8"
:lang="lang" :lang="lang"
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'" :theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
:style="{ height: `${maximized ? '87vh' : '64vh'}` }" :style="{ height: `${maximized ? '82vh' : '64vh'}` }"
wrap wrap
:printMargin="false" :printMargin="false"
:options="{ fontSize: '14px' }" :options="{ fontSize: '14px' }"
@@ -220,9 +247,11 @@
<script> <script>
// composable imports // composable imports
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { useQuasar, useDialogPluginComponent } from "quasar"; import { useQuasar, useDialogPluginComponent } from "quasar";
import { saveScript, editScript, downloadScript } from "@/api/scripts"; import { saveScript, editScript, downloadScript } from "@/api/scripts";
import { useAgentDropdown, agentPlatformOptions } from "@/composables/agents"; import { useAgentDropdown, agentPlatformOptions } from "@/composables/agents";
import { generateScript } from "@/api/core";
import { notifySuccess } from "@/utils/notify"; import { notifySuccess } from "@/utils/notify";
// ui imports // ui imports
@@ -266,6 +295,10 @@ export default {
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
const $q = useQuasar(); const $q = useQuasar();
// setup store
const store = useStore();
const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled);
// setup agent dropdown // setup agent dropdown
const { agent, agentOptions, getAgentOptions } = useAgentDropdown(); 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 // component life cycle hooks
onMounted(async () => { onMounted(async () => {
agentLoading.value = true; agentLoading.value = true;
@@ -380,10 +430,12 @@ export default {
//computed //computed
title, title,
openAIEnabled,
//methods //methods
submitForm, submitForm,
openTestScriptModal, openTestScriptModal,
generateScriptOpenAI,
// quasar dialog plugin // quasar dialog plugin
dialogRef, dialogRef,

View File

@@ -11,7 +11,17 @@
:style="maximized ? '' : 'width: 70vw; max-width: 90vw'" :style="maximized ? '' : 'width: 70vw; max-width: 90vw'"
> >
<q-bar> <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-space />
<q-btn <q-btn
dense dense
@@ -97,6 +107,9 @@
<script> <script>
// composable imports // composable imports
import { ref, computed } from "vue"; import { ref, computed } from "vue";
import { useStore } from "vuex";
import { useQuasar } from "quasar";
import { generateScript } from "@/api/core";
import { useDialogPluginComponent } from "quasar"; import { useDialogPluginComponent } from "quasar";
import { saveScriptSnippet, editScriptSnippet } from "@/api/scripts"; import { saveScriptSnippet, editScriptSnippet } from "@/api/scripts";
import { notifySuccess } from "@/utils/notify"; import { notifySuccess } from "@/utils/notify";
@@ -128,6 +141,13 @@ export default {
// setup quasar plugins // setup quasar plugins
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
// setup quasar
const $q = useQuasar();
// setup store
const store = useStore();
const openAIEnabled = computed(() => store.state.openAIIntegrationEnabled);
// snippet form logic // snippet form logic
const snippet = props.snippet const snippet = props.snippet
? ref(Object.assign({}, props.snippet)) ? ref(Object.assign({}, props.snippet))
@@ -167,6 +187,23 @@ export default {
loading.value = false; 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 { return {
// reactive data // reactive data
formSnippet: snippet.value, formSnippet: snippet.value,
@@ -179,9 +216,11 @@ export default {
//computed //computed
title, title,
openAIEnabled,
//methods //methods
submitForm, submitForm,
generateScriptOpenAI,
// quasar dialog plugin // quasar dialog plugin
dialogRef, dialogRef,

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

@@ -33,6 +33,7 @@ export default function () {
currentTRMMVersion: null, currentTRMMVersion: null,
latestTRMMVersion: null, latestTRMMVersion: null,
dateFormat: "MMM-DD-YYYY - HH:mm", dateFormat: "MMM-DD-YYYY - HH:mm",
openAIIntegrationEnabled: false,
}; };
}, },
getters: { getters: {
@@ -136,6 +137,9 @@ export default function () {
setDateFormat(state, val) { setDateFormat(state, val) {
state.dateFormat = val; state.dateFormat = val;
}, },
setOpenAIIntegrationStatus(state, val) {
state.openAIIntegrationEnabled = val;
},
}, },
actions: { actions: {
setClientTreeSplitter(context, val) { setClientTreeSplitter(context, val) {
@@ -217,6 +221,10 @@ export default function () {
context.commit("setShowCommunityScripts", data.show_community_scripts); context.commit("setShowCommunityScripts", data.show_community_scripts);
context.commit("SET_HOSTED", data.hosted); context.commit("SET_HOSTED", data.hosted);
context.commit("SET_TOKEN_EXPIRED", data.token_is_expired); context.commit("SET_TOKEN_EXPIRED", data.token_is_expired);
context.commit(
"setOpenAIIntegrationStatus",
data.open_ai_integration_enabled
);
if (data.date_format && data.date_format !== "") if (data.date_format && data.date_format !== "")
context.commit("setDateFormat", data.date_format); context.commit("setDateFormat", data.date_format);

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,