mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Merge branch 'main' into mna-43050-load-test-ddm-vars-from-43047-part-2
This commit is contained in:
commit
0037a817ed
78 changed files with 1811 additions and 1129 deletions
|
|
@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -438,6 +439,9 @@ object ApiClient : CertificateApiClient {
|
|||
)
|
||||
}
|
||||
|
||||
// @EncodeDefault is marked @ExperimentalSerializationApi, but it has shipped in kotlinx.serialization since 1.3 (2022)
|
||||
// and is widely used and reliable in production. The opt-in only acknowledges that the API shape could change in a future version.
|
||||
@OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
|
||||
@Serializable
|
||||
data class EnrollRequest(
|
||||
@SerialName("enroll_secret")
|
||||
|
|
@ -446,6 +450,7 @@ data class EnrollRequest(
|
|||
val hardwareUUID: String,
|
||||
@SerialName("hardware_serial")
|
||||
val hardwareSerial: String,
|
||||
@EncodeDefault(EncodeDefault.Mode.ALWAYS)
|
||||
@SerialName("platform")
|
||||
val platform: String = "android",
|
||||
@SerialName("computer_name")
|
||||
|
|
|
|||
|
|
@ -100,10 +100,15 @@ class ApiClientReenrollTest {
|
|||
assertTrue("First call should succeed", firstResult.isSuccess)
|
||||
assertEquals(2, mockWebServer.requestCount) // enroll + config
|
||||
|
||||
// Verify first enrollment used the enroll secret
|
||||
// Verify first enrollment used the enroll secret and sent platform="android" on the wire
|
||||
val firstEnroll = mockWebServer.takeRequest()
|
||||
assertEquals("/api/fleet/orbit/enroll", firstEnroll.path)
|
||||
assertTrue(firstEnroll.body.readUtf8().contains("test-enroll-secret"))
|
||||
val firstEnrollBody = firstEnroll.body.readUtf8()
|
||||
assertTrue(firstEnrollBody.contains("test-enroll-secret"))
|
||||
assertTrue(
|
||||
"Expected platform=\"android\" in enroll body, got: $firstEnrollBody",
|
||||
firstEnrollBody.contains("\"platform\":\"android\""),
|
||||
)
|
||||
|
||||
// Verify first config used first-node-key
|
||||
val firstConfig = mockWebServer.takeRequest()
|
||||
|
|
|
|||
1
android/changes/43807-android-enroll-platform
Normal file
1
android/changes/43807-android-enroll-platform
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Fixed Android agent to always send the `platform` field on enrollment so the device is registered with the correct platform.
|
||||
1
changes/42885-api-only-endpoints-middleware
Normal file
1
changes/42885-api-only-endpoints-middleware
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Added new middleware (APIOnlyEndpointCheck) that enforces 403 for API-only users whose request either isn't in the API endpoint catalog or falls outside their configured per-user endpoint restrictions.
|
||||
1
changes/43142-script-package-icon-gitops
Normal file
1
changes/43142-script-package-icon-gitops
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Fixed an issue where adding a custom icon for a script-only package was not allowed in GitOps.
|
||||
|
|
@ -1935,9 +1935,10 @@ func createActivityBoundedContext(svc fleet.Service, dbConns *common_mysql.DBCon
|
|||
activityACLAdapter,
|
||||
logger,
|
||||
)
|
||||
// Create auth middleware for activity bounded context
|
||||
// Makes sure that api_only users are subject to endpoint
|
||||
// restrictions on activity routes.
|
||||
activityAuthMiddleware := func(next endpoint.Endpoint) endpoint.Endpoint {
|
||||
return auth.AuthenticatedUser(svc, next)
|
||||
return auth.AuthenticatedUser(svc, auth.APIOnlyEndpointCheck(next))
|
||||
}
|
||||
activityRoutes := activityRoutesFn(activityAuthMiddleware)
|
||||
return activitySvc, activityRoutes
|
||||
|
|
|
|||
11
ee/maintained-apps/inputs/winget/druva-insync.json
Normal file
11
ee/maintained-apps/inputs/winget/druva-insync.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "Druva inSync",
|
||||
"slug": "druva-insync/windows",
|
||||
"package_identifier": "Druva.inSync",
|
||||
"unique_identifier": "Druva inSync",
|
||||
"fuzzy_match_name": true,
|
||||
"installer_arch": "x64",
|
||||
"installer_type": "msi",
|
||||
"installer_scope": "machine",
|
||||
"default_categories": ["Productivity"]
|
||||
}
|
||||
|
|
@ -631,6 +631,13 @@
|
|||
"unique_identifier": "com.getdropbox.dropbox",
|
||||
"description": "Dropbox is a client for the Dropbox cloud storage service."
|
||||
},
|
||||
{
|
||||
"name": "Druva inSync",
|
||||
"slug": "druva-insync/windows",
|
||||
"platform": "windows",
|
||||
"unique_identifier": "Druva inSync",
|
||||
"description": "Druva inSync is a cloud-based product that protects endpoint data."
|
||||
},
|
||||
{
|
||||
"name": "Eclipse IDE",
|
||||
"slug": "eclipse-ide/darwin",
|
||||
|
|
@ -650,14 +657,14 @@
|
|||
"slug": "elgato-control-center/darwin",
|
||||
"platform": "darwin",
|
||||
"unique_identifier": "com.corsair.ControlCenter",
|
||||
"description": "Elgate Control Center is an app to control your Elgato key lights."
|
||||
"description": "Elgato Control Center is an app to control your Elgato key lights."
|
||||
},
|
||||
{
|
||||
"name": "Elgato Stream Deck",
|
||||
"slug": "elgato-stream-deck/darwin",
|
||||
"platform": "darwin",
|
||||
"unique_identifier": "com.elgato.StreamDeck",
|
||||
"description": "Elgateo Stream Deck is an app to assign keys, and then decorate and label them, on a Stream Deck."
|
||||
"description": "Elgato Stream Deck is an app to assign keys, and then decorate and label them, on a Stream Deck."
|
||||
},
|
||||
{
|
||||
"name": "Evernote",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.2026.098",
|
||||
"version": "1.2026.099",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.openai.chat';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.openai.chat' AND version_compare(bundle_short_version, '1.2026.098') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.openai.chat' AND version_compare(bundle_short_version, '1.2026.099') < 0);"
|
||||
},
|
||||
"installer_url": "https://persistent.oaistatic.com/sidekick/public/ChatGPT_Desktop_public_1.2026.098_1776453807.dmg",
|
||||
"installer_url": "https://persistent.oaistatic.com/sidekick/public/ChatGPT_Desktop_public_1.2026.099_1776706502.dmg",
|
||||
"install_script_ref": "105f16ab",
|
||||
"uninstall_script_ref": "dbaa4d2e",
|
||||
"sha256": "87883fbc5761f55114a658432c0f3a3c83edd9104bb8524ebcf04ae229dff798",
|
||||
"sha256": "f3a6d63494ff9ef8af8abf6dd3be5297db7f904ea01040b790251e6846b4ac88",
|
||||
"default_categories": [
|
||||
"Productivity"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.3109.0",
|
||||
"version": "1.3561.0",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.anthropic.claudefordesktop';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.anthropic.claudefordesktop' AND version_compare(bundle_short_version, '1.3109.0') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.anthropic.claudefordesktop' AND version_compare(bundle_short_version, '1.3561.0') < 0);"
|
||||
},
|
||||
"installer_url": "https://downloads.claude.ai/releases/darwin/universal/1.3109.0/Claude-35cbf6530e05912137624cde0f075dc7f121fa60.zip",
|
||||
"installer_url": "https://downloads.claude.ai/releases/darwin/universal/1.3561.0/Claude-fbc74be3fdc714a2c46ef1fb84f71d4e4c062930.zip",
|
||||
"install_script_ref": "d05235fc",
|
||||
"uninstall_script_ref": "4cfbec7d",
|
||||
"sha256": "f159693ebecd8a628cfe882c913a1979fc9a4b1d3bc9a218051f0f115c88bb68",
|
||||
"sha256": "b037ee8ebe3d11e7eba959179dfdc534951ae8e387e96cacda1dceeecd035b52",
|
||||
"default_categories": [
|
||||
"Developer tools"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.3109.0",
|
||||
"version": "1.3561.0",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM programs WHERE name = 'Claude' AND publisher = 'Anthropic, PBC';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Claude' AND publisher = 'Anthropic, PBC' AND version_compare(version, '1.3109.0') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Claude' AND publisher = 'Anthropic, PBC' AND version_compare(version, '1.3561.0') < 0);"
|
||||
},
|
||||
"installer_url": "https://downloads.claude.ai/releases/win32/x64/1.3109.0/Claude-35cbf6530e05912137624cde0f075dc7f121fa60.msix",
|
||||
"installer_url": "https://downloads.claude.ai/releases/win32/x64/1.3561.0/Claude-fbc74be3fdc714a2c46ef1fb84f71d4e4c062930.msix",
|
||||
"install_script_ref": "31a0b698",
|
||||
"uninstall_script_ref": "03f72055",
|
||||
"sha256": "4c664c5390a4d5b46286158f67e21102431a1c9863dc5edb2b8683ef69e1e300",
|
||||
"sha256": "726cf8552406f0a6260c5ddce9d5a6f560fb0f9098137447e636adfe752174cc",
|
||||
"default_categories": [
|
||||
"Productivity"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "3.1.15",
|
||||
"version": "3.1.17",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.todesktop.230313mzl4w4u92';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.todesktop.230313mzl4w4u92' AND version_compare(bundle_short_version, '3.1.15') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.todesktop.230313mzl4w4u92' AND version_compare(bundle_short_version, '3.1.17') < 0);"
|
||||
},
|
||||
"installer_url": "https://downloads.cursor.com/production/3a67af7b780e0bfc8d32aefa96b8ff1cb8817f88/darwin/arm64/Cursor-darwin-arm64.zip",
|
||||
"installer_url": "https://downloads.cursor.com/production/fce1e9ab7844f9ea35793da01e634aa7e50bce90/darwin/arm64/Cursor-darwin-arm64.zip",
|
||||
"install_script_ref": "5a1b1299",
|
||||
"uninstall_script_ref": "f7561d44",
|
||||
"sha256": "e0b65b57d30d94eb668151c9b44d474f7fbb9c78a2bc4ec1bdff1a537bbc21a2",
|
||||
"sha256": "2c308456eb5b834da262fe4f3cca744e179e68eb25591d0b4f83c3b742956290",
|
||||
"default_categories": [
|
||||
"Developer tools"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "3.1.15",
|
||||
"version": "3.1.17",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM programs WHERE name = 'Cursor' AND publisher = 'Anysphere';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Cursor' AND publisher = 'Anysphere' AND version_compare(version, '3.1.15') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Cursor' AND publisher = 'Anysphere' AND version_compare(version, '3.1.17') < 0);"
|
||||
},
|
||||
"installer_url": "https://downloads.cursor.com/production/3a67af7b780e0bfc8d32aefa96b8ff1cb8817f88/win32/x64/system-setup/CursorSetup-x64-3.1.15.exe",
|
||||
"installer_url": "https://downloads.cursor.com/production/fce1e9ab7844f9ea35793da01e634aa7e50bce90/win32/x64/system-setup/CursorSetup-x64-3.1.17.exe",
|
||||
"install_script_ref": "03589b5e",
|
||||
"uninstall_script_ref": "6c8096c5",
|
||||
"sha256": "d667e13c7e538e11b23569071f2a47b271494038f9ae0be6c424a30c318bc73b",
|
||||
"sha256": "14f6e7c8244175496bf8be1af05a7004d7013b6928576a0cdc350705baa8808d",
|
||||
"default_categories": [
|
||||
"Developer tools"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.0.385",
|
||||
"version": "0.0.386",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.hnc.Discord';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.hnc.Discord' AND version_compare(bundle_short_version, '0.0.385') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.hnc.Discord' AND version_compare(bundle_short_version, '0.0.386') < 0);"
|
||||
},
|
||||
"installer_url": "https://dl.discordapp.net/apps/osx/0.0.385/Discord.dmg",
|
||||
"installer_url": "https://dl.discordapp.net/apps/osx/0.0.386/Discord.dmg",
|
||||
"install_script_ref": "dac37ced",
|
||||
"uninstall_script_ref": "8ed76f08",
|
||||
"sha256": "c5b69b1d27abd56d610cdb6173b4575ec84971aa1bbf91875e992c85bad3cff7",
|
||||
"sha256": "a01cf51f325bb95f4f9dd2ca96f9c1a03f547ac00c4e2034164835508cd8a04d",
|
||||
"default_categories": [
|
||||
"Communication"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.9233",
|
||||
"version": "1.0.9234",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM programs WHERE name = 'Discord' AND publisher = 'Discord Inc.';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Discord' AND publisher = 'Discord Inc.' AND version_compare(version, '1.0.9233') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Discord' AND publisher = 'Discord Inc.' AND version_compare(version, '1.0.9234') < 0);"
|
||||
},
|
||||
"installer_url": "https://stable.dl2.discordapp.net/distro/app/stable/win/x64/1.0.9233/DiscordSetup.exe",
|
||||
"installer_url": "https://stable.dl2.discordapp.net/distro/app/stable/win/x64/1.0.9234/DiscordSetup.exe",
|
||||
"install_script_ref": "fd04e860",
|
||||
"uninstall_script_ref": "73fc6eff",
|
||||
"sha256": "eaba7863d5a7ed017a865f991267583e0768914aba99590874da0980832e45a3",
|
||||
"sha256": "3f0306caceaa9594c608b39dbee8ceaba4422abc51052ee21deda7280eee9173",
|
||||
"default_categories": [
|
||||
"Communication"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "4.69.0",
|
||||
"version": "4.70.0",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.electron.dockerdesktop';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.electron.dockerdesktop' AND version_compare(bundle_short_version, '4.69.0') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.electron.dockerdesktop' AND version_compare(bundle_short_version, '4.70.0') < 0);"
|
||||
},
|
||||
"installer_url": "https://desktop.docker.com/mac/main/arm64/224084/Docker.dmg",
|
||||
"installer_url": "https://desktop.docker.com/mac/main/arm64/224270/Docker.dmg",
|
||||
"install_script_ref": "091b8a34",
|
||||
"uninstall_script_ref": "c31a36e7",
|
||||
"sha256": "8e4638f2d427a4fb9d0f86f21d0975a344a164228d20fc22a620750b7943d644",
|
||||
"sha256": "38a19ac039dee38e0e445118da34962039ef1601b0656dc4d138592efda2caba",
|
||||
"default_categories": [
|
||||
"Developer tools"
|
||||
]
|
||||
|
|
|
|||
23
ee/maintained-apps/outputs/druva-insync/windows.json
Normal file
23
ee/maintained-apps/outputs/druva-insync/windows.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "7.5.7",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM programs WHERE name LIKE 'Druva inSync %' AND publisher = 'Druva Technologies Pte. Ltd.';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'Druva inSync %' AND publisher = 'Druva Technologies Pte. Ltd.' AND version_compare(version, '7.5.7') < 0);"
|
||||
},
|
||||
"installer_url": "https://downloads.druva.com/downloads/inSync/Windows/7.5.7/inSync7.5.7r110923.msi",
|
||||
"install_script_ref": "8959087b",
|
||||
"uninstall_script_ref": "b98f2558",
|
||||
"sha256": "44e7295a0efba9e386e5dc24fa1e024e84b14d5e70f290ddd4389da5c5e5c96e",
|
||||
"default_categories": [
|
||||
"Productivity"
|
||||
],
|
||||
"upgrade_code": "{99ADCB07-6ABD-4A1D-AFF6-03649753BAFF}"
|
||||
}
|
||||
],
|
||||
"refs": {
|
||||
"8959087b": "$logFile = \"${env:TEMP}/fleet-install-software.log\"\n\ntry {\n\n$installProcess = Start-Process msiexec.exe `\n -ArgumentList \"/quiet /norestart /lv ${logFile} /i `\"${env:INSTALLER_PATH}`\"\" `\n -PassThru -Verb RunAs -Wait\n\nGet-Content $logFile -Tail 500\n\nExit $installProcess.ExitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n",
|
||||
"b98f2558": "# Fleet uninstalls app by finding all related product codes for the specified upgrade code\n$inst = New-Object -ComObject \"WindowsInstaller.Installer\"\n$timeoutSeconds = 300 # 5 minute timeout per product\n\nforeach ($product_code in $inst.RelatedProducts('{99ADCB07-6ABD-4A1D-AFF6-03649753BAFF}')) {\n $process = Start-Process msiexec -ArgumentList @(\"/quiet\", \"/x\", $product_code, \"/norestart\") -PassThru\n \n # Wait for process with timeout\n $completed = $process.WaitForExit($timeoutSeconds * 1000)\n \n if (-not $completed) {\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n Exit 1603 # ERROR_UNINSTALL_FAILURE\n }\n \n # If the uninstall failed, bail\n if ($process.ExitCode -ne 0) {\n Write-Output \"Uninstall for $($product_code) exited $($process.ExitCode)\"\n Exit $process.ExitCode\n }\n}\n\n# All uninstalls succeeded; exit success\nExit 0\n"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.344.0",
|
||||
"version": "0.345.1",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.loom.desktop';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.loom.desktop' AND version_compare(bundle_short_version, '0.344.0') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.loom.desktop' AND version_compare(bundle_short_version, '0.345.1') < 0);"
|
||||
},
|
||||
"installer_url": "https://packages.loom.com/desktop-packages/Loom-0.344.0-arm64.dmg",
|
||||
"installer_url": "https://packages.loom.com/desktop-packages/Loom-0.345.1-arm64.dmg",
|
||||
"install_script_ref": "8185f7a5",
|
||||
"uninstall_script_ref": "393b959f",
|
||||
"sha256": "c5c561c45ce97d6bfaf344b0f12417165345e69a78dee3cfd02705e3c56b929d",
|
||||
"sha256": "dc837812b1e3255575e44e72b0a84f34b0f6500e91af32b234eb65f8fcd115ad",
|
||||
"default_categories": [
|
||||
"Productivity"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'dev.kdrag0n.MacVirt';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'dev.kdrag0n.MacVirt' AND version_compare(bundle_short_version, '2.1.0') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'dev.kdrag0n.MacVirt' AND version_compare(bundle_short_version, '2.1.1') < 0);"
|
||||
},
|
||||
"installer_url": "https://cdn-updates.orbstack.dev/arm64/OrbStack_v2.1.0_19993_arm64.dmg",
|
||||
"installer_url": "https://cdn-updates.orbstack.dev/arm64/OrbStack_v2.1.1_20026_arm64.dmg",
|
||||
"install_script_ref": "3d018c85",
|
||||
"uninstall_script_ref": "401d288b",
|
||||
"sha256": "65dcb7ac3f53022eed732d962ce97af000a10e8a3de77fa4ed47645b7591f4c3",
|
||||
"sha256": "18a41f759958e1fa0951696b820b5478d3ed7353f8ca486fe9d026a1a7d97207",
|
||||
"default_categories": [
|
||||
"Developer tools"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.232.2",
|
||||
"version": "0.232.3",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'dev.zed.Zed';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'dev.zed.Zed' AND version_compare(bundle_short_version, '0.232.2') < 0);"
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'dev.zed.Zed' AND version_compare(bundle_short_version, '0.232.3') < 0);"
|
||||
},
|
||||
"installer_url": "https://zed.dev/api/releases/stable/0.232.2/Zed-aarch64.dmg",
|
||||
"installer_url": "https://zed.dev/api/releases/stable/0.232.3/Zed-aarch64.dmg",
|
||||
"install_script_ref": "b0e5ef67",
|
||||
"uninstall_script_ref": "a96df5e9",
|
||||
"sha256": "82717ddea4c03ad033a9c99d1440d1b71d6f6141b02eedd1aa1cb8eb3dcb938f",
|
||||
"sha256": "2f6ca2b175772e57c1d5b2893f8c8556e16581341f9568ee0ce4f84f5128b488",
|
||||
"default_categories": [
|
||||
"Developer tools"
|
||||
]
|
||||
|
|
|
|||
14
frontend/pages/SoftwarePage/components/icons/DruvaInSync.tsx
Normal file
14
frontend/pages/SoftwarePage/components/icons/DruvaInSync.tsx
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -10,6 +10,7 @@ import BetterDisplay from "./BetterDisplay";
|
|||
import Charles from "./Charles";
|
||||
import ConnectFonts from "./ConnectFonts";
|
||||
import CrashPlan from "./CrashPlan";
|
||||
import DruvaInSync from "./DruvaInSync";
|
||||
import GoogleCredentialProviderForWindows from "./GoogleCredentialProviderForWindows";
|
||||
import Iina from "./Iina";
|
||||
import Kitty from "./Kitty";
|
||||
|
|
@ -316,6 +317,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
|
|||
crashplan: CrashPlan,
|
||||
"google credential provider for windows": GoogleCredentialProviderForWindows,
|
||||
iina: Iina,
|
||||
insyncclient: DruvaInSync,
|
||||
kitty: Kitty,
|
||||
krita: Krita,
|
||||
lastpass: LastPass,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,13 @@
|
|||
# youtubeVideoUrl:
|
||||
#. productCategories: [Observability, Software management, Device management]
|
||||
#
|
||||
# TODO figure out what to do with this other quote that both Austin and Dre :+1:'d: "Fleet lets us be more actionable with fewer people. It helps us to filter out the noise better than we could with the other big name products we replaced."
|
||||
# TODO figure out what to do with this other quote that both Austin and Dre :+1:'d: "Fleet lets us be more actionable with fewer people. It helps us to filter out the noise better than we could with the other big name products we replaced."
|
||||
- quote: I think it is key that people understand the leverage they have with AI if everything is 'code'. In the AI age, clickops will not prevail!
|
||||
quoteAuthorName: Thomas Lübker
|
||||
quoteAuthorProfileImageFilename: testimonial-author-thomas-luebker-48x48@2x.png
|
||||
quoteLinkUrl: https://www.linkedin.com/in/tluebker/?skipRedirect=true
|
||||
quoteAuthorJobTitle: Helping Apple devices take off in the enterprise
|
||||
productCategories: [Device management]
|
||||
- quote: Fleet is incredibly easy to deploy and perfect for lean IT teams. It's everything we need in a device management platform, simple, efficient, and powerful.
|
||||
quoteAuthorName: Chayce O'Neal
|
||||
quoteAuthorProfileImageFilename: testimonial-author-chayce-oneal-100x100@2x.png
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ If you find a piece of collateral that is duplicate to one listed below, please
|
|||
|
||||
## 🟢 Sales & enablement
|
||||
|
||||
Decks, battle cards, one-pagers, comparisons, and tools used in the sales cycle — from first touch through close. Audience: AEs, SDRs, SEs, and customer success.
|
||||
Decks, battle cards, one-pagers, comparisons, and tools used in the sales cycle - from first touch through close. Audience: AEs, SDRs, SEs, and customer success.
|
||||
|
||||
### Pitch & presentation
|
||||
|
||||
|
|
@ -46,14 +46,14 @@ Decks, battle cards, one-pagers, comparisons, and tools used in the sales cycle
|
|||
| [Fleet vs. Jamf](https://fleetdm.com/compare/jamf) | Jamf | Direct comparison of Fleet and Jamf Pro product offerings. | <nobr>Jan‑10‑2026</nobr> |
|
||||
| [MDM Providers Compared: Fleet vs Workspace ONE](https://fleetdm.com/articles/mdm-providers-compared) | Workspace ONE | Feature-by-feature comparison of Fleet and VMware Workspace ONE. | <nobr>Feb‑27‑2026</nobr> |
|
||||
| [A comparative look at VMware Workspace ONE and Fleet](https://fleetdm.com/articles/comparative-look-at-ws1-and-fleet) | Workspace ONE | Side-by-side comparison with VMware Workspace ONE. | <nobr>Feb‑01‑2024</nobr> |
|
||||
| [A comparative look at VMware Workspace ONE and Fleet (announcement)](https://fleetdm.com/announcements/a-comparative-look-at-vmware-workspace-one-and-fleet) | Workspace ONE | Original announcement post — side-by-side comparison with VMware Workspace ONE. | <nobr>Feb‑01‑2024</nobr> |
|
||||
| [Apple Push Notification Service: How APNs Works in MDM](https://fleetdm.com/articles/apple-push-notification-service-apns-mdm) | — | How APNs enables MDM communication with Apple devices. | <nobr>Mar‑09‑2026</nobr> |
|
||||
| [Battle card — Workspace ONE](https://docs.google.com/document/d/1dV9zooPnCnHa0UfZqXKWAi6Zmye2jfqRIr_UnX70Sq4/edit) | Workspace ONE | Strategic talking points for VMware competitive scenarios. | <nobr>Sep‑12‑2023</nobr> |
|
||||
| [A comparative look at VMware Workspace ONE and Fleet (announcement)](https://fleetdm.com/announcements/a-comparative-look-at-vmware-workspace-one-and-fleet) | Workspace ONE | Original announcement post - side-by-side comparison with VMware Workspace ONE. | <nobr>Feb‑01‑2024</nobr> |
|
||||
| [Apple Push Notification Service: How APNs Works in MDM](https://fleetdm.com/articles/apple-push-notification-service-apns-mdm) | - | How APNs enables MDM communication with Apple devices. | <nobr>Mar‑09‑2026</nobr> |
|
||||
| [Battle card - Workspace ONE](https://docs.google.com/document/d/1dV9zooPnCnHa0UfZqXKWAi6Zmye2jfqRIr_UnX70Sq4/edit) | Workspace ONE | Strategic talking points for VMware competitive scenarios. | <nobr>Sep‑12‑2023</nobr> |
|
||||
| [Jamf vs. Fleet terminology](https://docs.google.com/document/d/1RKojfpUMUiITPce5O7znYmxdQR9U6f5sVnVzQpPpp8Y/edit) | Jamf | Concept mapping between Jamf and Fleet for migration conversations. | <nobr>Sep‑09‑2025</nobr> |
|
||||
| [Death to Extension Attributes](https://github.com/allenhouchins/death-to-extension-attributes) | Jamf | Compares Fleet queries to Jamf extension attributes for technical audiences. | <nobr>Jun‑15‑2023</nobr> |
|
||||
| [ROI Spreadsheet](https://docs.google.com/spreadsheets/d/14Cfj77ynOB6z4pmb9DD7HNRGo0kcKJuEIifgnz-YO50/edit) | — | Financial value justification and ROI calculator for deals. | <nobr>Aug‑14‑2023</nobr> |
|
||||
| [Product Roadmap](https://docs.google.com/document/d/16si8Nkh0F25opUMpZYm6XwU8LEhGd7H4eOzPKFz8jGw/edit) | — | Future development vision for open roadmap conversations. | <nobr>Jan‑20‑2021</nobr> |
|
||||
| [Gartner IT Symposium 2025 Presentation](https://drive.google.com/file/d/1HK1QXA2kOCeOoOG1E0-tk7-xwBhQ2QMa/view) | — | Outcomes and value achieved via GitOps. (by Allen Houchins) | <nobr>Oct‑21‑2024</nobr> |
|
||||
| [ROI Spreadsheet](https://docs.google.com/spreadsheets/d/14Cfj77ynOB6z4pmb9DD7HNRGo0kcKJuEIifgnz-YO50/edit) | - | Financial value justification and ROI calculator for deals. | <nobr>Aug‑14‑2023</nobr> |
|
||||
| [Product Roadmap](https://docs.google.com/document/d/16si8Nkh0F25opUMpZYm6XwU8LEhGd7H4eOzPKFz8jGw/edit) | - | Future development vision for open roadmap conversations. | <nobr>Jan‑20‑2021</nobr> |
|
||||
| [Gartner IT Symposium 2025 Presentation](https://drive.google.com/file/d/1HK1QXA2kOCeOoOG1E0-tk7-xwBhQ2QMa/view) | - | Outcomes and value achieved via GitOps. (by Allen Houchins) | <nobr>Oct‑21‑2024</nobr> |
|
||||
|
||||
|
||||
## Webinar Recordings
|
||||
|
|
@ -122,7 +122,7 @@ Why Linux is now a critical enterprise platform and how to manage and secure it
|
|||
| <nobr>5 of 6</nobr> | [Protecting the Linux device: remote wipe, USB & sudo](https://fleetdm.com/articles/protecting-the-linux-device-remote-wipe-usb-sudo) | Ashish Kuthiala | <nobr>2026‑03‑10</nobr> |
|
||||
| <nobr>6 of 6</nobr> | [Owning your Linux destiny with open source](https://fleetdm.com/articles/owning-your-linux-destiny-with-open-source) | Ashish Kuthiala | <nobr>2026‑03‑06</nobr> |
|
||||
|
||||
#### 📚 OpenClaw — Governing Autonomous AI Agents (3-part series)
|
||||
#### 📚 OpenClaw - Governing Autonomous AI Agents (3-part series)
|
||||
|
||||
Security risks of autonomous AI agents running on managed endpoints, and what IT leaders can do about them.
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ Managing the Santa binary authorization tool using Fleet's GitOps workflow, with
|
|||
|
||||
#### 📚 Tales from Fleet Security (8-part series)
|
||||
|
||||
How Fleet's own security team secures the company — a window into Fleet's internal security practices useful for security-minded evaluators.
|
||||
How Fleet's own security team secures the company - a window into Fleet's internal security practices useful for security-minded evaluators.
|
||||
|
||||
| Part | Article | Author | Date |
|
||||
| --- | --- | --- | --- |
|
||||
|
|
@ -168,7 +168,7 @@ How Fleet's own security team secures the company — a window into Fleet's inte
|
|||
| [Mac device security: Apple's native protections and third-party tools](https://fleetdm.com/articles/mac-device-security) | How Mac endpoint security works, from Apple's protections (SIP, XProtect, TCC) to third-party tools. | Brock Walters | <nobr>2026‑02‑25</nobr> |
|
||||
| [How to manage company laptops: a complete guide](https://fleetdm.com/articles/how-to-manage-company-laptops-a-complete-guide) | A complete guide to managing company laptops across macOS, Windows, and Linux at scale. | Brock Walters | <nobr>2026‑03‑07</nobr> |
|
||||
| [Migrating Intune policies to Fleet with the CSP converter](https://fleetdm.com/articles/migrating-intune-policies-to-fleet-csp-converter) | How to translate Intune configuration policies to Fleet using the CSP converter tool. | Mitch Francese | <nobr>2026‑03‑06</nobr> |
|
||||
| [Why work laptops don't work on plane wifi](https://fleetdm.com/articles/why-work-laptops-dont-work-on-plane-wifi) | VPNs, DNS filters, and captive portals — and why IT teams should fix this by default. | Mike McNeil | <nobr>2026‑02‑13</nobr> |
|
||||
| [Why work laptops don't work on plane wifi](https://fleetdm.com/articles/why-work-laptops-dont-work-on-plane-wifi) | VPNs, DNS filters, and captive portals - and why IT teams should fix this by default. | Mike McNeil | <nobr>2026‑02‑13</nobr> |
|
||||
| [The GitOps idea](https://fleetdm.com/articles/the-gitops-idea) | An introduction to GitOps concepts and components. | Brock Walters | <nobr>2026‑02‑04</nobr> |
|
||||
| [iPad MDM: a complete guide](https://fleetdm.com/articles/ipad-mdm-a-complete-guide) | A complete guide to iPad MDM, covering deployment models, enrollment methods, and enterprise management at scale. | Brock Walters | <nobr>2026‑01‑13</nobr> |
|
||||
| [What is Apple Business Manager? A complete guide](https://fleetdm.com/articles/what-is-apple-business-manager-a-complete-guide) | How ABM works with MDM for automated enrollment and app management. | Brock Walters | <nobr>2026‑01‑12</nobr> |
|
||||
|
|
@ -180,7 +180,7 @@ How Fleet's own security team secures the company — a window into Fleet's inte
|
|||
| [Free migration from Jamf to Fleet](https://fleetdm.com/articles/free-migration-from-jamf-to-fleet) | Switch from Jamf to Fleet for free using Apple's new macOS migration flow. | Alex Mitchell | <nobr>2025‑10‑06</nobr> |
|
||||
| [Device enrollment lifecycle](https://fleetdm.com/articles/device-enrollment-lifecycle) | The five stages of a device enrollment lifecycle. | Brock Walters | <nobr>2025‑09‑19</nobr> |
|
||||
| [One agent, fewer tools, fewer gaps](https://fleetdm.com/articles/one-agent-fewer-tools-fewer-gaps) | Managing devices and vulnerabilities shouldn't mean installing two agents or paying for two platforms. | Harrison Ravazzolo | <nobr>2025‑06‑23</nobr> |
|
||||
| [I work in operations. I deployed Fleet in minutes.](https://fleetdm.com/articles/i-work-in-operations-i-deployed-fleet-in-minutes) | Self-hosting Fleet is easier than you think — even for non-technical roles. | Nate Holliday | <nobr>2025‑06‑05</nobr> |
|
||||
| [I work in operations. I deployed Fleet in minutes.](https://fleetdm.com/articles/i-work-in-operations-i-deployed-fleet-in-minutes) | Self-hosting Fleet is easier than you think - even for non-technical roles. | Nate Holliday | <nobr>2025‑06‑05</nobr> |
|
||||
| [Not everything runs in Kubernetes](https://fleetdm.com/articles/not-everything-runs-in-kubernete) | Why Fleet goes beyond Kubernetes to manage real-world infrastructure. | Zach Wasserman | <nobr>2025‑05‑27</nobr> |
|
||||
| [Work may be watching, but it might not be as bad as you think](https://fleetdm.com/articles/work-may-be-watching-but-it-might-not-be-as-bad-as-you-think) | A clear-eyed look at employee monitoring, what IT teams actually collect, and how to set expectations. | Mike Thomas | <nobr>2021‑10‑22</nobr> |
|
||||
| [eBPF & the future of osquery on Linux](https://fleetdm.com/articles/ebpf-the-future-of-osquery-on-linux) | How eBPF extends osquery's visibility on Linux and what it means for endpoint security. | Zach Wasserman | <nobr>2021‑01‑25</nobr> |
|
||||
|
|
@ -220,7 +220,7 @@ How-to guides, step-by-step walkthroughs, and reference material for IT admins a
|
|||
|
||||
#### Platform setup
|
||||
|
||||
Turn on MDM for each platform — start here if Fleet MDM is not yet configured.
|
||||
Turn on MDM for each platform - start here if Fleet MDM is not yet configured.
|
||||
|
||||
| Asset | Description | Author | Date updated |
|
||||
| --- | --- | --- | --- |
|
||||
|
|
@ -233,7 +233,7 @@ Turn on MDM for each platform — start here if Fleet MDM is not yet configured.
|
|||
|
||||
#### Enrolling devices
|
||||
|
||||
Add hosts to Fleet — corporate-owned and personal (BYOD) devices.
|
||||
Add hosts to Fleet - corporate-owned and personal (BYOD) devices.
|
||||
|
||||
| Asset | Description | Author | Date updated |
|
||||
| --- | --- | --- | --- |
|
||||
|
|
@ -282,7 +282,7 @@ Concepts, rationale, and first-time setup for teams adopting GitOps with Fleet.
|
|||
|
||||
#### Managing configuration with GitOps
|
||||
|
||||
Day-to-day workflows for managing Fleet resources — software, profiles, policies, and packages — through GitOps.
|
||||
Day-to-day workflows for managing Fleet resources - software, profiles, policies, and packages - through GitOps.
|
||||
|
||||
| Asset | Description | Author | Date updated |
|
||||
| --- | --- | --- | --- |
|
||||
|
|
@ -381,7 +381,7 @@ Command-line tools for managing Fleet configurations, running queries, and deplo
|
|||
| [Osquery: Consider joining against the users table](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table) | Tips for enriching osquery query results by joining against the users table for better context. | Zach Wasserman | <nobr>2021‑05‑06</nobr> |
|
||||
| [Import and export queries in Fleet](https://fleetdm.com/guides/import-and-export-queries-in-fleet) | How to import and export saved queries in Fleet for sharing or backup purposes. | Noah Talerman | <nobr>2021‑02‑16</nobr> |
|
||||
| [Generate process trees with osquery](https://fleetdm.com/guides/generate-process-trees-with-osquery) | How to use osquery in Fleet to generate parent-child process trees for forensic investigation. | Zach Wasserman | <nobr>2020‑03‑17</nobr> |
|
||||
| [Fleet quick tips — identify systems where the ProcDump EULA has been accepted](https://fleetdm.com/guides/fleet-quick-tips-querying-procdump-eula-has-been-accepted) | Use Fleet to query which systems have accepted the ProcDump EULA, a potential indicator of investigation activity. | Mike Thomas | <nobr>2021‑05‑11</nobr> |
|
||||
| [Fleet quick tips - identify systems where the ProcDump EULA has been accepted](https://fleetdm.com/guides/fleet-quick-tips-querying-procdump-eula-has-been-accepted) | Use Fleet to query which systems have accepted the ProcDump EULA, a potential indicator of investigation activity. | Mike Thomas | <nobr>2021‑05‑11</nobr> |
|
||||
|
||||
### Integrations & automations
|
||||
|
||||
|
|
@ -408,7 +408,7 @@ Command-line tools for managing Fleet configurations, running queries, and deplo
|
|||
| [Monitor DNS traffic on macOS](https://fleetdm.com/articles/monitor-dns-traffic-on-macos) | How to use Fleet and osquery to monitor DNS queries on macOS endpoints. | Victor Lyuboslavsky | <nobr>2026‑03‑08</nobr> |
|
||||
| [Deploying custom osquery extensions in Fleet](https://fleetdm.com/articles/deploying-custom-osquery-extensions-in-fleet) | How to deploy custom osquery extensions to managed devices using Fleet. | Allen Houchins | <nobr>2026‑03‑05</nobr> |
|
||||
| [Deploying custom osquery extensions in Fleet: A step-by-step guide](https://fleetdm.com/articles/deploying-custom-osquery-extensions-in-fleet-a-step-by-step-guide) | Step-by-step walkthrough for deploying custom osquery extensions across your Fleet-managed devices. | Allen Houchins | <nobr>2026‑03‑06</nobr> |
|
||||
| [What are Fleet policies?](https://fleetdm.com/articles/what-are-fleet-policies) | An introduction to Fleet policies — what they are, how they work, and how to use them for compliance and automation. | Andrew Baker | <nobr>2022‑05‑20</nobr> |
|
||||
| [What are Fleet policies?](https://fleetdm.com/articles/what-are-fleet-policies) | An introduction to Fleet policies - what they are, how they work, and how to use them for compliance and automation. | Andrew Baker | <nobr>2022‑05‑20</nobr> |
|
||||
| [End-user self remediation: empower your employees to fix security issues](https://fleetdm.com/articles/end-user-self-remediation) | How Fleet Desktop enables end users to self-remediate failing policies without IT intervention. | Chris McGillicuddy | <nobr>2022‑12‑15</nobr> |
|
||||
| [Labels in Fleet](https://fleetdm.com/guides/managing-labels-in-fleet) | Learn how to use labels in Fleet to organize and target hosts for policies, queries, and software. | Noah Talerman | <nobr>2025‑10‑24</nobr> |
|
||||
| [View and manage MDM configuration profile status](https://fleetdm.com/guides/configuration-profile-status) | Learn how to view and manage the status of MDM configuration profiles across your Fleet-enrolled devices. | Gabe Hernandez | <nobr>2025‑05‑26</nobr> |
|
||||
|
|
@ -482,21 +482,33 @@ ExpedITioners is Fleet's podcast, hosted by Zach Wasserman. Episodes feature gue
|
|||
|
||||
| Episode | Guest & role | Description | Date |
|
||||
| --- | --- | --- | --- |
|
||||
| [Huxley Barbee: The modern divergence of environments and security methodologies](https://expeditioners.podbean.com/e/huxley-barbee-the-modern-divergence-of-environments-and-security-methodologies/) | Huxley Barbee — Security Evangelist at RunZero; organizer of BSides NYC | The modern divergence of environments and security methodologies, improving the industry for newcomers, and comprehensive security tooling. | <nobr>2024‑01‑30</nobr> |
|
||||
| [Marcus Ransom: The positive future of collaboration between vendors and Apple for enterprise](https://expeditioners.podbean.com/e/marcus-ransom-the-positive-future-of-collaboration-between-vendors-and-apple-for-enterprise/) | Marcus Ransom — Sales Engineer at Jamf; co-host of the Mac Admins Podcast | The exciting future of Apple for enterprise, vendor collaboration, and the MacAdmin community that supports it. | <nobr>2023‑12‑11</nobr> |
|
||||
| [Jeff Chao: Configuration as code for efficiency and automation](https://expeditioners.podbean.com/e/jeff-chao-configuration-as-code-for-efficiency-and-automation/) | Jeff Chao — Co-Founder & CTO at Abbey Labs | Implementing configuration as code for access management, efficiency, and automation in modern engineering teams. | <nobr>2023‑11‑15</nobr> |
|
||||
| [Charles Edge: The past, present, and future of all things computing and device management](https://expeditioners.podbean.com/e/charles-edge-the-past-present-and-future-of-all-things-computing-and-device-management/) | Charles Edge — "Old School Mac Guy"; host of the MacAdmins Podcast | The history and future of computing and device management, and what the MacAdmin community can expect next. | <nobr>2023‑10‑23</nobr> |
|
||||
| [John Reynolds: Rehumanizing interactions between IT and end users](https://expeditioners.podbean.com/e/john-reynolds-rehumanizing-interactions-between-it-and-end-users/) | John Reynolds — IT professional and community advocate | How IT teams can rehumanize their interactions with end users and build better workplace technology experiences. | <nobr>2023‑09‑21</nobr> |
|
||||
| [Rich Trouton: Declarative Device Management and a promising future for Mac Admins](https://expeditioners.podbean.com/e/rich-trouton-declarative-device-management-and-a-promising-future-for-mac-admins/) | Rich Trouton — IT Technology Services Expert at SAP; author of Der Flounder blog | Apple's Declarative Device Management (DDM) framework and what it means for the future of Mac administration. | <nobr>2023‑08‑31</nobr> |
|
||||
| [Niels Hofmans: Threat modeling, open-source collaboration, and bug bounties](https://expeditioners.podbean.com/e/niels-hofmans-threat-modeling-open-source-collaboration-and-bug-bounties/) | Niels Hofmans — Head of Security at Intigriti | Threat modeling methodologies, open-source security collaboration, and the bug bounty ecosystem across 90,000+ security researchers. | <nobr>2023‑08‑18</nobr> |
|
||||
| [Bradley Chambers: The bright future and golden era of macOS](https://expeditioners.podbean.com/e/bradley-chambers-the-bright-future-and-golden-era-of-macos/) | Bradley Chambers — Enterprise technology writer for 9to5Mac; author of Apple @ Work | Why macOS is entering a golden era for enterprise, and how Apple's platform strategy is reshaping workplace IT. | <nobr>2023‑07‑14</nobr> |
|
||||
| [Mat X — Organizing community insights for industry growth with MacAdmin tools](https://expeditioners.podbean.com/e/mat-x-organizing-community-insights-for-industry-growth-with-macadmin-tools/) | Mat X — Storage, workflow, and broadcast systems engineer in film & VFX | How the MacAdmin community can better organize and share knowledge to accelerate industry growth. | <nobr>2022‑11‑03</nobr> |
|
||||
| [Whitney Champion — Scaling infrastructure automation with open source tools](https://expeditioners.podbean.com/e/ep-6-whitney-champion-scaling-infrastructure-automation-with-open-source-tools/) | Whitney Champion — Infrastructure and open-source automation engineer | Scaling infrastructure automation using open source tools and the culture of contributing back to the community. | <nobr>2022‑09‑23</nobr> |
|
||||
| [Jesse Peterson — Open source communities for better MDM and career growth](https://expeditioners.podbean.com/e/jesse-peterson-open-source-communities-for-better-mdm-and-career-growth/) | Jesse Peterson — Client Platform Engineer at Meta; creator of NanoMDM; contributor to MicroMDM | How open-source communities drive better MDM tooling and create career growth opportunities for contributors. | <nobr>2022‑09‑01</nobr> |
|
||||
| [Nick Anderson — Endpoint security for osquery](https://expeditioners.podbean.com/e/ep-4-nick-anderson-endpoint-security-for-osquery/) | Nick Anderson — Security Engineer at Meta; osquery Technical Steering Committee member | How osquery enables robust endpoint security and detection at scale in large enterprise environments. | <nobr>2022‑08‑12</nobr> |
|
||||
| [Chris Long — From osquery skeptic to believer](https://expeditioners.podbean.com/e/ep-3-chris-long-from-osquery-sceptic-to-believer/) | Chris Long — Staff Security Engineer at Material Security; creator of Detection Lab | The journey from skepticism to conviction on osquery, and how Detection Lab accelerates security team onboarding. | <nobr>2022‑07‑21</nobr> |
|
||||
| [Prima Virani — Improving endpoint monitoring and visibility with osquery](https://expeditioners.podbean.com/e/prima-virani-improving-endpoint-monitoring-and-visibility-with-osquery/) | Prima Virani — Detection & Response Engineering Lead at Twilio | Using osquery to improve endpoint monitoring, detection, and security visibility across a distributed fleet. | <nobr>2022‑06‑28</nobr> |
|
||||
| [Mike Arpaia — The story behind the creation of osquery](https://expeditioners.podbean.com/e/ep-1-mike-arpaia-the-story-behind-the-creation-of-osquery/) | Mike Arpaia — Co-creator of osquery; Partner at Moonfire Ventures | The origin story of osquery, the philosophy behind its design, and its lasting impact on endpoint security and observability. | <nobr>2022‑06‑02</nobr> |
|
||||
| [Huxley Barbee: The modern divergence of environments and security methodologies](https://expeditioners.podbean.com/e/huxley-barbee-the-modern-divergence-of-environments-and-security-methodologies/) | Huxley Barbee - Security Evangelist at RunZero; organizer of BSides NYC | The modern divergence of environments and security methodologies, improving the industry for newcomers, and comprehensive security tooling. | <nobr>2024‑01‑30</nobr> |
|
||||
| [Marcus Ransom: The positive future of collaboration between vendors and Apple for enterprise](https://expeditioners.podbean.com/e/marcus-ransom-the-positive-future-of-collaboration-between-vendors-and-apple-for-enterprise/) | Marcus Ransom - Sales Engineer at Jamf; co-host of the Mac Admins Podcast | The exciting future of Apple for enterprise, vendor collaboration, and the MacAdmin community that supports it. | <nobr>2023‑12‑11</nobr> |
|
||||
| [Jeff Chao: Configuration as code for efficiency and automation](https://expeditioners.podbean.com/e/jeff-chao-configuration-as-code-for-efficiency-and-automation/) | Jeff Chao - Co-Founder & CTO at Abbey Labs | Implementing configuration as code for access management, efficiency, and automation in modern engineering teams. | <nobr>2023‑11‑15</nobr> |
|
||||
| [Charles Edge: The past, present, and future of all things computing and device management](https://expeditioners.podbean.com/e/charles-edge-the-past-present-and-future-of-all-things-computing-and-device-management/) | Charles Edge - "Old School Mac Guy"; host of the MacAdmins Podcast | The history and future of computing and device management, and what the MacAdmin community can expect next. | <nobr>2023‑10‑23</nobr> |
|
||||
| [John Reynolds: Rehumanizing interactions between IT and end users](https://expeditioners.podbean.com/e/john-reynolds-rehumanizing-interactions-between-it-and-end-users/) | John Reynolds - IT professional and community advocate | How IT teams can rehumanize their interactions with end users and build better workplace technology experiences. | <nobr>2023‑09‑21</nobr> |
|
||||
| [Rich Trouton: Declarative Device Management and a promising future for Mac Admins](https://expeditioners.podbean.com/e/rich-trouton-declarative-device-management-and-a-promising-future-for-mac-admins/) | Rich Trouton - IT Technology Services Expert at SAP; author of Der Flounder blog | Apple's Declarative Device Management (DDM) framework and what it means for the future of Mac administration. | <nobr>2023‑08‑31</nobr> |
|
||||
| [Niels Hofmans: Threat modeling, open-source collaboration, and bug bounties](https://expeditioners.podbean.com/e/niels-hofmans-threat-modeling-open-source-collaboration-and-bug-bounties/) | Niels Hofmans - Head of Security at Intigriti | Threat modeling methodologies, open-source security collaboration, and the bug bounty ecosystem across 90,000+ security researchers. | <nobr>2023‑08‑18</nobr> |
|
||||
| [Bradley Chambers: The bright future and golden era of macOS](https://expeditioners.podbean.com/e/bradley-chambers-the-bright-future-and-golden-era-of-macos/) | Bradley Chambers - Enterprise technology writer for 9to5Mac; author of Apple @ Work | Why macOS is entering a golden era for enterprise, and how Apple's platform strategy is reshaping workplace IT. | <nobr>2023‑07‑14</nobr> |
|
||||
| [Mat X - Organizing community insights for industry growth with MacAdmin tools](https://expeditioners.podbean.com/e/mat-x-organizing-community-insights-for-industry-growth-with-macadmin-tools/) | Mat X - Storage, workflow, and broadcast systems engineer in film & VFX | How the MacAdmin community can better organize and share knowledge to accelerate industry growth. | <nobr>2022‑11‑03</nobr> |
|
||||
| [Whitney Champion - Scaling infrastructure automation with open source tools](https://expeditioners.podbean.com/e/ep-6-whitney-champion-scaling-infrastructure-automation-with-open-source-tools/) | Whitney Champion - Infrastructure and open-source automation engineer | Scaling infrastructure automation using open source tools and the culture of contributing back to the community. | <nobr>2022‑09‑23</nobr> |
|
||||
| [Jesse Peterson - Open source communities for better MDM and career growth](https://expeditioners.podbean.com/e/jesse-peterson-open-source-communities-for-better-mdm-and-career-growth/) | Jesse Peterson - Client Platform Engineer at Meta; creator of NanoMDM; contributor to MicroMDM | How open-source communities drive better MDM tooling and create career growth opportunities for contributors. | <nobr>2022‑09‑01</nobr> |
|
||||
| [Nick Anderson - Endpoint security for osquery](https://expeditioners.podbean.com/e/ep-4-nick-anderson-endpoint-security-for-osquery/) | Nick Anderson - Security Engineer at Meta; osquery Technical Steering Committee member | How osquery enables robust endpoint security and detection at scale in large enterprise environments. | <nobr>2022‑08‑12</nobr> |
|
||||
| [Chris Long - From osquery skeptic to believer](https://expeditioners.podbean.com/e/ep-3-chris-long-from-osquery-sceptic-to-believer/) | Chris Long - Staff Security Engineer at Material Security; creator of Detection Lab | The journey from skepticism to conviction on osquery, and how Detection Lab accelerates security team onboarding. | <nobr>2022‑07‑21</nobr> |
|
||||
| [Prima Virani - Improving endpoint monitoring and visibility with osquery](https://expeditioners.podbean.com/e/prima-virani-improving-endpoint-monitoring-and-visibility-with-osquery/) | Prima Virani - Detection & Response Engineering Lead at Twilio | Using osquery to improve endpoint monitoring, detection, and security visibility across a distributed fleet. | <nobr>2022‑06‑28</nobr> |
|
||||
| [Mike Arpaia - The story behind the creation of osquery](https://expeditioners.podbean.com/e/ep-1-mike-arpaia-the-story-behind-the-creation-of-osquery/) | Mike Arpaia - Co-creator of osquery; Partner at Moonfire Ventures | The origin story of osquery, the philosophy behind its design, and its lasting impact on endpoint security and observability. | <nobr>2022‑06‑02</nobr> |
|
||||
|
||||
|
||||
## Press Coverage
|
||||
|
||||
| Publication | Headline | Journalist | Date |
|
||||
|---|---|---|---|
|
||||
| CRN | [Five Companies That Came To Win This Week](https://www.crn.com/news/channel-news/2026/five-companies-that-came-to-win-this-week-april-17-2026) | Rick Whiting | <nobr>2026‑04‑20</nobr> |
|
||||
| Cyber Defense Wire | [Fleet Announces New Partner Program and Names MobileIron Co-founder Suresh Batchu to Board](https://cyberdefensewire.com/fleet-announces-new-partner-program-and-names-mobileiron-co-founder-suresh-batchu-to-board/) | Staff | <nobr>2026‑04‑17</nobr> |
|
||||
| Channele2e | [Channel Brief: MSP Growth Is Getting Harder to Win](https://www.channele2e.com/news/channel-brief-its-less-about-tools-more-about-running-them) | Suparna Chawla Bhasin | <nobr>2026‑04‑17</nobr> |
|
||||
| CRN | [Fleet Launches Inaugural Partner Program As It Adopts A 100 Percent Channel Sales Model: Exclusive](https://www.crn.com/news/channel-news/2026/fleet-launches-inaugural-partner-program-as-it-adopts-a-100-percent-channel-sales-model-exclusive) | Rick Whiting | <nobr>2026‑04‑16</nobr> |
|
||||
| Channelvision | [Fleet Launches Partner Program for its Device Management](https://channelvisionmag.com/fleet-launches-partner-program-for-its-device-management/) | Martin Vilaboy | <nobr>2026‑04‑16</nobr> |
|
||||
| Apple Must | [Fleet launches partner program, appoints MobileIron co-founder to its board](https://www.applemust.com/fleet-launches-partner-program-appoints-mobileiron-co-founder-to-its-board/) | Jonny Evans | <nobr>2026‑04‑16</nobr> |
|
||||
|
||||
|
||||
## Release notes
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ This page outlines the different roles in the marketing team and the DRIs execut
|
|||
| Technical product marketing and content specialist (Consultant) | [Dan Gordon](https://www.linkedin.com/in/dangordon/) | [@danbgordon](https://github.com/danbgordon) | Create core technical marketing strategy, positioning, messaging and assets for IT technical teams Market Fleet releases Create, drive and manage analyst relations for Fleet |
|
||||
| Product marketing (Consultant) | [Erin Miska](https://www.linkedin.com/in/erinmiska/) | [*@miskaek*](https://github.com/miskaek) | Positioning and messaging Leadership marketing facing assets, Sales and partner enablement Content strategy |
|
||||
| Social media strategy and management (Consultant) | [Thomas Basgil Jr.](https://www.linkedin.com/in/tombasgil/) | [*@tombasgil*](https://github.com/tombasgil) | Establish, manage and grow Fleet’s social media presence across all appropriate channels. Monitor and respond to comments on company page posts (e.g., LinkedIn); comments on tracked posts are surfaced in the [#_linkedin-comments-from-tracked-posts](https://fleetdm.slack.com/archives/C0AP1FM3ES2) Slack channel |
|
||||
| Public relations (Consultant) | [TBD] | | Establish Fleet PR program Identify and train key Fleet employees on PR interactions Establish, measure and improve Fleet share of voice with press, analysts, and media. Manage Fleet submissions for industry awards |
|
||||
| Public relations (Consultant) | [Alyssa Pallotti](https://www.linkedin.com/in/alyssapallotti/) | | Establish Fleet AR & PR program Identify and train key Fleet employees on AR & PR interactions Establish, measure and improve Fleet share of voice with press, analysts, and media. Manage Fleet submissions for industry awards |
|
||||
|
||||
<meta name="maintainedBy" value="akuthiala">
|
||||
<meta name="title" value="Fleet team responsibilities">
|
||||
|
|
|
|||
|
|
@ -275,12 +275,19 @@ type SoftwarePackage struct {
|
|||
fleet.SoftwarePackageSpec
|
||||
}
|
||||
|
||||
func (spec SoftwarePackage) HydrateToPackageLevel(packageLevel fleet.SoftwarePackageSpec) (fleet.SoftwarePackageSpec, error) {
|
||||
if spec.Icon.Path != "" || spec.InstallScript.Path != "" || spec.UninstallScript.Path != "" ||
|
||||
func (spec SoftwarePackage) HydrateToPackageLevel(packageLevel fleet.SoftwarePackageSpec, ext string) (fleet.SoftwarePackageSpec, error) {
|
||||
if spec.InstallScript.Path != "" || spec.UninstallScript.Path != "" ||
|
||||
spec.PostInstallScript.Path != "" || spec.URL != "" || spec.SHA256 != "" || spec.PreInstallQuery.Path != "" {
|
||||
return packageLevel, fmt.Errorf("the software package defined in %s must not have icons, scripts, queries, URL, or hash specified at the team level", *spec.Path)
|
||||
}
|
||||
|
||||
// Icon should be allowed at the team level yaml for script packages which must be specified as a path
|
||||
if spec.Icon.Path != "" {
|
||||
if ext != ".sh" && ext != ".ps1" {
|
||||
return packageLevel, fmt.Errorf("the software package defined in %s must not have icons, scripts, queries, URL, or hash specified at the team level", *spec.Path)
|
||||
}
|
||||
}
|
||||
|
||||
packageLevel.Categories = spec.Categories
|
||||
packageLevel.LabelsIncludeAny = spec.LabelsIncludeAny
|
||||
packageLevel.LabelsExcludeAny = spec.LabelsExcludeAny
|
||||
|
|
@ -1877,10 +1884,15 @@ func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
}
|
||||
// Script file becomes the install script for a script-only package
|
||||
scriptSpec := fleet.SoftwarePackageSpec{
|
||||
InstallScript: fleet.TeamSpecSoftwareAsset{Path: resolvedPath},
|
||||
ReferencedYamlPath: resolvedPath,
|
||||
Icon: teamLevelPackage.Icon,
|
||||
}
|
||||
scriptSpec, err = teamLevelPackage.HydrateToPackageLevel(scriptSpec)
|
||||
// Icon path needs to be resolved, but since this function will set
|
||||
// the install script it needs to be set to the correct path again.
|
||||
scriptSpec = scriptSpec.ResolveSoftwarePackagePaths(baseDir)
|
||||
scriptSpec.InstallScript.Path = resolvedPath
|
||||
|
||||
scriptSpec, err = teamLevelPackage.HydrateToPackageLevel(scriptSpec, ext)
|
||||
if err != nil {
|
||||
multiError = multierror.Append(multiError, err)
|
||||
continue
|
||||
|
|
@ -1922,7 +1934,7 @@ func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
|
||||
for i, spec := range softwarePackageSpecs {
|
||||
softwarePackageSpec := spec.ResolveSoftwarePackagePaths(filepath.Dir(spec.ReferencedYamlPath))
|
||||
softwarePackageSpec, err = teamLevelPackage.HydrateToPackageLevel(softwarePackageSpec)
|
||||
softwarePackageSpec, err = teamLevelPackage.HydrateToPackageLevel(softwarePackageSpec, ext)
|
||||
if err != nil {
|
||||
multiError = multierror.Append(multiError, err)
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -1524,6 +1524,36 @@ software:
|
|||
assert.ErrorContains(t, err, "the software package defined in software/single-package.yml must not have icons, scripts, queries, URL, or hash specified at the team level")
|
||||
}
|
||||
|
||||
func TestScriptOnlyPackagesPathWithInline(t *testing.T) {
|
||||
t.Parallel()
|
||||
config := getTeamConfig([]string{"software"})
|
||||
config += `
|
||||
software:
|
||||
packages:
|
||||
- path: software/script-only.sh
|
||||
icon:
|
||||
path: ./foo/bar.png
|
||||
`
|
||||
|
||||
path, basePath := createTempFile(t, "", config)
|
||||
|
||||
err := file.Copy(
|
||||
filepath.Join("testdata", "software", "script-only.sh"),
|
||||
filepath.Join(basePath, "software", "script-only.sh"),
|
||||
os.FileMode(0o755),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
appConfig := fleet.EnrichedAppConfig{}
|
||||
appConfig.License = &fleet.LicenseInfo{
|
||||
Tier: fleet.TierPremium,
|
||||
}
|
||||
gitops, err := GitOpsFromFile(path, basePath, &appConfig, nopLogf)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, gitops.Software.Packages, 1)
|
||||
assert.Equal(t, filepath.Join(basePath, "foo", "bar.png"), gitops.Software.Packages[0].Icon.Path)
|
||||
}
|
||||
|
||||
func TestIllegalFleetSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
config := getGlobalConfig([]string{"policies"})
|
||||
|
|
|
|||
3
pkg/spec/testdata/software/script-only.sh
vendored
Normal file
3
pkg/spec/testdata/software/script-only.sh
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "hello world"
|
||||
|
|
@ -144,6 +144,12 @@ func (e *endpointer) Service() any {
|
|||
func newUserAuthenticatedEndpointer(svc api.Service, authMiddleware endpoint.Middleware, opts []kithttp.ServerOption, r *mux.Router,
|
||||
versions ...string,
|
||||
) *eu.CommonEndpointer[handlerFunc] {
|
||||
// Append RouteTemplateRequestFunc so the api_only endpoint middleware
|
||||
// can read the matched mux route template from context.
|
||||
//
|
||||
// Full-slice expression prevents aliasing into the caller's backing array
|
||||
// if it happens to have spare capacity.
|
||||
opts = append(opts[:len(opts):len(opts)], kithttp.ServerBefore(eu.RouteTemplateRequestFunc))
|
||||
return &eu.CommonEndpointer[handlerFunc]{
|
||||
EP: &endpointer{
|
||||
svc: svc,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ var apiEndpointsYAML []byte
|
|||
|
||||
var apiEndpoints []fleet.APIEndpoint
|
||||
|
||||
var apiEndpointsSet map[string]struct{}
|
||||
|
||||
// GetAPIEndpoints returns a copy of the embedded API endpoints slice.
|
||||
func GetAPIEndpoints() []fleet.APIEndpoint {
|
||||
result := make([]fleet.APIEndpoint, len(apiEndpoints))
|
||||
|
|
@ -22,6 +24,12 @@ func GetAPIEndpoints() []fleet.APIEndpoint {
|
|||
return result
|
||||
}
|
||||
|
||||
// IsInCatalog reports whether the given endpoint fingerprint is in the catalog.
|
||||
func IsInCatalog(fingerprint string) bool {
|
||||
_, ok := apiEndpointsSet[fingerprint]
|
||||
return ok
|
||||
}
|
||||
|
||||
func Init(h http.Handler) error {
|
||||
r, ok := h.(*mux.Router)
|
||||
if !ok {
|
||||
|
|
@ -63,6 +71,12 @@ func Init(h http.Handler) error {
|
|||
|
||||
apiEndpoints = loadedApiEndpoints
|
||||
|
||||
set := make(map[string]struct{}, len(loadedApiEndpoints))
|
||||
for _, e := range loadedApiEndpoints {
|
||||
set[e.Fingerprint()] = struct{}{}
|
||||
}
|
||||
apiEndpointsSet = set
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func (e *APIEndpoint) normalize() {
|
|||
// Fingerprint return a string that uniquely identifies
|
||||
// the APIEndpoint
|
||||
func (e APIEndpoint) Fingerprint() string {
|
||||
return fmt.Sprintf("|%s|%s|", e.Method, e.NormalizedPath)
|
||||
return "|" + e.Method + "|" + e.NormalizedPath + "|"
|
||||
}
|
||||
|
||||
func (e APIEndpoint) validate() error {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ func (e *androidEndpointer) Service() any {
|
|||
func newUserAuthenticatedEndpointer(fleetSvc fleet.Service, svc android.Service, opts []kithttp.ServerOption, r *mux.Router,
|
||||
versions ...string,
|
||||
) *eu.CommonEndpointer[handlerFunc] {
|
||||
// Full-slice expression prevents aliasing into the caller's backing array
|
||||
// if it happens to have spare capacity.
|
||||
opts = append(opts[:len(opts):len(opts)], kithttp.ServerBefore(auth.RouteTemplateRequestFunc))
|
||||
return &eu.CommonEndpointer[handlerFunc]{
|
||||
EP: &androidEndpointer{
|
||||
svc: svc,
|
||||
|
|
@ -63,7 +66,7 @@ func newUserAuthenticatedEndpointer(fleetSvc fleet.Service, svc android.Service,
|
|||
EncodeFn: encodeResponse,
|
||||
Opts: opts,
|
||||
AuthMiddleware: func(next endpoint.Endpoint) endpoint.Endpoint {
|
||||
return auth.AuthenticatedUser(fleetSvc, next)
|
||||
return auth.AuthenticatedUser(fleetSvc, auth.APIOnlyEndpointCheck(next))
|
||||
},
|
||||
Router: r,
|
||||
Versions: versions,
|
||||
|
|
|
|||
42
server/platform/endpointer/route_template.go
Normal file
42
server/platform/endpointer/route_template.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package endpointer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// contextKeyRouteTemplate is the context key type for the mux route template.
|
||||
type contextKeyRouteTemplate struct{}
|
||||
|
||||
var routeTemplateKey = contextKeyRouteTemplate{}
|
||||
|
||||
// RouteTemplateRequestFunc captures the gorilla/mux route template for the
|
||||
// matched request and stores it in the context.
|
||||
func RouteTemplateRequestFunc(ctx context.Context, r *http.Request) context.Context {
|
||||
route := mux.CurrentRoute(r)
|
||||
if route == nil {
|
||||
return ctx
|
||||
}
|
||||
tpl, err := route.GetPathTemplate()
|
||||
if err != nil {
|
||||
// Only happens when a route has no path, which Fleet never registers.
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, routeTemplateKey, tpl)
|
||||
}
|
||||
|
||||
// RouteTemplateFromContext returns the mux route template stored by
|
||||
// RouteTemplateRequestFunc. Returns "" and false if no template is in context.
|
||||
func RouteTemplateFromContext(ctx context.Context) (string, bool) {
|
||||
tpl, ok := ctx.Value(routeTemplateKey).(string)
|
||||
return tpl, ok
|
||||
}
|
||||
|
||||
// WithRouteTemplate returns a new context with the given route template value.
|
||||
// Intended for tests that need to simulate what RouteTemplateRequestFunc would
|
||||
// have stored without running a real mux router.
|
||||
func WithRouteTemplate(ctx context.Context, tpl string) context.Context {
|
||||
return context.WithValue(ctx, routeTemplateKey, tpl)
|
||||
}
|
||||
|
|
@ -136,6 +136,9 @@ func (e *fleetEndpointer) Service() any {
|
|||
func newUserAuthenticatedEndpointer(svc fleet.Service, opts []kithttp.ServerOption, r *mux.Router,
|
||||
versions ...string,
|
||||
) *eu.CommonEndpointer[handlerFunc] {
|
||||
// Full-slice expression prevents aliasing into the caller's backing array
|
||||
// if it happens to have spare capacity.
|
||||
opts = append(opts[:len(opts):len(opts)], kithttp.ServerBefore(auth.RouteTemplateRequestFunc))
|
||||
return &eu.CommonEndpointer[handlerFunc]{
|
||||
EP: &fleetEndpointer{
|
||||
svc: svc,
|
||||
|
|
@ -144,7 +147,7 @@ func newUserAuthenticatedEndpointer(svc fleet.Service, opts []kithttp.ServerOpti
|
|||
EncodeFn: encodeResponse,
|
||||
Opts: opts,
|
||||
AuthMiddleware: func(next endpoint.Endpoint) endpoint.Endpoint {
|
||||
return auth.AuthenticatedUser(svc, next)
|
||||
return auth.AuthenticatedUser(svc, auth.APIOnlyEndpointCheck(next))
|
||||
},
|
||||
Router: r,
|
||||
Versions: versions,
|
||||
|
|
|
|||
|
|
@ -535,12 +535,15 @@ func (s *integrationTestSuite) TestModifyAPIOnlyUser() {
|
|||
"name": "New Name",
|
||||
}, http.StatusUnprocessableEntity)
|
||||
|
||||
// An API-only user cannot modify their own record via this endpoint.
|
||||
// An API-only user cannot reach this admin endpoint: the api_only middleware
|
||||
// rejects it at the catalog check (the user-management endpoint is not in the catalog).
|
||||
//
|
||||
// This is to protect against privilege escalation vulnerability.
|
||||
s.token = apiUserToken
|
||||
defer func() { s.token = s.getTestAdminToken() }()
|
||||
s.Do("PATCH", fmt.Sprintf("/api/latest/fleet/users/api_only/%d", apiUserID), map[string]any{
|
||||
"name": "Self Update",
|
||||
}, http.StatusUnprocessableEntity)
|
||||
}, http.StatusForbidden)
|
||||
s.token = s.getTestAdminToken()
|
||||
|
||||
s.Do("PATCH", fmt.Sprintf("/api/latest/fleet/users/api_only/%d", apiUserID), map[string]any{
|
||||
|
|
|
|||
|
|
@ -28985,3 +28985,73 @@ func (s *integrationEnterpriseTestSuite) TestGetUserReturnsAPIEndpoints() {
|
|||
require.False(t, foundRegular.APIOnly)
|
||||
require.Empty(t, foundRegular.APIEndpoints)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestAPIOnlyUserEndpointMiddleware() {
|
||||
t := s.T()
|
||||
|
||||
defer func() { s.token = s.getTestAdminToken() }()
|
||||
|
||||
createAPIOnlyUser := func(name string, endpoints []map[string]any) string {
|
||||
prev := s.token
|
||||
s.token = s.getTestAdminToken()
|
||||
defer func() { s.token = prev }()
|
||||
|
||||
body := map[string]any{
|
||||
"name": name,
|
||||
"global_role": "observer",
|
||||
}
|
||||
if endpoints != nil {
|
||||
body["api_endpoints"] = endpoints
|
||||
}
|
||||
var createResp struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/users/api_only", body, http.StatusOK, &createResp)
|
||||
require.NotEmpty(t, createResp.Token)
|
||||
return createResp.Token
|
||||
}
|
||||
|
||||
// With no endpoint restrictions the user can reach any endpoint in the catalog.
|
||||
t.Run("no restrictions allows all catalog endpoints", func(t *testing.T) {
|
||||
s.token = createAPIOnlyUser("api-only-mw-no-restrictions", nil)
|
||||
|
||||
s.Do("GET", "/api/latest/fleet/version", nil, http.StatusOK)
|
||||
s.Do("GET", "/api/latest/fleet/config", nil, http.StatusOK)
|
||||
s.Do("GET", "/api/latest/fleet/me", nil, http.StatusOK)
|
||||
})
|
||||
|
||||
// Paths not registered in the API endpoint catalog are always rejected for
|
||||
// api-only users, regardless of whether they have endpoint restrictions.
|
||||
t.Run("non-catalog path is rejected", func(t *testing.T) {
|
||||
s.token = createAPIOnlyUser("api-only-mw-non-catalog-unrestricted", nil)
|
||||
s.Do("PATCH", "/api/latest/fleet/users/api_only/1", map[string]any{"name": "x"}, http.StatusForbidden)
|
||||
|
||||
s.token = createAPIOnlyUser("api-only-mw-non-catalog-restricted", []map[string]any{
|
||||
{"method": "GET", "path": "/api/v1/fleet/version"},
|
||||
})
|
||||
s.Do("PATCH", "/api/latest/fleet/users/api_only/1", map[string]any{"name": "x"}, http.StatusForbidden)
|
||||
})
|
||||
|
||||
// With endpoint restrictions, only explicitly allowed endpoints are reachable.
|
||||
t.Run("endpoint restrictions limit access to the allowed list", func(t *testing.T) {
|
||||
s.token = createAPIOnlyUser("api-only-mw-restricted", []map[string]any{
|
||||
{"method": "GET", "path": "/api/v1/fleet/version"},
|
||||
})
|
||||
|
||||
// The only allowed endpoint returns 200.
|
||||
s.Do("GET", "/api/latest/fleet/version", nil, http.StatusOK)
|
||||
|
||||
// These are in the catalog but not in the user's allow list.
|
||||
s.Do("GET", "/api/latest/fleet/config", nil, http.StatusForbidden)
|
||||
s.Do("GET", "/api/latest/fleet/me", nil, http.StatusForbidden)
|
||||
})
|
||||
|
||||
// Non-api-only users must not be affected by the middleware at all.
|
||||
t.Run("non-api-only user is unaffected", func(t *testing.T) {
|
||||
s.token = s.getTestAdminToken()
|
||||
|
||||
s.Do("GET", "/api/latest/fleet/version", nil, http.StatusOK)
|
||||
s.Do("GET", "/api/latest/fleet/config", nil, http.StatusOK)
|
||||
s.Do("GET", "/api/latest/fleet/me", nil, http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
79
server/service/middleware/auth/api_only.go
Normal file
79
server/service/middleware/auth/api_only.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
apiendpoints "github.com/fleetdm/fleet/v4/server/api_endpoints"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
eu "github.com/fleetdm/fleet/v4/server/platform/endpointer"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
)
|
||||
|
||||
// RouteTemplateRequestFunc captures the gorilla/mux route template for the
|
||||
// matched request and stores it in the context. Alias of the platform
|
||||
// implementation, re-exported so callers that already import this package can
|
||||
// continue to reference it here.
|
||||
var RouteTemplateRequestFunc = eu.RouteTemplateRequestFunc
|
||||
|
||||
// APIOnlyEndpointCheck returns an endpoint.Endpoint middleware that enforces
|
||||
// access control for API-only users (api_only=true). It must be wired inside
|
||||
// AuthenticatedUser (so a Viewer is already in context when it runs) and the
|
||||
// enclosing transport must register RouteTemplateRequestFunc as a ServerBefore
|
||||
// option so the mux route template is available in context.
|
||||
//
|
||||
// For non-API-only users the check is skipped entirely. When there is no Viewer
|
||||
// in context, the call passes through — AuthenticatedUser guarantees that any
|
||||
// request that needs a Viewer has already been rejected before reaching here.
|
||||
//
|
||||
// For API-only users two checks are applied in order:
|
||||
// 1. The requested route must appear in the API endpoint catalog. If not, a
|
||||
// permission error (403) is returned.
|
||||
// 2. If the user has configured endpoint restrictions (rows in
|
||||
// user_api_endpoints), the route must match one of them. If not, a
|
||||
// permission error (403) is returned. An empty restriction list grants
|
||||
// full access to all catalog endpoints.
|
||||
func APIOnlyEndpointCheck(next endpoint.Endpoint) endpoint.Endpoint {
|
||||
return apiOnlyEndpointCheck(apiendpoints.IsInCatalog, next)
|
||||
}
|
||||
|
||||
func apiOnlyEndpointCheck(isInCatalog func(string) bool, next endpoint.Endpoint) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request any) (any, error) {
|
||||
v, ok := viewer.FromContext(ctx)
|
||||
if !ok || v.User == nil || !v.User.APIOnly {
|
||||
return next(ctx, request)
|
||||
}
|
||||
|
||||
requestMethod, _ := ctx.Value(kithttp.ContextKeyRequestMethod).(string)
|
||||
routeTemplate, _ := eu.RouteTemplateFromContext(ctx)
|
||||
|
||||
fp := fleet.NewAPIEndpointFromTpl(requestMethod, routeTemplate).Fingerprint()
|
||||
|
||||
if !isInCatalog(fp) {
|
||||
return nil, permissionDenied(ctx)
|
||||
}
|
||||
|
||||
// No endpoint restrictions: full access to all catalog endpoints.
|
||||
if len(v.User.APIEndpoints) == 0 {
|
||||
return next(ctx, request)
|
||||
}
|
||||
|
||||
// Check whether the requested endpoint matches any of the user's allowed endpoints.
|
||||
for _, ep := range v.User.APIEndpoints {
|
||||
if fleet.NewAPIEndpointFromTpl(ep.Method, ep.Path).Fingerprint() == fp {
|
||||
return next(ctx, request)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, permissionDenied(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func permissionDenied(ctx context.Context) error {
|
||||
if ac, ok := authz.FromContext(ctx); ok {
|
||||
ac.SetChecked()
|
||||
}
|
||||
return fleet.NewPermissionError("forbidden")
|
||||
}
|
||||
337
server/service/middleware/auth/api_only_test.go
Normal file
337
server/service/middleware/auth/api_only_test.go
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
authzctx "github.com/fleetdm/fleet/v4/server/contexts/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
eu "github.com/fleetdm/fleet/v4/server/platform/endpointer"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// muxVersionSegment is the gorilla/mux route template version segment that
|
||||
// RouteTemplateRequestFunc would extract from a real mux router.
|
||||
const muxVersionSegment = "/api/{fleetversion:(?:v1|2022-04|latest)}/"
|
||||
|
||||
// testCatalogEndpoints is the minimal set of endpoints used across tests.
|
||||
var testCatalogEndpoints = []fleet.APIEndpoint{
|
||||
fleet.NewAPIEndpointFromTpl("GET", "/api/v1/fleet/hosts"),
|
||||
fleet.NewAPIEndpointFromTpl("GET", "/api/v1/fleet/hosts/:id"),
|
||||
fleet.NewAPIEndpointFromTpl("POST", "/api/v1/fleet/scripts/run"),
|
||||
}
|
||||
|
||||
// testIsInCatalog builds a fingerprint set from testCatalogEndpoints and
|
||||
// returns an isInCatalog func suitable for injection into apiOnlyEndpointCheck.
|
||||
func testIsInCatalog() func(string) bool {
|
||||
set := make(map[string]struct{}, len(testCatalogEndpoints))
|
||||
for _, ep := range testCatalogEndpoints {
|
||||
set[ep.Fingerprint()] = struct{}{}
|
||||
}
|
||||
return func(fp string) bool {
|
||||
_, ok := set[fp]
|
||||
return ok
|
||||
}
|
||||
}
|
||||
|
||||
// muxTemplate returns a gorilla/mux route template for the given path suffix, simulating
|
||||
// what RouteTemplateRequestFunc would extract from mux.CurrentRoute(r).GetPathTemplate().
|
||||
func muxTemplate(pathSuffix string) string {
|
||||
return muxVersionSegment + pathSuffix
|
||||
}
|
||||
|
||||
func TestAPIOnlyEndpointCheck(t *testing.T) {
|
||||
newNext := func() (func(context.Context, any) (any, error), *bool) {
|
||||
called := false
|
||||
fn := func(ctx context.Context, request any) (any, error) {
|
||||
called = true
|
||||
return nil, nil
|
||||
}
|
||||
return fn, &called
|
||||
}
|
||||
|
||||
newEndpoint := func(next func(context.Context, any) (any, error)) func(context.Context, any) (any, error) {
|
||||
return apiOnlyEndpointCheck(testIsInCatalog(), next)
|
||||
}
|
||||
|
||||
ctxWithMethod := func(method, tpl string) context.Context {
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, kithttp.ContextKeyRequestMethod, method)
|
||||
ctx = eu.WithRouteTemplate(ctx, tpl)
|
||||
return ctx
|
||||
}
|
||||
|
||||
t.Run("non-api-only user always passes through", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("GET", muxTemplate("fleet/hosts"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{APIOnly: false}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, *called)
|
||||
})
|
||||
|
||||
t.Run("no viewer in context passes through", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("GET", muxTemplate("fleet/hosts"))
|
||||
// no viewer set
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, *called)
|
||||
})
|
||||
|
||||
t.Run("api-only user, endpoint in catalog, no restrictions", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("GET", muxTemplate("fleet/hosts"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
APIEndpoints: nil,
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, *called)
|
||||
})
|
||||
|
||||
t.Run("api-only user, empty APIEndpoints slice treated same as nil (no restrictions)", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("GET", muxTemplate("fleet/hosts"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
APIEndpoints: []fleet.APIEndpointRef{}, // empty, not nil
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, *called)
|
||||
})
|
||||
|
||||
t.Run("api-only user, endpoint with placeholder in catalog, no restrictions", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("GET", muxTemplate("fleet/hosts/{id:[0-9]+}"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
APIEndpoints: nil,
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, *called)
|
||||
})
|
||||
|
||||
t.Run("api-only user, endpoint not in catalog", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("GET", muxTemplate("fleet/secret_admin_endpoint"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{APIOnly: true}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.Error(t, err)
|
||||
require.False(t, *called)
|
||||
var permErr *fleet.PermissionError
|
||||
require.ErrorAs(t, err, &permErr)
|
||||
})
|
||||
|
||||
t.Run("api-only user, missing route template in context is rejected", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
// routeTemplateKey deliberately not set (simulates RouteTemplateRequestFunc failure).
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, kithttp.ContextKeyRequestMethod, "GET")
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{APIOnly: true}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.Error(t, err)
|
||||
require.False(t, *called)
|
||||
var permErr *fleet.PermissionError
|
||||
require.ErrorAs(t, err, &permErr)
|
||||
})
|
||||
|
||||
t.Run("api-only user, missing method and template are both rejected", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
// Neither method nor template set — empty fingerprint never matches catalog.
|
||||
ctx := context.Background()
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{APIOnly: true}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.Error(t, err)
|
||||
require.False(t, *called)
|
||||
var permErr *fleet.PermissionError
|
||||
require.ErrorAs(t, err, &permErr)
|
||||
})
|
||||
|
||||
t.Run("api-only user, method normalization is case-insensitive", func(t *testing.T) {
|
||||
// Lower-case method must normalize to the same fingerprint as upper-case.
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("get", muxTemplate("fleet/hosts")) // lower-case
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
APIEndpoints: nil,
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, *called)
|
||||
})
|
||||
|
||||
t.Run("api-only user, rejection marks authz context as checked", func(t *testing.T) {
|
||||
// Ensures authzcheck middleware does not emit a spurious "Missing
|
||||
// authorization check" log when we deny an api_only user.
|
||||
next, called := newNext()
|
||||
ac := &authzctx.AuthorizationContext{}
|
||||
ctx := authzctx.NewContext(context.Background(), ac)
|
||||
ctx = context.WithValue(ctx, kithttp.ContextKeyRequestMethod, "GET")
|
||||
ctx = eu.WithRouteTemplate(ctx, muxTemplate("fleet/secret_admin_endpoint"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{APIOnly: true}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.Error(t, err)
|
||||
require.False(t, *called)
|
||||
require.True(t, ac.Checked(), "authz context must be marked checked on denial")
|
||||
})
|
||||
|
||||
t.Run("api-only user with restrictions, accessing allowed endpoint", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("GET", muxTemplate("fleet/hosts"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
APIEndpoints: []fleet.APIEndpointRef{
|
||||
{Method: "GET", Path: "/api/v1/fleet/hosts"},
|
||||
},
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, *called)
|
||||
})
|
||||
|
||||
t.Run("api-only user with restrictions, accessing allowed placeholder endpoint", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("GET", muxTemplate("fleet/hosts/{id:[0-9]+}"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
// Stored path uses colon-prefix style as in the YAML catalog.
|
||||
APIEndpoints: []fleet.APIEndpointRef{
|
||||
{Method: "GET", Path: "/api/v1/fleet/hosts/:id"},
|
||||
},
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, *called)
|
||||
})
|
||||
|
||||
t.Run("api-only user with restrictions, accessing disallowed endpoint", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("POST", muxTemplate("fleet/scripts/run"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
APIEndpoints: []fleet.APIEndpointRef{
|
||||
{Method: "GET", Path: "/api/v1/fleet/hosts"},
|
||||
},
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.Error(t, err)
|
||||
require.False(t, *called)
|
||||
|
||||
var permErr *fleet.PermissionError
|
||||
require.ErrorAs(t, err, &permErr)
|
||||
})
|
||||
|
||||
t.Run("api-only user, allow-list entry for non-catalog endpoint is still denied", func(t *testing.T) {
|
||||
// The catalog check runs before the allow-list check; an explicit allow entry
|
||||
// must not grant access to an endpoint that is not in the catalog.
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("GET", muxTemplate("fleet/secret_admin_endpoint"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
APIEndpoints: []fleet.APIEndpointRef{
|
||||
{Method: "GET", Path: "/api/v1/fleet/secret_admin_endpoint"},
|
||||
},
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.Error(t, err)
|
||||
require.False(t, *called)
|
||||
var permErr *fleet.PermissionError
|
||||
require.ErrorAs(t, err, &permErr)
|
||||
})
|
||||
|
||||
t.Run("api-only user, wrong method for catalog endpoint is rejected at catalog step", func(t *testing.T) {
|
||||
// POST /fleet/hosts is not in the catalog (only GET is), so the catalog check rejects it.
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("POST", muxTemplate("fleet/hosts"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
APIEndpoints: []fleet.APIEndpointRef{
|
||||
{Method: "GET", Path: "/api/v1/fleet/hosts"},
|
||||
},
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.Error(t, err)
|
||||
require.False(t, *called)
|
||||
var permErr *fleet.PermissionError
|
||||
require.ErrorAs(t, err, &permErr)
|
||||
})
|
||||
|
||||
t.Run("api-only user with multiple allowed endpoints, accessing one of them", func(t *testing.T) {
|
||||
next, called := newNext()
|
||||
ctx := ctxWithMethod("POST", muxTemplate("fleet/scripts/run"))
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
|
||||
APIOnly: true,
|
||||
APIEndpoints: []fleet.APIEndpointRef{
|
||||
{Method: "GET", Path: "/api/v1/fleet/hosts"},
|
||||
{Method: "POST", Path: "/api/v1/fleet/scripts/run"},
|
||||
},
|
||||
}})
|
||||
|
||||
_, err := newEndpoint(next)(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, *called)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteTemplateRequestFunc(t *testing.T) {
|
||||
// Register a route and route the request through mux so mux.CurrentRoute
|
||||
// returns a non-nil value, mirroring what happens in production.
|
||||
newServedRequest := func(t *testing.T, routeTpl, reqPath string) (context.Context, bool) {
|
||||
t.Helper()
|
||||
var (
|
||||
got context.Context
|
||||
wasMatch bool
|
||||
)
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc(routeTpl, func(_ http.ResponseWriter, req *http.Request) {
|
||||
wasMatch = true
|
||||
got = RouteTemplateRequestFunc(req.Context(), req)
|
||||
}).Methods("GET")
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", reqPath, nil)
|
||||
r.ServeHTTP(rec, req)
|
||||
return got, wasMatch
|
||||
}
|
||||
|
||||
t.Run("stores the matched route template", func(t *testing.T) {
|
||||
ctx, matched := newServedRequest(t, "/api/v1/fleet/hosts/{id:[0-9]+}", "/api/v1/fleet/hosts/42")
|
||||
require.True(t, matched, "expected route to be matched")
|
||||
tpl, ok := eu.RouteTemplateFromContext(ctx)
|
||||
require.True(t, ok, "route template must be stored in context")
|
||||
require.Equal(t, "/api/v1/fleet/hosts/{id:[0-9]+}", tpl)
|
||||
})
|
||||
|
||||
t.Run("no matched route leaves context unchanged", func(t *testing.T) {
|
||||
// Call RouteTemplateRequestFunc directly with a request that never went
|
||||
// through a mux router, so mux.CurrentRoute returns nil.
|
||||
req := httptest.NewRequest("GET", "/whatever", nil)
|
||||
ctx := context.Background()
|
||||
got := RouteTemplateRequestFunc(ctx, req)
|
||||
_, ok := eu.RouteTemplateFromContext(got)
|
||||
require.False(t, ok, "no route template should be stored when no route is matched")
|
||||
})
|
||||
}
|
||||
265
website/.claude/CLAUDE.md
vendored
Normal file
265
website/.claude/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
# CLAUDE.md — Fleet Website
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in the `website/` folder.
|
||||
|
||||
## About
|
||||
|
||||
Sails.js 1.5.17 web application. Node.js 20+, PostgreSQL, EJS templates, Vue.js/Parasails frontend, LESS styles, Grunt build system.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
api/
|
||||
├── controllers/ # Sails Actions2 controllers, organized by feature
|
||||
├── models/ # Waterline ORM models (User, Subscription, Quote, etc.)
|
||||
├── helpers/ # Reusable logic, organized by domain (stripe/, salesforce/, ai/, etc.)
|
||||
├── policies/ # Auth middleware (is-logged-in, is-super-admin, etc.)
|
||||
├── responses/ # Custom response handlers (unauthorized, expired, badConfig)
|
||||
└── hooks/custom/ # Server initialization, security headers, globals
|
||||
assets/
|
||||
├── js/
|
||||
│ ├── components/ # Vue/Parasails components (*.component.js)
|
||||
│ ├── pages/ # Page scripts (parasails.registerPage)
|
||||
│ └── utilities/ # Shared utilities (parasails.registerUtility)
|
||||
└── styles/ # LESS stylesheets
|
||||
views/
|
||||
├── layouts/ # EJS layout templates
|
||||
├── pages/ # Page templates
|
||||
├── partials/ # Reusable template fragments
|
||||
└── emails/ # Email templates
|
||||
config/
|
||||
├── routes.js # All route definitions
|
||||
├── policies.js # Route-to-policy mappings
|
||||
├── custom.js # App settings (API keys, TTLs, feature flags)
|
||||
└── local.js # Local overrides (not committed)
|
||||
```
|
||||
|
||||
## Backend conventions
|
||||
|
||||
### Controllers & helpers
|
||||
Both use the Sails Actions2 machine format (`friendlyName`, `inputs`, `exits`, `fn`). Call helpers with `await sails.helpers.domain.name.with({...})`. Throw exit names (e.g., `throw 'notFound'`) to trigger non-success exits.
|
||||
|
||||
### Models (Waterline ORM)
|
||||
Declarative attribute schemas in `api/models/`. Use `protect: true` for sensitive fields (passwords, tokens).
|
||||
|
||||
### Routes
|
||||
All in `config/routes.js`. Webhooks need `csrf: false`.
|
||||
|
||||
### Authentication
|
||||
- Session-based: `req.session.userId`
|
||||
- Logged-in user auto-hydrated as `req.me`
|
||||
- Policies in `config/policies.js` control access; `'*': 'is-logged-in'` is the default
|
||||
|
||||
### Configuration
|
||||
- `config/custom.js` — app settings, integration keys, feature flags
|
||||
- `config/local.js` — local dev overrides (not committed, not deployed)
|
||||
- `config/env/production.js` — production overrides
|
||||
- Sensitive credentials go in `config/local.js` or environment variables, never in committed config
|
||||
|
||||
## Frontend conventions
|
||||
|
||||
### Data flow from controllers to pages
|
||||
Values returned by a page's view action (e.g., `api/controllers/view-pricing.js`) are sent to the page in the `data` object. In page scripts, they're available on `this` (e.g., `this.pricingTable`). In templates:
|
||||
- **EJS** (`<%- pricingTable %>`) — for server-side rendering of data from the view action
|
||||
- **Vue** (`{{pricingTable}}`) — for values that change based on user interaction (filters, toggles, etc.)
|
||||
|
||||
Use EJS when the data is static from the server. Use Vue templates when the value is reactive and updated by page methods.
|
||||
|
||||
### Reusable components
|
||||
Several Parasails components are used across multiple pages:
|
||||
- `<scrollable-tweets>` — testimonial carousel. Requires `testimonialsForScrollableTweets` data from the view action (see Testimonials below).
|
||||
- `<parallax-city>` — animated city skyline banner, used at the bottom of landing pages. Must sit at the top level of the page, outside `page-container`/`page-content`, so it can span the full viewport width with no padding. Typically appears just after a `bottom-gradient` section. See `views/pages/landing-pages/linux-management.ejs` for the full end-of-page structure.
|
||||
- `<logo-carousel>` — rotating customer logo strip, typically placed in hero sections.
|
||||
- `<modal>` — modal dialog. Control visibility with `v-if="modal === 'modal-name'"` and `@close="closeModal()"`. Commonly used for video embeds.
|
||||
|
||||
#### Video modal pattern
|
||||
Landing pages typically include a "See Fleet in action" video button. The pattern requires:
|
||||
1. Page script: add `modal: ''` to `data`, plus `clickOpenVideoModal` and `closeModal` methods
|
||||
2. Template: add a `<modal purpose="video-modal">` with a YouTube iframe
|
||||
3. LESS: include responsive video modal styles (see `assets/styles/pages/landing-pages/linux-management.less` for reference)
|
||||
|
||||
#### Testimonials
|
||||
Testimonials are defined in `handbook/company/testimonials.yml` and compiled into `sails.config.builtStaticContent.testimonials`. Each has `quote`, `quoteAuthorName`, `quoteAuthorJobTitle`, `productCategories` (e.g., `Device management`, `Observability`, `Software management`), and optional media fields.
|
||||
|
||||
View actions that use `<scrollable-tweets>` must filter/sort testimonials and return them as `testimonialsForScrollableTweets`. See `api/controllers/landing-pages/view-linux-management.js` for the pattern.
|
||||
|
||||
### Cloud SDK (API calls)
|
||||
Frontend-to-backend API calls use `Cloud.*` methods, invoked by the `ajax-form` component or via a page script's `handleSubmitting` function. Each Cloud method maps to a backend action. After adding or renaming an action, regenerate the SDK:
|
||||
```bash
|
||||
sails run rebuild-cloud-sdk
|
||||
```
|
||||
|
||||
### Ajax forms
|
||||
Form submission uses `<ajax-form>` — either `action="cloudMethodName"` for simple cases or `:handle-submitting="fn"` for custom logic. State props (`syncing`, `cloudError`, `formErrors`) use `.sync`. See `views/pages/contact.ejs` for a full example; see `assets/js/components/ajax-form.component.js` for supported validation rules.
|
||||
|
||||
### Global browser variables
|
||||
`parasails`, `Cloud`, `io`, `_` (Lodash), `$` (jQuery), `moment`, `bowser`, `Vue`, `Stripe`, `gtag`, `ace`
|
||||
|
||||
### Image naming
|
||||
Images in `assets/images/` follow the pattern: `{category}-{descriptor}-{css-dimensions}@2x.{extension}`
|
||||
|
||||
The dimensions in the filename are CSS pixels (half the actual pixel resolution). For example, a 32x32 pixel image used at 16x16 CSS pixels:
|
||||
```
|
||||
icon-checkmark-green-16x16@2x.png
|
||||
```
|
||||
|
||||
## CSS/LESS conventions
|
||||
|
||||
### Preprocessor & build
|
||||
LESS compiled via Grunt. Single entry point: `assets/styles/importer.less` imports everything. New `.less` files must be `@import`ed in `importer.less` to take effect.
|
||||
|
||||
### Selector convention
|
||||
**Use `[purpose='name']` attribute selectors** — this is the primary styling approach, not traditional CSS classes:
|
||||
```less
|
||||
// In EJS template:
|
||||
// <div purpose="hero-container">...</div>
|
||||
|
||||
// In LESS:
|
||||
[purpose='hero-container'] {
|
||||
padding: 80px 0;
|
||||
}
|
||||
```
|
||||
Nest `[purpose]` selectors to scope styles within a section. Traditional CSS classes are secondary — used only for Bootstrap utilities and state toggles (`.truncated`, `.expanded`, `.loading-spinner`).
|
||||
|
||||
### Page-level scoping
|
||||
Each page stylesheet is scoped to a page ID selector at the root:
|
||||
```less
|
||||
#pricing {
|
||||
// All page-specific styles nested inside
|
||||
[purpose='page-content'] { ... }
|
||||
[purpose='hero-text'] { ... }
|
||||
|
||||
@media (max-width: 991px) { ... }
|
||||
}
|
||||
```
|
||||
This prevents style leakage between pages. The page ID matches the `id` attribute on the page's outermost `<div>` in the EJS template.
|
||||
|
||||
Some pages use a `-page` suffix (e.g., `#software-management-page` instead of `#software-management`). This is done when the base name would collide with an auto-generated heading ID — for example, markdown articles with a "Software management" heading get `id="software-management"` automatically. Add the `-page` suffix when the page name could conflict with a heading ID elsewhere on the site.
|
||||
|
||||
### Variables and mixins
|
||||
All colors, fonts, weights, and mixins live in `mixins-and-variables/`. Always use variable names instead of raw hex (e.g., `@core-fleet-black` not `#192147`). Common mixins: `.page-container()`, `.page-content()`, `.btn-reset()`, `.fade-in()`.
|
||||
|
||||
Don't use `@core-vibrant-blue` in new code — it's deprecated.
|
||||
|
||||
Primary CTA buttons should use the `btn btn-primary` Bootstrap classes — this adds pseudo-element shine effects on hover (defined in `bootstrap-overrides.less`). The default color is `@core-vibrant-green` but can be overridden per page; the key benefit is the shine, not the color.
|
||||
|
||||
### Page backgrounds
|
||||
Pages don't set their own section backgrounds. The page background is a gradient defined in `layout.less` and overridden per-page. Pages with a `<parallax-city>` footer typically end with a dedicated `bottom-gradient` section just before the component.
|
||||
|
||||
### Responsive breakpoints
|
||||
Max-width media queries, typically nested inside the page's root ID selector:
|
||||
```less
|
||||
#my-page {
|
||||
// Desktop styles at root level
|
||||
|
||||
@media (max-width: 1199px) { /* large desktop adjustments */ }
|
||||
@media (max-width: 991px) { /* tablet: cards stack, padding reduces */ }
|
||||
@media (max-width: 767px) { /* mobile: single column, smaller text */ }
|
||||
@media (max-width: 575px) { /* small mobile: minimal padding */ }
|
||||
@media (max-width: 375px) { /* extra small: final adjustments */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Framework
|
||||
Bootstrap 4 is loaded as a base dependency. Global overrides live in `bootstrap-overrides.less`, page-specific overrides should be scoped inside the page's ID selector.
|
||||
|
||||
Avoid using Bootstrap utility classes (`.d-flex`, `.justify-content-center`, `.flex-column`, etc.) for layout and display properties. Define these styles in the LESS stylesheet using `[purpose]` selectors instead — this keeps all styles in one place and makes them easier to adjust later. Bootstrap's grid (`.row`, `.col-*`) is acceptable where already established, but prefer stylesheet-defined layout for new work.
|
||||
|
||||
### Browser compatibility
|
||||
|
||||
The website enforces minimum browser versions via a [bowser](https://github.com/lancedikson/bowser) check in `views/layouts/layout.ejs` (around line 970). Visitors on unsupported browsers see a full-page block prompting them to upgrade. These floors were chosen to enable modern CSS features — notably the flexbox/grid `gap` property.
|
||||
|
||||
**Minimum supported versions** (source of truth: `layout.ejs`):
|
||||
|
||||
| Browser | Min version | Notes |
|
||||
|---------|------------|-------|
|
||||
| Chrome | 84 | `gap` support |
|
||||
| Edge | 84 | `gap` support |
|
||||
| Opera | 70 | `gap` support |
|
||||
| Safari | 14 | `gap` support |
|
||||
| Firefox | 103 | `backdrop-filter` support |
|
||||
| iOS | 14 | Supports embedded podcast player |
|
||||
| Android | 6 | Google's search crawler user agent |
|
||||
|
||||
Internet Explorer is blocked entirely.
|
||||
|
||||
**What's safe to use**:
|
||||
- Flexbox and CSS Grid, including `gap` on both
|
||||
- `backdrop-filter`
|
||||
- CSS custom properties (variables) — supported everywhere above IE
|
||||
- Modern ES2017+ JavaScript (async/await, object spread, etc.)
|
||||
|
||||
**What to be cautious with**:
|
||||
- Container queries — Safari 14 does not support them; need to fall back to media queries or wait to raise the floor
|
||||
- `:has()` selector — Safari 14 does not support it
|
||||
- Any CSS feature newer than ~2021 — check [caniuse.com](https://caniuse.com) against the table above
|
||||
|
||||
**Manual QA**: Per the [handbook](https://fleetdm.com/handbook/engineering#check-browser-compatibility-for-fleetdm-com), cross-browser checks are done monthly via BrowserStack. Google Chrome (macOS) latest is the baseline; other supported browsers are checked against it. File issues as bugs and assign for fixing.
|
||||
|
||||
**Raising or lowering the floor**: Update the `LATEST_SUPPORTED_VERSION_BY_USER_AGENT` and `LATEST_SUPPORTED_VERSION_BY_OS` objects in `views/layouts/layout.ejs`. Add a comment explaining *why* that version was chosen (which CSS/JS feature it enables), matching the existing pattern.
|
||||
|
||||
### LESS formatting rules (from `.lesshintrc`)
|
||||
- One space before `{` — `[purpose='hero'] {` not `[purpose='hero']{`
|
||||
- One space after `:` in properties — `padding: 16px` not `padding:16px`
|
||||
- Avoid `!important` — if unavoidable, add `//lesshint-disable-line importantRule` on the same line
|
||||
- No strict property ordering enforced
|
||||
- Zero warnings policy — `npm run lint` must pass with zero lesshint warnings
|
||||
|
||||
## Markdown content pipeline
|
||||
|
||||
### Source files
|
||||
Markdown content lives outside the `website/` directory in three top-level folders:
|
||||
- `docs/` — technical documentation
|
||||
- `articles/` — blog posts, case studies, whitepapers, comparisons
|
||||
- `handbook/` — internal company handbook
|
||||
|
||||
### Build process
|
||||
The build script `scripts/build-static-content.js` compiles markdown to HTML:
|
||||
```bash
|
||||
sails run build-static-content # compile markdown → EJS partials
|
||||
npm run build-for-prod # full production build (includes above + asset minification)
|
||||
npm run start-dev # dev mode (runs build-static-content then starts console)
|
||||
```
|
||||
Compiled output lands in `views/partials/built-from-markdown/`; metadata is exposed at runtime as `sails.config.builtStaticContent`.
|
||||
|
||||
### Metadata
|
||||
Embedded as HTML `<meta name="X" value="Y">` tags in the markdown file (not YAML frontmatter). See existing files in each folder for the required tags per content type.
|
||||
|
||||
### Custom syntax
|
||||
- `((bubble-text))` — converts to `<bubble type="bubble-text">` elements
|
||||
- Blockquotes — automatically rendered with `purpose="tip"` styling
|
||||
- Code blocks — language-specific highlighting (`js`, `bash`, `yaml`, `mermaid`, etc.)
|
||||
- Checklists — `- [x]` and `- [ ]` syntax renders as checkboxes
|
||||
|
||||
### Restrictions
|
||||
The build script enforces several rules and will throw errors for:
|
||||
- Vue template syntax (`{{ }}`) outside code blocks (conflicts with client-side Vue)
|
||||
- Relative markdown links without `.md` extension
|
||||
- `@fleetdm.com` email addresses in markdown
|
||||
- Missing required meta tags per content type
|
||||
|
||||
## Creating new pages
|
||||
Use `sails generate page <folder>/<name>` — scaffolds controller, view, page script, and LESS file. Then: add the route in `config/routes.js`, add the LESS `@import` to `importer.less`, and update `config/policies.js` if bypassing `is-logged-in` (not needed under folders like `landing-pages/` that already bypass it).
|
||||
|
||||
## Code style
|
||||
|
||||
- **Indentation**: 2 spaces
|
||||
- **Quotes**: Single quotes (template literals allowed)
|
||||
- **Semicolons**: Always required
|
||||
- **Equality**: Strict only (`===` / `!==`)
|
||||
- **Variables/functions**: camelCase
|
||||
- **Files/directories**: kebab-case
|
||||
|
||||
## Development commands
|
||||
|
||||
```bash
|
||||
npm run start-dev # Start dev server with live reload
|
||||
npm run lint # Run ESLint + HTMLHint + lesshint
|
||||
npm run build-for-prod # Compile markdown, build and minify assets
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
- **JS**: ESLint (`.eslintrc` at root, browser override in `assets/.eslintrc`)
|
||||
- **HTML/EJS**: HTMLHint (`.htmlhintrc`)
|
||||
- **LESS**: lesshint (`.lesshintrc`) — zero warnings policy
|
||||
BIN
website/assets/images/app-icon-druva-insync-60x60@2x.png
vendored
Normal file
BIN
website/assets/images/app-icon-druva-insync-60x60@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9 KiB |
BIN
website/assets/images/testimonial-author-thomas-luebker-48x48@2x.png
vendored
Normal file
BIN
website/assets/images/testimonial-author-thomas-luebker-48x48@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 444 B |
|
|
@ -11,3 +11,76 @@
|
|||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Page container mixins (for [purpose='page-container'])
|
||||
// Bundle padding with responsive step-downs
|
||||
// -------------------------------------------
|
||||
|
||||
// Docs pages: all docs/ detail & library pages
|
||||
// Base: 48px, tablet: 32px, mobile: 32px 24px
|
||||
.page-container-docs() {
|
||||
padding: @page-padding-sm;
|
||||
@media (max-width: 991px) {
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
@media (max-width: 575px) {
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
}
|
||||
|
||||
// Standard pages: articles, customers, software-management,
|
||||
// legal/privacy, legal/terms, meetups, case-study, device-management,
|
||||
// deals, admin/query-generator
|
||||
// Base: 64px, tablet: 64px 32px, mobile: 32px 24px
|
||||
.page-container() {
|
||||
padding: @page-padding;
|
||||
@media (max-width: 991px) {
|
||||
padding: @page-padding @page-padding-tablet;
|
||||
}
|
||||
@media (max-width: 575px) {
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
}
|
||||
|
||||
// Wide pages with explicit max-width: basic-article, basic-whitepaper,
|
||||
// legal/privacy, legal/terms, basic-comparison, infrastructure-as-code
|
||||
// Base: max-width 1200px + 64px padding (1072px effective), centered
|
||||
.page-container-wide() {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: @page-padding;
|
||||
@media (max-width: 991px) {
|
||||
padding: @page-padding @page-padding-tablet;
|
||||
}
|
||||
@media (max-width: 575px) {
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Page content mixins (for [purpose='page-content'])
|
||||
// Bundle max-width + centering
|
||||
// -------------------------------------------
|
||||
|
||||
// Docs pages: all docs/ detail & library pages
|
||||
.page-content-docs() {
|
||||
max-width: @content-max-width-docs;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
// Standard product & marketing pages
|
||||
.page-content() {
|
||||
max-width: @content-max-width;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
// Narrow form/error pages
|
||||
.page-content-narrow() {
|
||||
max-width: @content-max-width-narrow;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
|
|
|||
128
website/assets/styles/mixins-and-variables/feature-blocks.less
vendored
Normal file
128
website/assets/styles/mixins-and-variables/feature-blocks.less
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Mixins for feature-block layout patterns.
|
||||
// These handle LAYOUT only — typography, icons, and text alignment inside each
|
||||
// feature are page-specific. Add those overrides on the selectors after calling
|
||||
// the mixin.
|
||||
|
||||
// Side-by-side: an image paired with a text block (headline + paragraph +
|
||||
// checklist or similar). Collapses to a single column below 992px.
|
||||
//
|
||||
// Expected markup:
|
||||
// <div purpose='feature-with-image'> (or add `class='reverse'`)
|
||||
// <div purpose='feature-image'>...</div>
|
||||
// <div purpose='feature-text'>...</div>
|
||||
// </div>
|
||||
//
|
||||
// `feature-image` can contain img OR video — both get `border-radius: 16px` and
|
||||
// scale to fit the parent. Pages set the exact desktop width on the image.
|
||||
.feature-with-image() {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 64px;
|
||||
&.reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
// Cap the image at half the container in row layout so both sides shrink
|
||||
// proportionally on narrow viewports.
|
||||
max-width: 50%;
|
||||
img, video {
|
||||
// Use max-width (not width) so images scale DOWN to fit narrower
|
||||
// containers but never stretch beyond their natural source size.
|
||||
// This avoids blurry upscaling in column mode at ≤768px, where the
|
||||
// container width can exceed the image's intrinsic dimensions.
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
gap: 48px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
&.reverse {
|
||||
flex-direction: column;
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive 3-column grid of feature items.
|
||||
// Expected markup: a parent with [purpose='feature'] children.
|
||||
// Flex-wraps, so any multiple of 3 works (3, 6, 9, etc.).
|
||||
// Stays 3-column down to 768px, then collapses straight to 1-column.
|
||||
// Skipping a 2-column intermediate state avoids orphaned items
|
||||
// (e.g. 3 items → 2 + 1 centered) at tablet widths.
|
||||
.three-column-features() {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 64px;
|
||||
max-width: @content-max-width;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
[purpose='feature'] {
|
||||
flex: 0 1 ~'calc((100% - 2 * 64px) / 3)';
|
||||
min-width: 0;
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
gap: 48px;
|
||||
[purpose='feature'] {
|
||||
flex-basis: ~'calc((100% - 2 * 48px) / 3)';
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
gap: 40px;
|
||||
[purpose='feature'] {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive feature-row: like .three-column-features(), but with a 2-column
|
||||
// intermediate state. Best for grids of 6 (or any multiple of 6) where the
|
||||
// layout divides cleanly at each step, and when individual items are content-
|
||||
// heavy enough that 3-across at tablet widths feels cramped.
|
||||
//
|
||||
// Breakpoints: 3 cols → 2 cols at ≤991px → 1 col (centered) at ≤575px.
|
||||
// Expected markup: same as .three-column-features() — parent with
|
||||
// [purpose='feature'] children.
|
||||
.responsive-feature-row() {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 64px;
|
||||
max-width: @content-max-width;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
[purpose='feature'] {
|
||||
flex: 0 1 ~'calc((100% - 2 * 64px) / 3)';
|
||||
min-width: 0;
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
gap: 48px;
|
||||
[purpose='feature'] {
|
||||
flex-basis: ~'calc((100% - 48px) / 2)';
|
||||
}
|
||||
}
|
||||
@media (max-width: 575px) {
|
||||
gap: 40px;
|
||||
[purpose='feature'] {
|
||||
flex-basis: 100%;
|
||||
text-align: center;
|
||||
img {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
@import 'colors.less';
|
||||
@import 'spacing.less';
|
||||
@import 'typography.less';
|
||||
@import 'buttons.less';
|
||||
@import 'animations.less';
|
||||
|
|
@ -6,3 +7,4 @@
|
|||
@import 'containers.less';
|
||||
@import 'tip-block.less';
|
||||
@import 'checklist.less';
|
||||
@import 'feature-blocks.less';
|
||||
|
|
|
|||
73
website/assets/styles/mixins-and-variables/spacing.less
vendored
Normal file
73
website/assets/styles/mixins-and-variables/spacing.less
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// -------------------------------------------
|
||||
// Spacing variables
|
||||
// Three categories: page, section, inner
|
||||
// Plus content max-widths
|
||||
// -------------------------------------------
|
||||
|
||||
// Page — padding for [purpose='page-container']
|
||||
@page-padding: 64px;
|
||||
@page-padding-sm: 48px;
|
||||
@page-padding-tablet: 32px;
|
||||
@page-padding-mobile: 24px;
|
||||
@page-padding-tablet-mobile: 32px 24px;
|
||||
|
||||
// Section — spacing between and around groups of elements
|
||||
@section-padding: 80px;
|
||||
@section-padding-sm: 64px;
|
||||
@section-gap: 32px;
|
||||
@section-gap-sm: 24px;
|
||||
@section-gap-lg: 120px;
|
||||
|
||||
// Inner — padding inside containers, cards, inputs, buttons
|
||||
@inner-padding: 16px;
|
||||
@inner-padding-sm: 12px;
|
||||
@inner-padding-xs: 8px;
|
||||
@inner-padding-xxs: 4px;
|
||||
|
||||
// -------------------------------------------
|
||||
// Content max-widths for [purpose='page-content']
|
||||
// -------------------------------------------
|
||||
|
||||
// Docs detail & library pages: vital-details, script-details,
|
||||
// query-detail, policy-details, osquery-table-details,
|
||||
// command-details, app-details, script-library, query-library,
|
||||
// policy-library, os-settings, mdm-commands, app-library,
|
||||
// basic-documentation
|
||||
@content-max-width-docs: 1104px;
|
||||
|
||||
// Product & marketing pages: homepage, customers, software-management,
|
||||
// observability, device-management, meetups, case-study, app-details
|
||||
@content-max-width: 1072px;
|
||||
|
||||
// Narrow form pages: okta-conditional-access-error,
|
||||
// microsoft-proxy/remediate, microsoft-proxy/turn-on-mdm, deals
|
||||
@content-max-width-narrow: 528px;
|
||||
|
||||
// Admin pages: admin/query-generator
|
||||
@content-max-width-admin: 800px;
|
||||
|
||||
// -------------------------------------------
|
||||
// Pages with non-standard spacing (won't use these variables as-is):
|
||||
//
|
||||
// /infrastructure-as-code — 160px (section gap)
|
||||
// /transparency — 60px, 30px (card padding)
|
||||
// /handbook/basic-handbook — 75px (quote card padding), 36px (list margins)
|
||||
// /pricing — 15px (card button padding), 1120px page-content max-width
|
||||
// /fleet-premium-trial — 9px (copy button), 10px (form field gaps)
|
||||
// /entrance/signup — 20px (form padding), 128px horizontal padding
|
||||
// /customers/dashboard — 30px (license card padding)
|
||||
// /articles/basic-article — 72px (quote block margin), 6px (form controls)
|
||||
// /articles/basic-whitepaper — 72px (quote block margin)
|
||||
// /articles/case-study — 72px (quote block margin)
|
||||
// /articles/articles — 6px (carousel controls)
|
||||
// /customers — 6px (carousel links), 10px (quote attribution)
|
||||
// /landing-pages/deployment — 10px (icon padding)
|
||||
// /landing-pages/gitops-workshop — 10px (icon/text gaps)
|
||||
// /fast-track — 1080px page-content max-width (could normalize to 1072px)
|
||||
// /transparency — responsive max-widths (960px/840px/480px)
|
||||
// layout.less — 19px, 20px, 23px (nav items), 5px/6px (badges)
|
||||
// call-to-action.component — 60px, 90px, 45px (CTA padding)
|
||||
// docs-nav-and-search.component — 6px, 15px (search controls)
|
||||
// animated-arrow-button.component — 6px (button padding)
|
||||
// scrollable-tweets.component — 20px (mobile card padding)
|
||||
// -------------------------------------------
|
||||
|
|
@ -2,9 +2,57 @@
|
|||
@main-font: 'Inter', sans-serif;
|
||||
@header-font: 'Inter', sans-serif;
|
||||
@navigation-font: 'Inter', sans-serif;
|
||||
|
||||
@kicker-font: 'Roboto Mono';
|
||||
@code-font: 'Source Code Pro', sans-serif;
|
||||
|
||||
// Font weights:
|
||||
@bold: 700;
|
||||
@normal: 400;
|
||||
|
||||
// Body text styles:
|
||||
.body-s() {
|
||||
font-size: 12px;
|
||||
line-height: 150%;
|
||||
font-weight: 400;
|
||||
}
|
||||
.body-m() {
|
||||
font-size: 14px;
|
||||
line-height: 150%;
|
||||
font-weight: 400;
|
||||
}
|
||||
.body-l() {
|
||||
font-size: 16px;
|
||||
line-height: 150%;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
// Title styles:
|
||||
.kicker-title() {
|
||||
font-family: 'Roboto Mono';
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 150%;
|
||||
text-transform: uppercase;
|
||||
color: @core-fleet-black-75;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.title-s() {
|
||||
font-size: 16px;
|
||||
line-height: 150%;
|
||||
font-weight: 400;
|
||||
}
|
||||
.title-m() {
|
||||
font-size: 24px;
|
||||
line-height: 120%;
|
||||
font-weight: 800;
|
||||
}
|
||||
.title-l() {
|
||||
font-size: 32px;
|
||||
line-height: 120%;
|
||||
font-weight: 800;
|
||||
}
|
||||
.title-xl() {
|
||||
font-size: 48px;
|
||||
line-height: 120%;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,14 +150,7 @@
|
|||
flex-direction: column;
|
||||
gap: 32px;
|
||||
h1 {
|
||||
color: #192147;
|
||||
|
||||
/* Heading/H1 */
|
||||
font-family: Inter;
|
||||
font-size: 48px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 120%;
|
||||
.title-xl();
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,7 @@
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
h3 {
|
||||
color: #192147;
|
||||
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 28.8px;
|
||||
.title-m();
|
||||
}
|
||||
p {
|
||||
color: #515774;
|
||||
|
|
|
|||
25
website/assets/styles/pages/customers.less
vendored
25
website/assets/styles/pages/customers.less
vendored
|
|
@ -1,33 +1,14 @@
|
|||
#customers {
|
||||
|
||||
h4 {
|
||||
color: #515774;
|
||||
font-feature-settings: 'salt' on, 'ss01' on, 'ss02' on;
|
||||
font-family: 'Roboto Mono';
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%;
|
||||
text-transform: uppercase;
|
||||
.kicker-title();
|
||||
}
|
||||
h1 {
|
||||
color: #192147;
|
||||
font-family: Inter;
|
||||
font-size: 48px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 120%;
|
||||
.title-xl();
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #192147;
|
||||
|
||||
/* Heading/H2 */
|
||||
font-family: Inter;
|
||||
font-size: 32px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 120%;
|
||||
.title-l();
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
[purpose='page-container'] {
|
||||
|
|
|
|||
290
website/assets/styles/pages/device-management.less
vendored
290
website/assets/styles/pages/device-management.less
vendored
|
|
@ -5,14 +5,10 @@
|
|||
background: linear-gradient(180deg, #E8F1F6 0%, #FFF 8.76%);
|
||||
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-lineheight;
|
||||
.title-xl();
|
||||
}
|
||||
h2 {
|
||||
font-weight: 800;
|
||||
font-size: 32px;
|
||||
line-height: @heading-lineheight;
|
||||
.title-l();
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
h3 {
|
||||
|
|
@ -21,14 +17,7 @@
|
|||
line-height: @heading-lineheight;
|
||||
}
|
||||
h4 {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 150%;
|
||||
text-transform: uppercase;
|
||||
color: @core-fleet-black-75;
|
||||
margin-bottom: 8px;
|
||||
.kicker-title();
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
|
|
@ -505,17 +494,25 @@
|
|||
|
||||
|
||||
|
||||
[purpose='feature'] {
|
||||
[purpose='feature-with-image'] {
|
||||
.feature-with-image();
|
||||
margin-bottom: 100px;
|
||||
h3 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
h3 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
p {
|
||||
font-size: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='feature-note'] {
|
||||
|
|
@ -523,35 +520,6 @@
|
|||
font-size: 12px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
max-width: 528px;
|
||||
img {
|
||||
border-radius: 8px;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 528px;
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column {
|
||||
[purpose='feature-text'] {
|
||||
margin-left: 32px;
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column-reverse {
|
||||
[purpose='feature-text'] {
|
||||
margin-right: 32px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
width: 468px;
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
[purpose='checklist'] {
|
||||
.checklist();
|
||||
}
|
||||
|
|
@ -560,36 +528,11 @@
|
|||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
[purpose='feature-row'] {
|
||||
margin-bottom: 64px;
|
||||
[purpose='feature-column'] {
|
||||
margin-left: 32px;
|
||||
margin-right: 32px;
|
||||
max-width: 440px;
|
||||
p {
|
||||
font-size: 14px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
img {
|
||||
height: 48px;
|
||||
width: auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
h5 {
|
||||
font-weight: 800;
|
||||
font-size: 16px;
|
||||
line-height: @heading-lineheight;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='responsive-feature-row'] {
|
||||
margin-right: 0px;
|
||||
margin-left: 0px;
|
||||
[purpose='feature-item'] {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 64px;
|
||||
[purpose='three-column-features'] {
|
||||
.responsive-feature-row();
|
||||
padding-top: 64px;
|
||||
[purpose='feature'] {
|
||||
margin-bottom: 0;
|
||||
h5 {
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
|
|
@ -602,15 +545,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
[purpose='three-column-features'] {
|
||||
max-width: 1080px;
|
||||
padding-top: 64px;
|
||||
h2 {
|
||||
font-size: 32px;
|
||||
line-height: 48px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
[purpose='button-row'] {
|
||||
a {
|
||||
font-weight: 700;
|
||||
|
|
@ -705,25 +639,6 @@
|
|||
|
||||
@media (max-width: 1199px) {
|
||||
|
||||
[purpose='feature'].flex-column {
|
||||
[purpose='feature-text'] {
|
||||
margin-left: 32px;
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column-reverse {
|
||||
[purpose='feature-text'] {
|
||||
margin-right: 32px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
// max-width: 528px;
|
||||
img {
|
||||
border-radius: 8px;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 455px;
|
||||
}
|
||||
}
|
||||
[purpose='calendar-feature'] {
|
||||
[purpose='feature-video'] {
|
||||
// width: 455px;
|
||||
|
|
@ -764,26 +679,6 @@
|
|||
padding-left: 32px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-row'] {
|
||||
[purpose='feature-column'] {
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
[purpose='responsive-feature-row'] {
|
||||
[purpose='feature-item'] {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 64px;
|
||||
p {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='testimonial-videos'] {
|
||||
width: 410px;
|
||||
}
|
||||
|
|
@ -792,22 +687,6 @@
|
|||
width: 410px;
|
||||
}
|
||||
|
||||
[purpose='feature-text'] {
|
||||
width: 410px;
|
||||
}
|
||||
|
||||
[purpose='feature'].flex-column {
|
||||
[purpose='feature-text'] {
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='feature'].flex-column-reverse {
|
||||
[purpose='feature-text'] {
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='comparison-column'] {
|
||||
max-width: 140px;
|
||||
min-width: 160px;
|
||||
|
|
@ -859,16 +738,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
[purpose='three-column-features'] {
|
||||
[purpose='feature-row'] {
|
||||
[purpose='feature-item'] {
|
||||
margin-left: 32px;
|
||||
margin-right: 32px;
|
||||
max-width: 266px;
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='video-modal'] {
|
||||
[purpose='modal-dialog'] {
|
||||
width: 100%;
|
||||
|
|
@ -893,8 +762,11 @@
|
|||
font-size: 42px;
|
||||
}
|
||||
}
|
||||
[purpose='feature'] {
|
||||
[purpose='feature-with-image'] {
|
||||
margin-bottom: 80px;
|
||||
[purpose='feature-image'] {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='calendar-feature'] {
|
||||
|
|
@ -911,16 +783,6 @@
|
|||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
max-width: 100%;
|
||||
margin-bottom: 64px;
|
||||
img {
|
||||
border-radius: 8px;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
[purpose='calendar-feature'] {
|
||||
flex-direction: column-reverse !important;//lesshint-disable-line importantRule
|
||||
}
|
||||
|
|
@ -947,32 +809,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
[purpose='feature-text'] {
|
||||
width: 510px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
[purpose='feature'].flex-column {
|
||||
[purpose='feature-with-image'] {
|
||||
[purpose='feature-text'] {
|
||||
width: 100%;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column-reverse {
|
||||
[purpose='feature-text'] {
|
||||
width: 100%;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
margin-bottom: 60px;
|
||||
&.left {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
&.right {
|
||||
margin-left: auto;
|
||||
[purpose='feature-image'] {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
}
|
||||
[purpose='hero-image'] {
|
||||
|
|
@ -997,28 +841,15 @@
|
|||
height: 304px;
|
||||
}
|
||||
}
|
||||
[purpose='three-column-features'] {
|
||||
[purpose='feature-row'] {
|
||||
[purpose='feature-item'] {
|
||||
margin-left: 24px;
|
||||
margin-right: 24px;
|
||||
max-width: 266px;
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
[purpose='feature-text'] {
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
margin-bottom: 48px;
|
||||
img {
|
||||
max-width: 100%;
|
||||
[purpose='feature-with-image'] {
|
||||
[purpose='feature-text'] {
|
||||
width: 100%;
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
}
|
||||
[purpose='hero-image'] {
|
||||
|
|
@ -1034,19 +865,6 @@
|
|||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
[purpose='responsive-feature-row'] {
|
||||
[purpose='feature-item'] {
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
margin-bottom: 64px;
|
||||
text-align: center;
|
||||
p {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='page-section'] {
|
||||
padding-top: 48px;
|
||||
padding-bottom: 48px;
|
||||
|
|
@ -1072,37 +890,9 @@
|
|||
}
|
||||
}
|
||||
[purpose='three-column-features'] {
|
||||
[purpose='feature-row'] {
|
||||
[purpose='feature-item'] {
|
||||
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
text-align: center;
|
||||
max-width: 320px;
|
||||
width: 100%;
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='two-column-features'] {
|
||||
[purpose='feature-row'] {
|
||||
margin-bottom: 64px;
|
||||
[purpose='feature-column'] {
|
||||
max-width: unset;
|
||||
margin-bottom: 64px;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
p {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
[purpose='feature'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
[purpose='comparison-section'] {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,10 @@
|
|||
}
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1072px;
|
||||
.page-content();
|
||||
}
|
||||
|
||||
[purpose='breadcrumbs'] {
|
||||
|
|
@ -336,7 +334,7 @@
|
|||
}
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='app-details'] {
|
||||
padding-right: 0px;
|
||||
|
|
@ -359,7 +357,7 @@
|
|||
|
||||
@media (max-width: 575px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
#app-library {
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
max-width: 1104px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
.page-content-docs();
|
||||
}
|
||||
[purpose='search-and-headline'] {
|
||||
margin-bottom: 64px;
|
||||
|
|
@ -192,7 +190,7 @@
|
|||
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='app-card'] {
|
||||
min-width: 40%;
|
||||
|
|
@ -237,7 +235,7 @@
|
|||
|
||||
@media (max-width: 575px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
[purpose='request-button'] {
|
||||
margin-top: 16px;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
|
@ -1094,7 +1092,7 @@
|
|||
// for smaller screens
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='back-to-top-button-container'] {
|
||||
display: none;
|
||||
|
|
@ -1161,7 +1159,7 @@
|
|||
padding: 0px 24px;
|
||||
}
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
[purpose='subtopics-modal'] {
|
||||
.modal.fade .modal-dialog {
|
||||
|
|
|
|||
|
|
@ -20,12 +20,10 @@
|
|||
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
}
|
||||
|
||||
[purpose='breadcrumbs'] {
|
||||
|
|
@ -464,7 +462,7 @@
|
|||
}
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='command-details'] {
|
||||
padding-right: 0px;
|
||||
|
|
@ -494,7 +492,7 @@
|
|||
|
||||
@media (max-width: 575px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
[purpose='supported-platforms'] {
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@
|
|||
padding-inline-start: 16px;
|
||||
}
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
color: #515774;
|
||||
}
|
||||
[purpose='page-headline'] {
|
||||
|
|
@ -354,7 +352,7 @@
|
|||
@media(max-width: 991px) {
|
||||
min-height: unset;
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='sidebar-and-content'] {
|
||||
display: flex;
|
||||
|
|
@ -363,14 +361,14 @@
|
|||
}
|
||||
@media(max-width: 767px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media(max-width: 576px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 16px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@
|
|||
padding-inline-start: 16px;
|
||||
}
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
color: #515774;
|
||||
}
|
||||
[purpose='page-headline'] {
|
||||
|
|
@ -273,7 +271,7 @@
|
|||
@media(max-width: 991px) {
|
||||
min-height: unset;
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='sidebar-and-content'] {
|
||||
display: flex;
|
||||
|
|
@ -282,14 +280,14 @@
|
|||
}
|
||||
@media(max-width: 767px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media(max-width: 576px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 16px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
}
|
||||
[purpose='page-headline'] {
|
||||
// max-width: 662px;
|
||||
|
|
@ -613,7 +611,7 @@
|
|||
}
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='table-container'] {
|
||||
max-width: unset;
|
||||
|
|
@ -671,7 +669,7 @@
|
|||
|
||||
@media (max-width: 576px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
[purpose='mobile-nav'] {
|
||||
padding-left: 24px;
|
||||
|
|
|
|||
|
|
@ -20,12 +20,10 @@
|
|||
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
}
|
||||
|
||||
[purpose='breadcrumbs'] {
|
||||
|
|
@ -462,7 +460,7 @@
|
|||
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='policy-details'] {
|
||||
padding-right: 0px;
|
||||
|
|
@ -492,7 +490,7 @@
|
|||
|
||||
@media (max-width: 575px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
[purpose='policy-check'] {
|
||||
[purpose='codeblock'] {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
#policy-library {
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -332,7 +330,7 @@
|
|||
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='policy-name-and-description'] {
|
||||
max-width: unset;
|
||||
|
|
|
|||
|
|
@ -20,12 +20,10 @@
|
|||
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
}
|
||||
|
||||
[purpose='breadcrumbs'] {
|
||||
|
|
@ -407,7 +405,7 @@
|
|||
}
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='query-details'] {
|
||||
padding-right: 0px;
|
||||
|
|
@ -437,7 +435,7 @@
|
|||
|
||||
@media (max-width: 575px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
[purpose='query-check'] {
|
||||
[purpose='codeblock'] {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
@ -337,7 +335,7 @@
|
|||
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='policy-name-and-description'] {
|
||||
max-width: unset;
|
||||
|
|
|
|||
|
|
@ -20,12 +20,10 @@
|
|||
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
}
|
||||
|
||||
[purpose='breadcrumbs'] {
|
||||
|
|
@ -441,7 +439,7 @@
|
|||
}
|
||||
@media (max-width: 991px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='script-details'] {
|
||||
padding-right: 0px;
|
||||
|
|
@ -471,7 +469,7 @@
|
|||
|
||||
@media (max-width: 575px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
[purpose='script-check'] {
|
||||
[purpose='codeblock'] {
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@
|
|||
padding-inline-start: 16px;
|
||||
}
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
color: #515774;
|
||||
}
|
||||
[purpose='page-headline'] {
|
||||
|
|
@ -330,7 +328,7 @@
|
|||
@media(max-width: 991px) {
|
||||
min-height: unset;
|
||||
[purpose='page-container'] {
|
||||
padding: 32px;
|
||||
padding: @page-padding-tablet;
|
||||
}
|
||||
[purpose='script-name-and-description'] {
|
||||
max-width: unset;
|
||||
|
|
@ -345,14 +343,14 @@
|
|||
}
|
||||
@media(max-width: 767px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 24px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media(max-width: 576px) {
|
||||
[purpose='page-container'] {
|
||||
padding: 32px 16px;
|
||||
padding: @page-padding-tablet-mobile;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,10 @@
|
|||
|
||||
|
||||
[purpose='page-container'] {
|
||||
padding: 48px;
|
||||
padding: @page-padding-sm;
|
||||
}
|
||||
[purpose='page-content'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1104px;
|
||||
.page-content-docs();
|
||||
}
|
||||
[purpose='search-and-headline'] {
|
||||
margin-bottom: 48px;
|
||||
|
|
|
|||
|
|
@ -3,14 +3,7 @@
|
|||
min-height: 800px;
|
||||
|
||||
h3 {
|
||||
color: #192147;
|
||||
|
||||
/* Title M (FKA h3) */
|
||||
font-family: Inter;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 28.8px; /* 120% */
|
||||
.title-m();
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
h4 {
|
||||
|
|
|
|||
159
website/assets/styles/pages/homepage.less
vendored
159
website/assets/styles/pages/homepage.less
vendored
|
|
@ -1,30 +1,18 @@
|
|||
#homepage {
|
||||
h1 {
|
||||
font-weight: 800;
|
||||
font-size: 48px;
|
||||
line-height: 120%;
|
||||
.title-xl();
|
||||
}
|
||||
h3 {
|
||||
font-weight: 800;
|
||||
font-size: 24px;
|
||||
line-height: 120%;
|
||||
.title-m();
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
h2 {
|
||||
font-weight: 800;
|
||||
font-size: 32px;
|
||||
line-height: 120%;
|
||||
.title-l();
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
text-transform: uppercase;
|
||||
font-family: 'Roboto Mono';
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 120%;
|
||||
color: @core-fleet-black-75;
|
||||
margin-bottom: 8px;
|
||||
.kicker-title();
|
||||
}
|
||||
p {
|
||||
color: @core-fleet-black-75;
|
||||
|
|
@ -321,22 +309,8 @@
|
|||
|
||||
|
||||
[purpose='feature-with-image'] {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.feature-with-image();
|
||||
margin-bottom: 160px;
|
||||
gap: 64px;
|
||||
[purpose='feature-image'] {
|
||||
img {
|
||||
width: 578px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
video {
|
||||
width: 578px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-list-block'] {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -363,12 +337,8 @@
|
|||
padding: 1.25px 8px 1.75px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
h5 {
|
||||
color: #192147;
|
||||
font-size: 16px;
|
||||
|
|
@ -387,8 +357,6 @@
|
|||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[purpose='statistics'] {
|
||||
|
|
@ -736,19 +704,9 @@
|
|||
}
|
||||
|
||||
[purpose='three-column-features'] {
|
||||
.three-column-features();
|
||||
margin-bottom: 180px;
|
||||
max-width: 1072px;
|
||||
[purpose='feature-row'] {
|
||||
[purpose='feature'] {
|
||||
max-width: 330px;
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='feature'] {
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
img {
|
||||
height: 64px;
|
||||
width: auto;
|
||||
|
|
@ -760,6 +718,9 @@
|
|||
line-height: 120%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -994,30 +955,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
[purpose='feature-with-image'] {
|
||||
[purpose='feature-image'] {
|
||||
width: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 16px;
|
||||
}
|
||||
video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 16px;
|
||||
}
|
||||
margin-right: 48px;
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
[purpose='feature-block'] {
|
||||
&:last-of-type {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
[purpose='comparison-column'] {
|
||||
max-width: 140px;
|
||||
min-width: 160px;
|
||||
|
|
@ -1179,36 +1116,19 @@
|
|||
}
|
||||
|
||||
[purpose='feature-with-image'] {
|
||||
margin-bottom: 128px;
|
||||
// Between 576 and 768px, text sits above the image. The mixin's column
|
||||
// mode uses DOM order (image first); flip with column-reverse here.
|
||||
flex-direction: column-reverse;
|
||||
&.reverse {
|
||||
flex-direction: column;
|
||||
}
|
||||
margin-bottom: 128px;
|
||||
[purpose='feature-image'] {
|
||||
width: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 16px;
|
||||
}
|
||||
video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 16px;
|
||||
}
|
||||
margin-right: 0px;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
[purpose='feature-list-block'] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
width: 33%;
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-bottom: 0px;
|
||||
padding-top: 0px;
|
||||
[purpose='feature-block'] {
|
||||
width: 33%;
|
||||
}
|
||||
|
|
@ -1217,22 +1137,11 @@
|
|||
|
||||
[purpose='three-column-features'] {
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
[purpose='three-column-features'] {
|
||||
h2 {
|
||||
[purpose='feature'] {
|
||||
text-align: center;
|
||||
}
|
||||
[purpose='feature-row'] {
|
||||
[purpose='feature'] {
|
||||
max-width: 100%;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
margin-bottom: 60px;
|
||||
text-align: center;
|
||||
img {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
img {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1341,48 +1250,24 @@
|
|||
margin-bottom: 48px;
|
||||
}
|
||||
[purpose='feature-with-image'] {
|
||||
// At ≤575px, image goes back above text (overrides the ≤768 reverse).
|
||||
flex-direction: column;
|
||||
&.reverse {
|
||||
flex-direction: column-reverse;
|
||||
flex-direction: column;
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
[purpose='feature-list-block'] {
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 16px;
|
||||
}
|
||||
video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 16px;
|
||||
}
|
||||
margin-right: 0px;
|
||||
// margin-bottom: 48px;
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
flex-direction: column;
|
||||
[purpose='feature-block'] {
|
||||
margin-right: 0px;
|
||||
width: 100%;
|
||||
&:last-of-type {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='feature-list-block'] {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
display: flex;
|
||||
padding-bottom: 0px;
|
||||
padding-top: 0px;
|
||||
[purpose='feature-block'] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='button-row'] {
|
||||
|
|
|
|||
|
|
@ -2,26 +2,13 @@
|
|||
// page-wide heading styles
|
||||
|
||||
h1 {
|
||||
color: #192147;
|
||||
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 48px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 120%;
|
||||
.title-xl();
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #192147;
|
||||
.title-l();
|
||||
scroll-margin-top: 100px;
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 32px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 120%;
|
||||
margin-bottom: 0px;
|
||||
page-break-before: always;
|
||||
|
||||
|
|
@ -39,14 +26,7 @@
|
|||
}
|
||||
|
||||
h4 {
|
||||
color: #515774;
|
||||
font-feature-settings: 'salt' on, 'ss01' on, 'ss02' on;
|
||||
font-family: 'Roboto Mono';
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%;
|
||||
text-transform: uppercase;
|
||||
.kicker-title();
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -299,45 +299,25 @@
|
|||
}
|
||||
|
||||
[purpose='feature-with-image'] {
|
||||
gap: 48px 64px;
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-lineheight;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
max-width: 528px;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 528px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
width: 468px;
|
||||
gap: 32px;
|
||||
h3 {
|
||||
margin-bottom: 0px;
|
||||
color: #192147;
|
||||
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 120%;
|
||||
}
|
||||
p {
|
||||
color: #515774;
|
||||
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%;
|
||||
.feature-with-image();
|
||||
[purpose='feature-text'] {
|
||||
h3 {
|
||||
margin-bottom: 0px;
|
||||
color: #192147;
|
||||
font-family: Inter;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 120%;
|
||||
}
|
||||
p {
|
||||
color: #515774;
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -495,9 +495,8 @@
|
|||
|
||||
|
||||
[purpose='three-column-features'] {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 64px;
|
||||
.three-column-features();
|
||||
max-width: unset;
|
||||
[purpose='feature'] {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
|
|
@ -823,9 +822,6 @@
|
|||
[purpose='banner-container'] {
|
||||
padding: 48px;
|
||||
}
|
||||
[purpose='three-column-features'] {
|
||||
gap: 48px;
|
||||
}
|
||||
[purpose='feature-list-with-image'] {
|
||||
flex-direction: column;
|
||||
gap: @inner-gap-l;
|
||||
|
|
@ -905,10 +901,6 @@
|
|||
align-items: flex-start;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
[purpose='three-column-features'] {
|
||||
flex-direction: column;
|
||||
gap: @inner-gap-m;
|
||||
}
|
||||
[purpose='testimonial-cards'] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
128
website/assets/styles/pages/observability.less
vendored
128
website/assets/styles/pages/observability.less
vendored
|
|
@ -4,9 +4,7 @@
|
|||
|
||||
background: linear-gradient(180deg, #E8F1F6 0%, #FFF 8.76%);
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-line-height;
|
||||
.title-xl();
|
||||
}
|
||||
h2 {
|
||||
font-size: 64px;
|
||||
|
|
@ -338,26 +336,9 @@
|
|||
}
|
||||
}
|
||||
[purpose='three-column-features'] {
|
||||
max-width: 1080px;
|
||||
.responsive-feature-row();
|
||||
margin-bottom: 160px;
|
||||
h2 {
|
||||
font-size: 32px;
|
||||
line-height: 48px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
[purpose='responsive-feature-row'] {
|
||||
margin-right: 0px;
|
||||
margin-left: 0px;
|
||||
[purpose='feature-item'] {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 80px;
|
||||
h5 {
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
font-weight: 800;
|
||||
}
|
||||
[purpose='feature'] {
|
||||
a {
|
||||
color: @core-fleet-black-75;
|
||||
text-decoration: underline;
|
||||
|
|
@ -376,7 +357,6 @@
|
|||
line-height: 1.2;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
|
|
@ -385,42 +365,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
[purpose='feature'] {
|
||||
[purpose='feature-with-image'] {
|
||||
.feature-with-image();
|
||||
margin-bottom: 160px;
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-line-height;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column {
|
||||
[purpose='feature-text'] {
|
||||
margin-left: 48px;
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-line-height;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column-reverse {
|
||||
[purpose='feature-text'] {
|
||||
margin-right: 48px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
max-width: 528px;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 528px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
width: 468px;
|
||||
}
|
||||
|
||||
[purpose='feature-footnote'] {
|
||||
max-width: 480px;
|
||||
font-size: 12px;
|
||||
padding-top: 80px;
|
||||
line-height: 1.5;
|
||||
flex-basis: 100%;
|
||||
p {
|
||||
max-width: 480px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='checklist'] {
|
||||
|
|
@ -428,10 +392,6 @@
|
|||
margin-top: 8px;
|
||||
}
|
||||
|
||||
[purpose='feature-link'] {
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
[purpose='tweets-container'] {
|
||||
margin-top: 200px;
|
||||
max-width: 960px;
|
||||
|
|
@ -527,18 +487,6 @@
|
|||
[purpose='feature-text'] {
|
||||
width: 410px;
|
||||
}
|
||||
[purpose='responsive-feature-row'] {
|
||||
[purpose='feature-item'] {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 80px;
|
||||
p {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
|
@ -602,26 +550,17 @@
|
|||
background: none;
|
||||
}
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
width: unset;
|
||||
}
|
||||
[purpose='feature'].flex-column {
|
||||
[purpose='feature-with-image'] {
|
||||
margin-bottom: 120px;
|
||||
[purpose='feature-text'] {
|
||||
// width: unset;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column-reverse {
|
||||
[purpose='feature-text'] {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
[purpose='feature'] {
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
[purpose='testimonial-videos'] {
|
||||
margin-left: auto;
|
||||
|
|
@ -684,11 +623,6 @@
|
|||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
[purpose='hero-image'] {
|
||||
img {
|
||||
max-width: 100%;
|
||||
|
|
@ -709,20 +643,6 @@
|
|||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
[purpose='responsive-feature-row'] {
|
||||
[purpose='feature-item'] {
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
margin-bottom: 64px;
|
||||
text-align: center;
|
||||
p {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='video-modal'] {
|
||||
[purpose='modal-content'] {
|
||||
width: 95vw;
|
||||
|
|
|
|||
26
website/assets/styles/pages/partners.less
vendored
26
website/assets/styles/pages/partners.less
vendored
|
|
@ -15,35 +15,15 @@
|
|||
@inner-gap-xxs: 8px;
|
||||
padding-bottom: 64px;
|
||||
h1 {
|
||||
color: #192147;
|
||||
/* Heading/H1 */
|
||||
font-family: Inter;
|
||||
font-size: 48px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: @heading-lineheight;
|
||||
.title-xl();
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
h2 {
|
||||
color: #192147;
|
||||
|
||||
/* Heading/H2 */
|
||||
font-family: Inter;
|
||||
font-size: 32px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
line-height: 120%;
|
||||
.title-l();
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
h4 {
|
||||
color: #515774;
|
||||
font-feature-settings: 'salt' on, 'ss01' on, 'ss02' on;
|
||||
font-family: Roboto Mono;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%;
|
||||
text-transform: uppercase;
|
||||
.kicker-title();
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
p {
|
||||
|
|
|
|||
109
website/assets/styles/pages/software-management.less
vendored
109
website/assets/styles/pages/software-management.less
vendored
|
|
@ -3,25 +3,18 @@
|
|||
@text-lineheight: 150%;
|
||||
|
||||
h1 {
|
||||
.title-xl();
|
||||
color: @core-fleet-black;
|
||||
font-size: 48px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-lineheight; /* 120% */
|
||||
}
|
||||
h2 {
|
||||
.title-l();
|
||||
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;
|
||||
.title-m();
|
||||
color: @core-fleet-black;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-lineheight;
|
||||
|
||||
}
|
||||
h4 {
|
||||
color: @core-fleet-black-75;
|
||||
|
|
@ -131,6 +124,25 @@
|
|||
[purpose='feature-slides'] {
|
||||
height: 660px;
|
||||
}
|
||||
[purpose='feature-slide'] {
|
||||
[purpose='feature-image'] {
|
||||
width: 50%;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
width: 50%;
|
||||
h3 {
|
||||
color: @core-fleet-black;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-lineheight; /* 120% */
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='feature-switch'] {
|
||||
padding-top: 64px;
|
||||
margin-right: auto;
|
||||
|
|
@ -163,13 +175,9 @@
|
|||
padding-left: 48px;
|
||||
margin-left: 16px;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
width: 50%;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
h3 {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='checklist'] {
|
||||
|
|
@ -236,36 +244,26 @@
|
|||
}
|
||||
|
||||
|
||||
[purpose='feature'] {
|
||||
[purpose='feature-with-image'] {
|
||||
.feature-with-image();
|
||||
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-image'] {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column-reverse {
|
||||
[purpose='feature-text'] {
|
||||
margin-right: 16px;
|
||||
padding-right: 48px;
|
||||
width: 50%;
|
||||
h3 {
|
||||
color: @core-fleet-black;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-lineheight; /* 120% */
|
||||
}
|
||||
}
|
||||
}
|
||||
[purpose='feature-text'] {
|
||||
width: 50%;
|
||||
}
|
||||
[parasails-component='scrollable-tweets'] {
|
||||
[purpose='tweets'] {
|
||||
margin-top: 32px;
|
||||
|
|
@ -329,15 +327,9 @@
|
|||
width: 50%;
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column {
|
||||
[purpose='feature-with-image'] {
|
||||
[purpose='feature-text'] {
|
||||
margin-left: 16px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
[purpose='feature'].flex-column-reverse {
|
||||
[purpose='feature-text'] {
|
||||
margin-right: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
|
|
@ -404,33 +396,26 @@
|
|||
width: 100%;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
[purpose='feature-image'] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
[purpose='testimonial'] {
|
||||
padding-bottom: 64px;
|
||||
padding-top: 0px;
|
||||
}
|
||||
[purpose='feature'].flex-column {
|
||||
[purpose='feature-with-image'] {
|
||||
padding-top: 32px;
|
||||
padding-bottom: 48px;
|
||||
[purpose='feature-text'] {
|
||||
margin-left: 0px;
|
||||
width: 100%;
|
||||
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='feature-image'] {
|
||||
width: 100%;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
[purpose='bottom-cta'] {
|
||||
h2 {
|
||||
|
|
|
|||
43
website/assets/styles/pages/transparency.less
vendored
43
website/assets/styles/pages/transparency.less
vendored
|
|
@ -7,21 +7,15 @@
|
|||
@page-container-max-width-md: 480px;
|
||||
|
||||
h1 {
|
||||
color: #192147;
|
||||
font-size: 48px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-lineheight; /* 120% */
|
||||
.title-xl();
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
line-height: @heading-lineheight;
|
||||
.title-l();
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
line-height: @heading-lineheight;
|
||||
.title-m();
|
||||
}
|
||||
h4 {
|
||||
font-weight: 700;
|
||||
|
|
@ -182,37 +176,6 @@
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
[purpose='three-column-features'] {
|
||||
max-width: 1080px;
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
[purpose='feature-row'] {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
[purpose='feature-column'] {
|
||||
max-width: 267px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
p {
|
||||
font-size: 14px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
img {
|
||||
height: 48px;
|
||||
width: auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
h5 {
|
||||
font-weight: 800;
|
||||
font-size: 16px;
|
||||
line-height: @heading-lineheight;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[purpose='agents-note'] {
|
||||
color: #515774;
|
||||
font-size: 16px;
|
||||
|
|
|
|||
101
website/views/pages/device-management.ejs
vendored
101
website/views/pages/device-management.ejs
vendored
|
|
@ -53,14 +53,14 @@
|
|||
|
||||
<%/* Modern change management section */%>
|
||||
<div purpose="page-section" class="pb-0 mt-5">
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column justify-content-betweenmx-auto align-items-center">
|
||||
<div purpose="feature-image" class="right">
|
||||
<div purpose="feature-with-image">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Modern change management" src="/images/mdm-modern-change-management-528x377@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-text">
|
||||
<h2>Modern change management</h2>
|
||||
<p>Ship device management changes in minutes from the tools your team already uses, like Slack or Microsoft Teams. No UI, code editor, or Git client required.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p>Create updates from code or chat prompts.</p>
|
||||
<p>Validate and peer-review changes before they reach your devices.</p>
|
||||
<p>Track everything with version control and a complete audit history.</p>
|
||||
|
|
@ -71,41 +71,36 @@
|
|||
</div>
|
||||
|
||||
<div purpose="page-section">
|
||||
<div purpose="three-column-features" class="mx-auto">
|
||||
<div purpose="responsive-feature-row" class="row d-flex flex-sm-row flex-column justify-content-lg-between justify-content-center align-items-center align-items-sm-start">
|
||||
<div purpose="feature-item" class="pl-0 col-12 col-sm-6 col-lg-4">
|
||||
<h5>Operating systems</h5>
|
||||
<p>Manage Apple, Windows, Linux, and Android devices in one place. Fleet supports diverse, mixed environments with no vendor lock-in.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 col-12 col-sm-6 col-lg-4" >
|
||||
<h5>Automated enrollment</h5>
|
||||
<p>Drop ship devices with Apple Business Manager or Autopilot. End users can set up their own devices. No IT help needed.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pl-0 pl-lg-4 pr-lg-0 col-12 col-sm-6 col-lg-4">
|
||||
<h5>Config management</h5>
|
||||
<p>Bring devices into a secure, predictable state, no matter where they start. Define, target, and deploy the right settings to the right devices.</p>
|
||||
</div>
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 pl-lg-0 col-12 col-sm-6 col-lg-4">
|
||||
<h5>Software management</h5>
|
||||
<p>Keep applications and plugins secure and up-to-date automatically. Install the software end users need or let them install it themselves via self service.</p>
|
||||
<p><a href="/software-management">More about software management</a></p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pl-0 pl-lg-4 col-12 col-sm-6 col-lg-4">
|
||||
<h5>Custom scripts</h5>
|
||||
<p>Run the right script at the right time, across any platform. Remediate drift, gather diagnostics, and automate fleet-wide tasks.</p>
|
||||
<p><a href="/orchestration">More about orchestration</a></p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 col-12 col-sm-6 col-lg-4">
|
||||
<h5>Flexible by design</h5>
|
||||
<p>Manage devices via Fleet’s UI, API, or infrastructure as code. The open-source community helps extend what’s possible, from platform support to deployment workflows.</p>
|
||||
</div>
|
||||
<div purpose="three-column-features">
|
||||
<div purpose="feature">
|
||||
<h5>Operating systems</h5>
|
||||
<p>Manage Apple, Windows, Linux, and Android devices in one place. Fleet supports diverse, mixed environments with no vendor lock-in.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-row" class="row d-flex flex-sm-row flex-column justify-content-lg-between justify-content-center mb-0 align-items-center align-items-sm-start">
|
||||
<div purpose="feature">
|
||||
<h5>Automated enrollment</h5>
|
||||
<p>Drop ship devices with Apple Business Manager or Autopilot. End users can set up their own devices. No IT help needed.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<h5>Config management</h5>
|
||||
<p>Bring devices into a secure, predictable state, no matter where they start. Define, target, and deploy the right settings to the right devices.</p>
|
||||
</div>
|
||||
<div purpose="feature">
|
||||
<h5>Software management</h5>
|
||||
<p>Keep applications and plugins secure and up-to-date automatically. Install the software end users need or let them install it themselves via self service.</p>
|
||||
<p><a href="/software-management">More about software management</a></p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<h5>Custom scripts</h5>
|
||||
<p>Run the right script at the right time, across any platform. Remediate drift, gather diagnostics, and automate fleet-wide tasks.</p>
|
||||
<p><a href="/orchestration">More about orchestration</a></p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<h5>Flexible by design</h5>
|
||||
<p>Manage devices via Fleet’s UI, API, or infrastructure as code. The open-source community helps extend what’s possible, from platform support to deployment workflows.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -122,31 +117,31 @@
|
|||
|
||||
<%/* Shorten the feedback loop section */%>
|
||||
<div purpose="page-section">
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column-reverse justify-content-between mx-auto align-items-center">
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-with-image" class="reverse">
|
||||
<div purpose="feature-image">
|
||||
<img alt="A laptop using Fleet to accurately display the status of MDM configurations" src="/images/mdm-shorten-feedback-loop-528x377@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text">
|
||||
<h2>Shorten the feedback loop</h2>
|
||||
<p>Built for speed and transparency. Stop waiting for scripts or reports to finish.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p><a href="/orchestration">Get near-instant visibility</a> into your Apple, Linux, and Windows devices from a single, open platform.</p>
|
||||
<p>Run reports to verify changes in real time with full audit logs.</p>
|
||||
<p>Reduce audit busywork. Some teams spend up to 20% of their time gathering audit evidence. Fleet helps you answer those questions instantly.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="feature-image">
|
||||
<img alt="A laptop using Fleet to accurately display the status of MDM configurations" src="/images/mdm-shorten-feedback-loop-528x377@2x.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%/* Deploy anywhere you want section */%>
|
||||
<div purpose="page-section">
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column justify-content-between mx-auto align-items-center">
|
||||
<div purpose="feature-image" class="right">
|
||||
<div purpose="feature-with-image">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Deploy anywhere you want" src="/images/flexible-deployment-528x377@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-text">
|
||||
<h2>Deploy anywhere, move between environments anytime</h2>
|
||||
<p>Run Fleet on-prem, in the cloud, or air-gapped. No lock-in or black boxes.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p>Change deployment models without rebuilding your stack.</p>
|
||||
<p>No hidden dependencies or forced cloud services.</p>
|
||||
<p>Flexible APIs and open data formats.</p>
|
||||
|
|
@ -157,20 +152,20 @@
|
|||
</div>
|
||||
<%/* Scope transparency section */%>
|
||||
<div purpose="page-section">
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column-reverse justify-content-between mx-auto align-items-center">
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-with-image" class="reverse">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Transparency for end users" src="/images/mdm-scope-transparency-528x377@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text">
|
||||
<h2>Scope transparency</h2>
|
||||
<p>Increase <a href="https://fleetdm.com/handbook/company/why-this-way#why-open-source">buy-in</a> from end users by clarifying how their devices are managed.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p>Give end users assurance about what actions IT can take on their computer.</p>
|
||||
<p>Let end users see exactly what data is collected, all the way down to the source code.</p>
|
||||
<p>Support regional privacy rules with configurable management controls.</p>
|
||||
<p>Set clear expectations about how to configure company devices.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="feature-image" class="right">
|
||||
<img alt="Transparency for end users" src="/images/mdm-scope-transparency-528x377@2x.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
42
website/views/pages/homepage.ejs
vendored
42
website/views/pages/homepage.ejs
vendored
|
|
@ -103,7 +103,9 @@
|
|||
<p>Manage devices your way, without vendor limits. Fleet gives you full control and supports the choices that work for your organization.</p>
|
||||
</div>
|
||||
<div purpose="feature-with-image" class="reverse">
|
||||
|
||||
<div purpose="feature-image">
|
||||
<video autoplay loop playsinline muted purpose="calendar-video"><source src="/videos/features-loop.mp4" type="video/mp4"></video>
|
||||
</div>
|
||||
<div purpose="feature-text">
|
||||
<div purpose="feature-block">
|
||||
<h5>All features, flexible deployment</h5>
|
||||
|
|
@ -118,10 +120,6 @@
|
|||
<p>Let employees use the OS they need, without compromising management and security.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-image">
|
||||
<video autoplay loop playsinline muted purpose="calendar-video"><source src="/videos/features-loop.mp4" type="video/mp4"></video>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="homepage-text-block">
|
||||
<h2>See reality clearly</h2>
|
||||
|
|
@ -1442,27 +1440,23 @@
|
|||
<p>At Fleet, everyone can contribute. We are dedicated to making tools that are easy for everyone to understand.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="three-column-features" class="mx-auto">
|
||||
<div purpose="feature-row" class="d-flex flex-md-row flex-column align-items-start justify-content-center">
|
||||
<div purpose="three-column-features">
|
||||
<div purpose="feature">
|
||||
<img alt="transparency" src="/images/homepage-icon-transparency-54x64@2x.png">
|
||||
<h5>"Why is this running on my computer?"</h5>
|
||||
<p>Let end users see the source code for exactly <a href="/better">how they are being monitored</a>, and set clear expectations about what is and isn't acceptable use of work computers.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature" class="ml-sm-0">
|
||||
<img alt="transparency" src="/images/homepage-icon-transparency-54x64@2x.png" class="mx-auto mx-md-0">
|
||||
<h5>"Why is this running on my computer?"</h5>
|
||||
<p>Let end users see the source code for exactly <a href="/better">how they are being monitored</a>, and set clear expectations about what is and isn't acceptable use of work computers.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="Free as in free" src="/images/homepage-icon-free-48x64@2x.png">
|
||||
<h5>Free as in free</h5>
|
||||
<p>The <a href="https://fleetdm.com/docs/get-started/faq#what-is-your-commitment-to-open-source-stewardship">free version of Fleet</a> will always be free. Fleet is independently backed and actively maintained with the help of many amazing <a target="_blank" href="https://github.com/fleetdm/fleet/graphs/contributors">contributors</a>.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature" class="mr-sm-0 mb-0">
|
||||
<img alt="An open book" src="/images/homepage-icon-openness-80x64@2x.png">
|
||||
<h5>Open source</h5>
|
||||
<p>All of Fleet's source code is <a href="/handbook/company/why-this-way#why-open-source">public</a>, and our company <a href="/handbook">handbook</a> is public and open to the world. You can read about the <a href="/handbook/company#history">history of Fleet and osquery</a> and our commitment to improving the product.</p>
|
||||
</div>
|
||||
<div purpose="feature">
|
||||
<img alt="Free as in free" src="/images/homepage-icon-free-48x64@2x.png">
|
||||
<h5>Free as in free</h5>
|
||||
<p>The <a href="https://fleetdm.com/docs/get-started/faq#what-is-your-commitment-to-open-source-stewardship">free version of Fleet</a> will always be free. Fleet is independently backed and actively maintained with the help of many amazing <a target="_blank" href="https://github.com/fleetdm/fleet/graphs/contributors">contributors</a>.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="An open book" src="/images/homepage-icon-openness-80x64@2x.png">
|
||||
<h5>Open source</h5>
|
||||
<p>All of Fleet's source code is <a href="/handbook/company/why-this-way#why-open-source">public</a>, and our company <a href="/handbook">handbook</a> is public and open to the world. You can read about the <a href="/handbook/company#history">history of Fleet and osquery</a> and our commitment to improving the product.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -42,14 +42,14 @@
|
|||
</div>
|
||||
|
||||
<div purpose="landing-page-section">
|
||||
<div purpose="feature-with-image" class="d-flex flex-md-row flex-column justify-content-between mx-auto align-items-center">
|
||||
<div purpose="feature-with-image">
|
||||
<div purpose="feature-image">
|
||||
<img alt="A screenshot of Fleet showing scripts that can be run on a Linux device." src="/images/linux-management-feature-image-scripts-504x378@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-text">
|
||||
<h3>Manage Linux with the same visibility and control as other devices</h3>
|
||||
<p>Use Fleet to manage Linux devices at scale. Apply the same workflows you use for macOS and Windows.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p>Run scripts remotely to automate installs, updates, and configuration</p>
|
||||
<p>Manage software across Debian-based and RPM-based distributions</p>
|
||||
<p>Monitor hardware, software, and compliance in real time</p>
|
||||
|
|
|
|||
240
website/views/pages/observability.ejs
vendored
240
website/views/pages/observability.ejs
vendored
|
|
@ -75,67 +75,74 @@
|
|||
<p>Use a live connection to every endpoint to simplify audit, compliance, and reporting from workstations to data centers.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="three-column-features" class="mx-auto">
|
||||
<div purpose="responsive-feature-row" class="row d-flex flex-sm-row flex-column justify-content-lg-between justify-content-center align-items-center align-items-sm-start">
|
||||
<div purpose="feature-item" class="pl-0 col-12 col-sm-6 col-lg-4">
|
||||
<img alt="Software and asset inventory" src="/images/icon-live-connection-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Live connection</h5>
|
||||
<p>Talk to online devices in real time with Fleet’s live query API. Implement custom workflows like conditional access based on device posture, Identity, and more.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 col-12 col-sm-6 col-lg-4" >
|
||||
<img alt="Software and asset inventory" src="/images/icon-software-and-asset-inventory-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Software and asset inventory</h5>
|
||||
<p>Get visibility into all endpoints across any operating system*, including support for servers and containers in every cloud infrastructure.</p>
|
||||
<p><a href="/software-management">More about software management</a></p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pl-0 pl-lg-4 pr-lg-0 col-12 col-sm-6 col-lg-4">
|
||||
<img alt="See logins for every endpoint" src="/images/icon-see-logins-for-every-endpoint-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>See logins for every endpoint</h5>
|
||||
<p>Identify who logs in to any system, including login history and current sessions. Look up any computer by the email address of the person using it.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 pl-lg-0 col-12 col-sm-6 col-lg-4 mb-lg-0">
|
||||
<img alt="CIS benchmarks" src="/images/icon-cis-benchmarks-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>CIS benchmarks</h5>
|
||||
<p>Keep all your endpoints* compliant with customizable baselines, or use common benchmarks like CIS.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pl-0 pl-lg-4 col-12 col-sm-6 col-lg-4 mb-sm-0">
|
||||
<img alt="Verify updates and settings" src="/images/icon-edr-health-check-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>EDR health checks</h5>
|
||||
<p>Verify that your EDR tools are installed and working so you can identify and address configuration issues quickly.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 col-12 col-sm-6 col-lg-4 mb-0">
|
||||
<img alt="Verify updates and settings" src="/images/icon-verify-updates-and-settings-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Verify updates and settings</h5>
|
||||
<p>Track progress towards deadlines for security posture remediation projects, and enforce due dates through automations.</p>
|
||||
</div>
|
||||
<div purpose="three-column-features">
|
||||
<div purpose="feature">
|
||||
<img alt="Software and asset inventory" src="/images/icon-live-connection-48x48@2x.png">
|
||||
<h5>Live connection</h5>
|
||||
<p>Talk to online devices in real time with Fleet’s live query API. Implement custom workflows like conditional access based on device posture, Identity, and more.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="Software and asset inventory" src="/images/icon-software-and-asset-inventory-48x48@2x.png">
|
||||
<h5>Software and asset inventory</h5>
|
||||
<p>Get visibility into all endpoints across any operating system*, including support for servers and containers in every cloud infrastructure.</p>
|
||||
<p><a href="/software-management">More about software management</a></p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="See logins for every endpoint" src="/images/icon-see-logins-for-every-endpoint-48x48@2x.png">
|
||||
<h5>See logins for every endpoint</h5>
|
||||
<p>Identify who logs in to any system, including login history and current sessions. Look up any computer by the email address of the person using it.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="CIS benchmarks" src="/images/icon-cis-benchmarks-48x48@2x.png">
|
||||
<h5>CIS benchmarks</h5>
|
||||
<p>Keep all your endpoints* compliant with customizable baselines, or use common benchmarks like CIS.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="Verify updates and settings" src="/images/icon-edr-health-check-48x48@2x.png">
|
||||
<h5>EDR health checks</h5>
|
||||
<p>Verify that your EDR tools are installed and working so you can identify and address configuration issues quickly.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="Verify updates and settings" src="/images/icon-verify-updates-and-settings-48x48@2x.png">
|
||||
<h5>Verify updates and settings</h5>
|
||||
<p>Track progress towards deadlines for security posture remediation projects, and enforce due dates through automations.</p>
|
||||
</div>
|
||||
<div purpose="feature-footnote">
|
||||
<p>*Currently limited to: Apple, Linux, Windows, Chromebooks, OT, data centers, Amazon Web Services (AWS), Google Cloud (GCP), and the Microsoft Cloud (Azure).</p>
|
||||
</div>
|
||||
<p purpose="feature-footnote">*Currently limited to: Apple, Linux, Windows, Chromebooks, OT, data centers, Amazon Web Services (AWS), Google Cloud (GCP), and the Microsoft Cloud (Azure).</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column justify-content-between mx-auto align-items-center">
|
||||
<div purpose="feature-with-image">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Orchestrate anything" src="/images/feature-image-orchestrate-anything-528x377@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<h3>Orchestrate anything</h3>
|
||||
<p>Remote-control IT tasks on every kind of computer – even you, Linux.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="feature-text">
|
||||
<div>
|
||||
<h3>Orchestrate anything</h3>
|
||||
<p class="mb-0">Remote-control IT tasks on every kind of computer – even you, Linux.</p>
|
||||
</div>
|
||||
<div purpose="checklist">
|
||||
<p>Write and run scripts remotely, report progress, and replay queued up tasks on computers that went offline.</p>
|
||||
<p>Optionally integrate Google Calendar to make changes when certain users’ devices are actually free.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column-reverse justify-content-between mx-auto align-items-center">
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<h3>Deep context from your environment</h3>
|
||||
<p>Get device context down to the chip level on every endpoint to help you make the right decisions.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="feature-with-image" class="reverse">
|
||||
<div purpose="feature-image">
|
||||
<img alt="On-demand data" src="/images/feature-image-on-demand-data-528x377@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text">
|
||||
<div>
|
||||
<h3>Deep context from your environment</h3>
|
||||
<p class="mb-0">Get device context down to the chip level on every endpoint to help you make the right decisions.</p>
|
||||
</div>
|
||||
<div purpose="checklist">
|
||||
<p>Gather real-time data by executing lightweight queries across all devices.</p>
|
||||
<p>Access over 300 tables of system state data. Use presets or create your own queries.</p>
|
||||
</div>
|
||||
|
|
@ -143,19 +150,18 @@
|
|||
<animated-arrow-button href="/tables">Explore data</animated-arrow-button>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="feature-image">
|
||||
<img alt="On-demand data" src="/images/feature-image-on-demand-data-528x377@2x.png">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column justify-content-between mx-auto align-items-center">
|
||||
<div purpose="feature-with-image">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Data portability" src="/images/feature-image-data-portability-528x377@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<h3>Data portability</h3>
|
||||
<p>Ship data to any platform like Splunk, Snowflake, or any streaming infrastructure like AWS Kinesis and Apache Kafka.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="feature-text">
|
||||
<div>
|
||||
<h3>Data portability</h3>
|
||||
<p class="mb-0">Ship data to any platform like Splunk, Snowflake, or any streaming infrastructure like AWS Kinesis and Apache Kafka.</p>
|
||||
</div>
|
||||
<div purpose="checklist">
|
||||
<p>Extract data and correlate it with your log aggregator, SIEM, or data lake.</p>
|
||||
<p>Ease your logging burden, pull the data you need.</p>
|
||||
</div>
|
||||
|
|
@ -169,37 +175,37 @@
|
|||
<h3>“Zero” trust, fewer tickets</h3>
|
||||
<p>You can use Fleet’s API to customize every aspect of conditional access – even the stuff your CISO hasn’t thought of yet.</p>
|
||||
</div>
|
||||
<div purpose="three-column-features" class="mx-auto">
|
||||
<div purpose="responsive-feature-row" class="row d-flex flex-sm-row flex-column justify-content-lg-between justify-content-sm-start justify-content-center align-items-center align-items-sm-start">
|
||||
<div purpose="feature-item" class="pl-0 col-12 col-sm-6 col-lg-4">
|
||||
<img alt="Get in front of the IdP" src="/images/device-management-icon-control-login-experience-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Get in front of the IdP</h5>
|
||||
<p>Gate access with <a href="https://fleetdm.com/queries">common device trust policies</a> from industry peers, or roll out your own device health checks using system data and events.</p>
|
||||
</div>
|
||||
<div purpose="three-column-features">
|
||||
<div purpose="feature">
|
||||
<img alt="Get in front of the IdP" src="/images/device-management-icon-control-login-experience-48x48@2x.png">
|
||||
<h5>Get in front of the IdP</h5>
|
||||
<p>Gate access with <a href="https://fleetdm.com/queries">common device trust policies</a> from industry peers, or roll out your own device health checks using system data and events.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 col-12 col-sm-6 col-lg-4" >
|
||||
<img alt="Step-by-step instructions" src="/images/device-management-icon-step-by-step-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Step-by-step instructions</h5>
|
||||
<p>Show resolution steps or use <a href="https://medium.com/pinterest-engineering/enforcing-device-authn-compliance-at-pinterest-a74938cb089b" target="_blank">custom HTML</a> to show employees what they need to do to restore their access without waiting on a ticket. Then give it back automatically, as soon as the problems are fixed.</p>
|
||||
</div>
|
||||
<div purpose="feature">
|
||||
<img alt="Step-by-step instructions" src="/images/device-management-icon-step-by-step-48x48@2x.png">
|
||||
<h5>Step-by-step instructions</h5>
|
||||
<p>Show resolution steps or use <a href="https://medium.com/pinterest-engineering/enforcing-device-authn-compliance-at-pinterest-a74938cb089b" target="_blank">custom HTML</a> to show employees what they need to do to restore their access without waiting on a ticket. Then give it back automatically, as soon as the problems are fixed.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pl-0 pl-lg-4 pr-lg-0 col-12 col-sm-6 col-lg-4 mb-0">
|
||||
<img alt="Manage device posture" src="/images/device-management-icon-manage-device-posture-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Live retry</h5>
|
||||
<p>Give people a way to get back to work quickly and minimize downtime. Fleet’s live query API instantly re-checks the OS version and other device state so users don’t get locked out.</p>
|
||||
</div>
|
||||
<div purpose="feature">
|
||||
<img alt="Manage device posture" src="/images/device-management-icon-manage-device-posture-48x48@2x.png">
|
||||
<h5>Live retry</h5>
|
||||
<p>Give people a way to get back to work quickly and minimize downtime. Fleet’s live query API instantly re-checks the OS version and other device state so users don’t get locked out.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column justify-content-between mx-auto align-items-center">
|
||||
<div purpose="feature-with-image">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Osquery on easy mode" src="/images/endpoint-ops-feature-image-2-380x380@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<h3>Osquery on easy mode</h3>
|
||||
<p>Accelerate deployment and get more out of osquery. You don’t need to be an osquery expert to get the answers you need from your endpoints.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="feature-text">
|
||||
<div>
|
||||
<h3>Osquery on easy mode</h3>
|
||||
<p class="mb-0">Accelerate deployment and get more out of osquery. You don’t need to be an osquery expert to get the answers you need from your endpoints.</p>
|
||||
</div>
|
||||
<div purpose="checklist">
|
||||
<p>Remotely disable/enable agent features, choose plugins, and keep osquery up to date.</p>
|
||||
<p>Import community queries from other security teams at top brands like Palantir and Fastly.</p>
|
||||
<p>Implement the Center for Internet Security (CIS) benchmarks (one click, 400+ queries, supported by Fleet). Or customize exactly the queries you need.</p>
|
||||
|
|
@ -213,45 +219,45 @@
|
|||
<p>Consolidate your security tooling on top of open data standards like YAML, SQL, and JSON.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="three-column-features" class="mx-auto">
|
||||
<div purpose="responsive-feature-row" class="row d-flex flex-sm-row flex-column justify-content-lg-between justify-content-center align-items-center align-items-sm-start">
|
||||
<div purpose="feature-item" class="pl-0 col-12 col-sm-6 col-lg-4">
|
||||
<img alt="Incident response (IR)" src="/images/icon-incident-response-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Incident response (IR)</h5>
|
||||
<p>Contain and recover from breaches using live data, remote commands, and automated workflows.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 col-12 col-sm-6 col-lg-4" >
|
||||
<img alt="Detection and response" src="/images/icon-detection-and-response-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Detection and response</h5>
|
||||
<p>Ship logs and alerts when unusual behavior is detected. Run scripts on demand or when alerts trigger.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pl-0 pl-lg-4 pr-lg-0 col-12 col-sm-6 col-lg-4">
|
||||
<img alt="File access monitoring (FIM)" src="/images/icon-fim-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>File access monitoring (FIM)</h5>
|
||||
<p>Specify files to monitor for changes or deletions, then log those events to your SIEM or data lake.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 pl-lg-0 col-12 col-sm-6 col-lg-4 mb-lg-0">
|
||||
<img alt="Attack surface management" src="/images/icon-attack-surface-management-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Security posture</h5>
|
||||
<p>Identify security misconfigurations and vulnerabilities and prioritize risks that matter to your organization.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pl-0 pl-lg-4 col-12 col-sm-6 col-lg-4 mb-sm-0">
|
||||
<img alt="Malware detection" src="/images/icon-malware-detection-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Threat management</h5>
|
||||
<p>Use attack signatures from threat intelligence sources to scan and resolve indicators of compromise (IOC).</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature-item" class="pr-0 pr-lg-4 col-12 col-sm-6 col-lg-4 mb-0">
|
||||
<img alt="Osquery made easy" src="/images/icon-osquery-made-easy-48x48@2x.png" class="mx-auto mx-sm-0">
|
||||
<h5>Osquery made easy</h5>
|
||||
<p>Collect exactly the data you need from your production infrastructure across every cloud and data center*.</p>
|
||||
</div>
|
||||
<div purpose="three-column-features">
|
||||
<div purpose="feature">
|
||||
<img alt="Incident response (IR)" src="/images/icon-incident-response-48x48@2x.png">
|
||||
<h5>Incident response (IR)</h5>
|
||||
<p>Contain and recover from breaches using live data, remote commands, and automated workflows.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="Detection and response" src="/images/icon-detection-and-response-48x48@2x.png">
|
||||
<h5>Detection and response</h5>
|
||||
<p>Ship logs and alerts when unusual behavior is detected. Run scripts on demand or when alerts trigger.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="File access monitoring (FIM)" src="/images/icon-fim-48x48@2x.png">
|
||||
<h5>File access monitoring (FIM)</h5>
|
||||
<p>Specify files to monitor for changes or deletions, then log those events to your SIEM or data lake.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="Attack surface management" src="/images/icon-attack-surface-management-48x48@2x.png">
|
||||
<h5>Security posture</h5>
|
||||
<p>Identify security misconfigurations and vulnerabilities and prioritize risks that matter to your organization.</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="Malware detection" src="/images/icon-malware-detection-48x48@2x.png">
|
||||
<h5>Threat management</h5>
|
||||
<p>Use attack signatures from threat intelligence sources to scan and resolve indicators of compromise (IOC).</p>
|
||||
</div>
|
||||
|
||||
<div purpose="feature">
|
||||
<img alt="Osquery made easy" src="/images/icon-osquery-made-easy-48x48@2x.png">
|
||||
<h5>Osquery made easy</h5>
|
||||
<p>Collect exactly the data you need from your production infrastructure across every cloud and data center*.</p>
|
||||
</div>
|
||||
<div purpose="feature-footnote">
|
||||
<p>*Companies like Fastly and Gusto use Fleet in production with hundreds of thousands of endpoints, including containers, OT, and laptops.</p>
|
||||
</div>
|
||||
<p purpose="feature-footnote">*Companies like Fastly and Gusto use Fleet in production with hundreds of thousands of endpoints, including containers, OT, and laptops.</p>
|
||||
</div>
|
||||
<% }%>
|
||||
</div>
|
||||
|
|
|
|||
42
website/views/pages/software-management.ejs
vendored
42
website/views/pages/software-management.ejs
vendored
|
|
@ -61,14 +61,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column justify-content-center mx-auto align-items-center">
|
||||
<div purpose="feature-with-image">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Deploy software your own way" src="/images/feature-image-fleet-maintained-software-1056x754@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-text">
|
||||
<h3>Deploy software your own way</h3>
|
||||
<p>Deploy software from any source using the method that works best for you—whether it's through the UI, API, or GitOps.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p>Install from a built-in catalog of Fleet-maintained apps without the need for additional configuration.</p>
|
||||
<p>Upload any third-party software as a custom package to deploy all of the tools your end users need.</p>
|
||||
<p>Manage app deployments and updates for your computers and mobile devices from app stores.</p>
|
||||
|
|
@ -76,29 +76,29 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column-reverse justify-content-center mx-auto align-items-center">
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-with-image" class="reverse">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Automate patch management" src="/images/feature-image-automate-patch-management-1056x754@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text">
|
||||
<h3>Automate patch management</h3>
|
||||
<p>Take the guesswork out of keeping devices up-to-date and secure by asking yes or no questions tied to automations.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p>Use policy automations to trigger app updates, execute scripts, or create tickets in Jira and Zendesk.</p>
|
||||
<p>Generate webhooks to integrate with your favorite tools and systems.</p>
|
||||
<p>Optionally integrate Google Calendar to schedule time for your users to update software when it’s convenient for them.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="feature-image">
|
||||
<img alt="Automate patch management" src="/images/feature-image-automate-patch-management-1056x754@2x.png">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column justify-content-center mx-auto align-items-center">
|
||||
<div purpose="feature-with-image">
|
||||
<div purpose="feature-image">
|
||||
<img alt="The right software at the right moment" src="/images/feature-image-self-service-1056x754@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-text">
|
||||
<h3>The right software at the right moment</h3>
|
||||
<p>Use self-service to keep employees productive and secure with software curated to their needs.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p>Install organization-approved software for macOS, Windows, and Linux.</p>
|
||||
<p>Quickly deliver software for teams of any size, tailored to small groups or the entire organization.</p>
|
||||
</div>
|
||||
|
|
@ -175,28 +175,28 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column-reverse justify-content-center mx-auto align-items-center">
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-with-image" class="reverse">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Up-to-date data without scans" src="/images/software-management-feature-image-up-to-date-data-without-scans-528x377@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text">
|
||||
<h3>Up-to-date data without scans</h3>
|
||||
<p>Traditional network vulnerability scans can clog your network and even haunt your printers with pages full of wingdings. Fleet does things differently.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p>Eliminate the risk of side effects from scanning the network.</p>
|
||||
<p>Lightweight enough for the most brittle environments (OT, data centers, embedded/BTS, low-latency gaming servers).</p>
|
||||
<p>Quickly pull data about important CVEs and zero days during an incident or audit.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="feature-image">
|
||||
<img alt="Up-to-date data without scans" src="/images/software-management-feature-image-up-to-date-data-without-scans-528x377@2x.png">
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="feature" class="d-flex flex-md-row flex-column justify-content-center mx-auto align-items-center">
|
||||
<div purpose="feature-with-image">
|
||||
<div purpose="feature-image">
|
||||
<img alt="Untangle your security stack" src="/images/software-management-feature-image-untangle-your-security-stack-528x377@2x.png">
|
||||
</div>
|
||||
<div purpose="feature-text" class="d-flex flex-column">
|
||||
<div purpose="feature-text">
|
||||
<h3>Untangle your security stack</h3>
|
||||
<p>Use open data and APIs to connect your vulnerability solution with osquery, the agent you might already have deployed.</p>
|
||||
<div purpose="checklist" class="flex-column d-flex">
|
||||
<div purpose="checklist">
|
||||
<p>Normalize asset management data and software inventories from multiple tools and operating systems.</p>
|
||||
<p>Help teams work on vulnerabilities that have actually been exploited (CISA KEVs) or have a high probability of being exploited (EPSS), or whatever is important in your environment.</p>
|
||||
<p>Process CVEs across cloud and non-cloud assets in a single platform and see who's responsible for what.</p>
|
||||
|
|
|
|||
Loading…
Reference in a new issue