mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Website: Fleet Sandbox (#6380)
* create pages, add routes, update policies * add new pages to importer * sandbox page * login -> sandbox-login * Update login.less * psuedo-code/code comments * remove sandbox page * Revert "remove sandbox page" This reverts commitd5a1280759. * view action drafts * delete forgot-password page * two new actions + draft code * change action name * Draft view actions and page scripts * Update signup.js * update comments * update signup & sandbox-login page script * update helper comments * update helper usage in comments * view-sandbox » view-sandbox-or-redirect * Update helpers, actions, and routes * login » sandbox-login * update attributes on user model * update signup action * update page scripts and importer * Update view-register.js * html + css * update signup and view-sandbox-or-redirect * Password reset Update user's sandbox password when they have a sandbox instance * add new-sandbox, update sandbox page - `/try-fleet/new-sandbox` added for users who don't have an existing Fleet Sandbox instance, - `/sandbox` updated to redirect users to the `/demologin` endpoint of their Fleet Sandbox instance if it is still valid, or display the sandbox expired state - updated policies & routes * layout and importer updates * update sandbox-login links & page script * update signup action * change logout redirect location to homepage * lint fixes * lint fixes * Update sandbox & sandbox-expired * Comment updates * update password requirements for existing pages * remove /get-started route * lint fixes * replace env variable with url * remove `required: false` from organization attribute on user model * send redirectToSandbox from view instead of routes * changes sandbox page name * add 10 second timeout to /healthz check, add authorization header to cloud provisioner request * update environment variable name * update authorization header * remove /new-sandbox * update unauthorized response to redirect to correct login screen * update comments * update layout * replace new-sandbox redirects with consistency violation errors * Provision Fleet sandbox for users logging in * Revert "Provision Fleet sandbox for users logging in" This reverts commit6297c33892. * Revert "Revert "Provision Fleet sandbox for users logging in"" This reverts commitc2a2567b68. * Revert "Revert "Revert "Provision Fleet sandbox for users logging in""" This reverts commitacc178ea76. * update sandbox-login mobile styles * update sandbox-expired page to match latest wireframes * remove required: false and planned changes comments, update signup errors and behavior * update error * lint fix on updated error * Update error's indentation * remove added forgot-password flow, add redirect for sandbox users changing their password * Use fleetSandboxDemoKey to login to Fleet Sandbox, remove password changing flow * update bootstrap to give admin user an expired sandbox * Update signup.js * remove unused exits, revert password recovery email changes * required:false is implied if unspecified, so can be omitted * Remove defaultsTo: '', since it is not needed This applies the changes discussed in https://github.com/fleetdm/fleet/pull/6380#discussion_r929538495 It also makes two other trivial changes. * Eliminate another unnecessary require:false I think this one is actually baked into the sails-generate template. * remove custom password validation * update page name (sandbox-teleporter) and view action name * revert minor changes to existing files * update sandbox login friendlyName * Update unauthorized response to redirect to /login * Delete new-sandbox.less * update layouts and importer * add /fleetctl-preview route for old get-started page, update sandbox route * update signup action with changes from review, add retry() to cloud provisioner request * Update routes.js * add missing comma to route * update layout, fix typo in signup * Update sandbox-expired.ejs * lint fixes * Update download-sitemap.js * small whitespace changes, regenerate cloud-sdk * remove placeholder text in password inputs * add loading spinner to sandbox teleporter * add logout button to header nav * hide header on sandbox-teleporter * update errors, check if a user already exists before cloud provisioner request * Update sandbox-teleporter.page.js * Update sandbox-teleporter.page.js * Update signup.js * resize loading spinner, history.pushState() » history.replaceState() * send users who reset their password back to the fleetdm.com homepage * Add Zapier webhook request for sandbox signups * rebuild-scloud-sdk after resolving merge conflict * update zapier request error * Add comment w/ context about how Zapier responds with a 2xx even if there was a problem * Update links to /get-started to go to /try-fleet/register, change /get-started redirect * Revert changes to links * add /test-fleet-sandbox redirect, revert /try-fleet redirect * send logged out users to the sandbox login page when they go to /try-fleet/sandbox Co-authored-by: Mike McNeil <mikermcneil@users.noreply.github.com>
This commit is contained in:
parent
d728189dd5
commit
7974bdfa80
38 changed files with 1242 additions and 36 deletions
2
website/api/controllers/account/logout.js
vendored
2
website/api/controllers/account/logout.js
vendored
|
|
@ -41,7 +41,7 @@ actually logged in. (If they weren't, then this action is just a no-op.)`,
|
|||
// > Under the covers, this persists the now-logged-out session back
|
||||
// > to the underlying session store.
|
||||
if (!this.req.wantsJSON) {
|
||||
throw {redirect: '/customers/login'};
|
||||
throw {redirect: '/'};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ module.exports = {
|
|||
|
||||
description: 'Display "Articles" page.',
|
||||
|
||||
|
||||
inputs: {
|
||||
category: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'The category of article to display',
|
||||
description: 'The category of article to display.',
|
||||
defaultsTo: '',
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ module.exports = {
|
|||
},
|
||||
|
||||
message: {
|
||||
required: false,
|
||||
type: 'string',
|
||||
description: 'The custom message, in plain text.'
|
||||
}
|
||||
|
|
|
|||
2
website/api/controllers/download-sitemap.js
vendored
2
website/api/controllers/download-sitemap.js
vendored
|
|
@ -44,7 +44,7 @@ module.exports = {
|
|||
// ╩ ╩╩ ╩╝╚╝═╩╝ ╚═╝╚═╝═╩╝╚═╝═╩╝ ╩ ╩ ╩╚═╝╚═╝╚═╝
|
||||
let HAND_CODED_HTML_PAGES = [
|
||||
'/',
|
||||
'/get-started',
|
||||
'/fleetctl-preview',
|
||||
'/company/contact',
|
||||
'/queries',
|
||||
'/platform',
|
||||
|
|
|
|||
92
website/api/controllers/entrance/signup.js
vendored
92
website/api/controllers/entrance/signup.js
vendored
|
|
@ -32,11 +32,10 @@ the account verification message.)`,
|
|||
type: 'string',
|
||||
maxLength: 200,
|
||||
example: 'passwordlol',
|
||||
description: 'The unencrypted password to use for the new account.'
|
||||
description: 'The unhashed (plain text) password to use for the new account.'
|
||||
},
|
||||
|
||||
organization: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
maxLength: 120,
|
||||
example: 'The Sails company',
|
||||
|
|
@ -84,13 +83,75 @@ the account verification message.)`,
|
|||
description: 'The provided email address is already in use.',
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
fn: async function ({emailAddress, password, firstName, lastName, organization, signupReason}) {
|
||||
|
||||
if(!sails.config.custom.cloudProvisionerSecret){
|
||||
throw new Error('The authorization token for the cloud provisioner API (sails.config.custom.cloudProvisionerSecret) is missing! If you just want to test aspects of fleetdm.com locally, and are OK with the cloud provisioner failing if you try to use it, you can set a fake secret when starting a local server by lifting the server with "sails_custom__cloudProvisionerSecret=test sails lift"');
|
||||
}
|
||||
|
||||
var newEmailAddress = emailAddress.toLowerCase();
|
||||
|
||||
// Checking if a user with this email address exists in our database before we send a request to the cloud provisioner.
|
||||
if(await User.findOne({emailAddress: newEmailAddress})) {
|
||||
throw 'emailAlreadyInUse';
|
||||
}
|
||||
|
||||
// Provisioning a Fleet sandbox instance for the new user. Note: Because this is the only place where we provision Sandbox instances, We'll provision a Sandbox instance BEFORE
|
||||
// creating the new User record. This way, if this fails, we won't save the new record to the database, and the user will see an error on the signup form asking them to try again.
|
||||
|
||||
// Creating an expiration JS timestamp for the Fleet sandbox instance. NOTE: We send this value to the cloud provisioner API as an ISO 8601 string.
|
||||
let fleetSandboxExpiresAt = Date.now() + (24*60*60*1000);
|
||||
|
||||
// Creating a fleetSandboxDemoKey, this will be used for the user's password when we log them into their Sandbox instance.
|
||||
let fleetSandboxDemoKey = await sails.helpers.strings.uuid();
|
||||
|
||||
// Send a POST request to the cloud provisioner API
|
||||
let cloudProvisionerResponseData = await sails.helpers.http.post(
|
||||
'https://sandbox.fleetdm.com/new',
|
||||
{ // Request body
|
||||
'name': firstName + ' ' + lastName,
|
||||
'email': newEmailAddress,
|
||||
'password': fleetSandboxDemoKey, //« this provisioner API was originally designed to accept passwords, but rather than specifying the real plaintext password, since users always access Fleet Sandbox from their fleetdm.com account anyway, this generated demo key is used instead to avoid any confusion
|
||||
'sandbox_expiration': new Date(fleetSandboxExpiresAt).toISOString(), // sending expiration_timestamp as an ISO string.
|
||||
},
|
||||
{ // Request headers
|
||||
'Authorization':sails.config.custom.cloudProvisionerSecret
|
||||
}
|
||||
)
|
||||
.timeout(5000)
|
||||
.intercept(['requestFailed', 'non200Response'], (err)=>{
|
||||
// If we recieved a non-200 response from the cloud provisioner API, we'll throw a 500 error.
|
||||
return new Error('When attempting to provision a new user who just signed up ('+emailAddress+'), the cloud provisioner gave a non 200 response. The incomplete user record has not been saved in the database, and the user will be asked to try signing up again. Raw response received from provisioner: '+err.stack);
|
||||
});
|
||||
|
||||
if(!cloudProvisionerResponseData.URL) {
|
||||
// If we didn't receive a URL in the response from the cloud provisioner API, we'll throwing an error before we save the new user record and the user will need to try to sign up again.
|
||||
throw new Error(
|
||||
`When provisioning a Fleet Sandbox instance for a new user who just signed up (${emailAddress}), the response data from the cloud provisioner API was malformed. It did not contain a valid Fleet Sandbox instance URL in its expected "URL" property.
|
||||
The incomplete user record has not been saved in the database, and the user will be asked to try signing up again.
|
||||
Here is the malformed response data (parsed response body) from the cloud provisioner API: ${cloudProvisionerResponseData}`
|
||||
);
|
||||
}
|
||||
|
||||
// If "Try Fleet Sandbox" was provided as the signupReason, we'll send a request to Zapier to add this user to our CRM and make sure their Sandbox instance is live before we continue.
|
||||
if(signupReason === 'Try Fleet Sandbox') {
|
||||
// Start polling the /healthz endpoint of the created Fleet Sandbox instance, once it returns a 200 response, we'll continue.
|
||||
await sails.helpers.flow.until( async()=>{
|
||||
let healthCheckResponse = await sails.helpers.http.sendHttpRequest('GET', cloudProvisionerResponseData.URL+'/healthz')
|
||||
.timeout(5000)
|
||||
.tolerate('non200Response')
|
||||
.tolerate('requestFailed');
|
||||
if(healthCheckResponse) {
|
||||
return true;
|
||||
}
|
||||
}, 10000).intercept('tookTooLong', ()=>{
|
||||
return new Error('This newly provisioned Fleet Sandbox instance (for '+emailAddress+') is taking too long to respond with a 2xx status code, even after repeatedly polling the health check endpoint. Note that failed requests and non-2xx responses from the health check endpoint were ignored during polling. Search for a bit of non-dynamic text from this error message in the fleetdm.com source code for more info on exactly how this polling works.');
|
||||
});
|
||||
}
|
||||
|
||||
// Build up data for the new user record and save it to the database.
|
||||
// (Also use `fetch` to retrieve the new ID so that we can use it below.)
|
||||
var newUserRecord = await User.create(_.extend({
|
||||
|
|
@ -99,6 +160,9 @@ the account verification message.)`,
|
|||
organization,
|
||||
emailAddress: newEmailAddress,
|
||||
password: await sails.helpers.passwords.hashPassword(password),
|
||||
fleetSandboxURL: cloudProvisionerResponseData.URL,
|
||||
fleetSandboxExpiresAt,
|
||||
fleetSandboxDemoKey,
|
||||
tosAcceptedByIp: this.req.ip
|
||||
}, sails.config.custom.verifyEmailAddresses? {
|
||||
emailProofToken: await sails.helpers.strings.random('url-friendly'),
|
||||
|
|
@ -109,17 +173,6 @@ the account verification message.)`,
|
|||
.intercept({name: 'UsageError'}, 'invalid')
|
||||
.fetch();
|
||||
|
||||
// If billing feaures are enabled, save a new customer entry in the Stripe API.
|
||||
// Then persist the Stripe customer id in the database.
|
||||
if (sails.config.custom.enableBillingFeatures) {
|
||||
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
|
||||
emailAddress: newEmailAddress
|
||||
}).timeout(5000).retry();
|
||||
await User.updateOne({id: newUserRecord.id})
|
||||
.set({
|
||||
stripeCustomerId
|
||||
});
|
||||
}
|
||||
// Send a POST request to Zapier
|
||||
await sails.helpers.http.post(
|
||||
'https://hooks.zapier.com/hooks/catch/3627242/bqsf4rj/',
|
||||
|
|
@ -138,6 +191,17 @@ the account verification message.)`,
|
|||
sails.log.warn(`When a new user signed up, a lead/contact could not be verified in the CRM for this email address: ${newEmailAddress}. Raw error: ${err}`);
|
||||
return;
|
||||
});
|
||||
// If billing feaures are enabled, save a new customer entry in the Stripe API.
|
||||
// Then persist the Stripe customer id in the database.
|
||||
if (sails.config.custom.enableBillingFeatures) {
|
||||
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
|
||||
emailAddress: newEmailAddress
|
||||
}).timeout(5000).retry();
|
||||
await User.updateOne({id: newUserRecord.id})
|
||||
.set({
|
||||
stripeCustomerId
|
||||
});
|
||||
}
|
||||
// Store the user's new id in their session.
|
||||
this.req.session.userId = newUserRecord.id;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,11 @@ module.exports = {
|
|||
fn: async function () {
|
||||
|
||||
if (this.req.me) {
|
||||
throw {redirect: '/customers/new-license'};
|
||||
if(this.req.me.hasBillingCard){
|
||||
throw {redirect: '/customers/new-license'};
|
||||
} else {
|
||||
throw {redirect: '/try-fleet/sandbox'};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
|
|
|
|||
37
website/api/controllers/try-fleet/view-register.js
vendored
Normal file
37
website/api/controllers/try-fleet/view-register.js
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View register',
|
||||
|
||||
|
||||
description: 'Display "Register" page. Note: This page is the "signup" page skinned for Fleet Sandbox.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/try-fleet/register'
|
||||
},
|
||||
|
||||
redirect: {
|
||||
description: 'The requesting user is already logged in.',
|
||||
responseType: 'redirect'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// If the user is logged in, redirect them to the Fleet sandbox page.
|
||||
if (this.req.me) {
|
||||
throw {redirect: '/try-fleet/sandbox'};
|
||||
}
|
||||
|
||||
// Respond with view.
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
27
website/api/controllers/try-fleet/view-sandbox-expired.js
vendored
Normal file
27
website/api/controllers/try-fleet/view-sandbox-expired.js
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View sandbox expired',
|
||||
|
||||
|
||||
description: 'Display "Sandbox expired" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/try-fleet/sandbox-expired'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// Respond with view.
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
38
website/api/controllers/try-fleet/view-sandbox-login.js
vendored
Normal file
38
website/api/controllers/try-fleet/view-sandbox-login.js
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View Sandbox login',
|
||||
|
||||
|
||||
description: 'Display the "Sandbox Login" page. Note: This page is the "login" page skinned for Fleet Sandbox.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/try-fleet/sandbox-login'
|
||||
},
|
||||
|
||||
redirect: {
|
||||
description: 'The requesting user is already logged in.',
|
||||
responseType: 'redirect'
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// If the user is logged in, redirect them to the Fleet sandbox page.
|
||||
if (this.req.me) {
|
||||
throw {redirect: '/try-fleet/sandbox'};
|
||||
}
|
||||
|
||||
// Respond with view.
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
61
website/api/controllers/try-fleet/view-sandbox-teleporter-or-redirect-because-expired.js
vendored
Normal file
61
website/api/controllers/try-fleet/view-sandbox-teleporter-or-redirect-because-expired.js
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View sandbox teleporter or redirect because sandbox expired',
|
||||
|
||||
description:
|
||||
`Display "Sandbox teleporter" page (an auto-submitting interstitial HTML form used as a hack to grab a bit of HTML
|
||||
from the Fleet Sandbox instance, which sets browser localstorage to consider this user logged in and "teleports" them,
|
||||
magically authenticated, into their Fleet Sandbox instance running on a different domain), or redirect the user to
|
||||
a page about their sandbox instance being expired.`,
|
||||
|
||||
moreInfoUrl: 'https://github.com/fleetdm/fleet/pull/6380',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/try-fleet/sandbox-teleporter',
|
||||
description: 'This user is being logged into their Fleet Sandbox instance.'
|
||||
},
|
||||
|
||||
redirect: {
|
||||
description: 'This user does not have a valid Fleet Sandbox instance and is being redirected.',
|
||||
responseType: 'redirect'
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
if(!this.req.me) {
|
||||
throw {redirect: '/try-fleet/login' };
|
||||
}
|
||||
|
||||
if(!this.req.me.fleetSandboxURL) {
|
||||
throw new Error(`Consistency violation: The logged-in user's (${this.req.me.emailAddress}) fleetSandboxURL has somehow gone missing!`);
|
||||
}
|
||||
|
||||
if(!this.req.me.fleetSandboxExpiresAt) {
|
||||
throw new Error(`Consistency violation: The logged-in user's (${this.req.me.emailAddress}) fleetSandboxExpiresAt has somehow gone missing!`);
|
||||
}
|
||||
|
||||
if(!this.req.me.fleetSandboxDemoKey) {
|
||||
throw new Error(`Consistency violation: The logged-in user's (${this.req.me.emailAddress}) fleetSandboxDemoKey has somehow gone missing!`);
|
||||
}
|
||||
|
||||
// If this user's Fleet Sandbox instance is expired, we'll redirect them to the sandbox-expired page
|
||||
if(this.req.me.fleetSandboxExpiresAt < Date.now()){
|
||||
throw {redirect: '/try-fleet/sandbox-expired' };
|
||||
}
|
||||
|
||||
// Respond with view.
|
||||
return {
|
||||
hideHeaderOnThisPage: true,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
18
website/api/models/User.js
vendored
18
website/api/models/User.js
vendored
|
|
@ -66,7 +66,6 @@ module.exports = {
|
|||
|
||||
organization: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The organization the user works for.',
|
||||
maxLength: 120,
|
||||
example: 'The Sails Company',
|
||||
|
|
@ -171,6 +170,23 @@ without necessarily having a billing card.`
|
|||
example: 1502844074211
|
||||
},
|
||||
|
||||
fleetSandboxURL: {
|
||||
type: 'string',
|
||||
description: 'The URL of the Fleet sandbox instance that was provisioned for this user',
|
||||
example: 'https://billybobcat.sandbox.fleetdm.com',
|
||||
},
|
||||
|
||||
fleetSandboxExpiresAt: {
|
||||
type: 'number',
|
||||
description: 'An JS timestamp (epoch ms) representing when this user\'s fleet sandbox instance will expire',
|
||||
example: '1502844074211',
|
||||
},
|
||||
|
||||
fleetSandboxDemoKey: {
|
||||
type: 'string',
|
||||
description: 'The UUID that is used as the password of this user\'s Fleet Sandbox instance that is generated when the user signs up. Only used to log the user into their Fleet Sandbox instance while it is still live.',
|
||||
}
|
||||
|
||||
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
|
||||
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
|
||||
// ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝
|
||||
|
|
|
|||
2
website/api/responses/unauthorized.js
vendored
2
website/api/responses/unauthorized.js
vendored
|
|
@ -37,7 +37,7 @@ module.exports = function unauthorized() {
|
|||
delete req.session.userId;
|
||||
}
|
||||
|
||||
return res.redirect('/customers/login');
|
||||
return res.redirect('/login');
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
BIN
website/assets/images/arrow-right-blue-18x10@2x.png
vendored
Normal file
BIN
website/assets/images/arrow-right-blue-18x10@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 372 B |
BIN
website/assets/images/fleet-sandbox-300x244@2x.png
vendored
Normal file
BIN
website/assets/images/fleet-sandbox-300x244@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
2
website/assets/js/cloud.setup.js
vendored
2
website/assets/js/cloud.setup.js
vendored
|
|
@ -13,7 +13,7 @@
|
|||
Cloud.setup({
|
||||
|
||||
/* eslint-disable */
|
||||
methods: {"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostStatusWebhookEnabled","numWeeklyActiveUsers","hostsEnrolledByOperatingSystem","storedErrors","numHostsNotResponding","organization"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label"]},"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"]},"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","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"]}}
|
||||
methods: {"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostStatusWebhookEnabled","numWeeklyActiveUsers","hostsEnrolledByOperatingSystem","storedErrors","numHostsNotResponding","organization"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label"]},"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","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"]}}
|
||||
/* eslint-enable */
|
||||
|
||||
});
|
||||
|
|
|
|||
2
website/assets/js/pages/get-started.page.js
vendored
2
website/assets/js/pages/get-started.page.js
vendored
|
|
@ -14,7 +14,7 @@ parasails.registerPage('get-started', {
|
|||
// If the user navigated to this page from the 'try it now' button, we'll strip the '?tryitnow' from the url.
|
||||
if(window.location.search){
|
||||
// https://caniuse.com/mdn-api_history_replacestate
|
||||
window.history.replaceState({}, document.title, '/get-started' );
|
||||
window.history.replaceState({}, document.title, '/fleetctl-preview' );
|
||||
}
|
||||
},
|
||||
mounted: async function() {
|
||||
|
|
|
|||
55
website/assets/js/pages/try-fleet/register.page.js
vendored
Normal file
55
website/assets/js/pages/try-fleet/register.page.js
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
parasails.registerPage('register', {
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
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},
|
||||
password: {required: true, minLength: 8},
|
||||
},
|
||||
// Syncing / loading state
|
||||
syncing: false,
|
||||
// Server error state
|
||||
cloudError: '',
|
||||
},
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
// If the user navigated to this page from the 'try it now' button, we'll strip the '?tryitnow' from the url.
|
||||
if(window.location.search){
|
||||
// https://caniuse.com/mdn-api_history_replacestate
|
||||
window.history.replaceState({}, document.title, '/try-fleet/register' );
|
||||
}
|
||||
},
|
||||
mounted: async function() {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
|
||||
// Using handle-submitting to add firstName, and lastName values to our formData before sending it to signup.js
|
||||
handleSubmittingRegisterForm: async function(argins) {
|
||||
argins.firstName = argins.emailAddress.split('@')[0];
|
||||
argins.lastName = argins.emailAddress.split('@')[1];
|
||||
argins.signupReason = 'Try Fleet Sandbox';
|
||||
return await Cloud.signup.with(argins);
|
||||
},
|
||||
|
||||
// After the form is submitted, we'll redirect the user to their Fleet sandbox instance.
|
||||
submittedRegisterForm: async function() {
|
||||
this.syncing = true;
|
||||
window.location = '/try-fleet/sandbox';
|
||||
}
|
||||
}
|
||||
});
|
||||
25
website/assets/js/pages/try-fleet/sandbox-expired.page.js
vendored
Normal file
25
website/assets/js/pages/try-fleet/sandbox-expired.page.js
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
parasails.registerPage('sandbox-expired', {
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
data: {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
//…
|
||||
},
|
||||
mounted: async function() {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
//…
|
||||
}
|
||||
});
|
||||
47
website/assets/js/pages/try-fleet/sandbox-login.page.js
vendored
Normal file
47
website/assets/js/pages/try-fleet/sandbox-login.page.js
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
parasails.registerPage('sandbox-login', {
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
data: {
|
||||
// Main syncing/loading state for this page.
|
||||
syncing: false,
|
||||
|
||||
// Form data
|
||||
formData: { },
|
||||
|
||||
// For tracking client-side validation errors in our form.
|
||||
// > Has property set to `true` for each invalid property in `formData`.
|
||||
formErrors: { /* … */ },
|
||||
|
||||
// A set of validation rules for our form.
|
||||
// > The form will not be submitted if these are invalid.
|
||||
formRules: {
|
||||
emailAddress: { required: true, isEmail: true },
|
||||
password: { required: true },
|
||||
},
|
||||
|
||||
// Server error state for the form
|
||||
cloudError: '',
|
||||
},
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
//…
|
||||
},
|
||||
mounted: async function() {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
|
||||
submittedLoginForm: async function() {
|
||||
this.syncing = true;
|
||||
window.location = '/try-fleet/sandbox';
|
||||
}
|
||||
}
|
||||
});
|
||||
38
website/assets/js/pages/try-fleet/sandbox-teleporter.page.js
vendored
Normal file
38
website/assets/js/pages/try-fleet/sandbox-teleporter.page.js
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
parasails.registerPage('sandbox-teleporter', {
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
data: {
|
||||
// Main syncing/loading state for this page.
|
||||
syncing: false,
|
||||
|
||||
},
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
//…
|
||||
},
|
||||
mounted: async function() {
|
||||
|
||||
// Replacing this page with the fleetdm.com homepage in the user's browser history, so when users click the back button from their Sandbox instance, they won't be redirected to their Sandbox instance.
|
||||
window.history.replaceState({}, '', '/');
|
||||
|
||||
// Binding an event handler to 'onpageshow', if a user navigates to a locally cached version of this page (e.g., A Safari user clicking the back button from their Fleet Sandbox), they will be taken to the fleetdm.com homepage.
|
||||
window.onpageshow = function(event) {
|
||||
if(event.persisted) {
|
||||
window.location = '/';
|
||||
}
|
||||
};
|
||||
// Confused? Understandable, this approach is a bit unusual. See this page's view action for more info on what this code is doing and why, as well as a link where you can read more information.
|
||||
document.forms['demologin'].submit();
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
//…
|
||||
}
|
||||
});
|
||||
4
website/assets/styles/importer.less
vendored
4
website/assets/styles/importer.less
vendored
|
|
@ -49,6 +49,10 @@
|
|||
@import 'pages/customers/new-license.less';
|
||||
@import 'pages/customers/dashboard.less';
|
||||
@import 'pages/landing.less';
|
||||
@import 'pages/try-fleet/sandbox-login.less';
|
||||
@import 'pages/try-fleet/register.less';
|
||||
@import 'pages/try-fleet/sandbox-teleporter.less';
|
||||
@import 'pages/try-fleet/sandbox-expired.less';
|
||||
@import 'pages/sales-one-pager.less';
|
||||
@import 'pages/query-detail.less';
|
||||
@import 'pages/query-library.less';
|
||||
|
|
|
|||
53
website/assets/styles/pages/try-fleet/register.less
vendored
Normal file
53
website/assets/styles/pages/try-fleet/register.less
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#register {
|
||||
padding-top: 120px;
|
||||
background-color: #FFF;
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
font-weight: 800;
|
||||
}
|
||||
a {
|
||||
color: @core-vibrant-blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
input {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
input:focus-visible {
|
||||
border: 1px solid @core-vibrant-blue;
|
||||
outline: none;
|
||||
}
|
||||
input::placeholder {
|
||||
color: #8B8FA2;
|
||||
}
|
||||
[purpose='sandbox-image'] {
|
||||
width: 300px;
|
||||
margin-right: 65px;
|
||||
}
|
||||
[purpose='error-message'] {
|
||||
font-size: 16px;
|
||||
}
|
||||
[parasails-component='cloud-error'] {
|
||||
padding: 16px 10px 16px 16px;
|
||||
}
|
||||
[parasails-component='ajax-button'] {
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
padding-top: 17.5px;
|
||||
padding-bottom: 17.5px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
padding-top: 0px;
|
||||
[purpose='sandbox-image'] {
|
||||
margin-right: 0px;
|
||||
margin-bottom: 16px;
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
website/assets/styles/pages/try-fleet/sandbox-expired.less
vendored
Normal file
69
website/assets/styles/pages/try-fleet/sandbox-expired.less
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#sandbox-expired {
|
||||
|
||||
padding-top: 120px;
|
||||
background-color: #FFF;
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
font-weight: 800;
|
||||
}
|
||||
a {
|
||||
color: @core-vibrant-blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
input {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
input:focus-visible {
|
||||
border: 1px solid @core-vibrant-blue;
|
||||
outline: none;
|
||||
}
|
||||
input::placeholder {
|
||||
color: #8B8FA2;
|
||||
}
|
||||
[purpose='sandbox-image'] {
|
||||
width: 300px;
|
||||
margin-right: 65px;
|
||||
}
|
||||
[purpose='error-message'] {
|
||||
font-size: 16px;
|
||||
}
|
||||
[purpose='next-steps-button'] {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
padding: 16px;
|
||||
img {
|
||||
display: inline;
|
||||
height: 24px;
|
||||
width: auto;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
[parasails-component='cloud-error'] {
|
||||
padding: 16px 10px 16px 16px;
|
||||
}
|
||||
[parasails-component='ajax-button'] {
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
padding-top: 17.5px;
|
||||
padding-bottom: 17.5px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
padding-top: 0px;
|
||||
[purpose='sandbox-image'] {
|
||||
margin-right: 0px;
|
||||
margin-bottom: 16px;
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
53
website/assets/styles/pages/try-fleet/sandbox-login.less
vendored
Normal file
53
website/assets/styles/pages/try-fleet/sandbox-login.less
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#sandbox-login {
|
||||
padding-top: 120px;
|
||||
background-color: #FFF;
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
font-weight: 800;
|
||||
}
|
||||
a {
|
||||
color: @core-vibrant-blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
input {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
input:focus-visible {
|
||||
border: 1px solid @core-vibrant-blue;
|
||||
outline: none;
|
||||
}
|
||||
input::placeholder {
|
||||
color: #8B8FA2;
|
||||
}
|
||||
[purpose='sandbox-image'] {
|
||||
width: 300px;
|
||||
margin-right: 65px;
|
||||
}
|
||||
[purpose='error-message'] {
|
||||
font-size: 14px;
|
||||
}
|
||||
[parasails-component='cloud-error'] {
|
||||
padding: 16px 10px 16px 16px;
|
||||
}
|
||||
[parasails-component='ajax-button'] {
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
padding-top: 17.5px;
|
||||
padding-bottom: 17.5px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
padding-top: 0px;
|
||||
[purpose='sandbox-image'] {
|
||||
margin-right: 0px;
|
||||
margin-bottom: 16px;
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
website/assets/styles/pages/try-fleet/sandbox-teleporter.less
vendored
Normal file
17
website/assets/styles/pages/try-fleet/sandbox-teleporter.less
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#sandbox-teleporter {
|
||||
padding-top: 30vh;
|
||||
[purpose='loading-spinner'] {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 3px solid @core-vibrant-blue;
|
||||
border-bottom-color: transparent;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
animation: rotation 1s linear infinite;
|
||||
}
|
||||
@keyframes rotation {
|
||||
0% {transform: rotate(0deg);}
|
||||
100% {transform: rotate(360deg);}
|
||||
}
|
||||
}
|
||||
5
website/config/bootstrap.js
vendored
5
website/config/bootstrap.js
vendored
|
|
@ -15,7 +15,7 @@ module.exports.bootstrap = async function() {
|
|||
var path = require('path');
|
||||
|
||||
// This bootstrap version indicates what version of fake data we're dealing with here.
|
||||
var HARD_CODED_DATA_VERSION = 0;
|
||||
var HARD_CODED_DATA_VERSION = 1;
|
||||
|
||||
// This path indicates where to store/look for the JSON file that tracks the "last run bootstrap info"
|
||||
// locally on this development computer (if we happen to be on a development computer).
|
||||
|
|
@ -65,6 +65,9 @@ module.exports.bootstrap = async function() {
|
|||
lastName: 'Dahl',
|
||||
organization: 'Golaith Industries',
|
||||
isSuperAdmin: true,
|
||||
fleetSandboxURL: 'http://example.com',
|
||||
fleetSandboxExpiresAt: 1,
|
||||
fleetSandboxDemoKey: await sails.helpers.strings.uuid(),
|
||||
password: await sails.helpers.passwords.hashPassword('abc123')
|
||||
}).fetch();
|
||||
|
||||
|
|
|
|||
3
website/config/policies.js
vendored
3
website/config/policies.js
vendored
|
|
@ -37,5 +37,8 @@ module.exports.policies = {
|
|||
'articles/*': true,
|
||||
'reports/*': true,
|
||||
'view-sales-one-pager': true,
|
||||
'try-fleet/view-register': true,
|
||||
'try-fleet/view-sandbox-login': true,
|
||||
'try-fleet/view-sandbox-teleporter-or-redirect-because-expired': true,
|
||||
|
||||
};
|
||||
|
|
|
|||
38
website/config/routes.js
vendored
38
website/config/routes.js
vendored
|
|
@ -26,12 +26,12 @@ module.exports.routes = {
|
|||
}
|
||||
},
|
||||
|
||||
'GET /get-started': {
|
||||
action: 'view-get-started' ,
|
||||
'GET /fleetctl-preview': {
|
||||
action: 'view-get-started',
|
||||
locals: {
|
||||
currentPage: 'get started',
|
||||
pageTitleForMeta: 'Get started | Fleet for osquery',
|
||||
pageDescriptionForMeta: 'Learn about getting started with Fleet.'
|
||||
pageTitleForMeta: 'fleetctl preview | Fleet for osquery',
|
||||
pageDescriptionForMeta: 'Learn about getting started with Fleet using fleetctl.'
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -180,6 +180,33 @@ module.exports.routes = {
|
|||
},
|
||||
},
|
||||
|
||||
'GET /try-fleet/register': {
|
||||
action: 'try-fleet/view-register',
|
||||
locals: {
|
||||
layout: 'layouts/layout-sandbox',
|
||||
}
|
||||
},
|
||||
|
||||
'GET /try-fleet/login': {
|
||||
action: 'try-fleet/view-sandbox-login',
|
||||
locals: {
|
||||
layout: 'layouts/layout-sandbox',
|
||||
}
|
||||
},
|
||||
|
||||
'GET /try-fleet/sandbox': {
|
||||
action: 'try-fleet/view-sandbox-teleporter-or-redirect-because-expired',
|
||||
locals: {
|
||||
layout: 'layouts/layout-sandbox',
|
||||
},
|
||||
},
|
||||
|
||||
'GET /try-fleet/sandbox-expired': {
|
||||
action: 'try-fleet/view-sandbox-expired',
|
||||
locals: {
|
||||
layout: 'layouts/layout-sandbox',
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// ╦ ╔═╗╔═╗╔═╗╔═╗╦ ╦ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗
|
||||
|
|
@ -250,7 +277,9 @@ module.exports.routes = {
|
|||
'GET /docs/using-fleet/updating-fleet': '/docs/deploying/upgrading-fleet',
|
||||
'GET /blog': '/articles',
|
||||
'GET /brand': '/logos',
|
||||
'GET /get-started': '/fleetctl-preview',
|
||||
'GET /g': (req,res)=> { let originalQueryStringWithAmp = req.url.match(/\?(.+)$/) ? '&'+req.url.match(/\?(.+)$/)[1] : ''; return res.redirect(301, sails.config.custom.baseUrl+'/?meet-fleet'+originalQueryStringWithAmp); },
|
||||
'GET /test-fleet-sandbox': '/try-fleet/register',
|
||||
|
||||
// Sitemap
|
||||
// =============================================================================================================
|
||||
|
|
@ -299,5 +328,4 @@ module.exports.routes = {
|
|||
'POST /api/v1/customers/save-billing-info-and-subscribe': { action: 'customers/save-billing-info-and-subscribe' },
|
||||
'POST /api/v1/entrance/update-password-and-login': { action: 'entrance/update-password-and-login' },
|
||||
'POST /api/v1/deliver-demo-signup': { action: 'deliver-demo-signup' },
|
||||
|
||||
};
|
||||
|
|
|
|||
4
website/views/layouts/layout-customer.ejs
vendored
4
website/views/layouts/layout-customer.ejs
vendored
|
|
@ -220,6 +220,10 @@
|
|||
<script src="/js/pages/reports/state-of-device-management.page.js"></script>
|
||||
<script src="/js/pages/sales-one-pager.page.js"></script>
|
||||
<script src="/js/pages/transparency.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/register.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/sandbox-expired.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/sandbox-login.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/sandbox-teleporter.page.js"></script>
|
||||
<!--SCRIPTS END-->
|
||||
|
||||
<% /* Display an overlay if the current browser is not supported.
|
||||
|
|
|
|||
4
website/views/layouts/layout-landing.ejs
vendored
4
website/views/layouts/layout-landing.ejs
vendored
|
|
@ -227,6 +227,10 @@
|
|||
<script src="/js/pages/reports/state-of-device-management.page.js"></script>
|
||||
<script src="/js/pages/sales-one-pager.page.js"></script>
|
||||
<script src="/js/pages/transparency.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/register.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/sandbox-expired.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/sandbox-login.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/sandbox-teleporter.page.js"></script>
|
||||
<!--SCRIPTS END-->
|
||||
|
||||
<% /* Display an overlay if the current browser is not supported.
|
||||
|
|
|
|||
430
website/views/layouts/layout-sandbox.ejs
vendored
Normal file
430
website/views/layouts/layout-sandbox.ejs
vendored
Normal file
File diff suppressed because one or more lines are too long
17
website/views/layouts/layout.ejs
vendored
17
website/views/layouts/layout.ejs
vendored
|
|
@ -189,8 +189,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<a href="/pricing" class="d-flex align-items-center mb-2 pb-3" style="text-decoration: none; font-weight: 700;">Pricing</a>
|
||||
<a style="padding: 4px 16px; line-height: 24px; width: 100px" class="btn btn-sm btn-primary align-items-center d-flex " href="/get-started?tryitnow" >Try it out</a>
|
||||
<a href="/pricing" class="d-flex align-items-center" style="text-decoration: none; font-weight: 700;">Pricing</a>
|
||||
<% if(_.has(me, 'id')) {%>
|
||||
<hr>
|
||||
<a href="/logout" class="d-flex mt-2 text-decoration-none" style="text-decoration: none; line-height: 23px;">Log out</a>
|
||||
<% }%>
|
||||
<a style="padding: 4px 16px; line-height: 24px; width: 100px" class="btn btn-sm btn-primary align-items-center d-flex mt-4" href="/get-started?tryitnow" >Try it out</a>
|
||||
</div>
|
||||
</div>
|
||||
<%/* Desktop Navigation bar */%>
|
||||
|
|
@ -240,7 +244,10 @@
|
|||
<iframe src="//ghbtns.com/github-btn.html?user=fleetdm&repo=fleet&type=watch&count=true"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20"></iframe>
|
||||
</span>
|
||||
<a style="padding: 4px 16px; line-height: 24px; color: #FFFFFF" class="btn btn-sm btn-primary align-items-center d-flex" href="/get-started?tryitnow" >Try it out</a>
|
||||
<a style="padding: 4px 16px; line-height: 24px; color: #FFFFFF" class="btn btn-sm btn-primary align-items-center d-flex" href="/get-started" >Try it out</a>
|
||||
<% if(_.has(me, 'id')) {%>
|
||||
<a href="/logout" class="justify-content-end pl-4 py-2 text-decoration-none" style="text-decoration: none; line-height: 23px;">Log out</a>
|
||||
<% }%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -452,6 +459,10 @@
|
|||
<script src="/js/pages/reports/state-of-device-management.page.js"></script>
|
||||
<script src="/js/pages/sales-one-pager.page.js"></script>
|
||||
<script src="/js/pages/transparency.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/register.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/sandbox-expired.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/sandbox-login.page.js"></script>
|
||||
<script src="/js/pages/try-fleet/sandbox-teleporter.page.js"></script>
|
||||
<!--SCRIPTS END-->
|
||||
|
||||
<% /* Display an overlay if the current browser is not supported.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<p>
|
||||
If the email you entered is associated with a Fleet account, you should receive a recovery email shortly. If the email doesn’t arrive, please try again, or <a href="/contact">contact support</a>.
|
||||
</p>
|
||||
<p class="text-center"><a purpose="back-button" class="btn btn-info" href="/login"><span>Back to login</span></a></p>
|
||||
<p class="text-center"><a purpose="back-button" class="btn btn-info" href="/"><span>Back to homepage</span></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@
|
|||
<ajax-form action="updatePasswordAndLogin" :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="password">New password</label>
|
||||
<input class="form-control" id="password" name="password" type="password" :class="[formErrors.password ? 'is-invalid' : '']" v-model.trim="formData.password" placeholder="••••••••" autocomplete="new-password" focus-first>
|
||||
<input class="form-control" id="password" name="password" type="password" :class="[formErrors.password ? 'is-invalid' : '']" v-model.trim="formData.password" autocomplete="new-password" focus-first>
|
||||
<div class="invalid-feedback" v-if="formErrors.password === 'required'">Please enter a password.</div>
|
||||
<div class="invalid-feedback" v-if="formErrors.password === 'minLength'">Password too short.</div>
|
||||
<p class="mt-2"> Minimum length is 8 characters</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm-password">Confirm password</label>
|
||||
<input class="form-control" id="confirm-password" name="confirm-password" type="password" :class="[formErrors.confirmPassword ? 'is-invalid' : '']" v-model.trim="formData.confirmPassword" placeholder="••••••••" autocomplete="new-password">
|
||||
<input class="form-control" id="confirm-password" name="confirm-password" type="password" :class="[formErrors.confirmPassword ? 'is-invalid' : '']" v-model.trim="formData.confirmPassword" autocomplete="new-password">
|
||||
<div class="invalid-feedback" v-if="formErrors.password === 'minLength'">Password too short.</div>
|
||||
<div class="invalid-feedback" v-if="formErrors.confirmPassword">Your new password and confirmation do not match.</div>
|
||||
</div>
|
||||
|
|
|
|||
41
website/views/pages/try-fleet/register.ejs
vendored
Normal file
41
website/views/pages/try-fleet/register.ejs
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<div id="register" v-cloak>
|
||||
<div style="max-width: 750px; padding-top: 80px; padding-bottom: 80px;" class="container d-flex flex-md-row flex-column justify-content-center align-items-center mx-auto px-4 px-md-3">
|
||||
<div>
|
||||
<img purpose="sandbox-image" style="width: 300px; height: auto;" src="/images/fleet-sandbox-300x244@2x.png" alt="Play in Fleet sandbox">
|
||||
</div>
|
||||
<div style="max-width: 400px;" class="flex-column d-flex">
|
||||
<h2>Play in Fleet Sandbox</h2>
|
||||
<p>
|
||||
Fleet Sandbox is designed for testing Fleet features only.<br>
|
||||
<a href="/docs/deploying">Click here</a> for production-ready deployments.
|
||||
</p>
|
||||
<div class="pt-4">
|
||||
<ajax-form :handle-submitting="handleSubmittingRegisterForm" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-errors.sync="formErrors" :form-data="formData" :form-rules="formRules" @submitted="submittedRegisterForm()">
|
||||
<div class="form-group mb-3">
|
||||
<input id="emailAddress" type="text" class="form-control d-flex w-100" :class="[formErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="formData.emailAddress" placeholder="Sign up with email">
|
||||
<div class="invalid-feedback mt-2">This doesn’t appear to be a valid email address</div>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<input id="password" type="password" class="form-control d-flex w-100" :class="[formErrors.password ? 'is-invalid' : '']" v-model.trim="formData.password" placeholder="Choose a password">
|
||||
<div class="invalid-feedback mt-2" v-if="formErrors.password === 'minLength'">Password too short.</div>
|
||||
<div class="invalid-feedback mt-2" v-if="formErrors.password === 'required'">Please enter a password.</div>
|
||||
</div>
|
||||
<ajax-button style="height: 53px;" purpose="submit-button" spinner="true" type="submit" :syncing="syncing" class="btn btn-block btn-lg btn-info" v-if="!cloudError">Sign up</ajax-button>
|
||||
<div class="d-flex flex-column" v-if="cloudError==='emailAlreadyInUse'">
|
||||
<cloud-error class="my-0">
|
||||
<p purpose="error-message">This email is already linked to a Fleet account.</p>
|
||||
</cloud-error>
|
||||
<a class="mx-auto font-weight-bold d-flex align-items-center py-3" href="/try-fleet/login">Sign in with existing account <img alt="A blue arrow pointing right" style="height: 10px; margin-left: 6px;" src="/images/arrow-right-blue-18x10@2x.png"></a>
|
||||
<ajax-button style="height: 53px;" purpose="submit-button" spinner="true" type="submit" :syncing="syncing" class="btn btn-block btn-lg btn-info">Try again</ajax-button>
|
||||
</div>
|
||||
<cloud-error purpose="cloud-error" v-else-if="cloudError"></cloud-error>
|
||||
</ajax-form>
|
||||
</div>
|
||||
<div class="d-flex flex-column" v-if="!cloudError">
|
||||
<a class="mx-auto d-flex py-3" href="/try-fleet/login">I have an account</a>
|
||||
<p class="pt-4">By continuing you agree to the<br><a href="https://docs.google.com/document/d/1OM6YDVIs7bP8wg6iA3VG13X086r64tWDqBSRudG4a0Y" target="_blank">terms of service</a> and <a href="https://docs.google.com/document/d/17i_g1aGpnuSmlqj35-yHJiwj7WRrLdC_Typc1Yb7aBE" target="_blank">privacy policy</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
||||
20
website/views/pages/try-fleet/sandbox-expired.ejs
vendored
Normal file
20
website/views/pages/try-fleet/sandbox-expired.ejs
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<div id="sandbox-expired" v-cloak>
|
||||
<div style="max-width: 800px; padding-top: 80px; padding-bottom: 80px; margin-top: auto; margin-bottom: auto" class="container d-flex flex-md-row flex-column justify-content-center align-items-center mx-auto px-4 px-md-3">
|
||||
<div>
|
||||
<img purpose="sandbox-image" style="height: auto;" src="/images/fleet-sandbox-300x244@2x.png" alt="Play in Fleet sandbox">
|
||||
</div>
|
||||
<div style="max-width: 400px;" class="flex-column d-flex">
|
||||
<h2 class="pb-2">Thanks for trying Fleet Sandbox</h2>
|
||||
<p>
|
||||
Your trial period for Fleet Sandbox has expired.<br>
|
||||
</p>
|
||||
<a href="https://calendly.com/fleetdm/demo?utm_source=sandbox+expired+page" style="color: #FFF" class="btn btn-info" purpose="next-steps-button">Schedule a demo</a>
|
||||
<a href="/slack" target="_blank" style="color: #000" class="d-flex btn btn-md btn-outline-secondary justify-content-center align-items-center mt-3" purpose="next-steps-button">
|
||||
<img class="pr-3" alt="Slack logo" src="/images/logo-slack-24x24@2x.png"/>
|
||||
Join the community on Slack
|
||||
</a>
|
||||
<p class="mt-3"> Got questions? <a href="/contact">Contact support.</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
||||
42
website/views/pages/try-fleet/sandbox-login.ejs
vendored
Normal file
42
website/views/pages/try-fleet/sandbox-login.ejs
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<div id="sandbox-login" v-cloak>
|
||||
<div style="max-width: 750px; padding-top: 80px; padding-bottom: 80px;" class="container d-flex flex-md-row flex-column justify-content-center align-items-center mx-auto px-4 px-md-3">
|
||||
<div>
|
||||
<img purpose="sandbox-image" style="width: 300px; height: auto;" src="/images/fleet-sandbox-300x244@2x.png" alt="Play in Fleet sandbox">
|
||||
</div>
|
||||
<div style="max-width: 400px;" class="flex-column d-flex">
|
||||
<h2>Play in Fleet Sandbox</h2>
|
||||
<p>
|
||||
Fleet Sandbox is designed for testing Fleet features only.<br>
|
||||
<a href="/docs/deploying">Click here</a> for production-ready deployments.
|
||||
</p>
|
||||
<div class="pt-4">
|
||||
<ajax-form action="login" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-errors.sync="formErrors" :form-data="formData" :form-rules="formRules" @submitted="submittedLoginForm()" >
|
||||
<div class="form-group mb-3">
|
||||
<input id="emailAddress" type="text" class="form-control d-flex w-100" :class="[formErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="formData.emailAddress" placeholder="Email">
|
||||
<div class="invalid-feedback mt-2">This doesn’t appear to be a valid email address</div>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<input id="password" type="password" class="form-control d-flex w-100" :class="[formErrors.password ? 'is-invalid' : '']" v-model.trim="formData.password" placeholder="Password">
|
||||
<div class="invalid-feedback mt-2" v-if="formErrors.password === 'minLength'">Password too short.</div>
|
||||
<div class="invalid-feedback mt-2" v-if="formErrors.password === 'required'">Please enter a password.</div>
|
||||
</div>
|
||||
<cloud-error v-if="cloudError==='badCombo' || cloudError==='noUser'">
|
||||
<p purpose="error-message">Something's not right with your email or password</p>
|
||||
</cloud-error>
|
||||
<cloud-error purpose="cloud-error" v-else-if="cloudError"></cloud-error>
|
||||
<ajax-button style="height: 53px;" purpose="submit-button" spinner="true" type="submit" :syncing="syncing" class="btn btn-block btn-lg btn-info">Sign in</ajax-button>
|
||||
</ajax-form>
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<div class="mx-sm-auto d-flex flex-column flex-sm-row justify-content-start align-items-start justify-content-sm-center py-3">
|
||||
<a href="/customers/forgot-password">Forgot your password?</a>
|
||||
<span class="d-none d-sm-inline"> | </span>
|
||||
<a class="pt-2 pt-sm-0" href="/try-fleet/register">Create an account</a>
|
||||
</div>
|
||||
<p class="pt-4">By continuing you agree to the<br><a href="https://docs.google.com/document/d/1OM6YDVIs7bP8wg6iA3VG13X086r64tWDqBSRudG4a0Y" target="_blank">terms of service</a> and <a href="https://docs.google.com/document/d/17i_g1aGpnuSmlqj35-yHJiwj7WRrLdC_Typc1Yb7aBE" target="_blank">privacy policy</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
||||
|
||||
13
website/views/pages/try-fleet/sandbox-teleporter.ejs
vendored
Normal file
13
website/views/pages/try-fleet/sandbox-teleporter.ejs
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<div id="sandbox-teleporter" v-cloak>
|
||||
<!-- Confused? Understandable, this approach is a bit unusual. See this page's view action for more info on what this code is doing and why, as well as a link where you can read more information. -->
|
||||
<form method="post" action="<%=me.fleetSandboxURL%>/api/latest/fleet/demologin" id="demologin">
|
||||
<input type="hidden" name="email" :value="me.emailAddress" />
|
||||
<input type="hidden" name="password" :value="me.fleetSandboxDemoKey" />
|
||||
<input class="d-none" type="submit"/>
|
||||
</form>
|
||||
<div class="d-flex flex-column align-items-center justify-content-center h-100">
|
||||
<div purpose="loading-spinner">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
||||
Loading…
Reference in a new issue