Website: Add MDM demo video modal to /device-management (#12380)

Changes:
- Added a modal to the /device-management page where users can fill out
a form to see an MDM demo video.
- Added a new email template: `email-mdm-video`, that is sent to users
who submit the form on the device management page.
- Added a new action: `deliver-mdm-demo-email.js`, that sends an MDM
demo video email.
- Updated the modal component to allow the default styling to be
overridden on a page-by-page basis (Moved inline styles into the
component's stylesheet)
- Updated `website/config/routes.js`, `website/config/policies.js`, and
ran the `rebuild-cloud-sdk` script.
This commit is contained in:
Eric 2023-06-16 15:08:24 -05:00 committed by GitHub
parent ad7c1909a5
commit 7942b9008b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 247 additions and 7 deletions

View File

@ -0,0 +1,47 @@
module.exports = {
friendlyName: 'Deliver MDM demo video email',
description: 'Sends an email address containing a link to a MDM demo video to the specified email address',
extendedDescription: 'This action is triggered by form submissions on the /device-management page',
inputs: {
emailAddress: {
description: 'The email address provided when this user requested access to the MDM demo video',
type: 'string',
required: true,
},
},
exits: {
success: {
description: 'An MDM demo video email was successfully sent'
}
},
fn: async function ({emailAddress}) {
// Send an email to the provided email address that contains a link to Dave's MDM demo.
await sails.helpers.sendTemplateEmail.with({
to: emailAddress,
subject: 'Daves MDM video (again)',
from: sails.config.custom.fromEmailAddress,
fromName: sails.config.custom.fromName,
template: 'email-mdm-video',
templateData: {}
}).intercept((err)=>{
return new Error(`When trying to send a MDM demo video email for a user with the email address ${emailAddress}, an error occured. full error: ${err.stack}`);
});
// All done.
return;
}
};

View File

@ -13,7 +13,7 @@
Cloud.setup({
/* eslint-disable */
methods: {"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","hostStatusWebhookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization"]},"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"]},"receiveFromCustomerFleetInstance":{"verb":"POST","url":"/api/v1/webhooks/receive-from-customer-fleet-instance","args":["timestamp","host"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","topic","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","subscribeTo"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"generateLicenseKey":{"verb":"POST","url":"/api/v1/admin/generate-license-key","args":["numberOfHosts","organization","expiresAt"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData"]},"deliverPremiumUpgradeForm":{"verb":"POST","url":"/api/v1/deliver-premium-upgrade-form","args":["organization","monthsUsingFleetFree","emailAddress","numberOfHosts"]},"deliverLaunchPartySignup":{"verb":"POST","url":"/api/v1/deliver-launch-party-signup","args":["emailAddress","firstName","lastName","jobTitle","phoneNumber"]}}
methods: {"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","hostStatusWebhookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization"]},"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"]},"receiveFromCustomerFleetInstance":{"verb":"POST","url":"/api/v1/webhooks/receive-from-customer-fleet-instance","args":["timestamp","host","webhookSecret"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","topic","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","subscribeTo"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"generateLicenseKey":{"verb":"POST","url":"/api/v1/admin/generate-license-key","args":["numberOfHosts","organization","expiresAt"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData"]},"deliverPremiumUpgradeForm":{"verb":"POST","url":"/api/v1/deliver-premium-upgrade-form","args":["organization","monthsUsingFleetFree","emailAddress","numberOfHosts"]},"deliverLaunchPartySignup":{"verb":"POST","url":"/api/v1/deliver-launch-party-signup","args":["emailAddress","firstName","lastName","jobTitle","phoneNumber"]},"deliverMdmDemoEmail":{"verb":"POST","url":"/api/v1/deliver-mdm-demo-email","args":[]}}
/* eslint-enable */
});

View File

@ -48,7 +48,7 @@ parasails.registerComponent('modal', {
<div class="petticoat"></div>
<div class="modal-dialog custom-width position-relative" role="document" purpose="modal-dialog">
<div class="modal-content" purpose="modal-content">
<button type="button" style="top: 5px; right: 0; font-size: 28px; line-height: 1;" class="py-2 px-3 position-absolute" data-dismiss="modal" aria-label="Close" purpose="modal-close-button" v-if="!hideCloseButton">&times;</button>
<button type="button" class="position-absolute" data-dismiss="modal" aria-label="Close" purpose="modal-close-button" v-if="!hideCloseButton">&times;</button>
<slot></slot>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->

View File

@ -3,7 +3,24 @@ parasails.registerPage('device-management', {
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
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: {
emailAddress: {required: true, isEmail: true},
hasOverOneThousandHosts: {required: true },
},
cloudError: '',
// Syncing / loading state
syncing: false,
showSignupFormSuccess: false,
// Modal
modal: '',
},
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
@ -25,5 +42,27 @@ parasails.registerPage('device-management', {
window.HubSpotConversations.widget.open();
}
},
clickOpenMdmModal: function() {
this.modal = 'mdm';
},
closeModal: async function () {
this.modal = '';
await this._resetForms();
},
typeClearOneFormError: async function(field) {
if(this.formErrors[field]){
this.formErrors = _.omit(this.formErrors, field);
}
},
submittedForm: function() {
this.showSignupFormSuccess = true;
},
_resetForms: async function() {
this.cloudError = '';
this.formData = {};
this.formRules = {};
this.formErrors = {};
await this.forceRender();
},
}
});

