Website: Build pricing page from yaml file (#8549)

* Create pricing-features-table.yml

* update built-static-content to add the pricing table configuration to builtStaticContent

* Update pricing page to use builtStaticContent.pricingTable

* Update view-pricing.js

* update pricing table, lint fix

Co-authored-by: Mike McNeil <mikermcneil@users.noreply.github.com>
This commit is contained in:
Eric 2022-12-05 14:11:46 -06:00 committed by GitHub
parent a59e1cce80
commit d92d9980ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 230 additions and 1041 deletions

View File

@ -0,0 +1,131 @@
- categoryName: Support
features:
- name: Public issue tracker (GitHub)
tier: Free
comingSoon: false
- name: Community Slack channel
tier: Free
comingSoon: false
- name: Unlimited email support (confidential)
tier: Premium
comingSoon: false
- name: Phone and video call support
tier: Premium
comingSoon: false
- categoryName: Inventory management
features:
- name: Secure REST API
tier: Free
comingSoon: false
- name: Command line tool (CLI)
tier: Free
comingSoon: false
- name: Realtime device inventory dashboard
tier: Free
comingSoon: false
- name: Browse installed software packages
tier: Free
comingSoon: false
- name: Search devices by IP, serial, hostname, UUID
tier: Free
comingSoon: false
- name: Target and configure specific groups of devices
tier: Premium
comingSoon: false
- name: Aggregate insights for groups of devices
tier: Premium
comingSoon: false
- categoryName: Collaboration
features:
- name: Shareable device health reports
tier: Free
comingSoon: false
- name: Versionable queries and config (GitOps)
tier: Free
comingSoon: false
- name: Human-to-device mapping
tier: Free
comingSoon: false
- name: Scope transparency
tier: Free
comingSoon: false
- name: Multiple teams
tier: Premium
comingSoon: false
- categoryName: Security and compliance
features:
- name: Single sign on (SSO, SAML)
tier: Free
comingSoon: false
- name: Audit queries and user activities
tier: Free
comingSoon: false
- name: Grant API-only access
tier: Free
comingSoon: false
- name: Role-based access control
tier: Free
comingSoon: false
- name: Just-in-time provisioning
tier: Premium
comingSoon: false
- categoryName: Monitoring
features:
- name: Schedule and automate custom queries
tier: Free
comingSoon: false
- name: Detect vulnerable software
tier: Free
comingSoon: false
- name: Query performance monitoring
tier: Free
comingSoon: false
- name: Standard query and policy library
tier: Free
comingSoon: false
- name: Detect and surface issues with devices
tier: Free
comingSoon: false
- name: Policy and vulnerability automations (webhook, Zendesk, JIRA, ServiceNow*)
tier: Free
comingSoon: false
- name: Vulnerability scores (EPSS and CVSS)
tier: Premium
comingSoon: false
- name: CISA known exploited vulnerabilities
tier: Premium
comingSoon: false
- name: End-user self-service
tier: Premium
comingSoon: false
- categoryName: Data outputs
features:
- name: Flexible log destinations (AWS Kinesis, Lambda, GCP, Kafka)
tier: Free
comingSoon: false
- name: File carving (AWS S3)
tier: Free
comingSoon: false
- categoryName: Deployment
features:
- name: Self-managed
tier: Free
comingSoon: false
- name: Deployment tools (Helm, Terraform)
tier: Free
comingSoon: false
- name: Configure osquery startup flags remotely
tier: Free
comingSoon: true
- name: Autoupdate osquery agents
tier: Free
comingSoon: false
- name: Self-managed autoupdate registry
tier: Premium
comingSoon: false
- name: Manage osquery extensions remotely
tier: Premium
comingSoon: false

View File

@ -11,15 +11,24 @@ module.exports = {
success: {
viewTemplatePath: 'pages/pricing'
}
},
badConfig: {
responseType: 'badConfig'
},
},
fn: async function () {
if(!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.pricingTable)) {
throw {badConfig: 'builtStaticContent.pricingTable'};
}
let pricingTable = sails.config.builtStaticContent.pricingTable;
// Respond with view.
return {};
return { pricingTable };
}

View File

