Merge branch 'main' into feat-software-installers

This commit is contained in:
Roberto Dip 2024-05-16 19:43:55 -03:00 committed by GitHub
commit 75e86ff321
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 217 additions and 110 deletions

View file

@ -37,13 +37,19 @@ jobs:
run: |
npm install && npm run test
- name: Set the version
working-directory: ./ee/fleetd-chrome
run: |
echo "FLEETD_CHROME_VERSION=$(npm pkg get version --workspaces=false | tr -d \")" >> $GITHUB_ENV
- name: Build & sign extension
working-directory: ./ee/fleetd-chrome
env:
CHROME_SIGNING_KEY: ${{ secrets.FLEETD_CHROME_SIGNING_KEY_BETA }}
run: |
echo -e 'FLEET_URL=""\nFLEET_ENROLL_SECRET=""' > .env
npm install && npm run build
npm run build
sed -i "s/FLEETD_CHROME_VERSION/${{ env.FLEETD_CHROME_VERSION }}/g" updates-beta.xml
echo "$CHROME_SIGNING_KEY" > chrome.pem
/usr/bin/google-chrome --pack-extension=./dist --pack-extension-key=chrome.pem

View file

@ -38,13 +38,19 @@ jobs:
run: |
npm install && npm run test
- name: Set the version
working-directory: ./ee/fleetd-chrome
run: |
echo "FLEETD_CHROME_VERSION=$(npm pkg get version --workspaces=false | tr -d \")" >> $GITHUB_ENV
- name: Build & sign extension
working-directory: ./ee/fleetd-chrome
env:
CHROME_SIGNING_KEY: ${{ secrets.FLEETD_CHROME_SIGNING_KEY }}
run: |
echo -e 'FLEET_URL=""\nFLEET_ENROLL_SECRET=""' > .env
npm install && npm run build
npm run build
sed -i "s/FLEETD_CHROME_VERSION/${{ env.FLEETD_CHROME_VERSION }}/g" updates.xml
echo "$CHROME_SIGNING_KEY" > chrome.pem
/usr/bin/google-chrome --pack-extension=./dist --pack-extension-key=chrome.pem

View file

@ -1,3 +1,9 @@
## fleetd-chrome 1.3.1 (May 20, 2024)
* Fixed bug where fleetd-chrome sent multiple read requests to Fleet server at the same time.
* Improved console log output messages when Fleet server is down.
## fleetd-chrome 1.3.0 (Apr 29, 2024)
* Created a fix to recover after a rare RuntimeError coming from sqlite web assembly code by reinitializing the DB.

View file

@ -67,18 +67,17 @@ npm run test
## Release
1. Update CHANGELOG.md by running `version="X.X.X" make changelog-chrome`
1. At the top of the repo, update CHANGELOG.md by running `version="X.X.X" make changelog-chrome`
2. Review CHANGELOG.md
3. Run `npm version X.X.X` to update the version in `package.json` and `package-lock.json`
4. Update [updates.xml](./updates.xml) and [updates-beta.xml](./updates-beta.xml) versions.
5. Commit the changes and tag the commit with `fleetd-chrome-vX.X.X-beta`. This will trigger the beta release workflow.
6. Once the beta release is tested and PR merged, tag the commit with `fleetd-chrome-vX.X.X`. This will trigger the release workflow.
7. Announce the release in the #help-engineering channel in Slack.
3. At `ee/fleetd-chrome`, run `npm version X.X.X` to update the version in `package.json` and `package-lock.json`
4. Commit the changes and tag the commit with `fleetd-chrome-vX.X.X-beta`. This will trigger the beta release workflow.
5. Once the beta release is tested and PR merged, tag the commit with `fleetd-chrome-vX.X.X`. This will trigger the release workflow.
6. Announce the release in the #help-engineering channel in Slack.
Release a new version via GitHub automation. Update the [package.json](./package.json) and [updates.xml](./updates.xml) versions, then tag a commit with `fleetd-chrome-vX.X.X` to kick off the build and deploy. The build is automatically uploaded to R2 and properly configured clients should be able to update immediately when the job completes. Note that automatic updates seem to only happen about once a day in Chrome -- Hit the "Update" button in `chrome://extensions` to trigger the update manually.
Using GitHub Actions, the build is automatically uploaded to R2 and properly configured clients should be able to update immediately when the job completes. Note that automatic updates seem to only happen about once a day in Chrome -- Hit the "Update" button in `chrome://extensions` to trigger the update manually.
### Beta releases
Beta releases are pushed to `https://chrome-beta.fleetdm.com/updates.xml` with the extension ID `bfleegjcoffelppfmadimianphbcdjkb`.
Kick off a beta release by updating the [package.json](./package.json) and [updates-beta.xml](./updates-beta.xml) versions, then tag a commit with `fleetd-chrome-vX.X.X-beta` to kick off the build and deploy.
Kick off a beta release by updating the [package.json](./package.json), then tag a commit with `fleetd-chrome-vX.X.X-beta` to kick off the build and deploy.

