0 && opts[0].WithDEPWebview {
+ frontendHandler := WithMDMEnrollmentMiddleware(svc, logger, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // do nothing and return 200
+ w.WriteHeader(http.StatusOK)
+ }))
+ rootMux.Handle("/", frontendHandler)
+ }
+
apiHandler := MakeHandler(svc, cfg, logger, limitStore, WithLoginRateLimit(throttled.PerMin(1000)))
rootMux.Handle("/api/", apiHandler)
var errHandler *errorstore.Handler
@@ -653,7 +662,7 @@ func newMockAPNSPushProviderFactory() (*mock.APNSPushProviderFactory, *mock.APNS
return factory, provider
}
-func mockSuccessfulPush(pushes []*mdm.Push) (map[string]*push.Response, error) {
+func mockSuccessfulPush(_ context.Context, pushes []*mdm.Push) (map[string]*push.Response, error) {
res := make(map[string]*push.Response, len(pushes))
for _, p := range pushes {
res[p.Token.String()] = &push.Response{
@@ -684,18 +693,13 @@ func mdmConfigurationRequiredEndpoints() []struct {
{"GET", "/api/latest/fleet/mdm/apple/profiles/1", false, false},
{"DELETE", "/api/latest/fleet/mdm/apple/profiles/1", false, false},
{"GET", "/api/latest/fleet/mdm/apple/profiles/summary", false, false},
- {"GET", "/api/latest/fleet/mdm/profiles/summary", false, false},
- {"GET", "/api/latest/fleet/configuration_profiles/summary", false, false},
{"PATCH", "/api/latest/fleet/mdm/hosts/1/unenroll", false, false},
{"DELETE", "/api/latest/fleet/hosts/1/mdm", false, false},
- {"GET", "/api/latest/fleet/mdm/hosts/1/encryption_key", false, false},
- {"GET", "/api/latest/fleet/hosts/1/encryption_key", false, false},
{"GET", "/api/latest/fleet/mdm/hosts/1/profiles", false, true},
{"GET", "/api/latest/fleet/hosts/1/configuration_profiles", false, true},
{"POST", "/api/latest/fleet/mdm/hosts/1/lock", false, false},
{"POST", "/api/latest/fleet/mdm/hosts/1/wipe", false, false},
{"PATCH", "/api/latest/fleet/mdm/apple/settings", false, false},
- {"POST", "/api/latest/fleet/disk_encryption", false, false},
{"GET", "/api/latest/fleet/mdm/apple", false, false},
{"GET", "/api/latest/fleet/apns", false, false},
{"GET", apple_mdm.EnrollPath + "?token=test", false, false},
@@ -725,8 +729,6 @@ func mdmConfigurationRequiredEndpoints() []struct {
{"GET", "/api/latest/fleet/mdm/commands", false, false},
{"GET", "/api/latest/fleet/commands", false, false},
{"POST", "/api/fleet/orbit/disk_encryption_key", false, false},
- {"GET", "/api/latest/fleet/mdm/disk_encryption/summary", false, true},
- {"GET", "/api/latest/fleet/disk_encryption", false, true},
{"GET", "/api/latest/fleet/mdm/profiles/1", false, false},
{"GET", "/api/latest/fleet/configuration_profiles/1", false, false},
{"DELETE", "/api/latest/fleet/mdm/profiles/1", false, false},
diff --git a/server/vulnerabilities/nvd/cpe_test.go b/server/vulnerabilities/nvd/cpe_test.go
index 386234d8ac58..0628f95d67b5 100644
--- a/server/vulnerabilities/nvd/cpe_test.go
+++ b/server/vulnerabilities/nvd/cpe_test.go
@@ -1663,6 +1663,24 @@ func TestCPEFromSoftwareIntegration(t *testing.T) {
},
cpe: "cpe:2.3:a:oracle:virtualbox:7.0.12:*:*:*:*:macos:*:*",
},
+ {
+ software: fleet.Software{
+ Name: "gh",
+ Source: "deb_packages",
+ Version: "2.61.0",
+ Vendor: "",
+ BundleIdentifier: "",
+ }, cpe: "cpe:2.3:a:github:cli:2.61.0:*:*:*:*:*:*:*",
+ },
+ {
+ software: fleet.Software{
+ Name: "gh",
+ Source: "homebrew_packages",
+ Version: "2.61.0",
+ Vendor: "",
+ BundleIdentifier: "",
+ }, cpe: "cpe:2.3:a:github:cli:2.61.0:*:*:*:*:macos:*:*",
+ },
}
// NVD_TEST_CPEDB_PATH can be used to speed up development (sync cpe.sqlite only once).
diff --git a/server/vulnerabilities/nvd/cpe_translations.json b/server/vulnerabilities/nvd/cpe_translations.json
index ec03b2e26c35..59518162bdaa 100644
--- a/server/vulnerabilities/nvd/cpe_translations.json
+++ b/server/vulnerabilities/nvd/cpe_translations.json
@@ -426,5 +426,14 @@
"product": ["virtualbox"],
"vendor": ["oracle"]
}
+ },
+ {
+ "software": {
+ "name": ["gh"]
+ },
+ "filter": {
+ "product": ["cli"],
+ "vendor": ["github"]
+ }
}
]
\ No newline at end of file
diff --git a/server/vulnerabilities/nvd/cve_test.go b/server/vulnerabilities/nvd/cve_test.go
index 2b2d796198b1..1169b93b9673 100644
--- a/server/vulnerabilities/nvd/cve_test.go
+++ b/server/vulnerabilities/nvd/cve_test.go
@@ -343,12 +343,14 @@ func TestTranslateCPEToCVE(t *testing.T) {
excludedCVEs: []string{"CVE-2024-4030"},
continuesToUpdate: true,
},
- "cpe:2.3:a:python:python:3.9.6:*:*:*:*:windows:*:*": {
- includedCVEs: []cve{
- {ID: "CVE-2024-4030", resolvedInVersion: "3.9.20"},
- },
- continuesToUpdate: true,
- },
+ // Skipping test while troubleshooting https://github.com/fleetdm/fleet/issues/24286
+ //
+ // "cpe:2.3:a:python:python:3.9.6:*:*:*:*:windows:*:*": {
+ // includedCVEs: []cve{
+ // {ID: "CVE-2024-4030", resolvedInVersion: "3.9.20"},
+ // },
+ // continuesToUpdate: true,
+ // },
// Tests the expandCPEAliases rule for virtualbox on macOS
"cpe:2.3:a:oracle:virtualbox:7.0.6:*:*:*:*:macos:*:*": {
includedCVEs: []cve{
diff --git a/server/worker/apple_mdm.go b/server/worker/apple_mdm.go
index 01ac59ea79f9..235a3a7333d7 100644
--- a/server/worker/apple_mdm.go
+++ b/server/worker/apple_mdm.go
@@ -50,12 +50,13 @@ func (a *AppleMDM) Name() string {
// appleMDMArgs is the payload for the Apple MDM job.
type appleMDMArgs struct {
- Task AppleMDMTask `json:"task"`
- HostUUID string `json:"host_uuid"`
- TeamID *uint `json:"team_id,omitempty"`
- EnrollReference string `json:"enroll_reference,omitempty"`
- EnrollmentCommands []string `json:"enrollment_commands,omitempty"`
- Platform string `json:"platform,omitempty"`
+ Task AppleMDMTask `json:"task"`
+ HostUUID string `json:"host_uuid"`
+ TeamID *uint `json:"team_id,omitempty"`
+ EnrollReference string `json:"enroll_reference,omitempty"`
+ EnrollmentCommands []string `json:"enrollment_commands,omitempty"`
+ Platform string `json:"platform,omitempty"`
+ UseWorkerDeviceRelease bool `json:"use_worker_device_release,omitempty"`
}
// Run executes the apple_mdm job.
@@ -163,9 +164,10 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs)
}
}
- // proceed to release the device only if it is not a macos, as those are
- // released via the setup experience flow.
- if !isMacOS(args.Platform) {
+ // proceed to release the device if it is not a macos, as those are released
+ // via the setup experience flow, or if we were told to use the worker based
+ // release.
+ if !isMacOS(args.Platform) || args.UseWorkerDeviceRelease {
var manualRelease bool
if args.TeamID == nil {
ac, err := a.Datastore.AppConfig(ctx)
@@ -187,7 +189,7 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs)
// be final and same for MDM profiles of that host; it means the DEP
// enrollment process is done and the device can be released.
if err := QueueAppleMDMJob(ctx, a.Datastore, a.Log, AppleMDMPostDEPReleaseDeviceTask,
- args.HostUUID, args.Platform, args.TeamID, args.EnrollReference, awaitCmdUUIDs...); err != nil {
+ args.HostUUID, args.Platform, args.TeamID, args.EnrollReference, false, awaitCmdUUIDs...); err != nil {
return ctxerr.Wrap(ctx, err, "queue Apple Post-DEP release device job")
}
}
@@ -198,10 +200,11 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs)
// This job is deprecated for macos because releasing devices is now done via
// the orbit endpoint /setup_experience/status that is polled by a swift dialog
-// UI window during the setup process, and automatically releases the device
-// once all pending setup tasks are done. However, it must remain implemented
-// for iOS and iPadOS and in case there are such jobs to process after a Fleet
-// migration to a new version.
+// UI window during the setup process (unless there are no setup experience
+// items, in which case this worker job is used), and automatically releases
+// the device once all pending setup tasks are done. However, it must remain
+// implemented for iOS and iPadOS and in case there are such jobs to process
+// after a Fleet migration to a new version.
func (a *AppleMDM) runPostDEPReleaseDevice(ctx context.Context, args appleMDMArgs) error {
// Edge cases:
// - if the device goes offline for a long time, should we go ahead and
@@ -355,6 +358,7 @@ func QueueAppleMDMJob(
platform string,
teamID *uint,
enrollReference string,
+ useWorkerDeviceRelease bool,
enrollmentCommandUUIDs ...string,
) error {
attrs := []interface{}{
@@ -373,12 +377,13 @@ func QueueAppleMDMJob(
level.Info(logger).Log(attrs...)
args := &appleMDMArgs{
- Task: task,
- HostUUID: hostUUID,
- TeamID: teamID,
- EnrollReference: enrollReference,
- EnrollmentCommands: enrollmentCommandUUIDs,
- Platform: platform,
+ Task: task,
+ HostUUID: hostUUID,
+ TeamID: teamID,
+ EnrollReference: enrollReference,
+ EnrollmentCommands: enrollmentCommandUUIDs,
+ Platform: platform,
+ UseWorkerDeviceRelease: useWorkerDeviceRelease,
}
// the release device task is always added with a delay
diff --git a/server/worker/apple_mdm_test.go b/server/worker/apple_mdm_test.go
index 8b497379aba0..f27aa32bf15a 100644
--- a/server/worker/apple_mdm_test.go
+++ b/server/worker/apple_mdm_test.go
@@ -141,7 +141,7 @@ func TestAppleMDM(t *testing.T) {
// create a host and enqueue the job
h := createEnrolledHost(t, 1, nil, true)
- err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "")
+ err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "", false)
require.NoError(t, err)
// run the worker, should mark the job as done
@@ -171,7 +171,7 @@ func TestAppleMDM(t *testing.T) {
// create a host and enqueue the job
h := createEnrolledHost(t, 1, nil, true)
- err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMTask("no-such-task"), h.UUID, "darwin", nil, "")
+ err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMTask("no-such-task"), h.UUID, "darwin", nil, "", false)
require.NoError(t, err)
// run the worker, should mark the job as failed
@@ -204,7 +204,7 @@ func TestAppleMDM(t *testing.T) {
w.Register(mdmWorker)
// use "" instead of "darwin" as platform to test a queued job after the upgrade to iOS/iPadOS support.
- err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "", nil, "")
+ err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "", nil, "", false)
require.NoError(t, err)
// run the worker, should succeed
@@ -239,7 +239,7 @@ func TestAppleMDM(t *testing.T) {
w := NewWorker(ds, nopLog)
w.Register(mdmWorker)
- err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "")
+ err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "", false)
require.NoError(t, err)
// run the worker, should succeed
@@ -281,7 +281,7 @@ func TestAppleMDM(t *testing.T) {
w := NewWorker(ds, nopLog)
w.Register(mdmWorker)
- err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "")
+ err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "", false)
require.NoError(t, err)
// run the worker, should succeed
@@ -330,7 +330,7 @@ func TestAppleMDM(t *testing.T) {
w := NewWorker(ds, nopLog)
w.Register(mdmWorker)
- err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, "")
+ err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, "", false)
require.NoError(t, err)
// run the worker, should succeed
@@ -380,7 +380,7 @@ func TestAppleMDM(t *testing.T) {
w := NewWorker(ds, nopLog)
w.Register(mdmWorker)
- err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, "")
+ err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, "", false)
require.NoError(t, err)
// run the worker, should succeed
@@ -418,7 +418,7 @@ func TestAppleMDM(t *testing.T) {
w := NewWorker(ds, nopLog)
w.Register(mdmWorker)
- err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "abcd")
+ err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "abcd", false)
require.NoError(t, err)
// run the worker, should succeed
@@ -461,7 +461,7 @@ func TestAppleMDM(t *testing.T) {
w := NewWorker(ds, nopLog)
w.Register(mdmWorker)
- err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, idpAcc.UUID)
+ err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, idpAcc.UUID, false)
require.NoError(t, err)
// run the worker, should succeed
@@ -514,7 +514,7 @@ func TestAppleMDM(t *testing.T) {
w := NewWorker(ds, nopLog)
w.Register(mdmWorker)
- err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, idpAcc.UUID)
+ err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, idpAcc.UUID, false)
require.NoError(t, err)
// run the worker, should succeed
@@ -548,7 +548,7 @@ func TestAppleMDM(t *testing.T) {
w := NewWorker(ds, nopLog)
w.Register(mdmWorker)
- err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostManualEnrollmentTask, h.UUID, "darwin", nil, "")
+ err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostManualEnrollmentTask, h.UUID, "darwin", nil, "", false)
require.NoError(t, err)
// run the worker, should succeed
@@ -564,4 +564,40 @@ func TestAppleMDM(t *testing.T) {
require.Empty(t, jobs)
require.ElementsMatch(t, []string{"InstallEnterpriseApplication"}, getEnqueuedCommandTypes(t))
})
+
+ t.Run("use worker for automatic release", func(t *testing.T) {
+ mysql.SetTestABMAssets(t, ds, testOrgName)
+ defer mysql.TruncateTables(t, ds)
+
+ h := createEnrolledHost(t, 1, nil, true)
+
+ mdmWorker := &AppleMDM{
+ Datastore: ds,
+ Log: nopLog,
+ Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
+ }
+ w := NewWorker(ds, nopLog)
+ w.Register(mdmWorker)
+
+ err := QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "", true)
+ require.NoError(t, err)
+
+ // run the worker, should succeed
+ err = w.ProcessJobs(ctx)
+ require.NoError(t, err)
+
+ // ensure the job's not_before allows it to be returned if it were to run
+ // again
+ time.Sleep(time.Second)
+
+ require.ElementsMatch(t, []string{"InstallEnterpriseApplication"}, getEnqueuedCommandTypes(t))
+
+ // the release device job got enqueued
+ jobs, err := ds.GetQueuedJobs(ctx, 1, time.Now().Add(time.Minute)) // release job is always added with a delay
+ require.NoError(t, err)
+ require.Len(t, jobs, 1)
+ require.Equal(t, fleet.JobStateQueued, jobs[0].State)
+ require.Equal(t, appleMDMJobName, jobs[0].Name)
+ require.Contains(t, string(*jobs[0].Args), AppleMDMPostDEPReleaseDeviceTask)
+ })
}
diff --git a/terraform/README.md b/terraform/README.md
index f8015f428c3e..243586d48509 100644
--- a/terraform/README.md
+++ b/terraform/README.md
@@ -72,7 +72,7 @@ No resources.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [alb\_config](#input\_alb\_config) | n/a | object({
name = optional(string, "fleet")
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
allowed_ipv6_cidrs = optional(list(string), ["::/0"])
egress_cidrs = optional(list(string), ["0.0.0.0/0"])
egress_ipv6_cidrs = optional(list(string), ["::/0"])
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
idle_timeout = optional(number, 60)
})
| `{}` | no |
+| [alb\_config](#input\_alb\_config) | n/a | object({
name = optional(string, "fleet")
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
allowed_ipv6_cidrs = optional(list(string), ["::/0"])
egress_cidrs = optional(list(string), ["0.0.0.0/0"])
egress_ipv6_cidrs = optional(list(string), ["::/0"])
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
idle_timeout = optional(number, 905)
})
| `{}` | no |
| [certificate\_arn](#input\_certificate\_arn) | n/a | `string` | n/a | yes |
| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module | object({
autoscaling_capacity_providers = optional(any, {})
cluster_configuration = optional(any, {
execute_command_configuration = {
logging = "OVERRIDE"
log_configuration = {
cloud_watch_log_group_name = "/aws/ecs/aws-ec2"
}
}
})
cluster_name = optional(string, "fleet")
cluster_settings = optional(map(string), {
"name" : "containerInsights",
"value" : "enabled",
})
create = optional(bool, true)
default_capacity_provider_use_fargate = optional(bool, true)
fargate_capacity_providers = optional(any, {
FARGATE = {
default_capacity_provider_strategy = {
weight = 100
}
}
FARGATE_SPOT = {
default_capacity_provider_strategy = {
weight = 0
}
}
})
tags = optional(map(string))
})
| {
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
}
| no |
| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. | object({
task_mem = optional(number, null)
task_cpu = optional(number, null)
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.54.1")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
mount_points = optional(list(any), [])
volumes = optional(list(any), [])
extra_environment_variables = optional(map(string), {})
extra_iam_policies = optional(list(string), [])
extra_execution_iam_policies = optional(list(string), [])
extra_secrets = optional(map(string), {})
security_group_name = optional(string, "fleet")
iam_role_arn = optional(string, null)
repository_credentials = optional(string, "")
private_key_secret_name = optional(string, "fleet-server-private-key")
service = optional(object({
name = optional(string, "fleet")
}), {
name = "fleet"
})
database = optional(object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
}), {
password_secret_arn = null
user = null
database = null
address = null
rr_address = null
})
redis = optional(object({
address = string
use_tls = optional(bool, true)
}), {
address = null
use_tls = true
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
create = optional(bool, true)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = optional(object({
arn = string
}), {
arn = null
})
extra_load_balancers = optional(list(any), [])
networking = optional(object({
subnets = optional(list(string), null)
security_groups = optional(list(string), null)
ingress_sources = optional(object({
cidr_blocks = optional(list(string), [])
ipv6_cidr_blocks = optional(list(string), [])
security_groups = optional(list(string), [])
prefix_list_ids = optional(list(string), [])
}), {
cidr_blocks = []
ipv6_cidr_blocks = []
security_groups = []
prefix_list_ids = []
})
}), {
subnets = null
security_groups = null
ingress_sources = {
cidr_blocks = []
ipv6_cidr_blocks = []
security_groups = []
prefix_list_ids = []
}
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
iam = optional(object({
role = optional(object({
name = optional(string, "fleet-role")
policy_name = optional(string, "fleet-iam-policy")
}), {
name = "fleet-role"
policy_name = "fleet-iam-policy"
})
execution = optional(object({
name = optional(string, "fleet-execution-role")
policy_name = optional(string, "fleet-execution-role")
}), {
name = "fleet-execution-role"
policy_name = "fleet-iam-policy-execution"
})
}), {
name = "fleetdm-execution-role"
})
software_installers = optional(object({
create_bucket = optional(bool, true)
bucket_name = optional(string, null)
bucket_prefix = optional(string, "fleet-software-installers-")
s3_object_prefix = optional(string, "")
}), {
create_bucket = true
bucket_name = null
bucket_prefix = "fleet-software-installers-"
s3_object_prefix = ""
})
})
| {
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"create": true,
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"depends_on": [],
"extra_environment_variables": {},
"extra_execution_iam_policies": [],
"extra_iam_policies": [],
"extra_load_balancers": [],
"extra_secrets": {},
"family": "fleet",
"iam": {
"execution": {
"name": "fleet-execution-role",
"policy_name": "fleet-iam-policy-execution"
},
"role": {
"name": "fleet-role",
"policy_name": "fleet-iam-policy"
}
},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.54.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"mount_points": [],
"networking": {
"ingress_sources": {
"cidr_blocks": [],
"ipv6_cidr_blocks": [],
"prefix_list_ids": [],
"security_groups": []
},
"security_groups": null,
"subnets": null
},
"pid_mode": null,
"private_key_secret_name": "fleet-server-private-key",
"redis": {
"address": null,
"use_tls": true
},
"repository_credentials": "",
"security_group_name": "fleet",
"security_groups": null,
"service": {
"name": "fleet"
},
"sidecars": [],
"software_installers": {
"bucket_name": null,
"bucket_prefix": "fleet-software-installers-",
"create_bucket": true,
"s3_object_prefix": ""
},
"task_cpu": null,
"task_mem": null,
"volumes": []
}
| no |
diff --git a/terraform/addons/mdmproxy/variables.tf b/terraform/addons/mdmproxy/variables.tf
index cb3d924d25f9..cd743e0276ab 100644
--- a/terraform/addons/mdmproxy/variables.tf
+++ b/terraform/addons/mdmproxy/variables.tf
@@ -79,7 +79,7 @@ variable "alb_config" {
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
- idle_timeout = optional(number, 60)
+ idle_timeout = optional(number, 905)
})
}
diff --git a/terraform/byo-vpc/README.md b/terraform/byo-vpc/README.md
index 16998b14b18f..c40128a9da0e 100644
--- a/terraform/byo-vpc/README.md
+++ b/terraform/byo-vpc/README.md
@@ -31,7 +31,7 @@ No requirements.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [alb\_config](#input\_alb\_config) | n/a | object({
name = optional(string, "fleet")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
allowed_ipv6_cidrs = optional(list(string), ["::/0"])
egress_cidrs = optional(list(string), ["0.0.0.0/0"])
egress_ipv6_cidrs = optional(list(string), ["::/0"])
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
idle_timeout = optional(number, 60)
})
| n/a | yes |
+| [alb\_config](#input\_alb\_config) | n/a | object({
name = optional(string, "fleet")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
allowed_ipv6_cidrs = optional(list(string), ["::/0"])
egress_cidrs = optional(list(string), ["0.0.0.0/0"])
egress_ipv6_cidrs = optional(list(string), ["::/0"])
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
idle_timeout = optional(number, 905)
})
| n/a | yes |
| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module | object({
autoscaling_capacity_providers = optional(any, {})
cluster_configuration = optional(any, {
execute_command_configuration = {
logging = "OVERRIDE"
log_configuration = {
cloud_watch_log_group_name = "/aws/ecs/aws-ec2"
}
}
})
cluster_name = optional(string, "fleet")
cluster_settings = optional(map(string), {
"name" : "containerInsights",
"value" : "enabled",
})
create = optional(bool, true)
default_capacity_provider_use_fargate = optional(bool, true)
fargate_capacity_providers = optional(any, {
FARGATE = {
default_capacity_provider_strategy = {
weight = 100
}
}
FARGATE_SPOT = {
default_capacity_provider_strategy = {
weight = 0
}
}
})
tags = optional(map(string))
})
| {
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
}
| no |
| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. | object({
task_mem = optional(number, null)
task_cpu = optional(number, null)
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.54.1")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
mount_points = optional(list(any), [])
volumes = optional(list(any), [])
extra_environment_variables = optional(map(string), {})
extra_iam_policies = optional(list(string), [])
extra_execution_iam_policies = optional(list(string), [])
extra_secrets = optional(map(string), {})
security_group_name = optional(string, "fleet")
iam_role_arn = optional(string, null)
repository_credentials = optional(string, "")
private_key_secret_name = optional(string, "fleet-server-private-key")
service = optional(object({
name = optional(string, "fleet")
}), {
name = "fleet"
})
database = optional(object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
}), {
password_secret_arn = null
user = null
database = null
address = null
rr_address = null
})
redis = optional(object({
address = string
use_tls = optional(bool, true)
}), {
address = null
use_tls = true
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
create = optional(bool, true)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = optional(object({
arn = string
}), {
arn = null
})
extra_load_balancers = optional(list(any), [])
networking = optional(object({
subnets = optional(list(string), null)
security_groups = optional(list(string), null)
ingress_sources = optional(object({
cidr_blocks = optional(list(string), [])
ipv6_cidr_blocks = optional(list(string), [])
security_groups = optional(list(string), [])
prefix_list_ids = optional(list(string), [])
}), {
cidr_blocks = []
ipv6_cidr_blocks = []
security_groups = []
prefix_list_ids = []
})
}), {
subnets = null
security_groups = null
ingress_sources = {
cidr_blocks = []
ipv6_cidr_blocks = []
security_groups = []
prefix_list_ids = []
}
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
iam = optional(object({
role = optional(object({
name = optional(string, "fleet-role")
policy_name = optional(string, "fleet-iam-policy")
}), {
name = "fleet-role"
policy_name = "fleet-iam-policy"
})
execution = optional(object({
name = optional(string, "fleet-execution-role")
policy_name = optional(string, "fleet-execution-role")
}), {
name = "fleet-execution-role"
policy_name = "fleet-iam-policy-execution"
})
}), {
name = "fleetdm-execution-role"
})
software_installers = optional(object({
create_bucket = optional(bool, true)
bucket_name = optional(string, null)
bucket_prefix = optional(string, "fleet-software-installers-")
s3_object_prefix = optional(string, "")
}), {
create_bucket = true
bucket_name = null
bucket_prefix = "fleet-software-installers-"
s3_object_prefix = ""
})
})
| {
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"create": true,
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"depends_on": [],
"extra_environment_variables": {},
"extra_execution_iam_policies": [],
"extra_iam_policies": [],
"extra_load_balancers": [],
"extra_secrets": {},
"family": "fleet",
"iam": {
"execution": {
"name": "fleet-execution-role",
"policy_name": "fleet-iam-policy-execution"
},
"role": {
"name": "fleet-role",
"policy_name": "fleet-iam-policy"
}
},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.54.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"mount_points": [],
"networking": {
"ingress_sources": {
"cidr_blocks": [],
"ipv6_cidr_blocks": [],
"prefix_list_ids": [],
"security_groups": []
},
"security_groups": null,
"subnets": null
},
"pid_mode": null,
"private_key_secret_name": "fleet-server-private-key",
"redis": {
"address": null,
"use_tls": true
},
"repository_credentials": "",
"security_group_name": "fleet",
"security_groups": null,
"service": {
"name": "fleet"
},
"sidecars": [],
"software_installers": {
"bucket_name": null,
"bucket_prefix": "fleet-software-installers-",
"create_bucket": true,
"s3_object_prefix": ""
},
"task_cpu": null,
"task_mem": null,
"volumes": []
}
| no |
| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. | object({
mem = number
cpu = number
})
| {
"cpu": 1024,
"mem": 2048
}
| no |
diff --git a/terraform/byo-vpc/byo-db/README.md b/terraform/byo-vpc/byo-db/README.md
index 60d144448934..ddc7c0e656f2 100644
--- a/terraform/byo-vpc/byo-db/README.md
+++ b/terraform/byo-vpc/byo-db/README.md
@@ -26,7 +26,7 @@ No requirements.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [alb\_config](#input\_alb\_config) | n/a | object({
name = optional(string, "fleet")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
allowed_ipv6_cidrs = optional(list(string), ["::/0"])
egress_cidrs = optional(list(string), ["0.0.0.0/0"])
egress_ipv6_cidrs = optional(list(string), ["::/0"])
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
idle_timeout = optional(number, 60)
})
| n/a | yes |
+| [alb\_config](#input\_alb\_config) | n/a | object({
name = optional(string, "fleet")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
allowed_ipv6_cidrs = optional(list(string), ["::/0"])
egress_cidrs = optional(list(string), ["0.0.0.0/0"])
egress_ipv6_cidrs = optional(list(string), ["::/0"])
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
idle_timeout = optional(number, 905)
})
| n/a | yes |
| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module | object({
autoscaling_capacity_providers = optional(any, {})
cluster_configuration = optional(any, {
execute_command_configuration = {
logging = "OVERRIDE"
log_configuration = {
cloud_watch_log_group_name = "/aws/ecs/aws-ec2"
}
}
})
cluster_name = optional(string, "fleet")
cluster_settings = optional(map(string), {
"name" : "containerInsights",
"value" : "enabled",
})
create = optional(bool, true)
default_capacity_provider_use_fargate = optional(bool, true)
fargate_capacity_providers = optional(any, {
FARGATE = {
default_capacity_provider_strategy = {
weight = 100
}
}
FARGATE_SPOT = {
default_capacity_provider_strategy = {
weight = 0
}
}
})
tags = optional(map(string))
})
| {
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
}
| no |
| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. | object({
task_mem = optional(number, null)
task_cpu = optional(number, null)
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.54.1")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
mount_points = optional(list(any), [])
volumes = optional(list(any), [])
extra_environment_variables = optional(map(string), {})
extra_iam_policies = optional(list(string), [])
extra_execution_iam_policies = optional(list(string), [])
extra_secrets = optional(map(string), {})
security_group_name = optional(string, "fleet")
iam_role_arn = optional(string, null)
repository_credentials = optional(string, "")
private_key_secret_name = optional(string, "fleet-server-private-key")
service = optional(object({
name = optional(string, "fleet")
}), {
name = "fleet"
})
database = optional(object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
}), {
password_secret_arn = null
user = null
database = null
address = null
rr_address = null
})
redis = optional(object({
address = string
use_tls = optional(bool, true)
}), {
address = null
use_tls = true
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
create = optional(bool, true)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = optional(object({
arn = string
}), {
arn = null
})
extra_load_balancers = optional(list(any), [])
networking = optional(object({
subnets = optional(list(string), null)
security_groups = optional(list(string), null)
ingress_sources = optional(object({
cidr_blocks = optional(list(string), [])
ipv6_cidr_blocks = optional(list(string), [])
security_groups = optional(list(string), [])
prefix_list_ids = optional(list(string), [])
}), {
cidr_blocks = []
ipv6_cidr_blocks = []
security_groups = []
prefix_list_ids = []
})
}), {
subnets = null
security_groups = null
ingress_sources = {
cidr_blocks = []
ipv6_cidr_blocks = []
security_groups = []
prefix_list_ids = []
}
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
iam = optional(object({
role = optional(object({
name = optional(string, "fleet-role")
policy_name = optional(string, "fleet-iam-policy")
}), {
name = "fleet-role"
policy_name = "fleet-iam-policy"
})
execution = optional(object({
name = optional(string, "fleet-execution-role")
policy_name = optional(string, "fleet-execution-role")
}), {
name = "fleet-execution-role"
policy_name = "fleet-iam-policy-execution"
})
}), {
name = "fleetdm-execution-role"
})
software_installers = optional(object({
create_bucket = optional(bool, true)
bucket_name = optional(string, null)
bucket_prefix = optional(string, "fleet-software-installers-")
s3_object_prefix = optional(string, "")
}), {
create_bucket = true
bucket_name = null
bucket_prefix = "fleet-software-installers-"
s3_object_prefix = ""
})
})
| {
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"create": true,
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"depends_on": [],
"extra_environment_variables": {},
"extra_execution_iam_policies": [],
"extra_iam_policies": [],
"extra_load_balancers": [],
"extra_secrets": {},
"family": "fleet",
"iam": {
"execution": {
"name": "fleet-execution-role",
"policy_name": "fleet-iam-policy-execution"
},
"role": {
"name": "fleet-role",
"policy_name": "fleet-iam-policy"
}
},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.54.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"mount_points": [],
"networking": {
"ingress_sources": {
"cidr_blocks": [],
"ipv6_cidr_blocks": [],
"prefix_list_ids": [],
"security_groups": []
},
"security_groups": null,
"subnets": null
},
"pid_mode": null,
"private_key_secret_name": "fleet-server-private-key",
"redis": {
"address": null,
"use_tls": true
},
"repository_credentials": "",
"security_group_name": "fleet",
"security_groups": null,
"service": {
"name": "fleet"
},
"sidecars": [],
"software_installers": {
"bucket_name": null,
"bucket_prefix": "fleet-software-installers-",
"create_bucket": true,
"s3_object_prefix": ""
},
"task_cpu": null,
"task_mem": null,
"volumes": []
}
| no |
| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. | object({
mem = number
cpu = number
})
| {
"cpu": 1024,
"mem": 2048
}
| no |
diff --git a/terraform/byo-vpc/byo-db/variables.tf b/terraform/byo-vpc/byo-db/variables.tf
index ddd474e14ba7..16c7d7a1e9fd 100644
--- a/terraform/byo-vpc/byo-db/variables.tf
+++ b/terraform/byo-vpc/byo-db/variables.tf
@@ -309,6 +309,6 @@ variable "alb_config" {
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
- idle_timeout = optional(number, 60)
+ idle_timeout = optional(number, 905)
})
}
diff --git a/terraform/byo-vpc/variables.tf b/terraform/byo-vpc/variables.tf
index f85ddb408381..4c8e17338781 100644
--- a/terraform/byo-vpc/variables.tf
+++ b/terraform/byo-vpc/variables.tf
@@ -402,6 +402,6 @@ variable "alb_config" {
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
- idle_timeout = optional(number, 60)
+ idle_timeout = optional(number, 905)
})
}
diff --git a/terraform/example/main.tf b/terraform/example/main.tf
index 7f169df3e89b..81ff3cd69304 100644
--- a/terraform/example/main.tf
+++ b/terraform/example/main.tf
@@ -108,7 +108,7 @@ module "fleet" {
alb_config = {
# Script execution can run for up to 300s plus overhead.
# Ensure the load balancer does not 5XX before we have results.
- idle_timeout = 605
+ idle_timeout = 905
}
}
diff --git a/terraform/variables.tf b/terraform/variables.tf
index 9f08b701df38..6bb2f2231748 100644
--- a/terraform/variables.tf
+++ b/terraform/variables.tf
@@ -448,7 +448,7 @@ variable "alb_config" {
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
- idle_timeout = optional(number, 60)
+ idle_timeout = optional(number, 905)
})
default = {}
}
diff --git a/tools/dialog/main.go b/tools/dialog/main.go
new file mode 100644
index 000000000000..23e46da66c30
--- /dev/null
+++ b/tools/dialog/main.go
@@ -0,0 +1,55 @@
+package main
+
+// This is a tool to test the zenity package on Linux
+// It will show an entry dialog, a progress dialog, and an info dialog
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
+ "github.com/fleetdm/fleet/v4/orbit/pkg/zenity"
+)
+
+func main() {
+ prompt := zenity.New()
+ ctx := context.Background()
+
+ output, err := prompt.ShowEntry(ctx, dialog.EntryOptions{
+ Title: "Zenity Test Entry Title",
+ Text: "Zenity Test Entry Text",
+ HideText: true,
+ TimeOut: 10 * time.Second,
+ })
+ if err != nil {
+ fmt.Println("Err ShowEntry")
+ panic(err)
+ }
+
+ ctx, cancelProgress := context.WithCancel(context.Background())
+
+ go func() {
+ err := prompt.ShowProgress(ctx, dialog.ProgressOptions{
+ Title: "Zenity Test Progress Title",
+ Text: "Zenity Test Progress Text",
+ })
+ if err != nil {
+ fmt.Println("Err ShowProgress")
+ panic(err)
+ }
+ }()
+
+ time.Sleep(2 * time.Second)
+ cancelProgress()
+
+ err = prompt.ShowInfo(ctx, dialog.InfoOptions{
+ Title: "Zenity Test Info Title",
+ Text: "Result: " + string(output),
+ TimeOut: 10 * time.Second,
+ })
+ if err != nil {
+ fmt.Println("Err ShowInfo")
+ panic(err)
+ }
+}
diff --git a/tools/fleetctl-docker/Dockerfile b/tools/fleetctl-docker/Dockerfile
index 6b82ed628541..ca678cedf7fa 100644
--- a/tools/fleetctl-docker/Dockerfile
+++ b/tools/fleetctl-docker/Dockerfile
@@ -2,7 +2,7 @@ FROM rust:latest@sha256:56418f03475cf7b107f87d7fabe99ce9a4a9f9904daafa99be7c50d9
ARG transporter_url=https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/resources/download/public/Transporter__Linux/bin
-RUN cargo install --version 0.16.0 apple-codesign \
+RUN cargo install --locked --version 0.16.0 apple-codesign \
&& curl -sSf $transporter_url -o transporter_install.sh \
&& sh transporter_install.sh --target transporter --accept --noexec
diff --git a/tools/luks/luks/main.go b/tools/luks/luks/main.go
new file mode 100644
index 000000000000..f20c28f4e289
--- /dev/null
+++ b/tools/luks/luks/main.go
@@ -0,0 +1,72 @@
+//go:build linux
+
+package main
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
+ "github.com/fleetdm/fleet/v4/orbit/pkg/lvm"
+ "github.com/fleetdm/fleet/v4/orbit/pkg/zenity"
+ "github.com/siderolabs/go-blockdevice/v2/encryption"
+ "github.com/siderolabs/go-blockdevice/v2/encryption/luks"
+)
+
+func main() {
+ devicePath, err := lvm.FindRootDisk()
+ if err != nil {
+ fmt.Println("devicepath err:", err)
+ panic(err)
+ }
+
+ prompt := zenity.New()
+
+ // Prompt existing passphrase from the user.
+ currentPassphrase, err := prompt.ShowEntry(context.Background(), dialog.EntryOptions{
+ Title: "Enter Existing LUKS Passphrase",
+ Text: "Enter your existing LUKS passphrase:",
+ HideText: true,
+ })
+ if err != nil {
+ fmt.Println("Err ShowEntry")
+ panic(err)
+ }
+
+ const escrowPassPhrase = "fleet123"
+
+ device := luks.New(luks.AESXTSPlain64Cipher)
+
+ keySlot := 1
+ for {
+ if keySlot == 8 {
+ panic(errors.New("all LUKS key slots are full"))
+ }
+
+ userKey := encryption.NewKey(0, currentPassphrase)
+ escrowKey := encryption.NewKey(keySlot, []byte(escrowPassPhrase))
+
+ if err := device.AddKey(context.Background(), devicePath, userKey, escrowKey); err != nil {
+ if errors.Is(err, encryption.ErrEncryptionKeyRejected) {
+ currentPassphrase, err = prompt.ShowEntry(context.Background(), dialog.EntryOptions{
+ Title: "Enter Existing LUKS Passphrase",
+ Text: "Bad password. Enter your existing LUKS passphrase:",
+ HideText: true,
+ })
+ if err != nil {
+ fmt.Println("Err Retry ShowEntry")
+ panic(err)
+ }
+ continue
+ }
+
+ keySlot++
+ continue
+ }
+
+ break
+ }
+
+ fmt.Println("Key escrowed successfully.")
+}
diff --git a/tools/luks/lvm/main.go b/tools/luks/lvm/main.go
new file mode 100644
index 000000000000..de6cbe143195
--- /dev/null
+++ b/tools/luks/lvm/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/fleetdm/fleet/v4/orbit/pkg/lvm"
+)
+
+func main() {
+ disk, err := lvm.FindRootDisk()
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println("Root Partition:", disk)
+}
diff --git a/tools/release/publish_release.sh b/tools/release/publish_release.sh
index cea7e6fccb70..3757432b474f 100755
--- a/tools/release/publish_release.sh
+++ b/tools/release/publish_release.sh
@@ -626,15 +626,6 @@ fi
start_ver_tag=fleet-$start_version
-# Check if there are updates to fleetctl dependencies (only when doing security updates to base images).
-if [[ $(git diff $start_ver_tag ./tools/wix-docker ./tools/bomutils-docker) ]]; then
- echo "⚠️ Changes in fleetctl dependencies detected, please run the following before continuing the release:"
- echo "1. git tag fleetctl-docker-deps-$next_ver && git push origin fleetctl-docker-deps-$next_ver"
- echo "2. Wait for the triggered https://github.com/fleetdm/fleet/actions/workflows/release-fleetctl-docker-deps.yaml build to finish."
- echo "3. Smoke test the pushed images by manually running the following action: https://github.com/fleetdm/fleet/actions/workflows/test-packaging.yml"
- exit 1
-fi
-
if [[ "$minor" == "true" ]]; then
echo "Minor release from $start_version to $next_ver"
# For scheduled minor releases, we want to branch off of main
diff --git a/website/api/controllers/customers/get-stripe-checkout-session-url.js b/website/api/controllers/customers/get-stripe-checkout-session-url.js
new file mode 100644
index 000000000000..324e428a9135
--- /dev/null
+++ b/website/api/controllers/customers/get-stripe-checkout-session-url.js
@@ -0,0 +1,70 @@
+module.exports = {
+
+
+ friendlyName: 'Get Stripe checkout session url',
+
+
+ description: 'Creates a Stripe checkout session for a new Fleet Premium subscription and returns the URL',
+
+
+ inputs: {
+ quoteId: {
+ type: 'number',
+ required: true,
+ description: 'The quote to use (determines the price and number of hosts.)'
+ },
+
+ },
+
+
+ exits: {
+ success: {
+ description: 'A Stripe checkout session was successfully created for a new Fleet Premium subscription.'
+ }
+ },
+
+
+ fn: async function (inputs) {
+ // Configure Stripe
+ const stripe = require('stripe')(sails.config.custom.stripeSecret);
+
+ // Find the quote record that was created.
+ let quoteRecord = await Quote.findOne({id: inputs.quoteId});
+ if(!quoteRecord) {
+ throw new Error(`Consistency violation: The specified quote (${inputs.quoteId}) no longer seems to exist.`);
+ }
+
+ // What if the stripe customer id doesn't already exist on the user?
+ if (!this.req.me.stripeCustomerId) {
+ throw new Error(`Consistency violation: The logged-in user's (${this.req.me.emailAddress}) Stripe customer id has somehow gone missing!`);
+ }
+ // Create a new Stripe checkout session for this subscription.
+ let stripeCheckoutSession = await stripe.checkout.sessions.create({
+ customer: this.req.me.stripeCustomerId,
+ customer_update: {// eslint-disable-line camelcase
+ name: 'auto',
+ address: 'auto',
+ },
+ success_url: `${sails.config.custom.baseUrl}/customers/dashboard?order-complete`,// eslint-disable-line camelcase
+ line_items: [// eslint-disable-line camelcase
+ {
+ price: sails.config.custom.stripeSubscriptionPriceId,
+ quantity: quoteRecord.numberOfHosts,
+ },
+ ],
+ mode: 'subscription',
+ billing_address_collection: 'required',// eslint-disable-line camelcase
+ tax_id_collection: {// eslint-disable-line camelcase
+ enabled: true,
+ required: 'if_supported'
+ }
+ });
+
+ // Return the url of the Stripe checkout session.
+ // Users will be taken to this URL via the handleSubmitting function of the on the /customers/new-license page.
+ return stripeCheckoutSession.url;
+
+ }
+
+
+};
diff --git a/website/api/controllers/customers/redirect-to-stripe-billing-portal.js b/website/api/controllers/customers/redirect-to-stripe-billing-portal.js
new file mode 100644
index 000000000000..9e094da4290c
--- /dev/null
+++ b/website/api/controllers/customers/redirect-to-stripe-billing-portal.js
@@ -0,0 +1,41 @@
+module.exports = {
+
+
+ friendlyName: 'Redirect to stripe billing portal',
+
+
+ description: 'Creates a Stripe billing portal session for a Fleet Premium subscriber and redirects them.',
+
+
+ exits: {
+ redirect: {
+ responseType: 'redirect',
+ description: 'The requesting user is being redirected to the Stripe customer billing portal.'
+ },
+ noSubscription: {
+ responseType: 'redirect',
+ description: 'The Requesting user does not have a Fleet premium subscription.'
+ },
+ },
+
+
+ fn: async function () {
+ // Note: This action is covered by the 'is-logged-in' policy.
+ const stripe = require('stripe')(sails.config.custom.stripeSecret);
+
+ let thisUsersSubscription = await Subscription.findOne({user: this.req.me.id});
+ if(!thisUsersSubscription){
+ throw {noSubscription: '/customers/new-license'};
+ }
+
+ let session = await stripe.billingPortal.sessions.create({
+ customer: this.req.me.stripeCustomerId,
+ return_url: `${sails.config.custom.baseUrl}/customers/dashboard`,// eslint-disable-line camelcase
+ });
+ // All done.
+ throw {redirect: session.url};
+
+ }
+
+
+};
diff --git a/website/api/controllers/customers/view-dashboard.js b/website/api/controllers/customers/view-dashboard.js
index 2cc99e4820cb..338159e6b4ff 100644
--- a/website/api/controllers/customers/view-dashboard.js
+++ b/website/api/controllers/customers/view-dashboard.js
@@ -22,7 +22,7 @@ module.exports = {
fn: async function () {
-
+ const stripe = require('stripe')(sails.config.custom.stripeSecret);
const today = Date.now();
const oneYearInMs = (1000 * 60 * 60 * 24 * 365);
const oneYearAgoAt = today - oneYearInMs;
@@ -30,24 +30,33 @@ module.exports = {
const thirtyDaysFromNowAt = today + (1000 * 60 * 60 * 24 * 30);
let subscriptionHasBeenRecentlyRenewed = false;
let subscriptionExpiresSoon = false;
+ let subscriptionIsExpired = false;
// Get subscription Info
let thisSubscription = await Subscription.findOne({user: this.req.me.id});
-
// If the user does not have a subscription, then help them subscribe.
if(!thisSubscription) {
throw {redirect: '/customers/new-license'};
}
+ let stripeSubscriptionDetails = await stripe.subscriptions.retrieve(thisSubscription.stripeSubscriptionId);
+ let willSubscriptionRenew = true;
+ if(stripeSubscriptionDetails.cancel_at_period_end === true){
+ willSubscriptionRenew = false;
+ }
// If this subscription is over a year old, and was renewed in the past 30 days set subscriptionHasBeenRecentlyRenewed to true.
if(thisSubscription.createdAt <= oneYearAgoAt && (thisSubscription.nextBillingAt - oneYearInMs) >= thirtyDaysAgoAt) {
subscriptionHasBeenRecentlyRenewed = true;
}
// If this subscription will renew in the next 30 days, set subscriptionExpiresSoon to true.
- if(thisSubscription.nextBillingAt <= thirtyDaysFromNowAt){
+ if(thisSubscription.nextBillingAt <= thirtyDaysFromNowAt && willSubscriptionRenew){
subscriptionExpiresSoon = true;
}
+ // If this subscription is expired, set subscriptionIsExpired to true.
+ if(thisSubscription.nextBillingAt <= Date.now()){
+ subscriptionIsExpired = true;
+ }
// Respond with view.
return {
@@ -55,6 +64,8 @@ module.exports = {
thisSubscription,
subscriptionExpiresSoon,
subscriptionHasBeenRecentlyRenewed,
+ willSubscriptionRenew,
+ subscriptionIsExpired,
};
}
diff --git a/website/api/controllers/get-est-device-certificate.js b/website/api/controllers/get-est-device-certificate.js
index a69e83d0ebcc..5a69979f2fa7 100644
--- a/website/api/controllers/get-est-device-certificate.js
+++ b/website/api/controllers/get-est-device-certificate.js
@@ -83,11 +83,11 @@ module.exports = {
throw 'invalidToken';
}
- if (!introspectResponse.body.active) {
+ const introspectBody = JSON.parse(introspectResponse.body);
+ if (!introspectBody.active) {
throw 'invalidToken';
}
-
- const introspectUsername = introspectResponse.body.username;
+ const introspectUsername = introspectBody.username;
// Extract the email and username from the CSR. Ensure they match.
let jsrsasign = require('jsrsasign');
diff --git a/website/api/controllers/view-app-details.js b/website/api/controllers/view-app-details.js
new file mode 100644
index 000000000000..e757164e3c05
--- /dev/null
+++ b/website/api/controllers/view-app-details.js
@@ -0,0 +1,60 @@
+module.exports = {
+
+
+ friendlyName: 'View app details',
+
+
+ description: 'Display "App details" page.',
+
+
+ inputs: {
+ appIdentifier: {
+ type: 'string',
+ required: true,
+ description: 'the identifier of an app in Fleet\'s maintained app library.',
+ example: '1password'
+ },
+ },
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/app-details'
+ },
+
+ badConfig: {
+ responseType: 'badConfig'
+ },
+
+ notFound: {
+ responseType: 'notFound'
+ },
+
+ },
+
+
+ fn: async function ({appIdentifier}) {
+
+ if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.appLibrary) || !sails.config.builtStaticContent.appLibrary) {
+ throw {badConfig: 'builtStaticContent.appLibrary'};
+ }
+
+ let thisApp = _.find(sails.config.builtStaticContent.appLibrary, { identifier: appIdentifier });
+ if (!thisApp) {
+ throw 'notFound';
+ }
+ // FUTURE: make these better.
+ let pageTitleForMeta = thisApp.name + ' | Fleet app library';
+ // let pageDescriptionForMeta = 'TODO'
+
+ // Respond with view.
+ return {
+ thisApp,
+ // pageDescriptionForMeta,
+ pageTitleForMeta,
+ };
+
+ }
+
+
+};
diff --git a/website/api/controllers/view-app-library.js b/website/api/controllers/view-app-library.js
new file mode 100644
index 000000000000..1ea01f1e25a0
--- /dev/null
+++ b/website/api/controllers/view-app-library.js
@@ -0,0 +1,33 @@
+module.exports = {
+
+
+ friendlyName: 'View app library',
+
+
+ description: 'Display "App library" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/app-library'
+ },
+ badConfig: { responseType: 'badConfig' },
+ },
+
+
+ fn: async function () {
+
+ if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.appLibrary) || !sails.config.builtStaticContent.appLibrary) {
+ throw {badConfig: 'builtStaticContent.appLibrary'};
+ }
+
+ let allApps = sails.config.builtStaticContent.appLibrary;
+ allApps = _.sortBy(allApps, 'name');
+ // Respond with view.
+ return {allApps};
+
+ }
+
+
+};
diff --git a/website/api/controllers/view-endpoint-ops.js b/website/api/controllers/view-observability.js
similarity index 92%
rename from website/api/controllers/view-endpoint-ops.js
rename to website/api/controllers/view-observability.js
index 7e1f33c5ac8c..6fa1a3381e14 100644
--- a/website/api/controllers/view-endpoint-ops.js
+++ b/website/api/controllers/view-observability.js
@@ -1,16 +1,16 @@
module.exports = {
- friendlyName: 'View endpoint ops',
+ friendlyName: 'View observability',
- description: 'Display "Endpoint ops" page.',
+ description: 'Display "Observability" page.',
exits: {
success: {
- viewTemplatePath: 'pages/endpoint-ops'
+ viewTemplatePath: 'pages/observability'
},
badConfig: { responseType: 'badConfig' },
},
@@ -22,8 +22,8 @@ module.exports = {
}
// Get testimonials for the component.
let testimonialsForScrollableTweets = _.clone(sails.config.builtStaticContent.testimonials);
- // Default the pagePersonalization to the user's primaryBuyingSituation.
- let pagePersonalization = this.req.session.primaryBuyingSituation;
+ // Default the pagePersonalization to the user's primaryBuyingSituation if it is set, otherwise, default to the eo-it view..
+ let pagePersonalization = this.req.session.primaryBuyingSituation ? this.req.session.primaryBuyingSituation : 'eo-it';
// If a purpose query parameter is set, update the pagePersonalization value.
// Note: This is the only page we're using this method instead of using the primaryBuyingSiutation value set in the users session.
// This lets us link to the security and IT versions of the endpoint ops page from the unpersonalized homepage without changing the users primaryBuyingSituation.
diff --git a/website/api/controllers/view-vulnerability-management.js b/website/api/controllers/view-software-management.js
similarity index 90%
rename from website/api/controllers/view-vulnerability-management.js
rename to website/api/controllers/view-software-management.js
index 2a9c2ae04ce2..b95b292b45b3 100644
--- a/website/api/controllers/view-vulnerability-management.js
+++ b/website/api/controllers/view-software-management.js
@@ -1,16 +1,16 @@
module.exports = {
- friendlyName: 'View vulnerability-management',
+ friendlyName: 'View software-management',
- description: 'Display "Vulnerability management" page.',
+ description: 'Display "Software management" page.',
exits: {
success: {
- viewTemplatePath: 'pages/vulnerability-management'
+ viewTemplatePath: 'pages/software-management'
},
badConfig: { responseType: 'badConfig' },
},
diff --git a/website/api/controllers/view-testimonials.js b/website/api/controllers/view-testimonials.js
index dfd040ccf569..5365a312d817 100644
--- a/website/api/controllers/view-testimonials.js
+++ b/website/api/controllers/view-testimonials.js
@@ -28,12 +28,82 @@ module.exports = {
let testimonialsForMdm = _.filter(testimonials, (testimonial)=>{
return _.contains(testimonial.productCategories, 'Device management');
});
+ let testimonialOrderForMdm = [
+ 'Scott MacVicar',
+ 'Wes Whetstone',
+ 'Nick Fohs',
+ 'Erik Gomez',
+ 'Matt Carr',
+ 'Nico Waisman',
+ 'Kenny Botelho',
+ 'Dan Grzelak',
+ 'Eric Tan',
+ ];
+ testimonialsForMdm.sort((a, b)=>{
+ if(testimonialOrderForMdm.indexOf(a.quoteAuthorName) === -1){
+ return 1;
+ } else if(testimonialOrderForMdm.indexOf(b.quoteAuthorName) === -1) {
+ return -1;
+ }
+ return testimonialOrderForMdm.indexOf(a.quoteAuthorName) - testimonialOrderForMdm.indexOf(b.quoteAuthorName);
+ });
let testimonialsForSecurityEngineering = _.filter(testimonials, (testimonial)=>{
return _.contains(testimonial.productCategories, 'Vulnerability management');
});
+ let testimonialOrderForSecurityEngineering = [
+ 'Nico Waisman',
+ 'Austin Anderson',
+ 'Chandra Majumdar',
+ 'Andre Shields',
+ 'Dan Grzelak',
+ 'Charles Zaffery',
+ 'Erik Gomez',
+ 'Nick Fohs',
+ 'Dhruv Majumdar',
+ 'Arsenio Figueroa',
+ ];
+ testimonialsForSecurityEngineering.sort((a, b)=>{
+ if(testimonialOrderForSecurityEngineering.indexOf(a.quoteAuthorName) === -1){
+ return 1;
+ } else if(testimonialOrderForSecurityEngineering.indexOf(b.quoteAuthorName) === -1) {
+ return -1;
+ }
+ return testimonialOrderForSecurityEngineering.indexOf(a.quoteAuthorName) - testimonialOrderForSecurityEngineering.indexOf(b.quoteAuthorName);
+ });
let testimonialsForItEngineering = _.filter(testimonials, (testimonial)=>{
return _.contains(testimonial.productCategories, 'Endpoint operations');
});
+ let testimonialOrderForItEngineering = [
+ 'Charles Zaffery',
+ 'Nico Waisman',
+ 'Erik Gomez',
+ 'Mike Arpaia',
+ 'Ahmed Elshaer',
+ 'Kenny Botelho',
+ 'Alvaro Gutierrez',
+ 'Tom Larkin',
+ 'Nick Fohs',
+ 'charles zaffery',// Note: This testimonial's quoteAuthorName value is lowercased so it can be sorted to a different position than the other Charles Zaffery quote.
+ 'Andre Shields',
+ 'Abubakar Yousafzai',
+ 'Chandra Majumdar',
+ 'Joe Pistone',
+ 'Dan Grzelak',
+ 'Austin Anderson',
+ 'Brendan Shaklovitz',
+ 'Dhruv Majumdar',
+ 'Wes Whetstone',
+ 'Eric Tan',
+ 'Arsenio Figueroa',
+ ];
+ testimonialsForItEngineering.sort((a, b)=>{
+ if(testimonialOrderForItEngineering.indexOf(a.quoteAuthorName) === -1){
+ return 1;
+ } else if(testimonialOrderForItEngineering.indexOf(b.quoteAuthorName) === -1) {
+ return -1;
+ }
+ return testimonialOrderForItEngineering.indexOf(a.quoteAuthorName) - testimonialOrderForItEngineering.indexOf(b.quoteAuthorName);
+ });
let testimonialsWithVideoLinks = _.filter(testimonials, (testimonial)=>{
return testimonial.youtubeVideoUrl;
});
diff --git a/website/api/controllers/webhooks/receive-from-stripe.js b/website/api/controllers/webhooks/receive-from-stripe.js
index 394c17851e2c..9f505c8d02c2 100644
--- a/website/api/controllers/webhooks/receive-from-stripe.js
+++ b/website/api/controllers/webhooks/receive-from-stripe.js
@@ -41,7 +41,7 @@ module.exports = {
fn: async function ({id, type, data, webhookSecret}) {
-
+ const stripe = require('stripe')(sails.config.custom.stripeSecret);
let assert = require('assert');
if(!this.req.get('stripe-signature')) {
@@ -77,6 +77,7 @@ module.exports = {
'invoice.payment_action_required',// Sent when a user's billing card requires additional verification from stripe.
'invoice.updated',// Sent before an incomplete invoice is voided. (~24 hours after a payment fails)
'invoice.voided',// Sent when an incomplete invoice is marked as voided. (~24 hours after a payment fails)
+ 'checkout.session.completed'// Sent when a user completes a Stripe Checkout session.
];
// If this event is for a subscription that was just created, we won't have a matching Subscription record in the database. This is because we wait until the subscription's invoice is paid to create the record in our database.
@@ -86,15 +87,16 @@ module.exports = {
throw new Error(`The Stripe subscription events webhook received a event for a subscription with stripeSubscriptionId: ${subscriptionIdToFind}, but no matching record was found in our database.`);
} else {
let userReferencedInStripeEvent = await User.findOne({stripeCustomerId: stripeEventData.customer});
- if(!userReferencedInStripeEvent){
+ if(!userReferencedInStripeEvent) {
throw new Error(`The receive-from-stripe webhook received an event for an invoice (type: ${type}) for a subscription (stripeSubscriptionId: ${subscriptionIdToFind}) but no matching Subscription or User record (stripeCustomerId: ${stripeEventData.customer}) was found in our databse.`);
- } else {
- return;
}
}
}
- let userForThisSubscription = subscriptionForThisEvent.user;
+ let userForThisSubscription = await User.findOne({stripeCustomerId: stripeEventData.customer});
+ if(!userForThisSubscription){
+ throw new Error(`The stripe subscription events webhook received a tpye ${type} event for a user with stripeCustomerId: ${stripeEventData.customer}, but no matching user was found in the databse. Stripe event ID: ${id}`);
+ }
// ┬ ┬┌─┐┌─┐┌─┐┌┬┐┬┌┐┌┌─┐ ┬─┐┌─┐┌┐┌┌─┐┬ ┬┌─┐┬
// │ │├─┘│ │ │││││││││ ┬ ├┬┘├┤ │││├┤ │││├─┤│
// └─┘┴ └─┘└─┘┴ ┴┴┘└┘└─┘ ┴└─└─┘┘└┘└─┘└┴┘┴ ┴┴─┘
@@ -200,6 +202,37 @@ module.exports = {
fleetLicenseKey: newLicenseKeyForThisSubscription,
nextBillingAt: nextBillingAt
});
+ } else if(type === 'checkout.session.completed' && stripeEventData.payment_status === 'paid') {
+ // For handling successful payments from a Stripe checkout session.
+ // Note: This event is sent the moment the user's payment succeeds.
+ if(subscriptionForThisEvent){// Throw an error if there is an existing subscription with this ID that matches this event in the website's database.
+ throw new Error(`Consistency violation! The stripe webhook received a "${type}" event for a new subscription being created, but a subscription with the stripe ID ${subscriptionForThisEvent.stripeSubscriptionId} already exists.`);
+ }
+ // Retrieve the subscription details from Stripe.
+ let newSubscriptionDetails = await stripe.subscriptions.retrieve(stripeEventData.subscription);
+ // Convert the timestamp of the next time this subscription will be billed into a JS timestamp (Epoch MS)
+ let nextBillingAt = newSubscriptionDetails.current_period_end * 1000;
+ // Get the number of Hosts.
+ let numberOfHosts = newSubscriptionDetails.quantity;
+ // Get the whole dollar price per host.
+ let subscriptionPricePerHost = newSubscriptionDetails.plan.amount / 100;
+ // Determine the annual cost of this user's subscription
+ let subscriptionPrice = subscriptionPricePerHost * numberOfHosts;
+ // Generate a new license key.
+ let newLicenseKey = await sails.helpers.createLicenseKey.with({
+ numberOfHosts,
+ organization: userForThisSubscription.organization,
+ expiresAt: nextBillingAt,
+ });
+ // Create the database record for this subscription.
+ await Subscription.create({
+ nextBillingAt,
+ numberOfHosts,
+ subscriptionPrice,
+ stripeSubscriptionId: newSubscriptionDetails.id,
+ fleetLicenseKey: newLicenseKey,
+ user: userForThisSubscription.id,
+ });
}
// FUTURE: send emails about failed payments. (type === 'invoice.payment_failed' && stripeEventData.billing_reason === 'subscription_cycle')
diff --git a/website/api/helpers/salesforce/update-or-create-contact-and-account.js b/website/api/helpers/salesforce/update-or-create-contact-and-account.js
index 0f2c26c96516..5d0b24dcd548 100644
--- a/website/api/helpers/salesforce/update-or-create-contact-and-account.js
+++ b/website/api/helpers/salesforce/update-or-create-contact-and-account.js
@@ -161,8 +161,9 @@ module.exports = {
delete valuesToSet.Intent_signals__c;
}
}
- // Check the existing contact record's psychologicalStage.
- if(psychologicalStage) {
+
+ // Check the existing contact record's psychologicalStage (If it is set).
+ if(psychologicalStage && existingContactRecord.Stage__c !== null) {
let recordsCurrentPsyStage = existingContactRecord.Stage__c;
// Because each psychological stage starts with a number, we'll get the first character in the record's current psychological stage and the new psychological stage to make comparison easier.
let psyStageStageNumberToChangeTo = Number(psychologicalStage[0]);
diff --git a/website/assets/images/app-icon-1password-60x60@2x.png b/website/assets/images/app-icon-1password-60x60@2x.png
new file mode 100644
index 000000000000..99bf1fe9ba4f
Binary files /dev/null and b/website/assets/images/app-icon-1password-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-adobe-acrobat-reader-60x60@2x.png b/website/assets/images/app-icon-adobe-acrobat-reader-60x60@2x.png
new file mode 100644
index 000000000000..b7d47badf5d6
Binary files /dev/null and b/website/assets/images/app-icon-adobe-acrobat-reader-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-box-drive-60x60@2x.png b/website/assets/images/app-icon-box-drive-60x60@2x.png
new file mode 100644
index 000000000000..807083aca3c0
Binary files /dev/null and b/website/assets/images/app-icon-box-drive-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-brave-browser-60x60@2x.png b/website/assets/images/app-icon-brave-browser-60x60@2x.png
new file mode 100644
index 000000000000..53fe7548c40a
Binary files /dev/null and b/website/assets/images/app-icon-brave-browser-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-cloudflare-warp-60x60@2x.png b/website/assets/images/app-icon-cloudflare-warp-60x60@2x.png
new file mode 100644
index 000000000000..3c38697db8ce
Binary files /dev/null and b/website/assets/images/app-icon-cloudflare-warp-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-docker-60x60@2x.png b/website/assets/images/app-icon-docker-60x60@2x.png
new file mode 100644
index 000000000000..074766f398e0
Binary files /dev/null and b/website/assets/images/app-icon-docker-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-figma-60x60@2x.png b/website/assets/images/app-icon-figma-60x60@2x.png
new file mode 100644
index 000000000000..a30d4e902425
Binary files /dev/null and b/website/assets/images/app-icon-figma-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-firefox-60x60@2x.png b/website/assets/images/app-icon-firefox-60x60@2x.png
new file mode 100644
index 000000000000..1ff3b7c35dec
Binary files /dev/null and b/website/assets/images/app-icon-firefox-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-google-chrome-60x60@2x.png b/website/assets/images/app-icon-google-chrome-60x60@2x.png
new file mode 100644
index 000000000000..3bf92c2a9e99
Binary files /dev/null and b/website/assets/images/app-icon-google-chrome-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-microsoft-edge-60x60@2x.png b/website/assets/images/app-icon-microsoft-edge-60x60@2x.png
new file mode 100644
index 000000000000..bd42b2fad42d
Binary files /dev/null and b/website/assets/images/app-icon-microsoft-edge-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-microsoft-excel-60x60@2x.png b/website/assets/images/app-icon-microsoft-excel-60x60@2x.png
new file mode 100644
index 000000000000..8401843239d7
Binary files /dev/null and b/website/assets/images/app-icon-microsoft-excel-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-microsoft-teams-60x60@2x.png b/website/assets/images/app-icon-microsoft-teams-60x60@2x.png
new file mode 100644
index 000000000000..08c254d1018d
Binary files /dev/null and b/website/assets/images/app-icon-microsoft-teams-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-microsoft-word-60x60@2x.png b/website/assets/images/app-icon-microsoft-word-60x60@2x.png
new file mode 100644
index 000000000000..18cd2362c147
Binary files /dev/null and b/website/assets/images/app-icon-microsoft-word-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-notion-60x60@2x.png b/website/assets/images/app-icon-notion-60x60@2x.png
new file mode 100644
index 000000000000..7afb82ba8298
Binary files /dev/null and b/website/assets/images/app-icon-notion-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-postman-60x60@2x.png b/website/assets/images/app-icon-postman-60x60@2x.png
new file mode 100644
index 000000000000..3801a78f7813
Binary files /dev/null and b/website/assets/images/app-icon-postman-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-slack-60x60@2x.png b/website/assets/images/app-icon-slack-60x60@2x.png
new file mode 100644
index 000000000000..9811ab1eece8
Binary files /dev/null and b/website/assets/images/app-icon-slack-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-teamviewer-60x60@2x.png b/website/assets/images/app-icon-teamviewer-60x60@2x.png
new file mode 100644
index 000000000000..298113cfb141
Binary files /dev/null and b/website/assets/images/app-icon-teamviewer-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-visual-studio-code-60x60@2x.png b/website/assets/images/app-icon-visual-studio-code-60x60@2x.png
new file mode 100644
index 000000000000..b9fe5290acd8
Binary files /dev/null and b/website/assets/images/app-icon-visual-studio-code-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-whatsapp-60x60@2x.png b/website/assets/images/app-icon-whatsapp-60x60@2x.png
new file mode 100644
index 000000000000..0a44830984fb
Binary files /dev/null and b/website/assets/images/app-icon-whatsapp-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-zoom-60x60@2x.png b/website/assets/images/app-icon-zoom-60x60@2x.png
new file mode 100644
index 000000000000..069259362d4f
Binary files /dev/null and b/website/assets/images/app-icon-zoom-60x60@2x.png differ
diff --git a/website/assets/images/articles/fedora-1-1200x675@2x.png b/website/assets/images/articles/fedora-1-1200x675@2x.png
new file mode 100644
index 000000000000..ec32e32b0647
Binary files /dev/null and b/website/assets/images/articles/fedora-1-1200x675@2x.png differ
diff --git a/website/assets/images/articles/fedora-2-1200x675@2x.png b/website/assets/images/articles/fedora-2-1200x675@2x.png
new file mode 100644
index 000000000000..1d533640c2b9
Binary files /dev/null and b/website/assets/images/articles/fedora-2-1200x675@2x.png differ
diff --git a/website/assets/images/articles/fleet-4.60.0-1600x900@2x.png b/website/assets/images/articles/fleet-4.60.0-1600x900@2x.png
new file mode 100644
index 000000000000..01feb42ba2a2
Binary files /dev/null and b/website/assets/images/articles/fleet-4.60.0-1600x900@2x.png differ
diff --git a/website/assets/images/articles/fleet-and-workbrew-1600x900@2x.png b/website/assets/images/articles/fleet-and-workbrew-1600x900@2x.png
new file mode 100644
index 000000000000..9074d10b9781
Binary files /dev/null and b/website/assets/images/articles/fleet-and-workbrew-1600x900@2x.png differ
diff --git a/website/assets/images/articles/ubuntu-1-1200x675@2x.png b/website/assets/images/articles/ubuntu-1-1200x675@2x.png
new file mode 100644
index 000000000000..e6c1f3edf89b
Binary files /dev/null and b/website/assets/images/articles/ubuntu-1-1200x675@2x.png differ
diff --git a/website/assets/images/articles/ubuntu-2-1200x675@2x.png b/website/assets/images/articles/ubuntu-2-1200x675@2x.png
new file mode 100644
index 000000000000..6c5e5357d8fc
Binary files /dev/null and b/website/assets/images/articles/ubuntu-2-1200x675@2x.png differ
diff --git a/website/assets/images/articles/workbrew-console-3412x2020px.png b/website/assets/images/articles/workbrew-console-3412x2020px.png
new file mode 100644
index 000000000000..060143e9874e
Binary files /dev/null and b/website/assets/images/articles/workbrew-console-3412x2020px.png differ
diff --git a/website/assets/images/software-management-feature-image-1-528x377@2x.png b/website/assets/images/software-management-feature-image-1-528x377@2x.png
new file mode 100644
index 000000000000..451b9beb3d2f
Binary files /dev/null and b/website/assets/images/software-management-feature-image-1-528x377@2x.png differ
diff --git a/website/assets/images/software-management-feature-image-2-528x377@2x.png b/website/assets/images/software-management-feature-image-2-528x377@2x.png
new file mode 100644
index 000000000000..3f6ca292fdf4
Binary files /dev/null and b/website/assets/images/software-management-feature-image-2-528x377@2x.png differ
diff --git a/website/assets/images/software-management-feature-image-3-528x377@2x.png b/website/assets/images/software-management-feature-image-3-528x377@2x.png
new file mode 100644
index 000000000000..ec2d6cb51a3f
Binary files /dev/null and b/website/assets/images/software-management-feature-image-3-528x377@2x.png differ
diff --git a/website/assets/images/software-management-feature-image-4-528x377@2x.png b/website/assets/images/software-management-feature-image-4-528x377@2x.png
new file mode 100644
index 000000000000..cc5d38526295
Binary files /dev/null and b/website/assets/images/software-management-feature-image-4-528x377@2x.png differ
diff --git a/website/assets/images/software-management-feature-image-5-528x377@2x.png b/website/assets/images/software-management-feature-image-5-528x377@2x.png
new file mode 100644
index 000000000000..2e14bf963f60
Binary files /dev/null and b/website/assets/images/software-management-feature-image-5-528x377@2x.png differ
diff --git a/website/assets/images/software-management-feature-slide-1-1072x480@2x.png b/website/assets/images/software-management-feature-slide-1-1072x480@2x.png
new file mode 100644
index 000000000000..ca7d076f2ab2
Binary files /dev/null and b/website/assets/images/software-management-feature-slide-1-1072x480@2x.png differ
diff --git a/website/assets/images/software-management-feature-slide-2-1072x480@2x.png b/website/assets/images/software-management-feature-slide-2-1072x480@2x.png
new file mode 100644
index 000000000000..7df8d28de473
Binary files /dev/null and b/website/assets/images/software-management-feature-slide-2-1072x480@2x.png differ
diff --git a/website/assets/images/software-management-feature-slide-3-1072x480@2x.png b/website/assets/images/software-management-feature-slide-3-1072x480@2x.png
new file mode 100644
index 000000000000..b75534a680b3
Binary files /dev/null and b/website/assets/images/software-management-feature-slide-3-1072x480@2x.png differ
diff --git a/website/assets/images/vuln-management-feature-image-1-380x281@2x.png b/website/assets/images/vuln-management-feature-image-1-380x281@2x.png
deleted file mode 100644
index 38b72e56d47e..000000000000
Binary files a/website/assets/images/vuln-management-feature-image-1-380x281@2x.png and /dev/null differ
diff --git a/website/assets/images/vuln-management-feature-image-2-380x323@2x.png b/website/assets/images/vuln-management-feature-image-2-380x323@2x.png
deleted file mode 100644
index 154dd7236527..000000000000
Binary files a/website/assets/images/vuln-management-feature-image-2-380x323@2x.png and /dev/null differ
diff --git a/website/assets/images/vuln-management-feature-image-3-380x320@2x.png b/website/assets/images/vuln-management-feature-image-3-380x320@2x.png
deleted file mode 100644
index 23a9928dd57a..000000000000
Binary files a/website/assets/images/vuln-management-feature-image-3-380x320@2x.png and /dev/null differ
diff --git a/website/assets/images/vuln-management-feature-image-4-380x270@2x.png b/website/assets/images/vuln-management-feature-image-4-380x270@2x.png
deleted file mode 100644
index 8b3131ca6b18..000000000000
Binary files a/website/assets/images/vuln-management-feature-image-4-380x270@2x.png and /dev/null differ
diff --git a/website/assets/images/vuln-management-feature-image-5-380x312@2x.png b/website/assets/images/vuln-management-feature-image-5-380x312@2x.png
deleted file mode 100644
index 7f5d1da9f849..000000000000
Binary files a/website/assets/images/vuln-management-feature-image-5-380x312@2x.png and /dev/null differ
diff --git a/website/assets/js/cloud.setup.js b/website/assets/js/cloud.setup.js
index 9c08e4dde162..35f7bcdf45de 100644
--- a/website/assets/js/cloud.setup.js
+++ b/website/assets/js/cloud.setup.js
@@ -13,7 +13,7 @@
Cloud.setup({
/* eslint-disable */
- methods: {"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"downloadRssFeed":{"verb":"GET","url":"/rss/:categoryName","args":["categoryName"]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostsStatusWebHookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization","mdmMacOsEnabled","mdmWindowsEnabled","liveQueryDisabled","hostExpiryEnabled","numSoftwareVersions","numHostSoftwares","numSoftwareTitles","numHostSoftwareInstalledPaths","numSoftwareCPEs","numSoftwareCVEs"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label","release"]},"receiveFromStripe":{"verb":"POST","url":"/api/v1/webhooks/receive-from-stripe","args":["id","type","data","webhookSecret"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","firstName","lastName","message"]},"sendPasswordRecoveryEmail":{"verb":"POST","url":"/api/v1/entrance/send-password-recovery-email","args":["emailAddress"]},"signup":{"verb":"POST","url":"/api/v1/customers/signup","args":["emailAddress","password","organization","firstName","lastName","signupReason"]},"updateProfile":{"verb":"POST","url":"/api/v1/account/update-profile","args":["firstName","lastName","organization","emailAddress"]},"updatePassword":{"verb":"POST","url":"/api/v1/account/update-password","args":["oldPassword","newPassword"]},"updateBillingCard":{"verb":"POST","url":"/api/v1/account/update-billing-card","args":["stripeToken","billingCardLast4","billingCardBrand","billingCardExpMonth","billingCardExpYear"]},"login":{"verb":"POST","url":"/api/v1/customers/login","args":["emailAddress","password","rememberMe"]},"logout":{"verb":"GET","url":"/api/v1/account/logout","args":[]},"createQuote":{"verb":"POST","url":"/api/v1/customers/create-quote","args":["numberOfHosts"]},"saveBillingInfoAndSubscribe":{"verb":"POST","url":"/api/v1/customers/save-billing-info-and-subscribe","args":["quoteId","organization","firstName","lastName","paymentSource"]},"updatePasswordAndLogin":{"verb":"POST","url":"/api/v1/entrance/update-password-and-login","args":["password","token"]},"deliverDemoSignup":{"verb":"POST","url":"/api/v1/deliver-demo-signup","args":["emailAddress"]},"createOrUpdateOneNewsletterSubscription":{"verb":"POST","url":"/api/v1/create-or-update-one-newsletter-subscription","args":["emailAddress","subscribeTo"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"buildLicenseKey":{"verb":"POST","url":"/api/v1/admin/build-license-key","args":["numberOfHosts","organization","expiresAt","partnerName"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey","redirectToExternalPageAfterAuthorization","sharedSecret"]},"redirectVantaAuthorizationRequest":{"verb":"GET","url":"/redirect-vanta-authorization-request","args":["vantaSourceId","state","vantaAuthorizationRequestURL","redirectAfterSetup"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"getHumanInterpretationFromOsquerySql":{"verb":"POST","url":"/api/v1/get-human-interpretation-from-osquery-sql","args":["sql"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData","deliveryMethod"]},"deliverMdmDemoEmail":{"verb":"POST","url":"/api/v1/deliver-mdm-demo-email","args":["emailAddress"]},"provisionSandboxInstanceAndDeliverEmail":{"verb":"POST","url":"/api/v1/admin/provision-sandbox-instance-and-deliver-email","args":["userId"]},"deliverTalkToUsFormSubmission":{"verb":"POST","url":"/api/v1/deliver-talk-to-us-form-submission","args":["emailAddress","firstName","lastName","organization","numberOfHosts","primaryBuyingSituation"]},"saveQuestionnaireProgress":{"verb":"POST","url":"/api/v1/save-questionnaire-progress","args":["currentStep","formData"]},"updateStartCtaVisibility":{"verb":"POST","url":"/api/v1/account/update-start-cta-visibility","args":[]},"deliverDealRegistrationSubmission":{"verb":"POST","url":"/api/v1/deliver-deal-registration-submission","args":["submittersFirstName","submittersLastName","submittersEmailAddress","submittersOrganization","customersFirstName","customersLastName","customersEmailAddress","linkedinUrl","customersOrganization","customersCurrentMdm","otherMdmEvaluated","preferredHosting","expectedDealSize","expectedCloseDate","notes"]}}
+ methods: {"redirectToStripeBillingPortal":{"verb":"GET","url":"/customers/update-subscription","args":[]},"downloadSitemap":{"verb":"GET","url":"/sitemap.xml","args":[]},"downloadRssFeed":{"verb":"GET","url":"/rss/:categoryName","args":["categoryName"]},"receiveUsageAnalytics":{"verb":"POST","url":"/api/v1/webhooks/receive-usage-analytics","args":["anonymousIdentifier","fleetVersion","licenseTier","numHostsEnrolled","numUsers","numTeams","numPolicies","numLabels","softwareInventoryEnabled","vulnDetectionEnabled","systemUsersEnabled","hostsStatusWebHookEnabled","numWeeklyActiveUsers","numWeeklyPolicyViolationDaysActual","numWeeklyPolicyViolationDaysPossible","hostsEnrolledByOperatingSystem","hostsEnrolledByOrbitVersion","hostsEnrolledByOsqueryVersion","storedErrors","numHostsNotResponding","organization","mdmMacOsEnabled","mdmWindowsEnabled","liveQueryDisabled","hostExpiryEnabled","numSoftwareVersions","numHostSoftwares","numSoftwareTitles","numHostSoftwareInstalledPaths","numSoftwareCPEs","numSoftwareCVEs","aiFeaturesDisabled","maintenanceWindowsEnabled","maintenanceWindowsConfigured","numHostsFleetDesktopEnabled"]},"receiveFromGithub":{"verb":"GET","url":"/api/v1/webhooks/github","args":["botSignature","action","sender","repository","changes","issue","comment","pull_request","label","release"]},"receiveFromStripe":{"verb":"POST","url":"/api/v1/webhooks/receive-from-stripe","args":["id","type","data","webhookSecret"]},"deliverContactFormMessage":{"verb":"POST","url":"/api/v1/deliver-contact-form-message","args":["emailAddress","firstName","lastName","message"]},"sendPasswordRecoveryEmail":{"verb":"POST","url":"/api/v1/entrance/send-password-recovery-email","args":["emailAddress"]},"signup":{"verb":"POST","url":"/api/v1/customers/signup","args":["emailAddress","password","organization","firstName","lastName","signupReason"]},"updateProfile":{"verb":"POST","url":"/api/v1/account/update-profile","args":["firstName","lastName","organization","emailAddress"]},"updatePassword":{"verb":"POST","url":"/api/v1/account/update-password","args":["oldPassword","newPassword"]},"updateBillingCard":{"verb":"POST","url":"/api/v1/account/update-billing-card","args":["stripeToken","billingCardLast4","billingCardBrand","billingCardExpMonth","billingCardExpYear"]},"login":{"verb":"POST","url":"/api/v1/customers/login","args":["emailAddress","password","rememberMe"]},"logout":{"verb":"GET","url":"/api/v1/account/logout","args":[]},"createQuote":{"verb":"POST","url":"/api/v1/customers/create-quote","args":["numberOfHosts"]},"saveBillingInfoAndSubscribe":{"verb":"POST","url":"/api/v1/customers/save-billing-info-and-subscribe","args":["quoteId","organization","firstName","lastName","paymentSource"]},"updatePasswordAndLogin":{"verb":"POST","url":"/api/v1/entrance/update-password-and-login","args":["password","token"]},"deliverDemoSignup":{"verb":"POST","url":"/api/v1/deliver-demo-signup","args":["emailAddress"]},"createOrUpdateOneNewsletterSubscription":{"verb":"POST","url":"/api/v1/create-or-update-one-newsletter-subscription","args":["emailAddress"]},"unsubscribeFromAllNewsletters":{"verb":"GET","url":"/api/v1/unsubscribe-from-all-newsletters","args":["emailAddress"]},"buildLicenseKey":{"verb":"POST","url":"/api/v1/admin/build-license-key","args":["numberOfHosts","organization","expiresAt","partnerName"]},"createVantaAuthorizationRequest":{"verb":"POST","url":"/api/v1/create-vanta-authorization-request","args":["emailAddress","fleetInstanceUrl","fleetApiKey","redirectToExternalPageAfterAuthorization","sharedSecret"]},"redirectVantaAuthorizationRequest":{"verb":"GET","url":"/redirect-vanta-authorization-request","args":["vantaSourceId","state","vantaAuthorizationRequestURL","redirectAfterSetup"]},"deliverMdmBetaSignup":{"verb":"POST","url":"/api/v1/deliver-mdm-beta-signup","args":["emailAddress","fullName","jobTitle","numberOfHosts"]},"getHumanInterpretationFromOsquerySql":{"verb":"POST","url":"/api/v1/get-human-interpretation-from-osquery-sql","args":["sql"]},"deliverAppleCsr":{"verb":"POST","url":"/api/v1/deliver-apple-csr","args":["unsignedCsrData","deliveryMethod"]},"deliverMdmDemoEmail":{"verb":"POST","url":"/api/v1/deliver-mdm-demo-email","args":["emailAddress"]},"provisionSandboxInstanceAndDeliverEmail":{"verb":"POST","url":"/api/v1/admin/provision-sandbox-instance-and-deliver-email","args":["userId"]},"deliverTalkToUsFormSubmission":{"verb":"POST","url":"/api/v1/deliver-talk-to-us-form-submission","args":["emailAddress","firstName","lastName","organization","numberOfHosts","primaryBuyingSituation"]},"saveQuestionnaireProgress":{"verb":"POST","url":"/api/v1/save-questionnaire-progress","args":["currentStep","formData"]},"updateStartCtaVisibility":{"verb":"POST","url":"/api/v1/account/update-start-cta-visibility","args":[]},"deliverDealRegistrationSubmission":{"verb":"POST","url":"/api/v1/deliver-deal-registration-submission","args":["submittersFirstName","submittersLastName","submittersEmailAddress","submittersOrganization","customersFirstName","customersLastName","customersEmailAddress","linkedinUrl","customersOrganization","customersCurrentMdm","otherMdmEvaluated","preferredHosting","expectedDealSize","expectedCloseDate","notes"]},"unsubscribeFromMarketingEmails":{"verb":"GET","url":"/api/v1/unsubscribe-from-marketing-emails","args":["emailAddress"]},"getStripeCheckoutSessionUrl":{"verb":"POST","url":"/api/v1/customers/get-stripe-checkout-session-url","args":["quoteId"]}}
/* eslint-enable */
});
diff --git a/website/assets/js/pages/app-details.page.js b/website/assets/js/pages/app-details.page.js
new file mode 100644
index 000000000000..bba7bb6ac186
--- /dev/null
+++ b/website/assets/js/pages/app-details.page.js
@@ -0,0 +1,47 @@
+parasails.registerPage('app-details', {
+ // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
+ // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
+ // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
+ data: {
+ //…
+ },
+
+ // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
+ // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
+ // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
+ beforeMount: function() {
+ //…
+ },
+ mounted: async function() {
+
+ if(this.algoliaPublicKey) { // Note: Docsearch will only be enabled if sails.config.custom.algoliaPublicKey is set. If the value is undefined, the documentation search will be disabled.
+ docsearch({
+ appId: 'NZXAYZXDGH',
+ apiKey: this.algoliaPublicKey,
+ indexName: 'fleetdm',
+ container: '#docsearch-query',
+ placeholder: 'Search',
+ debug: false,
+ searchParameters: {
+ 'facetFilters': ['section:queries']
+ },
+ });
+ }
+
+ $('[purpose="copy-button"]').on('click', async function() {
+ let code = $(this).siblings('pre').find('code').text();
+ $(this).addClass('copied');
+ await setTimeout(()=>{
+ $(this).removeClass('copied');
+ }, 2000);
+ navigator.clipboard.writeText(code);
+ });
+ },
+
+ // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
+ // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
+ // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
+ methods: {
+ //…
+ }
+});
diff --git a/website/assets/js/pages/vulnerability-management.page.js b/website/assets/js/pages/app-library.page.js
similarity index 79%
rename from website/assets/js/pages/vulnerability-management.page.js
rename to website/assets/js/pages/app-library.page.js
index 29c8bfc12ffc..595a8ad10e5b 100644
--- a/website/assets/js/pages/vulnerability-management.page.js
+++ b/website/assets/js/pages/app-library.page.js
@@ -1,9 +1,9 @@
-parasails.registerPage('vulnerability-management-page', {
+parasails.registerPage('app-library', {
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
data: {
- modal: undefined,
+ //…
},
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
@@ -20,11 +20,6 @@ parasails.registerPage('vulnerability-management-page', {
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: {
- clickOpenVideoModal: function(modalName) {
- this.modal = modalName;
- },
- closeModal: function() {
- this.modal = undefined;
- }
+ //…
}
});
diff --git a/website/assets/js/pages/customers/new-license.page.js b/website/assets/js/pages/customers/new-license.page.js
index b601c8a80f0b..e72ea5b39519 100644
--- a/website/assets/js/pages/customers/new-license.page.js
+++ b/website/assets/js/pages/customers/new-license.page.js
@@ -19,6 +19,10 @@ parasails.registerPage('new-license', {
selfHostedAcknowledgment: {required: true, is: true},
},
+ checkoutFormRules: {
+ selfHostedAcknowledgment: {required: true, is: true},
+ },
+
// Syncing / loading state
syncing: false,
@@ -40,7 +44,7 @@ parasails.registerPage('new-license', {
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
beforeMount: function() {
if(window.location.hash) {
- if(typeof analytics !== 'undefined') {
+ if(window.analytics !== undefined) {
if(window.location.hash === '#signup') {
analytics.identify(this.me.id, {
email: this.me.emailAddress,
@@ -93,7 +97,12 @@ parasails.registerPage('new-license', {
this.syncing = true;
this.goto('/customers/dashboard?order-complete');
},
-
+ handleSubmittingCheckoutForm: async function() {
+ let redirectUrl = await Cloud.getStripeCheckoutSessionUrl.with({
+ quoteId: this.formData.quoteId
+ });
+ this.goto(redirectUrl);
+ },
submittedQuoteForm: async function(quote) {
this.showQuotedPrice = true;
this.quotedPrice = quote.quotedPrice;
diff --git a/website/assets/js/pages/device-management.page.js b/website/assets/js/pages/device-management.page.js
index cb36f759ba03..3dda3f939d6d 100644
--- a/website/assets/js/pages/device-management.page.js
+++ b/website/assets/js/pages/device-management.page.js
@@ -31,13 +31,13 @@ parasails.registerPage('device-management-page', {
this.modal = undefined;
},
clickSwagRequestCTA: function () {
- if(typeof gtag !== 'undefined') {
+ if(window.gtag !== undefined){
gtag('event','fleet_website__swag_request');
}
- if(typeof window.lintrk !== 'undefined') {
+ if(window.lintrk !== undefined) {
window.lintrk('track', { conversion_id: 18587105 });// eslint-disable-line camelcase
}
- if(typeof analytics !== 'undefined'){
+ if(window.analytics !== undefined) {
analytics.track('fleet_website__swag_request');
}
this.goto('https://kqphpqst851.typeform.com/to/ZfA3sOu0#from_page=device-managment');
diff --git a/website/assets/js/pages/docs/basic-documentation.page.js b/website/assets/js/pages/docs/basic-documentation.page.js
index c402f57d969b..ba111ea84b0f 100644
--- a/website/assets/js/pages/docs/basic-documentation.page.js
+++ b/website/assets/js/pages/docs/basic-documentation.page.js
@@ -221,13 +221,13 @@ parasails.registerPage('basic-documentation', {
methods: {
clickSwagRequestCTA: function () {
- if(typeof gtag !== 'undefined') {
+ if(window.gtag !== undefined){
gtag('event','fleet_website__swag_request');
}
- if(typeof window.lintrk !== 'undefined') {
+ if(window.lintrk !== undefined) {
window.lintrk('track', { conversion_id: 18587105 });// eslint-disable-line camelcase
}
- if(typeof analytics !== 'undefined'){
+ if(window.analytics !== undefined) {
analytics.track('fleet_website__swag_request');
}
this.goto('https://kqphpqst851.typeform.com/to/ZfA3sOu0#from_page=docs');
diff --git a/website/assets/js/pages/entrance/signup.page.js b/website/assets/js/pages/entrance/signup.page.js
index d749af7b30a2..d34b6ed08650 100644
--- a/website/assets/js/pages/entrance/signup.page.js
+++ b/website/assets/js/pages/entrance/signup.page.js
@@ -64,19 +64,22 @@ parasails.registerPage('signup', {
}
},
- submittedSignUpForm: async function() {
- // redirect to the /start page.
- // > (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.)
- //
- // Naming convention: (like sails config)
- // "Website - Sign up" becomes "fleet_website__sign_up" (double-underscore representing hierarchy)
- if(typeof gtag !== 'undefined'){
- gtag('event','fleet_website__sign_up');
+ submittedSignUpForm: async function() {// When the server says everything worked…
+ // Track a "key event" in Google Analytics. (? but don't we do that when we call analytics.track() [segment] later on in start.page.js? TODO: eric help please – I suspect this one is either duplicate OR it's actually writing to Google Ads, and not to Google Analytics. I'm pretty sure segment's .track() is what writes to google analytics.)
+ // > Naming convention: (like sails config)
+ // > "Website - Sign up" becomes "fleet_website__sign_up" (double-underscore representing hierarchy)
+ if(window.gtag !== undefined){
+ window.gtag('event','fleet_website__sign_up');
}
- if(typeof window.lintrk !== 'undefined') {
+
+ // Track a "conversion" in LinkedIn Campaign Manager.
+ if(window.lintrk !== undefined) {
window.lintrk('track', { conversion_id: 18587097 });// eslint-disable-line camelcase
}
+
+ // Redirect to the /start page.
+ // > (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;
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.
}
diff --git a/website/assets/js/pages/endpoint-ops.page.js b/website/assets/js/pages/observability.page.js
similarity index 95%
rename from website/assets/js/pages/endpoint-ops.page.js
rename to website/assets/js/pages/observability.page.js
index f79d51d83095..0386d1f3f34c 100644
--- a/website/assets/js/pages/endpoint-ops.page.js
+++ b/website/assets/js/pages/observability.page.js
@@ -1,4 +1,4 @@
-parasails.registerPage('endpoint-ops-page', {
+parasails.registerPage('observability-page', {
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
diff --git a/website/assets/js/pages/osquery-table-details.page.js b/website/assets/js/pages/osquery-table-details.page.js
index 4897b68ccc3c..d940b76af959 100644
--- a/website/assets/js/pages/osquery-table-details.page.js
+++ b/website/assets/js/pages/osquery-table-details.page.js
@@ -73,19 +73,31 @@ parasails.registerPage('osquery-table-details', {
keywordsForThisTable = keywordsForThisTable.sort((a,b)=>{// Sorting the array of keywords by length to match larger keywords first.
return a.length < b.length ? 1 : -1;
});
+ keywordsForThisTable = _.pull(keywordsForThisTable, this.tableToDisplay.title);
(()=>{
$('pre code').each((i, block) => {
- let keywordsToHighlight = [];// Empty array to track the keywords that we will need to highlight
+ let tableNamesToHighlight = [];// Empty array to track the keywords that we will need to highlight
+ for(let match of block.innerHTML.match(this.tableToDisplay.title)||[]){
+ tableNamesToHighlight.push(match);
+ }
+ // Now iterate through the keywordsToHighlight, replacing all matches in the elements innerHTML.
+ let replacementHMTL = block.innerHTML;
+ for(let keywordInExample of tableNamesToHighlight) {
+ let regexForThisExample = new RegExp(keywordInExample, 'g');
+ replacementHMTL = replacementHMTL.replace(regexForThisExample, ''+keywordInExample+'');
+ }
+ // $(block).html(replacementHMTL);
+ let columnNamesToHighlight = [];// Empty array to track the keywords that we will need to highlight
for(let keyword of keywordsForThisTable){// Going through the array of keywords for this table, if the entire word matches, we'll add it to the
for(let match of block.innerHTML.match(keyword)||[]){
- keywordsToHighlight.push(match);
+ columnNamesToHighlight.push(match);
}
}
// Now iterate through the keywordsToHighlight, replacing all matches in the elements innerHTML.
- let replacementHMTL = block.innerHTML;
- for(let keywordInExample of keywordsToHighlight) {
+ // let replacementHMTL = block.innerHTML;
+ for(let keywordInExample of columnNamesToHighlight) {
let regexForThisExample = new RegExp(keywordInExample, 'g');
- replacementHMTL = replacementHMTL.replace(regexForThisExample, ''+keywordInExample+'');
+ replacementHMTL = replacementHMTL.replace(regexForThisExample, ''+keywordInExample+'');
}
$(block).html(replacementHMTL);
// After we've highlighted our keywords, we'll highlight the rest of the codeblock
diff --git a/website/assets/js/pages/software-management.page.js b/website/assets/js/pages/software-management.page.js
new file mode 100644
index 000000000000..02c7a6f39f20
--- /dev/null
+++ b/website/assets/js/pages/software-management.page.js
@@ -0,0 +1,42 @@
+parasails.registerPage('software-management-page', {
+ // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
+ // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
+ // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
+ data: {
+ modal: undefined,
+ visibleFeature: 'mitigate-cves-automatically',
+ },
+
+ // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
+ // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
+ // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
+ beforeMount: function() {
+ //…
+ },
+ mounted: async function() {
+ $('#heroCarousel').carousel({
+ interval: 5000,
+ });
+ $('#heroCarousel').on('slide.bs.carousel', (e)=>{
+ let toIndicatorElement = $('ol[purpose="carousel-indicators"] li')[e.to];
+ let fromIndicatorElement = $('ol[purpose="carousel-indicators"] li')[e.from];
+ $(toIndicatorElement).addClass('active');
+ $(fromIndicatorElement).removeClass('active');
+ });
+ },
+
+ // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
+ // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
+ // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
+ methods: {
+ clickSwitchFeature: function(feature) {
+ this.visibleFeature = feature;
+ },
+ clickOpenVideoModal: function(modalName) {
+ this.modal = modalName;
+ },
+ closeModal: function() {
+ this.modal = undefined;
+ }
+ }
+});
diff --git a/website/assets/js/pages/start.page.js b/website/assets/js/pages/start.page.js
index 3a9992cda58e..2f62581ab90d 100644
--- a/website/assets/js/pages/start.page.js
+++ b/website/assets/js/pages/start.page.js
@@ -97,7 +97,7 @@ parasails.registerPage('start', {
this.psychologicalStage = this.me.psychologicalStage;
}
if(window.location.hash) {
- if(typeof analytics !== 'undefined') {
+ if(window.analytics !== undefined) {
if(window.location.hash === '#signup') {
analytics.identify(this.me.id, {
email: this.me.emailAddress,
diff --git a/website/assets/styles/components/scrollable-tweets.component.less b/website/assets/styles/components/scrollable-tweets.component.less
index b11473169f0a..b890e5d6c38c 100644
--- a/website/assets/styles/components/scrollable-tweets.component.less
+++ b/website/assets/styles/components/scrollable-tweets.component.less
@@ -92,6 +92,7 @@
line-height: 18px !important;//lesshint-disable-line importantRule,duplicateProperty
}
[purpose='name'] {
+ text-transform: capitalize;
color: @core-fleet-black !important;//lesshint-disable-line importantRule,duplicateProperty
}
[purpose='profile-picture'] {
diff --git a/website/assets/styles/importer.less b/website/assets/styles/importer.less
index f68370a395a9..8fc197e4c2a0 100644
--- a/website/assets/styles/importer.less
+++ b/website/assets/styles/importer.less
@@ -67,10 +67,10 @@
@import 'pages/vanta-authorization.less';
@import 'pages/admin/generate-license.less';
@import 'pages/device-management.less';
-@import 'pages/endpoint-ops.less';
+@import 'pages/observability.less';
@import 'pages/transparency.less';
@import 'pages/press-kit.less';
-@import 'pages/vulnerability-management.less';
+@import 'pages/software-management.less';
@import 'pages/support.less';
@import 'pages/try-fleet/waitlist.less';
@import 'pages/admin/sandbox-waitlist.less';
@@ -78,4 +78,6 @@
@import 'pages/start.less';
@import 'pages/deals.less';
@import 'pages/testimonials.less';
+@import 'pages/app-library.less';
+@import 'pages/app-details.less';
diff --git a/website/assets/styles/layout.less b/website/assets/styles/layout.less
index 7452a27bd7e8..61dbd0f5fdd3 100644
--- a/website/assets/styles/layout.less
+++ b/website/assets/styles/layout.less
@@ -17,6 +17,27 @@ html, body {
padding-bottom: @footer-height;
background: linear-gradient(180deg, #E8F1F6 0%, #FFFFFF 200px);
background-position: center 40px;
+ opacity: 1;
+ // Note: This element has the "show" class toggled by the mobile header nav menu button.
+ // We are overriding bootstrap classes here to allow us to prevent the page being scrolled while the mobile menu is open.
+ &.collapse {
+ display: block;
+ [purpose='mobile-nav'] {
+ display: none;
+ }
+ }
+ &.collapsing {
+ opacity: 0;
+ transition: 0s;
+ }
+ &.collapse.show {
+ max-height: 100vh;
+ overflow: hidden;
+ [purpose='mobile-nav'] {
+ display: block;
+ opacity: 1;
+ }
+ }
}
[purpose='header-background'] {
@@ -40,7 +61,7 @@ html, body {
}
[purpose='continue-banner'] {
- z-index: 199;
+ z-index: 198;
position: fixed;
bottom: 24px;
left: 24px;
@@ -279,14 +300,14 @@ html, body {
left: 0;
right: 0;
bottom: 0;
- pointer-events: none;
+ z-index: 200;
background-color: #ffffff;
hr {
margin-top: 4px;
margin-bottom: 8px;
}
[purpose='mobile-nav-header'] {
- padding: 19px 40px;
+ padding: 19px 32px;
height: 80px;
}
[purpose='mobile-nav-container'] {
@@ -783,6 +804,7 @@ body.detected-mobile {
@media (max-width: 375px) {
[purpose='page-header'] {
+ padding: 19px 16px;
[purpose='mobile-nav'] {
[purpose='mobile-nav-header'] {
padding: 19px 16px;
diff --git a/website/assets/styles/pages/app-details.less b/website/assets/styles/pages/app-details.less
new file mode 100644
index 000000000000..b4e52a0343de
--- /dev/null
+++ b/website/assets/styles/pages/app-details.less
@@ -0,0 +1,418 @@
+#app-details {
+
+ h3 {
+ padding-top: 32px;
+ color: #192147;
+ font-size: 24px;
+ font-weight: 800;
+ line-height: 120%;
+ margin-bottom: 0px;
+ }
+
+ p {
+ color: #515774;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 150%;
+ }
+
+ [purpose='page-container'] {
+ padding: 64px 64px 32px 64px;
+ }
+ [purpose='page-content'] {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 1072px;
+ }
+
+ [purpose='breadcrumbs-and-search'] {
+ margin-bottom: 64px;
+ max-width: 1072px;
+ font-size: 14px;
+ [purpose='breadcrumbs'] {
+ margin-right: 24px;
+ }
+ [purpose='search'] {
+ // Note: We're using classes here to override the default Docsearch styles;
+ button {
+ width: 100%;
+ cursor: text;
+ margin: 0;
+ }
+ .DocSearch-Button {
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ border: 1px solid @core-fleet-black-25;
+ background-color: #FFF;
+ padding: 6px;
+ height: 36px;
+ margin: 0;
+ width: 256px;
+ }
+ .DocSearch-Button:hover {
+ box-shadow: none;
+ border: 1px solid @core-fleet-black-25;
+ color: @core-fleet-black-50;
+ }
+ .DocSearch-Search-Icon {
+ margin-left: 10px;
+ height: 16px;
+ width: 16px;
+ color: @core-fleet-black-50;
+ stroke-width: 3px;
+ }
+ .DocSearch-Button-Keys {
+ display: none;
+ }
+ .input-group:focus-within {
+ border: 1px solid @core-vibrant-blue;
+ }
+ .DocSearch-Button-Placeholder {
+ font-size: 16px;
+ font-weight: 400;
+ padding-left: 12px;
+ }
+ [purpose='disabled-search'] {
+ input {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ border: none;
+ } &::placeholder {
+ font-size: 16px;
+ line-height: 24px;
+ color: #8B8FA2;
+ }
+ .input-group {
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ border: 1px solid @core-fleet-black-25;
+ background: #FFF;
+ }
+ .input-group:focus-within {
+ border: 1px solid @core-vibrant-blue;
+ }
+ .form-control {
+ border-radius: 6px;
+ padding: 6px;
+ height: 36px;
+ margin: 0;
+ width: 212px;
+ }
+ .docsearch-input:focus-visible {
+ outline: none;
+ }
+ .ds-input:focus {
+ outline: rgba(0, 0, 0, 0);
+ }
+ .input-group-text {
+ color: @core-fleet-black-50;
+ }
+ .form-control {
+ height: 36px;
+ padding: 0px;
+ font-size: 16px;
+ } &:focus {
+ border: none;
+ }
+ }
+ }
+
+ [purpose='breadcrumbs-category'] {
+ color: #8B8FA2;
+ margin-right: 8px;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 150%; /* */
+ &:hover {
+ color: #192147;
+ text-decoration: none;
+ }
+ }
+ [purpose='breadcrumbs-title'] {
+ margin-left: 8px;
+ }
+ }
+ [purpose='icon-and-name'] {
+ flex-direction: row;
+ align-items: center;
+
+ }
+ [purpose='app-icon'] {
+ img {
+ height: 80px;
+ }
+ margin-right: 24px;
+ }
+ [purpose='app-name'] {
+ color: #192147;
+ font-size: 32px;
+ font-style: normal;
+ font-weight: 800;
+ line-height: 150%;
+ margin-bottom: 0px;
+ }
+
+ [purpose='app-description'] {
+ color: #515774;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 150%;
+ margin-bottom: 0px;
+ padding: 16px 0px 32px 0px;
+ }
+ [purpose='app-details'] {
+ padding-right: 64px;
+ max-width: 800px;
+ width: 100%;
+ p {
+ margin-bottom: 24px;
+ margin-top: 16px;
+ color: #515774;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 150%;
+ }
+ a {
+ color: #515774;
+ text-decoration: underline;
+ text-underline-offset: 1px;
+ &:hover {
+ color: #515774;
+ }
+ }
+ [purpose='platform-and-version'] {
+ p {
+ color: #8B8FA2;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 150%;
+ margin-bottom: 0px;
+ margin-top: 0px;
+ }
+ }
+
+ }
+ [purpose='app-uninstall'] {
+ pre {
+ height: 141px;
+ }
+ }
+ [purpose='app-install'] {
+ ol {
+ counter-reset: custom-counter;
+ list-style-type: none;
+ padding-inline-start: 0px;
+ padding: 0;
+ margin-top: 16px;
+ margin-bottom: 32px;
+ ul > li {
+ text-indent: 0px;
+ margin-left: 0px;
+ }
+ > li {
+ counter-increment: custom-counter;
+ margin-left: 36px;
+ text-indent: -36px;
+ padding-left: 0px;
+ margin-bottom: 16px;
+ code:not(.nohighlight):not(.mermaid) {
+ display: inline;
+ }
+ p {
+ display: inline;
+ margin-bottom: 0px;
+ }
+ blockquote {
+ text-indent: 0px;
+ }
+ }
+ > li::before {
+ content: counter(custom-counter);
+ background-color: #E2E4EA;
+ width: 24px;
+ font-size: 13px;
+ display: inline-block;
+ border-radius: 50%;
+ margin-right: 10px;
+ padding: 2px 4px;
+ text-align: center;
+ line-height: 20px;
+ text-indent: 0px;
+ }
+ }
+ }
+ [purpose='app-check'] {
+ padding-bottom: 24px;
+ }
+
+ [purpose='right-sidebar'] {
+ width: 256px;
+ margin-left: 16px;
+ font-size: 14px;
+ transition-property: transform;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 500ms;
+ a:not([purpose='edit-button']) {
+ margin-bottom: 8px;
+ display: block;
+ color: #515774;
+ &:hover {
+ text-decoration: none;
+ color: @core-fleet-black;
+ }
+ }
+ }
+ [purpose='docs-links'] {
+ a {
+ display: block;
+ }
+ }
+ [purpose='social-share-buttons'] {
+ padding-bottom: 24px;
+ margin-bottom: 24px;
+ border-bottom: 1px solid #E2E4EA;
+ a {
+ margin-right: 16px;
+ }
+ img {
+ height: 20px;
+ width: 20px;
+ }
+ }
+ [purpose='edit-button'] {
+ margin-top: 24px;
+ img {
+ width: 16px;
+ height: 16px;
+ display: inline;
+ margin-right: 8px;
+ }
+ padding: 6px 8px;
+ display: block;
+ color: @core-fleet-black-75;
+ text-decoration: none;
+ font-size: 14px;
+ line-height: 21px;
+ border-radius: 6px;
+ width: 102px;
+ background: rgba(25, 33, 71, 0.05);
+ &:hover {
+ background-color: rgba(25, 33, 71, 0.1);
+ }
+ &:active {
+ background-color: rgba(25, 33, 71, 0.1);
+ }
+ }
+
+ [purpose='codeblock'] {
+ padding: 0;
+ position: relative;
+ [purpose='copy-button'] {
+ position: absolute;
+ top: 11px;
+ right: 10px;
+ border-radius: 8px;
+ height: 32px;
+ width: 32px;
+ background: url('/images/icon-copy-16x16@2x.png');
+ background-color: #F9FAFC;
+ background-size: 14px 14px;
+ background-position: center;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ &:hover {
+ background-color: #F2F2F5;
+ }
+ &.copied {
+ background: url('/images/icon-copy-clicked-checkmark-32x32@2x.png');
+ background-size: 32px 32px;
+ background-repeat: no-repeat;
+ background-position: center;
+ }
+ }
+ }
+
+ pre {
+ width: 100%;
+ max-width: 100%;
+ padding: 16px 44px 16px 24px;
+ border: 1px solid #E2E4EA;
+ background: #F9FAFC;
+ border-radius: 4px;
+ margin-top: 32px;
+ margin-bottom: 24px;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ overflow: auto;
+ code {
+ color: #515774;
+ font-family: 'Source Code Pro';
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 150%;
+ background-color: @ui-off-white;
+ border: none;
+ padding: 0;
+ dispaly: block;
+ }
+ }
+
+ @media (max-width: 1200px) {
+
+
+ }
+ @media (max-width: 991px) {
+ [purpose='page-container'] {
+ padding: 32px;
+ }
+ [purpose='app-details'] {
+ padding-right: 0px;
+ max-width: 100%;
+ }
+ [purpose='right-sidebar'] {
+ width: 100%;
+ margin-left: 0px;
+ }
+ [purpose='breadcrumbs-and-search'] {
+ margin-bottom: 32px;
+ }
+ }
+
+ @media (max-width: 768px) {
+ [purpose='breadcrumbs-and-search'] {
+ max-width: 1072px;
+ font-size: 14px;
+ [purpose='breadcrumbs'] {
+ margin-bottom: 24px;
+ }
+ [purpose='search'] {
+ width: 100%;
+ .DocSearch-Button {
+ width: 100%;
+ }
+ }
+ }
+ }
+
+ @media (max-width: 575px) {
+ [purpose='page-container'] {
+ padding: 32px 24px;
+ }
+
+ }
+ @media (max-width: 375px) {
+ [purpose='icon-and-name'] {
+ flex-direction: column;
+ align-items: flex-start;
+
+ }
+
+ }
+
+
+ }
diff --git a/website/assets/styles/pages/app-library.less b/website/assets/styles/pages/app-library.less
new file mode 100644
index 000000000000..0f5ba623ce19
--- /dev/null
+++ b/website/assets/styles/pages/app-library.less
@@ -0,0 +1,244 @@
+#app-library {
+
+ [purpose='page-container'] {
+ padding: 64px;
+ }
+ [purpose='page-content'] {
+ max-width: 1072px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+ [purpose='search-and-headline'] {
+ margin-bottom: 64px;
+ }
+ [purpose='page-title'] {
+ max-width: 662px;
+ margin-right: 16px;
+ h1 {
+ margin-bottom: 16px;
+ font-size: 32px;
+ font-weight: 800;
+ line-height: 38.4px; /* 120% */
+ }
+ p {
+ margin-bottom: 0px;
+ }
+ }
+
+ [purpose='request-button'] {
+ display: flex;
+ padding: 8px 16px;
+ justify-content: center;
+ align-items: center;
+ border-radius: 16px;
+ background: #F9FAFC;
+ color: #515774;
+ text-align: center;
+ height: 49px;
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 150%;
+ &:hover {
+ text-decoration: none;
+ }
+ }
+
+ [purpose='app-search'] {
+
+ // Note: We're using classes here to override the default Docsearch styles;
+ button {
+ width: 100%;
+ cursor: text;
+ margin: 0;
+ }
+ .DocSearch-Button {
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ border: 1px solid @core-fleet-black-25;
+ background-color: #FFF;
+ padding: 8px 15px;
+ height: 36px;
+ margin: 0;
+ width: 221px;
+ }
+ .DocSearch-Button:hover {
+ box-shadow: none;
+ border: 1px solid @core-fleet-black-25;
+ color: @core-fleet-black-50;
+ }
+ .DocSearch-Search-Icon {
+ margin-left: 0px;
+ margin-right: 8px;
+ height: 16px;
+ width: 16px;
+ color: @core-fleet-black-50;
+ stroke-width: 3px;
+ }
+ .DocSearch-Button-Keys {
+ display: none;
+ }
+ .input-group:focus-within {
+ border: 1px solid @core-vibrant-blue;
+ }
+ .DocSearch-Button-Placeholder {
+ font-size: 16px;
+ line-height: 16px;
+ font-weight: 400;
+ padding-left: 0px;
+ }
+ [purpose='disabled-search'] {
+ input {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ border: none;
+ } &::placeholder {
+ font-size: 16px;
+ line-height: 24px;
+ color: #8B8FA2;
+ }
+ .input-group {
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ border: 1px solid @core-fleet-black-25;
+ background: #FFF;
+ }
+ .input-group:focus-within {
+ border: 1px solid @core-vibrant-blue;
+ }
+ .form-control {
+ border-radius: 6px;
+ padding: 6px;
+ height: 36px;
+ margin: 0;
+ width: 212px;
+ }
+ .docsearch-input:focus-visible {
+ outline: none;
+ }
+ .ds-input:focus {
+ outline: rgba(0, 0, 0, 0);
+ }
+ .input-group-text {
+ color: @core-fleet-black-50;
+ }
+ .form-control {
+ height: 36px;
+ padding: 0px;
+ font-size: 16px;
+ } &:focus {
+ border: none;
+ }
+ }
+ img {
+ height: 16px;
+ margin-right: 8px;
+ }
+ background: #FFF;
+ &::placeholder {
+ font-size: 16px;
+ color: @core-fleet-black-50;
+ }
+ }
+ [purpose='app-cards'] {
+ column-count: 3;
+ margin-right: -8px;
+ margin-left: -8px;
+ margin-bottom: -8px;
+ margin-top: -8px;
+ }
+
+ [purpose='app-card'] {
+ margin-right: 8px;
+ margin-left: 8px;
+ margin-bottom: 8px;
+ margin-top: 8px;
+ display: flex;
+ height: 92px;
+ min-width: 30%;
+ padding: 16px;
+ align-items: flex-start;
+ gap: 16px;
+ // flex: 1 0 0;
+ border-radius: 8px;
+ border: 1px solid #E2E4EA;
+ background: #FFF;
+ box-shadow: none;
+ &:hover {
+ text-decoration: none;
+ color: unset;
+ box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, 0.03);
+ }
+ &.invisible {
+ height: 0px;
+ border: none;
+ }
+ h4 {
+ color: var(--text-text-brand, #192147);
+
+ /* Title XS */
+ font-family: Inter;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 800;
+ line-height: 19.2px; /* 120% */
+ margin-bottom: 0px;
+ }
+ p {
+ color: var(--text-text-primary, #515774);
+
+ /* Body SM (FKA Card text) */
+ font-family: Inter;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 21px; /* 150% */
+ }
+ }
+ [purpose='app-icon'] {
+ margin-right: 16px;
+ img {
+ height: 60px;
+ }
+ }
+
+ @media (max-width: 991px) {
+ [purpose='page-container'] {
+ padding: 64px 32px;
+ }
+ [purpose='app-card'] {
+ min-width: 40%;
+ &.invisible {
+ display: none;
+ border: none;
+ height: 0px;
+ padding: 0;
+ margin: 0;
+ }
+ }
+ }
+
+ @media (max-width: 768px) {
+ [purpose='page-container'] {
+ padding: 48px 24px;
+ }
+ [purpose='app-search'] {
+ margin-top: 32px;
+ width: 100%;
+ .input-group {
+ width: 100%;
+ }
+ }
+ }
+
+ @media (max-width: 575px) {
+ [purpose='page-container'] {
+ padding: 32px 24px;
+ }
+
+ }
+
+}
diff --git a/website/assets/styles/pages/articles/articles.less b/website/assets/styles/pages/articles/articles.less
index 770943f00c35..7d5fd9f09742 100644
--- a/website/assets/styles/pages/articles/articles.less
+++ b/website/assets/styles/pages/articles/articles.less
@@ -220,7 +220,7 @@
}
[purpose='guides'] {
column-count: 3;
- margin-left: -2px;
+ margin-left: -10px;
margin-right: 14px;
}
[purpose='guide-card'] {
diff --git a/website/assets/styles/pages/entrance/login.less b/website/assets/styles/pages/entrance/login.less
index 46ffa53c306f..de5aafbe2cb9 100644
--- a/website/assets/styles/pages/entrance/login.less
+++ b/website/assets/styles/pages/entrance/login.less
@@ -164,7 +164,7 @@
[purpose='customer-portal-form'] {
max-width: unset;
}
- [purpose='signup-form'] {
+ [purpose='login-form'] {
width: 100%;
}
[purpose='quote-and-logos'] {
@@ -183,7 +183,7 @@
[purpose='page-container'] {
padding: 48px 24px;
}
- [purpose='login-link'] {
+ [purpose='register-link'] {
margin-bottom: 12px;
}
[purpose='customer-portal-form'] {
diff --git a/website/assets/styles/pages/handbook/basic-handbook.less b/website/assets/styles/pages/handbook/basic-handbook.less
index 431cb9ef604c..ca7c09f970d1 100644
--- a/website/assets/styles/pages/handbook/basic-handbook.less
+++ b/website/assets/styles/pages/handbook/basic-handbook.less
@@ -580,6 +580,7 @@
position: sticky;
bottom: 107px;
overflow-x: hidden;
+ pointer-events: none;
}
[purpose='back-to-top-button'] {
display: inline-block;
@@ -593,6 +594,7 @@
cursor: pointer;
border: 1px solid #E2E4EA;
border-radius: 16px 0px 0px 16px;
+ pointer-events: auto;
p {
color: #515774;
font-size: 11px;
diff --git a/website/assets/styles/pages/homepage.less b/website/assets/styles/pages/homepage.less
index 38c1fa16caef..e00c3c2565c3 100644
--- a/website/assets/styles/pages/homepage.less
+++ b/website/assets/styles/pages/homepage.less
@@ -85,11 +85,12 @@
flex-direction: row;
justify-content: center;
margin-top: 64px;
+ margin-bottom: 64px;
h4 {
color: #515774;
text-align: center;
- /* Body LG (bold) */
+ text-transform: unset;
font-family: Inter;
font-size: 18px;
font-style: normal;
@@ -115,26 +116,36 @@
// flex-direction: column;
// align-items: center;
// }
+ [purpose='statistics-column'] {
+ display: flex;
+ flex-direction: row;
+ }
[purpose='customers'] {
border-right: 1px solid #E2E4EA;
display: flex;
- padding: 8px 64px;
+ padding: 8px 48px;
flex-direction: column;
align-items: center;
}
[purpose='devices'] {
border-right: 1px solid #E2E4EA;
display: flex;
- padding: 8px 64px;
+ padding: 8px 48px;
flex-direction: column;
align-items: center;
}
[purpose='countries'] {
display: flex;
- padding: 8px 64px;
+ padding: 8px 48px;
+ flex-direction: column;
+ align-items: center;
+ border-right: 1px solid #E2E4EA;
+ }
+ [purpose='response-time'] {
+ display: flex;
+ padding: 8px 48px;
flex-direction: column;
align-items: center;
-
}
}
@@ -1099,6 +1110,31 @@
[purpose='integrations-section'] {
margin-top: 80px;
}
+ [purpose='statistics'] {
+ [purpose='statistics-column'] {
+ display: flex;
+ flex-direction: column;
+ width: 227px;
+ }
+ [purpose='countries'] {
+ border-right: none;
+ padding: 16px 32px;
+
+ }
+ [purpose='customers'] {
+ padding: 16px 32px;
+ }
+ [purpose='devices'] {
+ padding: 16px 32px;
+ }
+ [purpose='response-time'] {
+ padding: 16px 32px;
+ }
+
+ }
+
+
+
[purpose='homepage-text-block'] {
margin-bottom: 80px;
p {
@@ -1399,12 +1435,13 @@
max-width: fit-content;
margin-left: auto;
margin-right: auto;
- margin-top: 32px;
+ margin-top: 48px;
+ margin-bottom: 48px;
h4 {
margin-bottom: 0px;
}
[purpose='customers'] {
- padding: 0px 64px 24px 64px;
+ padding: 0px 24px 24px 24px;
border-right: none;
}
[purpose='devices'] {
@@ -1413,13 +1450,15 @@
padding: 24px;
border-right: none;
}
-
+ [purpose='response-time'] {
+ padding: 24px;
+ border-bottom: 1px solid #E2E4EA;
+ }
[purpose='countries'] {
- padding: 24px 64px 0px 64px;
+ order: 1;
+ padding: 24px 24px 0px 24px;
border-right: none;
}
-
-
}
[purpose='hero-background-image'] {
background-size: auto 320px;
@@ -1606,6 +1645,7 @@
font-size: 12px;
}
+
[purpose='truncated-vulnerability-management-text'] {
display: none;
}
@@ -1624,6 +1664,11 @@
font-size: 16px;
}
}
+ [purpose='statistics'] {
+ margin-top: 32px;
+ margin-bottom: 32px;
+ }
+
[purpose='endpoints-banner'] {
[purpose='endpoint-banner-text'] {
padding-left: 24px;
diff --git a/website/assets/styles/pages/endpoint-ops.less b/website/assets/styles/pages/observability.less
similarity index 99%
rename from website/assets/styles/pages/endpoint-ops.less
rename to website/assets/styles/pages/observability.less
index a2459aa9ebe9..b1a29e3dac81 100644
--- a/website/assets/styles/pages/endpoint-ops.less
+++ b/website/assets/styles/pages/observability.less
@@ -1,4 +1,4 @@
-#endpoint-ops-page {
+#observability-page {
@heading-line-height: 120%;
@text-line-height: 150%;
diff --git a/website/assets/styles/pages/osquery-table-details.less b/website/assets/styles/pages/osquery-table-details.less
index b452731b137e..e7b42e2f31f7 100644
--- a/website/assets/styles/pages/osquery-table-details.less
+++ b/website/assets/styles/pages/osquery-table-details.less
@@ -278,19 +278,26 @@
.hljs-keyword {
color: #FFF;
}
+ .hljs-string { // For words wrapped in quotation marks
+ color: #FFF;
+ }
color: #FFF;
background-color: #AE6DDF;
- border-radius: 4px;
- padding: 4px 4px 4px 4px;
+ border-radius: 3px;
white-space: pre;
vertical-align: baseline;
- line-height: 16px;
span {
padding: 0;
}
}
+ .hljs-number {
+ color: #f5871f;
+ }
.hljs-string { // For words wrapped in quotation marks
- color: #3DB67B;
+ color: #4fd061;
+ .hljs-keyword {
+ color: #4fd061;
+ }
}
background-color: @ui-off-white;
border: none;
diff --git a/website/assets/styles/pages/query-library.less b/website/assets/styles/pages/query-library.less
index 7560755e2ba4..924a4dc887ad 100644
--- a/website/assets/styles/pages/query-library.less
+++ b/website/assets/styles/pages/query-library.less
@@ -51,6 +51,7 @@
}
.DocSearch-Button-Placeholder {
font-size: 16px;
+ line-height: 16px;
font-weight: 400;
padding-left: 0px;
}
diff --git a/website/assets/styles/pages/software-management.less b/website/assets/styles/pages/software-management.less
new file mode 100644
index 000000000000..c8ae0e0f3feb
--- /dev/null
+++ b/website/assets/styles/pages/software-management.less
@@ -0,0 +1,497 @@
+#software-management-page {
+ @heading-lineheight: 120%;
+ @text-lineheight: 150%;
+
+ h1 {
+ color: @core-fleet-black;
+ font-size: 48px;
+ font-weight: 800;
+ line-height: @heading-lineheight; /* 120% */
+ }
+ h2 {
+ color: @core-fleet-black;
+ text-align: center;
+ font-feature-settings: 'salt' on, 'ss01' on, 'ss02' on;
+ font-size: 32px;
+ font-weight: 800;
+ line-height: @text-lineheight;
+ }
+ h3 {
+ margin-bottom: 32px;
+ color: @core-fleet-black;
+ font-size: 24px;
+ font-weight: 800;
+ line-height: @heading-lineheight;
+ }
+ h4 {
+ color: @core-fleet-black-75;
+ font-feature-settings: 'salt' on, 'ss01' on, 'ss02' on;
+ font-family: 'Roboto Mono';
+ font-size: 14px;
+ font-weight: 400;
+ line-height: @text-lineheight;
+ text-transform: uppercase;
+ }
+ p {
+ font-size: 16px;
+ line-height: @text-lineheight;
+ color: @core-fleet-black-75;
+ }
+
+
+
+ [parasails-component='animated-arrow-button'] {
+ font-weight: 600;
+ }
+ [purpose='page-container'] {
+ padding: 64px;
+ }
+ [purpose='page-content'] {
+ max-width: 1072px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+ [purpose='hero-text'] {
+ max-width: 568px;
+ padding-top: 16px;
+ padding-bottom: 32px;
+ }
+ [purpose='button-row'] {
+ flex-direction: row;
+ [purpose='contact-button'] {
+ display: flex;
+ height: 36px;
+ padding: 16px;
+ justify-content: center;
+ align-items: center;
+ gap: 4px;
+ border-radius: 8px;
+ background: @core-vibrant-red;
+ color: #FFF;
+ text-align: center;
+ font-size: 16px;
+ font-weight: 700;
+ line-height: @text-lineheight;
+ margin-right: 24px;
+ &:hover {
+ text-decoration: none;
+ }
+ }
+ }
+ [purpose='hero-slides'] {
+ padding-top: 64px;
+ padding-bottom: 24px;
+ [purpose='carousel-headings'] {
+ position: unset;
+ padding-top: 24px;
+ padding-bottom: 16px;
+ margin-left: unset;
+ margin-right: unset;
+ }
+ [purpose='slide-text'] {
+ width: 33%;
+ cursor: pointer;
+ h5 {
+ font-size: 16px;
+ font-weight: 800;
+ line-height: @heading-lineheight;
+ }
+ p {
+ margin-right: 16px;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: @text-lineheight;
+ }
+ h5, p {
+ color: @core-fleet-black-50;
+ }
+ &:not(:last-of-type) {
+ margin-right: 16px;
+ }
+ &.active {
+ h5, p {
+ color: @core-fleet-black;
+ }
+ }
+ &:hover {
+ h5, p {
+ color: @core-fleet-black;
+ }
+ }
+ }
+ [purpose='carousel-indicators'] {
+ position: relative;
+ bottom: 0px;
+ margin-top: 16px;
+ margin-bottom: 16px;
+ li {
+ width: 40px;
+ background-color: @core-fleet-black-10;
+ &.active {
+ background-color: @core-fleet-black-50;
+ }
+ }
+ }
+ }
+ [purpose='logo-row'] {
+ [parasails-component='logo-carousel'] {
+ margin-top: 64px;
+ margin-bottom: 32px;
+ }
+ }
+ [purpose='feature-slides'] {
+ height: 660px;
+ }
+ [purpose='feature-switch'] {
+ padding-top: 64px;
+ margin-right: auto;
+ margin-left: auto;
+ border-bottom: 1px solid @core-fleet-black-10;
+ }
+ [purpose='feature-option'] {
+ width: 33%;
+ padding: 16px 40px;
+ white-space: nowrap;
+ text-align: center;
+ cursor: pointer;
+ color: @core-fleet-black-50;
+ &.active {
+ color: @core-fleet-black;
+ border-bottom: 2px solid @core-fleet-black;
+ }
+ &:hover {
+ color: @core-fleet-black;
+ }
+ }
+ [purpose='feature-slide'] {
+ padding-top: 64px;
+ padding-bottom: 32px;
+ &.invisible {
+ height: 0;
+ padding: 0;
+ }
+ [purpose='feature-text'] {
+ padding-left: 48px;
+ margin-left: 16px;
+ width: 50%;
+ }
+ }
+ [purpose='feature-image'] {
+ width: 50%;
+ img {
+ max-width: 100%;
+ max-height: 100%;
+ }
+ }
+ [purpose='checklist'] {
+ margin-top: 8px;
+ p {
+ font-size: 14px;
+ font-weight: 400;
+ line-height: @text-lineheight;
+ padding-left: 37px;
+ text-indent: -37px;
+ margin-bottom: 1.5rem;
+ &:last-of-type {
+ margin-bottom: 0px;
+ }
+ }
+ p::before {
+ content: ' ';
+ background-image: url('/images/icon-checkmark-green-20x20@2x.png');
+ background-size: 20px 20px;
+ display: inline-block;
+ position: relative;
+ top: 5px;
+ margin-right: 16px;
+ width: 20px;
+ height: 20px;
+ }
+ }
+ [purpose='feature-link'] {
+ padding-top: 32px;
+ }
+
+ [purpose='testimonial'] {
+ max-width: 524px;
+ margin-left: auto;
+ margin-right: auto;
+ padding-bottom: 64px;
+ padding-top: 32px;
+ text-align: center;
+ }
+ [purpose='it-testimonial'] {
+ max-width: 524px;
+ margin-left: auto;
+ margin-right: auto;
+ padding-bottom: 32px;
+ padding-top: 32px;
+ text-align: center;
+ }
+ [purpose='testimonial-image'] {
+ height: 48px;
+ margin-bottom: 5px;
+ }
+ [purpose='testimonial-text'] {
+ color: @core-fleet-black-75;
+ text-align: center;
+ font-size: 18px;
+ font-style: italic;
+ font-weight: 400;
+ line-height: @text-lineheight;
+ margin-bottom: 24px;
+ }
+ [purpose='testimonial-attribution'] {
+ [purpose='name'] {
+ color: @core-fleet-black-75;
+ text-align: center;
+ font-size: 12px;
+ font-weight: 700;
+ line-height: @text-lineheight; /* 150% */
+ margin-bottom: 0px;
+ }
+ [purpose='job-title'] {
+ color: @core-fleet-black-75;
+ margin-bottom: 0px;
+ font-size: 12px;
+ font-weight: 400;
+ line-height: @text-lineheight;
+ }
+ }
+
+
+ [purpose='feature'] {
+ padding-top: 64px;
+ padding-bottom: 64px;
+ h3 {
+ color: @core-fleet-black;
+ font-size: 24px;
+ font-weight: 800;
+ line-height: @heading-lineheight; /* 120% */
+ margin-bottom: 32px;
+ }
+ &:last-of-type {
+ margin-bottom: 0px;
+ }
+ }
+
+ [purpose='feature'].flex-column {
+ [purpose='feature-text'] {
+ margin-left: 16px;
+ padding-left: 48px;
+ }
+ }
+ [purpose='feature'].flex-column-reverse {
+ [purpose='feature-text'] {
+ margin-right: 16px;
+ padding-right: 48px;
+ }
+ }
+ [purpose='feature-text'] {
+ width: 50%;
+ }
+ [parasails-component='scrollable-tweets'] {
+ [purpose='tweets'] {
+ margin-top: 32px;
+ margin-bottom: 88px;
+ [purpose='quote'] {
+ color: @core-fleet-black-75;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: @text-lineheight;
+ }
+ [purpose='video-link'] {
+ text-decoration: underline;
+ color: @core-fleet-black-75;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: @text-lineheight;
+ text-decoration-line: underline;
+ }
+ }
+ }
+ [purpose='section-heading'] {
+ h4 {
+ margin-bottom: 0px;
+ }
+ [purpose='button-row'] {
+ [purpose='contact-button'] {
+ padding: 16px 32px;
+ }
+ }
+ }
+
+ [parasails-component='parallax-city'] {
+ background: linear-gradient(358deg, #E9F4F4 0%, #FFFFFF 100%);
+ }
+
+ @media (max-width: 991px) {
+ [purpose='page-container'] {
+ padding: 64px 32px;
+ }
+ [purpose='feature-slides'] {
+ height: unset;
+ }
+ [purpose='feature-option'] {
+ width: 33%;
+ padding: 16px 8px;
+ &:last-of-type {
+ padding: 16px 8px;
+ }
+ }
+ [purpose='feature-slide'] {
+ [purpose='feature-text'] {
+ padding-left: 16px;
+ width: 50%;
+ }
+ }
+ [purpose='feature'].flex-column {
+ [purpose='feature-text'] {
+ margin-left: 16px;
+ padding-left: 16px;
+ }
+ }
+ [purpose='feature'].flex-column-reverse {
+ [purpose='feature-text'] {
+ margin-right: 16px;
+ padding-right: 16px;
+ }
+ }
+ }
+ @media (max-width: 767px) {
+ [purpose='page-container'] {
+ padding: 64px 32px;
+ }
+ [purpose='hero-slides'] {
+ [purpose='carousel-headings'] {
+ position: unset;
+ padding-top: 16px;
+ padding-bottom: 16px;
+ margin-left: unset;
+ margin-right: unset;
+ }
+ [purpose='slide-text'] {
+ width: 100%;
+ p {
+ margin-right: 16px;
+ margin-bottom: 0px;
+ }
+ &:not(:last-of-type) {
+ margin-right: unset;
+ margin-bottom: 32px;
+ }
+ }
+ [purpose='feature-slide'] {
+ padding-top: 64px;
+ padding-bottom: 62px;
+ [purpose='feature-text'] {
+ margin-left: 0px;
+ padding-left: 0px;
+ width: 50%;
+ }
+ }
+ }
+ [purpose='feature-switch'] {
+ margin-right: 0;
+ margin-left: auto;
+ border-bottom: none;
+ border-left: 1px solid @core-fleet-black-10;
+ margin-top: 64px;
+ padding-top: 0px;
+ }
+ [purpose='feature-option'] {
+ width: 100%;
+ text-align: center;
+ &.active {
+ border-left: 2px solid @core-fleet-black;
+ border-bottom: none;
+ }
+ }
+ [purpose='feature-slide'] {
+ padding-top: 32px;
+ padding-bottom: 64px;
+ &.invisible {
+ height: 0;
+ padding: 0;
+ }
+ [purpose='feature-text'] {
+ margin-left: 0px;
+ padding-left: 0px;
+ width: 100%;
+ margin-bottom: 32px;
+ }
+ }
+ [purpose='testimonial'] {
+ padding-bottom: 64px;
+ padding-top: 0px;
+ }
+ [purpose='feature'].flex-column {
+ padding-top: 32px;
+ padding-bottom: 48px;
+ [purpose='feature-text'] {
+ margin-left: 0px;
+ padding-left: 0px;
+ }
+ }
+ [purpose='feature'].flex-column-reverse {
+ padding-top: 48px;
+ padding-bottom: 32px;
+ [purpose='feature-text'] {
+ padding-right: 0px;
+ margin-right: 0px;
+ }
+ }
+ [purpose='feature-text'] {
+ width: 100%;
+ }
+ [purpose='feature-image'] {
+ width: 100%;
+ margin-bottom: 32px;
+ }
+ [purpose='section-heading'] {
+ padding: 64px 32px;
+ }
+ }
+ @media (max-width: 576px) {
+ [purpose='page-container'] {
+ padding: 32px 24px;
+ }
+ [purpose='quote'] {
+ padding: 0px 32px 64px 32px;
+ }
+ [purpose='section-heading'] {
+ padding: 40px 24px;
+ }
+ [parasails-component='scrollable-tweets'] {
+ [purpose='tweets'] {
+ margin-bottom: 56px;
+ }
+ }
+ }
+
+ @media (max-width: 376px) {
+ h1 {
+ font-size: 32px;
+ }
+ [purpose='page-container'] {
+ padding: 32px 16px;
+ }
+ [purpose='testimonial'] {
+ padding-bottom: 32px;
+ padding-top: 0px;
+ }
+ [purpose='button-row'] {
+ flex-direction: column;
+ align-items: center;
+ [purpose='contact-button'] {
+ margin-bottom: 12px;
+ width: 100%;
+ margin-right: 0px;
+ }
+ }
+ [purpose='section-heading'] {
+ padding: 40px 0px;
+ }
+ }
+
+}
diff --git a/website/assets/styles/pages/testimonials.less b/website/assets/styles/pages/testimonials.less
index 7a1f24e14ab4..79e1e3d8c8a7 100644
--- a/website/assets/styles/pages/testimonials.less
+++ b/website/assets/styles/pages/testimonials.less
@@ -115,7 +115,7 @@
border-radius: 16px;
border: 1px solid var(--UI-Fleet-Black-10, #E2E4EA);
background: var(--Core-White, #FFF);
- height: min-content;
+ margin-bottom: 24px;
[purpose='logo'] {
img {
max-height: 32px;
@@ -146,6 +146,7 @@
}
[purpose='name'] {
color: @core-fleet-black;
+ text-transform: capitalize;
}
[purpose='profile-picture'] {
margin-right: 16px;
@@ -352,7 +353,22 @@
height: 641px;
}
}
-
+ @media (max-width: 1199px) {
+ [purpose='video-modal'] {
+ [purpose='modal-dialog'] {
+ width: 100%;
+ max-width: 100%;
+ }
+ [purpose='modal-content'] {
+ max-width: 960px;
+ height: 540px;
+ }
+ iframe {
+ width: 960px;
+ height: 540px;
+ }
+ }
+ }
@media (max-width: 991px) {
@@ -362,14 +378,40 @@
[purpose='page-container'] {
padding: 64px 32px;
}
+ [purpose='video-modal'] {
+ [purpose='modal-dialog'] {
+ max-width: 97vw;
+ }
+ [purpose='modal-content'] {
+ max-width: 540px;
+ height: 304px;
+ }
+ iframe {
+ width: 540px;
+ height: 304px;
+ }
+ }
}
@media (max-width: 776px) {
- [purpose='page-container'] {
- padding: 48px 24px;
- }
- [purpose='testimonials-container'] {
- columns: 2;
- }
+ [purpose='page-container'] {
+ padding: 48px 24px;
+ }
+ [purpose='testimonials-container'] {
+ columns: 2;
+ }
+ [purpose='video-modal'] {
+ [purpose='modal-dialog'] {
+ max-width: 97vw;
+ }
+ [purpose='modal-content'] {
+ max-width: 540px;
+ height: 304px;
+ }
+ iframe {
+ width: 540px;
+ height: 304px;
+ }
+ }
}
@media (max-width: 576px) {
@@ -435,7 +477,16 @@
}
}
}
-
+ [purpose='video-modal'] {
+ [purpose='modal-content'] {
+ width: 95vw;
+ height: calc(~'9/16 * 95vw');
+ }
+ iframe {
+ width: 95vw;
+ height: calc(~'9/16 * 95vw');
+ }
+ }
}
}
diff --git a/website/assets/styles/pages/vulnerability-management.less b/website/assets/styles/pages/vulnerability-management.less
deleted file mode 100644
index 87998dd3d2cc..000000000000
--- a/website/assets/styles/pages/vulnerability-management.less
+++ /dev/null
@@ -1,709 +0,0 @@
-#vulnerability-management-page {
- background: linear-gradient(180deg, #E8F1F6 0%, #FFF 8.76%);
- h1 {
- font-size: 56px;
- font-weight: 800;
- line-height: 54px;
- }
- h2 {
- font-weight: 800;
- font-size: 32px;
- line-height: 38px;
- }
- h3 {
- font-weight: 800;
- font-size: 32px;
- line-height: 120%;
- }
- h4 {
- font-family: 'Roboto Mono';
- font-style: normal;
- font-weight: 400;
- font-size: 14px;
- line-height: 150%;
- text-transform: uppercase;
- color: @core-fleet-black-75;
- margin-bottom: 4px;
- }
- p {
- font-size: 16px;
- line-height: 24px;
- color: @core-fleet-black-75;
- }
- strong {
- colore: @core-fleet-black;
- }
- [purpose='page-content'] {
- max-width: 960px;
- }
-
- [purpose='page-container'] {
- padding-left: 120px;
- padding-right: 120px;
- margin-left: auto;
- margin-right: auto;
- }
-
- [purpose='page-headline'] {
- padding-bottom: 80px;
- max-width: 780px;
- h2 {
- font-size: 48px;
- font-style: normal;
- font-weight: 800;
- line-height: 57.6px;
- margin-bottom: 0px;
- }
- }
- [purpose='hero'] {
- padding-top: 80px;
- padding-bottom: 80px;
- }
- [purpose='hero-image'] {
- max-width: 360px;
- img {
- padding-left: 25px;
- max-width: 100%;
- max-height: 100%;
- width: 360px;
- }
- }
- [purpose='hero-text'] {
- width: 468px;
- margin-left: 40px;
- text-align: left;
- strong {
- margin-bottom: 8px;
- font-weight: 800;
- display: block;
- }
- p {
- margin-bottom: 40px;
- }
- }
-
- [purpose='button-row'] {
- a {
- font-weight: 700;
- font-size: 16px;
- line-height: 24px;
- }
- [purpose='cta-button'] {
- cursor: pointer;
- margin-right: 32px;
- background: @core-vibrant-red;
- border-radius: 8px;
- padding: 16px 32px;
- height: 36px;
- display: flex;
- justify-content: center;
- align-items: center;
- color: #FFF;
- position: relative;
- text-decoration: none;
- overflow: hidden;
- }
- [purpose='cta-button']::before {
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
- opacity: 1;
- content: ' ';
- position: absolute;
- top: 0;
- left: -5px;
- width: 70%;
- height: 100%;
- transform: skew(-10deg);
- transition: left 0.5s ease-in, opacity 0.50s ease-in, width 0.5s ease-in;
- }
- [purpose='cta-button']:hover:before {
- opacity: 0;
- left: 160px;
- width: 110%;
- }
-
- }
-
-
- [purpose='testimonial-videos'] {
- width: 468px;
- margin-left: 40px;
- }
- [purpose='testimonials'] {
- margin-bottom: 80px;
- }
- [purpose='testimonial-quote'] {
- width: 380px;
- a {
- text-decoration: none;
- color: unset;
- &:hover {
- text-decoration: none;
- }
- }
- [purpose='quote'] {
- p {
- color: @core-fleet-black-75;
- font-size: 20px;
- font-style: italic;
- font-weight: 400;
- line-height: 30px;
- }
- }
- [purpose='quote-image'] {
- margin-right: 16px;
- img {
- width: 48px;
- height: 48px;
- }
- }
- [purpose='quote-attribution'] {
- display: inline-flex;
- padding: 4px 16px 4px 4px;
- border-radius: 28px;
- width: fit-content;
- margin-top: 8px;
- [purpose='name'] {
- font-size: 12px;
- font-weight: 700;
- line-height: 18px;
- margin-bottom: 0px;
- }
- [purpose='title'] {
- color: @core-fleet-black-75;
- font-size: 12px;
- font-weight: 400;
- line-height: 18px;
- margin-bottom: 0px;
- }
- &:hover {
- background-color: #F9FAFC;
- }
- &:active {
- background-color: #F2F2F5;
- }
- }
-
-
- }
-
- [purpose='testimonial-video'] {
- cursor: pointer;
- width: 223px;
- height: 168px;
- border-radius: 22.265px;
- border: 0.975px solid @core-fleet-black-50;
- display: flex;
- margin-bottom: 0px;
- position: relative;
- span {
- img {
- height: 10.235px;
- width: auto;
- margin-right: 5.5px;
- }
- position: absolute;
- left: 20px;
- bottom: 20px;
- border-radius: 16.698px;
- background: rgba(0, 2, 10, 0.50);
- backdrop-filter: blur(5.566145420074463px);
- display: inline-flex;
- padding: 5.566px 11.132px;
- justify-content: center;
- align-items: center;
- color: #FFF;
- font-size: 11.132px;
- font-style: normal;
- font-weight: 400;
- line-height: 16.698px;
- }
- &:hover {
- box-shadow: 0px 4px 16px 0px #E2E4EA;
- }
- &:first-of-type {
- background: url('/images/video-testimonial-thumbnail-austin-anderson-223x168@2x.jpg');
- background-position: center;
- background-size: cover;
- margin-right: 12px;
- margin-left: 0px;
- }
- &:last-of-type {
- background: url('/images/video-testimonial-thumbnail-andre-shields-223x168@2x.png');
- background-position: center;
- background-size: cover;
- margin-right: 0px;
- margin-left: 12px;
- }
- }
- [purpose='video-modal'] {
- [purpose='modal-dialog'] {
- width: 100%;
- max-width: 100%;
- }
- [purpose='modal-content'] {
- max-width: 1140px;
- height: 641px;
- background-color: transparent;
- box-shadow: none;
- border: none;
- padding: 0px;
- margin-top: 150px;
- margin-left: auto;
- margin-right: auto;
- [purpose='modal-close-button'] {
- top: -40px;
- right: 0px;
- border-radius: 50%;
- width: 32px;
- height: 32px;
- padding: 0px 0px 4px 0px;
- background-color: #192147;
- color: #FFF;
- opacity: 1;
- }
- }
- iframe {
- width: 1140px;
- height: 641px;
- }
- }
- [parasails-component='logo-carousel'] {
- margin-bottom: 80px;
- }
- [purpose='calendar-feature'] {
- margin-bottom: 140px;
- h3 {
- margin-bottom: 24px;
- }
- [purpose='calendar-feature-text'] {
- max-width: 480px;
- }
- [purpose='new-badge'] {
- background-color: #0587FF;
- padding: 4px 8px 3px 8px;
- display: flex;
- align-items: center;
- border-radius: 14px;
- color: #FFF;
- font-size: 12px;
- font-weight: 500;
- line-height: 18px;
- text-transform: uppercase;
- margin-bottom: 12px;
- width: min-content;
- }
- [purpose='calendar-checklist'] {
- margin-top: 8px;
- margin-bottom: 24px;
- p {
- font-size: 14px;
- font-style: normal;
- font-weight: 400;
- line-height: 21px;
- padding-left: 37px;
- text-indent: -37px;
- margin-bottom: 1.5rem;
- &:last-of-type {
- margin-bottom: 0px;
- }
- }
- p::before {
- content: ' ';
- background-image: url('/images/icon-checkmark-green-20x20@2x.png');
- background-size: 20px 20px;
- display: inline-block;
- position: relative;
- top: 5px;
- margin-right: 16px;
- width: 20px;
- height: 20px;
- }
- }
- [purpose='feature-video'] {
- margin-left: 80px;
- max-width: 468px;
- video {
- max-width: 100%;
- max-height: 100%;
- border-radius: 16px;
- }
- }
- [purpose='video-button'] {
- margin-top: 12px;
- cursor: pointer;
- img {
- height: 32px;
- margin-right: 8px;
- }
- font-size: 14px;
- font-weight: 700;
- line-height: 21px;
- }
- }
-
- [purpose='feature'] {
- margin-bottom: 180px;
- h3 {
- margin-bottom: 24px;
- }
- &:last-of-type {
- margin-bottom: 0px;
- }
- }
-
- [purpose='feature'].flex-column {
- [purpose='feature-text'] {
- margin-left: 48px;
- }
- }
- [purpose='feature'].flex-column-reverse {
- [purpose='feature-text'] {
- margin-right: 48px;
- }
- }
- [purpose='feature-image'] {
- max-width: 380px;
- img {
- max-width: 100%;
- max-height: 100%;
- width: 380px;
- }
- }
- [purpose='feature-text'] {
- width: 468px;
- }
- [purpose='checklist'] {
- margin-top: 8px;
- p {
- font-size: 14px;
- font-style: normal;
- font-weight: 400;
- line-height: 21px;
- padding-left: 37px;
- text-indent: -37px;
- margin-bottom: 1.5rem;
- &:last-of-type {
- margin-bottom: 0px;
- }
- }
- p::before {
- content: ' ';
- background-image: url('/images/icon-checkmark-green-20x20@2x.png');
- background-size: 20px 20px;
- display: inline-block;
- position: relative;
- top: 5px;
- margin-right: 16px;
- width: 20px;
- height: 20px;
- }
- }
- [purpose='tweets-container'] {
- padding-top: 200px;
- max-width: 960px;
- }
- [parasails-component='scrollable-tweets'] {
- [purpose='tweets'] {
- margin-top: 40px;
- }
- }
- [purpose='bottom-gradient'] {
- background: linear-gradient(180deg, #FFFFFF 0%, #E9F4F4 100%);
- }
- [purpose='bottom-cloud-city-banner'] {
- background: linear-gradient(180deg, #E9F4F4 0%, #FFFFFF 100%);
- img {
- width: 100%;
- }
- }
-
- @media (max-width: 1199px) {
- [purpose='page-container'] {
- padding-left: 80px;
- padding-right: 80px;
- }
- [purpose='video-modal'] {
- [purpose='modal-dialog'] {
- width: 100%;
- max-width: 100%;
- }
- [purpose='modal-content'] {
- max-width: 960px;
- height: 540px;
- }
- iframe {
- width: 960px;
- height: 540px;
- }
- }
- }
-
- @media (max-width: 991px) {
- [purpose='page-container'] {
- padding-left: 40px;
- padding-right: 40px;
- }
- [purpose='calendar-section'] {
- padding: 0px 40px 40px 40px;
- }
- [purpose='calendar-feature'] {
- [purpose='feature-video'] {
- margin-left: auto;
- margin-right: auto;
- margin-bottom: 40px;
- }
- }
- [purpose='page-content'] {
- max-width: 840px;
- }
- [purpose='tweets-container'] {
- max-width: 840px;
- }
- [purpose='testimonial-videos'] {
- width: 410px;
- }
- [purpose='hero-text'] {
- width: 410px;
- }
- [purpose='feature-text'] {
- width: 410px;
- }
- }
-
- @media (max-width: 767px) {
-
- [purpose='page-container'] {
- padding-left: 40px;
- padding-right: 40px;
- }
- [purpose='page-content'] {
- max-width: 480px;
- }
- [purpose='tweets-container'] {
- max-width: 480px;
- padding-left: 40px;
- padding-right: 40px;
- padding-top: 120px;
- }
- [purpose='page-headline'] {
- padding-bottom: 80px;
- width: 100%;
- h2 {
- font-size: 42px;
- line-height: 50.4px;
- }
- }
- [purpose='hero-image'] {
- margin-right: unset;
- }
- [purpose='hero-text'] {
- width: unset;
- margin-left: auto;
- }
- [purpose='testimonial-videos'] {
- width: unset;
- margin-top: 60px;
- }
- [purpose='button-row'] {
- max-width: 100%;
- [purpose='cta-button'] {
- margin-right: 0px;
- width: 100%;
- margin-bottom: 24px;
- }
- }
- [purpose='calendar-section'] {
- padding: 0px 32px 40px 32px;
- }
- [purpose='calendar-card-body'] {
- width: 100%;
- padding-left: 24px;
- padding-right: 24px;
- padding-bottom: 48px;
- padding-top: 60px;
- margin-right: 0px;
- text-align: center;
- }
- [purpose='calendar-card'] {
- height: unset;
- }
- [purpose='calendar-image'] {
- padding-top: 0;
- padding-bottom: 0;
- height: 420px;
- width: 100%;
- &:before {
- content: '';
- background: none;
- }
- }
- [purpose='feature-text'] {
- width: unset;
- }
- [purpose='feature'] {
- margin-bottom: 120px;
- }
- [purpose='feature'].flex-column {
- [purpose='feature-text'] {
- margin-left: auto;
- }
- }
- [purpose='feature'].flex-column-reverse {
- [purpose='feature-text'] {
- margin-right: auto;
- }
- }
- [purpose='feature-image'] {
- margin-left: auto;
- margin-right: auto;
- margin-bottom: 60px;
- }
- [purpose='hero-image'] {
- margin-bottom: 60px;
- }
- [purpose='testimonial-videos'] {
- margin-left: auto;
- margin-right: auto;
- }
- [purpose='testimonial-quote'] {
- width: 100%;
- [purpose='quote'] {
- img {
- margin-right: auto;
- margin-left: auto;
- }
- text-align: center;
- }
- [purpose='quote-attribution'] {
- margin-right: auto;
- margin-left: auto;
- }
- }
- [purpose='video-modal'] {
- [purpose='modal-dialog'] {
- max-width: 97vw;
- }
- [purpose='modal-content'] {
- max-width: 540px;
- height: 304px;
- }
- iframe {
- width: 540px;
- height: 304px;
- }
- }
- }
-
- @media (max-width: 575px) {
- [purpose='page-container'] {
- padding-left: 40px;
- padding-right: 40px;
- }
- [purpose='tweets-container'] {
- padding-left: 40px;
- padding-right: 40px;
- }
- [purpose='feature-image'] {
- img {
- max-width: 100%;
- }
- }
- [purpose='hero-image'] {
- img {
- max-width: 100%;
- }
- }
- [purpose='testimonial-video'] {
- width: 200px;
- height: 160px;
- &:first-of-type {
- margin-right: 10px;
- margin-left: auto;
- }
- &:last-of-type {
- margin-right: auto;
- margin-left: 10px;
- }
- }
-
- [purpose='video-modal'] {
- [purpose='modal-content'] {
- width: 95vw;
- height: calc(~'9/16 * 95vw');
- }
- iframe {
- width: 95vw;
- height: calc(~'9/16 * 95vw');
- }
- }
- [purpose='calendar-section'] {
- padding: 0px 24px 40px 24px;
- }
- }
- @media (max-width: 472px) {
- [purpose='testimonial-videos'] {
- flex-direction: column;
- }
- [purpose='testimonial-video'] {
- width: 223px;
- height: 168px;
- &:first-of-type {
- margin-right: auto;
- margin-left: auto;
- margin-bottom: 24px;
- }
- &:last-of-type {
- margin-right: auto;
- margin-left: auto;
- }
- }
-
- }
- @media (max-width: 375px) {
- [purpose='page-container'] {
- padding-left: 32px;
- padding-right: 32px;
- }
- [purpose='tweets-container'] {
- padding-left: 32px;
- padding-right: 32px;
- }
- [purpose='hero'] {
- padding-top: 40px;
- padding-bottom: 40px;
- }
- [purpose='hero-image'] {
- max-height: 360px;
- max-width: unset;
- img {
- max-width: 100%;
- max-height: 100%;
- width: 261px;
- }
- }
- [purpose='testimonials'] {
- padding-top: 40px;
- margin-bottom: 80px;
- }
- [purpose='testimonial-videos'] {
- flex-direction: column;
- margin-top: 40px;
- }
- [purpose='calendar-card-body'] {
- padding-left: 16px;
- padding-right: 16px;
- h3 {
- font-size: 24px;
- }
- }
- [purpose='calendar-image'] {
- height: 287px;
- }
- [purpose='calendar-section'] {
- padding: 0px 16px 40px 16px;
- }
- [purpose='feature-image'] {
- margin-bottom: 40px;
- }
- }
-}
diff --git a/website/config/custom.js b/website/config/custom.js
index f791a82e6f37..6b04fd379d5c 100644
--- a/website/config/custom.js
+++ b/website/config/custom.js
@@ -220,18 +220,17 @@ module.exports.custom = {
// Articles and release notes
'CHANGELOG.md': ['mikermcneil', 'noahtalerman', 'lukeheath'],
- 'articles': ['mike-j-thomas', 'mike-j-thomas', 'eashaw', 'mikermcneil', 'rachaelshaw'],
- 'website/assets/images/articles': ['mike-j-thomas', 'mike-j-thomas', 'eashaw', 'mikermcneil'],
+ 'articles': ['mike-j-thomas', 'eashaw', 'mikermcneil', 'rachaelshaw'],
+ 'website/assets/images/articles': ['mike-j-thomas', 'eashaw', 'mikermcneil'],
// Website (fleetdm.com)
'website': ['mikermcneil', 'eashaw'],// (default for website)
- 'website/views': 'eashaw',
+ 'website/views': ['eashaw', 'mike-j-thomas'],
'website/generators': 'eashaw',
'website/assets': 'eashaw',
'website/package.json': 'eashaw',
'website/config/routes.js': ['eashaw', 'mike-j-thomas'],// (for managing website URLs)
'website/config/policies.js': ['eashaw', 'mikermcneil'],// (for adding new pages and managing permissions)
- 'website/api/controllers/imagine': ['eashaw', 'mike-j-thomas'],// landing pages
// 🫧 Vulnerability dashboard
'ee/vulnerability-dashboard': ['eashaw', 'mikermcneil'],// (catch-all)
@@ -301,7 +300,6 @@ module.exports.custom = {
// "Secret handbook"
// Standard operating procedures (SOP), etc that would be public handbook content except for that it's confidential.
'README.md': ['mikermcneil'],// « about this repo
- 'cold-outbound-strategy.md': ['mikermcneil', 'sampfluger88'],// « Cold outbound strategy (see fleetdm.com/handbook/company/why-this-way for our vision of a better way to sell)
// GitHub issue templates
'.github/ISSUE_TEMPLATE': ['mikermcneil', 'sampfluger88', 'lukeheath'],// FUTURE: Bust out individual maintainership for issue templates once relevant DRIs are GitHub, markdown, and content design-certified
diff --git a/website/config/policies.js b/website/config/policies.js
index befa1d166c0a..00c0dbecbb25 100644
--- a/website/config/policies.js
+++ b/website/config/policies.js
@@ -45,8 +45,8 @@ module.exports.policies = {
'deliver-mdm-beta-signup': true,
'deliver-apple-csr': true,
'download-rss-feed': true,
- 'view-endpoint-ops': true,
- 'view-vulnerability-management': true,
+ 'view-observability': true,
+ 'view-software-management': true,
'deliver-mdm-demo-email': true,
'view-support': true,
'view-integrations': true,
@@ -58,4 +58,6 @@ module.exports.policies = {
'deliver-deal-registration-submission': true,
'get-est-device-certificate': true,
'view-testimonials': true,
+ 'view-app-library': true,
+ 'view-app-details': true,
};
diff --git a/website/config/routes.js b/website/config/routes.js
index d73683ded0d1..ab38d48a1d76 100644
--- a/website/config/routes.js
+++ b/website/config/routes.js
@@ -136,6 +136,7 @@ module.exports.routes = {
pageDescriptionForMeta: 'View and edit information about your Fleet Premium license.',
}
},
+ 'GET /customers/update-subscription': { action: 'customers/redirect-to-stripe-billing-portal' },
'GET /customers/forgot-password': {
action: 'entrance/view-forgot-password',
locals: {
@@ -227,20 +228,20 @@ module.exports.routes = {
}
},
- 'GET /endpoint-ops': {
- action: 'view-endpoint-ops',
+ 'GET /observability': {
+ action: 'view-observability',
locals: {
- pageTitleForMeta: 'Endpoint ops',
+ pageTitleForMeta: 'Observability',
pageDescriptionForMeta: 'Pulse check anything, build reports, and ship data to any platform with Fleet.',
currentSection: 'platform',
}
},
- 'GET /vulnerability-management': {
- action: 'view-vulnerability-management',
+ 'GET /software-management': {
+ action: 'view-software-management',
locals: {
- pageTitleForMeta: 'Vulnerability management',
- pageDescriptionForMeta: 'Report CVEs, software inventory, security posture, and other risks down to the chipset of any endpoint with Fleet.',
+ pageTitleForMeta: 'Software management',
+ pageDescriptionForMeta: 'Pick from a curated app library or upload your own custom packages. Configure custom installation scripts if you need or let Fleet do it for you.',
currentSection: 'platform',
}
},
@@ -292,14 +293,26 @@ module.exports.routes = {
}
},
- 'GET /customer-stories': {
+ 'GET /testimonials': {
action: 'view-testimonials',
locals: {
- pageTitleForMeta: 'Customer stories',
- pageDescriptionForMeta: 'See what people are saying about Fleet'
+ pageTitleForMeta: 'What people are saying',
+ pageDescriptionForMeta: 'See what people are saying about Fleet.'
}
},
+ 'GET /app-library': {
+ action: 'view-app-library',
+ locals: {
+ pageTitleForMeta: 'App library',
+ pageDescriptionForMeta: 'Install Fleet-maintained apps on your hosts without the need for additional configuration. Activate self-service for your end users.',
+ }
+ },
+
+ 'GET /app-library/:appIdentifier': {
+ action: 'view-app-details',// Meta title and description set in view action
+ },
+
// ╦ ╔═╗╔═╗╔═╗╔═╗╦ ╦ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗
// ║ ║╣ ║ ╦╠═╣║ ╚╦╝ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗
// ╩═╝╚═╝╚═╝╩ ╩╚═╝ ╩ ╩╚═╚═╝═╩╝╩╩╚═╚═╝╚═╝ ╩ ╚═╝
@@ -318,6 +331,7 @@ module.exports.routes = {
// ```
// 'GET /docs/using-fleet/learn-how-to-use-fleet': '/docs/using-fleet/fleet-for-beginners',
// ```
+ 'GET /customer-stories': '/testimonials',
'GET /try': '/get-started',
'GET /docs/deploying/fleet-public-load-testing': '/docs/deploying/load-testing',
'GET /handbook/customer-experience': '/handbook/customers',
@@ -508,7 +522,6 @@ module.exports.routes = {
//
// For example, a clever user might try to visit fleetdm.com/documentation, not knowing that Fleet's website
// puts this kind of thing under /docs, NOT /documentation. These "convenience" redirects are to help them out.
- 'GET /testimonials': '/customer-stories',
'GET /admin': '/admin/email-preview',
'GET /renew': 'https://calendly.com/zayhanlon/fleet-renewal-discussion',
'GET /documentation': '/docs',
@@ -538,12 +551,14 @@ module.exports.routes = {
'GET /try-fleet/waitlist': '/try-fleet',
'GET /endpoint-operations': '/endpoint-ops',// « just in case we type it the wrong way
'GET /example-dep-profile': 'https://github.com/fleetdm/fleet/blob/main/it-and-security/lib/automatic-enrollment.dep.json',
+ 'GET /vulnerability-management': (req,res)=> { let originalQueryString = req.url.match(/\?(.+)$/) ? '?'+req.url.match(/\?(.+)$/)[1] : ''; return res.redirect(301, sails.config.custom.baseUrl+'/software-management'+originalQueryString);},
+ 'GET /endpoint-ops': (req,res)=> { let originalQueryString = req.url.match(/\?(.+)$/) ? '?'+req.url.match(/\?(.+)$/)[1] : ''; return res.redirect(301, sails.config.custom.baseUrl+'/observability'+originalQueryString);},
// Shortlinks for texting friends, radio ads, etc
'GET /mdm': '/device-management?utm_content=mdm',// « alias for radio ad
- 'GET /it': '/endpoint-ops?utm_content=eo-it',
- 'GET /seceng': '/endpoint-ops?utm_content=eo-security',
- 'GET /vm': '/vulnerability-management?utm_content=vm',
+ 'GET /it': '/observability?utm_content=eo-it',
+ 'GET /seceng': '/observability?utm_content=eo-security',
+ 'GET /vm': '/software-management?utm_content=vm',
// Fleet UI
// =============================================================================================================
@@ -594,6 +609,8 @@ module.exports.routes = {
'GET /feature-request': 'https://github.com/fleetdm/fleet/issues/new?assignees=&labels=~feature+fest%2C%3Aproduct&projects=&template=feature-request.md&title=',
'GET /learn-more-about/policy-automation-run-script': '/guides/policy-automation-run-script',
'GET /learn-more-about/installing-fleetctl': '/guides/fleetctl#installing-fleetctl',
+ 'GET /learn-more-about/mdm-disk-encryption': '/guides/enforce-disk-encryption',
+ 'GET /learn-more-about/encrypt-linux-device': '/guides/linux-disk-encryption-end-user',
'GET /contribute-to/policies': 'https://github.com/fleetdm/fleet/edit/main/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml',
// Sitemap
@@ -673,4 +690,5 @@ module.exports.routes = {
'POST /api/v1/account/update-start-cta-visibility': { action: 'account/update-start-cta-visibility' },
'POST /api/v1/deliver-deal-registration-submission': { action: 'deliver-deal-registration-submission' },
'/api/v1/unsubscribe-from-marketing-emails': { action: 'unsubscribe-from-marketing-emails' },
+ 'POST /api/v1/customers/get-stripe-checkout-session-url': { action: 'customers/get-stripe-checkout-session-url' },
};
diff --git a/website/package.json b/website/package.json
index 23408ba0f6be..d6029c1187a5 100644
--- a/website/package.json
+++ b/website/package.json
@@ -17,7 +17,8 @@
"sails-hook-organics": "^3.0.0",
"sails-hook-orm": "^4.0.3",
"sails-hook-sockets": "^3.0.0",
- "sails-postgresql": "^5.0.1"
+ "sails-postgresql": "^5.0.1",
+ "stripe": "17.3.1"
},
"devDependencies": {
"eslint": "5.16.0",
diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js
index 75d0d640437f..d14d4f8e8b43 100644
--- a/website/scripts/build-static-content.js
+++ b/website/scripts/build-static-content.js
@@ -1116,7 +1116,48 @@ module.exports = {
// Add the rituals dictionary to builtStaticContent.rituals
builtStaticContent.rituals = rituals;
},
+ //
+ // █████╗ ██████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗
+ // ██╔══██╗██╔══██╗██╔══██╗ ██║ ██║██╔══██╗██╔══██╗██╔══██╗██╔══██╗╚██╗ ██╔╝
+ // ███████║██████╔╝██████╔╝ ██║ ██║██████╔╝██████╔╝███████║██████╔╝ ╚████╔╝
+ // ██╔══██║██╔═══╝ ██╔═══╝ ██║ ██║██╔══██╗██╔══██╗██╔══██║██╔══██╗ ╚██╔╝
+ // ██║ ██║██║ ██║ ███████╗██║██████╔╝██║ ██║██║ ██║██║ ██║ ██║
+ // ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
+ //
+ async()=>{
+ let appLibrary = [];
+ // Get app library json
+ let appsJsonData = await sails.helpers.fs.readJson(path.join(topLvlRepoPath, '/server/mdm/maintainedapps/apps.json'));
+ // Then for each item in the json, build a configuration object to add to the sails.builtStaticContent.appLibrary array.
+ await sails.helpers.flow.simultaneouslyForEach(appsJsonData, async(app)=>{
+ let appInformation = {
+ identifier: app.identifier,
+ bundleIdentifier: app.bundle_identifier,
+ installerFormat: app.installer_format,
+ };
+ // Note: This method of getting information about the apps will be out of date until the JSON files in the /server/mdm/maintainedapps/testdata/ folder are updated.
+ let detailedInformationAboutThisApp = await sails.helpers.fs.readJson(path.join(topLvlRepoPath, '/server/mdm/maintainedapps/testdata/'+app.identifier+'.json'))
+ .intercept('doesNotExist', ()=>{
+ return new Error(`Could not build app library configuration from testdata folder. When attempting to read a JSON configuration file for ${app.identifier}, no file was found at ${path.join(topLvlRepoPath, '/server/mdm/maintainedapps/testdata/'+app.identifier+'.json. Was it moved?')}.`);
+ });
+ // Grab the latest information about these apps from the Homebrew API.
+ // let detailedInformationAboutThisApp = await sails.helpers.http.get(`https://formulae.brew.sh/api/cask/${app.identifier}.json`)
+ // .intercept((error)=>{
+ // return new Error(`Could not build app library configuration. When attempting to send a request to the homebrew API to get the latest information about ${app.identifier}, an error occured. Full error: ${util.inspect(error, {depth: null})}`);
+ // });
+ // let scriptToUninstallThisApp = await sails.helpers.fs.read(path.join(topLvlRepoPath, `/server/mdm/maintainedapps/testdata/scripts/${app.identifier}_uninstall.golden.sh`))
+ // .intercept('doesNotExist', ()=>{
+ // return new Error(`Could not build app library configuration from testdata folder. When attempting to read an uninstall script for ${app.identifier}, no file was found at ${path.join(topLvlRepoPath, '/server/mdm/maintainedapps/testdata/scripts/'+app.identifier+'_uninstall.golden.sh. Was it moved?')}.`);
+ // });
+ // appInformation.uninstallScript = scriptToUninstallThisApp;
+ appInformation.version = detailedInformationAboutThisApp.version.split(',')[0];
+ appInformation.description = detailedInformationAboutThisApp.desc;
+ appInformation.name = detailedInformationAboutThisApp.name[0];
+ appLibrary.push(appInformation);
+ });
+ builtStaticContent.appLibrary = appLibrary;
+ },
]);
// ██████╗ ███████╗██████╗ ██╗ █████╗ ██████╗███████╗ ███████╗ █████╗ ██╗██╗ ███████╗██████╗ ██████╗
// ██╔══██╗██╔════╝██╔══██╗██║ ██╔══██╗██╔════╝██╔════╝ ██╔════╝██╔══██╗██║██║ ██╔════╝██╔══██╗██╔════╝██╗
diff --git a/website/views/layouts/layout.ejs b/website/views/layouts/layout.ejs
index 0338df7a7bc7..1ec367716a65 100644
--- a/website/views/layouts/layout.ejs
+++ b/website/views/layouts/layout.ejs
@@ -130,7 +130,7 @@
-
@@ -189,7 +197,7 @@
<%= primaryBuyingSituation==='vm'? 'Instrument your endpoints' : 'Talk to your computers'%>
A <%= primaryBuyingSituation==='vm'? 'lightweight' : 'quick-fast' %> way to gather <%= primaryBuyingSituation==='vm'? 'patch level and custom reports across all your computing devices, even in OT and production environments' : primaryBuyingSituation==='vm'? 'deep context and custom telemetry across all your endpoints, even servers' : primaryBuyingSituation==='mdm'||primaryBuyingSituation==='eo-it'? 'compliance and inventory data across all your devices' : 'device data across all your computers' %>. Pulse check or automate anything on any platform.
-
Start with <%= primaryBuyingSituation==='mdm' ? 'IT engineering' : 'security engineering'%>
+
Start with <%= primaryBuyingSituation==='mdm' ? 'IT engineering' : 'security engineering'%>