From 06bb4f708da0e20d0de7b29239860db90a3a1bad Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:08:05 -0400 Subject: [PATCH 01/45] Custom OS settings guide: Modify OS settings on macOS, iOS, iPadOS, and Windows (#23046) Document interim best practice for modifying OS settings. Later Fleet might add an "Edit" button in the UI so the IT admin doesn't have to add a new profile and then remove the old. --- articles/custom-os-settings.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/articles/custom-os-settings.md b/articles/custom-os-settings.md index 1e7dbdee69..1b28774d45 100644 --- a/articles/custom-os-settings.md +++ b/articles/custom-os-settings.md @@ -6,7 +6,7 @@ In Fleet you can enforce OS settings like security restrictions, screen lock, Wi You can enforce OS settings using the Fleet UI, Fleet API, or [Fleet's GitOps workflow](https://github.com/fleetdm/fleet-gitops). -For macOS hosts, Fleet recommends the [iMazing Profile Creator](https://imazing.com/profile-editor) tool for creating and exporting macOS configuration profiles. +For macOS, iOS, and iPadOS hosts, Fleet recommends the [iMazing Profile Creator](https://imazing.com/profile-editor) tool for creating and exporting macOS configuration profiles. For Windows hosts, copy out this [Windows configuration profile template](https://fleetdm.com/example-windows-profile) and update the profile using any configuration service providers (CSPs) from [Microsoft's MDM protocol](https://learn.microsoft.com/en-us/windows/client-management/mdm/). @@ -18,6 +18,10 @@ Fleet UI: 3. Select **Upload** and choose your configuration profile. +4. To modify the OS setting, first remove the old configuration profile and then add the new one. + +> On macOS, iOS, and iPadOS, removing a configuration profile will remove enforcement of the OS setting. + Fleet API: API documentation is [here](https://fleetdm.com/docs/rest-api/rest-api#add-custom-os-setting-configuration-profile) ### OS settings status From fb0f69c010161c4de1bbc2c0aeb339a255f5db37 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 21 Oct 2024 10:26:41 -0500 Subject: [PATCH 02/45] Website: Fix lint errors in script (#23049) Changes: - Fixed the lint errors in the `test-llm-generated-sql` script --- website/scripts/test-llm-generated-sql.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/website/scripts/test-llm-generated-sql.js b/website/scripts/test-llm-generated-sql.js index d1a45662f9..dc83e7ac2a 100644 --- a/website/scripts/test-llm-generated-sql.js +++ b/website/scripts/test-llm-generated-sql.js @@ -13,7 +13,7 @@ module.exports = { fn: async function ({naturalLanguageQuestion}) { - let assert = require('assert'); + // let assert = require('assert'); let completeTables = await sails.helpers.getExtendedOsquerySchema(); let prunedTables = completeTables.map((table)=>{ @@ -33,13 +33,13 @@ module.exports = { Provided context: \`\`\` ${JSON.stringify(prunedTables.map((table)=>{ - let lighterTable = _.pick(table, ['name','description','platforms']); - lighterTable.columns = table.columns.map((column)=>{ - let lighterColumn = _.pick(column, ['name', 'description', 'platforms']); - return lighterColumn; - }); - return lighterTable; - }))} + let lighterTable = _.pick(table, ['name','description','platforms']); + lighterTable.columns = table.columns.map((column)=>{ + let lighterColumn = _.pick(column, ['name', 'description', 'platforms']); + return lighterColumn; + }); + return lighterTable; + }))} \`\`\` Please respond in JSON, with the same data shape as the provided context, but with the array filtered to include only relevant tables.`; From 476dd3cc8644b745f0c97f96de64b467d1562733 Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky Date: Mon, 21 Oct 2024 11:20:41 -0500 Subject: [PATCH 03/45] Ignore `--delete-other-teams` for non-premium gitops (#23052) #23050 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Manual QA for all new/changed functionality --- changes/23050-delete-other-teams | 1 + cmd/fleetctl/gitops.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/23050-delete-other-teams diff --git a/changes/23050-delete-other-teams b/changes/23050-delete-other-teams new file mode 100644 index 0000000000..a6e555e3f2 --- /dev/null +++ b/changes/23050-delete-other-teams @@ -0,0 +1 @@ +Ignore `--delete-other-teams` flag in `fleetctl gitops` command for non-Premium license users. diff --git a/cmd/fleetctl/gitops.go b/cmd/fleetctl/gitops.go index 1c64e3f607..2382840f67 100644 --- a/cmd/fleetctl/gitops.go +++ b/cmd/fleetctl/gitops.go @@ -234,7 +234,7 @@ func gitopsCommand() *cli.Command { return err } } - if flDeleteOtherTeams { + if flDeleteOtherTeams && appConfig.License.IsPremium() { // skip team deletion for non-premium users teams, err := fleetClient.ListTeams("") if err != nil { return err From 85bf9b6e02a248c4fe91dc4aef17b5ee5555ccfb Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 21 Oct 2024 13:17:49 -0500 Subject: [PATCH 04/45] Website: update hosts note on contact form (#23031) Changes: - Updated the note about hosts on the contact form page. --- website/views/pages/contact.ejs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/views/pages/contact.ejs b/website/views/pages/contact.ejs index c75f967dab..f18609cb97 100644 --- a/website/views/pages/contact.ejs +++ b/website/views/pages/contact.ejs @@ -92,11 +92,11 @@
-

Includes computers, servers, containers, and other hosts.

-

Includes servers, containers, workstations, and other hosts.

-

Includes macOS, Windows, Linux workstations, Chromebooks, servers, and other hosts.

-

Includes computers, servers, OT/ICS, containers, and other hosts.

-

Includes macOS, Windows, and most flavors of Linux.

+

Includes computers, servers, phones, and more.

+

Includes servers, supercomputers, and everything else.

+ + +
Please enter a number of <%= primaryBuyingSituation === 'mdm' ? 'devices' : 'hosts' %>
From ff97bf7ad2d8b30240fce10355bef8bfa228abd9 Mon Sep 17 00:00:00 2001 From: onasismunro Date: Mon, 21 Oct 2024 13:40:13 -0500 Subject: [PATCH 05/45] Handbook: Update communications.md (#23032) --- handbook/company/communications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/communications.md b/handbook/company/communications.md index 1628e7a9ad..c736b31e23 100644 --- a/handbook/company/communications.md +++ b/handbook/company/communications.md @@ -691,7 +691,7 @@ For recurring business expenses paid by credit card, there are four accounts pay - Choose a modest location with inexpensive lodging and [minimally-viable comfort](https://fleetdm.com/handbook/company/why-this-way#why-spend-less). - For events, check the event's website for travel & hotel accommodations and book as soon as the event is approved. As a general rule, do not spend more on lodging than the average listed hotel accommodations. - Avoid expensive neighborhoods and popular destination cities. -- Do not share bedrooms. Shared bathrooms are OK, but make sure every hotel room or Airbnb has at least one bathroom per gender identity. +- Please do not share bedrooms. Shared bathrooms are OK, but make sure every hotel room or Airbnb has at least one bathroom per gender identity. - Be creative. If an AirBnb is the most efficient way to house the team, then do that. If separate hotel rooms are more efficient, then do that. - If the stay is longer than 4 nights and an Airbnb with a washing machine is not available, then dry cleaning can be purchased with your Brex card. - If you need to meet with a large group that won't fit in your hotel room or Airbnb (e.g. more than 5 people), [contact Digital Experience](https://fleetdm.com/handbook/digital-experience#contact-us) for their help approving and booking additional event space. From 348c0a23bf968cf95ff2d8a7455ba6388eda1334 Mon Sep 17 00:00:00 2001 From: Janis Watts Date: Mon, 21 Oct 2024 13:41:40 -0500 Subject: [PATCH 06/45] Handbook: Update communications.md (#23053) # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features. - [ ] Added/updated tests - [ ] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes - [ ] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [ ] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [ ] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [ ] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [ ] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [ ] Orbit runs on macOS, Linux and Windows. Check if the orbit feature/bugfix should only apply to one platform (`runtime.GOOS`). - [ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - [ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)). --- handbook/company/communications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/communications.md b/handbook/company/communications.md index c736b31e23..f13d7f8c08 100644 --- a/handbook/company/communications.md +++ b/handbook/company/communications.md @@ -1039,7 +1039,7 @@ Learn how to communicate as Fleet with guidelines for tone of voice, our approac ### Writing style - Infuse the core [values](https://fleetdm.com/handbook/company#values) into everything you write. - - Read and reread, then rewrite to make it shorter. Use links rather than explanations, short sentences. + - Read and reread, then rewrite to make it shorter. Use links rather than explanations, and favor short sentences. - Get to where you feel like it’s really good, short, simple, and clear, hack away at any word that’s too confusing. - Don’t sound formal, sound welcoming so that anyone can understand. Translate "[puffery](https://www.linkedin.com/pulse/puffery-adam-frankl%3FtrackingId=SBVWxzqXTBm9qlO7Rw3ddw%253D%253D/?trackingId=SBVWxzqXTBm9qlO7Rw3ddw%3D%3D)" into "ease of use" or "readability". - Disarm puffery for engineers by replacing puffery with real data. From f3c18a14fe2ed28780b78bc8a9a0d1118c8aa8dd Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky Date: Mon, 21 Oct 2024 13:49:01 -0500 Subject: [PATCH 07/45] Allow Render connectionString as redis.address (#23058) #23057 Render provides [connectionString](https://docs.render.com/blueprint-spec#connectionstring) for their Redis service, which Fleet cannot take because it includes `redis://` prefix. Once this change is released, we can [update our render.yaml](https://github.com/fleetdm/fleet/pull/23056/files#diff-a64cf250b418ab8feee6c682a3d8cbd3b72cf24d4a241adeaf35c98b84045f93) # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Manual QA for all new/changed functionality --- changes/23057-redis-address | 1 + cmd/fleet/serve.go | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/23057-redis-address diff --git a/changes/23057-redis-address b/changes/23057-redis-address new file mode 100644 index 0000000000..f037237c67 --- /dev/null +++ b/changes/23057-redis-address @@ -0,0 +1 @@ +Allow FLEET_REDIS_ADDRESS to include a `redis://` prefix. Allowed formats are: `redis://host:port` or `host:port` diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index 143e00746b..9ba3618331 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -312,8 +312,12 @@ the way that the Fleet server works. } } + // Strip the Redis URI scheme if it's present. Scheme docs are at: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml + // This allows us to use Render's Redis service in render.yaml, including the free tier. + // In the future, we could support the full Redis URI if needed (including username, password, database, etc.) + redisAddress := strings.TrimPrefix(config.Redis.Address, "redis://") redisPool, err := redis.NewPool(redis.PoolConfig{ - Server: config.Redis.Address, + Server: redisAddress, Username: config.Redis.Username, Password: config.Redis.Password, Database: config.Redis.Database, From 3dbee14cb41fedd8aaab10defc51ef17fb43250d Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:12:25 -0500 Subject: [PATCH 08/45] Update results/be-realistic (#23059) --- handbook/company/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/README.md b/handbook/company/README.md index 037922d3d1..03de731421 100644 --- a/handbook/company/README.md +++ b/handbook/company/README.md @@ -84,7 +84,7 @@ We work to get results. How we work determines what we get. Aim to deliver resu - **Iterate.** [Look for ways](https://youtu.be/BW6TWwNZzIQ) to make [smaller changes](https://about.gitlab.com/handbook/product/product-principles/#the-minimal-viable-change-mvc), more often. Always publish. - **Start quickly.** Be 70% sure. Resist [bike-shedding](https://en.wikipedia.org/wiki/Law_of_triviality). Between overthinking and rushing, there is a [golden mean](https://en.wikipedia.org/wiki/Golden_mean_%28philosophy%29). - **Keep it simple.** [Spend less](https://fleetdm.com/handbook/company/why-this-way#why-spend-less). Avoid preemptive structure. Choose ["boring solutions"](https://about.gitlab.com/blog/2020/08/18/boring-solutions-faster-iteration/). Reuse systems. Avoid adding steps, especially handoffs. -- **Be realistic.** Focus on one or two tasks at a time. When you can't take on [more work](http://www.paulgraham.com/hwh.html), clarify your boundaries. Schedule [time off](https://fleetdm.com/handbook/company/communications#paid-time-off) to recharge. +- **Be realistic.** Focus on one task at a time. Resist [the urge to commit](https://www.linkedin.com/posts/37signals_the-problem-with-commitments-is-that-they-ugcPost-7252753513485619201-PdAw?utm_source=share&utm_medium=member_ios) to every good idea. Work [hard](https://paulgraham.com/hwh.html). Schedule [time off](https://fleetdm.com/handbook/company/communications#paid-time-off) to recharge. ### 🔵 Objectivity To reach our goals, we need to [see reality clearly](https://en.wikipedia.org/wiki/Intellectual_honesty). From 8d27835976f1018fd24ef48afa8798ba8982908e Mon Sep 17 00:00:00 2001 From: Josh Brower Date: Mon, 21 Oct 2024 15:24:18 -0400 Subject: [PATCH 09/45] CIS Update - macOS 13, CIS v2.1.0 (#21486) Changelog ADD: ADD - 2.3.3.2 Ensure the Time Service Is Enabled ADD - 6.3.10 Ensure Show Status Bar Is Enabled UPDATE: UPDATE - 2.6.1.2 Ensure Location Services Is in the Menu Bar UPDATE - 3.1 Ensure Security Auditing Is Enabled UPDATE - 5.7 Ensure an Administrator Account Cannot Login to Another User's Active and Locked Session UPDATE - 5.1.6 Ensure No World Writable Folders Exist in the System Folder UPDATE - 2.9.1.1 Ensure the OS Is Not Active When Resuming from Standby (Intel) UPDATE - 2.9.1.2 Ensure the OS Is Not Active When Resuming from Sleep and Display Sleep (Apple Silicon) --------- Co-authored-by: Sharon Katz <121527325+sharon-fdm@users.noreply.github.com> --- ee/cis/macos-13/README.md | 2 +- ee/cis/macos-13/cis-policy-queries.yml | 94 ++++++++++++++++++++------ 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/ee/cis/macos-13/README.md b/ee/cis/macos-13/README.md index e49628ac6e..edbe27f690 100644 --- a/ee/cis/macos-13/README.md +++ b/ee/cis/macos-13/README.md @@ -1,6 +1,6 @@ # macOS 13.0 Ventura benchmark -Fleet's policies have been written against v1.0 of the benchmark. You can refer to the [CIS website](https://www.cisecurity.org/cis-benchmarks) for full details about this version. +Fleet's policies have been written against v2.1.0 of the benchmark. You can refer to the [CIS website](https://www.cisecurity.org/cis-benchmarks) for full details about this version. For requirements and usage details, see the [CIS Benchmarks](https://fleetdm.com/docs/using-fleet/cis-benchmarks) documentation. diff --git a/ee/cis/macos-13/cis-policy-queries.yml b/ee/cis/macos-13/cis-policy-queries.yml index 29481efce6..5d1b1e9ba5 100644 --- a/ee/cis/macos-13/cis-policy-queries.yml +++ b/ee/cis/macos-13/cis-policy-queries.yml @@ -1,5 +1,5 @@ --- -# The latest version of CIS Benchmarks for macOS as of January 2023 was used which was benchmark 1.0 for macOS 13.0 https://workbench.cisecurity.org/benchmarks/10541 +# The latest version of CIS Benchmarks for macOS as of August 2024 was used which was benchmark 2.1.0 for macOS 13 apiVersion: v1 kind: policy spec: @@ -517,6 +517,23 @@ spec: --- apiVersion: v1 kind: policy +spec: + name: CIS - Ensure the Time Service Is Enabled + platforms: macOS + platform: darwin + description: | + In macOS 10.14, Apple replace ntp with timed for time services, and is used to ensure correct time is kept. Correct date and time settings are required for authentication protocols, file creation, modification dates and log entries. + resolution: | + Ask your system administrator to deploy a script that will configure: + /usr/bin/sudo /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.timed.plist + query: | + SELECT 1 WHERE EXISTS(SELECT * FROM processes WHERE path = '/usr/libexec/timed'); + purpose: Informational + tags: compliance, CIS, CIS_Level1 + contributors: defensivedepth +--- +apiVersion: v1 +kind: policy spec: name: CIS - Ensure Time Is Set Within Appropriate Limits (Fleetd Required) platforms: macOS @@ -1366,10 +1383,10 @@ spec: apiVersion: v1 kind: policy spec: - name: CIS - Ensure Location Services Is in the Menu Bar + name: CIS - Ensure 'Show Location Icon in Control Center when System Services Request Your Location' Is Enabled platforms: macOS platform: darwin - description: Checks that Location Services option is presented in the Menu Bar. + description: This setting provides the user an understanding of the current status of Location Services and which applications are using it. resolution: | Automated method: Ask your system administrator to deploy an MDM profile that enables the "location services" icon in menu bar when System Services request your location. @@ -1383,7 +1400,7 @@ spec: location is set to your organization's parameters query: SELECT 1 FROM plist WHERE path='/Library/Preferences/com.apple.locationmenu.plist' AND key='ShowSystemServices' AND value=1; purpose: Informational - tags: compliance, CIS, CIS_Level2, CIS-macos-13-2.6.1.2 + tags: compliance, CIS, CIS_Level2 contributors: sharon-fdm --- apiVersion: v1 @@ -1713,15 +1730,16 @@ spec: that can be attempted against the RAM that has the encryption keys or the running operating system protected by a login screen. - Mac systems should be set to hibernate after sleeping for a risk-acceptable time period. MacBooks should be set so that the `standbydelay` is 15 minutes (900 seconds) or less. + Mac systems should be set to hibernate after sleeping for a risk-acceptable time period. + Organizations may still decide to use hibernate after sleeping if there is a security need + (ex. international travel), but it can cause kernel panics in Apple Silicon Macs. resolution: | Ask your system administrator to deploy the following script to Macbook devices: if [[ $(uname -m) == 'arm64' ]]; then # Apple silicon /usr/bin/sudo /usr/bin/pmset -a standby 900 /usr/bin/sudo /usr/bin/pmset -a destroyfvkeyonstandby 1 - /usr/bin/sudo /usr/bin/pmset -a hibernatemode 25 else # Intel /usr/bin/sudo /usr/bin/pmset -a standbydelaylow 900 @@ -1762,12 +1780,11 @@ spec: SELECT 1 FROM system_info WHERE cpu_type = 'arm64e' ) AND EXISTS ( SELECT - CAST(JSON_EXTRACT(battery, '$.standby') AS INTEGER) AS standby, - CAST(JSON_EXTRACT(battery, '$.hibernatemode') AS INTEGER) AS hibernatemode + CAST(JSON_EXTRACT(battery, '$.standby') AS INTEGER) AS standby FROM ( SELECT JSON_EXTRACT(json_result, '$.Battery Power:') AS battery FROM pmset WHERE getting = 'custom' ) - WHERE standby <= 900 AND hibernatemode = 25 + WHERE standby <= 900 ) ) ); @@ -2184,10 +2201,10 @@ spec: platforms: macOS platform: darwin description: | - macOS's audit facility, auditd, receives notifications from the kernel when certain system calls, such as open, fork, and exit, are made. These notifications are captured and written to an audit log. + macOS's audit facility, auditd, receives notifications from the kernel when certain system calls, such as open, fork, and exit, are made. These notifications are captured and written to an audit log. Apple has deprecated auditd as of macOS 11.0 Big Sur. In macOS 14.0 Sonoma it is no longer enabled by default. resolution: | Automated method: - Ask your system administrator to deploy the following script which will enable security auditing: + Ask your system administrator to deploy the following script which will load auditd and create the audit_control file: /usr/bin/sudo /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.auditd.plist query: | SELECT 1 WHERE EXISTS ( @@ -2635,14 +2652,11 @@ spec: platforms: macOS platform: darwin description: | - Software sometimes insists on being installed in the /System/Volumes/Data/System Directory and has inappropriate world-writable permissions. - Macs with writable files in System should be investigated forensically. A file with open writable permissions is a sign of at best a rogue application. - It could also be a sign of a computer compromise and a persistent presence on the system. - The audit check excludes the "Drop Box" folder that is part of Apple's default user template. + Software sometimes insists on being installed in the /System/Volumes/Data/System Directory and has inappropriate world-writable permissions. Macs with writable files in System should be investigated forensically. A file with open writable permissions is a sign of at best a rogue application. It could also be a sign of a computer compromise and a persistent presence on the system. resolution: | Ask your system administrator to deploy a script that will ensure folders are not world-writable in the /System folder. /usr/bin/sudo IFS=$'\n' - for sysPermissions in $( /usr/bin/find /System/Volumes/Data/System -type d -perm -2 | /usr/bin/grep -v "Drop Box" ); + for sysPermissions in $( /usr/bin/find /System/Volumes/Data/System -type d -perm -2 | /usr/bin/grep -vE "Drop Box|locks" ); do /bin/chmod -R o-w "$sysPermissions" done @@ -2666,11 +2680,15 @@ spec: platform: darwin description: | Software sometimes insists on being installed in the /System/Volumes/Data/Library Directory and has inappropriate world-writable permissions. - Folders in /System/Volumes/Data/Library should not be world-writable. The audit check excludes the /System/Volumes/Data/Library/Caches and /System/Volumes/Data/Library/Preferences/Audio/Data folders where the sticky bit is set. + Folders in /System/Volumes/Data/Library should not be world-writable. Macs with writable files in System should be investigated forensically. + A file with open writable permissions is a sign of at best a rogue application. It could also be a sign of a computer compromise and a + persistent presence on the system. + + The audit check excludes the /System/Volumes/Data/Library/Caches and /System/Volumes/Data/Library/Preferences/Audio/Data folders where the sticky bit is set. resolution: | Ask your system administrator to deploy a script that will ensure folders are not world-writable in the /System folder. /usr/bin/sudo IFS=$'\n' - for libPermissions in $( /usr/bin/find /System/Volumes/Data/Library -type d -perm -2 | /usr/bin/grep -v Caches | /usr/bin/grep -v /Preferences/Audio/Data); + for libPermissions in $(/usr/bin/find /System/Volumes/Data/Library -type d -perm -2 | /usr/bin/grep -Ev "Caches|/Preferences/Audio/Data|locks"); do /bin/chmod -R o-w "$libPermissions" done @@ -3006,16 +3024,16 @@ spec: resolution: | Automated method: Ask your system administrator to deploy a script that runs the following: - /usr/bin/sudo /usr/bin/security authorizationdb write system.login.screensaver use-login-window-ui + /usr/bin/sudo /usr/bin/security authorizationdb write system.login.screensaver authenticate-session-owner query: | SELECT 1 WHERE EXISTS ( SELECT JSON_EXTRACT(json_result, '$.rule') AS rule FROM authdb WHERE right_name = 'system.login.screensaver' AND - rule LIKE '%use-login-window-ui%' + rule LIKE '%authenticate-session-owner%' ); purpose: Informational - tags: compliance, CIS, CIS_Level1, CIS-macos-13-5.7 + tags: compliance, CIS, CIS_Level1 contributors: lucasmrod --- apiVersion: v1 @@ -3449,6 +3467,40 @@ spec: --- apiVersion: v1 kind: policy +spec: + name: CIS - Ensure Show Status Bar Is Enabled (MDM Required) + platforms: macOS + platform: darwin + description: | + The Status Bar in Safari shows the full URL of any link on hover. It protects the user from visiting sites where the domain has been obfuscated by allowing the user to review whether the link points to an unexpected location. + resolution: | + Automated method: + Profile Method: + Create or edit a configuration profile with the following information: + 1. The PayloadType string is com.apple.Safari + 2. The key to include is ShowOverlayStatusBar + 3. The key must be set to: + query: | + SELECT 1 WHERE + EXISTS ( + SELECT 1 FROM managed_policies WHERE + domain='com.apple.Safari' AND + name='ShowOverlayStatusBar' AND + (value = 1 OR value = 'true') AND + username = '' + ) + AND NOT EXISTS ( + SELECT 1 FROM managed_policies WHERE + domain='com.apple.Safari' AND + name='ShowOverlayStatusBar' AND + (value != 1 AND value != 'true') + ); + purpose: Informational + tags: compliance, CIS, CIS_Level1 + contributors: defensivedepth +--- +apiVersion: v1 +kind: policy spec: name: CIS - Ensure Secure Keyboard Entry Terminal.app Is Enabled (MDM Required) platforms: macOS From 16afe9f7c025a98d6c1d7b293fd5330f97d561c3 Mon Sep 17 00:00:00 2001 From: Josh Brower Date: Mon, 21 Oct 2024 15:24:36 -0400 Subject: [PATCH 10/45] CIS Update - macOS 14, CIS v1.1.0 (#21478) Changelog ADD: ADD - 2.3.3.2 Ensure the Time Service Is Enabled ADD - 6.3.10 Ensure Show Status Bar Is Enabled REMOVE: REMOVE - 2.3.2.2 Ensure Time Is Set Within Appropriate Limits UPDATE: UPDATE - 2.6.1.2 Ensure Location Services Is in the Menu Bar UPDATE - 3.1 Ensure Security Auditing Is Enabled UPDATE - 5.1.6 Ensure No World Writable Folders Exist in the System Folder UPDATE - 5.7 Ensure an Administrator Account Cannot Login to Another User's Active and Locked Session UPDATE - 2.9.1.1 Ensure the OS Is Not Active When Resuming from Standby (Intel) UPDATE - 2.9.1.2 Ensure the OS Is Not Active When Resuming from Sleep and Display Sleep (Apple Silicon) --------- Co-authored-by: Sharon Katz <121527325+sharon-fdm@users.noreply.github.com> --- ee/cis/macos-14/README.md | 4 +- ee/cis/macos-14/cis-policy-queries.yml | 79 ++++++++++++++++++-------- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/ee/cis/macos-14/README.md b/ee/cis/macos-14/README.md index a83cd38d4a..0d42e27f6b 100644 --- a/ee/cis/macos-14/README.md +++ b/ee/cis/macos-14/README.md @@ -1,6 +1,6 @@ -# macOS 14.0 Sonoma benchmark +# macOS 14 Sonoma benchmark -Fleet's policies have been written against v1.0 of the benchmark. You can refer to the [CIS website](https://www.cisecurity.org/cis-benchmarks) for full details about this version. +Fleet's policies have been written against v1.1.0 of the benchmark. You can refer to the [CIS website](https://www.cisecurity.org/cis-benchmarks) for full details about this version. For requirements and usage details, see the [CIS Benchmarks](https://fleetdm.com/docs/using-fleet/cis-benchmarks) documentation. diff --git a/ee/cis/macos-14/cis-policy-queries.yml b/ee/cis/macos-14/cis-policy-queries.yml index d277be53fd..250f3acdba 100644 --- a/ee/cis/macos-14/cis-policy-queries.yml +++ b/ee/cis/macos-14/cis-policy-queries.yml @@ -1,5 +1,5 @@ --- -# The latest version of CIS Benchmarks for macOS as of January 2024 for macOS 14. +# The latest version of CIS Benchmarks for macOS as of August 2024 for macOS 14. apiVersion: v1 kind: policy spec: @@ -518,17 +518,19 @@ spec: apiVersion: v1 kind: policy spec: - name: CIS - Ensure Time Is Set Within Appropriate Limits (Fleetd Required) + name: CIS - Ensure the Time Service Is Enabled platforms: macOS platform: darwin description: | - Correct date and time settings are required for authentication protocols, file creation, modification dates and log entries. - The time offset compared to time.apple.com must be between -270.x and 270.x seconds. - resolution: Make sure the device can connect to time.apple.com to synchronize time. - query: SELECT * FROM sntp_request WHERE server = 'time.apple.com' AND clock_offset_ms <= 270000 AND clock_offset_ms >= -270000; + In macOS 10.14, Apple replace ntp with timed for time services, and is used to ensure correct time is kept. Correct date and time settings are required for authentication protocols, file creation, modification dates and log entries. + resolution: | + Ask your system administrator to deploy a script that will configure: + /usr/bin/sudo /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.timed.plist + query: | + SELECT 1 WHERE EXISTS(SELECT * FROM processes WHERE path = '/usr/libexec/timed'); purpose: Informational tags: compliance, CIS, CIS_Level1 - contributors: lucasmrod + contributors: defensivedepth --- apiVersion: v1 kind: policy @@ -1366,10 +1368,10 @@ spec: apiVersion: v1 kind: policy spec: - name: CIS - Ensure Location Services Is in the Menu Bar + name: CIS - Ensure 'Show Location Icon in Control Center when System Services Request Your Location' Is Enabled platforms: macOS platform: darwin - description: Checks that Location Services option is presented in the Menu Bar. + description: This setting provides the user an understanding of the current status of Location Services and which applications are using it. resolution: | Automated method: Ask your system administrator to deploy an MDM profile that enables the "location services" icon in menu bar when System Services request your location. @@ -1713,15 +1715,16 @@ spec: that can be attempted against the RAM that has the encryption keys or the running operating system protected by a login screen. - Mac systems should be set to hibernate after sleeping for a risk-acceptable time period. MacBooks should be set so that the `standbydelay` is 15 minutes (900 seconds) or less. + Mac systems should be set to hibernate after sleeping for a risk-acceptable time period. + Organizations may still decide to use hibernate after sleeping if there is a security need + (ex. international travel), but it can cause kernel panics in Apple Silicon Macs. resolution: | Ask your system administrator to deploy the following script to Macbook devices: if [[ $(uname -m) == 'arm64' ]]; then # Apple silicon /usr/bin/sudo /usr/bin/pmset -a standby 900 /usr/bin/sudo /usr/bin/pmset -a destroyfvkeyonstandby 1 - /usr/bin/sudo /usr/bin/pmset -a hibernatemode 25 else # Intel /usr/bin/sudo /usr/bin/pmset -a standbydelaylow 900 @@ -1762,12 +1765,11 @@ spec: SELECT 1 FROM system_info WHERE cpu_type = 'arm64e' ) AND EXISTS ( SELECT - CAST(JSON_EXTRACT(battery, '$.standby') AS INTEGER) AS standby, - CAST(JSON_EXTRACT(battery, '$.hibernatemode') AS INTEGER) AS hibernatemode + CAST(JSON_EXTRACT(battery, '$.standby') AS INTEGER) AS standby FROM ( SELECT JSON_EXTRACT(json_result, '$.Battery Power:') AS battery FROM pmset WHERE getting = 'custom' ) - WHERE standby <= 900 AND hibernatemode = 25 + WHERE standby <= 900 ) ) ); @@ -2213,11 +2215,12 @@ spec: platforms: macOS platform: darwin description: | - macOS's audit facility, auditd, receives notifications from the kernel when certain system calls, such as open, fork, and exit, are made. These notifications are captured and written to an audit log. + macOS's audit facility, auditd, receives notifications from the kernel when certain system calls, such as open, fork, and exit, are made. These notifications are captured and written to an audit log. Apple has deprecated auditd as of macOS 11.0 Big Sur. In macOS 14.0 Sonoma it is no longer enabled by default.. resolution: | Automated method: - Ask your system administrator to deploy the following script which will enable security auditing: + Ask your system administrator to deploy the following script which will enable security auditing and create the audit_control file:: /usr/bin/sudo /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.auditd.plist + /usr/bin/sudo /bin/cp /etc/security/audit_control.example /etc/security/audit_control query: | SELECT 1 WHERE EXISTS ( SELECT @@ -2664,14 +2667,11 @@ spec: platforms: macOS platform: darwin description: | - Software sometimes insists on being installed in the /System/Volumes/Data/System Directory and has inappropriate world-writable permissions. - Macs with writable files in System should be investigated forensically. A file with open writable permissions is a sign of at best a rogue application. - It could also be a sign of a computer compromise and a persistent presence on the system. - The audit check excludes the "Drop Box" folder that is part of Apple's default user template. + Software sometimes insists on being installed in the /System/Volumes/Data/System Directory and has inappropriate world-writable permissions. Macs with writable files in System should be investigated forensically. A file with open writable permissions is a sign of at best a rogue application. It could also be a sign of a computer compromise and a persistent presence on the system. resolution: | Ask your system administrator to deploy a script that will ensure folders are not world-writable in the /System folder. /usr/bin/sudo IFS=$'\n' - for sysPermissions in $( /usr/bin/find /System/Volumes/Data/System -type d -perm -2 | /usr/bin/grep -v "Drop Box" ); + for sysPermissions in $( /usr/bin/find /System/Volumes/Data/System -type d -perm -2 | /usr/bin/grep -vE "Drop Box|locks" ); do /bin/chmod -R o-w "$sysPermissions" done @@ -3035,13 +3035,13 @@ spec: resolution: | Automated method: Ask your system administrator to deploy a script that runs the following: - /usr/bin/sudo /usr/bin/security authorizationdb write system.login.screensaver use-login-window-ui + /usr/bin/sudo /usr/bin/security authorizationdb write system.login.screensaver authenticate-session-owner query: | SELECT 1 WHERE EXISTS ( SELECT JSON_EXTRACT(json_result, '$.rule') AS rule FROM authdb WHERE right_name = 'system.login.screensaver' AND - rule LIKE '%use-login-window-ui%' + rule LIKE '%authenticate-session-owner%' ); purpose: Informational tags: compliance, CIS, CIS_Level1 @@ -3448,6 +3448,39 @@ spec: --- apiVersion: v1 kind: policy +spec: + name: CIS - Ensure Show Status Bar Is Enabled (MDM Required) + platforms: macOS + platform: darwin + description: | + The Status Bar in Safari shows the full URL of any link on hover. It protects the user from visiting sites where the domain has been obfuscated by allowing the user to review whether the link points to an unexpected location. + resolution: | + Automated method: + Profile Method: + Create or edit a configuration profile with the following information: + 1. The PayloadType string is com.apple.Safari + 2. The key to include is ShowOverlayStatusBar + 3. The key must be set to: + query: | + SELECT 1 WHERE + EXISTS ( + SELECT 1 FROM managed_policies WHERE + domain='com.apple.Safari' AND + name='ShowOverlayStatusBar' AND + (value = 1 OR value = 'true') + ) + AND NOT EXISTS ( + SELECT 1 FROM managed_policies WHERE + domain='com.apple.Safari' AND + name='ShowOverlayStatusBar' AND + (value != 1 AND value != 'true') + ); + purpose: Informational + tags: compliance, CIS, CIS_Level1 + contributors: defensivedepth +--- +apiVersion: v1 +kind: policy spec: name: CIS - Ensure Secure Keyboard Entry Terminal.app Is Enabled (MDM Required) platforms: macOS From bbac39f22a8694b1d4048f8f539e4304c7b528f0 Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Mon, 21 Oct 2024 17:46:50 -0500 Subject: [PATCH 11/45] Ensure software installs aren't deleted, and have enough info to display, even if associated installer or title are deleted (#22996) #21654 #22087 Also persists title/installer filename/version to the install record in case those are edited, though we'll continue showing the current title name when pulling the install record at this point, and don't expose installed version anywhere for now. # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests - [x] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [x] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [x] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. (**force-set updated_at to avoid**) - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [x] Manual QA for all new/changed functionality --- changes/21654-install-details | 14 ++ ...3402_AddSoftwareDetailsToInstallRecords.go | 75 +++++++++ ...AddSoftwareDetailsToInstallRecords_test.go | 152 ++++++++++++++++++ server/datastore/mysql/schema.sql | 16 +- server/datastore/mysql/software_installers.go | 61 +++++-- .../mysql/software_installers_test.go | 28 ++++ server/fleet/software_installer.go | 12 +- 7 files changed, 328 insertions(+), 30 deletions(-) create mode 100644 changes/21654-install-details create mode 100644 server/datastore/mysql/migrations/tables/20241017163402_AddSoftwareDetailsToInstallRecords.go create mode 100644 server/datastore/mysql/migrations/tables/20241017163402_AddSoftwareDetailsToInstallRecords_test.go diff --git a/changes/21654-install-details b/changes/21654-install-details new file mode 100644 index 0000000000..0666cd5f43 --- /dev/null +++ b/changes/21654-install-details @@ -0,0 +1,14 @@ +* Ensure details for a software installation run are available and accurate even after the corresponding installer has been edited or deleted + +NOTE: The database migration included with this update backfills installer data into installation details based on the currently uploaded installer. To backfill data from activities (which will be more comprehensive and accurate than the migration default, but may take awhile as the entire activities table will be scanned), run this database query _after_ running database migrations: + +```sql +UPDATE host_software_installs i +JOIN activities a ON a.activity_type = 'installed_software' + AND i.execution_id = a.details->>"$.install_uuid" +SET i.software_title_name = COALESCE(a.details->>"$.software_title", i.software_title_name), + i.installer_filename = COALESCE(a.details->>"$.software_package", i.installer_filename), + i.updated_at = i.updated_at +``` + +The above query is optional, and is unnecessary if no software installers have been edited. \ No newline at end of file diff --git a/server/datastore/mysql/migrations/tables/20241017163402_AddSoftwareDetailsToInstallRecords.go b/server/datastore/mysql/migrations/tables/20241017163402_AddSoftwareDetailsToInstallRecords.go new file mode 100644 index 0000000000..1d31585a98 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20241017163402_AddSoftwareDetailsToInstallRecords.go @@ -0,0 +1,75 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20241017163402, Down_20241017163402) +} + +func Up_20241017163402(tx *sql.Tx) error { + _, err := tx.Exec("ALTER TABLE host_software_installs ADD COLUMN installer_filename VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '[deleted installer]'") + if err != nil { + return fmt.Errorf("failed to create installer_filename column on host_software_installs table: %w", err) + } + + _, err = tx.Exec("ALTER TABLE host_software_installs ADD COLUMN version VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'unknown'") + if err != nil { + return fmt.Errorf("failed to create version column on host_software_installs table: %w", err) + } + + _, err = tx.Exec("ALTER TABLE host_software_installs ADD COLUMN software_title_id INT UNSIGNED DEFAULT NULL") + if err != nil { + return fmt.Errorf("failed to create software_title_id column on host_software_installs table: %w", err) + } + + _, err = tx.Exec("ALTER TABLE host_software_installs ADD COLUMN software_title_name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '[deleted title]'") + if err != nil { + return fmt.Errorf("failed to create software_title_name column on host_software_installs table: %w", err) + } + + _, err = tx.Exec("ALTER TABLE host_software_installs CHANGE COLUMN software_installer_id software_installer_id INT UNSIGNED DEFAULT NULL") + if err != nil { + return fmt.Errorf("failed to allow nullability on software_installer_id column in host_software_installs table: %w", err) + } + + _, err = tx.Exec(`ALTER TABLE host_software_installs DROP CONSTRAINT fk_host_software_installs_installer_id`) + if err != nil { + return fmt.Errorf("failed to switch on-delete behavior of software installer foreign key from host_software_installs table (constraint drop): %w", err) + } + + _, err = tx.Exec(`ALTER TABLE host_software_installs ADD CONSTRAINT fk_host_software_installs_installer_id + FOREIGN KEY (software_installer_id) REFERENCES software_installers (id) ON DELETE SET NULL ON UPDATE CASCADE`) + if err != nil { + return fmt.Errorf("failed to switch on-delete behavior of software installer foreign key from host_software_installs table (constraint re-add): %w", err) + } + + _, err = tx.Exec(`ALTER TABLE host_software_installs ADD CONSTRAINT fk_host_software_installs_software_title_id + FOREIGN KEY (software_title_id) REFERENCES software_titles (id) ON DELETE SET NULL ON UPDATE CASCADE`) + if err != nil { + return fmt.Errorf("failed to add foreign key for software_title_id to host_software_installs table: %w", err) + } + + _, err = tx.Exec(` +UPDATE host_software_installs i +JOIN software_installers si ON si.id = i.software_installer_id +LEFT JOIN software_titles st ON st.id = si.title_id +SET + i.software_title_id = st.id, + i.software_title_name = COALESCE(st.name, "[deleted title]"), + i.installer_filename = IF(i.uninstall, "", si.filename), + i.version = IF(i.uninstall = 0 AND i.created_at >= si.uploaded_at, si.version, "unknown"), + i.updated_at = i.updated_at +`) // only one left join because prior to this migration software_installer_id wasn't nullable on host_software_installs + if err != nil { + return fmt.Errorf("failed to propagate software title and installer information into host_software_installs: %w", err) + } + + return nil +} + +func Down_20241017163402(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20241017163402_AddSoftwareDetailsToInstallRecords_test.go b/server/datastore/mysql/migrations/tables/20241017163402_AddSoftwareDetailsToInstallRecords_test.go new file mode 100644 index 0000000000..0c58be339e --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20241017163402_AddSoftwareDetailsToInstallRecords_test.go @@ -0,0 +1,152 @@ +package tables + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUp_20241017163402(t *testing.T) { + db := applyUpToPrev(t) + + // Create host + insertHostStmt := ` + INSERT INTO hosts ( + hostname, uuid, platform, osquery_version, os_version, build, platform_like, code_name, + cpu_type, cpu_subtype, cpu_brand, hardware_vendor, hardware_model, hardware_version, + hardware_serial, computer_name, team_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ` + hostName := "Dummy Hostname" + hostUUID := "12345678-1234-1234-1234-123456789012" + hostPlatform := "darwin" + osqueryVer := "5.9.1" + osVersion := "Windows 10" + buildVersion := "10.0.19042.1234" + platformLike := "apple" + codeName := "20H2" + cpuType := "x86_64" + cpuSubtype := "x86_64" + cpuBrand := "Intel" + hwVendor := "Dell Inc." + hwModel := "OptiPlex 7090" + hwVersion := "1.0" + hwSerial := "ABCDEFGHIJ" + computerName := "DESKTOP-TEST" + + hostID1 := execNoErrLastID(t, db, insertHostStmt, hostName, hostUUID, hostPlatform, osqueryVer, + osVersion, buildVersion, platformLike, codeName, cpuType, cpuSubtype, cpuBrand, hwVendor, hwModel, hwVersion, hwSerial, + computerName, nil) + + dataStmts := ` + INSERT INTO script_contents (id, md5_checksum, contents) VALUES + (1, 'checksum', 'script content'); + + INSERT INTO software_titles (id, name, source, browser) VALUES + (1, 'Foo.app', 'apps', ''), (2, 'WillBeDeleted.app', 'apps', ''); + + INSERT INTO software_installers + (id, title_id, filename, version, platform, install_script_content_id, storage_id, package_ids, uninstall_script_content_id, uploaded_at) + VALUES + (1, 1, 'foo-installer.pkg', '1.1', 'darwin', 1, 'storage-id', '', 1, NOW() + INTERVAL 5 SECOND), + (2, 2, 'to-delete-installer.pkg', '1.2', 'darwin', 1, 'storage-id', '', 1, '2024-09-30 00:00:00'); + ` + _, err := db.Exec(dataStmts) + require.NoError(t, err) + + hsiStmt := ` + INSERT INTO host_software_installs ( + host_id, + execution_id, + software_installer_id, + install_script_exit_code, + updated_at, + uninstall + ) VALUES (?, ?, ?, ?, '2024-10-01 00:00:00', ?)` + hsi1 := execNoErrLastID(t, db, hsiStmt, hostID1, "execution-id1", 1, 0, 0) + hsi2 := execNoErrLastID(t, db, hsiStmt, hostID1, "execution-id2", 2, 0, 0) + hsiUn := execNoErrLastID(t, db, hsiStmt, hostID1, "execution-id3", 2, 0, 1) + + execNoErr(t, db, `DELETE FROM software_titles WHERE id = 2`) // sets title ID to null for installer 2 + + // Apply current migration. + applyNext(t, db) + + result := struct { + Filename string `db:"installer_filename"` + Version string `db:"version"` + InstallerID *uint `db:"software_installer_id"` + TitleID *uint `db:"software_title_id"` + TitleName string `db:"software_title_name"` + UpdatedAt string `db:"updated_at"` + }{} + + err = db.Get(&result, "SELECT installer_filename, version, software_installer_id, software_title_id, software_title_name, updated_at FROM host_software_installs WHERE id = ?", hsi1) + require.NoError(t, err) + require.Equal(t, "foo-installer.pkg", result.Filename) + require.Equal(t, "unknown", result.Version) + require.Equal(t, uint(1), *result.InstallerID) + require.Equal(t, uint(1), *result.TitleID) + require.Equal(t, "Foo.app", result.TitleName) + require.Equal(t, "2024-10-01T00:00:00Z", result.UpdatedAt) + + err = db.Get(&result, "SELECT installer_filename, version, software_installer_id, software_title_id, software_title_name, updated_at FROM host_software_installs WHERE id = ?", hsi2) + require.NoError(t, err) + require.Equal(t, "to-delete-installer.pkg", result.Filename) + require.Equal(t, "1.2", result.Version) + require.Equal(t, uint(2), *result.InstallerID) + require.Nil(t, result.TitleID) + require.Equal(t, "[deleted title]", result.TitleName) + require.Equal(t, "2024-10-01T00:00:00Z", result.UpdatedAt) + + // we know less about uninstalls as we may be able to uninstall a version that was installed earlier + err = db.Get(&result, "SELECT installer_filename, version, software_installer_id, software_title_id, software_title_name, updated_at FROM host_software_installs WHERE id = ?", hsiUn) + require.NoError(t, err) + require.Equal(t, "", result.Filename) + require.Equal(t, "unknown", result.Version) + require.Equal(t, uint(2), *result.InstallerID) + require.Nil(t, result.TitleID) + require.Equal(t, "[deleted title]", result.TitleName) + require.Equal(t, "2024-10-01T00:00:00Z", result.UpdatedAt) + + execNoErr(t, db, `DELETE FROM software_installers WHERE id = 2`) // sets installer ID to null for install 2 + + err = db.Get(&result, "SELECT installer_filename, version, software_installer_id, software_title_id, software_title_name, updated_at FROM host_software_installs WHERE id = ?", hsi2) + require.NoError(t, err) + require.Equal(t, "to-delete-installer.pkg", result.Filename) + require.Equal(t, "1.2", result.Version) + require.Nil(t, result.InstallerID) + require.Equal(t, "2024-10-01T00:00:00Z", result.UpdatedAt) + + // test activity hydration manual query + execNoErr(t, db, `INSERT INTO activities (activity_type, details) VALUES + ("installed_software", '{"install_uuid": "execution-id1", "software_title": "Foo", "software_package": "foo.pkg"}'), + ("installed_software", '{"install_uuid": "execution-id2", "software_title": "A Real Title"}'), + ("uninstalled_software", '{"execution_id": "execution-id3", "software_title": "Ignore Me"}')`) + + execNoErr(t, db, `UPDATE host_software_installs i +JOIN activities a ON a.activity_type = 'installed_software' + AND i.execution_id = a.details->>"$.install_uuid" +SET i.software_title_name = COALESCE(a.details->>"$.software_title", i.software_title_name), + i.installer_filename = COALESCE(a.details->>"$.software_package", i.installer_filename), + i.updated_at = i.updated_at`) + + err = db.Get(&result, "SELECT installer_filename, version, software_installer_id, software_title_id, software_title_name, updated_at FROM host_software_installs WHERE id = ?", hsi1) + require.NoError(t, err) + require.Equal(t, "foo.pkg", result.Filename) + require.Equal(t, "Foo", result.TitleName) + require.Equal(t, "2024-10-01T00:00:00Z", result.UpdatedAt) + + err = db.Get(&result, "SELECT installer_filename, version, software_installer_id, software_title_id, software_title_name, updated_at FROM host_software_installs WHERE id = ?", hsi2) + require.NoError(t, err) + require.Equal(t, "to-delete-installer.pkg", result.Filename) + require.Equal(t, "A Real Title", result.TitleName) + require.Equal(t, "2024-10-01T00:00:00Z", result.UpdatedAt) + + // uninstall should not have been modified + err = db.Get(&result, "SELECT installer_filename, version, software_installer_id, software_title_id, software_title_name, updated_at FROM host_software_installs WHERE id = ?", hsiUn) + require.NoError(t, err) + require.Equal(t, "", result.Filename) + require.Equal(t, "[deleted title]", result.TitleName) + require.Equal(t, "2024-10-01T00:00:00Z", result.UpdatedAt) +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 042d3d020f..1d8b39fcfe 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -583,7 +583,7 @@ CREATE TABLE `host_software_installs` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `execution_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `host_id` int unsigned NOT NULL, - `software_installer_id` int unsigned NOT NULL, + `software_installer_id` int unsigned DEFAULT NULL, `pre_install_query_output` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `install_script_output` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `install_script_exit_code` int DEFAULT NULL, @@ -600,13 +600,19 @@ CREATE TABLE `host_software_installs` ( `uninstall` tinyint unsigned NOT NULL DEFAULT '0', `status` enum('pending_install','failed_install','installed','pending_uninstall','failed_uninstall') COLLATE utf8mb4_unicode_ci GENERATED ALWAYS AS ((case when (`removed` = 1) then NULL when ((`post_install_script_exit_code` is not null) and (`post_install_script_exit_code` = 0)) then _utf8mb4'installed' when ((`post_install_script_exit_code` is not null) and (`post_install_script_exit_code` <> 0)) then _utf8mb4'failed_install' when ((`install_script_exit_code` is not null) and (`install_script_exit_code` = 0)) then _utf8mb4'installed' when ((`install_script_exit_code` is not null) and (`install_script_exit_code` <> 0)) then _utf8mb4'failed_install' when ((`pre_install_query_output` is not null) and (`pre_install_query_output` = _utf8mb4'')) then _utf8mb4'failed_install' when ((`host_id` is not null) and (`uninstall` = 0)) then _utf8mb4'pending_install' when ((`uninstall_script_exit_code` is not null) and (`uninstall_script_exit_code` <> 0)) then _utf8mb4'failed_uninstall' when ((`uninstall_script_exit_code` is not null) and (`uninstall_script_exit_code` = 0)) then NULL when ((`host_id` is not null) and (`uninstall` = 1)) then _utf8mb4'pending_uninstall' else NULL end)) STORED, `policy_id` int unsigned DEFAULT NULL, + `installer_filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '[deleted installer]', + `version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'unknown', + `software_title_id` int unsigned DEFAULT NULL, + `software_title_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '[deleted title]', PRIMARY KEY (`id`), UNIQUE KEY `idx_host_software_installs_execution_id` (`execution_id`), - KEY `fk_host_software_installs_installer_id` (`software_installer_id`), KEY `fk_host_software_installs_user_id` (`user_id`), KEY `idx_host_software_installs_host_installer` (`host_id`,`software_installer_id`), KEY `fk_software_install_policy_id` (`policy_id`), - CONSTRAINT `fk_host_software_installs_installer_id` FOREIGN KEY (`software_installer_id`) REFERENCES `software_installers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + KEY `fk_host_software_installs_installer_id` (`software_installer_id`), + KEY `fk_host_software_installs_software_title_id` (`software_title_id`), + CONSTRAINT `fk_host_software_installs_installer_id` FOREIGN KEY (`software_installer_id`) REFERENCES `software_installers` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `fk_host_software_installs_software_title_id` FOREIGN KEY (`software_title_id`) REFERENCES `software_titles` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `fk_host_software_installs_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL, CONSTRAINT `host_software_installs_ibfk_1` FOREIGN KEY (`policy_id`) REFERENCES `policies` (`id`) ON DELETE SET NULL ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; @@ -1078,9 +1084,9 @@ CREATE TABLE `migration_status_tables` ( `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=321 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=322 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( diff --git a/server/datastore/mysql/software_installers.go b/server/datastore/mysql/software_installers.go index 7a93ff539f..e062521a92 100644 --- a/server/datastore/mysql/software_installers.go +++ b/server/datastore/mysql/software_installers.go @@ -436,13 +436,13 @@ func (ds *Datastore) DeleteSoftwareInstaller(ctx context.Context, id uint) error func (ds *Datastore) InsertSoftwareInstallRequest(ctx context.Context, hostID uint, softwareInstallerID uint, selfService bool, policyID *uint) (string, error) { const ( + getInstallerStmt = `SELECT filename, "version", title_id, COALESCE(st.name, '[deleted title]') title_name + FROM software_installers si LEFT JOIN software_titles st ON si.title_id = st.id WHERE si.id = ?` insertStmt = ` INSERT INTO host_software_installs - (execution_id, host_id, software_installer_id, user_id, self_service, policy_id) - VALUES - (?, ?, ?, ?, ?, ?) + (execution_id, host_id, software_installer_id, user_id, self_service, policy_id, installer_filename, version, software_title_id, software_title_name) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` - hostExistsStmt = `SELECT 1 FROM hosts WHERE id = ?` ) @@ -457,6 +457,20 @@ func (ds *Datastore) InsertSoftwareInstallRequest(ctx context.Context, hostID ui return "", ctxerr.Wrap(ctx, err, "checking if host exists") } + var installerDetails struct { + Filename string `db:"filename"` + Version string `db:"version"` + TitleID *uint `db:"title_id"` + TitleName *string `db:"title_name"` + } + if err = sqlx.GetContext(ctx, ds.reader(ctx), &installerDetails, getInstallerStmt, softwareInstallerID); err != nil { + if err == sql.ErrNoRows { + return "", notFound("SoftwareInstaller").WithID(softwareInstallerID) + } + + return "", ctxerr.Wrap(ctx, err, "getting installer data") + } + var userID *uint if ctxUser := authz.UserFromContext(ctx); ctxUser != nil { userID = &ctxUser.ID @@ -469,6 +483,10 @@ func (ds *Datastore) InsertSoftwareInstallRequest(ctx context.Context, hostID ui userID, selfService, policyID, + installerDetails.Filename, + installerDetails.Version, + installerDetails.TitleID, + installerDetails.TitleName, ) return installID, ctxerr.Wrap(ctx, err, "inserting new install software request") @@ -510,11 +528,12 @@ func (ds *Datastore) runInstallerUpdateSideEffectsInTransaction(ctx context.Cont func (ds *Datastore) InsertSoftwareUninstallRequest(ctx context.Context, executionID string, hostID uint, softwareInstallerID uint) error { const ( + getInstallerStmt = `SELECT title_id, COALESCE(st.name, '[deleted title]') title_name + FROM software_installers si LEFT JOIN software_titles st ON si.title_id = st.id WHERE si.id = ?` insertStmt = ` INSERT INTO host_software_installs - (execution_id, host_id, software_installer_id, user_id, uninstall) - VALUES - (?, ?, ?, ?, 1) + (execution_id, host_id, software_installer_id, user_id, uninstall, installer_filename, software_title_id, software_title_name, version) + VALUES (?, ?, ?, ?, 1, '', ?, ?, 'unknown') ` hostExistsStmt = `SELECT 1 FROM hosts WHERE id = ?` ) @@ -529,6 +548,18 @@ func (ds *Datastore) InsertSoftwareUninstallRequest(ctx context.Context, executi return ctxerr.Wrap(ctx, err, "checking if host exists") } + var installerDetails struct { + TitleID *uint `db:"title_id"` + TitleName *string `db:"title_name"` + } + if err = sqlx.GetContext(ctx, ds.reader(ctx), &installerDetails, getInstallerStmt, softwareInstallerID); err != nil { + if err == sql.ErrNoRows { + return notFound("SoftwareInstaller").WithID(softwareInstallerID) + } + + return ctxerr.Wrap(ctx, err, "getting installer data") + } + var userID *uint if ctxUser := authz.UserFromContext(ctx); ctxUser != nil { userID = &ctxUser.ID @@ -538,6 +569,8 @@ func (ds *Datastore) InsertSoftwareUninstallRequest(ctx context.Context, executi hostID, softwareInstallerID, userID, + installerDetails.TitleID, + installerDetails.TitleName, ) return ctxerr.Wrap(ctx, err, "inserting new uninstall software request") @@ -551,10 +584,10 @@ SELECT hsi.post_install_script_output, hsi.install_script_output, hsi.host_id AS host_id, - st.name AS software_title, - st.id AS software_title_id, + COALESCE(st.name, hsi.software_title_name) AS software_title, + hsi.software_title_id, COALESCE(hsi.status, '') AS status, - si.filename AS software_package, + hsi.installer_filename AS software_package, hsi.user_id AS user_id, hsi.post_install_script_exit_code, hsi.install_script_exit_code, @@ -562,14 +595,10 @@ SELECT hsi.host_deleted_at, hsi.policy_id, hsi.created_at as created_at, - hsi.updated_at as updated_at, - si.user_id AS software_installer_user_id, - si.user_name AS software_installer_user_name, - si.user_email AS software_installer_user_email + hsi.updated_at as updated_at FROM host_software_installs hsi - JOIN software_installers si ON si.id = hsi.software_installer_id - JOIN software_titles st ON si.title_id = st.id + LEFT JOIN software_titles st ON hsi.software_title_id = st.id WHERE hsi.execution_id = :execution_id `) diff --git a/server/datastore/mysql/software_installers_test.go b/server/datastore/mysql/software_installers_test.go index 3539a381f3..be4169f246 100644 --- a/server/datastore/mysql/software_installers_test.go +++ b/server/datastore/mysql/software_installers_test.go @@ -495,6 +495,7 @@ func testGetSoftwareInstallResult(t *testing.T, ds *Datastore) { Title: "foo" + tc.name, Source: "bar" + tc.name, InstallScript: "echo " + tc.name, + Version: "1.11", TeamID: &teamID, Filename: swFilename, UserID: user1.ID, @@ -534,8 +535,35 @@ func testGetSoftwareInstallResult(t *testing.T, ds *Datastore) { }) require.NoError(t, err) + // edit installer to ensure host software install is unaffected + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + _, err = q.ExecContext(ctx, ` + UPDATE software_installers SET filename = 'something different', version = '1.23' WHERE id = ?`, + installerID) + require.NoError(t, err) + return nil + }) + res, err = ds.GetSoftwareInstallResults(ctx, installUUID) require.NoError(t, err) + require.Equal(t, swFilename, res.SoftwarePackage) + + // delete installer to confirm that we can still access the install record + err = ds.DeleteSoftwareInstaller(ctx, installerID) + require.NoError(t, err) + + res, err = ds.GetSoftwareInstallResults(ctx, installUUID) + require.NoError(t, err) + + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { // ensure version is not changed, though we don't expose it yet + var version string + err := sqlx.GetContext(ctx, q, &version, `SELECT "version" FROM host_software_installs WHERE execution_id = ?`, installUUID) + require.NoError(t, err) + require.Equal(t, "1.11", version) + + return nil + }) + require.NoError(t, err) require.Equal(t, installUUID, res.InstallUUID) require.Equal(t, tc.expectedStatus, res.Status) diff --git a/server/fleet/software_installer.go b/server/fleet/software_installer.go index df479d72b5..52c009e398 100644 --- a/server/fleet/software_installer.go +++ b/server/fleet/software_installer.go @@ -210,10 +210,10 @@ type HostSoftwareInstallerResult struct { InstallUUID string `json:"install_uuid" db:"execution_id"` // SoftwareTitle is the title of the software. SoftwareTitle string `json:"software_title" db:"software_title"` - // SoftwareVersion is the version of the software. - SoftwareTitleID uint `json:"software_title_id" db:"software_title_id"` + // SoftwareTitleID is the unique numerical ID of the software title assigned by the datastore. + SoftwareTitleID *uint `json:"software_title_id" db:"software_title_id"` // SoftwareInstallerID is the unique numerical ID of the software installer assigned by the datastore. - SoftwareInstallerID uint `json:"-" db:"software_installer_id"` + SoftwareInstallerID *uint `json:"-" db:"software_installer_id"` // SoftwarePackage is the name of the software installer package. SoftwarePackage string `json:"software_package" db:"software_package"` // HostID is the ID of the host. @@ -245,12 +245,6 @@ type HostSoftwareInstallerResult struct { // HostDeletedAt indicates if the data is associated with a // deleted host HostDeletedAt *time.Time `json:"-" db:"host_deleted_at"` - // SoftwareInstallerUserID is the ID of the user that uploaded the software installer. - SoftwareInstallerUserID *uint `json:"-" db:"software_installer_user_id"` - // SoftwareInstallerUserID is the name of the user that uploaded the software installer. - SoftwareInstallerUserName string `json:"-" db:"software_installer_user_name"` - // SoftwareInstallerUserEmail is the email of the user that uploaded the software installer. - SoftwareInstallerUserEmail string `json:"-" db:"software_installer_user_email"` // PolicyID is the id of the policy that triggered the install, or // nil if the install was not triggered by a policy failure PolicyID *uint `json:"policy_id" db:"policy_id"` From 91eef00d05cc8db5ab0c2ab9c82ece2111535bdd Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Mon, 21 Oct 2024 18:20:34 -0500 Subject: [PATCH 12/45] Fall back to pending-outline when we see an unexpected or blank host software install status (#23069) #23068 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Manual QA for all new/changed functionality --- changes/23068-hsi-status-icon-fe | 1 + .../SoftwareInstallDetails/SoftwareInstallDetails.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/23068-hsi-status-icon-fe diff --git a/changes/23068-hsi-status-icon-fe b/changes/23068-hsi-status-icon-fe new file mode 100644 index 0000000000..3c50bb90f4 --- /dev/null +++ b/changes/23068-hsi-status-icon-fe @@ -0,0 +1 @@ +* Fix frontend error when trying to view install details for an install with a blank status diff --git a/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx b/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx index 42ed1d6dd3..e3747f952a 100644 --- a/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx +++ b/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx @@ -58,7 +58,7 @@ const StatusMessage = ({ : ""; return (
- + Fleet {getInstallDetailsStatusPredicate(status)} {software_title}{" "} ({software_package}) on {formattedHost} From 1e593375f81917639f69ac91618414621f0aa278 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 21 Oct 2024 21:13:06 -0500 Subject: [PATCH 13/45] Msp dashboard: Update error handling in software-related actions (#23065) For: https://github.com/fleetdm/confidential/issues/8473 Changes: - Updated the cloud error components message - Updated the upload-software action to log errors about failed requests to the Fleet API - Updated the edit-software action to log errors about failed requests to the Fleet API, and to delete temporary files when requests fail. - Updated the software page to clear cloud errors when modals are closed. --- .../api/controllers/software/edit-software.js | 16 ++++++++++++++-- .../api/controllers/software/upload-software.js | 14 ++++++++++++-- .../js/components/cloud-error.component.js | 2 +- .../assets/js/pages/software/software.page.js | 1 + 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js b/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js index 4793c2f00e..a840b666d7 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js @@ -160,9 +160,15 @@ module.exports = { }; }, }) - .intercept((error)=>{ + .intercept(async (error)=>{ // Note: with this current behavior, all errors from this upload are currently swallowed and a softwareUploadFailed response is returned. // FUTURE: Test to make sure that uploading duplicate software to a team results in a 409 response. + // Before handline errors, decide what to do about the file uploaded to s3, if this is undeployed software, we'll leave it alone, but if this was a temporary file created to transfer it between teams on the Fleet instance, we'll delete the file. + if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3. + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + // Log a warning containing an error + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 0})}`); return {'softwareUploadFailed': error}; }); // console.timeEnd(`transfering ${software.name} to fleet instance for team id ${team}`); @@ -229,9 +235,15 @@ module.exports = { }; }, }) - .intercept((error)=>{ + .intercept(async (error)=>{ // Note: with this current behavior, all errors from this upload are currently swallowed and a softwareUploadFailed response is returned. // FUTURE: Test to make sure that uploading duplicate software to a team results in a 409 response. + // Before handling errors, decide what to do about the file uploaded to s3, if this is undeployed software, we'll leave it alone, but if this was a temporary file created to transfer it between teams on the Fleet instance, we'll delete the file. + if(!software.id) { + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + // Log a warning containing an error + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 0})}`); return {'softwareUploadFailed': error}; }); // console.timeEnd(`transfering ${software.name} to fleet instance for team id ${teamApid}`); diff --git a/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js b/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js index 25897ab1b4..29b4817b3b 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js @@ -33,6 +33,10 @@ module.exports = { statusCode: 409, }, + softwareUploadFailed: { + description:'An unexpected error occurred communicating with the Fleet API' + } + }, @@ -51,8 +55,8 @@ module.exports = { }; await UndeployedSoftware.create(newSoftwareInfo); } else { + uploadedSoftware = await sails.uploadOne(newSoftware, {bucket: sails.config.uploads.bucketWithPostfix}); for(let teamApid of teams) { - uploadedSoftware = await sails.uploadOne(newSoftware, {bucket: sails.config.uploads.bucketWithPostfix}); var WritableStream = require('stream').Writable; await sails.cp(uploadedSoftware.fd, {bucket: sails.config.uploads.bucketWithPostfix}, { adapter: ()=>{ @@ -96,8 +100,14 @@ module.exports = { }; } }) - .intercept({response: {status: 409}}, (error)=>{ + .intercept({response: {status: 409}}, async (error)=>{ + await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd); return {'softwareAlreadyExistsOnThisTeam': error}; + }) + .intercept({name: 'AxiosError'}, async (error)=>{ + await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd); + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 0})}`); + return {'softwareUploadFailed': error}; }); } // Remove the file from the s3 bucket after it has been sent to the Fleet server. diff --git a/ee/bulk-operations-dashboard/assets/js/components/cloud-error.component.js b/ee/bulk-operations-dashboard/assets/js/components/cloud-error.component.js index 79107b6c1a..a5ebbf60d0 100644 --- a/ee/bulk-operations-dashboard/assets/js/components/cloud-error.component.js +++ b/ee/bulk-operations-dashboard/assets/js/components/cloud-error.component.js @@ -43,7 +43,7 @@ parasails.registerComponent('cloud-error', { // ╩ ╩ ╩ ╩ ╩╩═╝ template: `
-

An error occured while processing your request. Please check your information and try again, or contact support if the error persists.

+

An unexpected error occurred communicating with the Fleet API

`, diff --git a/ee/bulk-operations-dashboard/assets/js/pages/software/software.page.js b/ee/bulk-operations-dashboard/assets/js/pages/software/software.page.js index 83d2950e4b..3eb72e8109 100644 --- a/ee/bulk-operations-dashboard/assets/js/pages/software/software.page.js +++ b/ee/bulk-operations-dashboard/assets/js/pages/software/software.page.js @@ -95,6 +95,7 @@ parasails.registerPage('software', { this.modal = ''; this.formErrors = {}; this.formData = {}; + this.cloudError = ''; this.showAdvancedOptions = false; await this.forceRender(); }, From cc3f4e0bde7f97c4536e7a35aeafc9dafee0e1ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 07:28:08 -0300 Subject: [PATCH 14/45] Update versions of fleetd components in Fleet's TUF [automated] (#23071) Automated change from [GitHub action](https://github.com/fleetdm/fleet/actions/workflows/fleetd-tuf.yml). Co-authored-by: lucasmrod --- orbit/TUF.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orbit/TUF.md b/orbit/TUF.md index a2f2144000..b7a8bf24c6 100644 --- a/orbit/TUF.md +++ b/orbit/TUF.md @@ -20,7 +20,7 @@ Following are the currently deployed versions of fleetd components on the `stabl |--------------|--------|--------|---------|---------------| | orbit | 1.34.0 | 1.34.0 | 1.34.0 | 1.34.0 | | desktop | 1.34.0 | 1.34.0 | 1.34.0 | 1.34.0 | -| osqueryd | 5.13.1 | 5.13.1 | 5.13.1 | 5.13.1 | +| osqueryd | 5.14.1 | 5.14.1 | 5.14.1 | 5.14.1 | | nudge | - | - | - | - | | swiftDialog | - | - | - | - | | escrowBuddy | - | - | - | - | From d8b3a1c7bbb401972296464e49743c622c8811c4 Mon Sep 17 00:00:00 2001 From: Sharon Katz <121527325+sharon-fdm@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:54:19 -0400 Subject: [PATCH 15/45] Fix CIS query (#23062) --- ee/cis/macos-13/cis-policy-queries.yml | 18 ++++-------------- ee/cis/macos-14/cis-policy-queries.yml | 17 ++++------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/ee/cis/macos-13/cis-policy-queries.yml b/ee/cis/macos-13/cis-policy-queries.yml index 5d1b1e9ba5..043d4bd418 100644 --- a/ee/cis/macos-13/cis-policy-queries.yml +++ b/ee/cis/macos-13/cis-policy-queries.yml @@ -3481,20 +3481,10 @@ spec: 2. The key to include is ShowOverlayStatusBar 3. The key must be set to: query: | - SELECT 1 WHERE - EXISTS ( - SELECT 1 FROM managed_policies WHERE - domain='com.apple.Safari' AND - name='ShowOverlayStatusBar' AND - (value = 1 OR value = 'true') AND - username = '' - ) - AND NOT EXISTS ( - SELECT 1 FROM managed_policies WHERE - domain='com.apple.Safari' AND - name='ShowOverlayStatusBar' AND - (value != 1 AND value != 'true') - ); + SELECT 1 FROM managed_policies WHERE + domain='com.apple.Safari' AND + name='ShowOverlayStatusBar' AND + (value = 1 OR value = 'true'); purpose: Informational tags: compliance, CIS, CIS_Level1 contributors: defensivedepth diff --git a/ee/cis/macos-14/cis-policy-queries.yml b/ee/cis/macos-14/cis-policy-queries.yml index 250f3acdba..1ad66a3a2e 100644 --- a/ee/cis/macos-14/cis-policy-queries.yml +++ b/ee/cis/macos-14/cis-policy-queries.yml @@ -3462,19 +3462,10 @@ spec: 2. The key to include is ShowOverlayStatusBar 3. The key must be set to: query: | - SELECT 1 WHERE - EXISTS ( - SELECT 1 FROM managed_policies WHERE - domain='com.apple.Safari' AND - name='ShowOverlayStatusBar' AND - (value = 1 OR value = 'true') - ) - AND NOT EXISTS ( - SELECT 1 FROM managed_policies WHERE - domain='com.apple.Safari' AND - name='ShowOverlayStatusBar' AND - (value != 1 AND value != 'true') - ); + SELECT 1 FROM managed_policies WHERE + domain='com.apple.Safari' AND + name='ShowOverlayStatusBar' AND + (value = 1 OR value = 'true'); purpose: Informational tags: compliance, CIS, CIS_Level1 contributors: defensivedepth From ad7c529a90f86df3b96f8260186c7239ca484f7d Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:21:57 -0500 Subject: [PATCH 16/45] Add DRIs (#23076) --- CODEOWNERS | 32 ++++++++++++++++---------------- website/config/custom.js | 7 +++++-- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 15b5f51323..4422121ec4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -59,7 +59,7 @@ go.mod @fleetdm/go /infrastructure/ @rfairburn @ksatter @lukeheath @edwardsb @georgekarrv /charts/ @rfairburn @ksatter @lukeheath @edwardsb @georgekarrv /terraform/ @rfairburn @ksatter @lukeheath @edwardsb @georgekarrv -/it-and-security/ @noahtalerman @lukeheath @spokanemac @getvictor +/it-and-security/ @noahtalerman @lukeheath @getvictor ############################################################################################## # ⚗️ Reference, config surface, built-in queries, API, and other documentation. @@ -83,26 +83,26 @@ go.mod @fleetdm/go # # (see website/config/custom.js for DRIs of other paths not listed here) ############################################################################################## -/handbook/company/README.md @mikermcneil -/handbook/company/communications.md @mikermcneil -/handbook/company/leadership.md @mikermcneil -/handbook/company/why-this-way.md @mikermcneil -/handbook/README.md @mikermcneil -/handbook/company/open-positions.yml @sampfluger88 -/handbook/company/product-groups.md @lukeheath -/handbook/finance/README.md @sampfluger88 -/handbook/finance/finance.rituals.yml @sampfluger88 +/handbook/company/README.md @mikermcneil +/handbook/company/communications.md @mikermcneil +/handbook/company/leadership.md @mikermcneil +/handbook/company/why-this-way.md @mikermcneil +/handbook/README.md @mikermcneil +/handbook/company/open-positions.yml @sampfluger88 +#/handbook/company/product-groups.md @lukeheath Covered in custom.js +/handbook/finance/README.md @sampfluger88 +/handbook/finance/finance.rituals.yml @sampfluger88 /handbook/digital-experience/application-security.md @lukeheath /handbook/digital-experience/security-audits.md @lukeheath /handbook/digital-experience/security-policies.md @lukeheath /handbook/digital-experience/security.md @lukeheath /handbook/digital-experience/vendor-questionnaires.md @lukeheath -/handbook/digital-experience @sampfluger88 -/handbook/customer-success @sampfluger88 -/handbook/demand @sampfluger88 -/handbook/engineering @sampfluger88 @lukeheath -/handbook/sales @sampfluger88 -/handbook/product-design @sampfluger88 +/handbook/digital-experience @sampfluger88 +/handbook/customer-success @sampfluger88 +/handbook/demand @sampfluger88 +#/handbook/engineering @sampfluger88 @lukeheath Covered in custom.js +/handbook/sales @sampfluger88 +#/handbook/product-design @sampfluger88 Covered in custom.js ############################################################################################## # 🌐 GitHub issue templates diff --git a/website/config/custom.js b/website/config/custom.js index 731e6ce643..51438306de 100644 --- a/website/config/custom.js +++ b/website/config/custom.js @@ -162,8 +162,11 @@ module.exports.custom = { // 🫧 Pricing and features // 'website/views/pages/pricing.ejs': '', // « Covered in CODEOWNERS (2023-07-22) 'handbook/company/pricing-features-table.yml': 'noahtalerman', - 'handbook/company/testimonials.yml': 'mike-j-thomas', + 'handbook/company/product-groups.md': 'lukeheath', + 'handbook/engineering': 'lukeheath', + 'handbook/product-design': 'sampfluger88', + // 🫧 Other brandfronts 'README.md': 'mikermcneil',// « GitHub brandfront @@ -268,7 +271,7 @@ module.exports.custom = { 'handbook/digital-experience': ['sampfluger88','mikermcneil'], 'handbook/finance': ['sampfluger88','mikermcneil'], 'handbook/engineering': ['sampfluger88','mikermcneil', 'lukeheath'], - 'handbook/product-design': ['sampfluger88','mikermcneil'], + 'handbook/product-design': ['sampfluger88','mikermcneil','noahtalerman'], 'handbook/sales': ['sampfluger88','mikermcneil'], 'handbook/demand': ['sampfluger88','mikermcneil'], 'handbook/customer-success': ['sampfluger88','mikermcneil'], From c3085bc8a1c288b2dcf847b46e88a49e96ea6bca Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:22:27 -0500 Subject: [PATCH 17/45] Add responsibilities to product design (#23019) - Handbook flow from new feature request to drafting board. - File new GitHub issues for every customer/prospect requests: https://github.com/fleetdm/fleet/pull/23019/files#diff-c99d12c3af50c0c2aca2b9ef7597c02ccfe87678291956ff0b2e83d63978ea38R77 - Handbook new "Unpacking the why", "Unpacking the how", and "Product design check in" product design responsibilities --------- Co-authored-by: Noah Talerman --- handbook/company/product-groups.md | 6 +++++- handbook/product-design/README.md | 19 +++++++++++++++++++ .../product-design/product-design.rituals.yml | 13 ++++++++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index 4aae4cdc4a..e9edb51ad9 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -73,7 +73,11 @@ To deliver on this mission, we need a clear, repeatable process for turning an i > Learn more about Fleet's philosophy and process for making interface changes to the product, and [why we use a wireframe-first approach](https://fleetdm.com/handbook/company/why-this-way#why-do-we-use-a-wireframe-first-approach). To make a change to Fleet: -- First, [get it prioritized](https://fleetdm.com/handbook/product-design). +- First, [write it down](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=~feature+fest%2C%3Aproduct&projects=&template=feature-request.md&title=) + - For every customer/prospect requests, file a new GitHub issue. Whether the request is the same as an existing request will be determined by the Head of Product Design and a subject matter expert (SME) in the next step. +- Then, it will be looked at by Fleet's [Head of Product Design](https://fleetdm.com/handbook/product-design#team) and a SME [unpack the "why"](https://fleetdm.com/handbook/product-design#inbox-review). + - For customer/prospect requests to be looked at, they must have a Gong snippet. +- Then, it will be [prioritized](https://fleetdm.com/handbook/company/product-groups#feature-fest) and written up as one or more user stories. - Then, it will be [drafted](https://fleetdm.com/handbook/company/product-groups#drafting) (planned). - Next, it will be [implemented](https://fleetdm.com/handbook/company/product-groups#implementing) and [released](https://fleetdm.com/handbook/engineering#release-process). diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index 6b9a74ce64..11db47196d 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -23,6 +23,25 @@ This handbook page details processes specific to working [with](#contact-us) and The Product Design department is responsible for reviewing and collecting feedback from users, would-be users, and future users, prioritizing changes, designing the changes, and delivering these changes to the engineering team. Product Design prioritizes and shapes all changes involving functionality or usage, including the UI, REST API, command line, and webhooks. +### Unpacking the why + +The Head of Product Design and a former IT admin review the new customer/prospect/community requests in the "Inbox" column the [drafting board](https://github.com/fleetdm/fleet/issues#workspaces/drafting-6192dd66ea2562000faea25c/board) to synthesize why users are making the request (i.e. what problem are they trying to solve). + +If a customer/prospect request is missing a Gong snippet or requires additional information to understand the "why", the Head of Product Design will @ mention the relevant Customer Success Manager (CSM) or Account Executive (AE), assign them, and move the request to the "Waiting" column. + + +### Unpacking the how + +3 weeks before end of each quarter, The Head of Product Design starts a daily 1h meeting with the CEO. The Head of Product Design brings an objective for the next quarter and the appropriate subject matter expert to understand what users will expect. This helps Product Designers at Fleet understand how Fleet will design a particular feature. + +As soon as we've addressed the next quarter's objectives, the Head of Product Design cancels the daily meeting. + + +### Product design check in + +The Head of Product Design summarizes the current week's design reviews to discuss with the CEO. + + ### Drafting At Fleet, like [GitLab](https://about.gitlab.com/handbook/product-development-flow/#but-wait-isnt-this-waterfall) and [other organizations](https://speakerdeck.com/mikermcneil/i-love-apis), every change to the product's UI gets [wireframed first](https://fleetdm.com/handbook/company/why-this-way#why-do-we-use-a-wireframe-first-approach). diff --git a/handbook/product-design/product-design.rituals.yml b/handbook/product-design/product-design.rituals.yml index 86e9af4c44..438cd1b5ca 100644 --- a/handbook/product-design/product-design.rituals.yml +++ b/handbook/product-design/product-design.rituals.yml @@ -34,11 +34,18 @@ moreInfoUrl: "https://fleetdm.com/handbook/company/product-groups#design-reviews" dri: "noahtalerman" - - task: "🦢🔄 Product design sync" + task: "🦢📨 Unpacking the 'why'" + startedOn: "2024-09-30" + frequency: "Daily" + description: "Break down the 'why' for new customer requests in the 'Inbox' column on the drafting board." + moreInfoUrl: "https://fleetdm.com/handbook/product-design#unpacking-the-why" + dri: "noahtalerman" +- + task: "🦢👁️ Product design check-in" startedOn: "2023-07-11" frequency: "Weekly" - description: "Weekly time to chat about product design work (design reviews, conventions & best practices, using Figma, etc.)" - moreInfoUrl: "https://docs.google.com/document/d/1GDEcXuTUjHI2CD9Jqega_GyF9DL6-PBmcyJpj55Lmos/edit" # 2024-03-06 TODO: Add responsibility to README and link to it here + description: "The Head of Product Design summarizes the current week's design reviews to discuss with the CEO" + moreInfoUrl: "https://fleetdm.com/handbook/product-design#product-design-check-in" # 2024-03-06 TODO: Add responsibility to README and link to it here dri: "noahtalerman" - task: "🦢🗣 Product office hours" From fe59c7482cdc17b77bea54b7b3a7a5fb91e4da2b Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:59:16 -0500 Subject: [PATCH 18/45] Remove CPE (#23007) --- .github/ISSUE_TEMPLATE/release-article.md | 2 +- handbook/company/communications.md | 4 +- handbook/demand/README.md | 59 +- handbook/demand/demand.rituals.yml | 7 + handbook/digital-experience/README.md | 873 ++++++++++--------- handbook/engineering/README.md | 283 +----- handbook/engineering/engineering.rituals.yml | 18 +- 7 files changed, 526 insertions(+), 720 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release-article.md b/.github/ISSUE_TEMPLATE/release-article.md index 8f19418f5b..67f8ecdf65 100644 --- a/.github/ISSUE_TEMPLATE/release-article.md +++ b/.github/ISSUE_TEMPLATE/release-article.md @@ -3,7 +3,7 @@ name: 📰 Release article about: Propose a new feature or enhancement in Fleet. title: 'Release article: vXXX.YYY.ZZZ' labels: '#g-demand,:improve documentation' -assignees: 'spokanemac' +assignees: 'rachaelshaw' --- diff --git a/handbook/company/communications.md b/handbook/company/communications.md index f13d7f8c08..c184346337 100644 --- a/handbook/company/communications.md +++ b/handbook/company/communications.md @@ -1015,7 +1015,7 @@ Fleet provides laptops, YubiKey security keys, and software licenses for core te As soon as an offer is accepted, Digital Experience will reach out to the new team member to start this process and will work with the new team member to get their equipment requested and shipped to them on time. From time to time, team members need to purchase additional equipment in the interest of the company. -If you are in need of additional equipment for any reason, [open an IT support request](https://github.com/fleetdm/confidential/issues/new?assignees=spokanemac&labels=%3Ahelp-it&projects=&template=request-it-support.md&title=%F0%9F%92%BB+Request+IT+support). When possible, Fleet will pull from its warehouse of existing assets before spending [more money on new equipment](https://fleetdm.com/handbook/company/why-this-way#why-spend-less). +If you are in need of additional equipment for any reason, [open a support request](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=custom-request.md&title=) with Digital Experience. When possible, Fleet will pull from its warehouse of existing assets before spending [more money on new equipment](https://fleetdm.com/handbook/company/why-this-way#why-spend-less). - **Tracking equipment:** When a device has been purchased, it's added to the [spreadsheet of company equipment](https://docs.google.com/spreadsheets/d/1hFlymLlRWIaWeVh14IRz03yE-ytBLfUaqVz0VVmmoGI/edit#gid=0) where we keep track of devices and equipment, purchased by Fleet. When you receive your new computer, complete the entry by adding a description, model, and serial number to the spreadsheet. @@ -1028,7 +1028,7 @@ If you are in need of additional equipment for any reason, [open an IT support r ### Requesting IT support -If you are in need of IT support to assist with a software or hardware issue, [open an IT support request](https://github.com/fleetdm/confidential/issues/new?assignees=spokanemac&labels=%3Ahelp-it&projects=&template=request-it-support.md&title=%F0%9F%92%BB+Request+IT+support). A Fleet IT team member will attempt to correct your issue remotely using Fleet, and will notify you if any action is needed on your part. +If you are in need of IT support to assist with a software or hardware issue, [open a support request](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=custom-request.md&title=) with Digital Experience. A Fleet IT team member will attempt to correct your issue remotely using Fleet, and will notify you if any action is needed on your part. ## Writing diff --git a/handbook/demand/README.md b/handbook/demand/README.md index fb031e4772..b96bbacaee 100644 --- a/handbook/demand/README.md +++ b/handbook/demand/README.md @@ -127,6 +127,57 @@ The Head of Marketing is the DRI for deploying Fleet's event budget, and events - Finally, qualify or disqualify any newly-entered event ideas by either verifying and setting the buying situation, or removing the event idea from the spreadsheet. +### Book an event + +Fleet's Client Platform Engineer & Community Advocate is responsible for booking events that Fleet has chosen to attend and/or sponsor. To book an event, complete the steps in each event issue. Contact the [🫧 Digital Marketing Manager](https://fleetdm.com/handbook/demand#team) as needed with any questions or blockers to booking an event. + +> Note: The Demand department [settles all event strategy](https://fleetdm.com/handbook/demand#settle-event-strategy) prior to booking an event. + + + + +### Order SWAG + +**To order T-shirts:** + + - Check [Postal](https://app.postal.io/items/postals) first and see if the warehouse has enough shirts. + - Navigate to the [approved items page](https://app.postal.io/items/postals). + - Hover over the shirt design and click on the airplane. + - Click bulk send and choose one shirt size and the expected quantity of that particular shirt size. + - Make sure the address matches the expected receiving address. + - If the Postal warehouse can't fulfill the order or To order swag quickly: + - Login to [https://www.rushordertees.com/my-account/login/) (saved in 1Password). + - Choose Fleet logo design t-shirt under [my designs](https://www.rushordertees.com/my-account/designs/). + - Order shirts based on the pre-determined number (~5% of total event attendees). + - Submit the order. Ensure the address matches the expected receiving address. + +**To order stickers:** + + - Login to [StickerMule](https://www.stickermule.com/) (saved in 1Password). + - Find the [brand kit](https://www.stickermule.com/studio/brand-kits) after logging in. + - Click on the "Fleet Device Management" brand kit and order preapproved stickers from the templates. + - Total sticker quantity should be ~10% of total event attendees. + - Complete the checkout process. Ensure the address matches the expected receiving address. + +**To order pens and sticky note pads** + + - Pens and sticky note pads are ordered through Everything Branded. + - Email our sales representative Jake William (saved in 1Password) to order any of the following: + - [Javalina™ Metallic Stylus Pen](https://www.everythingbranded.com/product/javalina-metallic-stylus-pen-us-pat-8847930-9092077-350220) + - [Sharpie Fine Point Markers](https://www.everythingbranded.com/product/sharpie-fine-point-332908) + - [Custom sticky note pads](https://www.everythingbranded.com/product/custom-sticky-notes-585601) (design is in the StickerMule [brand kit](https://www.stickermule.com/studio/brand-kits)) + + ### Upload contacts to Salesforce after an event 1. [Create a new lead source](https://fleetdm.lightning.force.com/lightning/setup/ObjectManager/Contact/FieldsAndRelationships/LeadSource/view) with naming convention "[Retired]Events - {Event name}". @@ -228,14 +279,6 @@ Here are some links that filter relevant conversations on each platform: 4. If a post is helpful to our audience, reshare it. -### Respond to a "Talk to an expert" request - -When a community member has a question about getting started with Fleet, they can [book a 15-minute call](https://calendly.com/jd-fleet/let-s-talk-fleet) with the Community Advocate. - -1. Add questionnaire details to the [Talk to an Expert](https://docs.google.com/spreadsheets/d/1aMLQ9z5TYn90AnE3g1sMPoxsMNMKXn_l3aWb9Kdi7G4/edit) spreadsheet. -2. After meeting the contributor, follow up with an email including appropriate links. - - ### Upload to YouTube Fleet regularly uploads a variety of content to YouTube such as podcast episodes, sprint demos, educational updates, design reviews, and more. diff --git a/handbook/demand/demand.rituals.yml b/handbook/demand/demand.rituals.yml index 1c8152a900..7d4e3c0104 100644 --- a/handbook/demand/demand.rituals.yml +++ b/handbook/demand/demand.rituals.yml @@ -71,6 +71,13 @@ description: "Research any research-ready accounts in this Salesforce report (https://fleetdm.lightning.force.com/lightning/r/Report/00OUG000001PUIT2A4/view) and advance them to 'Ads running'" moreInfoUrl: "https://fleetdm.com/handbook/demand#research-an-account" dri: "Drew-P-drawers" +- + task: "Check ongoing events" + startedOn: "2024-02-09" + frequency: "Daily" + description: "Check event issues and complete steps." + moreInfoUrl: "https://fleetdm.com/handbook/engineering#book-an-event" + dri: "Drew-P-drawers" # - # task: "Propose a fleet event" # startedOn: "2023-10-02" diff --git a/handbook/digital-experience/README.md b/handbook/digital-experience/README.md index 5c50d9ac23..2796e59f81 100644 --- a/handbook/digital-experience/README.md +++ b/handbook/digital-experience/README.md @@ -34,218 +34,6 @@ The Digital Experience department is directly responsible for the culture, train > _**Note:** If a user story involves only changes to fleetdm.com, without changing the core product, then that user story is prioritized, drafted, implemented, and shipped by the [Digital Experience](https://fleetdm.com/handbook/digital-experience) department. Otherwise, if the story **also** involves changes to the core product **as well as** fleetdm.com, then that user story is prioritized, drafted, implemented, and shipped by [the other relevant product group](https://fleetdm.com/handbook/company/product-groups#current-product-groups), and not by `#g-digital-experience`._ -### Access a background check - -All Fleet team members undergo a background check provided through [Vetty](https://vetty.co/). Only the most recent background checks appear on the home page of Vetty's dashboard. To access a complete list of background checks run in Vetty, scroll down to the bottom of the candidates page and click "View Historical". - - -### Convert a Fleetie to a consultant - -If a Fleetie decides they want to move to being a [consultant](https://fleetdm.com/handbook/company/leadership#consultants), either the Fleetie or their manager need to create a [custom issue for the Digital Experience team](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=custom-request.md&title=Request%3A+_______________________) to notify them of the change. -Once notified, Digital Experience takes the following steps: -1. Confirm the following details with the Fleetie: - - Date of change - - Term of consultancy (time period) - - Hours/capacity expected (hours per week or month) - - Confirm hourly rate -2. Once details are confirmed, use the information given to create the consulting agreement for the Fleetie (either in docusign (US-based) or via Plane (international)), and send to their personal email for signature. Once signed, save in Fleetie's [employee file](https://drive.google.com/drive/folders/1UL7o3BzkTKnpvIS4hm_RtbOilSABo3oG?usp=drive_link). -3. Schedule the Fleetie's final day in HRIS (Gusto or Plane). -4. Update final day in ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) spreadsheet. -5. Create an [offboarding issue](https://github.com/fleetdm/classified/blob/main/.github/ISSUE_TEMPLATE/%F0%9F%9A%AA-offboarding-____________.md) for the Fleetie converting to a consultant, and confirm with their manager if there is a need to retain any tools or access while they are a consultant (default to removing all access from Fleet email, and migrating to personal email for Slack and other tools unless there is a business case to retain the Fleet email and associated tool access). -6. Follow the offboarding issue for next steps, including communicating to teammates and updating equity plan. - - -### Inform managers about hours worked - -Every Friday, we collect hours worked for all hourly employees at Fleet, including core team members and consultants, regardless of their location. Consultants submit their hours through Gusto (US consultants) or Plane.com (international consultants) for DRI (generally their manager) review. Here's how: -1. Find the DRI using the [Digital Experience KPIs](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit#gid=0). -2. Copy the template in the consultants column of the KPIs and send the teammate's DRI a direct message in Slack hours with an FYI including the total hours logged since last Saturday at midnight. For international teammates, they cannot enter hours weekly in Plane.com, so you will need to request the hours worked from them in order to have the DRI approve them. -3. The following Monday, check for updates to logged hours and ensure the KPI sheet aligns with HRIS records. If there are discrepancies between what was previously reported, reconfirm logged hours with the teammate's DRI and update the KPI sheet to reflect the correct amount. - - -### Change the DRI of a consultant - -1. In the [KPIs](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit#gid=0) sheet, find the consultant's column. -2. Change the DRI documented there to the new DRI who will receive information about the consultant's hours. - - -### Update personnel details - -When a Fleetie, consultant or advisor requests an update to their personnel details (name, location, phone, etc), follow these steps to ensure accurate representation across systems. -1. Team member submits a [custom issue](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=custom-request.md&title=Request%3A+_______________________) to update their personnel details (or Digital Experience team creates if the request comes via email or is sensitive and needs a classified issue). - - If change is for a primary identification or contact method, ask for evidence of change and capture in [employee's personnel file](https://drive.google.com/drive/folders/1UL7o3BzkTKnpvIS4hm_RtbOilSABo3oG?usp=drive_link). -2. Digital Experience makes change to HRIS (Gusto or Plane) to reflect change. - - Note: if making the change requires follow up steps, resolve those steps to action the change. -3. Once change is effected in HRIS, Digital Experience makes changes to ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) spreadsheet. -4. If required, Digital Experience makes any relevant changes to [Fleet's equity plan](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit#gid=0). -5. If required, Digital Experience makes any relevant changes to the ["🗺️ Geographical factors"](https://docs.google.com/spreadsheets/d/1rCVCs-eOo-VSEG7fPLgdq5l7oSaActl5bewaWP7PnSE/edit#gid=1533353559) spreadsheet and follows through on any action items involving tax implications (i.e. registering with a new state for employer taxes). -6. If required, Digital Experience also makes changes to other core systems (e.g: creating a new email alias in google workspace; updating details in Carta; etc). -7. The change is now actioned, notify the team member and close the issue. - -> Note: if the Fleetie is US based and has a qualifying life event that impacts benefit coverage, they can [follow the Gusto steps](https://support.gusto.com/article/100895878100000/Change-your-benefits-with-a-qualifying-life-event) to update their coverage elections. - - -### Change a Fleetie's role - -When Digital Experience receives [notification of a Fleetie's role changing](https://fleetdm.com/handbook/company/leadership#request-a-role-change-for-a-fleetie), The Head of Digital Experience will bring the proposed title change to the next Roundup meeting with the CEO for approval. If the proposed change is rejected, the Head of Digital Experience will inform the requesting manager as to why. If approved, use the following steps to change a Fleetie's role: -1. Update ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0): - - Search the spreadsheet for the Fleetie in need of a job title change. - - Input the new job title in the Fleetie's row in the "Job title" cell. - - Navigate to the "Org chart" tab of the spreadsheet, and verify that the Fleetie's title appears correctly in the org chart. -2. Update the departmental handbook page with the change of job title -3. [Prepare salary benchmarking information](#prepare-salary-benchmarking-information) to determine whether the teammate's current compensation aligns with the benchmarks of the new role. - - If the benchmark is significantly different, take the steps to [update a team member's compensation](#prepare-salary-benchmarking-information). -4. Update the relevant payroll/HRIS system. - - For updating Gusto (US-based Fleeties): - - Login to Gusto and navigate to "People > Team members". - - Find the Fleetie and select them to see their profile page. - - Under the "Compensation" heading, select edit and update the "Job title" and input the specific date the change happened. Save the changes. - - For updating Plane (non-US Fleeties): - - Login to Plane and navigate to "People > Team". - - Find the Fleetie and select them to see their profile page. - - Use the "Help" function, or email support@plane.com to notify Plane of the need to change the job title for the Fleetie. Include the Fleetie's name, current title, new title, and effective date. - - Take any relevant steps as directed by Plane in order to make the required changes to the Fleetie's profile. - - -### Change a Fleetie's manager - -When Digital Experience receives notification of a Fleetie's manager changing, follow these steps to ensure correct recording in our systems. -1. Update [🧑‍🚀 Fleeties](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0): - - Search for the Fleetie's new manager, and copy the new manager's unique ID from the far left "Unique ID" column. - - Search for the Fleetie whose manager is changing, and paste (without formatting) their new manager's unique ID in the "Reports to: (manager unique ID)" cell in the Fleetie's row. - - Verify that the "Reports to (auto: manager name and job title)" cell in the Fleetie's row reflects the new manager's details. - - Verify that in the new manager's row, the "# direct reports" cell reflect the correct number. - - Navigate to the "Org chart" tab in the spreadsheet, and verify that the Fleetie now appears in the correct place in the org chart. -2. If the person's department is changing, then update both departmental handbook pages to move the person to their new department: - - Remove the person from the "Team" section of the old department and add them to the "Team" section of the new department. -3. If the person's level of confidential access will change along with the change to their manager, then update that level of access: - - Update Google Workspace to make sure this person lives in the correct Google Group, removing them from the old and/or adding them to the new. - - Update 1password to remove this person from old vaults and/or add them to new vaults. - - For a team member moving from "classified" to "confidential" access, check Gusto, Plane, and other systems to remove their access. - -> **Note:** The Fleeties spreadsheet is the source of truth for who everyone's manager is and their job titles. - - -### Recognize employee workiversaries - -At Fleet, everyone is recognized on their [workiversary](https://fleetdm.com/handbook/company/communications#workiversaries). To ensure this happens, take the following steps: - -1. On the 15th of every month, use [Fleeties (private google doc)](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) to determine who is celebrating their workiversary in the next month. -2. At-mention the Head of Digital Experience. Use the following template: - - - ``` - [Month] - [workiversary date (DD-MMM)] - [teammate name] - [number of years at Fleet] - ``` - - The Head of Digital Experience will also use this post to update the [All hands](https://fleetdm.com/handbook/company/communications#all-hands) deck. -3. On the day prior to a workiversary, send the teammate’s manager a DM on Slack: - - - ``` - Hey! Just a heads up, tomorrow is [teammate’s name] [number of years at Fleet] workiversary at Fleet. - Digital Experience can post something in the #random channel to recognize them, would you like to make that post instead? - ``` - - > If a manager elects to post and hasn't done so by 2pm ET on the day of the workiversary, send them a friendly reminder and offer to post instead. - -4. If the manager has deferred to Digital Experience, schedule a Slack post for the following day to recognize the teammate's contributions at Fleet. If you’re unsure about what to post, take a look at what’s been [posted previously](https://docs.google.com/document/d/1Va4TYAs9Tb0soDQPeoeMr-qHxk0Xrlf-DUlBe4jn29Q/edit). - - -### Prepare salary benchmarking information - -1. Use the relevant template text in the README section of the [¶¶ 💌 Compensation decisions document](https://docs.google.com/document/d/1NQ-IjcOTbyFluCWqsFLMfP4SvnopoXDcX0civ-STS5c/edit?usp=sharing) for a current Fleetie, a new role, a prospective hire, or other benchmarking use case. -2. Copy the template text and paste at the end of the document. -3. Fill in details as required, pulling from [🧑‍🚀 Fleeties spreadsheet](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) and [equity spreadsheet](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit?usp=sharing) as required. -4. Use the teammate's information to benchmark in [Pave](https://www.pave.com/) (login details in 1Password). You can pattern match from previous benchmarking entries, and include all company assumtions. Add the direct link to the Pave benchmark. - - -### Update a team member's compensation - -To [change a teammate's compensation](https://fleetdm.com/handbook/company/communications#compensation-changes), follow these steps: -1. Create a copy of the ["Values assessment" template](https://docs.google.com/spreadsheets/d/1P5TyRV2v-YN0aR_X8vd8GksKcr3uHfUDdshqpVzamV8/edit?usp=drive_link) and move it to the teammate's [personnel folder in Google Drive](https://drive.google.com/drive/folders/1UL7o3BzkTKnpvIS4hm_RtbOilSABo3oG?usp=drive_link). -2. Share the values assessment document with the manager and ask them to perform the values assessment. -3. Once the values assessment is complete, [prepare salary benchmarking information](#prepare-salary-benchmarking-information) and notify the Head of Digital Experience so the compensation change can be added to the e-group agenda for discussion amongst Fleet leadership. - - If the teammate's manager is not part of the e-group, the Head of Digital Experience will ensure they're included in the discussion at e-group as well. -4. Once compensation decisions have been finalized, the Head of Digital Experience will post in slack to `#help-classified` to confirm the decisions have been recorded in ["¶¶ 💌 Compensation decisions (offer math)"](https://docs.google.com/document/d/1NQ-IjcOTbyFluCWqsFLMfP4SvnopoXDcX0civ-STS5c/edit#heading=h.slomq4whmyas). -5. Send the teammates manager a Slack DM to determine who will communicate the decision to the teammate. -6. Update the respective payroll platform (Gusto or Plane) by navigating to the personnel page, selecting salary field, and updating with an effective date that makes the next payroll. -7. Update the [equity spreadsheet](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit?usp=sharing) (internal doc) by copying existing OTE to the bottom of the "Notes" cell, updating the OTE column with the new compensation information, and updating the "Last compensation change" column with the effective date from payroll platform. -8. Calculate the monthly burn rate increase percentage and notify the CEO via a Slack DM. - -> If the company decides on an additional equity grant as part of a compensation change, note the previous equity and new situation in detail in the "Notes" column of the equity plan. Update the "Grant started?" column to "todo" which adds it to the queue for the next time grants are processed (quarterly). - - -### Review Fleet's US company benefits - -Annually, around mid-year, Fleet will be prompted by Gusto to review company benefits. The goal is to keep changes minimal. Follow these steps: -1. Log in to your [Gusto admin account](https://gusto.com/). -2. Navigate to "Benefits" and select "Renewal survey". -3. Complete the survey questions, aiming for minimal changes. -4. Approximately 2-3 months after survery completion, Gusto will suggest plans based on Fleet's responses. Choose plans with minimal changes. -5. Gusto will offer these plans to employees during open enrollment, with new coverage starting 3-4 weeks afterward. - - -### Grant equity - -Equity grants for new hires are queued up as part of the [hiring process](https://fleetdm.com/handbook/digital-experience#hiring), then grants and consents are [batched and processed quarterly](https://github.com/fleetdm/confidential/issues/new/choose). - -Doing an equity grant involves: -- Executing a board consent -- The recipient and CEO signing paperwork about the stock options -- Updating the number of shares for the recipient in the equity plan -- Updating Carta to reflect the grant - -For the status of stock option grants, exercises, and all other _common stock_ including advisor, founder, and team member equity ownership, see [Fleet's equity plan](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit#gid=0). For information about investor ownership, see [Carta](https://app.carta.com/corporations/1234715/summary/). - -> Fleet's [equity plan](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit#gid=0) is the source of truth, not Carta. Neither are pro formas sent in an email attachment, even if they come from lawyers. -> -> Anyone can make mistakes, and none of us are perfect. Even when we triple check. Small mistakes in share counts can be hard to attribute, and can cause headaches and eat up nights of our CEO's and operations team's time. If you notice what might be a discrepancy between the equity plan and any other secondary source of information, please speak up and let Fleet's CEO know ASAP. Even if you're wrong, your note will be appreciated. - - -### Review an NDA - -We need to review an NDA anytime a vendor, customer or other party wants to: -- Use their own NDA rather than Fleet's standard NDA, or -- "Redline" (modify) Fleet's NDA by removing, adding or altering its terms. - -We should always seek to use Fleet's own NDA first, without alteration. - -When reading an NDA, we want to pay close attention to the following: -- We want to be sure that the confidentiality obligations of the NDA are reciprocal. Fleet and the other party to the agreement should be bound to the same standards of confidentiality toward the handling of each other's confidential information. -- Fleet does not agree to _"do not compete"_ or _"do not solicit clauses"_. An NDA should not contain provisions beyond the scope of an NDA. The two most commonly encountered examples of this are the "do not compete" and "do not solicit" clauses. We want to be free to hire the best people and make the best products, so when reading through an NDA it is important to keep an eye out for language that prohibits Fleet from hiring or soliciting current or former employees of other companies or that prohibit Fleet from independently developing products that compete with another company's products. Using the `cmd + f` function to search for "solici", "compet" and "hir" and reading through the results is a helpful method to quickly scan for these clauses. -- Look for any language that discusses a transfer of property rights. Rarely, you may find a clause snuck into an agreement that discusses the transfer of intellectual property rights. _We want to avoid any situation where Fleet transfers its intellectual property to another party as part of an NDA_. -- Should you find any clauses in steps 2 or 3 that are beyond the scope of protecting both party's confidential information in a customer NDA or an altered version of Fleet's NDA, reject this language and communicate that Fleet cannot agree to those terms. -- Any concerns or uncertainty over _any_ provisions in an NDA should be brought to Nathanael Holliday in Digital Experience, who will consult legal counsel if necessary to resolve any concerns. - - -### Review a vendor agreement - -When reviewing contracts from a vendor, Fleet is concerned about the following: -- If there are confidentiality provisions in the agreement in place of a stand-alone NDA, verify the confidentiality provisions are appropriate and protect Fleet when sensitive data is involved that isn't otherwise available to the public. -- We want to make sure there are no _do not solicit_ or _do not compete_ clauses in the contract. To aid in this search, we double check by using the cmd + f function and searching for "solici", "compet" and "hir" and then looking through the results to be sure that nothing prohibits Fleet from independently developing competing products or from hiring personnel with ties to the vendor. -- We want to make sure that contracts can be terminated relatively easily and be aware of what the process is for terminating them, avoiding commitments over 12 months in length. -- We want to make sure the payment terms work for us (i.e. being able to pay via wire transfer, credit card or bill.com) and that the price in any contract or order form is what we have agreed to. While almost never malicious, mistakes often occur in the steps between agreeing on a price, negotiating a contract, and receiving an invoice. We want to be sure at every step that the dollar amount and service provided is consistent with what has been negotiated and agreed upon. -- Remember, once we have signed the agreement - we're stuck with it. If any clause in the agreement appears strange or gives you pause or concern, it is better to seek clarification than to commit to something that might be detrimental to Fleet. Contracts are fairly standardized, and you'll quickly learn what is normal and what feels out of place. Unusual clauses or wording that seems out of the ordinary should get a second set of eyes just to be sure, do not hesitate to reach out to Nathanael Holliday with questions, who will reach out to legal counsel as necessary. - - -### Review an order form - -- We should always check order forms for additional terms that go beyond the scope of the order form (caps on price increases, for example). -- Be sure the order form includes contact information + billing address and information so that Fleet knows how and who to invoice for payment. -- Verify that the payment terms are correct and matches what's in the agreement. This is a frequent common mistake as companies usually have default payment terms and overlook changing them to match atypical payment terms. -- Make sure the effective term of the order matches what was agreed upon (usually a one year term) and that the order form includes the correct number of hosts and whether or not it should contain professional services (usually, it does not). -- Check that the amount on the order form reflects what Fleet agreed to, as this is the amount that the customer will expect to be invoiced for. -- Lastly, double check one more time to make sure there are no sneaky, unusual terms snuck in at the bottom of an order form or stashed away in fine print. Common things that are included in order forms and not always communicated to Fleet are caps on price increases upon renewal, new SLAs, or a product roadmap or milestones we may not have agreed upon. Any clauses on an order form that appear beyond the scope of simply elaborating on the services being provided, the purchase cost, the contract that the purchase is being made under, how Fleet will bill and how the customer will pay deserves a careful look. Reach out to Nathanael Holliday in Digital Experience with concerns. - - -### Review a non-standard subscription agreement - -We want to use our standard terms whenever possible with our customers, but it is common that customers want to use their own agreement or redline (modify) Fleet's terms. -When reviewing subscription agreements on customer paper or when a customer has made changes to Fleet's terms, we review it using [these guidelines](https://docs.google.com/document/d/1aGgN5It1i3fdsBF37vWSbvukO_gQhy5vCp4fINg191Q/edit?usp=sharing). - - ### QA a change to fleetdm.com Each PR to the website is manually checked for quality and tested before going live on fleetdm.com. To test any change to fleetdm.com @@ -257,20 +45,6 @@ Each PR to the website is manually checked for quality and tested before going l 3. Check the change in relation to all breakpoints and [browser compatibility](https://fleetdm.com/handbook/digital-experience#check-browser-compatibility-for-fleetdm-com), Tests are carried out on [supported browsers](https://fleetdm.com/docs/using-fleet/supported-browsers) before website changes go live. -### Update the host count of a premium subscription - -When a self-service license dispenser customer reaches out to upgrade a license via the contact form, a member of the [Demand department](https://fleetdm.com/handbook/demand) will create a confidential issue detailing the request and add it to the new requests column of [Digital Experience kanban board](https://github.com/fleetdm/confidential/issues#workspaces/g-digital-experience-6451748b4eb15200131d4bab/board). A member of this team will then log into Stripe using the shared login, and upgrade the customer's subscription. - -To update the host count on a user's subscription: - -1. Log in to the [Stripe dashboard](https://dashboard.stripe.com/dashboard) and search for the customer's email address. -2. Click on their subscription and select the "Update subscription" option in the "Actions" dropdown -3. Update the quantity of the user's subscription to be their desired host count. -4. Turn the "Proration charges" option on and select the "Charge proration amount immediately" option. -5. Under "Payment" select "Email invoice to the customer", and set the payment due date to be 15 days, and make sure the "Invoice payment page" option is checked. -6. Select "Update subscription" to send the user an updated invoice for their subscription. Once the customer pays their new invoice, the Fleet website will update the user's subscription and generate a new Fleet Premium license with an updated host count. -7. Let the person who created the request know what actions were taken so they can communicate them to the customer. - ### Test fleetdm.com locally @@ -380,6 +154,7 @@ To generate a new page, you'll need: 6. Replace the lorum ipsum and placeholder images on the generated page with the page's real content, and add a meta description and title by changing the `pageTitleForMeta` and `pageDescriptionForMeta in the page's `locals` in `website/config/routes.js`. --> + ### Restart Algolia manually At least once every hour, an Algolia crawler reindexes the Fleet website's content. If an error occurs while the website is being indexed, Algolia will block our crawler and respond to requests with this message: `"This action cannot be executed on a blocked crawler"`. @@ -398,25 +173,216 @@ If the action fails, please complete the following steps: 3. Head to the fleetdm/fleet GitHub repository and re-run the Deploy Fleet Website action. -### Communicate Fleet's potential energy to stakeholders +### Update a company brand front -On the first business day of every month, the Head of Digital Experience will send an update to the stakeholders of Fleet using the following steps: -1. Copy the following template into an outgoing email with the subject line: "[Investor update] Fleet, YYYY-MM". - -``` -Hi investors and friends, +Fleet has several brand fronts that need to be updated from time to time. Check each [brand front](https://docs.google.com/spreadsheets/d/1c15vwMZytpCLHUdGvXxi0d6WGgPcQU1UBMniC1F9oKk/edit?gid=0#gid=0) for consistency and update as needed with the following: +- The current pitch, found in the blurbs section of the [🎐 Why Fleet?](https://docs.google.com/document/d/1E0VU4AcB6UTVRd4JKD45Saxh9Gz-mkO3LnGSTBDLEZo/edit#heading=h.uovxedjegxdc) doc. +- The current [brand imagery](https://www.figma.com/design/1J2yxqH8Q7u8V7YTtA1iej/Social-media-(logos%2C-covers%2C-banners)?node-id=3962-65895). Check this [Loom video](https://www.loom.com/share/4432646cc9614046aaa4a74da1c0adb5?sid=2f84779f-f0bd-4055-be69-282c5a16f5c5) for more info. -FYI we just updated the self-service investor update portal with the numbers from last month: https://docs.google.com/spreadsheets/d/10T7Q9iuHA4vpfV7qZCm6oMd5U1bLftBSobYD0RR8RkM/edit#gid=0 +### Respond to a "Contact us" submission + +1. Check the [_from-prospective-customers](https://fleetdm.slack.com/archives/C01HE9GQW6B) Slack channel for "Contact us" submissions. +2. Mark submission as seen with the "👀" emoji. +3. Within 4 business hours, use the [_from-prospective-customers workflow (private Google doc)](https://docs.google.com/document/d/1D02k0tc5v-sEJ4uahAouuqnvZ6phxA_gP-IqmkBdMTE/edit) to respond to general asks. Follow the "High-level workflow" to understand how to respond and who to loop into the conversation. +4. Answer any technical questions to the best of your ability. If you are unable to answer a technical/product question, ask a Solutions Consultant in `#help-solutions-consulting`. If an SC is unavailable, post in `#g-mdm`or `#g-endpoint-ops`and notify @on-call. +5. Mark the Slack message as complete with the "✅" emoji. + +> For any support-related questions, forward the submission to [Fleet's support team](https://docs.google.com/document/d/1tE-NpNfw1icmU2MjYuBRib0VWBPVAdmq4NiCrpuI0F0/edit#heading=h.wqalwz1je6rq). -Thanks for your support, -Mike and the Fleet team +### Update the host count of a premium subscription -``` +When a self-service license dispenser customer reaches out to upgrade a license via the contact form, a member of the [Demand department](https://fleetdm.com/handbook/demand) will create a confidential issue detailing the request and add it to the new requests column of [Digital Experience kanban board](https://github.com/fleetdm/confidential/issues#workspaces/g-digital-experience-6451748b4eb15200131d4bab/board). A member of this team will then log into Stripe using the shared login, and upgrade the customer's subscription. -2. Address the email to the executive team's Gmail. -3. Using the [🌧️🦉 Investors + advisors](https://docs.google.com/spreadsheets/d/15knBE2-PrQ1Ad-QcIk0mxCN-xFsATKK9hcifqrm0qFQ/edit#gid=1068113636) spreadsheet, bcc the correct individuals and send the email. +To update the host count on a user's subscription: + +1. Log in to the [Stripe dashboard](https://dashboard.stripe.com/dashboard) and search for the customer's email address. +2. Click on their subscription and select the "Update subscription" option in the "Actions" dropdown +3. Update the quantity of the user's subscription to be their desired host count. +4. Turn the "Proration charges" option on and select the "Charge proration amount immediately" option. +5. Under "Payment" select "Email invoice to the customer", and set the payment due date to be 15 days, and make sure the "Invoice payment page" option is checked. +6. Select "Update subscription" to send the user an updated invoice for their subscription. Once the customer pays their new invoice, the Fleet website will update the user's subscription and generate a new Fleet Premium license with an updated host count. +7. Let the person who created the request know what actions were taken so they can communicate them to the customer. + + +### Cancel a Fleet Premium subscription + +Use the following steps to cancel a Fleet Premium subscription: +1. Log into [Stripe](https://dashboard.stripe.com/dashboard) (login in 1Password) and paste the customer's email they used to sign up in the search bar at the top of the page. +2. Select the subscription related to the email and use the "Actions" drop-down to "Cancel immediately". +3. Reach out to the community member (using the [correct email template](https://docs.google.com/document/d/1D02k0tc5v-sEJ4uahAouuqnvZ6phxA_gP-IqmkBdMTE/edit#heading=h.vw9mkh5e9msx)) and let them know their subscription was canceled. + + +### Secure company-issued equipment for a team member + +As soon as an offer is accepted, Fleet provides laptops and YubiKey security keys for core team members to use while working at Fleet. The IT engineer will work with the new team member to get their equipment requested and shipped to them on time. + +- [**Check the Fleet IT warehouse**](https://docs.google.com/spreadsheets/d/1hFlymLlRWIaWeVh14IRz03yE-ytBLfUaqVz0VVmmoGI/edit#gid=0) before purchasing any equipment including laptops, monitors, and Yubikeys to ensure we efficiently [utilize existing assets before spending money](https://fleetdm.com/handbook/company/why-this-way#why-spend-less). If Fleet IT warehouse inventory can meet the needs of the request, file a [warehouse request](https://github.com/fleetdm/confidential/issues/new?assignees=sampfluger88&labels=%23g-digital-experience&projects=&template=warehouse-request.md&title=%F0%9F%92%BB+Warehouse+request). + +- Apple computers shipping to the United States and Canada are ordered using the Apple [eCommerce Portal](https://ecommerce2.apple.com/asb2bstorefront/asb2b/en/USD/?accountselected=true), or by contacting the business team at an Apple Store or contacting the online sales team at [800-854-3680](tel:18008543680). The IT engineer can arrange for same-day pickup at a store local to the Fleetie if needed. + - **Note:** Most Fleeties use 16-inch MacBook Pros. Team members are free to choose any laptop or operating system that works for them, as long as the price [is within reason](https://www.fleetdm.com/handbook/communications#spending-company-money). + + - When ordering through the Apple eCommerce Portal, look for a banner with *Apple Store for FLEET DEVICE MANAGEMENT | Welcome [Your Name].* Hovering over *Welcome* should display *Your Profile.* If Fleet's account number is displayed, purchases will be automatically made available in Apple Business Manager (ABM). + +- Apple computers for Fleeties in other countries should be purchased through an authorized reseller to ensure the device is enrolled in ADE. In countries that Apple does not operate or that do not allow ADE, work with the authorized reseller to find the best solution, or consider shipping to a US based Fleetie and then shipping on to the teammate. + + > A 3-year AppleCare+ Protection Plan (APP) should be considered default for Apple computers >$1500. Base MacBook Airs, Mac minis, etc. do not need APP unless configured beyond the $1500 price point. APP provides 24/7 support, and global repair coverage in case of accidental screen damage or liquid spill, and battery service. + + - Order a pack of two [YubiKey 5C NFC security keys](https://www.yubico.com/product/yubikey-5-series/yubikey-5c-nfc/) for new team member, shipped to them directly. + +- Include delivery tracking information when closing the support request so the new employee can be notified. + + +### Process incoming equipment + +Upon receiving any device, follow these steps to process incoming equipment. +1. Search for the SN of the physical device in the ["Company equipment" spreadsheet](https://docs.google.com/spreadsheets/d/1hFlymLlRWIaWeVh14IRz03yE-ytBLfUaqVz0VVmmoGI/edit#gid=0) to confirm the correct equipment was received. +3. Visibly inspect equipment and all related components (e.g. laptop charger) for damage. +4. Remove any stickers and clean devices and components. +5. Using the device's charger plug in the device. +6. Turn on the device and enter recovery mode using the [appropriate method](https://support.apple.com/en-us/HT204904). +7. Connect the device to WIFI. +8. Using the "Recovery assistant" tab (In the top left corner), select "Delete this Mac". +9. Follow the prompts to activate the device and reinstall the appropriate version of macOS. +> If you are prevented from completing the steps above, create a ["💻 IT support issue](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=custom-request.md&title=) for IT, for the device to be scheduled for troubleshooting and remediation. Please note in the issue where you encountered blockers to completing the steps. + + +### Ship approved equipment + +Once the Digital Experience department approves inventory to be shipped from Fleet IT, follow these step to ship the equipment. +1. Compare the equipment request issue with the ["Company equipment" spreadsheet](https://docs.google.com/spreadsheets/d/1hFlymLlRWIaWeVh14IRz03yE-ytBLfUaqVz0VVmmoGI/edit#gid=0) and verify physical inventory. +2. Plug in the device and ensure inventory has been correctly processed and all components are present (e.g. charger cord, power converter). +3. Package equipment for shipment and include Yubikeys (if requested). +4. Change the "Company equipment" spreadsheet to reflect the new user. + - If you encounter any issues, repeat the [process incoming equipment steps](https://fleetdm.com/handbook/digital-experience#process-incoming-equipment). If problems persist, create a ["💻 IT support issue](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=custom-request.md&title=) for IT to troubleshoot the device. +6. Ship via FedEx to the address listed in the equipment request. +7. Add a comment to the equipment request issue, at-mentioning the requestor with the FedEx tracking info and close the issue. + + +### Update personnel details + +When a Fleetie, consultant or advisor requests an update to their personnel details (name, location, phone, etc), follow these steps to ensure accurate representation across systems. +1. Team member submits a [custom issue](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=custom-request.md&title=) to update their personnel details (or Digital Experience team creates if the request comes via email or is sensitive and needs a classified issue). + - If change is for a primary identification or contact method, ask for evidence of change and capture in [employee's personnel file](https://drive.google.com/drive/folders/1UL7o3BzkTKnpvIS4hm_RtbOilSABo3oG?usp=drive_link). +2. Digital Experience makes change to HRIS (Gusto or Plane) to reflect change. + - Note: if making the change requires follow up steps, resolve those steps to action the change. +3. Once change is effected in HRIS, Digital Experience makes changes to ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) spreadsheet. +4. If required, Digital Experience makes any relevant changes to [Fleet's equity plan](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit#gid=0). +5. If required, Digital Experience makes any relevant changes to the ["🗺️ Geographical factors"](https://docs.google.com/spreadsheets/d/1rCVCs-eOo-VSEG7fPLgdq5l7oSaActl5bewaWP7PnSE/edit#gid=1533353559) spreadsheet and follows through on any action items involving tax implications (i.e. registering with a new state for employer taxes). +6. If required, Digital Experience also makes changes to other core systems (e.g: creating a new email alias in google workspace; updating details in Carta; etc). +7. The change is now actioned, notify the team member and close the issue. + +> Note: if the Fleetie is US based and has a qualifying life event that impacts benefit coverage, they can [follow the Gusto steps](https://support.gusto.com/article/100895878100000/Change-your-benefits-with-a-qualifying-life-event) to update their coverage elections. + + +### Change a Fleetie's role + +When Digital Experience receives [notification of a Fleetie's role changing](https://fleetdm.com/handbook/company/leadership#request-a-role-change-for-a-fleetie), The Head of Digital Experience will bring the proposed title change to the next Roundup meeting with the CEO for approval. If the proposed change is rejected, the Head of Digital Experience will inform the requesting manager as to why. If approved, use the following steps to change a Fleetie's role: +1. Update ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0): + - Search the spreadsheet for the Fleetie in need of a job title change. + - Input the new job title in the Fleetie's row in the "Job title" cell. + - Navigate to the "Org chart" tab of the spreadsheet, and verify that the Fleetie's title appears correctly in the org chart. +2. Update the departmental handbook page with the change of job title +3. [Prepare salary benchmarking information](#prepare-salary-benchmarking-information) to determine whether the teammate's current compensation aligns with the benchmarks of the new role. + - If the benchmark is significantly different, take the steps to [update a team member's compensation](#prepare-salary-benchmarking-information). +4. Update the relevant payroll/HRIS system. + - For updating Gusto (US-based Fleeties): + - Login to Gusto and navigate to "People > Team members". + - Find the Fleetie and select them to see their profile page. + - Under the "Compensation" heading, select edit and update the "Job title" and input the specific date the change happened. Save the changes. + - For updating Plane (non-US Fleeties): + - Login to Plane and navigate to "People > Team". + - Find the Fleetie and select them to see their profile page. + - Use the "Help" function, or email support@plane.com to notify Plane of the need to change the job title for the Fleetie. Include the Fleetie's name, current title, new title, and effective date. + - Take any relevant steps as directed by Plane in order to make the required changes to the Fleetie's profile. + + +### Change a Fleetie's manager + +When Digital Experience receives notification of a Fleetie's manager changing, follow these steps to ensure correct recording in our systems. +1. Update [🧑‍🚀 Fleeties](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0): + - Search for the Fleetie's new manager, and copy the new manager's unique ID from the far left "Unique ID" column. + - Search for the Fleetie whose manager is changing, and paste (without formatting) their new manager's unique ID in the "Reports to: (manager unique ID)" cell in the Fleetie's row. + - Verify that the "Reports to (auto: manager name and job title)" cell in the Fleetie's row reflects the new manager's details. + - Verify that in the new manager's row, the "# direct reports" cell reflect the correct number. + - Navigate to the "Org chart" tab in the spreadsheet, and verify that the Fleetie now appears in the correct place in the org chart. +2. If the person's department is changing, then update both departmental handbook pages to move the person to their new department: + - Remove the person from the "Team" section of the old department and add them to the "Team" section of the new department. +3. If the person's level of confidential access will change along with the change to their manager, then update that level of access: + - Update Google Workspace to make sure this person lives in the correct Google Group, removing them from the old and/or adding them to the new. + - Update 1password to remove this person from old vaults and/or add them to new vaults. + - For a team member moving from "classified" to "confidential" access, check Gusto, Plane, and other systems to remove their access. + +> **Note:** The Fleeties spreadsheet is the source of truth for who everyone's manager is and their job titles. + + +### Recognize employee workiversaries + +At Fleet, everyone is recognized on their [workiversary](https://fleetdm.com/handbook/company/communications#workiversaries). To ensure this happens, take the following steps: + +1. On the 15th of every month, use [Fleeties (private google doc)](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) to determine who is celebrating their workiversary in the next month. +2. At-mention the Head of Digital Experience. Use the following template: + + + ``` + [Month] + [workiversary date (DD-MMM)] - [teammate name] - [number of years at Fleet] + ``` + + The Head of Digital Experience will also use this post to update the [All hands](https://fleetdm.com/handbook/company/communications#all-hands) deck. +3. On the day prior to a workiversary, send the teammate’s manager a DM on Slack: + + + ``` + Hey! Just a heads up, tomorrow is [teammate’s name] [number of years at Fleet] workiversary at Fleet. + Digital Experience can post something in the #random channel to recognize them, would you like to make that post instead? + ``` + + > If a manager elects to post and hasn't done so by 2pm ET on the day of the workiversary, send them a friendly reminder and offer to post instead. + +4. If the manager has deferred to Digital Experience, schedule a Slack post for the following day to recognize the teammate's contributions at Fleet. If you’re unsure about what to post, take a look at what’s been [posted previously](https://docs.google.com/document/d/1Va4TYAs9Tb0soDQPeoeMr-qHxk0Xrlf-DUlBe4jn29Q/edit). + + +### Prepare salary benchmarking information + +1. Use the relevant template text in the README section of the [¶¶ 💌 Compensation decisions document](https://docs.google.com/document/d/1NQ-IjcOTbyFluCWqsFLMfP4SvnopoXDcX0civ-STS5c/edit?usp=sharing) for a current Fleetie, a new role, a prospective hire, or other benchmarking use case. +2. Copy the template text and paste at the end of the document. +3. Fill in details as required, pulling from [🧑‍🚀 Fleeties spreadsheet](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) and [equity spreadsheet](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit?usp=sharing) as required. +4. Use the teammate's information to benchmark in [Pave](https://www.pave.com/) (login details in 1Password). You can pattern match from previous benchmarking entries, and include all company assumtions. Add the direct link to the Pave benchmark. + + +### Update a team member's compensation + +To [change a teammate's compensation](https://fleetdm.com/handbook/company/communications#compensation-changes), follow these steps: +1. Create a copy of the ["Values assessment" template](https://docs.google.com/spreadsheets/d/1P5TyRV2v-YN0aR_X8vd8GksKcr3uHfUDdshqpVzamV8/edit?usp=drive_link) and move it to the teammate's [personnel folder in Google Drive](https://drive.google.com/drive/folders/1UL7o3BzkTKnpvIS4hm_RtbOilSABo3oG?usp=drive_link). +2. Share the values assessment document with the manager and ask them to perform the values assessment. +3. Once the values assessment is complete, [prepare salary benchmarking information](#prepare-salary-benchmarking-information) and notify the Head of Digital Experience so the compensation change can be added to the e-group agenda for discussion amongst Fleet leadership. + - If the teammate's manager is not part of the e-group, the Head of Digital Experience will ensure they're included in the discussion at e-group as well. +4. Once compensation decisions have been finalized, the Head of Digital Experience will post in slack to `#help-classified` to confirm the decisions have been recorded in ["¶¶ 💌 Compensation decisions (offer math)"](https://docs.google.com/document/d/1NQ-IjcOTbyFluCWqsFLMfP4SvnopoXDcX0civ-STS5c/edit#heading=h.slomq4whmyas). +5. Send the teammates manager a Slack DM to determine who will communicate the decision to the teammate. +6. Update the respective payroll platform (Gusto or Plane) by navigating to the personnel page, selecting salary field, and updating with an effective date that makes the next payroll. +7. Update the [equity spreadsheet](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit?usp=sharing) (internal doc) by copying existing OTE to the bottom of the "Notes" cell, updating the OTE column with the new compensation information, and updating the "Last compensation change" column with the effective date from payroll platform. +8. Calculate the monthly burn rate increase percentage and notify the CEO via a Slack DM. + +> If the company decides on an additional equity grant as part of a compensation change, note the previous equity and new situation in detail in the "Notes" column of the equity plan. Update the "Grant started?" column to "todo" which adds it to the queue for the next time grants are processed (quarterly). + + +### Grant equity + +Equity grants for new hires are queued up as part of the [hiring process](https://fleetdm.com/handbook/digital-experience#hiring), then grants and consents are [batched and processed quarterly](https://github.com/fleetdm/confidential/issues/new/choose). + +Doing an equity grant involves: +- Executing a board consent +- The recipient and CEO signing paperwork about the stock options +- Updating the number of shares for the recipient in the equity plan +- Updating Carta to reflect the grant + +For the status of stock option grants, exercises, and all other _common stock_ including advisor, founder, and team member equity ownership, see [Fleet's equity plan](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit#gid=0). For information about investor ownership, see [Carta](https://app.carta.com/corporations/1234715/summary/). + +> Fleet's [equity plan](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit#gid=0) is the source of truth, not Carta. Neither are pro formas sent in an email attachment, even if they come from lawyers. +> +> Anyone can make mistakes, and none of us are perfect. Even when we triple check. Small mistakes in share counts can be hard to attribute, and can cause headaches and eat up nights of our CEO's and operations team's time. If you notice what might be a discrepancy between the equity plan and any other secondary source of information, please speak up and let Fleet's CEO know ASAP. Even if you're wrong, your note will be appreciated. ### Grant role-specific license to a team member @@ -465,6 +431,37 @@ Here are the steps we take to grant appropriate Salesforce licenses to a new hir - Navigate to the ["Einstein Activity Capture Settings"](https://fleetdm.lightning.force.com/lightning/setup/ActivitySyncEngineSettingsMain/home) and click the "Configurations" tab. - Select "Edit", under "User and Profile Assignments" move the new user's name from "Available" to "Selected", scroll all the way down and click save. + +### Inform managers about hours worked + +Every Friday, we collect hours worked for all hourly employees at Fleet, including core team members and consultants, regardless of their location. Consultants submit their hours through Gusto (US consultants) or Plane.com (international consultants) for DRI (generally their manager) review. Here's how: +1. Find the DRI using the [Digital Experience KPIs](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit#gid=0). +2. Copy the template in the consultants column of the KPIs and send the teammate's DRI a direct message in Slack hours with an FYI including the total hours logged since last Saturday at midnight. For international teammates, they cannot enter hours weekly in Plane.com, so you will need to request the hours worked from them in order to have the DRI approve them. +3. The following Monday, check for updates to logged hours and ensure the KPI sheet aligns with HRIS records. If there are discrepancies between what was previously reported, reconfirm logged hours with the teammate's DRI and update the KPI sheet to reflect the correct amount. + + +### Change the DRI of a consultant + +1. In the [KPIs](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit#gid=0) sheet, find the consultant's column. +2. Change the DRI documented there to the new DRI who will receive information about the consultant's hours. + + +### Convert a Fleetie to a consultant + +If a Fleetie decides they want to move to being a [consultant](https://fleetdm.com/handbook/company/leadership#consultants), either the Fleetie or their manager need to create a [custom issue for the Digital Experience team](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=custom-request.md&title=Request%3A+_______________________) to notify them of the change. +Once notified, Digital Experience takes the following steps: +1. Confirm the following details with the Fleetie: + - Date of change + - Term of consultancy (time period) + - Hours/capacity expected (hours per week or month) + - Confirm hourly rate +2. Once details are confirmed, use the information given to create the consulting agreement for the Fleetie (either in docusign (US-based) or via Plane (international)), and send to their personal email for signature. Once signed, save in Fleetie's [employee file](https://drive.google.com/drive/folders/1UL7o3BzkTKnpvIS4hm_RtbOilSABo3oG?usp=drive_link). +3. Schedule the Fleetie's final day in HRIS (Gusto or Plane). +4. Update final day in ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) spreadsheet. +5. Create an [offboarding issue](https://github.com/fleetdm/classified/blob/main/.github/ISSUE_TEMPLATE/%F0%9F%9A%AA-offboarding-____________.md) for the Fleetie converting to a consultant, and confirm with their manager if there is a need to retain any tools or access while they are a consultant (default to removing all access from Fleet email, and migrating to personal email for Slack and other tools unless there is a business case to retain the Fleet email and associated tool access). +6. Follow the offboarding issue for next steps, including communicating to teammates and updating equity plan. + + ### Change the "Integrations admin" Salesforce account password Salesforce requires that the password to the "Integrations admin" account is changed every 90 days. When this happens, the Salesforce integrations on the Fleet website/Hydroplane will fail with an `INVALID_LOGIN` error. To prevent this from happening, a member of the Digital expererience team will: @@ -475,75 +472,62 @@ Salesforce requires that the password to the "Integrations admin" account is cha 4. Update the `sails_config__custom_salesforceIntegrationPasskey` config variable in Heroku to be `[password][security token]` (For both the Fleet website and Hydroplane). -### Schedule press release +### Review Fleet's US company benefits -Fleet will occasionally release information to the press regarding upcoming initiatives before updating the functionality of the core product. Use the following steps to schedule a press release: - -1. Add context for the next press release to the [e-group agenda](https://docs.google.com/document/d/13fjq3T0bZGOUah9cqHVxngckv0EB2R24A3gfl5cH7eo/edit) as a "DISCUSS:" to be reviewed by Fleet's executive team for alignment and finalization of date. -2. Once a release date is set, at-mention our public relations firm in the [#help-public-relations-firm--mindshare-pr--brand-marketing](https://fleetdm.slack.com/archives/C04PC9H34LF) and schedule a 30m call for our CEO and to communicate the press release. - -> The above must be completed 6 weeks before the press release date. - -3. Schedule a 1.5h discussion between the [Head of Digital Experience](https://fleetdm.com/handbook/digital-experience#team) and the CEO to review the first draft linked as "Agenda: LINK" to the calendar event description. -4. Schedule a 60m call with the CEO and public relations firm to review the first draft linked as above to the calendar event (first draft provided by the PR firm) -5. Schedule 2.5 hrs of async time for the CEO work on edits and a 60m followup postgame (solo) where CEO edits and then settles+sends final release. +Annually, around mid-year, Fleet will be prompted by Gusto to review company benefits. The goal is to keep changes minimal. Follow these steps: +1. Log in to your [Gusto admin account](https://gusto.com/). +2. Navigate to "Benefits" and select "Renewal survey". +3. Complete the survey questions, aiming for minimal changes. +4. Approximately 2-3 months after survery completion, Gusto will suggest plans based on Fleet's responses. Choose plans with minimal changes. +5. Gusto will offer these plans to employees during open enrollment, with new coverage starting 3-4 weeks afterward. -### Process the help-being-ceo Slack channel +### Access a background check -The Apprentice will perform the following steps to process all communication from the CEO in the [help-being-ceo Slack channel](https://fleetdm.slack.com/archives/C03U703J0G5). -1. As soon as the message is received in the channel, add the "`:eyes:` (👀)" emoji to the Slack message to signify that you have seen and understood the task or question. -2. Start a Slack thread to add any context or let the stakeholders know the status of the task. -3. After each task is completed, apply the "`:white_check_mark:`" (✅) to the slack message. +All Fleet team members undergo a background check provided through [Vetty](https://vetty.co/). Only the most recent background checks appear on the home page of Vetty's dashboard. To access a complete list of background checks run in Vetty, scroll down to the bottom of the candidates page and click "View Historical". -### Archive a document +### Review an NDA -Follow these steps to archive any document: -1. Create a copy of the document prefixed with the date using the format "`YYYY-MM-DD` Backup of `DOCUMENT_NAME`" (e.g. "2024-03-22 Backup of 🪂🗞️ Customer voice"). -2. Be sure to "Share it with the same people", "Copy comments and suggestions", and "Include resolved comments and suggestions" as shown below. +We need to review an NDA anytime a vendor, customer or other party wants to: +- Use their own NDA rather than Fleet's standard NDA, or +- "Redline" (modify) Fleet's NDA by removing, adding or altering its terms. -Screenshot 2024-03-23 at 12 14 00 PM +We should always seek to use Fleet's own NDA first, without alteration. -3. Save this backup copy to the same location in Google Drive where the original is found. -4. Link to the backup copy at the top of the original document. Be sure to use the full URL, no abbreviated pill links (e.g. "Notes from last time: URL_OF_MOST_RECENT_BACKUP_DOCUMENT"). -5. Delete all non-structural content from the original document, including past meeting notes and current answers to "evergreen" questions. +When reading an NDA, we want to pay close attention to the following: +- We want to be sure that the confidentiality obligations of the NDA are reciprocal. Fleet and the other party to the agreement should be bound to the same standards of confidentiality toward the handling of each other's confidential information. +- Fleet does not agree to _"do not compete"_ or _"do not solicit clauses"_. An NDA should not contain provisions beyond the scope of an NDA. The two most commonly encountered examples of this are the "do not compete" and "do not solicit" clauses. We want to be free to hire the best people and make the best products, so when reading through an NDA it is important to keep an eye out for language that prohibits Fleet from hiring or soliciting current or former employees of other companies or that prohibit Fleet from independently developing products that compete with another company's products. Using the `cmd + f` function to search for "solici", "compet" and "hir" and reading through the results is a helpful method to quickly scan for these clauses. +- Look for any language that discusses a transfer of property rights. Rarely, you may find a clause snuck into an agreement that discusses the transfer of intellectual property rights. _We want to avoid any situation where Fleet transfers its intellectual property to another party as part of an NDA_. +- Should you find any clauses in steps 2 or 3 that are beyond the scope of protecting both party's confidential information in a customer NDA or an altered version of Fleet's NDA, reject this language and communicate that Fleet cannot agree to those terms. +- Any concerns or uncertainty over _any_ provisions in an NDA should be brought to Nathanael Holliday in Digital Experience, who will consult legal counsel if necessary to resolve any concerns. -### Schedule CEO interview +### Review a vendor agreement -Use the following steps to schedule an interview between a candidate and the CEO: -1. Once you receive a [CEO interview request](https://fleetdm.com/handbook/company/leadership#hiring-a-new-team-member), apply the "eyes" (👀) emoji to the Slack post to acknowledge you've seen the request. -2. Reach out to the candidate via email to find a time when the CEO and candidate are both available. - > This entire process takes an hour for the CEO: a 30-minute interview followed by a 30-minute "¶¶ Postgame" Be sure to offer times that accommodate this. -3. [Make a copy of the "¶¶ CEO interview template"](https://docs.google.com/document/d/1yARlH6iZY-cP9cQbmL3z6TbMy-Ii7lO64RbuolpWQzI/copy) (private Google doc) and move it to the "[¶¶ Interview feedback](https://drive.google.com/drive/folders/1v5Z1WB9S855hLZMUWgOiXA_ei2EpEGlA?usp=drive_link)" folder in Google Drive. -4. Prep the CEO interview doc: - - Change file name and heading of doc to `¶¶ CANDIDATE_NAME (CANDIDATE_TITLE) <> Mike McNeil, CEO final interview (YYYY-MM-DD)`. - - Add candidate's personal email in the "👥" (attendees) section at the top of the doc. - - Add candidate's [LinkedIn url](https://www.linkedin.com/search/results/all/?keywords=people) on the first bullet for Mike. - - Share the CEO interview doc with the hiring manager as a "Commenter". -5. Link the CEO interview doc at the top of the "feedback" doc shared in the CEO interview request -6. Create a Google Calendar event at a time when the CEO and the candidate are both available. - - Create a Google Calendar event matching the title of the interview doc. - - Add the interview doc to the calendar event description as the agenda (i.e. `Agenda: INTERVIEW_DOC_FULL_URL`) and save the calendar event. -7. Schedule a 30-minute "¶¶ Postgame" working session for the CEO to evaluate the candidate and give his recommendation. -8. In the hiring channel for the position, apply the "green-check-mark" (✅) emoji to the CEO interview request to confirm the request has been processed. +When reviewing contracts from a vendor, Fleet is concerned about the following: +- If there are confidentiality provisions in the agreement in place of a stand-alone NDA, verify the confidentiality provisions are appropriate and protect Fleet when sensitive data is involved that isn't otherwise available to the public. +- We want to make sure there are no _do not solicit_ or _do not compete_ clauses in the contract. To aid in this search, we double check by using the cmd + f function and searching for "solici", "compet" and "hir" and then looking through the results to be sure that nothing prohibits Fleet from independently developing competing products or from hiring personnel with ties to the vendor. +- We want to make sure that contracts can be terminated relatively easily and be aware of what the process is for terminating them, avoiding commitments over 12 months in length. +- We want to make sure the payment terms work for us (i.e. being able to pay via wire transfer, credit card or bill.com) and that the price in any contract or order form is what we have agreed to. While almost never malicious, mistakes often occur in the steps between agreeing on a price, negotiating a contract, and receiving an invoice. We want to be sure at every step that the dollar amount and service provided is consistent with what has been negotiated and agreed upon. +- Remember, once we have signed the agreement - we're stuck with it. If any clause in the agreement appears strange or gives you pause or concern, it is better to seek clarification than to commit to something that might be detrimental to Fleet. Contracts are fairly standardized, and you'll quickly learn what is normal and what feels out of place. Unusual clauses or wording that seems out of the ordinary should get a second set of eyes just to be sure, do not hesitate to reach out to Nathanael Holliday with questions, who will reach out to legal counsel as necessary. -### Program the CEO to do something +### Review an order form -1. If necessary or if unsure, immediately direct message the CEO on Slack to clarify priority level, timing, and level of effort. (For example, whether to schedule 30m or 60m to complete in full, or 30m planning as an iterative step.) -2. If there is not room on the calendar to schedule this soon enough with both Mike and Sam as needed (erring on the side of sooner), then either immediately direct message the CEO with a backup plan, or if it can obviously wait, then discuss at the next roundup. -3. Create a calendar event with a Zoom meeting for the CEO and Apprentice. Keep the title short. For the description, keep it very brief and use this template: +- We should always check order forms for additional terms that go beyond the scope of the order form (caps on price increases, for example). +- Be sure the order form includes contact information + billing address and information so that Fleet knows how and who to invoice for payment. +- Verify that the payment terms are correct and matches what's in the agreement. This is a frequent common mistake as companies usually have default payment terms and overlook changing them to match atypical payment terms. +- Make sure the effective term of the order matches what was agreed upon (usually a one year term) and that the order form includes the correct number of hosts and whether or not it should contain professional services (usually, it does not). +- Check that the amount on the order form reflects what Fleet agreed to, as this is the amount that the customer will expect to be invoiced for. +- Lastly, double check one more time to make sure there are no sneaky, unusual terms snuck in at the bottom of an order form or stashed away in fine print. Common things that are included in order forms and not always communicated to Fleet are caps on price increases upon renewal, new SLAs, or a product roadmap or milestones we may not have agreed upon. Any clauses on an order form that appear beyond the scope of simply elaborating on the services being provided, the purchase cost, the contract that the purchase is being made under, how Fleet will bill and how the customer will pay deserves a careful look. Reach out to Nathanael Holliday in Digital Experience with concerns. + + +### Review a non-standard subscription agreement + +We want to use our standard terms whenever possible with our customers, but it is common that customers want to use their own agreement or redline (modify) Fleet's terms. +When reviewing subscription agreements on customer paper or when a customer has made changes to Fleet's terms, we review it using [these guidelines](https://docs.google.com/document/d/1aGgN5It1i3fdsBF37vWSbvukO_gQhy5vCp4fINg191Q/edit?usp=sharing). -``` -Agenda: -1. Apprentice: Is there enough context for you (CEO) to accomplish this? -2. Apprentice: Is this still a priority for you (CEO) to do.. right now? Or should it be "someday/maybe"? -3. Apprentice: Is there enough time for you (CEO) to do this live? (Right now during this meeting?) -4. Apprentice: What are the next steps after you (CEO) complete this? -5. Apprentice: LINK_TO_DOC_OR_ISSUE -``` ### Obtain a signature from the CEO @@ -556,92 +540,6 @@ When an agreement is routed to the CEO for signature, the [Apprentice](https://f 3. Comment in the issue once the CEO has signed the agreement and assign the issue to [Nathan Holiday](https://fleetdm.com/handbook/digital-experience#team). -### Prepare for CEO office minutes - -Before the start of the meeting, the Apprentice will prepare the "CEO office minutes" meeting [agenda](https://docs.google.com/document/d/12cd0N8KvHkfJxYlo7ggdisrvqw4MCErDoIzLjmBIdj4/edit) such that the following is true: -1. All agenda items are prefixed with a date of when the item will be covered and name of the person requesting to discuss the issue. -2. All team members with an agenda item have added themselves **and their manager** to the correct calendar event. If the team member or manager hasn't been added to the calendar event before the meeting begins, the agenda item is de-prioritized in favor of others with representatives in attendance. -3. If there are more that two team members attending, the Apprentice will work with the team members to schedule additional time to cover the agenda. - -> If the manager is unable to attend the scheduled time of the meeting, the Apprentice will work with the team member to schedule an adhoc meeting between them, their manager, and the CEO. - - -### Process the CEO's calendar - -Time management for the CEO is essential. The Apprentice processes the CEO's calendar multiple times per day. - -- **Clear any unexpected new events or double-bookings.** Look for any new double-bookings, invites that haven't been accepted, or other events you don't recognize. - 1. Double-book temporarily with a "UNCONFIRMED" calendar block so that the CEO ignores it and doesn't spend time trying to figure out what it is. - 2. Go to the organizer (or nearest fleetie who's not the CEO): - - Get full context on what the CEO should know as to the purpose of the meeting and why the organizer thinks it is helpful or necessary for the CEO to attend. - - Remind the organizer with [this link to the handbook that all CEO events have times chosen by Sam before booking](https://fleetdm.com/handbook/company/communications#schedule-time-with-the-ceo). - 3. Bring prepped discussion item about this proposed event to the next CEO roundup, including the purpose of the event and why it is helpful or necessary for the CEO to attend (according to the person requesting the CEO's attendance). The CEO will decide whether to attend. - 4. Delete the "UNCONFIRMED" block if the meeting is confirmed, or otherwise work with the organizer to pick a new time or let them know the decision. - -- **Prepare the agenda for any newly-added meetings**: [Meeting agenda prep](https://docs.google.com/document/d/1gH3IRRgptrqSYzBFy-77g98JROTL8wqrazJIMkp-Gb4/edit#heading=h.i7mkhr6m123r) is especially important to help the CEO focus and transition quickly in and between meetings. Using the CEO's browser, prepare each document by including the following: - -> If a meeting agenda has to be created from scratch, be sure to move it to the "Meeting notes" folder in Google Drive so that he isn't locked out of any documents. -> If preparing for a meeting with a current advisor, use the existing journal as the meeting agenda using these steps: -> 1. Search for the journal in Mike's browser using the advisor's name or email. -> 2. Update the journal by adding the date of the meeting as an H3 in the Google document (pattern matching the document) and link the document to the calendar description. - - 1. LinkedIn profile url of all outside participants. Connect with any of the attendees that the CEO is not already connected to on LinkedIn, this should always be a blank connect request meaning "Send without note". Nest everything from prep under the LinkedIn url (ie all under #1) - 2. A screen-shot of LinkedIn profile pic - 3. Company name (in doc title, file name and Google calendar event title) - 4. Correct date (20XX-XX-XX in doc title and file name) - 5. Context that helps the CEO to understand the purpose of the meeting at a glance from: - - CEO's email - - LinkedIn messages (careful not to mark things as read!) - - Google Drive - 6. Edit the calendar event description, changing “Notes” to “Agenda” when you're finished preparing the document to signify that this meeting has been prepped. - - -### Confirm CEO shadow dates - -After the team member notifies the Head of Digital Experience (via Slack), the Head of DigExp will bring the dates to the next roundup as a "DISCUSS: CEO shadow dates". Use the following steps to confirm CEO shadow dates: -1. Create an "All day", "Free" event on the CEO's calendar that matches the CEO shadow dates and name the calendar event "CEO shadow - [NAME] (Job title)". -3. Confirm the "shadowability" for external and nonrecurring internal meetings with the CEO during the next daily 🐈‍⬛🌪️ Roundup. -4. Go through the calendar and make sure all private meetings (e.g. 1:1's, E-Group, and quarterly board meetings) have "[no shadows]" in the event title. - - -### Process the CEO's inbox - -- The Apprentice is [responsible](https://fleetdm.com/handbook/company/why-this-way#why-direct-responsibility) for [processing all email traffic](https://docs.google.com/document/d/1gH3IRRgptrqSYzBFy-77g98JROTL8wqrazJIMkp-Gb4/edit#heading=h.i7mkhr6m123r) prior to CEO review to reduce the scope of Mike's inbox to only include necessary and actionable communication. - - Marking spam emails as read (same for emails Mike doesn't actually need to read). - - Escalate actionable sales communication and update Mike directly. - - Ensure all calendar invites have the necessary documents included. - - Forward any emails from customers about paying Fleet to the Buisness Operations department using [Fleet's billing email](https://docs.google.com/document/d/1tE-NpNfw1icmU2MjYuBRib0VWBPVAdmq4NiCrpuI0F0/edit#heading=h.wqalwz1je6rq). - - -### Document performance feedback - -Every Friday at 5PM a [Digital Experience team member](https://fleetdm.com/handbook/digital-experience#team) will look for missing data in the [KPIs spreadsheet](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit#gid=0). -1. If KPIs are not reported on time, notify the Head of Digital Experience and the DRI. -2. The Head of Digital Experience will update the "performance management" section of the appropriate individual's 1:1 doc so that the CEO can address during the next 1:1 meeting with the DRI. - - -### Send the weekly update - -We like to be open about milestones and announcements. Every Friday, e-group members [report their KPIs for the week](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit) by 5:00pm U.S. CT. Every Friday at 6PM, the Apprentice will post a short update in [#general](https://fleetdm.slack.com/archives/C019FNQPA23) including: - - A link to view KPIs - - Who was on-call that week - - Fleeties who are currently onboarding - - Planned hires who haven't started yet - - Fleeties that departed that week - - To send the weekly update follow these steps: - -1. Navigate to the current weeks row in the [KPIs Google Sheet](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit#gid=0). -2. Check the KPI sheet at 5pm US central time to ensure all departments have updated their KPIs on time. If any departments are delinquent, notify the department head and [document performance feedback](https://fleetdm.com/handbook/digital-experience#document-performance-feedback). -3. Copy the entire formula in this weeks "Weekly update" update cell and paste without formating (CMD+⇧+V) back into the same cell. The formula will now look like this: - -image - -3. In the "Weekly update" column (column E) for that row, double-click into that cell and change the "⚡️" to "🔭" in the beginning of the formula. -4. Paste the newly formatted message in the [#general Slack channel](https://fleetdm.slack.com/archives/C019FNQPA23) and delete the double quotes around the message and any links that unfurl from links in the weekly update message. -5. Use the drop-down next to the send button and select "Custom time" and schedule the message to send "Today" at 18:00 or 6pm CT. - - ### Troubleshoot signature automation We use Zapier to automate how completed DocuSign envelopes are formatted and stored. This process ensures we store signed documents in the correct folder and that filenames are formatted consistently. @@ -661,17 +559,26 @@ When the final signature is added to an envelope in DocuSign, it is marked as co link: drive.google.com/[destinationFolderID] ``` +### Send the weekly update -### Schedule travel for the CEO +We like to be open about milestones and announcements. Every Friday, e-group members [report their KPIs for the week](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit) by 5:00pm U.S. CT. Every Friday at 6PM, the Apprentice will post a short update in [#general](https://fleetdm.slack.com/archives/C019FNQPA23) including: + - A link to view KPIs + - Who was on-call that week + - Fleeties who are currently onboarding + - Planned hires who haven't started yet + - Fleeties that departed that week + + To send the weekly update follow these steps: + +1. Navigate to the current weeks row in the [KPIs Google Sheet](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit#gid=0). +2. Check the KPI sheet at 5pm US central time to ensure all departments have updated their KPIs on time. If any departments are delinquent, notify the department head and [document performance feedback](https://fleetdm.com/handbook/digital-experience#document-performance-feedback). +3. Copy the entire formula in this weeks "Weekly update" update cell and paste without formating (CMD+⇧+V) back into the same cell. The formula will now look like this: -The Apprentice schedules all travel arrangements for the CEO including flights, hotel, and reservations if needed. CEO traveling preferences in descending order of importance are: - - Direct flight whenever possible (as long as the cost of the direct flight is ≤2x the cost of a reasonable non-direct flight) - - Select a non-middle seat, whenever possible - - Don't upgrade seats (unless there's a cheap upgrade that gets a non-middle seat, or if a flight is longer than 5 hours. Even then, never buy a seat upgrade that costs >$100.) - - The CEO does not like to be called "Michael". Unfortunately, this is necessary when booking flights. (He has missed flights before by not doing this.) - - Default to carry-on only, no checked bags. (For trips longer than 5 nights, add 1 checked bag.) - - Use the Brex card. - - Frequent flyer details of all (previously flown) airlines are in 1Password as well as important travel documents. +image + +3. In the "Weekly update" column (column E) for that row, double-click into that cell and change the "⚡️" to "🔭" in the beginning of the formula. +4. Paste the newly formatted message in the [#general Slack channel](https://fleetdm.slack.com/archives/C019FNQPA23) and delete the double quotes around the message and any links that unfurl from links in the weekly update message. +5. Use the drop-down next to the send button and select "Custom time" and schedule the message to send "Today" at 18:00 or 6pm CT. ### Prepare for the All hands @@ -730,17 +637,13 @@ Follow these steps to process and backup the E-group agenda: 2. **In the backup copy**, leave Google Doc comments assigning all TODOs to the correct DRI. 3. If the "All hands" meeting has happened today remove any spotlights covered in the current "All hands" presentation. -### Check LinkedIn for new activity -Once a day the Apprentice will check LinkedIn for unread messages and pending connect request. +### Process the help-being-ceo Slack channel - 1. Log into the CEO's [LinkedIn](https://www.linkedin.com/search/results/all/?sid=s2%3A) and bring up the messaging window. - 2. Filter out all read messages by clicking "filter" and then "Unread". - 3. Bring all unreads to the CEO during the daily roundup. - -To check for pending connect requests, perform the following steps: - 1. Log into the CEO's LinkedIn (if you're not already) and click "My Network". - 2. Bring all pending connect requests to the CEO during the daily roundup. +The Apprentice will perform the following steps to process all communication from the CEO in the [help-being-ceo Slack channel](https://fleetdm.slack.com/archives/C03U703J0G5). +1. As soon as the message is received in the channel, add the "`:eyes:` (👀)" emoji to the Slack message to signify that you have seen and understood the task or question. +2. Start a Slack thread to add any context or let the stakeholders know the status of the task. +3. After each task is completed, apply the "`:white_check_mark:`" (✅) to the slack message. ### Unroll a Slack thread @@ -767,30 +670,172 @@ It's not enough to just "delete" a recording of a meeting in Gong. Instead, use - Search for the title of the meeting Google Drive and delete the auto-generated Google Doc containing the transcript. - Always check back to ensure the recording **and** transcript were both deleted. -### Update a company brand front -Fleet has several brand fronts that need to be updated from time to time. Check each [brand front](https://docs.google.com/spreadsheets/d/1c15vwMZytpCLHUdGvXxi0d6WGgPcQU1UBMniC1F9oKk/edit?gid=0#gid=0) for consistency and update as needed with the following: -- The current pitch, found in the blurbs section of the [🎐 Why Fleet?](https://docs.google.com/document/d/1E0VU4AcB6UTVRd4JKD45Saxh9Gz-mkO3LnGSTBDLEZo/edit#heading=h.uovxedjegxdc) doc. -- The current [brand imagery](https://www.figma.com/design/1J2yxqH8Q7u8V7YTtA1iej/Social-media-(logos%2C-covers%2C-banners)?node-id=3962-65895). Check this [Loom video](https://www.loom.com/share/4432646cc9614046aaa4a74da1c0adb5?sid=2f84779f-f0bd-4055-be69-282c5a16f5c5) for more info. +### Communicate Fleet's potential energy to stakeholders + +On the first business day of every month, the Head of Digital Experience will send an update to the stakeholders of Fleet using the following steps: +1. Copy the following template into an outgoing email with the subject line: "[Investor update] Fleet, YYYY-MM". + +``` +Hi investors and friends, -### Respond to a "Contact us" submission - -1. Check the [_from-prospective-customers](https://fleetdm.slack.com/archives/C01HE9GQW6B) Slack channel for "Contact us" submissions. -2. Mark submission as seen with the "👀" emoji. -3. Within 4 business hours, use the [_from-prospective-customers workflow (private Google doc)](https://docs.google.com/document/d/1D02k0tc5v-sEJ4uahAouuqnvZ6phxA_gP-IqmkBdMTE/edit) to respond to general asks. Follow the "High-level workflow" to understand how to respond and who to loop into the conversation. -4. Answer any technical questions to the best of your ability. If you are unable to answer a technical/product question, ask a Solutions Consultant in `#help-solutions-consulting`. If an SC is unavailable, post in `#g-mdm`or `#g-endpoint-ops`and notify @on-call. -5. Mark the Slack message as complete with the "✅" emoji. - -> For any support-related questions, forward the submission to [Fleet's support team](https://docs.google.com/document/d/1tE-NpNfw1icmU2MjYuBRib0VWBPVAdmq4NiCrpuI0F0/edit#heading=h.wqalwz1je6rq). +FYI we just updated the self-service investor update portal with the numbers from last month: https://docs.google.com/spreadsheets/d/10T7Q9iuHA4vpfV7qZCm6oMd5U1bLftBSobYD0RR8RkM/edit#gid=0 -### Cancel a Fleet Premium subscription +Thanks for your support, +Mike and the Fleet team + +``` + +2. Address the email to the executive team's Gmail. +3. Using the [🌧️🦉 Investors + advisors](https://docs.google.com/spreadsheets/d/15knBE2-PrQ1Ad-QcIk0mxCN-xFsATKK9hcifqrm0qFQ/edit#gid=1068113636) spreadsheet, bcc the correct individuals and send the email. + + +### Schedule press release + +Fleet will occasionally release information to the press regarding upcoming initiatives before updating the functionality of the core product. Use the following steps to schedule a press release: + +1. Add context for the next press release to the [e-group agenda](https://docs.google.com/document/d/13fjq3T0bZGOUah9cqHVxngckv0EB2R24A3gfl5cH7eo/edit) as a "DISCUSS:" to be reviewed by Fleet's executive team for alignment and finalization of date. +2. Once a release date is set, at-mention our public relations firm in the [#help-public-relations-firm--mindshare-pr--brand-marketing](https://fleetdm.slack.com/archives/C04PC9H34LF) and schedule a 30m call for our CEO and to communicate the press release. + +> The above must be completed 6 weeks before the press release date. + +3. Schedule a 1.5h discussion between the [Head of Digital Experience](https://fleetdm.com/handbook/digital-experience#team) and the CEO to review the first draft linked as "Agenda: LINK" to the calendar event description. +4. Schedule a 60m call with the CEO and public relations firm to review the first draft linked as above to the calendar event (first draft provided by the PR firm) +5. Schedule 2.5 hrs of async time for the CEO work on edits and a 60m followup postgame (solo) where CEO edits and then settles+sends final release. + + +### Archive a document + +Follow these steps to archive any document: +1. Create a copy of the document prefixed with the date using the format "`YYYY-MM-DD` Backup of `DOCUMENT_NAME`" (e.g. "2024-03-22 Backup of 🪂🗞️ Customer voice"). +2. Be sure to "Share it with the same people", "Copy comments and suggestions", and "Include resolved comments and suggestions" as shown below. + +Screenshot 2024-03-23 at 12 14 00 PM + +3. Save this backup copy to the same location in Google Drive where the original is found. +4. Link to the backup copy at the top of the original document. Be sure to use the full URL, no abbreviated pill links (e.g. "Notes from last time: URL_OF_MOST_RECENT_BACKUP_DOCUMENT"). +5. Delete all non-structural content from the original document, including past meeting notes and current answers to "evergreen" questions. + + + + + +### Process the CEO's inbox + +- The Apprentice is [responsible](https://fleetdm.com/handbook/company/why-this-way#why-direct-responsibility) for [processing all email traffic](https://docs.google.com/document/d/1gH3IRRgptrqSYzBFy-77g98JROTL8wqrazJIMkp-Gb4/edit#heading=h.i7mkhr6m123r) prior to CEO review to reduce the scope of Mike's inbox to only include necessary and actionable communication. + - Marking spam emails as read (same for emails Mike doesn't actually need to read). + - Escalate actionable sales communication and update Mike directly. + - Ensure all calendar invites have the necessary documents included. + - Forward any emails from customers about paying Fleet to the Buisness Operations department using [Fleet's billing email](https://docs.google.com/document/d/1tE-NpNfw1icmU2MjYuBRib0VWBPVAdmq4NiCrpuI0F0/edit#heading=h.wqalwz1je6rq). + + +### Process the CEO's calendar + +Time management for the CEO is essential. The Apprentice processes the CEO's calendar multiple times per day. + +- **Clear any unexpected new events or double-bookings.** Look for any new double-bookings, invites that haven't been accepted, or other events you don't recognize. + 1. Double-book temporarily with a "UNCONFIRMED" calendar block so that the CEO ignores it and doesn't spend time trying to figure out what it is. + 2. Go to the organizer (or nearest fleetie who's not the CEO): + - Get full context on what the CEO should know as to the purpose of the meeting and why the organizer thinks it is helpful or necessary for the CEO to attend. + - Remind the organizer with [this link to the handbook that all CEO events have times chosen by Sam before booking](https://fleetdm.com/handbook/company/communications#schedule-time-with-the-ceo). + 3. Bring prepped discussion item about this proposed event to the next CEO roundup, including the purpose of the event and why it is helpful or necessary for the CEO to attend (according to the person requesting the CEO's attendance). The CEO will decide whether to attend. + 4. Delete the "UNCONFIRMED" block if the meeting is confirmed, or otherwise work with the organizer to pick a new time or let them know the decision. + +- **Prepare the agenda for any newly-added meetings**: [Meeting agenda prep](https://docs.google.com/document/d/1gH3IRRgptrqSYzBFy-77g98JROTL8wqrazJIMkp-Gb4/edit#heading=h.i7mkhr6m123r) is especially important to help the CEO focus and transition quickly in and between meetings. Using the CEO's browser, prepare each document by including the following: + +> If a meeting agenda has to be created from scratch, be sure to move it to the "Meeting notes" folder in Google Drive so that he isn't locked out of any documents. +> If preparing for a meeting with a current advisor, use the existing journal as the meeting agenda using these steps: +> 1. Search for the journal in Mike's browser using the advisor's name or email. +> 2. Update the journal by adding the date of the meeting as an H3 in the Google document (pattern matching the document) and link the document to the calendar description. + + 1. LinkedIn profile url of all outside participants. Connect with any of the attendees that the CEO is not already connected to on LinkedIn, this should always be a blank connect request meaning "Send without note". Nest everything from prep under the LinkedIn url (ie all under #1) + 2. A screen-shot of LinkedIn profile pic + 3. Company name (in doc title, file name and Google calendar event title) + 4. Correct date (20XX-XX-XX in doc title and file name) + 5. Context that helps the CEO to understand the purpose of the meeting at a glance from: + - CEO's email + - LinkedIn messages (careful not to mark things as read!) + - Google Drive + 6. Edit the calendar event description, changing “Notes” to “Agenda” when you're finished preparing the document to signify that this meeting has been prepped. + + +### Check LinkedIn for new activity + +Once a day the Apprentice will check LinkedIn for unread messages and pending connect request. + + 1. Log into the CEO's [LinkedIn](https://www.linkedin.com/search/results/all/?sid=s2%3A) and bring up the messaging window. + 2. Filter out all read messages by clicking "filter" and then "Unread". + 3. Bring all unreads to the CEO during the daily roundup. + +To check for pending connect requests, perform the following steps: + 1. Log into the CEO's LinkedIn (if you're not already) and click "My Network". + 2. Bring all pending connect requests to the CEO during the daily roundup. + + +### Schedule travel for the CEO + +The Apprentice schedules all travel arrangements for the CEO including flights, hotel, and reservations if needed. CEO traveling preferences in descending order of importance are: + - Direct flight whenever possible (as long as the cost of the direct flight is ≤2x the cost of a reasonable non-direct flight) + - Select a non-middle seat, whenever possible + - Don't upgrade seats (unless there's a cheap upgrade that gets a non-middle seat, or if a flight is longer than 5 hours. Even then, never buy a seat upgrade that costs >$100.) + - The CEO does not like to be called "Michael". Unfortunately, this is necessary when booking flights. (He has missed flights before by not doing this.) + - Default to carry-on only, no checked bags. (For trips longer than 5 nights, add 1 checked bag.) + - Use the Brex card. + - Frequent flyer details of all (previously flown) airlines are in 1Password as well as important travel documents. + + +### Schedule CEO interview + +Use the following steps to schedule an interview between a candidate and the CEO: +1. Once you receive a [CEO interview request](https://fleetdm.com/handbook/company/leadership#hiring-a-new-team-member), apply the "eyes" (👀) emoji to the Slack post to acknowledge you've seen the request. +2. Reach out to the candidate via email to find a time when the CEO and candidate are both available. + > This entire process takes an hour for the CEO: a 30-minute interview followed by a 30-minute "¶¶ Postgame" Be sure to offer times that accommodate this. +3. [Make a copy of the "¶¶ CEO interview template"](https://docs.google.com/document/d/1yARlH6iZY-cP9cQbmL3z6TbMy-Ii7lO64RbuolpWQzI/copy) (private Google doc) and move it to the "[¶¶ Interview feedback](https://drive.google.com/drive/folders/1v5Z1WB9S855hLZMUWgOiXA_ei2EpEGlA?usp=drive_link)" folder in Google Drive. +4. Prep the CEO interview doc: + - Change file name and heading of doc to `¶¶ CANDIDATE_NAME (CANDIDATE_TITLE) <> Mike McNeil, CEO final interview (YYYY-MM-DD)`. + - Add candidate's personal email in the "👥" (attendees) section at the top of the doc. + - Add candidate's [LinkedIn url](https://www.linkedin.com/search/results/all/?keywords=people) on the first bullet for Mike. + - Share the CEO interview doc with the hiring manager as a "Commenter". +5. Link the CEO interview doc at the top of the "feedback" doc shared in the CEO interview request +6. Create a Google Calendar event at a time when the CEO and the candidate are both available. + - Create a Google Calendar event matching the title of the interview doc. + - Add the interview doc to the calendar event description as the agenda (i.e. `Agenda: INTERVIEW_DOC_FULL_URL`) and save the calendar event. +7. Schedule a 30-minute "¶¶ Postgame" working session for the CEO to evaluate the candidate and give his recommendation. +8. In the hiring channel for the position, apply the "green-check-mark" (✅) emoji to the CEO interview request to confirm the request has been processed. + + +### Program the CEO to do something + +1. If necessary or if unsure, immediately direct message the CEO on Slack to clarify priority level, timing, and level of effort. (For example, whether to schedule 30m or 60m to complete in full, or 30m planning as an iterative step.) +2. If there is not room on the calendar to schedule this soon enough with both Mike and Sam as needed (erring on the side of sooner), then either immediately direct message the CEO with a backup plan, or if it can obviously wait, then discuss at the next roundup. +3. Create a calendar event with a Zoom meeting for the CEO and Apprentice. Keep the title short. For the description, keep it very brief and use this template: + +``` +Agenda: +1. Apprentice: Is there enough context for you (CEO) to accomplish this? +2. Apprentice: Is this still a priority for you (CEO) to do.. right now? Or should it be "someday/maybe"? +3. Apprentice: Is there enough time for you (CEO) to do this live? (Right now during this meeting?) +4. Apprentice: What are the next steps after you (CEO) complete this? +5. Apprentice: LINK_TO_DOC_OR_ISSUE +``` + + +### Confirm CEO shadow dates + +After the team member notifies the Head of Digital Experience (via Slack), the Head of DigExp will bring the dates to the next roundup as a "DISCUSS: CEO shadow dates". Use the following steps to confirm CEO shadow dates: +1. Create an "All day", "Free" event on the CEO's calendar that matches the CEO shadow dates and name the calendar event "CEO shadow - [NAME] (Job title)". +3. Confirm the "shadowability" for external and nonrecurring internal meetings with the CEO during the next daily 🐈‍⬛🌪️ Roundup. +4. Go through the calendar and make sure all private meetings (e.g. 1:1's, E-Group, and quarterly board meetings) have "[no shadows]" in the event title. -Use the following steps to cancel a Fleet Premium subscription: -1. Log into [Stripe](https://dashboard.stripe.com/dashboard) (login in 1Password) and paste the customer's email they used to sign up in the search bar at the top of the page. -2. Select the subscription related to the email and use the "Actions" drop-down to "Cancel immediately". -3. Reach out to the community member (using the [correct email template](https://docs.google.com/document/d/1D02k0tc5v-sEJ4uahAouuqnvZ6phxA_gP-IqmkBdMTE/edit#heading=h.vw9mkh5e9msx)) and let them know their subscription was canceled. ## Rituals @@ -806,8 +851,6 @@ Use the following steps to cancel a Fleet Premium subscription: #### Stubs The following stubs are included only to make links backward compatible. -##### Why not mention the CEO in Slack threads? -Please see [handbook/company/why-this-way/why-not-mention-the-ceo-in-slack-threads](https://www.fleetdm.com/handbook/company/why-this-way#why-not-mention-the-ceo-in-slack-threads) diff --git a/handbook/engineering/README.md b/handbook/engineering/README.md index 9df23ec0b5..312770cb5f 100644 --- a/handbook/engineering/README.md +++ b/handbook/engineering/README.md @@ -8,7 +8,6 @@ This handbook page details processes specific to working [with](#contact-us) and | Role                            | Contributor(s) | |:--------------------------------|:-----------------------------------------------------------------------------------------------------------| | Chief Technology Officer (CTO) | [Luke Heath](https://www.linkedin.com/in/lukeheath/) _([@lukeheath](https://github.com/lukeheath))_ -| Client Platform Engineer & Community Advocate | [JD Strong](https://www.linkedin.com/in/jackdaniyelstrong/) _([@spokanemac](https://github.com/spokanemac/spokanemac))_ | Engineering Manager (EM) | _See [🛩️ Product groups](https://fleetdm.com/handbook/company/product-groups#current-product-groups)_ | Quality Assurance Engineer (QA) | _See [🛩️ Product groups](https://fleetdm.com/handbook/company/product-groups#current-product-groups)_ | Software Engineer | _See [🛩️ Product groups](https://fleetdm.com/handbook/company/product-groups#current-product-groups)_ @@ -45,7 +44,7 @@ We write [guides](https://fleetdm.com/guides) for all new features. Feature guid 1. Review and follow the [Fleet writing style guide](https://fleetdm.com/handbook/company/communications#writing). 2. Make a copy of the ["Article: Guide Template"](https://docs.google.com/document/d/1OPcDouyfyFPg3ScrN4bo6ol8vMfMa3P9-BIfbnEdcg4/copy) and rename "Article: [Guide] {feature name}". 3. Open the template and insert your [drafted content](https://fleetdm.com/handbook/company/product-groups#defining-done) into the provided template format. -4. Create a [new GitHub issue](https://github.com/fleetdm/fleet/issues/new?assignees=spokanemac&labels=:help-it&title=New%20guide:) with the guide title, and add the `:help-it` label. It will be processed and added to the website by our [Community Advocate](https://fleetdm.com/handbook/engineering#team). +4. Create a [new GitHub issue](https://github.com/fleetdm/fleet/issues/new?assignees=rachaelshaw&labels=:help-it&title=New%20guide:) with the guide title, and add the `:help-it` label. It will be processed and added to the website by our [Community Advocate](https://fleetdm.com/handbook/engineering#team). ### Create an engineering-initiated story @@ -207,24 +206,7 @@ Immediately after publishing a new release, we close out the associated GitHub i The [Fleet releases Google calendar](https://calendar.google.com/calendar/embed?src=c_v7943deqn1uns488a65v2d94bs%40group.calendar.google.com&ctz=America%2FChicago) is kept up-to-date by the [release ritual DRI](https://fleetdm.com/handbook/engineering#rituals). Any change to targeted release dates is reflected on this calendar. -### Secure company-issued equipment for a team member -As soon as an offer is accepted, Fleet provides laptops and YubiKey security keys for core team members to use while working at Fleet. The IT engineer will work with the new team member to get their equipment requested and shipped to them on time. - -- [**Check the Fleet IT warehouse**](https://docs.google.com/spreadsheets/d/1hFlymLlRWIaWeVh14IRz03yE-ytBLfUaqVz0VVmmoGI/edit#gid=0) before purchasing any equipment including laptops, monitors, and Yubikeys to ensure we efficiently [utilize existing assets before spending money](https://fleetdm.com/handbook/company/why-this-way#why-spend-less). If Fleet IT warehouse inventory can meet the needs of the request, file a [warehouse request](https://github.com/fleetdm/confidential/issues/new?assignees=sampfluger88&labels=%23g-digital-experience&projects=&template=warehouse-request.md&title=%F0%9F%92%BB+Warehouse+request). - -- Apple computers shipping to the United States and Canada are ordered using the Apple [eCommerce Portal](https://ecommerce2.apple.com/asb2bstorefront/asb2b/en/USD/?accountselected=true), or by contacting the business team at an Apple Store or contacting the online sales team at [800-854-3680](tel:18008543680). The IT engineer can arrange for same-day pickup at a store local to the Fleetie if needed. - - **Note:** Most Fleeties use 16-inch MacBook Pros. Team members are free to choose any laptop or operating system that works for them, as long as the price [is within reason](https://www.fleetdm.com/handbook/communications#spending-company-money). - - - When ordering through the Apple eCommerce Portal, look for a banner with *Apple Store for FLEET DEVICE MANAGEMENT | Welcome [Your Name].* Hovering over *Welcome* should display *Your Profile.* If Fleet's account number is displayed, purchases will be automatically made available in Apple Business Manager (ABM). - -- Apple computers for Fleeties in other countries should be purchased through an authorized reseller to ensure the device is enrolled in ADE. In countries that Apple does not operate or that do not allow ADE, work with the authorized reseller to find the best solution, or consider shipping to a US based Fleetie and then shipping on to the teammate. - - > A 3-year AppleCare+ Protection Plan (APP) should be considered default for Apple computers >$1500. Base MacBook Airs, Mac minis, etc. do not need APP unless configured beyond the $1500 price point. APP provides 24/7 support, and global repair coverage in case of accidental screen damage or liquid spill, and battery service. - - - Order a pack of two [YubiKey 5C NFC security keys](https://www.yubico.com/product/yubikey-5-series/yubikey-5c-nfc/) for new team member, shipped to them directly. - -- Include delivery tracking information when closing the support request so the new employee can be notified. ### Register a domain for Fleet @@ -289,57 +271,6 @@ ChromeOS devices are automatically enrolled in dogfood after the IT admin sets u - You will then be logged into the default device profile, and can complete any needed actions (wipe, recover data). -### Book an event - -Fleet's Client Platform Engineer & Community Advocate is responsible for booking events that Fleet has chosen to attend and/or sponsor. To book an event, complete the steps in each event issue. Contact the [🫧 Digital Marketing Manager](https://fleetdm.com/handbook/demand#team) as needed with any questions or blockers to booking an event. - -> Note: The Demand department [settles all event strategy](https://fleetdm.com/handbook/demand#settle-event-strategy) prior to booking an event. - - - - -### Order SWAG - -**To order T-shirts:** - - - Check [Postal](https://app.postal.io/items/postals) first and see if the warehouse has enough shirts. - - Navigate to the [approved items page](https://app.postal.io/items/postals). - - Hover over the shirt design and click on the airplane. - - Click bulk send and choose one shirt size and the expected quantity of that particular shirt size. - - Make sure the address matches the expected receiving address. - - If the Postal warehouse can't fulfill the order or To order swag quickly: - - Login to [https://www.rushordertees.com/my-account/login/) (saved in 1Password). - - Choose Fleet logo design t-shirt under [my designs](https://www.rushordertees.com/my-account/designs/). - - Order shirts based on the pre-determined number (~5% of total event attendees). - - Submit the order. Ensure the address matches the expected receiving address. - -**To order stickers:** - - - Login to [StickerMule](https://www.stickermule.com/) (saved in 1Password). - - Find the [brand kit](https://www.stickermule.com/studio/brand-kits) after logging in. - - Click on the "Fleet Device Management" brand kit and order preapproved stickers from the templates. - - Total sticker quantity should be ~10% of total event attendees. - - Complete the checkout process. Ensure the address matches the expected receiving address. - -**To order pens and sticky note pads** - - - Pens and sticky note pads are ordered through Everything Branded. - - Email our sales representative Jake William (saved in 1Password) to order any of the following: - - [Javalina™ Metallic Stylus Pen](https://www.everythingbranded.com/product/javalina-metallic-stylus-pen-us-pat-8847930-9092077-350220) - - [Sharpie Fine Point Markers](https://www.everythingbranded.com/product/sharpie-fine-point-332908) - - [Custom sticky note pads](https://www.everythingbranded.com/product/custom-sticky-notes-585601) (design is in the StickerMule [brand kit](https://www.stickermule.com/studio/brand-kits)) - - ### Review another product group's pull request Some code paths require pull request review from multiple product groups to confirm there are no @@ -519,32 +450,6 @@ Conduct a postmortem meetings for every service or feature outage and every crit [Example Finished Document](https://docs.google.com/document/d/1YnETKhH9R7STAY-PaFnPy2qxhNht2EAFfkv-kyEwebQ/edit?usp=share_link) -### Process incoming equipment - -Upon receiving any device, follow these steps to process incoming equipment. -1. Search for the SN of the physical device in the ["Company equipment" spreadsheet](https://docs.google.com/spreadsheets/d/1hFlymLlRWIaWeVh14IRz03yE-ytBLfUaqVz0VVmmoGI/edit#gid=0) to confirm the correct equipment was received. -3. Visibly inspect equipment and all related components (e.g. laptop charger) for damage. -4. Remove any stickers and clean devices and components. -5. Using the device's charger plug in the device. -6. Turn on the device and enter recovery mode using the [appropriate method](https://support.apple.com/en-us/HT204904). -7. Connect the device to WIFI. -8. Using the "Recovery assistant" tab (In the top left corner), select "Delete this Mac". -9. Follow the prompts to activate the device and reinstall the appropriate version of macOS. -> If you are prevented from completing the steps above, create a ["💻 IT support issue](https://github.com/fleetdm/confidential/issues/new?assignees=%40spokanemac&labels=%3Ahelp-it&projects=&template=request-it-support.md&title=%F0%9F%92%BB+Request+IT+support) for IT, for the device to be scheduled for troubleshooting and remediation. Please note in the issue where you encountered blockers to completing the steps. - - -### Ship approved equipment - -Once the Digital Experience department approves inventory to be shipped from Fleet IT, follow these step to ship the equipment. -1. Compare the equipment request issue with the ["Company equipment" spreadsheet](https://docs.google.com/spreadsheets/d/1hFlymLlRWIaWeVh14IRz03yE-ytBLfUaqVz0VVmmoGI/edit#gid=0) and verify physical inventory. -2. Plug in the device and ensure inventory has been correctly processed and all components are present (e.g. charger cord, power converter). -3. Package equipment for shipment and include Yubikeys (if requested). -4. Change the "Company equipment" spreadsheet to reflect the new user. - - If you encounter any issues, repeat the [process incoming equipment steps](https://fleetdm.com/handbook/engineering#process-incoming-equipment). If problems persist, create a ["💻 IT support issue](https://github.com/fleetdm/confidential/issues/new?assignees=%40spokanemac&labels=%3Ahelp-it&projects=&template=request-it-support.md&title=%F0%9F%92%BB+Request+IT+support) for IT to troubleshoot the device. -6. Ship via FedEx to the address listed in the equipment request. -7. Add a comment to the equipment request issue, at-mentioning the requestor with the FedEx tracking info and close the issue. - - ### Provide same-day support for major version macOS releases Beginning with macOS 16, Fleet will offer same-day support for all major version macOS releases. @@ -565,193 +470,7 @@ Beginning with macOS 16, Fleet will offer same-day support for all major version #### Stubs The following stubs are included only to make links backward compatible. -##### Weekly bug review -[handbook/company/product-groups#weekly-bug-review](https://fleetdm.com/handbook/company/product-groups#weekly-bug-review) -Please see [docs/contributing/infrastructure](https://fleetdm.com/docs/contributing/infrastructure) for **below** -##### Infrastructure -##### Infrastructure links -##### Best practices for containers -Please see [docs/contributing/infrastructure](https://fleetdm.com/docs/contributing/infrastructure) for **above** - -##### Measurement -Please see [handbook/engineering#record-engineering-kpis](https://fleetdm.com/handbook/engineering#record-engineering-kpis) - -##### Critical bug notification process -Please see [handbook/engineering#notify-community-members-about-a-critical-bug](https://fleetdm.com/handbook/engineering#notify-community-members-about-a-critical-bug) - -##### Finding bugs -Please see [handbook/engineering#run-fleet-locally-for-qa-purposes](https://fleetdm.com/handbook/engineering#run-fleet-localy-for-qa-purposes) - -##### Scrum at Fleet -Please see [handbook/company/product-groups#scrum-at-fleet](https://fleetdm.com/handbook/company/product-groups#scrum-at-fleet) - -##### Scrum items -Please see [handbook/company/product-groups#scrum-items)](https://fleetdm.com/handbook/company/product-groups#scrum-items) - -##### Sprint ceremonies -Please see [handbook/company/product-groups#sprint-ceremonies](https://fleetdm.com/handbook/company/product-groups#sprint-ceremonies) - -##### Meetings -Please see [handbook/company/product-groups#meetings](https://fleetdm.com/handbook/company/product-groups#meetings) - -##### Principles -Please see [handbook/company/product-groups#principles](https://fleetdm.com/handbook/company/product-groups#principles) - -Please see [handbook/company/product-groups#eng-together](https://fleetdm.com/handbook/company/product-groups#eng-together) for **below** -##### Eng Together -##### Participants -##### Agenda -Please see [handbook/company/product-groups#eng-together](https://fleetdm.com/handbook/company/product-groups#eng-together) for **above** - -Please see [handbook/company/product-groups#group-weeklies](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **below** -##### User story discovery -##### Participants -##### Agenda -Please see [handbook/company/product-groups#group-weeklies](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **above** - -Please see [handbook/company/product-groups#group-weeklies](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **below** -##### Group weeklies -##### Participants -##### Sample agenda (Frontend weekly) -Please see [handbook/company/product-groups#group-weeklies](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **above** - -##### Engineering-initiated stories -##### Creating an engineering-initiated story - -Please see [handbook/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) for **above** - -Please see [handbook/engineering#accept-new-apple-developer-account-terms](https://fleetdm.com/handbook/engineering#accept-new-apple-developer-account-terms) for **below** -##### Accounts -##### Apple developer account -Please see [handbook/engineering#accept-new-apple-developer-account-terms](https://fleetdm.com/handbook/engineering#accept-new-apple-developer-account-terms) for **above** - -##### Merging during the freeze period -Please see [handbook/engineering#merge-a-pull-request-during-the-freeze-period](https://fleetdm.com/handbook/engineering#merge-a-pull-request-during-the-freeze-period) - -##### Merge a bug fix during the freeze period -Please see [merge-bug-fixes-into-the-release-candidate](https://fleetdm.com/handbook/engineering#merge-bug-fixes-into-the-release-candidate) - -##### Merge a pull request during the freeze period -Please see [merge-bug-fixes-into-the-release-candidate](https://fleetdm.com/handbook/engineering#merge-bug-fixes-into-the-release-candidate) - -##### Begin a merge freeze -Please see [handbook/engineering#create-a-release-candidate](https://fleetdm.com/handbook/engineering#create-a-release-candidate) - -##### Scrum boards -Please see [handbook//product-groups#current-product-groups](https://fleetdm.com/handbook/engineering#contact-us) - -Please see [handbook/engineering#begin-a-merge-freeze](https://fleetdm.com/handbook/engineering#begin-a-merge-freeze) for **below** -##### Release freeze period -##### Freeze day -Please see [handbook/engineering#begin-a-merge-freeze](https://fleetdm.com/handbook/engineering#begin-a-merge-freeze) for **above** - -##### Release day -Please see [handbook/engineering#prepare-fleet-release](https://fleetdm.com/handbook/engineering#prepare-fleet-release) - -##### Deploying to dogfood -Please see [handbook/engineering#deploy-a-new-release-to-dogfood](https://fleetdm.com/handbook/engineering#deploy-a-new-release-to-dogfood) - -Please see [handbook/engineering#conclude-current-milestone](https://fleetdm.com/handbook/engineering#conclude-current-milestone) for **below** -##### Milestone release ritual -##### Update milestone in GitHub -##### ZenHub housekeeping -Please see [handbook/engineering#conclude-current-milestone](https://fleetdm.com/handbook/engineering#conclude-current-milestone) for **above** - -##### Clearing the plate -Please see [handbook/engineering#schedule-developer-on-call-workload](https://fleetdm.com/handbook/engineering#schedule-developer-on-call-workload) - -##### Check dependencies -Please see [handbook/engineering#confirm-latest-versions-of-dependencies](https://fleetdm.com/handbook/engineering#confirm-latest-versions-of-dependencies) - -##### Release readiness -Please see [handbook/engineering#indicate-your-product-group-is-release-ready](https://fleetdm.com/handbook/engineering#indicate-your-product-group-is-release-ready) - -##### Improve documentation -Please see [handbook/company/product-groups#documentation-for-contributors](https://fleetdm.com/handbook/company/product-groups#documentation-for-contributors) - -##### How to reach the on-call engineer -Please see [handbook/company/product-groups#how-to-reach-the-developer-on-call](https://fleetdm.com/handbook/company/product-groups#how-to-reach-the-developer-on-call) - -##### The rotation -Please see [handbook/company/product-groups#the-developer-on-call-rotation](https://fleetdm.com/handbook/company/product-groups#the-developer-on-call-rotation) - -Please see [handbook/company/product-groups#the-developer-on-call-rotation](https://fleetdm.com/handbook/company/product-groups#developer-on-call-responsibilities) for **below** -##### Second-line response -##### PR reviews -##### Customer success meetings -Please see [handbook/company/product-groups#the-developer-on-call-rotation](https://fleetdm.com/handbook/company/product-groups#developer-on-call-responsibilities) for **above** - -##### Escalations -Please see [handbook/company/product-groups#escalations](https://fleetdm.com/handbook/company/product-groups#escalations) - -##### Handoff -Please see [handbook/company/product-groups#changing-of-the-guard](https://fleetdm.com/handbook/company/product-groups#changing-of-the-guard) - -Please see [handbook/company/product-groups#quality](https://fleetdm.com/handbook/company/product-groups#quality) for **below** -##### Quality -##### Human-oriented QA -##### Bug process -##### Debugging -##### Bug states -Please see [handbook/company/product-groups#quality](https://fleetdm.com/handbook/company/product-groups#quality) for **above** - -##### Inbox -Please see [handbook/company/product-groups#inbox](https://fleetdm.com/handbook/company/product-groups#inbox) - -Please see [handbook/company/product-groups#reproduced](https://fleetdm.com/handbook/company/product-groups#reproduced) for **below** -##### Reproduced -##### Fast track for Fleeties -Please see [handbook/company/product-groups#reproduced](https://fleetdm.com/handbook/company/product-groups#reproduced) for **above** - -##### In product drafting (as needed) -Please see [handbook/company/product-groups#in-product-drafting-as-needed](https://fleetdm.com/handbook/company/product-groups#in-product-drafting-as-needed) - -##### In engineering -Please see [handbook/company/product-groups#in-engineering](https://fleetdm.com/handbook/company/product-groups#in-engineering) - -##### Awaiting QA -Please see [handbook/company/product-groups#awaiting-qa](https://fleetdm.com/handbook/company/product-groups#awaiting-qa) - -Please see [handbook/company/product-groups#all-bugs](https://fleetdm.com/handbook/company/product-groups#all-bugs) for **below** -##### All bugs -##### Bugs closed this week -##### Bugs closed this week -Please see [handbook/company/product-groups#all-bugs](https://fleetdm.com/handbook/company/product-groups#all-bugs) for **above** - -Please see [handbook/company/product-groups#release-testing](https://fleetdm.com/handbook/company/product-groups#release-testing) for **below** -##### Release testing -##### Release blockers -##### Critical bugs -Please see [handbook/company/product-groups#release-testing](https://fleetdm.com/handbook/company/product-groups#release-testing) for **above** - -##### Reviewing PRs from the community -Please see [handbook/engineering#review-a-community-pull-request](https://fleetdm.com/handbook/engineering#review-a-community-pull-request) - -##### Merging community PRs -Please see [handbook/engineering#merge-a-community-pull-request](https://fleetdm.com/handbook/engineering#merge-a-community-pull-request) - -##### Changes to tables' schema -Please see [handbook/company/product-groups#changes-to-tables-schema](https://fleetdm.com/handbook/company/product-groups#changes-to-tables-schema) - -Please see [handbook/engineering#preform-an-incident-postmortem](https://fleetdm.com/handbook/engineering#preform-an-incident-postmortem) for **below** -##### Incident postmortems -##### Postmortem document -##### Postmortem meeting -##### Postmortem action items -Please see [handbook/engineering#preform-an-incident-postmortem](https://fleetdm.com/handbook/engineering#preform-an-incident-postmortem) for **below** - -##### Outages -[handbook/company/product-groups#outages](https://fleetdm.com/handbook/company/product-groups#outages) - -##### Scaling Fleet -[handbook/company/product-groups#scaling-fleet](https://fleetdm.com/handbook/company/product-groups#scaling-fleet) - -##### Load testing -[handbook/company/product-groups#load-testing](https://fleetdm.com/handbook/company/product-groups#load-testing) - -##### Version support -[handbook/company/product-groups#version-support](https://fleetdm.com/handbook/company/product-groups#version-support) diff --git a/handbook/engineering/engineering.rituals.yml b/handbook/engineering/engineering.rituals.yml index bdc8aa69ec..b82f36b5ca 100644 --- a/handbook/engineering/engineering.rituals.yml +++ b/handbook/engineering/engineering.rituals.yml @@ -84,20 +84,14 @@ description: "Every release cycle, by end of day Friday of release week, move all issues to the ”✅ Ready for release” column on the #g-mdm and #g-endpoint-ops sprint boards." moreInfoUrl: dri: "xpkoala" -#- -# task: "Review ongoing articles" -# startedOn: "2023-10-02" -# frequency: "Daily" -# description: "Check 📃 Planned articles and complete steps in each issue" -# moreInfoUrl: "https://fleetdm.com/handbook/demand#review-ongoing-articles" -# dri: "spokanemac" - - task: "Check ongoing events" - startedOn: "2024-02-09" + task: "Review ongoing articles" + startedOn: "2023-10-02" frequency: "Daily" - description: "Check event issues and complete steps." - moreInfoUrl: "https://fleetdm.com/handbook/engineering#book-an-event" - dri: "spokanemac" + description: "Check 📃 Planned articles and complete steps in each issue" + moreInfoUrl: "https://fleetdm.com/handbook/demand#review-ongoing-articles" + dri: "rachaelshaw" + From 6b4f705f2009939226a476c8ec1a77f3dea018ae Mon Sep 17 00:00:00 2001 From: Brock Walters <153771548+nonpunctual@users.noreply.github.com> Date: Tue, 22 Oct 2024 12:23:13 -0400 Subject: [PATCH 19/45] Update macos-mdm-setup.md (#23084) Added info box around "no team" verbiage for clarity. --- articles/macos-mdm-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/macos-mdm-setup.md b/articles/macos-mdm-setup.md index 0f32ccb090..0f2e7c5eb3 100644 --- a/articles/macos-mdm-setup.md +++ b/articles/macos-mdm-setup.md @@ -48,7 +48,7 @@ Hosts that automatically enroll will be assigned to a default team. You can conf 2. Click on the "Actions" dropdown for the ABM token you want to update, and then click "Edit teams". 3. Use the dropdowns in the modal to select the default team for each type of host, and click "Save" to save your selections. -If no default team is set for a host platform (macOS, iOS, or iPadOS), then newly enrolled hosts of that platform will be placed in "No team". +> If no default team is set for a host platform (macOS, iOS, or iPadOS), then newly enrolled hosts of that platform will be placed in "No team". > A host can be transferred to a new (not default) team before it enrolls. In the Fleet UI, you can do this under **Settings** > **Teams**. From d69100940e639f097b661fb58085674a4034f81c Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Tue, 22 Oct 2024 11:05:35 -0600 Subject: [PATCH 20/45] Add config for custom MDM URL (#22878) --- .../expectedGetConfigAppConfigJson.json | 1 + .../expectedGetConfigAppConfigYaml.yml | 1 + ...ectedGetConfigIncludeServerConfigJson.json | 1 + ...pectedGetConfigIncludeServerConfigYaml.yml | 1 + .../macosSetupExpectedAppConfigEmpty.yml | 1 + .../macosSetupExpectedAppConfigSet.yml | 1 + ee/server/service/mdm.go | 10 ++--- server/datastore/mysql/apple_mdm.go | 18 ++++---- server/datastore/mysql/schema.sql | 2 +- server/fleet/app.go | 13 ++++++ server/fleet/app_test.go | 44 +++++++++++++++++++ server/mdm/apple/apple_mdm.go | 2 +- server/mdm/apple/util.go | 2 +- server/service/appconfig.go | 9 ++-- server/service/apple_mdm.go | 21 ++++----- server/service/devices.go | 2 +- server/service/handler.go | 1 + server/worker/apple_mdm.go | 2 +- .../generated_files/appconfig.txt | 1 + 19 files changed, 101 insertions(+), 32 deletions(-) diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json index f1fca92952..ce6017b7da 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json @@ -95,6 +95,7 @@ }, "mdm": { "apple_bm_terms_expired": false, + "apple_server_url": "", "apple_bm_enabled_and_configured": false, "enabled_and_configured": false, "apple_business_manager": null, diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml index a79e0f0b36..3637ccc5d7 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml @@ -20,6 +20,7 @@ spec: zendesk: null mdm: apple_bm_terms_expired: false + apple_server_url: "" apple_bm_enabled_and_configured: false enabled_and_configured: false apple_business_manager: null diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json index ce6f6c4c22..598e99c53e 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json +++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json @@ -47,6 +47,7 @@ }, "mdm": { "apple_business_manager": null, + "apple_server_url": "", "volume_purchasing_program": null, "apple_bm_terms_expired": false, "apple_bm_enabled_and_configured": false, diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml index bc910c8ede..56d2d13eac 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml +++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml @@ -20,6 +20,7 @@ spec: zendesk: null mdm: apple_business_manager: null + apple_server_url: "" volume_purchasing_program: null apple_bm_enabled_and_configured: false apple_bm_terms_expired: false diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml index c2b0a4fed4..a158f27c1b 100644 --- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml +++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml @@ -20,6 +20,7 @@ spec: zendesk: null mdm: apple_business_manager: + apple_server_url: "" volume_purchasing_program: apple_bm_enabled_and_configured: false apple_bm_terms_expired: false diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml index b2c5fec52e..4d9ffe1f50 100644 --- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml +++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml @@ -20,6 +20,7 @@ spec: zendesk: null mdm: apple_business_manager: + apple_server_url: "" volume_purchasing_program: apple_bm_enabled_and_configured: false apple_bm_terms_expired: false diff --git a/ee/server/service/mdm.go b/ee/server/service/mdm.go index 8fa7980d3f..354ae1e322 100644 --- a/ee/server/service/mdm.go +++ b/ee/server/service/mdm.go @@ -33,6 +33,7 @@ import ( "github.com/google/uuid" ) +// Deprecated: use the new GetABMTokens API endpoint. func (svc *Service) GetAppleBM(ctx context.Context) (*fleet.AppleBM, error) { if err := svc.authz.Authorize(ctx, &fleet.AppleBM{}, fleet.ActionRead); err != nil { return nil, err @@ -663,10 +664,9 @@ func (svc *Service) InitiateMDMAppleSSO(ctx context.Context) (string, error) { return "", ctxerr.Wrap(ctx, err, "InitiateSSO getting metadata") } - serverURL := appConfig.ServerSettings.ServerURL authSettings := sso.Settings{ Metadata: metadata, - AssertionConsumerServiceURL: serverURL + svc.config.Server.URLPrefix + "/api/v1/fleet/mdm/sso/callback", + AssertionConsumerServiceURL: appConfig.MDMUrl() + svc.config.Server.URLPrefix + "/api/v1/fleet/mdm/sso/callback", SessionStore: svc.ssoSessionStore, OriginalURL: "/api/v1/fleet/mdm/sso/callback", } @@ -727,8 +727,8 @@ func (svc *Service) mdmSSOHandleCallbackAuth(ctx context.Context, auth fleet.Aut *metadata, auth, settings.EntityID, - appConfig.ServerSettings.ServerURL, - appConfig.ServerSettings.ServerURL+svc.config.Server.URLPrefix+"/api/v1/fleet/mdm/sso/callback", + appConfig.MDMUrl(), + appConfig.MDMUrl()+svc.config.Server.URLPrefix+"/api/v1/fleet/mdm/sso/callback", ) if err != nil { return "", "", "", ctxerr.Wrap(ctx, err, "validating sso response") @@ -1170,7 +1170,7 @@ func (svc *Service) GetMDMManualEnrollmentProfile(ctx context.Context) ([]byte, mobileConfig, err := apple_mdm.GenerateEnrollmentProfileMobileconfig( appConfig.OrgInfo.OrgName, - appConfig.ServerSettings.ServerURL, + appConfig.MDMUrl(), string(assets[fleet.MDMAssetSCEPChallenge].Value), topic, ) diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go index 8cf0d67658..e11a22ef5f 100644 --- a/server/datastore/mysql/apple_mdm.go +++ b/server/datastore/mysql/apple_mdm.go @@ -906,7 +906,7 @@ func updateMDMAppleHostDB( return ctxerr.Wrap(ctx, err, "error clearing mdm apple host_mdm_actions") } - if err := upsertMDMAppleHostMDMInfoDB(ctx, tx, appCfg.ServerSettings, false, hostID); err != nil { + if err := upsertMDMAppleHostMDMInfoDB(ctx, tx, appCfg, false, hostID); err != nil { return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert MDM info") } @@ -967,7 +967,7 @@ func insertMDMAppleHostDB( return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert label membership") } - if err := upsertMDMAppleHostMDMInfoDB(ctx, tx, appCfg.ServerSettings, false, mdmHost.ID); err != nil { + if err := upsertMDMAppleHostMDMInfoDB(ctx, tx, appCfg, false, mdmHost.ID); err != nil { return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert MDM info") } return nil @@ -1102,7 +1102,7 @@ func createHostFromMDMDB( if err := upsertMDMAppleHostMDMInfoDB( ctx, tx, - appCfg.ServerSettings, + appCfg, fromADE, unmanagedHostIDs..., ); err != nil { @@ -1258,12 +1258,12 @@ func upsertMDMAppleHostDisplayNamesDB(ctx context.Context, tx sqlx.ExtContext, h return nil } -func upsertMDMAppleHostMDMInfoDB(ctx context.Context, tx sqlx.ExtContext, serverSettings fleet.ServerSettings, fromSync bool, hostIDs ...uint) error { +func upsertMDMAppleHostMDMInfoDB(ctx context.Context, tx sqlx.ExtContext, appCfg *fleet.AppConfig, fromSync bool, hostIDs ...uint) error { if len(hostIDs) == 0 { return nil } - serverURL, err := apple_mdm.ResolveAppleMDMURL(serverSettings.ServerURL) + serverURL, err := apple_mdm.ResolveAppleMDMURL(appCfg.MDMUrl()) if err != nil { return ctxerr.Wrap(ctx, err, "resolve Fleet MDM URL") } @@ -1563,7 +1563,7 @@ INSERT INTO hosts ( if err := upsertMDMAppleHostLabelMembershipDB(ctx, tx, ds.logger, *host); err != nil { return ctxerr.Wrap(ctx, err, "restore pending dep host label membership") } - if err := upsertMDMAppleHostMDMInfoDB(ctx, tx, ac.ServerSettings, true, host.ID); err != nil { + if err := upsertMDMAppleHostMDMInfoDB(ctx, tx, ac, true, host.ID); err != nil { return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert MDM info") } @@ -5099,7 +5099,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?) return nil, ctxerr.Wrap(ctx, err, "get app config") } - url, err := apple_mdm.ResolveAppleMDMURL(cfg.ServerSettings.ServerURL) + url, err := apple_mdm.ResolveAppleMDMURL(cfg.MDMUrl()) if err != nil { return nil, ctxerr.Wrap(ctx, err, "getting ABM token MDM server url") } @@ -5150,7 +5150,7 @@ LEFT OUTER JOIN return nil, ctxerr.Wrap(ctx, err, "get app config") } - url, err := apple_mdm.ResolveAppleMDMURL(cfg.ServerSettings.ServerURL) + url, err := apple_mdm.ResolveAppleMDMURL(cfg.MDMUrl()) if err != nil { return nil, ctxerr.Wrap(ctx, err, "getting ABM token MDM server url") } @@ -5275,7 +5275,7 @@ LEFT OUTER JOIN return nil, ctxerr.Wrap(ctx, err, "get app config") } - url, err := apple_mdm.ResolveAppleMDMURL(cfg.ServerSettings.ServerURL) + url, err := apple_mdm.ResolveAppleMDMURL(cfg.MDMUrl()) if err != nil { return nil, ctxerr.Wrap(ctx, err, "getting ABM token MDM server url") } diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 1d8b39fcfe..da34cc3ddd 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -65,7 +65,7 @@ CREATE TABLE `app_config_json` ( UNIQUE KEY `id` (`id`) ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"volume_purchasing_program\": null, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"ndes_scep_proxy\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01'); +INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"volume_purchasing_program\": null, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"ndes_scep_proxy\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `calendar_events` ( diff --git a/server/fleet/app.go b/server/fleet/app.go index c56c3bebfa..f0810e352d 100644 --- a/server/fleet/app.go +++ b/server/fleet/app.go @@ -139,6 +139,12 @@ type MDMAppleVolumePurchasingProgramInfo struct { // MDM is part of AppConfig and defines the mdm settings. type MDM struct { + // AppleServerURL is an alternate URL to be used in MDM configuration profiles to differentiate MDM + // requests from fleetd requests on customer networks. AppleServerURL DNS should resolve to the + // same IP as the Fleet Server URL. + // If not set, the server will use Fleet server URL (recommended). + AppleServerURL string `json:"apple_server_url"` + // Deprecated: use AppleBussinessManager instead DeprecatedAppleBMDefaultTeam string `json:"apple_bm_default_team,omitempty"` @@ -206,6 +212,13 @@ type MDM struct { ///////////////////////////////////////////////////////////////// } +func (c *AppConfig) MDMUrl() string { + if c.MDM.AppleServerURL == "" { + return c.ServerSettings.ServerURL + } + return c.MDM.AppleServerURL +} + // AtLeastOnePlatformEnabledAndConfigured returns true if at least one supported platform // (macOS or Windows) has MDM enabled and configured. func (m MDM) AtLeastOnePlatformEnabledAndConfigured() bool { diff --git a/server/fleet/app_test.go b/server/fleet/app_test.go index bec5c458e5..7f70fe0c9a 100644 --- a/server/fleet/app_test.go +++ b/server/fleet/app_test.go @@ -361,3 +361,47 @@ func TestFeaturesCopy(t *testing.T) { require.Equal(t, f.DetailQueryOverrides, clone.DetailQueryOverrides) }) } + +func TestMDMUrl(t *testing.T) { + cases := []struct { + name string + mdmURL string + serverURL string + want string + }{ + { + name: "mdm url set", + mdmURL: "https://mdm.example.com", + serverURL: "https://fleet.example.com", + want: "https://mdm.example.com", + }, + { + name: "mdm url not set", + mdmURL: "", + serverURL: "https://mdm.example.com", + want: "https://mdm.example.com", + }, + { + name: "mdm url and server url not set", + mdmURL: "", + serverURL: "", + want: "", + }, + { + name: "server url not set", + mdmURL: "https://mdm.example.com", + serverURL: "", + want: "https://mdm.example.com", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + appConfig := AppConfig{ + MDM: MDM{AppleServerURL: tc.mdmURL}, + ServerSettings: ServerSettings{ServerURL: tc.serverURL}, + } + require.Equal(t, tc.want, appConfig.MDMUrl()) + }) + } +} diff --git a/server/mdm/apple/apple_mdm.go b/server/mdm/apple/apple_mdm.go index 2361c474cb..cc4af44936 100644 --- a/server/mdm/apple/apple_mdm.go +++ b/server/mdm/apple/apple_mdm.go @@ -171,7 +171,7 @@ func (d *DEPService) buildJSONProfile(ctx context.Context, setupAsstJSON json.Ra endUserAuthEnabled = team.Config.MDM.MacOSSetup.EnableEndUserAuthentication } if endUserAuthEnabled { - jsonProf.ConfigurationWebURL = appCfg.ServerSettings.ServerURL + "/mdm/sso" + jsonProf.ConfigurationWebURL = appCfg.MDMUrl() + "/mdm/sso" } } diff --git a/server/mdm/apple/util.go b/server/mdm/apple/util.go index a52cb8bb9e..88267f953f 100644 --- a/server/mdm/apple/util.go +++ b/server/mdm/apple/util.go @@ -99,7 +99,7 @@ func FmtDDMError(reasons []fleet.MDMAppleDDMStatusErrorReason) string { } func EnrollURL(token string, appConfig *fleet.AppConfig) (string, error) { - enrollURL, err := url.Parse(appConfig.ServerSettings.ServerURL) + enrollURL, err := url.Parse(appConfig.MDMUrl()) if err != nil { return "", err } diff --git a/server/service/appconfig.go b/server/service/appconfig.go index ba93c48f0d..77a4f1440b 100644 --- a/server/service/appconfig.go +++ b/server/service/appconfig.go @@ -30,8 +30,10 @@ import ( ) // Functions that can be overwritten in tests -var validateNDESSCEPAdminURL = eeservice.ValidateNDESSCEPAdminURL -var validateNDESSCEPURL = eeservice.ValidateNDESSCEPURL +var ( + validateNDESSCEPAdminURL = eeservice.ValidateNDESSCEPAdminURL + validateNDESSCEPURL = eeservice.ValidateNDESSCEPURL +) //////////////////////////////////////////////////////////////////////////////// // Get AppConfig @@ -812,7 +814,8 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle mdmSSOSettingsChanged := oldAppConfig.MDM.EndUserAuthentication.SSOProviderSettings != appConfig.MDM.EndUserAuthentication.SSOProviderSettings serverURLChanged := oldAppConfig.ServerSettings.ServerURL != appConfig.ServerSettings.ServerURL - if (mdmEnableEndUserAuthChanged || mdmSSOSettingsChanged || serverURLChanged) && license.IsPremium() { + appleMDMUrlChanged := oldAppConfig.MDMUrl() != appConfig.MDMUrl() + if (mdmEnableEndUserAuthChanged || mdmSSOSettingsChanged || serverURLChanged || appleMDMUrlChanged) && license.IsPremium() { if err := svc.EnterpriseOverrides.MDMAppleSyncDEPProfiles(ctx); err != nil { return nil, ctxerr.Wrap(ctx, err, "sync DEP profiles") } diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 9eb53c32b8..7115c06ce0 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -1052,6 +1052,7 @@ func (uploadAppleInstallerRequest) DecodeRequest(ctx context.Context, r *http.Re func (r uploadAppleInstallerResponse) error() error { return r.Err } +// Deprecated: Not in Use func uploadAppleInstallerEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) { req := request.(*uploadAppleInstallerRequest) ff, err := req.Installer.Open() @@ -1421,7 +1422,7 @@ func (svc *Service) GetMDMAppleEnrollmentProfileByToken(ctx context.Context, tok return nil, ctxerr.Wrap(ctx, err) } - enrollURL, err := apple_mdm.AddEnrollmentRefToFleetURL(appConfig.ServerSettings.ServerURL, ref) + enrollURL, err := apple_mdm.AddEnrollmentRefToFleetURL(appConfig.MDMUrl(), ref) if err != nil { return nil, ctxerr.Wrap(ctx, err, "adding reference to fleet URL") } @@ -3136,7 +3137,7 @@ func ensureFleetProfiles(ctx context.Context, ds fleet.Datastore, logger kitlog. var contents bytes.Buffer params := mobileconfig.FleetdProfileOptions{ EnrollSecret: es.Secret, - ServerURL: appCfg.ServerSettings.ServerURL, + ServerURL: appCfg.ServerSettings.ServerURL, // ServerURL must be set to the Fleet URL. Do not use appCfg.MDMUrl() here. PayloadType: mobileconfig.FleetdConfigPayloadIdentifier, PayloadName: mdm_types.FleetdConfigProfileName, } @@ -3521,7 +3522,6 @@ func preprocessProfileContents( profileContents map[string]mobileconfig.Mobileconfig, hostProfilesToInstallMap map[hostProfileUUID]*fleet.MDMAppleBulkUpsertHostProfilePayload, ) error { - // This method replaces Fleet variables ($FLEET_VAR_) in the profile contents, generating a unique profile for each host. // For a 2KB profile and 30K hosts, this method may generate ~60MB of profile data in memory. @@ -3702,7 +3702,7 @@ func preprocessProfileContents( hostContents = replaceFleetVariable(fleetVarNDESSCEPChallengeRegexp, hostContents, challenge) case FleetVarNDESSCEPProxyURL: // Insert the SCEP URL into the profile contents - proxyURL := fmt.Sprintf("%s%s%s", appConfig.ServerSettings.ServerURL, apple_mdm.SCEPProxyPath, + proxyURL := fmt.Sprintf("%s%s%s", appConfig.MDMUrl(), apple_mdm.SCEPProxyPath, url.PathEscape(fmt.Sprintf("%s,%s", hostUUID, profUUID))) hostContents = replaceFleetVariable(fleetVarNDESSCEPProxyURLRegexp, hostContents, proxyURL) case FleetVarHostEndUserEmailIDP: @@ -3887,7 +3887,7 @@ func RenewSCEPCertificates( if len(assocsWithoutRefs) > 0 { profile, err := apple_mdm.GenerateEnrollmentProfileMobileconfig( appConfig.OrgInfo.OrgName, - appConfig.ServerSettings.ServerURL, + appConfig.MDMUrl(), scepChallenge, mdmPushCertTopic, ) @@ -3902,7 +3902,7 @@ func RenewSCEPCertificates( // send individual commands for each host with a reference for _, assoc := range assocsWithRefs { - enrollURL, err := apple_mdm.AddEnrollmentRefToFleetURL(appConfig.ServerSettings.ServerURL, assoc.EnrollReference) + enrollURL, err := apple_mdm.AddEnrollmentRefToFleetURL(appConfig.MDMUrl(), assoc.EnrollReference) if err != nil { return ctxerr.Wrap(ctx, err, "adding reference to fleet URL") } @@ -4541,7 +4541,7 @@ func (svc *Service) GetOTAProfile(ctx context.Context, enrollSecret string) ([]b return nil, ctxerr.Wrap(ctx, err, "getting app config to get org name") } - profBytes, err := apple_mdm.GenerateOTAEnrollmentProfileMobileconfig(cfg.OrgInfo.OrgName, cfg.ServerSettings.ServerURL, enrollSecret) + profBytes, err := apple_mdm.GenerateOTAEnrollmentProfileMobileconfig(cfg.OrgInfo.OrgName, cfg.MDMUrl(), enrollSecret) if err != nil { return nil, ctxerr.Wrap(ctx, err, "generating ota mobileconfig file") } @@ -4673,12 +4673,13 @@ func (svc *Service) MDMAppleProcessOTAEnrollment( if err != nil { return nil, ctxerr.Wrap(ctx, err, "reading app config") } - fleetURL := appCfg.ServerSettings.ServerURL + + mdmURL := appCfg.MDMUrl() // if the root signer was issued by Apple's CA, it means we're in the // first phase and we should return a SCEP payload. if err := apple_mdm.VerifyFromAppleIphoneDeviceCA(rootSigner); err == nil { - scepURL, err := apple_mdm.ResolveAppleSCEPURL(fleetURL) + scepURL, err := apple_mdm.ResolveAppleSCEPURL(mdmURL) if err != nil { return nil, ctxerr.Wrap(ctx, err, "resolve Apple SCEP url") } @@ -4710,7 +4711,7 @@ func (svc *Service) MDMAppleProcessOTAEnrollment( enrollmentProf, err := apple_mdm.GenerateEnrollmentProfileMobileconfig( appCfg.OrgInfo.OrgName, - appCfg.ServerSettings.ServerURL, + mdmURL, string(assets[fleet.MDMAssetSCEPChallenge].Value), topic, ) diff --git a/server/service/devices.go b/server/service/devices.go index 18f404748b..494d2b329a 100644 --- a/server/service/devices.go +++ b/server/service/devices.go @@ -555,7 +555,7 @@ func (svc *Service) GetDeviceMDMAppleEnrollmentProfile(ctx context.Context) ([]b } enrollSecret := tmSecrets[0].Secret - profBytes, err := apple_mdm.GenerateOTAEnrollmentProfileMobileconfig(cfg.OrgInfo.OrgName, cfg.ServerSettings.ServerURL, enrollSecret) + profBytes, err := apple_mdm.GenerateOTAEnrollmentProfileMobileconfig(cfg.OrgInfo.OrgName, cfg.MDMUrl(), enrollSecret) if err != nil { return nil, ctxerr.Wrap(ctx, err, "generating ota mobileconfig file for manual enrollment") } diff --git a/server/service/handler.go b/server/service/handler.go index 3bbcff4023..19135d7bbc 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -578,6 +578,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC // Deprecated: GET /mdm/manual_enrollment_profile is now deprecated, replaced by the // GET /enrollment_profiles/manual endpoint. + // Ref: https://github.com/fleetdm/fleet/issues/16252 mdmAppleMW.GET("/api/_version_/fleet/mdm/manual_enrollment_profile", getManualEnrollmentProfileEndpoint, getManualEnrollmentProfileRequest{}) mdmAppleMW.GET("/api/_version_/fleet/enrollment_profiles/manual", getManualEnrollmentProfileEndpoint, getManualEnrollmentProfileRequest{}) diff --git a/server/worker/apple_mdm.go b/server/worker/apple_mdm.go index 933a0c7441..52bf530d89 100644 --- a/server/worker/apple_mdm.go +++ b/server/worker/apple_mdm.go @@ -311,7 +311,7 @@ func (a *AppleMDM) installBootstrapPackage(ctx context.Context, hostUUID string, return "", err } - url, err := meta.URL(appCfg.ServerSettings.ServerURL) + url, err := meta.URL(appCfg.MDMUrl()) if err != nil { return "", err } diff --git a/tools/cloner-check/generated_files/appconfig.txt b/tools/cloner-check/generated_files/appconfig.txt index 557865187b..bc3e7c0a22 100644 --- a/tools/cloner-check/generated_files/appconfig.txt +++ b/tools/cloner-check/generated_files/appconfig.txt @@ -104,6 +104,7 @@ github.com/fleetdm/fleet/v4/server/fleet/NDESSCEPProxyIntegration AdminURL strin github.com/fleetdm/fleet/v4/server/fleet/NDESSCEPProxyIntegration Username string github.com/fleetdm/fleet/v4/server/fleet/NDESSCEPProxyIntegration Password string github.com/fleetdm/fleet/v4/server/fleet/AppConfig MDM fleet.MDM +github.com/fleetdm/fleet/v4/server/fleet/MDM AppleServerURL string github.com/fleetdm/fleet/v4/server/fleet/MDM DeprecatedAppleBMDefaultTeam string github.com/fleetdm/fleet/v4/server/fleet/MDM AppleBusinessManager optjson.Slice[github.com/fleetdm/fleet/v4/server/fleet.MDMAppleABMAssignmentInfo] github.com/fleetdm/fleet/v4/pkg/optjson/Slice[github.com/fleetdm/fleet/v4/server/fleet.MDMAppleABMAssignmentInfo] Set bool From ad9c919219925bf3928d1cbb6da223c7e3cb8761 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:10:50 -0700 Subject: [PATCH 21/45] =?UTF-8?q?Frontend=20=E2=80=93=20Custom=20MDM=20URL?= =?UTF-8?q?=20UI,=20Update=20data=20validation=20pattern=20(#22727)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## #22700 ![ezgif-6-801855a36d](https://github.com/user-attachments/assets/72ddd339-acfa-4afe-a322-c1e6a2a38dd2) - [x] Changes file added for user-visible changes in `changes/`, - [ ] Manual QA for all new/changed functionality - TODO when backend is ready --------- Co-authored-by: Jacob Shandling --- changes/22700-add-custom-mdm-url-field | 1 + frontend/__mocks__/configMock.ts | 1 + frontend/docs/patterns.md | 56 ++++++++++- frontend/interfaces/config.ts | 2 + .../cards/Advanced/Advanced.tsx | 95 +++++++++++++++---- .../cards/FleetDesktop/FleetDesktop.tsx | 2 - 6 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 changes/22700-add-custom-mdm-url-field diff --git a/changes/22700-add-custom-mdm-url-field b/changes/22700-add-custom-mdm-url-field new file mode 100644 index 0000000000..90d7672555 --- /dev/null +++ b/changes/22700-add-custom-mdm-url-field @@ -0,0 +1 @@ +* Add a config field to the UI for custom MDM URLs diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts index 63bd830651..c589f05386 100644 --- a/frontend/__mocks__/configMock.ts +++ b/frontend/__mocks__/configMock.ts @@ -1,6 +1,7 @@ import { IConfig, IMdmConfig } from "interfaces/config"; const DEFAULT_CONFIG_MDM_MOCK: IMdmConfig = { + apple_server_url: "", enable_disk_encryption: false, windows_enabled_and_configured: true, apple_bm_default_team: "Apples", diff --git a/frontend/docs/patterns.md b/frontend/docs/patterns.md index 66d6e6b245..9916643017 100644 --- a/frontend/docs/patterns.md +++ b/frontend/docs/patterns.md @@ -11,7 +11,7 @@ should be discussed within the team and documented before merged. - [Typing](#typing) - [Utilities](#utilities) - [Components](#components) -- [Forms] (#forms) +- [Forms](#forms) - [React hooks](#react-hooks) - [React Context](#react-context) - [Fleet API calls](#fleet-api-calls) @@ -200,6 +200,7 @@ export default PackComposerPage; ### Data validation +#### How to validate Forms should make use of a pure `validate` function whose input(s) correspond to form data (may include new and possibly former form data) and whose output is an object of formFieldName:errorMessage key-value pairs (`Record`) e.g. @@ -211,8 +212,57 @@ const validate = (newFormData: IFormData) => { return errors; } ``` -The output of `validate` should be used by the calling `onChange` handler to set a `formErrors` -state. +The output of `validate` should be used by the calling handler to set a `formErrors` +state. + +#### When to validate +Form fields should *set only new errors* on blur and on save, and *set or remove* errors on change. This provides +an "optimistic" user experience. The user is only told they have an error once they navigate +away from a field or hit enter, actions which imply they are finished editing the field, while they are informed they have fixed +an error as soon as possible, that is, as soon as they make the fixing change. e.g. +``` +const onInputChange = ({ name, value }: IFormField) => { + const newFormData = { ...formData, [name]: value }; + setFormData(newFormData); + const newErrs = validateFormData(newFormData); + // only set errors that are updates of existing errors + // new errors are only set onBlur + const errsToSet: Record = {}; + Object.keys(formErrors).forEach((k) => { + if (newErrs[k]) { + errsToSet[k] = newErrs[k]; + } + }); + setFormErrors(errsToSet); +}; + +``` + +, + +``` +const onInputBlur = () => { + setFormErrors(validateFormData(formData)); +}; +``` + +, and + +``` +const onFormSubmit = (evt: React.MouseEvent) => { + evt.preventDefault(); + + // return null if there are errors + const errs = validateFormData(formData); + if (Object.keys(errs).length > 0) { + setFormErrors(errs); + return; + } + + ... + // continue with submit logic if no errors + +``` ## React hooks diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts index 4c4d4d6e9e..0de36a5d36 100644 --- a/frontend/interfaces/config.ts +++ b/frontend/interfaces/config.ts @@ -41,6 +41,8 @@ export interface IAppleDeviceUpdates { } export interface IMdmConfig { + /** Update this URL if you're self-hosting Fleet and you want your hosts to talk to a different URL for MDM features. (If not configured, hosts will use the base URL of the Fleet instance.) */ + apple_server_url: string; enable_disk_encryption: boolean; /** `enabled_and_configured` only tells us if Apples MDM has been enabled and configured correctly. The naming is slightly confusing but at one point we diff --git a/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx b/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx index 6beca4bc0d..a81bc5e763 100644 --- a/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx +++ b/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx @@ -1,5 +1,6 @@ -import React, { useState, useEffect, useMemo } from "react"; +import React, { useState, useMemo } from "react"; +import validUrl from "components/forms/validators/valid_url"; import Button from "components/buttons/Button"; import Checkbox from "components/forms/fields/Checkbox"; // @ts-ignore @@ -16,6 +17,7 @@ import { IAppConfigFormProps, IFormField } from "../constants"; const baseClass = "app-config-form"; interface IAdvancedConfigFormData { + mdmAppleServerURL: string; domain: string; verifySSLCerts: boolean; enableStartTLS?: boolean; @@ -30,15 +32,47 @@ interface IAdvancedConfigFormData { } interface IAdvancedConfigFormErrors { - host_expiry_window?: string | null; + mdmAppleServerURL?: string | null; + domain?: string | null; + hostExpiryWindow?: string | null; } +const validateFormData = ({ + mdmAppleServerURL, + domain, + hostExpiryWindow, + enableHostExpiry, +}: IAdvancedConfigFormData) => { + const errors: Record = {}; + + if (!mdmAppleServerURL) { + delete errors.mdmAppleServerURL; + } else if (!validUrl({ url: mdmAppleServerURL })) { + errors.mdmAppleServerURL = `${mdmAppleServerURL} is not a valid URL`; + } + + if (!domain) { + delete errors.domain; + } else if (!validUrl({ url: domain })) { + errors.domain = `${domain} is not a valid URL`; + } + + if ( + enableHostExpiry && + (!hostExpiryWindow || parseInt(hostExpiryWindow, 10) <= 0) + ) { + errors.hostExpiryWindow = "Host expiry window must be a positive number"; + } + return errors; +}; + const Advanced = ({ appConfig, handleSubmit, isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const [formData, setFormData] = useState({ + mdmAppleServerURL: appConfig.mdm?.apple_server_url || "", domain: appConfig.smtp_settings?.domain || "", verifySSLCerts: appConfig.smtp_settings?.verify_ssl_certs || false, enableStartTLS: appConfig.smtp_settings?.enable_start_tls, @@ -60,6 +94,7 @@ const Advanced = ({ }); const { + mdmAppleServerURL, domain, verifySSLCerts, enableStartTLS, @@ -89,27 +124,33 @@ const Advanced = ({ ); const onInputChange = ({ name, value }: IFormField) => { - setFormData({ ...formData, [name]: value }); + const newFormData = { ...formData, [name]: value }; + setFormData(newFormData); + const newErrs = validateFormData(newFormData); + // only set errors that are updates of existing errors + // new errors are only set onBlur + const errsToSet: Record = {}; + Object.keys(formErrors).forEach((k) => { + if (newErrs[k]) { + errsToSet[k] = newErrs[k]; + } + }); + setFormErrors(errsToSet); }; - useEffect(() => { - // validate desired form fields - const errors: IAdvancedConfigFormErrors = {}; - - if ( - enableHostExpiry && - (!hostExpiryWindow || parseInt(hostExpiryWindow, 10) <= 0) - ) { - errors.host_expiry_window = - "Host expiry window must be a positive number"; - } - - setFormErrors(errors); - }, [enableHostExpiry, hostExpiryWindow]); + const onInputBlur = () => { + setFormErrors(validateFormData(formData)); + }; const onFormSubmit = (evt: React.MouseEvent) => { evt.preventDefault(); + const errs = validateFormData(formData); + if (Object.keys(errs).length > 0) { + setFormErrors(errs); + return; + } + // Formatting of API not UI const formDataToSubmit = { server_settings: { @@ -132,6 +173,9 @@ const Advanced = ({ activity_expiry_enabled: deleteActivities, activity_expiry_window: activityExpiryWindow || undefined, }, + mdm: { + apple_server_url: mdmAppleServerURL, + }, }; handleSubmit(formDataToSubmit); @@ -145,12 +189,27 @@ const Advanced = ({

Most users do not need to modify these options.

+ {appConfig.mdm.enabled_and_configured && ( + + )} If you need to specify a HELO domain,
@@ -231,7 +290,7 @@ const Advanced = ({ name="hostExpiryWindow" value={hostExpiryWindow} parseTarget - error={formErrors.host_expiry_window} + error={formErrors.hostExpiryWindow} /> )} Date: Tue, 22 Oct 2024 12:22:53 -0500 Subject: [PATCH 22/45] Update patch release instructions (#22831) --- tools/release/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/release/README.md b/tools/release/README.md index 7612df55ef..da5210c440 100644 --- a/tools/release/README.md +++ b/tools/release/README.md @@ -69,22 +69,23 @@ LinkedIn post: https://www.linkedin.com/feed/update/urn:li:activity:719950989670 ## Patch Release (middle of sprint / critical) -example ``` # Build release candidate and changelogs and QA ticket ./tools/release/publish_release.sh + # Do QA until ready to release # QA is passed on all teams and ready for release # Tag patch ./tools/release/publish_release.sh -g + # Publish patch ./tools/release/publish_release.sh -u + +# Make sure to wait for the CLI to open NPM to publish fleetctl. +# If that fails, manually publish by going to the `/tools/fleetctl-npm/` directory +# and running `npm publish` + # Go update osquery-slack version ``` - -... -TODO example output -... - From f9c5139b5e38ce59b9143845d33ece38eee8fbc4 Mon Sep 17 00:00:00 2001 From: Sharon Katz <121527325+sharon-fdm@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:26:14 -0400 Subject: [PATCH 23/45] Additional comments to the release instructions. (#23005) --- tools/release/README.md | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/tools/release/README.md b/tools/release/README.md index da5210c440..5f21ad47a6 100644 --- a/tools/release/README.md +++ b/tools/release/README.md @@ -17,6 +17,12 @@ This requires: The script will check that each of these are installed and available before running +Make sure the repo is set to default (Needed only once) +``` + gh repo set-default +``` + + ## Before publishing the release Make sure all tickets are tagged with the correct milestone. @@ -69,17 +75,44 @@ LinkedIn post: https://www.linkedin.com/feed/update/urn:li:activity:719950989670 ## Patch Release (middle of sprint / critical) +Follow this example: +It is recommended to do a "dry run" like this: ``` -# Build release candidate and changelogs and QA ticket -./tools/release/publish_release.sh +tools/release/publish_release.sh -d +``` +This provides an opportunity to go over the tickets and their attached PRs. +Specifically check cases where more than one ticket have the same PR and vice versa. +# Build release candidate, changelogs and QA ticket + +``` +./tools/release/publish_release.sh +``` # Do QA until ready to release +After running the script: +- Check #help-engineering for announcements and QA ticket. +- Merge the patch branch changes PR. +- Let QA know about the release here: https://github.com/fleetdm/fleet/actions/workflows/goreleaser-snapshot-fleet.yaml +- This is a good time to post a request in #g-sales that we may want to deploy this release to dogfood. + # QA is passed on all teams and ready for release -# Tag patch +Tag patch +``` ./tools/release/publish_release.sh -g +``` +Wait for goreleaser to finish in terminal (This will take a long time) + +Publish patch +``` +./tools/release/publish_release.sh -u +``` +Go update osquery-slack version + +# Merge the final changes PR into main: + # Publish patch ./tools/release/publish_release.sh -u From 2c891447d45ee679181496108db8a0c0556b1718 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Tue, 22 Oct 2024 14:27:00 -0300 Subject: [PATCH 24/45] Release osqueryd 5.14.1 (#23045) --- .github/workflows/generate-osqueryd-targets.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate-osqueryd-targets.yml b/.github/workflows/generate-osqueryd-targets.yml index b27e074b6a..762c76fa17 100644 --- a/.github/workflows/generate-osqueryd-targets.yml +++ b/.github/workflows/generate-osqueryd-targets.yml @@ -24,7 +24,7 @@ defaults: shell: bash env: - OSQUERY_VERSION: 5.13.1 + OSQUERY_VERSION: 5.14.1 permissions: contents: read From 89d53330f56e268ff53ebe5f963c59931671f73f Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Tue, 22 Oct 2024 12:41:33 -0500 Subject: [PATCH 25/45] Update process_open_sockets.yml (#23033) Co-authored-by: Eric --- schema/osquery_fleet_schema.json | 2 +- schema/tables/process_open_sockets.yml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/schema/osquery_fleet_schema.json b/schema/osquery_fleet_schema.json index 72e286f304..0a8574d1b2 100644 --- a/schema/osquery_fleet_schema.json +++ b/schema/osquery_fleet_schema.json @@ -21510,7 +21510,7 @@ }, { "name": "family", - "description": "Network protocol (IPv4, IPv6)", + "description": "Network protocol (IPv4, IPv6). [Learn more](https://students.mimuw.edu.pl/SO/Linux/Kod/include/linux/socket.h.html)", "type": "integer", "notes": "", "hidden": false, diff --git a/schema/tables/process_open_sockets.yml b/schema/tables/process_open_sockets.yml index 67c11152fd..101a87e4ad 100644 --- a/schema/tables/process_open_sockets.yml +++ b/schema/tables/process_open_sockets.yml @@ -8,6 +8,8 @@ columns: - name: net_namespace platforms: - linux + - name: family + description: Network protocol (IPv4, IPv6). [Learn more](https://students.mimuw.edu.pl/SO/Linux/Kod/include/linux/socket.h.html) examples: |- This table allows you to see network activity by process. With this query, list all connections made to or from a process, excluding connections to localhost and @@ -15,4 +17,4 @@ examples: |- ``` SELECT pos.local_port, pos.remote_port, pos.remote_address, p.pid, p.path FROM process_open_sockets pos JOIN processes p ON pos.pid = p.pid WHERE remote_address NOT LIKE '192.168%' AND remote_address NOT LIKE '10.%' AND remote_address NOT LIKE '172.16.%' AND remote_address NOT LIKE '127.%' AND remote_address!='0.0.0.0' AND remote_address NOT LIKE 'fe80%' AND remote_port!='0'; - ``` \ No newline at end of file + ``` From 3e8f68f80dae29a4b9bf7eed5301d4f0c27e6b0f Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Tue, 22 Oct 2024 12:47:46 -0500 Subject: [PATCH 26/45] Pass through original status of an install when the removed flag is set when GETing the install result (#23072) #23070 // https://www.loom.com/share/6ac7260712c94d4890f67fe8d5a26ea1 Also removes the unused, undocumented Detail field on software install results # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Added/updated tests - [x] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes - [x] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [x] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [x] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [x] Manual QA for all new/changed functionality --- changes/23070-install-execution-status | 1 + ...ddExecutionStatusToHostSoftwareInstalls.go | 57 ++++++++++ ...cutionStatusToHostSoftwareInstalls_test.go | 101 ++++++++++++++++++ server/datastore/mysql/schema.sql | 5 +- server/datastore/mysql/software_installers.go | 2 +- .../mysql/software_installers_test.go | 12 ++- server/fleet/software_installer.go | 3 - server/service/integration_enterprise_test.go | 5 + 8 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 changes/23070-install-execution-status create mode 100644 server/datastore/mysql/migrations/tables/20241021224359_AddExecutionStatusToHostSoftwareInstalls.go create mode 100644 server/datastore/mysql/migrations/tables/20241021224359_AddExecutionStatusToHostSoftwareInstalls_test.go diff --git a/changes/23070-install-execution-status b/changes/23070-install-execution-status new file mode 100644 index 0000000000..dc61f4432f --- /dev/null +++ b/changes/23070-install-execution-status @@ -0,0 +1 @@ +* Fixed blank install status when retrieving install details from the activity feed when the installer package has been updated or the software has since been removed from the host. diff --git a/server/datastore/mysql/migrations/tables/20241021224359_AddExecutionStatusToHostSoftwareInstalls.go b/server/datastore/mysql/migrations/tables/20241021224359_AddExecutionStatusToHostSoftwareInstalls.go new file mode 100644 index 0000000000..4ceba9c264 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20241021224359_AddExecutionStatusToHostSoftwareInstalls.go @@ -0,0 +1,57 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20241021224359, Down_20241021224359) +} + +func Up_20241021224359(tx *sql.Tx) error { + // Column is added for "status irrespective of whether removed flag is set", for showing details on a single + // (un)install. The normal status column is used for aggregate metrics of "how many hosts have this installed", + // so it needs to be reset when installers change. + if _, err := tx.Exec(` +ALTER TABLE host_software_installs +ADD COLUMN execution_status ENUM('pending_install', 'failed_install', 'installed', 'pending_uninstall', 'failed_uninstall') +GENERATED ALWAYS AS ( +CASE + WHEN post_install_script_exit_code IS NOT NULL AND + post_install_script_exit_code = 0 THEN 'installed' + + WHEN post_install_script_exit_code IS NOT NULL AND + post_install_script_exit_code != 0 THEN 'failed_install' + + WHEN install_script_exit_code IS NOT NULL AND + install_script_exit_code = 0 THEN 'installed' + + WHEN install_script_exit_code IS NOT NULL AND + install_script_exit_code != 0 THEN 'failed_install' + + WHEN pre_install_query_output IS NOT NULL AND + pre_install_query_output = '' THEN 'failed_install' + + WHEN host_id IS NOT NULL AND uninstall = 0 THEN 'pending_install' + + WHEN uninstall_script_exit_code IS NOT NULL AND + uninstall_script_exit_code != 0 THEN 'failed_uninstall' + + WHEN uninstall_script_exit_code IS NOT NULL AND + uninstall_script_exit_code = 0 THEN NULL -- available for install again + + WHEN host_id IS NOT NULL AND uninstall = 1 THEN 'pending_uninstall' + + ELSE NULL -- not installed from Fleet installer or successfully uninstalled +END +) VIRTUAL NULL`); err != nil { + return fmt.Errorf("failed to add execution_status column to host_software_installs: %w", err) + } + + return nil +} + +func Down_20241021224359(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20241021224359_AddExecutionStatusToHostSoftwareInstalls_test.go b/server/datastore/mysql/migrations/tables/20241021224359_AddExecutionStatusToHostSoftwareInstalls_test.go new file mode 100644 index 0000000000..cb770c35d9 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20241021224359_AddExecutionStatusToHostSoftwareInstalls_test.go @@ -0,0 +1,101 @@ +package tables + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUp_20241021224359(t *testing.T) { + db := applyUpToPrev(t) + + // Create host + insertHostStmt := ` + INSERT INTO hosts ( + hostname, uuid, platform, osquery_version, os_version, build, platform_like, code_name, + cpu_type, cpu_subtype, cpu_brand, hardware_vendor, hardware_model, hardware_version, + hardware_serial, computer_name, team_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ` + hostName := "Dummy Hostname" + hostUUID := "12345678-1234-1234-1234-123456789012" + hostPlatform := "darwin" + osqueryVer := "5.9.1" + osVersion := "Windows 10" + buildVersion := "10.0.19042.1234" + platformLike := "apple" + codeName := "20H2" + cpuType := "x86_64" + cpuSubtype := "x86_64" + cpuBrand := "Intel" + hwVendor := "Dell Inc." + hwModel := "OptiPlex 7090" + hwVersion := "1.0" + hwSerial := "ABCDEFGHIJ" + computerName := "DESKTOP-TEST" + + hostID1 := execNoErrLastID(t, db, insertHostStmt, hostName, hostUUID, hostPlatform, osqueryVer, + osVersion, buildVersion, platformLike, codeName, cpuType, cpuSubtype, cpuBrand, hwVendor, hwModel, hwVersion, hwSerial, + computerName, nil) + + dataStmts := ` + INSERT INTO script_contents (id, md5_checksum, contents) VALUES + (1, 'checksum', 'script content'); + + INSERT INTO software_titles (id, name, source, browser) VALUES (1, 'Foo.app', 'apps', ''); + + INSERT INTO software_installers + (id, title_id, filename, version, platform, install_script_content_id, storage_id, package_ids, uninstall_script_content_id) + VALUES + (1, 1, 'foo-installer.pkg', '1.1', 'darwin', 1, 'storage-id', '', 1); + ` + _, err := db.Exec(dataStmts) + require.NoError(t, err) + + hsiStmt := ` + INSERT INTO host_software_installs ( + host_id, + execution_id, + software_installer_id, + install_script_exit_code, + uninstall_script_exit_code, + updated_at, + uninstall, + removed + ) VALUES (?, ?, ?, ?, ?, '2024-10-01 00:00:00', ?, 1)` + hsiInstall := execNoErrLastID(t, db, hsiStmt, hostID1, "execution-id1", 1, 0, nil, 0) + hsiUninstall := execNoErrLastID(t, db, hsiStmt, hostID1, "execution-id2", 1, nil, 0, 1) + + // Apply current migration. + applyNext(t, db) + + var statuses struct { + Status *string `db:"status"` + ExecutionStatus *string `db:"execution_status"` + } + + err = db.Get(&statuses, "SELECT status, execution_status FROM host_software_installs WHERE id = ?", hsiInstall) + require.NoError(t, err) + require.NotNil(t, statuses.ExecutionStatus) + require.Equal(t, "installed", *statuses.ExecutionStatus) + require.Nil(t, statuses.Status) + + err = db.Get(&statuses, "SELECT status, execution_status FROM host_software_installs WHERE id = ?", hsiUninstall) + require.NoError(t, err) + require.Nil(t, statuses.ExecutionStatus) // uninstalls have null status + require.Nil(t, statuses.Status) + + execNoErr(t, db, `UPDATE host_software_installs SET removed = 0`) + + err = db.Get(&statuses, "SELECT status, execution_status FROM host_software_installs WHERE id = ?", hsiInstall) + require.NoError(t, err) + require.NotNil(t, statuses.ExecutionStatus) + require.Equal(t, "installed", *statuses.ExecutionStatus) + require.NotNil(t, statuses.Status) + require.Equal(t, "installed", *statuses.Status) + + err = db.Get(&statuses, "SELECT status, execution_status FROM host_software_installs WHERE id = ?", hsiUninstall) + require.NoError(t, err) + require.Nil(t, statuses.ExecutionStatus) // uninstalls have null status + require.Nil(t, statuses.Status) +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index da34cc3ddd..3395c33f60 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -604,6 +604,7 @@ CREATE TABLE `host_software_installs` ( `version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'unknown', `software_title_id` int unsigned DEFAULT NULL, `software_title_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '[deleted title]', + `execution_status` enum('pending_install','failed_install','installed','pending_uninstall','failed_uninstall') COLLATE utf8mb4_unicode_ci GENERATED ALWAYS AS ((case when ((`post_install_script_exit_code` is not null) and (`post_install_script_exit_code` = 0)) then _utf8mb4'installed' when ((`post_install_script_exit_code` is not null) and (`post_install_script_exit_code` <> 0)) then _utf8mb4'failed_install' when ((`install_script_exit_code` is not null) and (`install_script_exit_code` = 0)) then _utf8mb4'installed' when ((`install_script_exit_code` is not null) and (`install_script_exit_code` <> 0)) then _utf8mb4'failed_install' when ((`pre_install_query_output` is not null) and (`pre_install_query_output` = _utf8mb4'')) then _utf8mb4'failed_install' when ((`host_id` is not null) and (`uninstall` = 0)) then _utf8mb4'pending_install' when ((`uninstall_script_exit_code` is not null) and (`uninstall_script_exit_code` <> 0)) then _utf8mb4'failed_uninstall' when ((`uninstall_script_exit_code` is not null) and (`uninstall_script_exit_code` = 0)) then NULL when ((`host_id` is not null) and (`uninstall` = 1)) then _utf8mb4'pending_uninstall' else NULL end)) VIRTUAL, PRIMARY KEY (`id`), UNIQUE KEY `idx_host_software_installs_execution_id` (`execution_id`), KEY `fk_host_software_installs_user_id` (`user_id`), @@ -1084,9 +1085,9 @@ CREATE TABLE `migration_status_tables` ( `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=322 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=323 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( diff --git a/server/datastore/mysql/software_installers.go b/server/datastore/mysql/software_installers.go index e062521a92..9ab5a1cfec 100644 --- a/server/datastore/mysql/software_installers.go +++ b/server/datastore/mysql/software_installers.go @@ -586,7 +586,7 @@ SELECT hsi.host_id AS host_id, COALESCE(st.name, hsi.software_title_name) AS software_title, hsi.software_title_id, - COALESCE(hsi.status, '') AS status, + COALESCE(hsi.execution_status, '') AS status, hsi.installer_filename AS software_package, hsi.user_id AS user_id, hsi.post_install_script_exit_code, diff --git a/server/datastore/mysql/software_installers_test.go b/server/datastore/mysql/software_installers_test.go index be4169f246..e5ff78d9c9 100644 --- a/server/datastore/mysql/software_installers_test.go +++ b/server/datastore/mysql/software_installers_test.go @@ -552,17 +552,21 @@ func testGetSoftwareInstallResult(t *testing.T, ds *Datastore) { err = ds.DeleteSoftwareInstaller(ctx, installerID) require.NoError(t, err) - res, err = ds.GetSoftwareInstallResults(ctx, installUUID) - require.NoError(t, err) - - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { // ensure version is not changed, though we don't expose it yet + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + // ensure version is not changed, though we don't expose it yet var version string err := sqlx.GetContext(ctx, q, &version, `SELECT "version" FROM host_software_installs WHERE execution_id = ?`, installUUID) require.NoError(t, err) require.Equal(t, "1.11", version) + // let's also set the removed flag to ensure that the status we're pulling doesn't change + _, err = q.ExecContext(ctx, `UPDATE host_software_installs SET removed = 1 WHERE execution_id = ?`, installUUID) + require.NoError(t, err) + return nil }) + + res, err = ds.GetSoftwareInstallResults(ctx, installUUID) require.NoError(t, err) require.Equal(t, installUUID, res.InstallUUID) diff --git a/server/fleet/software_installer.go b/server/fleet/software_installer.go index 52c009e398..47e7ed91fe 100644 --- a/server/fleet/software_installer.go +++ b/server/fleet/software_installer.go @@ -220,9 +220,6 @@ type HostSoftwareInstallerResult struct { HostID uint `json:"host_id" db:"host_id"` // Status is the status of the software installer package on the host. Status SoftwareInstallerStatus `json:"status" db:"status"` - // Detail is the detail of the software installer package on the host. TODO: does this field - // have specific values that should be used? If so, how are they calculated? - Detail string `json:"detail" db:"detail"` // Output is the output of the software installer package on the host. Output *string `json:"output" db:"install_script_output"` // PreInstallQueryOutput is the output of the pre-install query on the host. diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 636bdc833a..801fc20867 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -11281,6 +11281,11 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec hostResp = getHostSoftwareResponse{} s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &hostResp) require.Nil(t, hostResp.Software[0].Status) + + // install details record should still show as installed + installDetailsResp := getSoftwareInstallResultsResponse{} + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/software/install/%s/results", installUUID), nil, http.StatusOK, &installDetailsResp) + require.Equal(t, fleet.SoftwareInstalled, installDetailsResp.Results.Status) } func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersWithPoliciesAssociated() { From d3ccb51755fc7853c35a26035a6605583b4cc0fd Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:52:20 -0700 Subject: [PATCH 27/45] UI - Improve UX of Flash messages (#22836) ## #22661 ![ezgif-6-71e48912ae](https://github.com/user-attachments/assets/01144620-0eba-48f0-9254-cc4795fde9fd) - Update `FlashMessage` behavior to, by default, hide itself when the user performs any URL-changing navigation - Add `persistOnPageChange` option to `renderFlash` API and associated notification context and reducer logic, allowing override of this behavior on a per-call basis - Ensure proper order of evaluation of URL changes and render flash action dispatches on the event loop - Clean up legacy unused "undo"-related arguments and logic - Allow the user to click in the same horizontal dimension as a flash message - Other misc. cleanup and refactoring [Demo - messages hidden on page (any URL) change](https://www.loom.com/share/1e884b6ba11c4b59bc74f51df3690131?sid=9b53e78b-6535-4541-b676-377760366cf4) - [x] Changes file added for user-visible changes in `changes/`, - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- changes/22661-improve-flash-message-ux | 4 +++ frontend/components/App/App.tsx | 1 - .../FlashMessage/FlashMessage.stories.tsx | 1 - .../components/FlashMessage/FlashMessage.tsx | 27 +++++++-------- .../top_nav/SiteTopNav/SiteTopNav.tests.tsx | 18 +++++----- .../top_nav/SiteTopNav/SiteTopNav.tsx | 10 +++--- .../components/top_nav/UserMenu/UserMenu.tsx | 10 +++--- frontend/context/notification.tsx | 34 +++++++++++-------- frontend/docs/patterns.md | 18 ++++++++++ frontend/interfaces/notification.ts | 4 +-- frontend/layouts/CoreLayout/CoreLayout.tsx | 27 +++------------ frontend/pages/AccountPage/AccountPage.tsx | 12 +++---- .../SoftwareCustomPackage.tsx | 19 ++++++----- .../FleetMaintainedAppDetailsPage.tsx | 12 +++---- .../RenewAbmModal/RenewAbmModal.tsx | 2 +- .../MdmSettings/AppleMdmPage/AppleMdmPage.tsx | 2 +- .../modals/RenewCertModal/RenewCertModal.tsx | 2 +- .../WindowsMdmPage/WindowsMdmPage.tsx | 15 +++++--- .../AgentOptionsPage/AgentOptionsPage.tsx | 2 +- .../TeamDetailsWrapper/TeamDetailsWrapper.tsx | 3 +- .../hosts/ManageHostsPage/ManageHostsPage.tsx | 11 +++--- .../DynamicLabel/DynamicLabel.tsx | 9 +++-- .../NewLabelPage/ManualLabel/ManualLabel.tsx | 9 +++-- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 1 - 24 files changed, 133 insertions(+), 120 deletions(-) create mode 100644 changes/22661-improve-flash-message-ux diff --git a/changes/22661-improve-flash-message-ux b/changes/22661-improve-flash-message-ux new file mode 100644 index 0000000000..fa3ff14167 --- /dev/null +++ b/changes/22661-improve-flash-message-ux @@ -0,0 +1,4 @@ +* Allow clicking in the horizontal dimension of a flash message, outside of the message itself, and +always hide flash messages when changing routes. +* Refactor `renderFlash` and associated logic to by default dismiss the rendered message on page + change. Provide optional `persistOnPageChange` option to allow overriding this behavior. \ No newline at end of file diff --git a/frontend/components/App/App.tsx b/frontend/components/App/App.tsx index da850f26d0..b70f56a17b 100644 --- a/frontend/components/App/App.tsx +++ b/frontend/components/App/App.tsx @@ -32,7 +32,6 @@ import Fleet404 from "pages/errors/Fleet404"; import Fleet500 from "pages/errors/Fleet500"; import Spinner from "components/Spinner"; -import { IMdmVppToken } from "interfaces/mdm"; interface IAppProps { children: JSX.Element; diff --git a/frontend/components/FlashMessage/FlashMessage.stories.tsx b/frontend/components/FlashMessage/FlashMessage.stories.tsx index ffafc46366..6f736143dc 100644 --- a/frontend/components/FlashMessage/FlashMessage.stories.tsx +++ b/frontend/components/FlashMessage/FlashMessage.stories.tsx @@ -26,7 +26,6 @@ export default { message: "I am a message. Hear me roar!", alertType: "success", isVisible: true, - undoAction: noop, }, }, } as Meta; diff --git a/frontend/components/FlashMessage/FlashMessage.tsx b/frontend/components/FlashMessage/FlashMessage.tsx index d8c36caf6c..dba9656245 100644 --- a/frontend/components/FlashMessage/FlashMessage.tsx +++ b/frontend/components/FlashMessage/FlashMessage.tsx @@ -4,7 +4,6 @@ import classnames from "classnames"; import { INotification } from "interfaces/notification"; // @ts-ignore import Icon from "components/Icon/Icon"; -import Button from "components/buttons/Button"; const baseClass = "flash-message"; @@ -14,9 +13,7 @@ export interface IFlashMessage { isPersistent?: boolean; className?: string; onRemoveFlash: () => void; - onUndoActionClick?: ( - value: () => void - ) => (evt: React.MouseEvent) => void; + pathname?: string; } const FlashMessage = ({ @@ -25,9 +22,10 @@ const FlashMessage = ({ isPersistent, className, onRemoveFlash, - onUndoActionClick, + pathname, }: IFlashMessage): JSX.Element | null => { - const { alertType, isVisible, message, undoAction } = notification || {}; + const { alertType, isVisible, message, persistOnPageChange } = + notification || {}; const baseClasses = classnames( baseClass, className, @@ -60,6 +58,14 @@ const FlashMessage = ({ return undefined; // No cleanup when we don't set a timeout. }, [notification, alertType, isVisible, setHide]); + useEffect(() => { + if (!persistOnPageChange) { + setHide(true); + } + // intentionally omit persistOnPageChange from dependencies to prevent hiding during initial + // update of the notification prop from its default empty value + }, [pathname]); + if (hide || !isVisible) { return null; } @@ -73,15 +79,6 @@ const FlashMessage = ({ color="core-fleet-white" /> {message} - {onUndoActionClick && undoAction && ( - - )}
diff --git a/frontend/components/top_nav/SiteTopNav/SiteTopNav.tests.tsx b/frontend/components/top_nav/SiteTopNav/SiteTopNav.tests.tsx index 92133a1423..5ef627eea7 100644 --- a/frontend/components/top_nav/SiteTopNav/SiteTopNav.tests.tsx +++ b/frontend/components/top_nav/SiteTopNav/SiteTopNav.tests.tsx @@ -33,7 +33,7 @@ describe("SiteTopNav - component", () => { currentUser={createMockUser()} location={urlLocation} onLogoutUser={noop} - onNavItemClick={noop} + onUserMenuItemClick={noop} /> ); @@ -69,7 +69,7 @@ describe("SiteTopNav - component", () => { })} location={urlLocation} onLogoutUser={noop} - onNavItemClick={noop} + onUserMenuItemClick={noop} /> ); @@ -105,7 +105,7 @@ describe("SiteTopNav - component", () => { })} location={urlLocation} onLogoutUser={noop} - onNavItemClick={noop} + onUserMenuItemClick={noop} /> ); @@ -139,7 +139,7 @@ describe("SiteTopNav - component", () => { currentUser={createMockUser()} location={urlLocation} onLogoutUser={noop} - onNavItemClick={noop} + onUserMenuItemClick={noop} /> ); @@ -175,7 +175,7 @@ describe("SiteTopNav - component", () => { })} location={urlLocation} onLogoutUser={noop} - onNavItemClick={noop} + onUserMenuItemClick={noop} /> ); @@ -211,7 +211,7 @@ describe("SiteTopNav - component", () => { })} location={urlLocation} onLogoutUser={noop} - onNavItemClick={noop} + onUserMenuItemClick={noop} /> ); @@ -248,7 +248,7 @@ describe("SiteTopNav - component", () => { })} location={urlLocation} onLogoutUser={noop} - onNavItemClick={noop} + onUserMenuItemClick={noop} /> ); @@ -285,7 +285,7 @@ describe("SiteTopNav - component", () => { })} location={urlLocation} onLogoutUser={noop} - onNavItemClick={noop} + onUserMenuItemClick={noop} /> ); @@ -321,7 +321,7 @@ describe("SiteTopNav - component", () => { })} location={urlLocation} onLogoutUser={noop} - onNavItemClick={noop} + onUserMenuItemClick={noop} /> ); diff --git a/frontend/components/top_nav/SiteTopNav/SiteTopNav.tsx b/frontend/components/top_nav/SiteTopNav/SiteTopNav.tsx index be5646f2c9..2f0f0d3b1f 100644 --- a/frontend/components/top_nav/SiteTopNav/SiteTopNav.tsx +++ b/frontend/components/top_nav/SiteTopNav/SiteTopNav.tsx @@ -20,12 +20,10 @@ interface ISiteTopNavProps { currentUser: IUser; location: { pathname: string; - search: string; - hash?: string; query: QueryParams; }; onLogoutUser: () => void; - onNavItemClick: (path: string) => void; + onUserMenuItemClick: (path: string) => void; } // TODO(sarah): Build RegExps for other routes that need to be differentiated in order to build @@ -75,9 +73,9 @@ const isGlobalPage = (path: string) => { const SiteTopNav = ({ config, currentUser, - location: { pathname: currentPath, search, hash = "", query }, + location: { pathname: currentPath, query }, onLogoutUser, - onNavItemClick, + onUserMenuItemClick, }: ISiteTopNavProps): JSX.Element => { const { currentTeam, @@ -212,7 +210,7 @@ const SiteTopNav = ({ void; - onNavItemClick: (path: string) => void; + onUserMenuItemClick: (path: string) => void; isAnyTeamAdmin: boolean | undefined; isGlobalAdmin: boolean | undefined; currentUser: IUser; @@ -21,12 +21,12 @@ interface IUserMenuProps { const UserMenu = ({ onLogout, - onNavItemClick, + onUserMenuItemClick, isAnyTeamAdmin, isGlobalAdmin, currentUser, }: IUserMenuProps): JSX.Element => { - const accountNavigate = onNavItemClick(PATHS.ACCOUNT); + const accountNavigate = onUserMenuItemClick(PATHS.ACCOUNT); const dropdownItems = [ { label: "My account", @@ -43,7 +43,7 @@ const UserMenu = ({ ]; if (isGlobalAdmin) { - const manageUsersNavigate = onNavItemClick(PATHS.ADMIN_USERS); + const manageUsersNavigate = onUserMenuItemClick(PATHS.ADMIN_USERS); const manageUserNavItem = { label: "Manage users", @@ -61,7 +61,7 @@ const UserMenu = ({ currentUser.global_role === "admin" ? PATHS.ADMIN_ORGANIZATION : `${PATHS.TEAM_DETAILS_USERS(sortedTeams[0].value)}`; - const settingsNavigate = onNavItemClick(settingsPath); + const settingsNavigate = onUserMenuItemClick(settingsPath); const adminNavItem = { label: "Settings", onClick: settingsNavigate, diff --git a/frontend/context/notification.tsx b/frontend/context/notification.tsx index 7b79a25382..5348090fd7 100644 --- a/frontend/context/notification.tsx +++ b/frontend/context/notification.tsx @@ -17,7 +17,7 @@ type InitialStateType = { renderFlash: ( alertType: "success" | "error" | "warning-filled" | null, message: JSX.Element | string | null, - undoAction?: (evt: React.MouseEvent) => void + options?: { persistOnPageChange?: boolean } ) => void; hideFlash: () => void; }; @@ -30,24 +30,24 @@ const initialState = { hideFlash: noop, }; -const actions = { +const actionTypes = { RENDER_FLASH: "RENDER_FLASH", HIDE_FLASH: "HIDE_FLASH", -}; +} as const; const reducer = (state: any, action: any) => { switch (action.type) { - case actions.RENDER_FLASH: + case actionTypes.RENDER_FLASH: return { ...state, notification: { alertType: action.alertType, isVisible: true, message: action.message, - undoAction: action.undoAction, + persistOnPageChange: action.options?.persistOnPageChange ?? false, }, }; - case actions.HIDE_FLASH: + case actionTypes.HIDE_FLASH: return initialState; default: return state; @@ -60,25 +60,31 @@ export const NotificationContext = createContext( const NotificationProvider = ({ children }: Props) => { const [state, dispatch] = useReducer(reducer, initialState); - const renderFlash = useCallback( ( alertType: "success" | "error" | "warning-filled" | null, message: JSX.Element | string | null, - undoAction?: (evt: React.MouseEvent) => void + options?: { + persistOnPageChange?: boolean; + } ) => { - dispatch({ - type: actions.RENDER_FLASH, - alertType, - message, - undoAction, + // wrapping the dispatch in a timeout ensures it is evaluated on the next event loop, + // preventing bugs related to the FlashMessage's self-hiding behavior on URL changes. + // react router v3 router.push is asynchronous + setTimeout(() => { + dispatch({ + type: actionTypes.RENDER_FLASH, + alertType, + message, + options, + }); }); }, [] ); const hideFlash = useCallback(() => { - dispatch({ type: actions.HIDE_FLASH }); + dispatch({ type: actionTypes.HIDE_FLASH }); }, []); const value = useMemo( diff --git a/frontend/docs/patterns.md b/frontend/docs/patterns.md index 9916643017..0b9c3ee900 100644 --- a/frontend/docs/patterns.md +++ b/frontend/docs/patterns.md @@ -475,3 +475,21 @@ then the [app's context](#react-context) should be used. If you are dealing with a page that *updates* any kind of config, you'll want to access that config with a fresh API call to be sure you have the updated values. Otherwise, that is, you are dealing with a page that is only *reading* config values, get them from context. + +### Rendering flash messages + +Flash messages by default will be hidden when the user performs any navigation that changes the URL, +in addition to the timeout set for success messages. The `renderFlash` method from notification +context accepts an optional third `options` argument which contains an optional +`persistOnPageChange` boolean field that can be set to `true` to negate this default behavior. + +If the `renderFlash` is accompanied by a router push, it's important to push to the router *before* +calling `renderFlash`. If the push comes after the `renderFlash` call, +the flash message may register the `push` and immediately hide itself. + +```tsx +// first push +router.push(newPath); +// then flash +renderFlash("error", "Something went wrong"); +``` \ No newline at end of file diff --git a/frontend/interfaces/notification.ts b/frontend/interfaces/notification.ts index 0d3b0ffa17..2a7f487bdb 100644 --- a/frontend/interfaces/notification.ts +++ b/frontend/interfaces/notification.ts @@ -4,12 +4,12 @@ export default PropTypes.shape({ alertType: PropTypes.string, isVisible: PropTypes.bool, message: PropTypes.string, - undoAction: PropTypes.func, + persistOnPageChange: PropTypes.bool, }); export interface INotification { alertType: "success" | "error" | "warning-filled" | null; isVisible: boolean; message: JSX.Element | string | null; - undoAction?: () => void; + persistOnPageChange?: boolean; } diff --git a/frontend/layouts/CoreLayout/CoreLayout.tsx b/frontend/layouts/CoreLayout/CoreLayout.tsx index 2a1830fb5b..1074a875de 100644 --- a/frontend/layouts/CoreLayout/CoreLayout.tsx +++ b/frontend/layouts/CoreLayout/CoreLayout.tsx @@ -1,6 +1,5 @@ import React, { useContext } from "react"; import { InjectedRouter } from "react-router"; -import { Params } from "react-router/lib/Router"; import UnsupportedScreenSize from "layouts/UnsupportedScreenSize"; @@ -24,15 +23,9 @@ interface ICoreLayoutProps { hash?: string; query: QueryParams; }; - params: Params; } -const CoreLayout = ({ - children, - router, - location, - params: routeParams, -}: ICoreLayoutProps) => { +const CoreLayout = ({ children, router, location }: ICoreLayoutProps) => { const { config, currentUser } = useContext(AppContext); const { notification, hideFlash } = useContext(NotificationContext); const { setResetSelectedRows } = useContext(TableContext); @@ -56,7 +49,7 @@ const CoreLayout = ({ router.push(LOGOUT); }; - const onNavItemClick = (path: string) => { + const onUserMenuItemClick = (path: string) => { return (evt: React.MouseEvent) => { evt.preventDefault(); @@ -70,18 +63,6 @@ const CoreLayout = ({ }; }; - const onUndoActionClick = (undoAction?: () => void) => { - return (evt: React.MouseEvent) => { - evt.preventDefault(); - - if (undoAction) { - undoAction(); - } - - hideFlash(); - }; - }; - const fullWidthFlash = !currentUser; if (!currentUser || !config) { @@ -97,7 +78,7 @@ const CoreLayout = ({ currentUser={currentUser} location={location} onLogoutUser={onLogoutUser} - onNavItemClick={onNavItemClick} + onUserMenuItemClick={onUserMenuItemClick} />
@@ -105,7 +86,7 @@ const CoreLayout = ({ fullWidth={fullWidthFlash} notification={notification} onRemoveFlash={hideFlash} - onUndoActionClick={onUndoActionClick} + pathname={location.pathname} /> {children} diff --git a/frontend/pages/AccountPage/AccountPage.tsx b/frontend/pages/AccountPage/AccountPage.tsx index 2042a41ecf..87e7276083 100644 --- a/frontend/pages/AccountPage/AccountPage.tsx +++ b/frontend/pages/AccountPage/AccountPage.tsx @@ -104,12 +104,12 @@ const AccountPage = ({ router }: IAccountPageProps): JSX.Element | null => { } catch (response) { const errorObject = formatErrorResponse(response); setErrors(errorObject); - - if (errorObject.base.includes("already exists")) { - renderFlash("error", "A user with this email address already exists."); - } else { - renderFlash("error", "Could not edit user. Please try again."); - } + renderFlash( + "error", + errorObject.base.includes("already exists") + ? "A user with this email address already exists." + : "Could not edit user. Please try again." + ); setShowEmailModal(false); return false; diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx index ba1caeaf7d..541c3d24d7 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx @@ -108,15 +108,6 @@ const SoftwareCustomPackage = ({ setUploadProgress(Math.max(progress - 0.03, 0.01)); }, }); - renderFlash( - "success", - <> - {formData.software?.name} successfully added. - {formData.selfService - ? " The end user can install from Fleet Desktop." - : ""} - - ); const newQueryParams: QueryParams = { team_id: currentTeamId }; if (formData.selfService) { @@ -127,6 +118,16 @@ const SoftwareCustomPackage = ({ router.push( `${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams(newQueryParams)}` ); + + renderFlash( + "success", + <> + {formData.software?.name} successfully added. + {formData.selfService + ? " The end user can install from Fleet Desktop." + : ""} + + ); } catch (e) { const isTimeout = isAxiosError(e) && diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx index f368e2608e..198c9962c3 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx @@ -136,18 +136,18 @@ const FleetMaintainedAppDetailsPage = ({ ...formData, appId, }); - renderFlash( - "success", - <> - {data?.name} successfully added. - - ); router.push( `${PATHS.SOFTWARE_TITLES}?${buildQueryStringFromParams({ team_id: teamId, available_for_install: true, })}` ); + renderFlash( + "success", + <> + {data?.name} successfully added. + + ); } catch (error) { renderFlash("error", getErrorReason(error)); // TODO: handle error messages } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/RenewAbmModal/RenewAbmModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/RenewAbmModal/RenewAbmModal.tsx index 42e5b8cb20..3a17a5e3d5 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/RenewAbmModal/RenewAbmModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/RenewAbmModal/RenewAbmModal.tsx @@ -39,7 +39,7 @@ const RenewAbmModal = ({ const onRenewToken = useCallback(async () => { if (!tokenFile) { - // this shouldn'r happen, but just in case + // this shouldn't happen, but just in case renderFlash("error", "Please provide a token file."); return; } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/AppleMdmPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/AppleMdmPage.tsx index 4e373f60e0..99c9e78616 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/AppleMdmPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/AppleMdmPage.tsx @@ -65,8 +65,8 @@ const AppleMdmPage = ({ router }: { router: InjectedRouter }) => { toggleTurnOffMdmModal(); try { await mdmAppleAPI.deleteApplePushCertificate(); - renderFlash("success", "MDM turned off successfully."); router.push(PATHS.ADMIN_INTEGRATIONS_MDM); + renderFlash("success", "MDM turned off successfully."); } catch (e) { renderFlash("error", "Couldn't turn off MDM. Please try again."); setIsUpdating(false); diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/components/modals/RenewCertModal/RenewCertModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/components/modals/RenewCertModal/RenewCertModal.tsx index f76d96461e..1bac46d3f0 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/components/modals/RenewCertModal/RenewCertModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/components/modals/RenewCertModal/RenewCertModal.tsx @@ -36,7 +36,7 @@ const RenewCertModal = ({ const onRenewClick = useCallback(async () => { if (!certFile) { - // this shouldn'r happen, but just in case + // this shouldn't happen, but just in case renderFlash("error", "Please provide a certificate file."); return; } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx index e9f76b8e30..405cf3b4a6 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage/WindowsMdmPage.tsx @@ -31,6 +31,7 @@ const useSetWindowsMdm = ({ const { renderFlash } = useContext(NotificationContext); const turnOnWindowsMdm = async () => { + let flashErrMsg = ""; try { const updatedConfig = await configAPI.updateMDMConfig( { @@ -39,18 +40,22 @@ const useSetWindowsMdm = ({ true ); setConfig(updatedConfig); - renderFlash("success", successMessage); } catch (e) { - let msg = errorMessage; if (enable && isAxiosError(e) && e.response?.status === 422) { - msg = + flashErrMsg = getErrorReason(e, { nameEquals: "mdm.windows_enabled_and_configured", - }) || msg; + }) || errorMessage; + } else { + flashErrMsg = errorMessage; } - renderFlash("error", msg); } finally { router.push(PATHS.ADMIN_INTEGRATIONS_MDM); + if (flashErrMsg) { + renderFlash("error", flashErrMsg); + } else { + renderFlash("success", successMessage); + } } }; diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/AgentOptionsPage/AgentOptionsPage.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/AgentOptionsPage/AgentOptionsPage.tsx index 97f1152ccd..da4469416e 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/AgentOptionsPage/AgentOptionsPage.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/AgentOptionsPage/AgentOptionsPage.tsx @@ -115,7 +115,7 @@ const AgentOptionsPage = ({ reason.includes("unsupported key provided") || reason.includes("invalid value type"); - return renderFlash( + renderFlash( "error", <> Couldn't update {teamName} team agent options: diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx index 6c4c8cedd6..49c66aaab7 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx @@ -295,9 +295,8 @@ const TeamDetailsWrapper = ({ try { await teamsAPI.destroy(teamIdForApi); - renderFlash("success", "Team removed"); - // setCurrentTeam(undefined); return router.push(PATHS.ADMIN_TEAMS); + renderFlash("success", "Team removed"); } catch (response) { renderFlash("error", "Something went wrong removing the team"); console.error(response); diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index 7c95b74cfc..341caa4e7f 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -1062,11 +1062,12 @@ const ManageHostsPage = ({ renderFlash("success", "Successfully deleted label."); } catch (error) { console.error(error); - if (getErrorReason(error).includes("built-in")) { - renderFlash("error", "Built-in labels can’t be modified or deleted."); - } else { - renderFlash("error", "Could not delete label. Please try again."); - } + renderFlash( + "error", + getErrorReason(error).includes("built-in") + ? "Built-in labels can’t be modified or deleted." + : "Could not delete label. Please try again." + ); } finally { setIsUpdatingLabel(false); } diff --git a/frontend/pages/labels/NewLabelPage/DynamicLabel/DynamicLabel.tsx b/frontend/pages/labels/NewLabelPage/DynamicLabel/DynamicLabel.tsx index b11deb64e5..c90f1544e7 100644 --- a/frontend/pages/labels/NewLabelPage/DynamicLabel/DynamicLabel.tsx +++ b/frontend/pages/labels/NewLabelPage/DynamicLabel/DynamicLabel.tsx @@ -37,9 +37,12 @@ const DynamicLabel = ({ renderFlash("success", "Label added successfully."); }) .catch((error: { data: IApiError }) => { - if (error.data.errors[0].reason.includes("Duplicate entry")) { - renderFlash("error", DUPLICATE_ENTRY_ERROR); - } else renderFlash("error", "Couldn't add label. Please try again."); + renderFlash( + "error", + error.data.errors[0].reason.includes("Duplicate entry") + ? DUPLICATE_ENTRY_ERROR + : "Couldn't add label. Please try again." + ); }); }, [renderFlash, router] diff --git a/frontend/pages/labels/NewLabelPage/ManualLabel/ManualLabel.tsx b/frontend/pages/labels/NewLabelPage/ManualLabel/ManualLabel.tsx index a1ed3ac2b9..48f405e55e 100644 --- a/frontend/pages/labels/NewLabelPage/ManualLabel/ManualLabel.tsx +++ b/frontend/pages/labels/NewLabelPage/ManualLabel/ManualLabel.tsx @@ -28,9 +28,12 @@ const ManualLabel = ({ router }: IManualLabelProps) => { renderFlash("success", "Label added successfully."); }) .catch((error: { data: IApiError }) => { - if (error.data.errors[0].reason.includes("Duplicate entry")) { - renderFlash("error", DUPLICATE_ENTRY_ERROR); - } else renderFlash("error", "Couldn't add label. Please try again."); + renderFlash( + "error", + error.data.errors[0].reason.includes("Duplicate entry") + ? DUPLICATE_ENTRY_ERROR + : "Couldn't add label. Please try again." + ); }); }, [renderFlash, router] diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 997a3785b4..d929a5f0e1 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -115,7 +115,6 @@ const ManagePolicyPage = ({ currentTeamId, currentTeamName, currentTeamSummary, - isAnyTeamSelected, isAllTeamsSelected, isTeamAdmin, isTeamMaintainer, From 6781d154fb4df68c25d82b9ccc62e2b203b37c1c Mon Sep 17 00:00:00 2001 From: Benjamin Edwards Date: Tue, 22 Oct 2024 14:06:02 -0400 Subject: [PATCH 28/45] only render the policy document if we are creating the bucket (#23085) --- terraform/byo-vpc/byo-db/byo-ecs/iam.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terraform/byo-vpc/byo-db/byo-ecs/iam.tf b/terraform/byo-vpc/byo-db/byo-ecs/iam.tf index a3cc53e830..c29aa1ad29 100644 --- a/terraform/byo-vpc/byo-db/byo-ecs/iam.tf +++ b/terraform/byo-vpc/byo-db/byo-ecs/iam.tf @@ -1,4 +1,5 @@ data "aws_iam_policy_document" "software_installers" { + count = var.fleet_config.software_installers.create_bucket == true ? 1 : 0 statement { actions = [ "s3:GetObject*", @@ -17,7 +18,7 @@ data "aws_iam_policy_document" "software_installers" { resource "aws_iam_policy" "software_installers" { count = var.fleet_config.software_installers.create_bucket == true ? 1 : 0 - policy = data.aws_iam_policy_document.software_installers.json + policy = data.aws_iam_policy_document.software_installers[count.index].json } resource "aws_iam_role_policy_attachment" "software_installers" { From 4ee4243f9faccfa190b750c882d2eb68ce83bb71 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Tue, 22 Oct 2024 14:46:59 -0400 Subject: [PATCH 29/45] feat: add pattern for transaction functions (#23087) > No issue, just a doc update # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Manual QA for all new/changed functionality --- server/docs/patterns.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/server/docs/patterns.md b/server/docs/patterns.md index abe86ca95b..245b55752c 100644 --- a/server/docs/patterns.md +++ b/server/docs/patterns.md @@ -46,3 +46,39 @@ MySQL. [Backend sync where discussed](https://us-65885.app.gong.io/call?id=80410 Sometimes we need data from rows that have been deleted from DB. For example, the activity feed may be retained forever, and it needs user info (or host info) that may not exist anymore. Going forward, we need to keep this data in a dedicated table(s). A reference unmerged PR is [here](https://github.com/fleetdm/fleet/pull/17472/files#diff-57a635e42320a87dd15a3ae03d66834f2cbc4fcdb5f3ebb7075d966b96f760afR16). The `id` may be the same as that of the original table. For example, if the `user` row is deleted, a new entry with the same `user.id` can be added to `user_persistent_info`. + +### Re-usable transactionable functions + +Sometimes we want to encapsulate a piece of functionality in such a way that it can be use both +independently and as part of a transaction. To do so, create a private function in the following way: + +```go +func myTransactionableFunction(ctx context.Context, tx sqlx.ExtContext, yourArgsHere any) error { + // some setup, statements, etc... + + _, err := tx.ExecContext(ctx, stmt, args) + if err != nil { + return ctxerr.Wrap(ctx, err, "doing some stuff in a transaction") + } +} +``` + +You can then use the function as a standalone call, like so + +```go +// *sqlx.DB implements the sqlx.ExtContext interface +err := myTransactionableFunction(ctx, ds.writer(ctx), myArgs) +``` + +or as part of a transaction, like so + +```go +func (ds *Datastore) MyDSMethodWithTransaction(ctx context.Context, yourArgsHere any) error { + return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + return myTransactionableFunction(ctx, tx, yourArgsHere) + }) +} +``` + +See [this commit](https://github.com/fleetdm/fleet/pull/22843/files#diff-c5babdad542a72acf2ec2ecb7cb43967fc53850b6998ac629e253336b87e008bR415) +for an example of this pattern. \ No newline at end of file From 4142ba7723ce264d9cf45a3732b32a0df1a64ed5 Mon Sep 17 00:00:00 2001 From: Rebecca Cowart Date: Tue, 22 Oct 2024 16:40:34 -0400 Subject: [PATCH 30/45] Update AddAbmModal.tsx (#23083) --- .../components/AddAbmModal/AddAbmModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/AddAbmModal/AddAbmModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/AddAbmModal/AddAbmModal.tsx index 6ed19ad10b..41b22e5ae4 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/AddAbmModal/AddAbmModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/AddAbmModal/AddAbmModal.tsx @@ -110,14 +110,14 @@ const AddAbmModal = ({ onCancel, onAdded }: IAddAbmModalProps) => { In the Default Device Assignment section, select{" "} Change, then assign the newly created server as the default - for your Macs, and select Done. + for your Macs, iPhones, and iPads. Then select Done.
  • 8. Select newly created server in the sidebar, then select{" "} - Download Token on the top. + Download MDM Server Token on the top.
  • From 103727b5f4096bbd5461a9150ddc5562dc9f7a35 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Tue, 22 Oct 2024 17:41:32 -0300 Subject: [PATCH 31/45] Use ubuntu 22.04 for trivy ci scheduled check (#23088) Fixing these failures: https://github.com/fleetdm/fleet/actions/runs/11454239765/job/31881900492 Following this advice https://github.com/orgs/community/discussions/139074#discussioncomment-10935090. It seems it works. --- .../build-and-check-fleetctl-docker-and-deps.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-check-fleetctl-docker-and-deps.yml b/.github/workflows/build-and-check-fleetctl-docker-and-deps.yml index ff20260409..1e12e45e8a 100644 --- a/.github/workflows/build-and-check-fleetctl-docker-and-deps.yml +++ b/.github/workflows/build-and-check-fleetctl-docker-and-deps.yml @@ -2,11 +2,6 @@ name: Build fleetctl docker dependencies and check vulnerabilities on: workflow_dispatch: - inputs: - image_tag: - description: "Docker image tag" - required: true - type: string schedule: - cron: "0 6 * * *" @@ -25,7 +20,7 @@ permissions: jobs: build-and-check: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 environment: Docker Hub permissions: contents: write @@ -47,7 +42,7 @@ jobs: - name: Set up Go uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: - go-version-file: 'go.mod' + go-version-file: "go.mod" - name: Install Go Dependencies run: make deps-go From 3c3e6d6d1a314d42c8610be519fc4fea81c84593 Mon Sep 17 00:00:00 2001 From: Marko Lisica <83164494+marko-lisica@users.noreply.github.com> Date: Tue, 22 Oct 2024 22:43:56 +0200 Subject: [PATCH 32/45] Add callout that software-related features are experimental (#23089) --- docs/REST API/rest-api.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index e2fb7c1da7..6dc53f0289 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -7166,6 +7166,8 @@ Team policies work the same as policies, but at the team level. ### Add team policy +> **Experimental feature**. Software related features (like install software policy automation) are undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows. + The semantics for creating a team policy are the same as for global policies, see [Add policy](#add-policy). `POST /api/v1/fleet/teams/:id/policies` @@ -7274,6 +7276,8 @@ Either `query` or `query_id` must be provided. ### Edit team policy +> **Experimental feature**. Software related features (like install software policy automation) are undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows. + `PATCH /api/v1/fleet/teams/:team_id/policies/:policy_id` #### Parameters From 6692ec9cfebdadf55a6cd35ed22310674a72a2d5 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:13:59 -0500 Subject: [PATCH 33/45] Update policy-automation-run-script.md (#23100) Walked through this guide during CS+QA setup and came up w/ some tweaks: - Consistent language - Less language --- articles/policy-automation-run-script.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/articles/policy-automation-run-script.md b/articles/policy-automation-run-script.md index 8501b54313..ed2d1ce82a 100644 --- a/articles/policy-automation-run-script.md +++ b/articles/policy-automation-run-script.md @@ -1,16 +1,14 @@ -# Automatic policy-based execution of scripts on hosts +# Automatically run scripts ![Fleet MDM Cover](../website/assets/images/articles/introducing-cross-platform-script-execution-800x450@2x.png) -Fleet [v4.58.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.58.0) introduces the ability to execute scripts on hosts automatically based on predefined policy failures. This guide will walk you through configuring Fleet to automatically execute scripts on hosts using uploaded scripts based on programmed policies. You'll learn how to configure and use this feature and understand how the underlying mechanism works. +Fleet [v4.58.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.58.0) introduces the ability to execute scripts on hosts automatically based on predefined policy failures. This guide will walk you through configuring Fleet to automatically execute scripts on hosts using uploaded scripts based on programmed policies. -Fleet allows users to upload scripts executed on macOS, Windows, and Linux hosts to remediate issues with those hosts. These scripts can now be automated to run when a policy fails. See [the scripts guide](https://fleetdm.com/guides/scripts) for more information. +Fleet allows users to upload scripts executed on macOS, Windows, and Linux hosts to remediate issues with those hosts. These scripts can now be automated to run when a policy fails. Learn more about scripts [here](https://fleetdm.com/guides/scripts). ## Prerequisites -* Fleet Premium with admin permissions -* Fleet [v4.58.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.58.0) or greater -* [Scripts enabled](https://fleetdm.com/guides/scripts#enable-scripts) on all target hosts +* `fleetd` deployed with the `--enable-scripts` flag. If you're using MDM features, scripts are enabled by default. ## Step-by-step instructions From c1d12529b9083b2630dbc73f2d64989c0316e468 Mon Sep 17 00:00:00 2001 From: Rebecca Cowart Date: Tue, 22 Oct 2024 17:14:31 -0400 Subject: [PATCH 34/45] Update automatic-software-install-in-fleet.md (#23099) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change “Press the top right “Add policy” button.” to “Press the “Add policy” button” # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features. - [ ] Added/updated tests - [ ] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes - [ ] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [ ] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [ ] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [ ] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [ ] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [ ] Orbit runs on macOS, Linux and Windows. Check if the orbit feature/bugfix should only apply to one platform (`runtime.GOOS`). - [ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - [ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)). --- articles/automatic-software-install-in-fleet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/automatic-software-install-in-fleet.md b/articles/automatic-software-install-in-fleet.md index 2c48424ae1..0b392f9cd8 100644 --- a/articles/automatic-software-install-in-fleet.md +++ b/articles/automatic-software-install-in-fleet.md @@ -26,7 +26,7 @@ Current supported software deployment formats: Coming soon: - VPP for iOS and iPadOS -2. **Add a policy**: In Fleet, add a policy that failure to pass will trigger the required installation. Go to Policies tab --> Press the top right "Add policy" button. --> Click "create your own policy" --> Enter your policy SQL --> Save --> Fill in details in the Save modal and Save. +2. **Add a policy**: In Fleet, add a policy that failure to pass will trigger the required installation. Go to Policies tab --> Press the "Add policy" button --> Click "create your own policy" --> Enter your policy SQL --> Save --> Fill in details in the Save modal and Save. ```sql SELECT 1 FROM apps WHERE name = 'Adobe Acrobat Reader.app' AND version_compare(bundle_short_version, '23.001.20687') >= 0; From a2c9e378f2233a2e1ae08f26a1331808c24e9cfc Mon Sep 17 00:00:00 2001 From: Rebecca Cowart Date: Tue, 22 Oct 2024 17:16:13 -0400 Subject: [PATCH 35/45] Update install-fleet-maintained-apps-on-macos-hosts.md (#23098) Updates to last paragraph --- articles/install-fleet-maintained-apps-on-macos-hosts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/install-fleet-maintained-apps-on-macos-hosts.md b/articles/install-fleet-maintained-apps-on-macos-hosts.md index cdef638068..ff8e038ee7 100644 --- a/articles/install-fleet-maintained-apps-on-macos-hosts.md +++ b/articles/install-fleet-maintained-apps-on-macos-hosts.md @@ -52,7 +52,7 @@ The uninstallation process is also visible in the **Activities** section on the ## How does Fleet maintain these apps? -Fleet checks Homebrew Casks every hour for updates to app definitions. When you add an app, Fleet downloads the latest version available. However, Fleet does not automatically update apps. Auto-updating is not supported to ensure greater control over application versions across your fleet. If security or feature updates are critical, we recommend uninstalling and reinstalling the app from the Fleet-maintained list to force an update. +Fleet checks [Homebrew Casks](https://github.com/Homebrew/homebrew-cask) every hour for updates to app definitions. When you add an app, Fleet downloads the latest version available. Currently, Fleet does not automatically update apps. To update the app, remove the app and re-add it from the Fleet-maintained list on the **Software** page, then reinstall it. From d25e6cd067527e15406b1b294c7cdb79d317a1c8 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Tue, 22 Oct 2024 18:22:34 -0400 Subject: [PATCH 36/45] fix: add ios minimum version to os list (#23104) > No issue, just a fix from a customer convo today # Checklist for submitter If some of the following don't apply, delete the relevant line. --- docs/Get started/FAQ.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Get started/FAQ.md b/docs/Get started/FAQ.md index 38b68be9ff..5c7feeacbd 100644 --- a/docs/Get started/FAQ.md +++ b/docs/Get started/FAQ.md @@ -76,6 +76,7 @@ Fleet supports the following operating system versions on hosts. | OS | Supported version(s) | | :------ | :------------------------------------- | | macOS | 13+ (Ventura) | +| iOS | 17+ | | Windows | Pro and Enterprise 10+, Server 2012+ | | Linux | CentOS 7.1+, Ubuntu 20.04+, Fedora 38+ | | ChromeOS | 112.0.5615.134+ | From 101c2adfe78ff59f254630a17601ebbdd189c236 Mon Sep 17 00:00:00 2001 From: Rebecca Cowart Date: Tue, 22 Oct 2024 18:44:39 -0400 Subject: [PATCH 37/45] Update deploy-software-packages.md (#23097) Adjusted prerequisites, moved sections, etc. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features. - [ ] Added/updated tests - [ ] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes - [ ] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [ ] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [ ] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [ ] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [ ] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [ ] Orbit runs on macOS, Linux and Windows. Check if the orbit feature/bugfix should only apply to one platform (`runtime.GOOS`). - [ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - [ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)). --- articles/deploy-software-packages.md | 50 ++++++++++++---------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/articles/deploy-software-packages.md b/articles/deploy-software-packages.md index ab9985ed3c..b203c0b895 100644 --- a/articles/deploy-software-packages.md +++ b/articles/deploy-software-packages.md @@ -6,36 +6,12 @@ Fleet [v4.50.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.50.0) int ## Prerequisites -* Fleet [v4.57.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.57.0). +* `fleetd` deployed with the `--enable-scripts` flag. If you're using MDM features, scripts are enabled by default. -* `fleetd` 1.25.0 deployed via MDM or built with the `--scripts-enabled` flag. - -> `fleetd` prior to 1.33.0 will use a hard-coded uninstall script to clean up from a failed install. As of 1.33.0, the (default or customized) uninstall script will be used to clean up failed installs. - -* An S3 bucket [configured](https://fleetdm.com/docs/configuration/fleet-server-configuration#s-3-software-installers-bucket) to store the installers. - -* Increase any load balancer timeouts to at least 5 minutes for the [Add package](https://fleetdm.com/docs/rest-api/rest-api#add-package) and [Modify package](https://fleetdm.com/docs/rest-api/rest-api#modify-package) endpoints. +* If you're self-hosting Fleet, you need an S3 bucket [configured](https://fleetdm.com/docs/configuration/fleet-server-configuration#s-3-software-installers-bucket) to store the packages. Increase any load balancer timeouts to at least 5 minutes for the [Add package](https://fleetdm.com/docs/rest-api/rest-api#add-package) and [Modify package](https://fleetdm.com/docs/rest-api/rest-api#modify-package) API endpoints. ## Step-by-step instructions -### Access software packages - -To access and manage software in Fleet: - -* **Navigate to the Software page**: Click on the "Software" tab in the main navigation menu. - -* **Select a team**: Click on the dropdown at the top left of the page. - -> Software packages are tied to a specific team. This allows you to, for example, test a newer release of an application within your IT team before rolling it out to the rest of your organization, or deploy the appropriate architecture-specific installer to both Intel and Apple Silicon Macs. - -* **Find your software**: using the filters on the top of the table, you can choose between: - - * “Available for install” filters software that can be installed on your hosts. - - * “Self-service” filters software that end users can install from Fleet Desktop. - -* **Select software package**: Click on a software package to view details and access additional actions for the software. - ### Add a software package to a team * **Navigate to the Software page**: Click on the "Software" tab in the main navigation menu. @@ -110,7 +86,7 @@ After a software package is added to a team, it can be installed on hosts via th * **Edit software package**: From the Actions menu, select "Edit." -> Editing the pre-install query, install script, post-install script, or uninstall script cancels all pending installations and uninstallations for that package, except for installs and uninstalls that are currently running on a host. If a new software package is uploaded, in addition to canceling pending installs and uninstalls, host counts (for installs and pending and failed installs and uninstalls) will be reset to zero, so counts reflect the currently uploaded version of the package. +> Editing the advanced options cancels all pending installations and uninstallations for that package. Installs and uninstalls currently running on a host will complete, but results won't appear in Fleet. The software's host counts will be reset. ### Uninstall a software package on a host @@ -124,7 +100,7 @@ After a software package is installed on a host, it can be uninstalled on the ho * **Find your software package**: Use the dropdown to select software “Available for install” or use the search bar to search for your software package by name. -* **Uninstall the software package from the host**: In the rightmost column of the table, click on “Actions” > “Uninstall.” Uninstallation will happen automatically or when the host comes online. +* **Uninstall the software package from the host**: In the rightmost column of the table, click on “Actions” > “Uninstall.” * **Track uninstallation status**: by either @@ -150,6 +126,24 @@ After a software package is installed on a host, it can be uninstalled on the ho > Removing a software package from a team will cancel pending installs for hosts that are not in the middle of installing the software but will not uninstall the software from hosts where it is already installed. +### Access software packages + +To access and manage software in Fleet: + +* **Navigate to the Software page**: Click on the "Software" tab in the main navigation menu. + +* **Select a team**: Click on the dropdown at the top left of the page. + +> Software packages are tied to a specific team. This allows you to, for example, test a newer release of an application within your IT team before rolling it out to the rest of your organization, or deploy the appropriate architecture-specific installer to both Intel and Apple Silicon Macs. + +* **Find your software**: using the filters on the top of the table, you can choose between: + + * “Available for install” filters software that can be installed on your hosts. + + * “Self-service” filters software that end users can install from Fleet Desktop. + +* **Select software package**: Click on a software package to view details and access additional actions for the software. + ### Manage software with the REST API Fleet also provides a REST API for managing software programmatically. The API allows you to add, update, retrieve, list, and delete software. Detailed documentation on Fleet's [REST API is available]([https://fleetdm.com/docs/rest-api/rest-api#software](https://fleetdm.com/docs/rest-api/rest-api#software)), including endpoints for installing and uninstalling packages. From 111426d62f512ed881bd11959dbe565503a19f2a Mon Sep 17 00:00:00 2001 From: Rebecca Cowart Date: Tue, 22 Oct 2024 18:46:37 -0400 Subject: [PATCH 38/45] Update mdm-migration.md (#23090) Updates for clarity --- articles/mdm-migration.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/articles/mdm-migration.md b/articles/mdm-migration.md index b28f6febd0..a018539110 100644 --- a/articles/mdm-migration.md +++ b/articles/mdm-migration.md @@ -1,6 +1,6 @@ # MDM migration -This guide provides instructions for migrating devices from your current MDM solution to Fleet. +This guide provides instructions for migrating devices from your current MDM solution to Fleet. There are two different workflows to migrate your devices. > For seamless MDM migration, [view this guide](https://fleetdm.com/guides/seamless-mdm-migration). @@ -8,6 +8,7 @@ This guide provides instructions for migrating devices from your current MDM sol - A [deployed Fleet instance](https://fleetdm.com/docs/deploy/deploy-fleet) - Fleet is connected to Apple Push Notification service (APNs) and Apple Business Manager (ABM). [See macOS MDM setup](https://fleetdm.com/guides/macos-mdm-setup) +- For the end-user workflow: A service is required that can receive a webhook to send an unenroll request to the existing MDM server. See [this example](https://victoronsoftware.com/posts/webhook-flow-with-tines/) using Fleet webhooks with Tines. ## Migrate hosts @@ -40,7 +41,7 @@ The end user migration workflow allows the user to kick off migration by unenrol End user experience: -- After a host is unenrolled from your current MDM solution, the end user will be prompted with Apple's **Remote Management** full-screen popup if the host is assigned to Fleet in ABM. +- After a host is unenrolled from your current MDM solution, eventually (within two hours) the end user will be prompted with Apple's **Remote Management** full-screen popup if the host is assigned to Fleet in ABM. macOS Remote Management popup - If the host is not assigned to Fleet in ABM (manual enrollment), the end user will be given the option to download the MDM enrollment profile on their **My device page**. Fleet icon in menu bar @@ -64,14 +65,16 @@ Configuration: - After configuring the end user workflow, instruct your end users to select the Fleet icon in their menu bar, select **Migrate to Fleet** and follow the on-screen instructions to migrate to Fleet. -- Fleet UI: +Fleet UI: 1. Select the avatar on the right side of the top navigation and select **Settings > Integrations > Mobile device management (MDM)**. 2. Scroll down to the **End user migration workflow** section and select the toggle to enable the workflow. 3. Under **Mode**, choose a mode, enter the webhook URL for your automation tool (e.g., Tines) under **Webhook URL**, and select **Save**. 4. During the end user migration workflow, an end user's device will have its selected system theme (light or dark) applied. If your logo is not easy to see on both light and dark backgrounds, you can optionally set a logo for each theme: Head to **Settings** > **Organization settings** > **Organization info**, add URLs to your logos in the **Organization avatar URL (for dark backgrounds)** and **Organization avatar URL (for light backgrounds)** fields, and select **Save**. -- Fleet API: API documentation is [here](https://fleetdm.com/docs/rest-api/rest-api#mdm-macos-migration) -- GitOps: + +Fleet API: API documentation is [here](https://fleetdm.com/docs/rest-api/rest-api#mdm-macos-migration) + +GitOps: - To manage macOS MDM migration configuration using Fleet's best practice GitOps, check out the `macos_migration` key in the [GitOps reference documentation](https://fleetdm.com/docs/configuration/yaml-files#macos-migration). - To manage your organization's logo for dark and light backgrounds using Fleet's best practice GitOps, check out the `org_info` key in the [GitOps reference documentation](https://fleetdm.com/docs/configuration/yaml-files#org-info). From 98f2d15c59aedff659dc2602d3109729b52980d8 Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Tue, 22 Oct 2024 17:51:11 -0500 Subject: [PATCH 39/45] Provide better error messages on 404s for install or uninstall results (#23106) #22965 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Manual QA for all new/changed functionality --- changes/22965-error-cleanup | 1 + .../SoftwareInstallDetails.tsx | 13 +++++++-- .../SoftwareUninstallDetailsModal.tsx | 27 ++++++++++++++----- 3 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 changes/22965-error-cleanup diff --git a/changes/22965-error-cleanup b/changes/22965-error-cleanup new file mode 100644 index 0000000000..40c23de691 --- /dev/null +++ b/changes/22965-error-cleanup @@ -0,0 +1 @@ +* Added a more descriptive error message when install or uninstall details do not exist for an activity diff --git a/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx b/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx index e3747f952a..91d13ad1ed 100644 --- a/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx +++ b/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails.tsx @@ -1,6 +1,7 @@ import React from "react"; import { useQuery } from "react-query"; import { formatDistanceToNow } from "date-fns"; +import { AxiosError } from "axios"; import { IActivityDetails } from "interfaces/activity"; import { @@ -90,9 +91,9 @@ export const SoftwareInstallDetails = ({ host_display_name = "", install_uuid = "", }: IPackageInstallDetails) => { - const { data: result, isLoading, isError } = useQuery< + const { data: result, isLoading, isError, error } = useQuery< ISoftwareInstallResults, - Error, + AxiosError, ISoftwareInstallResult >( ["softwareInstallResults", install_uuid], @@ -103,11 +104,19 @@ export const SoftwareInstallDetails = ({ refetchOnWindowFocus: false, staleTime: 3000, select: (data) => data.results, + retry: (failureCount, err) => err?.status !== 404 && failureCount < 3, } ); if (isLoading) { return ; + } else if (isError && error?.status === 404) { + return ( + + ); } else if (isError) { return ; } else if (!result) { diff --git a/frontend/components/ActivityDetails/InstallDetails/SoftwareUninstallDetailsModal/SoftwareUninstallDetailsModal.tsx b/frontend/components/ActivityDetails/InstallDetails/SoftwareUninstallDetailsModal/SoftwareUninstallDetailsModal.tsx index dfb4ef13c9..e0838b0717 100644 --- a/frontend/components/ActivityDetails/InstallDetails/SoftwareUninstallDetailsModal/SoftwareUninstallDetailsModal.tsx +++ b/frontend/components/ActivityDetails/InstallDetails/SoftwareUninstallDetailsModal/SoftwareUninstallDetailsModal.tsx @@ -9,6 +9,7 @@ import { IActivityDetails } from "interfaces/activity"; import { isPendingStatus, SoftwareInstallStatus } from "interfaces/software"; import React from "react"; import { useQuery } from "react-query"; +import { AxiosError } from "axios"; import scriptsAPI, { IScriptResultResponse } from "services/entities/scripts"; import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; import { @@ -55,7 +56,10 @@ const StatusMessage = ({ return (
    Fleet {getInstallDetailsStatusPredicate(status)} {software_title}{" "} @@ -80,20 +84,29 @@ const SoftwareUninstallDetailsModal = ({ software_title = "", status = "", }: ISoftwareUninstallDetails) => { - const { - data: scriptResult, - isLoading, - isError, - } = useQuery( + const { data: scriptResult, isLoading, isError, error } = useQuery< + IScriptResultResponse, + AxiosError + >( ["uninstallResult", details.script_execution_id], () => { return scriptsAPI.getScriptResult(script_execution_id); }, - { ...DEFAULT_USE_QUERY_OPTIONS } + { + ...DEFAULT_USE_QUERY_OPTIONS, + retry: (failureCount, err) => err?.status !== 404 && failureCount < 3, + } ); if (isLoading) { return ; + } else if (isError && error?.status === 404) { + return ( + + ); } else if (isError) { return ; } else if (!scriptResult) { From d9fabdb698e92e848fe6229c0536ca8dbd09882d Mon Sep 17 00:00:00 2001 From: Rebecca Cowart Date: Tue, 22 Oct 2024 18:58:00 -0400 Subject: [PATCH 40/45] Update macos-mdm-setup.md (#23081) Added step on line 15 concerning pressing a "Turn on" button (two different "Turn on" buttons are on screen) --- articles/macos-mdm-setup.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/articles/macos-mdm-setup.md b/articles/macos-mdm-setup.md index 0f2e7c5eb3..1eabefa809 100644 --- a/articles/macos-mdm-setup.md +++ b/articles/macos-mdm-setup.md @@ -6,24 +6,28 @@ To use automatic enrollment (aka zero-touch) features on macOS, iOS, and iPadOS, To turn on Windows MDM features, head to this [Windows MDM setup article](https://fleetdm.com/guides/windows-mdm-setup). -## Apple Push Notification service (APNs) +## Turn on Apple MDM Apple uses APNs to authenticate and manage interactions between Fleet and hosts. -To connect Fleet to APNs or renew APNs, head to the **Settings > Integrations > Mobile device management (MDM)** page. +To connect Fleet to APNs or renew APNs, head to the **Settings > Integrations > Mobile device management (MDM)** page. + +Then click **Turn on** under the Apple (macOS, iOS, iPadOS) MDM section. > Apple requires that APNs certificates are renewed annually. > - If your certificate expires, you will have to turn MDM off and back on for all macOS hosts. > - Be sure to use the same Apple ID from year-to-year. If you don't, you will have to turn MDM off and back on for all macOS hosts. -## Apple Business Manager (ABM) +## Automatic enrollment > Available in Fleet Premium +Add your ABM to automatically enroll newly purchased Apple hosts when they're first unboxed and set up by your end users. + To connect Fleet to ABM, you have to add an ABM token to Fleet. To add an ABM token: 1. Navigate to the **Settings > Integrations > Mobile device management (MDM)** page. -2. Under "Automatic enrollment", click "Add ABM", and then click "Add ABM" again on the next page. Follow the instructions in the modal and upload an ABM token to Fleet. +2. Under "Automatic enrollment", click "Add ABM", and then follow the instructions in the modal to upload an ABM token to Fleet. When one of your uploaded ABM tokens has expired or is within 30 days of expiring, you will see a warning banner at the top of page reminding you to renew your token. @@ -44,9 +48,10 @@ macOS, iOS, and iPadOS hosts listed in ABM and associated to a Fleet instance wi Hosts that automatically enroll will be assigned to a default team. You can configure the default team for macOS, iOS, and iPadOS hosts by: -1. Navigating to the **Settings > Integrations > Mobile device management (MDM)** page and clicking "Edit" under "Automatic enrollment". -2. Click on the "Actions" dropdown for the ABM token you want to update, and then click "Edit teams". -3. Use the dropdowns in the modal to select the default team for each type of host, and click "Save" to save your selections. +1. Creating teams, if you have not already, following [this guide](https://fleetdm.com/guides/teams#basic-article). Our [best practice](#best-practice) recommendation is to have a team for each device type. +2. Navigating to the **Settings > Integrations > Mobile device management (MDM)** page and clicking "Edit" under "Automatic enrollment". +3. Clicking on the "Actions" dropdown for the ABM token you want to update, and then clicking "Edit teams". +4. Using the dropdowns in the modal to select the default team for each type of host, and clicking "Save" to save your selections. > If no default team is set for a host platform (macOS, iOS, or iPadOS), then newly enrolled hosts of that platform will be placed in "No team". From 46e0549ed89d6f2a0b0229574935491716e6fc81 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 22 Oct 2024 18:18:46 -0500 Subject: [PATCH 41/45] Website: add CTA back to device-management page (#23105) Closes: #23028 Changes: - Added a CTA to the Hero section of the device management page --- website/assets/styles/pages/device-management.less | 4 ++-- website/views/pages/device-management.ejs | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/website/assets/styles/pages/device-management.less b/website/assets/styles/pages/device-management.less index 10557dc619..0e58eb4ec9 100644 --- a/website/assets/styles/pages/device-management.less +++ b/website/assets/styles/pages/device-management.less @@ -539,10 +539,10 @@ } [purpose='cta-button'] { cursor: pointer; - margin-right: 32px; + margin-right: 24px; background: @core-vibrant-red; border-radius: 8px; - padding: 16px 32px; + padding: 16px; height: 36px; display: flex; justify-content: center; diff --git a/website/views/pages/device-management.ejs b/website/views/pages/device-management.ejs index 6ef974bcef..b3ad70032e 100644 --- a/website/views/pages/device-management.ejs +++ b/website/views/pages/device-management.ejs @@ -28,6 +28,10 @@
  • +
    + Talk to us + Try it yourself +
    @@ -511,8 +515,8 @@

    Device management (MDM)

    Your easiest MDM migration

    - Start now - Talk to us + Talk to us + Try it yourself
    From 5309f8fe536db3a08983e6fee0d88d50ce4c6aa6 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 22 Oct 2024 18:25:34 -0500 Subject: [PATCH 42/45] Website: add swag request CTA to /better (#23108) Closes: https://github.com/fleetdm/confidential/issues/8439 Changes: - Added a swag request CTA to the /better page that is visible to users visiting the website from the contiguous US. --- website/api/controllers/view-transparency.js | 11 +++++++- .../images/icon-fleet-tumbler-55x51@2x.png | Bin 0 -> 3638 bytes website/assets/styles/pages/transparency.less | 24 ++++++++++++++++++ website/views/pages/transparency.ejs | 13 +++++++++- 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 website/assets/images/icon-fleet-tumbler-55x51@2x.png diff --git a/website/api/controllers/view-transparency.js b/website/api/controllers/view-transparency.js index 7267792026..4aa3a2b69f 100644 --- a/website/api/controllers/view-transparency.js +++ b/website/api/controllers/view-transparency.js @@ -16,8 +16,17 @@ module.exports = { fn: async function () { + + let showSwagForm = false; + // Due to shipping costs, we'll check the requesting user's cf-ipcountry to see if they're in the US, and their cf-iplongitude header to see if they're in the contiguous US. + if(this.req.get('cf-ipcountry') === 'US' && this.req.get('cf-iplongitude') > -125) { + showSwagForm = true; + } // Respond with view. - return {showSecureframeBanner: this.req.param('utm_content') === 'secureframe'}; + return { + showSecureframeBanner: this.req.param('utm_content') === 'secureframe', + showSwagForm, + }; } diff --git a/website/assets/images/icon-fleet-tumbler-55x51@2x.png b/website/assets/images/icon-fleet-tumbler-55x51@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d027315cc6d9032f3953d5ca1747812e32e2f9bf GIT binary patch literal 3638 zcmV-64$1L}P)o5Z7q~h*I_`==6$1nW0Y#G`*+&5a zxzCdD&pXz4LKPhs2*S74+!Cfh)~sq?3HRK2GvMQb1wJ@->?EXQIe62W+u-kS?AilW zR3s1tR@40Wc*%<8>b}=oql$OO+MA%3mDw5LCnDQJP(?);;TSReb7b(C;o!2R4e^HaI44Y{@U^8}5DP!6?ZPD? z)lLYB5|Z|5^AY@P_!(s&9bgfT=5bph+lJXCsAlhq%U3`pbNJ&U5NAFZNrBw>i;ngf zScvUD&Ir+c)tZ}DLnX8Qg}*|4C@q}J1|KX^I#AJg)m$?T0r&hB?(s-ZKr7eRFN8{` z)*xK9##Ic)8b{}^a4x9PKb+U=fhxt}m~aFXT{zy%%mOQA5zinl@;kXY}4k|{5m2kC&_6lE$U0*jzZUibiuf>iZ!l|l_C zoJ6SQU+_6i<=amU_p`506P9VF1MThK467y90v^5grwuJo#S!NAaSC*@gsUmDcc0pq zKr9KWhj>6-#K_H4u=K5PbfQ#$N5%`j-bn zE;sb{wl*3%Wvf0;AW|u4qXc@^y*#iP_t)ub+w7#{5+$J08tGx8OW3V>YW;%#PbHZd?D#z>PE=6h?R4z9O9OysD`b z4AneRb(rD-wgok_nN3x=GBKSQZ&C{P{&^tGna5_$Y~HGW8T=pG2s*-F?C!rV{~jBie)l2 z4n3bd0skF4pzg0;x(04J|30X58yl=Y8<~b32T#F=Lvc8}&JDL+?uQ!!xsBSVqHn>_ z>;D90Dh_p*Ho|%9egK}u;&z5qtf}1cK?n4odQ%k;w|^XcMHQ0g#h2}h*YF-MqQ>zPRV397Sc_xA zr63j4%A<44&dJB}x=V6=8FIGw9Cg!JbzWWG*I(#24o>ncX~=ut0_N*Pr6Ow_igxEk z8*+|q`0_6e$56O$Us`WCb`cW|0~8)L5Lse8cJ|HRP=gdvsn~P?qsYtZo8Z9kn^5Ok z2u%%N&sz)EUA0&h5CaqxEnh9f=<$;9COG}xhmbrq47KNP(}v9DtJ@L2VPd;sE;E+Mzm!zu#vWyq3DxCGJLdiTon)T)=Th}I~J;s zJj(m8f&8D>^nKcR9k^C8^XjRCN~U5><)K$6bkUH+n4%uzp@Z)kL_?H;eIS3-2jzy( z<7&Zg`8;=rg`0`Kipk~BG@?-1U@hd2D<}S!_w|&CXC)mSt@B+tFy-Ui-r1+FyJ`xU z*1li~l#_(<+HF}?!cpNUD5F|AQG{@HOI=V?pL^*ID?H$?oe@sD;L5zOmoB~z$_an% z#0lX-{XzeO?*yB01Di<{8C9-ifd~qyQGbCOE^P4`&LPV#1NRL-29l{^gast7udW5D zvAn|Z5hW$oC*O!3RX(sUxbgH9gxOa{jq)DY8;nUVX#L~0(F0%|F|AOna2P#avHmPQ zK$*>ak_)Z^H{0mM=KwW6RlNCtTI25D8*F2CtBVjgz-~i3rJydXqA1Y!(B5DGEQ6Gk z#~~=#Ts}|#PpsBiB^n=C5uh=!0ky_S6gE-m~ZQ5 zzHKw}b(>u-CKOXFMW6@A^2h|bz#?Rr9&u+@7fuvmDvv1%gJ>Ghv18w@Sqck@y)Ajh zbil@_lKHmq18)WWOu(=rGk=B#l!-=IkkY$aHJO%Rwrl68l7C3SI8DK6w%IM5VN7Ki z^0?v9;{ioc`dQJ>9@S$^n;vE|d({6ypXM|W;Zw4j z7iAAoun{vo1m{X#_)g6-czybW;rRWv%Wbc12=C)O;EzFxG<1bg{1BG0_MvbrT-~|O+aW8$G$H+UASV%kl1!+{+Q%!uQ{vG&SO zwe?QX!K8)7HW32{|s(@NIG&HQ2?HEs~A;|DNzI!KadQdntmz09g@HNirABo@*LO7O$ zAJodDT#ZA~L=IAhxM0ef5RF_n6a(@kaO$`T0>7ORu*xe!D+qNfB}lqO|{k;o}= zYaQ?3>`Vm^K}==tpdpF-_12`IEn($Y+r%tF2$zJEUgJ(=zQu$axFu?kVytnJmBScw z5JI@xd_IoMh=-zy=*#17$(ze@=S(NWYj}1lheHIKW;NsIj%RL&l-21p z%c>TRfNhjc<#32;;ig}Dcw_R#hxH}-reAolDe=-zcF|oSQ|l=4xt!K;D`gE(#t2iC zW-#O6CSt39L(F%iDwHt09%kYJRg$l=x9THGNp?P_71CWLE7bPNY~rleEm&v_UYvj* zcAoCVw-?hjuIBFFcd-z-lWk!XjX9(d51}ojwab)v{AxUUE|=%((9ekws5sRRr~gMN z0Rr;wOu^6IX*ap+nHZRa@0Zh30zQ}H!9d07ZlAF-P4uAsAwOE9;|T_4ouQ1H5HH) z*JkxcQzTD|@0zV)->R))D#m_XSgmEz-&LQ;Zka8Qxm`JFK^Dc(#{ zN}6O!_xPH-V%jlZGrM=`iKW3p-b1i;P6!vEq;Jk_0SXT`Lfa{xVMv~tR6wF5l0+`b zIgZQL7uY={lyH)v%`$BR-317T*eTu#V+U&S$v6ID3WcUbe>#}Ms50KaA;O9QN|kMe zyoFH0O@kcO%6czB5m$)fO-@=Lu$0;>P4#5FN6XVvV3dXf5%{4C9UA;9#C~0NVcjLxHZ1G+FbX%dX z7iytVObMQhY>(0O6lPVt+NF!te6E;Ya1;$w1z%lpq8G{uuEv!zt;&1#R{dw*ibcRh z*ho$bikX|QrV8WfR+e%yloe6hHLWTe0fn21(zGDJU4mVR$J0Sex%{4Cu{Z*bh=vF! zT;730uF`q~FlZ^4-{T`n_GLH-4k4Uy`5K!MSBc&jMH@d|dd~tMQA##XyXFy2IKHu2 zi!2d$nSzW&JT0Kv+Gm`oUp19FvyDIT6u#>387U=j;jMrh7TVtPJ@dZ>gaX~eyjlRu zc%zoFlmLBPz2!fPJ~yI7l!$;)B{CDB3=SR#Q8>=kHYbKF@CYy5`yYG^@AiKPPIF}N z7`(ObJ?;oGl{l9RhlkwE&XbYn`#@*oCR(%IgI}vusp2$W0DD*wkDA4P6#xJL07*qo IM6N<$f+v~EMgRZ+ literal 0 HcmV?d00001 diff --git a/website/assets/styles/pages/transparency.less b/website/assets/styles/pages/transparency.less index 89283f74b1..68d561ac88 100644 --- a/website/assets/styles/pages/transparency.less +++ b/website/assets/styles/pages/transparency.less @@ -83,6 +83,30 @@ line-height: @text-lineheight; } } + [purpose='swag-cta'] { + display: flex; + width: 263px; + // padding: 16px 24px; + align-items: center; + text-decoration: none; + border-radius: 6px; + border: 1px solid var(--UI-Gray, #E3E3E3); + background: #FFF; + box-shadow: 1px 2px 2px 0px rgba(197, 199, 209, 0.20); + font-size: 14px; + [purpose='swag-link'] { + padding: 16px 24px; + display: block; + } + strong { + font-weight: 800; + } + img { + width: 55px; + height: 51px; + margin-right: 24px; + } + } [purpose='feature'] { padding-top: 64px; padding-bottom: 64px; diff --git a/website/views/pages/transparency.ejs b/website/views/pages/transparency.ejs index 44f9b2854e..1a85af36ca 100644 --- a/website/views/pages/transparency.ejs +++ b/website/views/pages/transparency.ejs @@ -9,7 +9,18 @@
    Information about a users device in Fleet. From 4f485cc07cc89074b64d11b25b560f37496e22db Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Tue, 22 Oct 2024 20:25:47 -0500 Subject: [PATCH 43/45] Change DRI for security policies (#23110) --- .../digital-experience/security-policies.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/handbook/digital-experience/security-policies.md b/handbook/digital-experience/security-policies.md index 842e67a44d..6842c1bd1b 100644 --- a/handbook/digital-experience/security-policies.md +++ b/handbook/digital-experience/security-policies.md @@ -25,7 +25,7 @@ All Fleet employees and long-term collaborators are expected to read and electro | Policy owner | Effective date | | -------------- | -------------- | -| @Jostableford | 2024-03-14 | +| @sampfluger88 | 2024-03-14 | Fleet requires all team members to comply with the following acceptable use requirements and procedures: @@ -60,7 +60,7 @@ When in doubt, **ASK!** (in [#g-security](https://fleetdm.slack.com/archives/C03 | Policy owner | Effective date | | -------------- | -------------- | -| @Jostableford | 2024-03-14 | +| @sampfluger88 | 2024-03-14 | Fleet requires all workforce members to comply with the following acceptable use requirements and procedures, such that: @@ -117,7 +117,7 @@ Fleet policy requires that: | Policy owner | Effective date | | -------------- | -------------- | -| @Jostableford | 2024-03-14 | +| @sampfluger88 | 2024-03-14 | You can't protect what you can't see. Therefore, Fleet must maintain an accurate and up-to-date inventory of its physical and digital assets. @@ -134,7 +134,7 @@ Fleet policy requires that: | Policy owner | Effective date | | -------------- | -------------- | -| @Jostableford | 2024-03-14 | +| @sampfluger88 | 2024-03-14 | The Fleet business continuity and disaster recovery plan establishes procedures to recover Fleet following a disruption resulting from a disaster. @@ -335,7 +335,7 @@ This process is followed when offboarding a customer and deleting all of the pro | Policy owner | Effective date | | -------------- | -------------- | -| @Jostableford | 2024-03-14 | +| @sampfluger88 | 2024-03-14 | Fleet requires all workforce members to comply with the encryption policy, such that: @@ -599,7 +599,7 @@ incident response plan annually. | Policy owner | Effective date | | -------------- | -------------- | -| @Jostableford | 2024-03-14 | +| @sampfluger88 | 2024-03-14 | Fleet Device Management is committed to conducting business in compliance with all applicable laws, regulations, and company policies. Fleet has adopted this policy to outline the security measures required to protect electronic information systems and related equipment from unauthorized use. @@ -692,7 +692,7 @@ How we implement: | Policy owner | Effective date | | -------------- | -------------- | -| @Jostableford | 2024-03-14 | +| @sampfluger88 | 2024-03-14 | Fleet policy requires @@ -709,7 +709,7 @@ Fleet policy requires | Policy owner | Effective date | | -------------- | -------------- | -| @Jostableford | 2024-03-14 | +| @sampfluger88 | 2024-03-14 | Fleet policy requires: @@ -756,7 +756,7 @@ Fleet policy requires that: | Policy owner | Effective date | | -------------- | -------------- | -| @Jostableford | 2024-03-14 | +| @sampfluger88 | 2024-03-14 | Fleet policy requires that: - Fleet policies must be developed and maintained to meet all applicable compliance requirements and adhere to security best practices, including but not limited to: @@ -789,5 +789,5 @@ Fleet makes every effort to assure all third-party organizations are compliant a ## Anti-corruption policy > Fleet is committed to ethical business practices and compliance with the law. All Fleeties are required to comply with the "Foreign Corrup Practices Act" and anti-bribery laws and regulations in applicable jurisdictions including, but not limited to, the "UK Bribery Act 2010", "European Commission on Anti-Corruption" and others. The policies set forth in [this document](https://docs.google.com/document/d/16iHhLhAV0GS2mBrDKIBaIRe_pmXJrA1y7-gTWNxSR6c/edit?usp=sharing) go over Fleet's anti-corruption policy in detail. - - \ No newline at end of file + + From 59692fd4da8cf19b7af5432b25c6d9229267af16 Mon Sep 17 00:00:00 2001 From: Drew Baker <89049099+Drew-P-drawers@users.noreply.github.com> Date: Wed, 23 Oct 2024 02:43:36 -0400 Subject: [PATCH 44/45] Drew p drawers events (#23055) Updating DRI for events --------- Co-authored-by: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> --- handbook/demand/README.md | 19 +++++++------------ handbook/demand/demand.rituals.yml | 19 +++++++++++-------- handbook/engineering/engineering.rituals.yml | 1 - 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/handbook/demand/README.md b/handbook/demand/README.md index b96bbacaee..21751984a6 100644 --- a/handbook/demand/README.md +++ b/handbook/demand/README.md @@ -112,7 +112,7 @@ The Head of Marketing is the DRI for deploying Fleet's outward-facing content. T ### Settle event strategy -The Head of Marketing is the DRI for deploying Fleet's event budget, and events are settled significantly in advance to provide ample time for strategy and planning. Fleet's [Client Platform Engineer & Community Advocate](https://fleetdm.com/handbook/engineering#team) is the DRI for executing Fleet events efficiently, on-brand, and on-strategy. + Fleet's [Head of Marketing](https://fleetdm.com/handbook/demand#team) is the DRI for executing Fleet events efficiently, on-brand, and on-strategy within Fleet's event budget. Events are settled significantly in advance to provide ample time for strategy and planning. 1. Using the [event strategy workbook](https://docs.google.com/spreadsheets/d/1YQXAX2Q_WnGkAwMYjMbQpV3nbCj7gOBbv7Y0u4twxzQ/edit#gid=1411322737), propose the events that Fleet will attend in the next 6 months, and the strategy for those events, including: - target buying situation of the audience @@ -129,7 +129,7 @@ The Head of Marketing is the DRI for deploying Fleet's event budget, and events ### Book an event -Fleet's Client Platform Engineer & Community Advocate is responsible for booking events that Fleet has chosen to attend and/or sponsor. To book an event, complete the steps in each event issue. Contact the [🫧 Digital Marketing Manager](https://fleetdm.com/handbook/demand#team) as needed with any questions or blockers to booking an event. +Fleet's Head of Marketing is responsible for booking events that Fleet has chosen to attend and/or sponsor. To book an event, complete the steps in each event issue. Contact the [🫧 Head of Marketing](https://fleetdm.com/handbook/demand#team) as needed with any questions or blockers to booking an event. > Note: The Demand department [settles all event strategy](https://fleetdm.com/handbook/demand#settle-event-strategy) prior to booking an event. @@ -150,16 +150,11 @@ Check the "📃 Planned articles" column in [#g-demand board](https://app.zenhub **To order T-shirts:** - - Check [Postal](https://app.postal.io/items/postals) first and see if the warehouse has enough shirts. - - Navigate to the [approved items page](https://app.postal.io/items/postals). - - Hover over the shirt design and click on the airplane. - - Click bulk send and choose one shirt size and the expected quantity of that particular shirt size. - - Make sure the address matches the expected receiving address. - - If the Postal warehouse can't fulfill the order or To order swag quickly: - - Login to [https://www.rushordertees.com/my-account/login/) (saved in 1Password). - - Choose Fleet logo design t-shirt under [my designs](https://www.rushordertees.com/my-account/designs/). - - Order shirts based on the pre-determined number (~5% of total event attendees). - - Submit the order. Ensure the address matches the expected receiving address. + - Login to [https://www.rushordertees.com/my-account/login/) (saved in 1Password). + - Choose Fleet logo design t-shirt under [my designs](https://www.rushordertees.com/my-account/designs/). + - Order shirts based on the pre-determined number (~5% of total event attendees). + - Submit the order. Ensure the address matches the expected receiving address. + **To order stickers:** diff --git a/handbook/demand/demand.rituals.yml b/handbook/demand/demand.rituals.yml index 7d4e3c0104..d235607fd6 100644 --- a/handbook/demand/demand.rituals.yml +++ b/handbook/demand/demand.rituals.yml @@ -73,15 +73,18 @@ dri: "Drew-P-drawers" - task: "Check ongoing events" - startedOn: "2024-02-09" + + startedOn: "2024-10-21" + frequency: "Daily" description: "Check event issues and complete steps." moreInfoUrl: "https://fleetdm.com/handbook/engineering#book-an-event" dri: "Drew-P-drawers" -# - -# task: "Propose a fleet event" -# startedOn: "2023-10-02" -# frequency: "Daily" -# description: "Populate ideas for future events" -# moreInfoUrl: "https://fleetdm.com/handbook/demand#settle-event-strategy" -# dri: "Drew-P-drawers" +- + task: "Propose a Fleet event" + startedOn: "2023-10-21" + frequency: "Weekly" + description: "Populate ideas for future events" + moreInfoUrl: "https://fleetdm.com/handbook/demand#settle-event-strategy" + dri: "Drew-P-drawers" + diff --git a/handbook/engineering/engineering.rituals.yml b/handbook/engineering/engineering.rituals.yml index b82f36b5ca..4b30db5e86 100644 --- a/handbook/engineering/engineering.rituals.yml +++ b/handbook/engineering/engineering.rituals.yml @@ -101,4 +101,3 @@ - From 3e39be0604974aa6d1b63bbfed701b0e7c25b5a3 Mon Sep 17 00:00:00 2001 From: Sam Pfluger <108141731+Sampfluger88@users.noreply.github.com> Date: Wed, 23 Oct 2024 02:08:04 -0500 Subject: [PATCH 45/45] Add "Change a contact's organization in Salesforce" (#23112) --- handbook/sales/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/handbook/sales/README.md b/handbook/sales/README.md index be6df24ea6..e06cc96e64 100644 --- a/handbook/sales/README.md +++ b/handbook/sales/README.md @@ -32,6 +32,12 @@ We often hear objections to using Fleet that are important to track, understand, 2. Copy the template at the top of the page and paste it at the top of the "Objections" section completing all TODOs. +### Change a contact's organization in Salesforce + +Use the following steps to change a contact's organization in Salesforce: +- If the contact's organization in Salesforce is incorrect but their new organization is unknown, navigate to the contact in Salesforce and change the "Account name" to "?" and save. +- If the contact's organization in Salesforce is incorrect and we know where they're moving to, navigate to the contact in Salesforce, change the "Account name" to the contact's new organization, and save. + ### Onboard a new sales team member Once the standard Fleetie onboarding issue is complete, create a new ["Sales team onboarding"](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-sales&projects=&template=sales-team-onboarding.md&title=Sales%20onboarding%3A_____________) issue and complete it.