View file

@ -1 +0,0 @@
Fixed bug where fleetd-chrome sent multiple read requests to Fleet server at the same time.

View file

@ -1 +0,0 @@
Improved console log output messages when Fleet server is down.

View file

@ -1,12 +1,12 @@
{
"name": "fleetd-for-chrome",
"version": "1.3.0",
"version": "1.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fleetd-for-chrome",
"version": "1.3.0",
"version": "1.3.1",
"dependencies": {
"async-mutex": "^0.5.0",
"dotenv": "^16.0.3",

View file

@ -1,7 +1,7 @@
{
"name": "fleetd-for-chrome",
"description": "Extension for Fleetd on ChromeOS",
"version": "1.3.0",
"version": "1.3.1",
"dependencies": {
"async-mutex": "^0.5.0",
"dotenv": "^16.0.3",

View file

@ -1,6 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='bfleegjcoffelppfmadimianphbcdjkb'>
<updatecheck codebase='https://chrome-beta.fleetdm.com/fleetd.crx' version='1.3.0' />
<updatecheck codebase='https://chrome-beta.fleetdm.com/fleetd.crx' version='FLEETD_CHROME_VERSION' />
</app>
</gupdate>

View file

@ -1,6 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='fleeedmmihkfkeemmipgmhhjemlljidg'>
<updatecheck codebase='https://chrome.fleetdm.com/fleetd.crx' version='1.3.0' />
<updatecheck codebase='https://chrome.fleetdm.com/fleetd.crx' version='FLEETD_CHROME_VERSION' />
</app>
</gupdate>

View file

@ -71,31 +71,18 @@ module.exports = {
`Name: ${firstName + ' ' + lastName}, Email: ${emailAddress}, Message: ${message ? message : 'No message.'}`
});
await sails.helpers.salesforce.updateOrCreateContactAndAccount.with({
emailAddress: emailAddress,
firstName: firstName,
lastName: lastName,
});
// Send a POST request to Zapier
await sails.helpers.http.post(
'https://hooks.zapier.com/hooks/catch/3627242/3cxcriz/',
{
'emailAddress': emailAddress,
'firstName': firstName,
'lastName': lastName,
'message': message,
'webhookSecret': sails.config.custom.zapierSandboxWebhookSecret
}
)
.timeout(5000)
.tolerate(['non200Response', 'requestFailed'], (err)=>{
// Note that Zapier responds with a 2xx status code even if something goes wrong, so just because this message is not logged doesn't mean everything is hunky dory. More info: https://github.com/fleetdm/fleet/pull/6380#issuecomment-1204395762
sails.log.warn(`When a user submitted a contact form message, a lead/contact could not be updated in the CRM for this email address: ${emailAddress}. Raw error: ${err}`);
return;
});
// Use timers.setImmediate() to update/create CRM records in the background.
require('timers').setImmediate(async ()=>{
await sails.helpers.salesforce.updateOrCreateContactAndAccountAndCreateLead.with({
emailAddress: emailAddress,
firstName: firstName,
lastName: lastName,
leadSource: 'Website - Contact forms',
leadDescription: `Sent a contact form message: ${message}`,
}).tolerate((err)=>{
sails.log.warn(`Background task failed: When a user submitted a contact form message, a lead/contact could not be updated in the CRM for this email address: ${emailAddress}. Error:`, err.raw);
});
});//_∏_ (Meanwhile...)
}

View file

@ -74,37 +74,23 @@ module.exports = {
throw 'invalidEmailDomain';
}
await sails.helpers.salesforce.updateOrCreateContactAndAccount.with({
emailAddress,
firstName,
lastName,
organization: organization,
primaryBuyingSituation: primaryBuyingSituation === 'eo-security' ? 'Endpoint operations - Security' : primaryBuyingSituation === 'eo-it' ? 'Endpoint operations - IT' : primaryBuyingSituation === 'mdm' ? 'Device management (MDM)' : 'Vulnerability management',
});
await sails.helpers.http.post.with({
url: 'https://hooks.zapier.com/hooks/catch/3627242/3cxwxdo/',
data: {
emailAddress,
firstName,
lastName,
organization,
numberOfHosts,
primaryBuyingSituation,
webhookSecret: sails.config.custom.zapierSandboxWebhookSecret,
}
})
.timeout(5000)
.tolerate(['non200Response', 'requestFailed'], (err)=>{
// Note that Zapier responds with a 2xx status code even if something goes wrong, so just because this message is not logged doesn't mean everything is hunky dory. More info: https://github.com/fleetdm/fleet/pull/6380#issuecomment-1204395762
sails.log.warn(`When a user submitted a contact form message, a lead/contact could not be updated in the CRM for this email address: ${emailAddress}. Raw error: ${err}`);
return;
});
// Use timers.setImmediate() to update/create CRM records in the background.
require('timers').setImmediate(async ()=>{
await sails.helpers.salesforce.updateOrCreateContactAndAccountAndCreateLead.with({
emailAddress: emailAddress,
firstName: firstName,
lastName: lastName,
organization: organization,
numberOfHosts: numberOfHosts,
primaryBuyingSituation: primaryBuyingSituation === 'eo-security' ? 'Endpoint operations - Security' : primaryBuyingSituation === 'eo-it' ? 'Endpoint operations - IT' : primaryBuyingSituation === 'mdm' ? 'Device management (MDM)' : primaryBuyingSituation === 'vm' ? 'Vulnerability management' : undefined,
leadSource: 'Website - Contact forms',
leadDescription: `Submitted the "Talk to us" form.`,
}).tolerate((err)=>{
sails.log.warn(`Background task failed: When a user submitted the "Talk to us" form, a lead/contact could not be updated in the CRM for this email address: ${emailAddress}. Error:`, err.raw);
});
});//_∏_ (Meanwhile...)
return;
}

View file

@ -138,24 +138,18 @@ the account verification message.)`,
.intercept({name: 'UsageError'}, 'invalid')
.fetch();
if(sails.config.environment === 'production') {
let recordIds = await sails.helpers.salesforce.updateOrCreateContactAndAccount.with({
// Use timers.setImmediate() to update/create CRM records in the background.
require('timers').setImmediate(async ()=>{
await sails.helpers.salesforce.updateOrCreateContactAndAccountAndCreateLead.with({
emailAddress: newEmailAddress,
firstName: firstName,
lastName: lastName,
organization: organization,
});
await sails.helpers.salesforce.createLead.with({
salesforceContactId: recordIds.salesforceContactId,
salesforceAccountId: recordIds.salesforceAccountId,
leadSource: 'Website - Sign up',
})
.tolerate((err)=>{
sails.log.warn(`When a user signed up, a lead could not be created in the CRM for this email address: ${newEmailAddress}. Error from create-lead helper: ${err}`);
return;
}).tolerate((err)=>{
sails.log.warn(`Background task failed: When a user (email: ${newEmailAddress} sign up for a fleetdm.com account, a Contact, Account, and Lead record could not be created/updated in the CRM. Error:`, err.raw);
});
}
});//_∏_ (Meanwhile...)
// Store the user's new id in their session.
this.req.session.userId = newUserRecord.id;

View file

@ -0,0 +1,90 @@
module.exports = {
friendlyName: 'Update or create contact and account and create lead',
description: 'Updates or creates a contact and account in Salesforce, then uses the IDs of the created records to create a Lead record.',
extendedDescription: 'This is a wrapper for the update-or-create-contact-and-account and create-lead helpers used to run both of them in the background with timers.setImmediate().',
inputs: {
// Find by…
emailAddress: { type: 'string' },
linkedinUrl: { type: 'string' },
// Set…
firstName: { type: 'string', required: true },
lastName: { type: 'string', required: true },
organization: { type: 'string' },
primaryBuyingSituation: { type: 'string' },
psychologicalStage: {
type: 'string',
isIn: [
'1 - Unaware',
'2 - Aware',
'3 - Intrigued',
'4 - Has use case',
'5 - Personally confident',
'6 - Has team buy-in'
]
},
// For new leads.
leadDescription: {
type: 'string',
description: 'A description of what this lead is about; e.g. a contact form message, or the size of t-shirt being requested.'
},
leadSource: {
type: 'string',
required: true,
isIn: [
'Website - Contact forms',
'Website - Sign up',
'Website - Waitlist',
'Website - swag request',
],
},
numberOfHosts: { type: 'number' },
},
exits: {
success: {
extendedDescription: 'Note that this deliberately has no return value.',
},
},
fn: async function ({emailAddress, linkedinUrl, firstName, lastName, organization, primaryBuyingSituation, psychologicalStage, leadSource, leadDescription, numberOfHosts}) {
if(sails.config.environment !== 'production') {
sails.log('Skipping Salesforce integration...');
return;
}
let recordIds = await sails.helpers.salesforce.updateOrCreateContactAndAccount.with({
emailAddress,
firstName,
lastName,
organization,
linkedinUrl,
primaryBuyingSituation,
psychologicalStage,
});
await sails.helpers.salesforce.createLead.with({
salesforceContactId: recordIds.salesforceContactId,
salesforceAccountId: recordIds.salesforceAccountId,
leadDescription,
leadSource,
numberOfHosts,
});
return;
}
};

View file

@ -43,7 +43,7 @@ parasails.registerPage('edit-password', {
// > (Note that we re-enable the syncing state here. This is on purpose--
// > to make sure the spinner stays there until the page navigation finishes.)
this.syncing = true;
window.location = '/account';
this.goto('/account');
},
}

View file

@ -45,7 +45,7 @@ parasails.registerPage('edit-profile', {
// > (Note that we re-enable the syncing state here. This is on purpose--
// > to make sure the spinner stays there until the page navigation finishes.)
this.syncing = true;
window.location = '/account';
this.goto('/account');
},
}

View file

@ -39,7 +39,7 @@ parasails.registerPage('connect-vanta', {
submittedAuthorizationForm: async function() {
this.syncing = true;
window.location = this.vantaAuthorizationRequestURL;
this.goto(this.vantaAuthorizationRequestURL);
},
clickClearErrors: async function() {

View file

@ -88,9 +88,9 @@ parasails.registerPage('contact', {
submittedTalkToUsForm: async function() {
this.syncing = true;
if(this.formData.numberOfHosts > 700){
window.location = `https://calendly.com/fleetdm/talk-to-us?email=${encodeURIComponent(this.formData.emailAddress)}&name=${encodeURIComponent(this.formData.firstName+' '+this.formData.lastName)}`;
this.goto(`https://calendly.com/fleetdm/talk-to-us?email=${encodeURIComponent(this.formData.emailAddress)}&name=${encodeURIComponent(this.formData.firstName+' '+this.formData.lastName)}`);
} else {
window.location = `https://calendly.com/fleetdm/chat?email=${encodeURIComponent(this.formData.emailAddress)}&name=${encodeURIComponent(this.formData.firstName+' '+this.formData.lastName)}`;
this.goto(`https://calendly.com/fleetdm/chat?email=${encodeURIComponent(this.formData.emailAddress)}&name=${encodeURIComponent(this.formData.firstName+' '+this.formData.lastName)}`);
}
},