@ -8,10 +8,7 @@
.btn {
color: #fff;
}
.faq-question {
font-weight: @bold;
}
.faq-list {
[purpose='faq-list'] {
padding-left: 20px;
li {
@ -20,6 +17,9 @@
}
}
[purpose='pricing-table-category']:not(:first-of-type) {
margin-top: 48px;
}
@media (max-width: 767px) {
p {font-size: 16px;}

View File

@ -638,6 +638,42 @@ module.exports = {
builtStaticContent.compiledPagePartialsAppPath = APP_PATH_TO_COMPILED_PAGE_PARTIALS;
},
async()=>{
// Validate the pricing table yaml and add it to builtStaticContent.pricingTable.
let RELATIVE_PATH_TO_PRICING_TABLE_YML_IN_FLEET_REPO = 'handbook/product/pricing-features-table.yml';// TODO: Is there a better home for this file?
let yaml = await sails.helpers.fs.read(path.join(topLvlRepoPath, RELATIVE_PATH_TO_PRICING_TABLE_YML_IN_FLEET_REPO)).intercept('doesNotExist', (err)=>new Error(`Could not find pricing table features YAML file at "${RELATIVE_PATH_TO_PRICING_TABLE_YML_IN_FLEET_REPO}". Was it accidentally moved? Raw error: `+err.message));
let pricingTableCategories = YAML.parse(yaml, {prettyErrors: true});
for(let category of pricingTableCategories){
if(!category.categoryName){ // Throw an error if a category is missing a categoryName.
throw new Error('Could not build pricing table config from pricing-features-table.yml, a category in the pricing table configuration is missing a categoryName. To resolve, make sure every category in the pricing table YAML file has a categoryName');
}
if(!category.features){// Throw an error if a category is missing `features`.
throw new Error('Could not build pricing table config from pricing-features-table.yml, the "'+category.categoryName+'" category in the yaml file is missing features. To resolve, add an array of features to this category.');
}
if(!_.isArray(category.features)){ // Throw an error if a category's `features`` is not an array.
throw new Error('Could not build pricing table config from pricing-features-table.yml, The value of the "'+category.categoryName+'" category is invalid, to resolve, change the features for this category to be an array of objects.');
}
// Validate all features in a category.
for(let feature of category.features){
if(!feature.name) { // Throw an error if a feature is missing a `name`.
throw new Error('Could not build pricing table config from pricing-features-table.yml. A feature in the "'+category.categoryName+'" category is missing a "name". To resolve, add a "name" to this feature '+feature);
}
if(!feature.tier) { // Throw an error if a feature is missing a `tier`.
throw new Error('Could not build pricing table config from pricing-features-table.yml. The "'+feature.name+'" feature is missing a "tier". To resolve, add a "tier" (either "Free" or "Premium") to this feature.');
} else if(!_.contains(['Free', 'Premium'], feature.tier)){ // Throw an error if a feature's `tier` is not either "Free" or "Premium".
throw new Error('Could not build pricing table config from pricing-features-table.yml. The "'+feature.name+'" feature has an invalid "tier". to resolve, change the value of this features "tier" (currently set to '+feature.tier+') to be either "Free" or "Premium".');
}
if(feature.comingSoon === undefined) { // Throw an error if a feature is missing a `comingSoon` value
throw new Error('Could not build pricing table config from pricing-features-table.yml. The "'+feature.name+'" feature is missing a "comingSoon" value (boolean). To resolve, add a comingSoon value to this feature.');
} else if(typeof feature.comingSoon !== 'boolean'){ // Throw an error if the `comingSoon` value is not a boolean.
throw new Error('Could not build pricing table config from pricing-features-table.yml. The "'+feature.name+'" feature has an invalid "comingSoon" value (currently set to '+feature.comingSoon+'). To resolve, change the value of "comingSoon" for this feature to be either "true" or "false".');
}
}
}
builtStaticContent.pricingTable = pricingTableCategories;
},
]);
// ██████╗ ███████╗██████╗ ██╗ █████╗ ██████╗███████╗ ███████╗ █████╗ ██╗██╗ ███████╗██████╗ ██████╗

File diff suppressed because it is too large Load Diff