From fc10afe147f7e629c61dfbb6ed3e5dd38bd7d2bb Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com> Date: Fri, 22 May 2026 18:37:57 -0500 Subject: [PATCH] Fixed the "host is offline" banner on the My device page incorrectly appearing (#46091) --- ...91-suppress-offline-banner-recent-enrollment | 1 + .../details/DeviceUserPage/DeviceUserPage.tsx | 15 +++++++++++++-- .../hosts/details/DeviceUserPage/helpers.ts | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 changes/45091-suppress-offline-banner-recent-enrollment diff --git a/changes/45091-suppress-offline-banner-recent-enrollment b/changes/45091-suppress-offline-banner-recent-enrollment new file mode 100644 index 0000000000..225f78256a --- /dev/null +++ b/changes/45091-suppress-offline-banner-recent-enrollment @@ -0,0 +1 @@ +* Fixed the "host is offline" banner on the My device page incorrectly appearing during the first few minutes after an enrollment. diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx index 86282762fb..bcbefd0127 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx +++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx @@ -67,6 +67,7 @@ import { isSoftwareScriptSetup, isIPhone, isIPad, + isRecentlyEnrolled, } from "./helpers"; import PolicyDetailsModal from "../cards/Policies/HostPoliciesTable/PolicyDetailsModal"; @@ -295,10 +296,16 @@ const DeviceUserPage = ({ if (!refetchStartTime) { // Here and below: iOS/iPadOS refetches use MDM commands which can be slower/less predictable // than osquery. Don't show an error, just reset and let the user try again. + // Recently enrolled hosts are also exempted: orbit endpoints don't update host_seen_times, + // so a fresh host can read as offline until its first osquery distributed-read. const isIOSOrIPadOS = responseHost.platform === "ios" || responseHost.platform === "ipados"; - if (responseHost.status === "online" || isIOSOrIPadOS) { + if ( + responseHost.status === "online" || + isIOSOrIPadOS || + isRecentlyEnrolled(responseHost.last_enrolled_at) + ) { setRefetchStartTime(Date.now()); setTimeout(() => { refetchDupDetails(); @@ -317,7 +324,11 @@ const DeviceUserPage = ({ const isIOSOrIPadOS = responseHost.platform === "ios" || responseHost.platform === "ipados"; - if (responseHost.status === "online" || isIOSOrIPadOS) { + if ( + responseHost.status === "online" || + isIOSOrIPadOS || + isRecentlyEnrolled(responseHost.last_enrolled_at) + ) { setTimeout(() => { refetchDupDetails(); refetchExtensions(); diff --git a/frontend/pages/hosts/details/DeviceUserPage/helpers.ts b/frontend/pages/hosts/details/DeviceUserPage/helpers.ts index 2692b0e235..289cba399c 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/helpers.ts +++ b/frontend/pages/hosts/details/DeviceUserPage/helpers.ts @@ -47,6 +47,23 @@ export const isSoftwareScriptSetup = (s: ISetupStep) => { return s.source === "sh_packages" || s.source === "ps1_packages"; }; +// Hosts after enrollment during which we suppress the "host is offline" banner. +// Orbit endpoints do not update host_seen_times, so a freshly enrolled host can appear offline +// until its first osquery distributed-read check-in (typically within 5-10 minutes). +const RECENTLY_ENROLLED_THRESHOLD_MS = 10 * 60 * 1000; + +export const isRecentlyEnrolled = ( + lastEnrolledAt: string | undefined +): boolean => { + if (!lastEnrolledAt) return false; + const enrolledAt = new Date(lastEnrolledAt).getTime(); + if (isNaN(enrolledAt)) return false; + // Require a non-negative delta so a future timestamp (e.g. from client/server clock skew) is not + // treated as "recent" and does not hide a real offline state indefinitely. + const delta = Date.now() - enrolledAt; + return delta >= 0 && delta < RECENTLY_ENROLLED_THRESHOLD_MS; +}; + // Same solution as defined in /templates/enroll-ota.html (https://github.com/fleetdm/fleet/pull/26592) export const isIPhone = (navigator: Navigator) => /iPhone/i.test(navigator.userAgent);