+ {!osVersionRequirementMet && (
+
+ )}
+
+ Does not meet minimum version requirement.
+
+ Deadline to update: {osVersionRequirement.deadline}
+ >
+ )
+ }
+ >
+ {vitalsData.os_version}
+
+ >
+ }
+ />
+ );
+ };
+
const classNames = classnames(baseClass, className);
return (
@@ -202,13 +470,13 @@ const About = ({ aboutData, munki, mdm, className }: IAboutProps) => {
borderRadiusSize="xxlarge"
paddingSize="xlarge"
>
-
+
}
/>
@@ -217,19 +485,32 @@ const About = ({ aboutData, munki, mdm, className }: IAboutProps) => {
title="Last restarted"
value={
}
/>
)}
+ {renderDiskEncryptionSummary()}
+ {!isChromeHost && renderDiskSpaceSummary()}
+ {renderAgentSummary()}
{renderHardwareSerialAndIPs()}
+ {!isIosOrIpadosHost && (
+
+ )}
+ {renderBattery()}
+ {!isIosOrIpadosHost && (
+
+ )}
+ {renderOperatingSystemSummary()}
{renderMunkiData()}
{renderMdmData()}
{renderGeolocation()}
- {renderBattery()}
);
};
-export default About;
+export default Vitals;
diff --git a/frontend/pages/hosts/details/cards/About/_styles.scss b/frontend/pages/hosts/details/cards/About/_styles.scss
index e2eec3f20a..68b9f664f2 100644
--- a/frontend/pages/hosts/details/cards/About/_styles.scss
+++ b/frontend/pages/hosts/details/cards/About/_styles.scss
@@ -1,8 +1,8 @@
-.about-card {
+.vitals-card {
@include vertical-card-layout;
.truncated-tooltip {
- .about-card__device-mapping__source {
+ .vitals-card__device-mapping__source {
color: inherit;
}
}
@@ -44,16 +44,16 @@
// TooltipTruncatedText component.
&__info-grid {
display: grid;
- grid-template-columns: repeat(2, minmax(150px, max-content));
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: $gap-data-sets $pad-xxlarge;
- // at the larger screen widths we want to have 3 columns.
+ // at the larger screen widths we want to have 6 columns.
@media (min-width: $break-xxl) {
- grid-template-columns: repeat(3, minmax(150px, max-content));
+ grid-template-columns: repeat(6, minmax(150px, 1fr));
}
}
.text-muted {
color: $ui-fleet-black-50;
}
-}
+}
\ No newline at end of file
diff --git a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tests.tsx b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tests.tsx
index e6bf326b7c..b362ac6dd9 100644
--- a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tests.tsx
+++ b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tests.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { screen, waitFor } from "@testing-library/react";
+import { screen } from "@testing-library/react";
import { createCustomRenderer } from "test/test-utils";
import createMockUser from "__mocks__/userMock";
@@ -56,150 +56,8 @@ describe("Host Summary section", () => {
});
});
- describe("Disk encryption data", () => {
- it("renders 'On' for macOS when enabled", () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: true,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
- const summaryData = createMockHostSummary({
- platform: "darwin",
- disk_encryption_enabled: true,
- });
- render();
- expect(screen.getByText("Disk encryption")).toBeInTheDocument();
- expect(screen.getByText("On")).toBeInTheDocument();
- });
-
- it("renders 'Off' for Windows when disabled", () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: true,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
- const summaryData = createMockHostSummary({
- platform: "windows",
- disk_encryption_enabled: false,
- });
- render();
- expect(screen.getByText("Disk encryption")).toBeInTheDocument();
- expect(screen.getByText("Off")).toBeInTheDocument();
- });
-
- it("renders Chromebook message for Chrome platform", () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: true,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
- const summaryData = createMockHostSummary({ platform: "chrome" });
- render();
- expect(screen.getByText("Always on")).toBeInTheDocument();
- });
- });
-
- describe("Agent data", () => {
- it("with all info present, render Agent header with orbit_version and tooltip with all 3 data points", async () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: true,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
- const summaryData = createMockHostSummary();
- const orbitVersion = summaryData.orbit_version as string;
- const osqueryVersion = summaryData.osquery_version as string;
- const fleetdVersion = summaryData.fleet_desktop_version as string;
-
- const { user } = render();
-
- expect(screen.getByText("Agent")).toBeInTheDocument();
-
- await user.hover(screen.getByText(new RegExp(orbitVersion, "i")));
-
- await waitFor(() => {
- expect(
- screen.getByText(new RegExp(osqueryVersion, "i"))
- ).toBeInTheDocument();
- expect(
- screen.getByText(new RegExp(fleetdVersion, "i"))
- ).toBeInTheDocument();
- });
- });
-
- it("omit fleet desktop from tooltip if no fleet desktop version", async () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: true,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
- const summaryData = createMockHostSummary({
- fleet_desktop_version: null,
- });
- const orbitVersion = summaryData.orbit_version as string;
- const osqueryVersion = summaryData.osquery_version as string;
-
- const { user } = render();
-
- expect(screen.getByText("Agent")).toBeInTheDocument();
-
- await user.hover(screen.getByText(new RegExp(orbitVersion, "i")));
-
- await waitFor(() => {
- expect(
- screen.getByText(new RegExp(osqueryVersion, "i"))
- ).toBeInTheDocument();
- expect(screen.queryByText(/Fleet desktop:/i)).not.toBeInTheDocument();
- });
- });
-
- it("for Chromebooks, render Agent header with osquery_version that is the fleetd chrome version and no tooltip", async () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: true,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
- const summaryData = createMockHostSummary({
- platform: "chrome",
- osquery_version: "fleetd-chrome 1.2.0",
- });
-
- const fleetdChromeVersion = summaryData.osquery_version as string;
-
- const { user } = render();
-
- expect(screen.getByText("Agent")).toBeInTheDocument();
- await user.hover(screen.getByText(new RegExp(fleetdChromeVersion, "i")));
- expect(screen.queryByText("Osquery")).not.toBeInTheDocument();
- });
- });
-
describe("iOS and iPadOS data", () => {
- it("for iOS, renders Team, Disk space, and Operating system data only", async () => {
+ it("for iOS, renders Team data only", async () => {
const render = createCustomRenderer({
context: {
app: {
@@ -218,28 +76,14 @@ describe("Host Summary section", () => {
});
const teamName = summaryData.team_name as string;
- const diskSpaceAvailable = summaryData.gigs_disk_space_available as string;
- const osVersion = summaryData.os_version as string;
render();
expect(screen.getByText("Team").nextElementSibling).toHaveTextContent(
teamName
);
- expect(
- screen.getByText("Disk space").nextElementSibling
- ).toHaveTextContent(`${diskSpaceAvailable} GB available`);
- expect(
- screen.getByText("Operating system").nextElementSibling
- ).toHaveTextContent(osVersion);
-
- expect(screen.queryByText("Status")).not.toBeInTheDocument();
- expect(screen.queryByText("Memory")).not.toBeInTheDocument();
- expect(screen.queryByText("Processor type")).not.toBeInTheDocument();
- expect(screen.queryByText("Agent")).not.toBeInTheDocument();
- expect(screen.queryByText("Osquery")).not.toBeInTheDocument();
});
- it("for iPadOS, renders Team, Disk space, and Operating system data only", async () => {
+ it("for iPadOS, renders Team data only", async () => {
const render = createCustomRenderer({
context: {
app: {
@@ -258,26 +102,12 @@ describe("Host Summary section", () => {
});
const teamName = summaryData.team_name as string;
- const diskSpaceAvailable = summaryData.gigs_disk_space_available as string;
- const osVersion = summaryData.os_version as string;
render();
expect(screen.getByText("Team").nextElementSibling).toHaveTextContent(
teamName
);
- expect(
- screen.getByText("Disk space").nextElementSibling
- ).toHaveTextContent(`${diskSpaceAvailable} GB available`);
- expect(
- screen.getByText("Operating system").nextElementSibling
- ).toHaveTextContent(osVersion);
-
- expect(screen.queryByText("Status")).not.toBeInTheDocument();
- expect(screen.queryByText("Memory")).not.toBeInTheDocument();
- expect(screen.queryByText("Processor type")).not.toBeInTheDocument();
- expect(screen.queryByText("Agent")).not.toBeInTheDocument();
- expect(screen.queryByText("Osquery")).not.toBeInTheDocument();
});
});
@@ -334,98 +164,4 @@ describe("Host Summary section", () => {
expect(screen.getByText("Bootstrap package")).toBeInTheDocument();
});
});
-
- describe("Disk space field visibility", () => {
- it("hides disk space field when storage measurement is not supported (sentinel value -1)", () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: false,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
-
- const summaryData = createMockHostSummary({
- gigs_disk_space_available: -1,
- percent_disk_space_available: 0,
- platform: "android",
- });
-
- render();
-
- // Disk space field should not be rendered at all
- expect(screen.queryByText("Disk space")).not.toBeInTheDocument();
- });
-
- it("shows disk space field for zero storage (disk full)", () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: false,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
-
- const summaryData = createMockHostSummary({
- gigs_disk_space_available: 0,
- percent_disk_space_available: 0,
- platform: "android",
- });
-
- render();
-
- // Disk space field should be rendered
- expect(screen.getByText("Disk space")).toBeInTheDocument();
- });
-
- it("renders disk space normally for positive values", () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: false,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
-
- const summaryData = createMockHostSummary({
- gigs_disk_space_available: 25.5,
- percent_disk_space_available: 50,
- platform: "darwin",
- });
-
- render();
-
- // Disk space field should be rendered with the value
- expect(screen.getByText("Disk space")).toBeInTheDocument();
- });
-
- it("handles other negative values as not supported", () => {
- const render = createCustomRenderer({
- context: {
- app: {
- isPremiumTier: false,
- isGlobalAdmin: true,
- currentUser: createMockUser(),
- },
- },
- });
-
- const summaryData = createMockHostSummary({
- gigs_disk_space_available: -10,
- percent_disk_space_available: 0,
- platform: "android",
- });
-
- render();
-
- // Disk space field should not be rendered for any negative value
- expect(screen.queryByText("Disk space")).not.toBeInTheDocument();
- });
- });
});
diff --git a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx
index 0f83c1d87e..e2a3185cbb 100644
--- a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx
+++ b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx
@@ -8,37 +8,22 @@ import {
isLinuxDiskEncryptionStatus,
} from "interfaces/mdm";
import { IOSSettings, IHostMaintenanceWindow } from "interfaces/host";
-import { IAppleDeviceUpdates } from "interfaces/config";
import {
- DiskEncryptionSupportedPlatform,
isAndroid,
isIPadOrIPhone,
isDiskEncryptionSupportedLinuxPlatform,
isOsSettingsDisplayPlatform,
- platformSupportsDiskEncryption,
} from "interfaces/platform";
-import { ROLLING_ARCH_LINUX_VERSIONS } from "interfaces/software";
import getHostStatusTooltipText from "pages/hosts/helpers";
-import TooltipWrapperArchLinuxRolling from "components/TooltipWrapperArchLinuxRolling";
import TooltipWrapper from "components/TooltipWrapper";
-import Icon from "components/Icon/Icon";
import Card from "components/Card";
import DataSet from "components/DataSet";
import StatusIndicator from "components/StatusIndicator";
import IssuesIndicator from "pages/hosts/components/IssuesIndicator";
-import DiskSpaceIndicator from "pages/hosts/components/DiskSpaceIndicator";
-import {
- humanHostMemory,
- wrapFleetHelper,
- removeOSPrefix,
- compareVersions,
-} from "utilities/helpers";
-import {
- DATE_FNS_FORMAT_STRINGS,
- DEFAULT_EMPTY_CELL_VALUE,
-} from "utilities/constants";
+
+import { DATE_FNS_FORMAT_STRINGS } from "utilities/constants";
import OSSettingsIndicator from "./OSSettingsIndicator";
import BootstrapPackageIndicator from "./BootstrapPackageIndicator/BootstrapPackageIndicator";
@@ -62,68 +47,10 @@ interface IHostSummaryProps {
toggleOSSettingsModal?: () => void;
toggleBootstrapPackageModal?: () => void;
hostSettings?: IHostMdmProfile[];
- osVersionRequirement?: IAppleDeviceUpdates;
osSettings?: IOSSettings;
className?: string;
}
-const DISK_ENCRYPTION_MESSAGES = {
- darwin: {
- enabled: (
- <>
- The disk is encrypted. The user must enter their
-
password when they start their computer.
- >
- ),
- disabled: (
- <>
- The disk might be encrypted, but FileVault is off. The
-
disk can be accessed without entering a password.
- >
- ),
- },
- windows: {
- enabled: (
- <>
- The disk is encrypted. If recently turned on,
-
encryption could take awhile.
- >
- ),
- disabled: "The disk is unencrypted.",
- },
- linux: {
- enabled: "The disk is encrypted.",
- unknown: "The disk may be encrypted.",
- },
-};
-
-const getHostDiskEncryptionTooltipMessage = (
- platform: DiskEncryptionSupportedPlatform, // TODO: improve this type
- diskEncryptionEnabled = false
-) => {
- if (platform === "chrome") {
- return "Fleet does not check for disk encryption on Chromebooks, as they are encrypted by default.";
- }
-
- if (
- platform === "rhel" ||
- platform === "ubuntu" ||
- platform === "arch" ||
- platform === "archarm" ||
- platform === "manjaro" ||
- platform === "manjaro-arm"
- ) {
- return DISK_ENCRYPTION_MESSAGES.linux[
- diskEncryptionEnabled ? "enabled" : "unknown"
- ];
- }
-
- // mac or windows
- return DISK_ENCRYPTION_MESSAGES[platform][
- diskEncryptionEnabled ? "enabled" : "disabled"
- ];
-};
-
const HostSummary = ({
summaryData,
bootstrapPackageData,
@@ -131,21 +58,14 @@ const HostSummary = ({
toggleOSSettingsModal,
toggleBootstrapPackageModal,
hostSettings,
- osVersionRequirement,
osSettings,
className,
}: IHostSummaryProps): JSX.Element => {
const classNames = classnames(baseClass, className);
- const {
- status,
- platform,
- os_version,
- disk_encryption_enabled: diskEncryptionEnabled,
- } = summaryData;
+ const { status, platform, os_version } = summaryData;
const isAndroidHost = isAndroid(platform);
- const isChromeHost = platform === "chrome";
const isIosOrIpadosHost = isIPadOrIPhone(platform);
const renderIssues = () => (
@@ -177,179 +97,6 @@ const HostSummary = ({
/>
);
- const renderDiskSpaceSummary = () => {
- // Hide disk space field if storage measurement is not supported (sentinel value -1)
- if (
- typeof summaryData.gigs_disk_space_available === "number" &&
- summaryData.gigs_disk_space_available < 0
- ) {
- return null;
- }
-
- const title = isAndroidHost ? (
-
- Disk space
-
- ) : (
- "Disk space"
- );
-
- return (
-
- }
- />
- );
- };
- const renderDiskEncryptionSummary = () => {
- if (!platformSupportsDiskEncryption(platform, os_version)) {
- return <>>;
- }
- const tooltipMessage = getHostDiskEncryptionTooltipMessage(
- platform,
- diskEncryptionEnabled
- );
-
- let statusText;
- switch (true) {
- case isChromeHost:
- statusText = "Always on";
- break;
- case diskEncryptionEnabled === true:
- statusText = "On";
- break;
- case diskEncryptionEnabled === false:
- statusText = "Off";
- break;
- case (diskEncryptionEnabled === null ||
- diskEncryptionEnabled === undefined) &&
- platformSupportsDiskEncryption(platform, os_version):
- statusText = "Unknown";
- break;
- default:
- // something unexpected happened on the way to this component, display whatever we got or
- // "Unknown" to draw attention to the issue.
- statusText = diskEncryptionEnabled || "Unknown";
- }
-
- return (
-
- {statusText}
-
- }
- />
- );
- };
-
- const renderOperatingSystemSummary = () => {
- // No tooltip if minimum version is not set, including all Windows, Linux, ChromeOS, Android operating systems
- if (!osVersionRequirement?.minimum_version) {
- const version = summaryData.os_version;
- const versionForRender = ROLLING_ARCH_LINUX_VERSIONS.includes(version) ? (
- // wrap a tooltip around the "rolling" suffix
- <>
- {version.slice(0, -8)}
-
- >
- ) : (
- version
- );
- return (
-
- );
- }
-
- const osVersionWithoutPrefix = removeOSPrefix(summaryData.os_version);
- const osVersionRequirementMet =
- compareVersions(
- osVersionWithoutPrefix,
- osVersionRequirement.minimum_version
- ) >= 0;
-
- return (
-
- {!osVersionRequirementMet && (
-
- )}
-
- Does not meet minimum version requirement.
-
- Deadline to update: {osVersionRequirement.deadline}
- >
- )
- }
- >
- {summaryData.os_version}
-
- >
- }
- />
- );
- };
-
- const renderAgentSummary = () => {
- if (isIosOrIpadosHost || isAndroidHost) {
- return null;
- }
-
- if (isChromeHost) {
- return ;
- }
-
- if (summaryData.orbit_version !== DEFAULT_EMPTY_CELL_VALUE) {
- return (
-
- osquery: {summaryData.osquery_version}
-
- Orbit: {summaryData.orbit_version}
- {summaryData.fleet_desktop_version !==
- DEFAULT_EMPTY_CELL_VALUE && (
- <>
-
- Fleet Desktop: {summaryData.fleet_desktop_version}
- >
- )}
- >
- }
- >
- {summaryData.orbit_version}
-
- }
- />
- );
- }
- return ;
- };
-
const renderMaintenanceWindow = ({
starts_at,
timezone,
@@ -439,12 +186,7 @@ const HostSummary = ({
}
/>
)}
- {summaryData.issues?.total_issues_count > 0 &&
- !isIosOrIpadosHost &&
- !isAndroidHost &&
- renderIssues()}
{isPremiumTier && renderHostTeam()}
- {/* Rendering of OS Settings data */}
{isOsSettingsDisplayPlatform(platform, os_version) &&
hostSettings &&
hostSettings.length > 0 && (
@@ -458,6 +200,10 @@ const HostSummary = ({
}
/>
)}
+ {summaryData.issues?.total_issues_count > 0 &&
+ !isIosOrIpadosHost &&
+ !isAndroidHost &&
+ renderIssues()}
{bootstrapPackageData?.status && !isIosOrIpadosHost && !isAndroidHost && (
)}
- {!isChromeHost && renderDiskSpaceSummary()}
- {renderDiskEncryptionSummary()}
- {!isIosOrIpadosHost && (
-
- )}
- {!isIosOrIpadosHost && (
-
- )}
- {renderOperatingSystemSummary()}
- {renderAgentSummary()}
{isPremiumTier &&
// TODO - refactor normalizeEmptyValues pattern
!!summaryData.maintenance_window &&
diff --git a/frontend/utilities/constants.tsx b/frontend/utilities/constants.tsx
index 99c16bdc67..cedcad6d56 100644
--- a/frontend/utilities/constants.tsx
+++ b/frontend/utilities/constants.tsx
@@ -409,25 +409,14 @@ export const HOST_SUMMARY_DATA: (keyof IHost)[] = [
"id",
"status",
"issues",
- "memory",
- "cpu_type",
"platform",
- "os_version",
- "osquery_version",
- "orbit_version",
- "fleet_desktop_version",
"detail_updated_at",
- "percent_disk_space_available",
- "gigs_disk_space_available",
- "gigs_total_disk_space",
- "gigs_all_disk_space",
"team_name",
- "disk_encryption_enabled",
"display_name", // Not rendered on my device page
"maintenance_window", // Not rendered on my device page
];
-export const HOST_ABOUT_DATA = [
+export const HOST_VITALS_DATA = [
"seen_time",
"uptime",
"last_enrolled_at",
@@ -441,6 +430,17 @@ export const HOST_ABOUT_DATA = [
"last_restarted_at",
"platform",
"uuid",
+ "gigs_disk_space_available",
+ "percent_disk_space_available",
+ "gigs_total_disk_space",
+ "gigs_all_disk_space",
+ "disk_encryption_enabled",
+ "osquery_version",
+ "orbit_version",
+ "fleet_desktop_version",
+ "memory",
+ "cpu_type",
+ "os_version",
];
export const HOST_OSQUERY_DATA = [