From 0620d7e580398a0f68cc8c9146fc781205ae76ed Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 8 Jan 2025 13:47:17 -0600 Subject: [PATCH] Website: Add admin query generator (#25205) Changes: - Added /admin/query-generator, a page that admins can use to generate queries from natural language questions. - Added `/admin/get-llm-generated-sql.js` an action that uses the same method in the `test-llm-generated-sql` script to generate queries from a natural language question. --- .../admin/get-llm-generated-sql.js | 104 ++++++++++++++++++ .../controllers/admin/view-query-generator.js | 27 +++++ website/assets/js/cloud.setup.js | 2 +- .../js/pages/admin/query-generator.page.js | 56 ++++++++++ website/assets/styles/importer.less | 2 +- .../styles/pages/admin/query-generator.less | 11 ++ website/config/routes.js | 3 + website/views/layouts/layout.ejs | 2 + website/views/pages/admin/query-generator.ejs | 25 +++++ 9 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 website/api/controllers/admin/get-llm-generated-sql.js create mode 100644 website/api/controllers/admin/view-query-generator.js create mode 100644 website/assets/js/pages/admin/query-generator.page.js create mode 100644 website/assets/styles/pages/admin/query-generator.less create mode 100644 website/views/pages/admin/query-generator.ejs diff --git a/website/api/controllers/admin/get-llm-generated-sql.js b/website/api/controllers/admin/get-llm-generated-sql.js new file mode 100644 index 0000000000..7a56810edf --- /dev/null +++ b/website/api/controllers/admin/get-llm-generated-sql.js @@ -0,0 +1,104 @@ +module.exports = { + + + friendlyName: 'Get llm generated sql', + + + description: '', + + + + inputs: { + naturalLanguageQuestion: { type: 'string', required: true } + }, + + + exits: { + success: { + description: 'A SQL query was generated' + }, + + errorFromOpenAi: { + description: 'The Open AI API reutrned an error.' + } + }, + + + fn: async function ({naturalLanguageQuestion}) { + + let completeTables = await sails.helpers.getExtendedOsquerySchema(); + let prunedTables = completeTables.map((table)=>{ + let newTable = _.pick(table,['name','description','platforms', 'examples']); + newTable.columns = table.columns.map((column) => _.pick(column, ['name', 'description', 'type', 'platforms', 'required'])); + return newTable; + }); + + // Filter down the schema. + let schemaFiltrationPrompt = `Given this question from an IT admin, and using the provided context (the osquery schema), return the subset of tables that might be relevant for designing an osquery SQL query to answer this question for computers running macOS, Windows, Linux, and/or ChromeOS. + + Here is the question: + \`\`\` + ${naturalLanguageQuestion} + \`\`\` + + Provided context: + \`\`\` + ${JSON.stringify(prunedTables.map((table)=>{ + let lighterTable = _.pick(table, ['name','description','platforms']); + lighterTable.columns = table.columns.map((column)=>{ + let lighterColumn = _.pick(column, ['name', 'description', 'platforms']); + return lighterColumn; + }); + return lighterTable;}))} + \`\`\` + + Please respond in JSON, with the same data shape as the provided context, but with the array filtered to include only relevant tables.`; + let filteredTables = await sails.helpers.ai.prompt(schemaFiltrationPrompt, 'gpt-4o', true) + .intercept((err)=>{ + return new Error(`When trying to get a subset of tables to use to generate a query for an Admin user, the Open AI API returned an error. Full error: ${require('util').inspect(err, {depth: 2})}`); + }); + + + + // Now generate the SQL. + let sqlPrompt = `Given this question from an IT admin, return osquery SQL I could run on a computer (or fleet of computers) to answer this question. + + Here is the question: + \`\`\` + ${naturalLanguageQuestion} + \`\`\` + + When generating the SQL: + 1. Please do not use the SQL "AS" operator, nor alias tables. Always reference tables by their full name. + 2. If this question is related to an application or program, consider using LIKE instead of something verbatim. + 3. If this question is not possible to ask given the tables and columns available in the provided context (the osquery schema) for a particular operating system, then use empty string. + 4. If this question is a "yes" or "no" question, or a "how many people" question, or a "how many hosts" question, then build the query such that a "yes" returns exactly one row and a "no" returns zero rows. In other words, if this question is about finding out which hosts match a "yes" or "no" question, then if a host does not match, do not include any rows for it. + 5. Use only tables that are supported for each target platform, as documented in the provided context, considering the examples if they exist, and the available columns. + 6. For each table that you use, only use columns that are documented for that table, as documented in the provided context. + + Provided context: + \`\`\` + ${JSON.stringify(filteredTables)} + \`\`\` + + Please give me all of the above in JSON, with this data shape: + + { + "macOSQuery": "TODO", + "windowsQuery": "TODO", + "linuxQuery": "TODO", + "chromeOSQuery": "TODO", + "macOSCaveats": "TODO", + "windowsCaveats": "TODO", + "linuxCaveats": "TODO", + "chromeOSCaveats": "TODO", + }`; + let sqlReport = await sails.helpers.ai.prompt(sqlPrompt, 'o1-preview', true) + .intercept((err)=>{ + return new Error(`When trying to generate a query for an Admin user, the Open AI API returned an error. Full error: ${require('util').inspect(err, {depth: 2})}`); + }); + return sqlReport; + } + + +}; diff --git a/website/api/controllers/admin/view-query-generator.js b/website/api/controllers/admin/view-query-generator.js new file mode 100644 index 0000000000..db36affb4b --- /dev/null +++ b/website/api/controllers/admin/view-query-generator.js @@ -0,0 +1,27 @@ +module.exports = { + + + friendlyName: 'View query generator', + + + description: 'Display "Query generator" page.', + + + exits: { + + success: { + viewTemplatePath: 'pages/admin/query-generator' + } + + }, + + + fn: async function () { + + // Respond with view. + return {}; + + } + + +}; diff --git a/website/assets/js/cloud.setup.js b/website/assets/js/cloud.setup.js index 35f7bcdf45..a8c3aaac40 100644 --- a/website/assets/js/cloud.setup.js +++ b/website/assets/js/cloud.setup.js @@ -13,7 +13,7 @@ Cloud.setup({ /* eslint-disable */ - methods: {"redirectToStripeBillingPortal":{"verb":"GET","url":"/customers/update-subscription","args":[]},"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"downloadRssFeed":{"verb":"GET","url":"/rss/:categoryName","args":["categoryName"]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostsStatusWebHookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization","mdmMacOsEnabled","mdmWindowsEnabled","liveQueryDisabled","hostExpiryEnabled","numSoftwareVersions","numHostSoftwares","numSoftwareTitles","numHostSoftwareInstalledPaths","numSoftwareCPEs","numSoftwareCVEs","aiFeaturesDisabled","maintenanceWindowsEnabled","maintenanceWindowsConfigured","numHostsFleetDesktopEnabled"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label","release"]},"receiveFromStripe":{"verb":"POST","url":"/api/v1/webhooks/receive-from-stripe","args":["id","type","data","webhookSecret"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","firstName","lastName","message"]},"sendPasswordRecoveryEmail":{"verb":"POST","url":"/api/v1/entrance/send-password-recovery-email","args":["emailAddress"]},"signup":{"verb":"POST","url":"/api/v1/customers/signup","args":["emailAddress","password","organization","firstName","lastName","signupReason"]},"updateProfile":{"verb":"POST","url":"/api/v1/account/update-profile","args":["firstName","lastName","organization","emailAddress"]},"updatePassword":{"verb":"POST","url":"/api/v1/account/update-password","args":["oldPassword","newPassword"]},"updateBillingCard":{"verb":"POST","url":"/api/v1/account/update-billing-card","args":["stripeToken","billingCardLast4","billingCardBrand","billingCardExpMonth","billingCardExpYear"]},"login":{"verb":"POST","url":"/api/v1/customers/login","args":["emailAddress","password","rememberMe"]},"logout":{"verb":"GET","url":"/api/v1/account/logout","args":[]},"createQuote":{"verb":"POST","url":"/api/v1/customers/create-quote","args":["numberOfHosts"]},"saveBillingInfoAndSubscribe":{"verb":"POST","url":"/api/v1/customers/save-billing-info-and-subscribe","args":["quoteId","organization","firstName","lastName","paymentSource"]},"updatePasswordAndLogin":{"verb":"POST","url":"/api/v1/entrance/update-password-and-login","args":["password","token"]},"deliverDemoSignup":{"verb":"POST","url":"/api/v1/deliver-demo-signup","args":["emailAddress"]},"createOrUpdateOneNewsletterSubscription":{"verb":"POST","url":"/api/v1/create-or-update-one-newsletter-subscription","args":["emailAddress"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"buildLicenseKey":{"verb":"POST","url":"/api/v1/admin/build-license-key","args":["numberOfHosts","organization","expiresAt","partnerName"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey","redirectToExternalPageAfterAuthorization","sharedSecret"]},"redirectVantaAuthorizationRequest":{"verb":"GET","url":"/redirect-vanta-authorization-request","args":["vantaSourceId","state","vantaAuthorizationRequestURL","redirectAfterSetup"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"getHumanInterpretationFromOsquerySql":{"verb":"POST","url":"/api/v1/get-human-interpretation-from-osquery-sql","args":["sql"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData","deliveryMethod"]},"deliverMdmDemoEmail":{"verb":"POST","url":"/api/v1/deliver-mdm-demo-email","args":["emailAddress"]},"provisionSandboxInstanceAndDeliverEmail":{"verb":"POST","url":"/api/v1/admin/provision-sandbox-instance-and-deliver-email","args":["userId"]},"deliverTalkToUsFormSubmission":{"verb":"POST","url":"/api/v1/deliver-talk-to-us-form-submission","args":["emailAddress","firstName","lastName","organization","numberOfHosts","primaryBuyingSituation"]},"saveQuestionnaireProgress":{"verb":"POST","url":"/api/v1/save-questionnaire-progress","args":["currentStep","formData"]},"updateStartCtaVisibility":{"verb":"POST","url":"/api/v1/account/update-start-cta-visibility","args":[]},"deliverDealRegistrationSubmission":{"verb":"POST","url":"/api/v1/deliver-deal-registration-submission","args":["submittersFirstName","submittersLastName","submittersEmailAddress","submittersOrganization","customersFirstName","customersLastName","customersEmailAddress","linkedinUrl","customersOrganization","customersCurrentMdm","otherMdmEvaluated","preferredHosting","expectedDealSize","expectedCloseDate","notes"]},"unsubscribeFromMarketingEmails":{"verb":"GET","url":"/api/v1/unsubscribe-from-marketing-emails","args":["emailAddress"]},"getStripeCheckoutSessionUrl":{"verb":"POST","url":"/api/v1/customers/get-stripe-checkout-session-url","args":["quoteId"]}} + methods: {"redirectToStripeBillingPortal":{"verb":"GET","url":"/customers/update-subscription","args":[]},"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"downloadRssFeed":{"verb":"GET","url":"/rss/:categoryName","args":["categoryName"]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostsStatusWebHookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization","mdmMacOsEnabled","mdmWindowsEnabled","liveQueryDisabled","hostExpiryEnabled","numSoftwareVersions","numHostSoftwares","numSoftwareTitles","numHostSoftwareInstalledPaths","numSoftwareCPEs","numSoftwareCVEs","aiFeaturesDisabled","maintenanceWindowsEnabled","maintenanceWindowsConfigured","numHostsFleetDesktopEnabled","numQueries"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label","release"]},"receiveFromStripe":{"verb":"POST","url":"/api/v1/webhooks/receive-from-stripe","args":["id","type","data","webhookSecret"]},"getEstDeviceCertificate":{"verb":"POST","url":"/api/v1/get-est-device-certificate","args":["csrData","authToken"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","firstName","lastName","message"]},"sendPasswordRecoveryEmail":{"verb":"POST","url":"/api/v1/entrance/send-password-recovery-email","args":["emailAddress"]},"signup":{"verb":"POST","url":"/api/v1/customers/signup","args":["emailAddress","password","organization","firstName","lastName","signupReason"]},"updateProfile":{"verb":"POST","url":"/api/v1/account/update-profile","args":["firstName","lastName","organization","emailAddress"]},"updatePassword":{"verb":"POST","url":"/api/v1/account/update-password","args":["oldPassword","newPassword"]},"updateBillingCard":{"verb":"POST","url":"/api/v1/account/update-billing-card","args":["stripeToken","billingCardLast4","billingCardBrand","billingCardExpMonth","billingCardExpYear"]},"login":{"verb":"POST","url":"/api/v1/customers/login","args":["emailAddress","password","rememberMe"]},"logout":{"verb":"GET","url":"/api/v1/account/logout","args":[]},"createQuote":{"verb":"POST","url":"/api/v1/customers/create-quote","args":["numberOfHosts"]},"saveBillingInfoAndSubscribe":{"verb":"POST","url":"/api/v1/customers/save-billing-info-and-subscribe","args":["quoteId","organization","firstName","lastName","paymentSource"]},"updatePasswordAndLogin":{"verb":"POST","url":"/api/v1/entrance/update-password-and-login","args":["password","token"]},"deliverDemoSignup":{"verb":"POST","url":"/api/v1/deliver-demo-signup","args":["emailAddress"]},"createOrUpdateOneNewsletterSubscription":{"verb":"POST","url":"/api/v1/create-or-update-one-newsletter-subscription","args":["emailAddress"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"buildLicenseKey":{"verb":"POST","url":"/api/v1/admin/build-license-key","args":["numberOfHosts","organization","expiresAt","partnerName"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey","redirectToExternalPageAfterAuthorization","sharedSecret"]},"redirectVantaAuthorizationRequest":{"verb":"GET","url":"/redirect-vanta-authorization-request","args":["vantaSourceId","state","vantaAuthorizationRequestURL","redirectAfterSetup"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"getHumanInterpretationFromOsquerySql":{"verb":"POST","url":"/api/v1/get-human-interpretation-from-osquery-sql","args":["sql"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData","deliveryMethod"]},"deliverMdmDemoEmail":{"verb":"POST","url":"/api/v1/deliver-mdm-demo-email","args":["emailAddress"]},"provisionSandboxInstanceAndDeliverEmail":{"verb":"POST","url":"/api/v1/admin/provision-sandbox-instance-and-deliver-email","args":["userId"]},"deliverTalkToUsFormSubmission":{"verb":"POST","url":"/api/v1/deliver-talk-to-us-form-submission","args":["emailAddress","firstName","lastName","organization","numberOfHosts","primaryBuyingSituation"]},"saveQuestionnaireProgress":{"verb":"POST","url":"/api/v1/save-questionnaire-progress","args":["currentStep","formData"]},"updateStartCtaVisibility":{"verb":"POST","url":"/api/v1/account/update-start-cta-visibility","args":[]},"deliverDealRegistrationSubmission":{"verb":"POST","url":"/api/v1/deliver-deal-registration-submission","args":["submittersFirstName","submittersLastName","submittersEmailAddress","submittersOrganization","customersFirstName","customersLastName","customersEmailAddress","linkedinUrl","customersOrganization","customersCurrentMdm","otherMdmEvaluated","preferredHosting","expectedDealSize","expectedCloseDate","notes"]},"unsubscribeFromMarketingEmails":{"verb":"GET","url":"/api/v1/unsubscribe-from-marketing-emails","args":["emailAddress"]},"getStripeCheckoutSessionUrl":{"verb":"POST","url":"/api/v1/customers/get-stripe-checkout-session-url","args":["quoteId"]},"getLlmGeneratedSql":{"verb":"POST","url":"/api/v1/admin/get-llm-generated-sql","args":["naturalLanguageQuestion"]}} /* eslint-enable */ }); diff --git a/website/assets/js/pages/admin/query-generator.page.js b/website/assets/js/pages/admin/query-generator.page.js new file mode 100644 index 0000000000..319e923ab8 --- /dev/null +++ b/website/assets/js/pages/admin/query-generator.page.js @@ -0,0 +1,56 @@ +parasails.registerPage('query-generator', { + // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ + // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ + // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ + data: { + formData: { /* … */ }, + + // For tracking client-side validation errors in our form. + // > Has property set to `true` for each invalid property in `formData`. + formErrors: { /* … */ }, + + // Form rules + formRules: { + naturalLanguageQuestion: {required: true} + }, + // Syncing / loading state + syncing: false, + queryResult: '', + // Server error state + cloudError: '', + showGeneratedQuery: false, + }, + + // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ + // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣ + // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ + beforeMount: function() { + //… + }, + mounted: async function() { + //… + }, + + // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ + // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ + // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ + methods: { + handleSubmittingForm: async function() { + let argins = this.formData.naturalLanguageQuestion; + this.queryResult = await Cloud.getLlmGeneratedSql(argins) + .tolerate((err)=>{ + this.cloudError = err; + this.syncing = false; + }); + }, + submittedQueryForm: function() { + if(!this.cloudError){ + this.showGeneratedQuery = true; + } + }, + clickResetQueryGenerator: function() { + this.showGeneratedQuery = false; + this.formData.naturalLanguageQuestion = ''; + } + } +}); diff --git a/website/assets/styles/importer.less b/website/assets/styles/importer.less index 9ccdabe895..b44c9b3303 100644 --- a/website/assets/styles/importer.less +++ b/website/assets/styles/importer.less @@ -81,4 +81,4 @@ @import 'pages/app-library.less'; @import 'pages/app-details.less'; @import 'pages/meetups.less'; - +@import 'pages/admin/query-generator.less'; diff --git a/website/assets/styles/pages/admin/query-generator.less b/website/assets/styles/pages/admin/query-generator.less new file mode 100644 index 0000000000..d2a129edb7 --- /dev/null +++ b/website/assets/styles/pages/admin/query-generator.less @@ -0,0 +1,11 @@ +#query-generator { + + [purpose='page-container'] { + padding: 64px; + } + [purpose='page-content'] { + max-width: 1072px; + margin: auto; + } + +} diff --git a/website/config/routes.js b/website/config/routes.js index 6293d097c4..2e8ef0c804 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -448,6 +448,8 @@ module.exports.routes = { } }, + 'GET /admin/query-generator': { action: 'admin/view-query-generator' }, + // ╦ ╔═╗╔═╗╔═╗╔═╗╦ ╦ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗ // ║ ║╣ ║ ╦╠═╣║ ╚╦╝ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗ // ╩═╝╚═╝╚═╝╩ ╩╚═╝ ╩ ╩╚═╚═╝═╩╝╩╩╚═╚═╝╚═╝ ╩ ╚═╝ @@ -905,4 +907,5 @@ module.exports.routes = { 'POST /api/v1/deliver-deal-registration-submission': { action: 'deliver-deal-registration-submission' }, '/api/v1/unsubscribe-from-marketing-emails': { action: 'unsubscribe-from-marketing-emails' }, 'POST /api/v1/customers/get-stripe-checkout-session-url': { action: 'customers/get-stripe-checkout-session-url' }, + 'POST /api/v1/admin/get-llm-generated-sql': { action: 'admin/get-llm-generated-sql' }, }; diff --git a/website/views/layouts/layout.ejs b/website/views/layouts/layout.ejs index c8185011a9..de22654b3b 100644 --- a/website/views/layouts/layout.ejs +++ b/website/views/layouts/layout.ejs @@ -289,6 +289,7 @@ Admin pages
+ Generate queries License generator HTML Email preview tool
@@ -470,6 +471,7 @@ + diff --git a/website/views/pages/admin/query-generator.ejs b/website/views/pages/admin/query-generator.ejs new file mode 100644 index 0000000000..9cab588f4d --- /dev/null +++ b/website/views/pages/admin/query-generator.ejs @@ -0,0 +1,25 @@ +
+
+
+ + +

Query generator

+
+ + +
Ask your question.
+
+ An error occurred while generating your queries. Please reload this page and try again. + Generate queries +
+ +
+

Generated Query:

+
{{queryResult}}
+
Generate another
+
+ +
+
+
+<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>