View file

@ -67,7 +67,7 @@ parasails.registerPage('new-license', {
clickGoToDashboard: async function() {
this.syncing = true;
window.location = '/customers/dashboard?order-complete';
this.goto('/customers/dashboard?order-complete');
},
submittedQuoteForm: async function(quote) {
@ -90,7 +90,7 @@ parasails.registerPage('new-license', {
clickScheduleDemo: async function() {
this.syncing = true;
// Note: we keep loading spinner present indefinitely so that it is apparent that a new page is loading
window.location = `https://calendly.com/fleetdm/talk-to-us?email=${encodeURIComponent(this.me.emailAddress)}&name=${encodeURIComponent(this.me.firstName+' '+this.me.lastName)}`;
this.goto(`https://calendly.com/fleetdm/talk-to-us?email=${encodeURIComponent(this.me.emailAddress)}&name=${encodeURIComponent(this.me.firstName+' '+this.me.lastName)}`);
},
clickResetForm: async function() {

View file

@ -223,7 +223,7 @@ parasails.registerPage('basic-documentation', {
methods: {
clickCTA: function (slug) {
window.location = slug;
this.goto(slug);
},
isCurrentSection: function (section) {

View file

@ -59,7 +59,7 @@ parasails.registerPage('login', {
// > (Note that we re-enable the syncing state here. This is on purpose--
// > to make sure the spinner stays there until the page navigation finishes.)
this.syncing = true;
window.location = this.pageToRedirectToAfterLogin;
this.goto(this.pageToRedirectToAfterLogin);
},
}

View file

@ -45,7 +45,7 @@ parasails.registerPage('new-password', {
// > (Note that we re-enable the syncing state here. This is on purpose--
// > to make sure the spinner stays there until the page navigation finishes.)
this.syncing = true;
window.location = '/customers/login';
this.goto('/customers/login');
},
}

View file

@ -68,7 +68,7 @@ parasails.registerPage('signup', {
// > (Note that we re-enable the syncing state here. This is on purpose--
// > to make sure the spinner stays there until the page navigation finishes.)
this.syncing = true;
window.location = this.pageToRedirectToAfterRegistration;// « / start if the user came here from the start now button, or customers/new-license if the user came here from the "Get your license" link.
this.goto(this.pageToRedirectToAfterRegistration);// « / start if the user came here from the start now button, or customers/new-license if the user came here from the "Get your license" link.
}

View file

@ -23,7 +23,7 @@ parasails.registerPage('query-detail', {
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: {
clickAvatar: function (contributor) {
window.location = contributor.htmlUrl;
this.goto(contributor.htmlUrl);
},
getDisplayName: function (contributor) {
return !contributor.name ? contributor.handle : contributor.name;

View file

@ -57,11 +57,11 @@ parasails.registerPage('query-library', {
},
clickCard: function (querySlug) {
window.location = '/queries/' + querySlug; // we can trust the query slug is url-safe
this.goto('/queries/' + querySlug); // we can trust the query slug is url-safe
},
clickAvatar: function (contributor) {
window.location = contributor.htmlUrl;
this.goto(contributor.htmlUrl);
},
getAvatarUrl: function (contributorData) {

View file

@ -104,7 +104,7 @@ parasails.registerPage('start', {
});
this.previouslyAnsweredQuestions[this.currentStep] = getStartedProgress[this.currentStep];
if(_.startsWith(nextStep, '/')){
window.location = nextStep;
this.goto(nextStep);
} else {
this.syncing = false;
this.currentStep = nextStep;
@ -282,10 +282,10 @@ parasails.registerPage('start', {
return nextStepInForm;
},
clickGoToCalendly: function() {
window.location = `https://calendly.com/fleetdm/talk-to-us?email=${encodeURIComponent(this.me.emailAddress)}&name=${encodeURIComponent(this.me.firstName+' '+this.me.lastName)}`;
this.goto(`https://calendly.com/fleetdm/talk-to-us?email=${encodeURIComponent(this.me.emailAddress)}&name=${encodeURIComponent(this.me.firstName+' '+this.me.lastName)}`);
},
clickGoToContactPage: function() {
window.location = `/contact?prefillFormDataFromUserRecord`;
this.goto(`/contact`);
},
clickClearOneFormError: function(field) {
if(this.formErrors[field]){

View file

@ -55,17 +55,17 @@ parasails.registerPage('query-report', {
watch: {
selectedTable: function(val){
if(val !== this.tableToDisplay){
window.location = `/try-fleet/explore-data/${this.selectedHost}/${this.selectedTable}`;
this.goto(`/try-fleet/explore-data/${this.selectedHost}/${this.selectedTable}`);
}
},
hostToDisplayResultsFor: function(val){
if(val !== this.selectedHost){
if(val === 'Linux'){
window.location = `/try-fleet/explore-data/linux/apparmor_events`;
this.goto(`/try-fleet/explore-data/linux/apparmor_events`);
} else if(val === 'Windows'){
window.location = `/try-fleet/explore-data/windows/appcompat_shims`;
this.goto(`/try-fleet/explore-data/windows/appcompat_shims`);
} else {
window.location = `/try-fleet/explore-data/macos/account_policy_data`;
this.goto(`/try-fleet/explore-data/macos/account_policy_data`);
}
}
}
@ -85,7 +85,6 @@ parasails.registerPage('query-report', {
tableContainer.addEventListener('scroll', (event)=>{
let container = event.target;
console.log(container);
let isScrolledFullyToLeft = container.scrollLeft === 0;
let isScrolledFullyToRight = (container.scrollWidth - container.scrollLeft <= container.clientWidth + 1);
// Update the class on the table container based on how much the table is scrolled.

View file

@ -22,7 +22,7 @@ parasails.registerPage('sandbox-teleporter', {
// 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 = '/';
this.goto('/');
}
};
// 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.

View file

@ -38,6 +38,11 @@
font-size: 16px;
line-height: @text-lineheight;
color: @core-fleet-black-75;
a[href] {
text-decoration: underline;
color: @core-fleet-black-75;
text-underline-offset: 3px;
}
}
strong {
color: @core-fleet-black;
@ -185,6 +190,13 @@
}
[purpose='testimonial-quote'] {
width: 640px;
a {
text-decoration: none;
color: unset;
&:hover {
text-decoration: none;
}
}
[purpose='quote'] {
p {
color: @core-fleet-black-75;

View file

@ -174,6 +174,13 @@
}
}
[purpose='testimonial-quote'] {
a {
text-decoration: none;
color: unset;
&:hover {
text-decoration: none;
}
}
max-width: 591px;
[purpose='quote'] {
p {

View file

@ -172,6 +172,13 @@
}
[purpose='testimonial-quote'] {
width: 380px;
a {
text-decoration: none;
color: unset;
&:hover {
text-decoration: none;
}
}
[purpose='quote'] {
p {
color: @core-fleet-black-75;

View file

@ -25,7 +25,8 @@
</div>
</div>
<div purpose="testimonials" class="d-flex flex-md-row flex-column align-items-center justify-content-between">
<div purpose="testimonial-quote" @click="goto('<%= ['eo-security','vm'].includes(primaryBuyingSituation) ? 'https://www.linkedin.com/posts/nwaisman_movingtofleet-activity-7156319785981509632-bk_W' : ['eo-it'].includes(primaryBuyingSituation) ? 'https://www.linkedin.com/in/mrerictan/' : 'https://www.linkedin.com/in/kennybotelho/'%>')">
<div purpose="testimonial-quote">
<a href="<%= ['eo-security','vm'].includes(primaryBuyingSituation) ? 'https://www.linkedin.com/posts/nwaisman_movingtofleet-activity-7156319785981509632-bk_W' : ['eo-it'].includes(primaryBuyingSituation) ? 'https://www.linkedin.com/in/mrerictan/' : 'https://www.linkedin.com/in/kennybotelho/'%>" target="_blank">
<div purpose="quote">
<img alt="an opening quotation mark" style="width:20px; margin-bottom: 16px;" src="/images/icon-quote-21x17@2x.png">
<p>
@ -42,6 +43,7 @@
<p purpose="title"><%= ['eo-security','vm'].includes(primaryBuyingSituation) ? 'CISO of Lyft' : ['eo-it'].includes(primaryBuyingSituation) ? 'CIO & Chief Security Officer at Flock Safety' : 'Client Platform Engineer at gaming company'%></p>
</div>
</div>
</a>
</div>
</div>
<logo-carousel></logo-carousel>

View file

@ -42,7 +42,8 @@
</div>
<div purpose="testimonials" class="d-flex flex-md-row flex-column align-items-center justify-content-between">
<% if (['eo-security'].includes(primaryBuyingSituation)) { %>
<div purpose="testimonial-quote" @click="goto('https://www.linkedin.com/in/charleszaffery/')">
<div purpose="testimonial-quote">
<a href="https://www.linkedin.com/in/charleszaffery/" target="_blank">
<div purpose="quote">
<img alt="an opening quotation mark" style="width:20px; margin-bottom: 16px;" src="/images/icon-quote-21x17@2x.png">
<p>"Fleet has such a huge amount of use cases. My goal was to get telemetry on endpoints, but then our IR team, our TBM team, and multiple other folks in security started heavily utilizing the system in ways I didnt expect. It spread so naturally, even our corporate and infrastructure teams want to run it."</p>
@ -54,9 +55,11 @@
<p purpose="title">Principal computer janitor</p>
</div>
</div>
</a>
</div>
<% } else if (['vm'].includes(primaryBuyingSituation)) { %>
<div purpose="testimonial-quote" @click="goto('https://www.linkedin.com/in/austin-anderson-73172185/')">
<div purpose="testimonial-quote">
<a href="https://www.linkedin.com/in/austin-anderson-73172185/" target="_blank">
<div purpose="quote">
<img alt="an opening quotation mark" style="width:20px; margin-bottom: 16px;" src="/images/icon-quote-21x17@2x.png">
<p>"Fleet lets us be more actionable with fewer people. It helps us to filter out the noise better than we could with the other big name products we replaced."</p>
@ -68,9 +71,11 @@
<p purpose="title">Cybersecurity team senior manager</p>
</div>
</div>
</a>
</div>
<% } else { %>
<div purpose="testimonial-quote" @click="goto('https://www.linkedin.com/in/nickfohs/')">
<div purpose="testimonial-quote">
<a target="_blank" href="https://www.linkedin.com/in/nickfohs/">
<div purpose="quote">
<img alt="an opening quotation mark" style="width:20px; margin-bottom: 16px;" src="/images/icon-quote-21x17@2x.png">
<p>“Fleet provides a way to surface device data and telemetry to our other teams and partners.”</p>
@ -82,6 +87,7 @@
<p purpose="title">Systems and infrastructure manager</p>
</div>
</div>
</a>
</div>
<% } %>
<div purpose="testimonial-videos" class="d-flex">

View file

@ -26,6 +26,7 @@
</div>
<div purpose="testimonials" class="d-flex flex-md-row flex-column align-items-center justify-content-between">
<div purpose="testimonial-quote">
<a href="https://www.linkedin.com/in/andre-shields/" target="_blank">
<div purpose="quote">
<img alt="an opening quotation mark" style="width:20px; margin-bottom: 16px;" src="/images/icon-quote-21x17@2x.png">
<p>Fleet lets us be more actionable with fewer people. It helps us to filter out the noise better than we could with the other big name products we replaced.</p>
@ -37,6 +38,7 @@
<p purpose="title">Cybersecurity Security Engineer, Vulnerability Management</p>
</div>
</div>
</a>
</div>
<div purpose="testimonial-videos" class="d-flex">
<div purpose="testimonial-video" @click="clickOpenVideoModal('austin-anderson')">