View File

@ -16,6 +16,14 @@
[purpose='modal-close-button'] {
.btn-reset();
opacity: 0.6;
padding-top: 8px;
padding-bottom: 8px;
padding-left: 16px;
padding-right: 16px;
top: 5px;
right: 0;
font-size: 28px;
line-height: 1;
&:hover {
opacity: 1;
}
@ -91,6 +99,6 @@
.modal-backdrop {
background-color: #192147;
&.show {
opacity: 1;
opacity: 0.25;
}
}

View File

@ -55,6 +55,7 @@
[purpose='button-row'] {
a {
white-space: nowrap;
font-weight: 700;
font-size: 16px;
line-height: 24px;
@ -198,6 +199,71 @@
}
}
[purpose='modal-form'] {
color: #192147;
input {
border-radius: 6px;
// height: 48px;
}
// Disable default buttons on type="number" inputs
input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type='number'] {
-moz-appearance: textfield;
}
input[type='radio'] {
accent-color: #6a67fe;
}
label {
font-weight: 700;
margin-bottom: 12px;
}
width: 100%;
max-width: 450px;
}
[purpose='submit-button'] {
border-radius: 8px;
height: 54px;
}
[purpose='mdm-modal'] {
h2 {
font-weight: 800;
font-size: 28px;
line-height: 38px;
}
p {
font-size: 16px;
line-height: 24px;
}
}
[purpose='modal-dialog'] {
max-width: 800px;
}
[purpose='modal-content'] {
background-color: #F9FAFC;
padding: 40px;
margin-top: 150px;
margin-left: auto;
margin-right: auto;
[purpose='modal-close-button'] {
top: -40px;
right: -40px;
border-radius: 50%;
width: 32px;
height: 32px;
padding: 0px 0px 4px 0px;
background-color: #192147;
color: #FFF;
opacity: 1;
}
}
@media (min-width: 1200px) {
[purpose='page-container'] {
padding-left: 120px;
@ -315,6 +381,25 @@
@media (max-width: 575px) {
[purpose='modal-content'] {
background-color: #F9FAFC;
padding: 24px;
margin-top: 100px;
margin-left: auto;
margin-right: auto;
[purpose='modal-close-button'] {
top: 5px;
right: 0;
border-radius: 100%;
width: 32px;
height: 32px;
padding: 0;
background-color: unset;
color: #192147;
opacity: 1;
}
}
[purpose='button-row'] {
max-width: 100%;
[purpose='cta-button'] {

View File

@ -58,4 +58,5 @@ module.exports.policies = {
'view-compliance': true,
'view-osquery-management': true,
'view-vulnerability-management': true,
'deliver-mdm-demo-email': true,
};

View File

@ -466,4 +466,5 @@ module.exports.routes = {
'POST /api/v1/deliver-apple-csr ': { action: 'deliver-apple-csr', csrf: false},
'POST /api/v1/deliver-premium-upgrade-form': { action: 'deliver-premium-upgrade-form' },
'POST /api/v1/deliver-launch-party-signup': { action: 'imagine/deliver-launch-party-signup' },
'POST /api/v1/deliver-mdm-demo-email': { action: 'deliver-mdm-demo-email' },
};

View File

@ -0,0 +1,15 @@
<% /* Note: This is injected into `views/layouts/layout-email.ejs` */ %>
<div style="padding: 0px 0px 0px 22px">
<p style="margin-bottom: 4px;">In case you want to watch it again</p>
<h2 style="font-weight: 800; font-size: 32px; line-height: 48px; margin-bottom: 32px; margin-top: 0px">Fleet brings GitOps to MDM</h2>
<a style="cursor: pointer; font-size: 16px; line-height: 54px; font-weight: 700; margin-bottom: 32px; background: #FF5C83; border-radius: 8px; height: 54px; display: block; color: #FFF; width: 260px; text-decoration: none; text-align: center;" href="https://play.goconsensus.com/ue5caa4fa" target="_blank">Watch Dave's MDM demo</a>
<div style="font-size: 14px; line-height: 20px; color: #515774">
<p><span style="background-image: url('https://fleetdm.com/images/icon-checkmark-green-16x16@2x.png'); background-size: 14px 14px; display: inline-block; position: relative; top: 2px;margin-right: 8px; width: 14px; height: 14px;"></span>Zero-touch setup for macOS computers</p>
<p><span style="background-image: url('https://fleetdm.com/images/icon-checkmark-green-16x16@2x.png'); background-size: 14px 14px; display: inline-block; position: relative; top: 2px;margin-right: 8px; width: 14px; height: 14px;"></span>Safely execute remote scripts*</p>
<p><span style="background-image: url('https://fleetdm.com/images/icon-checkmark-green-16x16@2x.png'); background-size: 14px 14px; display: inline-block; position: relative; top: 2px;margin-right: 8px; width: 14px; height: 14px;"></span>Self-managed or private cloud hosted</p>
<p style="margin-top: 32px">* Coming soon</p>
</div>
</div>

View File

@ -12,7 +12,7 @@
<a href="https://fleetdm.com/slack"><img style="height: 20px; width: 20px;" alt="Join the osquery Slack community" src="<%= url.resolve(sails.config.custom.baseUrl,'images/logo-slack-24x24@2x.png')%>"></a>
</div>
<div style="text-align: left; padding-top: 15px; font-size: 12px; color: #3E4771;">
<p>© 2023 Fleet Device Management Inc. All trademarks, service marks, and company names are the property of their respective owners.</p>
<p>© 2023 Fleet Device Management Inc.<br> All trademarks, service marks, and company names are the property of their respective owners.</p>
</div>
</div>
</div>

View File

@ -8,7 +8,7 @@
<h1>Fleet brings GitOps to MDM</h1>
<p>We prefer a more collaborative approach to device management. Fleet uses a GitOps workflow, ensuring checks and balances for your configuration deployments.</p>
<div purpose="button-row" class="d-flex flex-sm-row flex-column justify-content-center align-items-center">
<a purpose="cta-button" href="/try-fleet/register?tryitnow">Try Fleet today</a>
<a purpose="cta-button" @click="clickOpenMdmModal()">See MDM in action</a>
<a @click="clickOpenChatWidget()" purpose="animated-arrow-button-red">Talk to an expert</a>
</div>
</div>
@ -47,7 +47,7 @@
</div>
<div purpose="button-row" style="margin-top: 60px;" class="d-flex flex-sm-row flex-column justify-content-center align-items-center mx-auto">
<a purpose="cta-button" href="/try-fleet/register?tryitnow">Try Fleet today</a>
<a purpose="cta-button" @click="clickOpenMdmModal()">See MDM in action</a>
<a @click="clickOpenChatWidget()" purpose="animated-arrow-button-red">Talk to an expert</a>
</div>
@ -81,6 +81,50 @@
<img alt="A glass city floating on top of fluffy white clouds" class="d-none d-md-flex d-lg-none" src="/images/homepage-cloud-city-banner-md-990x375@2x.png">
<img alt="A glass city floating on top of fluffy white clouds" class="d-flex d-md-none" src="/images/homepage-cloud-city-banner-sm-375x168@2x.png">
</div>
</div>
<modal v-if="modal === 'mdm'" @close="closeModal()" data-backdrop="false" v-cloak purpose="modal">
<div class="container p-0 d-flex flex-column justify-content-center align-items-center" purpose="mdm-modal">
<div purpose="modal-form" v-if="!showSignupFormSuccess">
<div class="modal-header pb-2">
<h2 class="text-center mb-2">Is it any good?</h2>
<p>Fill out the form below to see Dave bootstrapping a macOS device with MDM.</p>
</div>
<ajax-form action="deliverMdmDemoEmail" class="mdm-demo-video w-100" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-data="formData" :form-rules="formRules" :form-errors.sync="formErrors" @submitted="submittedForm()">
<div class="form-group">
<label for="emailAddress">Work email</label>
<input class="form-control" id="emailAddress" :class="[formErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="formData.emailAddress" @input="typeClearOneFormError('emailAddress')">
<div class="invalid-feedback" v-if="formErrors.emailAddress" focus-first>Please enter a valid email address</div>
</div>
<div class="form-group">
<p style="padding-bottom: 12px;" class="mb-0 font-weight-bold">How many hosts do you have?</p>
<div class="form-check">
<input class="form-check-input" type="radio" id="more-than-1000" value="true" v-model="formData.hasOverOneThousandHosts" :class="[formErrors.hasOverOneThousandHosts ? 'is-invalid' : '']">
<label class="form-check-label font-weight-normal" for="more-than-1000">
More than 1,000
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="less-than-1000" value="false" v-model="formData.hasOverOneThousandHosts" :class="[formErrors.hasOverOneThousandHosts ? 'is-invalid' : '']">
<label class="form-check-label font-weight-normal" for="less-than-1000">
Less than 1,000
</label>
<div class="invalid-feedback" style="margin-left: -20px;" v-if="formErrors.hasOverOneThousandHosts" focus-first>Please choose one option</div>
</div>
</div>
<cloud-error v-if="cloudError"></cloud-error>
<div class="border-0 justify-content-center">
<ajax-button purpose="submit-button" spinner="true" type="submit" :syncing="syncing" class="btn btn-sm btn-block btn-primary">Submit</ajax-button>
</div>
</ajax-form>
</div>
<div purpose="modal-form" class="text-center" v-else>
<h2 class="mb-2">Thank you!</h2>
<p class="mb-4">We just sent you an email in case you need to watch it again.</p>
<a purpose="cta-button" class="btn btn-primary btn-block" href="https://play.goconsensus.com/ue5caa4fa" target="_blank"><p class="mb-0">Watch Dave's MDM demo</p></a>
</div>
</div>
</modal>
</div>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>