diff --git a/frontend/components/AddHostsModal/AddHostsModal.tests.tsx b/frontend/components/AddHostsModal/AddHostsModal.tests.tsx index f3368a5140af..658bbd11882e 100644 --- a/frontend/components/AddHostsModal/AddHostsModal.tests.tsx +++ b/frontend/components/AddHostsModal/AddHostsModal.tests.tsx @@ -57,15 +57,13 @@ describe("AddHostsModal", () => { expect(windowsText).toBeInTheDocument(); expect(screen.queryByText(/--enable-scripts/i)).toBeInTheDocument(); - await user.click(screen.getByRole("tab", { name: "Linux (RPM)" })); - const linuxRPMText = screen.getByText(/--type=rpm/i); - expect(linuxRPMText).toBeInTheDocument(); - expect(screen.queryByText(/--enable-scripts/i)).toBeInTheDocument(); - - await user.click(screen.getByRole("tab", { name: "Linux (deb)" })); + await user.click(screen.getByRole("tab", { name: "Linux" })); const linuxDebText = screen.getByText(/--type=deb/i); expect(linuxDebText).toBeInTheDocument(); expect(screen.queryByText(/--enable-scripts/i)).toBeInTheDocument(); + expect( + screen.queryByText(/CentOS, Red Hat, and Fedora Linux, use --type=rpm/i) + ).toBeInTheDocument(); await user.click(screen.getByRole("tab", { name: "ChromeOS" })); const extensionId = screen.getByDisplayValue( @@ -74,6 +72,10 @@ describe("AddHostsModal", () => { expect(extensionId).toBeInTheDocument(); expect(screen.queryByText(/--enable-scripts/i)).not.toBeInTheDocument(); + await user.click(screen.getByRole("tab", { name: "iOS & iPadOS" })); + expect(screen.queryByText(/Apple Business Manager/i)).toBeInTheDocument(); + expect(screen.queryByText(/Learn more/i)).toBeInTheDocument(); + await user.click(screen.getByRole("tab", { name: "Advanced" })); const advancedText = screen.getByText(/--type=YOUR_TYPE/i); expect(advancedText).toBeInTheDocument(); @@ -146,41 +148,6 @@ describe("AddHostsModal", () => { expect(ctaButton).toBeEnabled(); }); - it("sandbox mode renders and download disabled until a platform is selected", async () => { - const render = createCustomRenderer({ - withBackendMock: true, - context: { - app: { - isPreviewMode: false, - config: createMockConfig(), - }, - }, - }); - - const { user } = render( - - ); - - const text = screen.getByText("Which platform is your host running?"); - const windowsText = screen.getByText("Windows"); - const downloadButton = screen.getByRole("button", { - name: /Download installer/i, - }); - - expect(text).toBeInTheDocument(); - expect(downloadButton).not.toBeEnabled(); - - await user.click(windowsText); - - expect(downloadButton).toBeEnabled(); - }); - it("excludes `--enable-scripts` flag if `config.server_settings.scripts-disabled` is `true`", async () => { const mockConfig = createMockConfig(); mockConfig.server_settings.scripts_disabled = true; @@ -214,16 +181,11 @@ describe("AddHostsModal", () => { expect(windowsText).toBeInTheDocument(); expect(screen.queryByText(/--enable-scripts/i)).not.toBeInTheDocument(); - await user.click(screen.getByRole("tab", { name: "Linux (RPM)" })); + await user.click(screen.getByRole("tab", { name: "Linux" })); const linuxRPMText = screen.getByText(/--type=rpm/i); expect(linuxRPMText).toBeInTheDocument(); expect(screen.queryByText(/--enable-scripts/i)).not.toBeInTheDocument(); - await user.click(screen.getByRole("tab", { name: "Linux (deb)" })); - const linuxDebText = screen.getByText(/--type=deb/i); - expect(linuxDebText).toBeInTheDocument(); - expect(screen.queryByText(/--enable-scripts/i)).not.toBeInTheDocument(); - await user.click(screen.getByRole("tab", { name: "ChromeOS" })); const extensionId = screen.getByDisplayValue( /fleeedmmihkfkeemmipgmhhjemlljidg/i diff --git a/frontend/components/AddHostsModal/AddHostsModal.tsx b/frontend/components/AddHostsModal/AddHostsModal.tsx index 7d74f7cfc3c2..04a42908ed2e 100644 --- a/frontend/components/AddHostsModal/AddHostsModal.tsx +++ b/frontend/components/AddHostsModal/AddHostsModal.tsx @@ -18,7 +18,6 @@ interface IAddHostsModal { enrollSecret?: string; isAnyTeamSelected: boolean; isLoading: boolean; - isSandboxMode?: boolean; onCancel: () => void; openEnrollSecretModal?: () => void; } @@ -28,7 +27,6 @@ const AddHostsModal = ({ enrollSecret, isAnyTeamSelected, isLoading, - isSandboxMode, onCancel, openEnrollSecretModal, }: IAddHostsModal): JSX.Element => { @@ -53,12 +51,6 @@ const AddHostsModal = ({ openEnrollSecretModal && openEnrollSecretModal(); }; - // TODO: Currently, prepacked installers in Fleet Sandbox use the global enroll secret, - // and Fleet Sandbox runs Fleet Free so the currentTeam check here is an - // additional precaution/reminder to revisit this in connection with future changes. - // See https://github.com/fleetdm/fleet/issues/4970#issuecomment-1187679407. - const shouldRenderDownloadInstallersContent = - isSandboxMode && !isAnyTeamSelected; const renderModalContent = () => { if (isLoading) { return ; @@ -81,9 +73,7 @@ const AddHostsModal = ({ ); } - return shouldRenderDownloadInstallersContent ? ( - - ) : ( + return ( ); } + + if (packageType === "ios-ipados") { + return ( +
+

+ Enroll iPhones and iPads by adding them to Fleet in Apple Business + Manager (ABM).{" "} + +

+
+ ); + } + if (packageType === "advanced") { return ( <> @@ -539,7 +556,11 @@ const PlatformWrapper = ({ label={renderLabel(packageType, renderInstallerString(packageType))} type="textarea" value={renderInstallerString(packageType)} - helpText="Distribute your package to add hosts to Fleet." + helpText={`Distribute your package to add hosts to Fleet.${ + packageType === "deb" + ? " For CentOS, Red Hat, and Fedora Linux, use --type=rpm." + : "" + }`} /> ); diff --git a/frontend/components/AddHostsModal/PlatformWrapper/_styles.scss b/frontend/components/AddHostsModal/PlatformWrapper/_styles.scss index b032d830b287..a038b91704e8 100644 --- a/frontend/components/AddHostsModal/PlatformWrapper/_styles.scss +++ b/frontend/components/AddHostsModal/PlatformWrapper/_styles.scss @@ -8,7 +8,7 @@ position: initial; .react-tabs { &__tab-list { - margin: 0 0 1.25rem; + margin: 0 0 $pad-large; } } } diff --git a/frontend/components/PlatformCompatibility/PlatformCompatibility.tests.tsx b/frontend/components/PlatformCompatibility/PlatformCompatibility.tests.tsx index b0fc1c950a56..10fbbb4faf7e 100644 --- a/frontend/components/PlatformCompatibility/PlatformCompatibility.tests.tsx +++ b/frontend/components/PlatformCompatibility/PlatformCompatibility.tests.tsx @@ -7,7 +7,7 @@ describe("Platform compatibility", () => { it("renders compatible platforms", () => { render( ); @@ -38,7 +38,7 @@ describe("Platform compatibility", () => { it("renders error state", () => { render( ); diff --git a/frontend/components/PlatformCompatibility/PlatformCompatibility.tsx b/frontend/components/PlatformCompatibility/PlatformCompatibility.tsx index 973647315a1e..700726d37ecb 100644 --- a/frontend/components/PlatformCompatibility/PlatformCompatibility.tsx +++ b/frontend/components/PlatformCompatibility/PlatformCompatibility.tsx @@ -1,13 +1,17 @@ import React from "react"; -import { OsqueryPlatform } from "interfaces/platform"; +import { + DisplayPlatform, + QueryableDisplayPlatform, + QueryablePlatform, +} from "interfaces/platform"; import { PLATFORM_DISPLAY_NAMES } from "utilities/constants"; import TooltipWrapper from "components/TooltipWrapper"; import Icon from "components/Icon"; interface IPlatformCompatibilityProps { - compatiblePlatforms: OsqueryPlatform[] | null; + compatiblePlatforms: any[] | null; error: Error | null; } @@ -18,13 +22,13 @@ const DISPLAY_ORDER = [ "Windows", "Linux", "ChromeOS", -] as OsqueryPlatform[]; +] as QueryableDisplayPlatform[]; const ERROR_NO_COMPATIBLE_TABLES = Error("no tables in query"); const formatPlatformsForDisplay = ( - compatiblePlatforms: OsqueryPlatform[] -): OsqueryPlatform[] => { + compatiblePlatforms: QueryablePlatform[] +): DisplayPlatform[] => { return compatiblePlatforms.map((str) => PLATFORM_DISPLAY_NAMES[str] || str); }; @@ -83,8 +87,9 @@ const PlatformCompatibility = ({ - Estimated compatiblity based on
the tables used in the - query. + Estimated compatibility based on the
+ tables used in the query. Querying
+ iPhones & iPads is not supported. } > diff --git a/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tests.tsx b/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tests.tsx index 630eb461043e..9f91b32ee384 100644 --- a/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tests.tsx +++ b/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tests.tsx @@ -2,10 +2,10 @@ import React from "react"; import { render, screen } from "@testing-library/react"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; -import { SupportedPlatform } from "interfaces/platform"; +import { QueryablePlatform } from "interfaces/platform"; import PlatformCell from "./PlatformCell"; -const PLATFORMS: SupportedPlatform[] = ["windows", "darwin", "linux", "chrome"]; +const PLATFORMS: QueryablePlatform[] = ["windows", "darwin", "linux", "chrome"]; describe("Platform cell", () => { it("renders platform icons in correct order", () => { diff --git a/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tsx b/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tsx index 48665040c643..f88026663840 100644 --- a/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tsx +++ b/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tsx @@ -1,22 +1,22 @@ import React from "react"; import Icon from "components/Icon"; -import { SupportedPlatform } from "interfaces/platform"; +import { QueryablePlatform } from "interfaces/platform"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; interface IPlatformCellProps { - platforms: SupportedPlatform[]; + platforms: QueryablePlatform[]; } const baseClass = "platform-cell"; -const ICONS: Record = { +const ICONS: Record = { darwin: "darwin", windows: "windows", linux: "linux", chrome: "chrome", }; -const DISPLAY_ORDER: SupportedPlatform[] = [ +const DISPLAY_ORDER: QueryablePlatform[] = [ "darwin", "windows", "linux", diff --git a/frontend/components/buttons/Button/Button.tsx b/frontend/components/buttons/Button/Button.tsx index 03749d419b2a..3e05fda3fecf 100644 --- a/frontend/components/buttons/Button/Button.tsx +++ b/frontend/components/buttons/Button/Button.tsx @@ -13,7 +13,7 @@ export type ButtonVariant = | "warning" | "link" | "label" - | "text-link" + | "text-link" // Underlines on hover | "text-icon" | "icon" // Buttons without text | "small-icon" // Buttons without text diff --git a/frontend/components/icons/iOS.tsx b/frontend/components/icons/iOS.tsx new file mode 100644 index 000000000000..a6fa41354a02 --- /dev/null +++ b/frontend/components/icons/iOS.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { COLORS, Colors } from "styles/var/colors"; +import { ICON_SIZES, IconSizes } from "styles/var/icon_sizes"; + +interface IiOSProps { + size: IconSizes; + color?: Colors; +} + +const iOS = ({ size = "medium", color = "ui-fleet-black-75" }: IiOSProps) => { + return ( + + + + + + ); +}; + +export default iOS; diff --git a/frontend/components/icons/iPadOS.tsx b/frontend/components/icons/iPadOS.tsx new file mode 100644 index 000000000000..25b654fac0d9 --- /dev/null +++ b/frontend/components/icons/iPadOS.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { COLORS, Colors } from "styles/var/colors"; +import { ICON_SIZES, IconSizes } from "styles/var/icon_sizes"; + +interface IiPadOSProps { + size: IconSizes; + color?: Colors; +} + +const iPadOS = ({ + size = "medium", + color = "ui-fleet-black-75", +}: IiPadOSProps) => { + return ( + + + + + ); +}; + +export default iPadOS; diff --git a/frontend/components/icons/index.ts b/frontend/components/icons/index.ts index 3b573cf9ac48..d9cbb63a8f7d 100644 --- a/frontend/components/icons/index.ts +++ b/frontend/components/icons/index.ts @@ -34,6 +34,8 @@ import M1 from "./M1"; import Centos from "./Centos"; import Ubuntu from "./Ubuntu"; import Chrome from "./Chrome"; +import iPadOS from "./iPadOS"; +import iOS from "./iOS"; // Status Icons import Success from "./Success"; @@ -114,6 +116,10 @@ export const ICON_MAP = { ubuntu: Ubuntu, chrome: Chrome, ChromeOS: Chrome, + ipados: iPadOS, + iPadOS, + ios: iOS, + iOS, "premium-feature": PremiumFeature, profile: Profile, download: Download, diff --git a/frontend/components/side_panels/QuerySidePanel/QueryTableColumns/ColumnListItem/ColumnListItem.tsx b/frontend/components/side_panels/QuerySidePanel/QueryTableColumns/ColumnListItem/ColumnListItem.tsx index fa67493864f4..8a061ca4a92d 100644 --- a/frontend/components/side_panels/QuerySidePanel/QueryTableColumns/ColumnListItem/ColumnListItem.tsx +++ b/frontend/components/side_panels/QuerySidePanel/QueryTableColumns/ColumnListItem/ColumnListItem.tsx @@ -1,10 +1,13 @@ import React from "react"; import classnames from "classnames"; -import { ColumnType, IQueryTableColumn } from "interfaces/osquery_table"; +import { + ColumnType, + IQueryTableColumn, + TableSchemaPlatform, +} from "interfaces/osquery_table"; import TooltipWrapper from "components/TooltipWrapper"; import { buildQueryStringFromParams } from "utilities/url"; -import { OsqueryPlatform } from "interfaces/platform"; interface IColumnListItemProps { column: IQueryTableColumn; @@ -55,7 +58,7 @@ const renderTooltip = ( ); }; - const renderPlatformFootnotes = (columnPlatforms: OsqueryPlatform[]) => { + const renderPlatformFootnotes = (columnPlatforms: TableSchemaPlatform[]) => { let platformsCopy; switch (columnPlatforms.length) { case 1: diff --git a/frontend/components/side_panels/QuerySidePanel/QueryTablePlatforms/QueryTablePlatforms.tsx b/frontend/components/side_panels/QuerySidePanel/QueryTablePlatforms/QueryTablePlatforms.tsx index c517e213505c..7b63f253d6a7 100644 --- a/frontend/components/side_panels/QuerySidePanel/QueryTablePlatforms/QueryTablePlatforms.tsx +++ b/frontend/components/side_panels/QuerySidePanel/QueryTablePlatforms/QueryTablePlatforms.tsx @@ -1,11 +1,11 @@ import React from "react"; -import { OsqueryPlatform } from "interfaces/platform"; import { PLATFORM_DISPLAY_NAMES } from "utilities/constants"; import Icon from "components/Icon"; +import { TableSchemaPlatform } from "interfaces/osquery_table"; interface IPLatformListItemProps { - platform: OsqueryPlatform; + platform: TableSchemaPlatform; } const baseClassListItem = "platform-list-item"; @@ -20,7 +20,7 @@ const PlatformListItem = ({ platform }: IPLatformListItemProps) => { }; // TODO: remove when freebsd is removed -type IPlatformsWithFreebsd = OsqueryPlatform | "freebsd"; +type IPlatformsWithFreebsd = TableSchemaPlatform | "freebsd"; interface IQueryTablePlatformsProps { platforms: IPlatformsWithFreebsd[]; @@ -38,7 +38,7 @@ const QueryTablePlatforms = ({ platforms }: IQueryTablePlatformsProps) => { return ( ); }); diff --git a/frontend/hooks/usePlatformCompatibility.tsx b/frontend/hooks/usePlatformCompatibility.tsx index 15fa948d33d7..ebb4ec1ee4e1 100644 --- a/frontend/hooks/usePlatformCompatibility.tsx +++ b/frontend/hooks/usePlatformCompatibility.tsx @@ -1,13 +1,13 @@ import React, { useCallback, useState } from "react"; import { useDebouncedCallback } from "use-debounce"; -import { OsqueryPlatform, SUPPORTED_PLATFORMS } from "interfaces/platform"; +import { QueryablePlatform, SUPPORTED_PLATFORMS } from "interfaces/platform"; import { checkPlatformCompatibility } from "utilities/sql_tools"; import PlatformCompatibility from "components/PlatformCompatibility"; export interface IPlatformCompatibility { - getCompatiblePlatforms: () => ("darwin" | "windows" | "linux" | "chrome")[]; + getCompatiblePlatforms: () => QueryablePlatform[]; setCompatiblePlatforms: (sqlString: string) => void; render: () => JSX.Element; } @@ -16,7 +16,7 @@ const DEBOUNCE_DELAY = 300; const usePlatformCompatibility = (): IPlatformCompatibility => { const [compatiblePlatforms, setCompatiblePlatforms] = useState< - OsqueryPlatform[] | null + QueryablePlatform[] | null >(null); const [error, setError] = useState(null); diff --git a/frontend/hooks/usePlatformSelector.tsx b/frontend/hooks/usePlatformSelector.tsx index 0583f16ec37f..b1fb384dd98f 100644 --- a/frontend/hooks/usePlatformSelector.tsx +++ b/frontend/hooks/usePlatformSelector.tsx @@ -4,13 +4,14 @@ import { forEach } from "lodash"; import { SelectedPlatformString, SUPPORTED_PLATFORMS, + QueryablePlatform, } from "interfaces/platform"; import PlatformSelector from "components/PlatformSelector"; export interface IPlatformSelector { setSelectedPlatforms: (platforms: string[]) => void; - getSelectedPlatforms: () => ("darwin" | "windows" | "linux" | "chrome")[]; + getSelectedPlatforms: () => QueryablePlatform[]; isAnyPlatformSelected: boolean; render: () => JSX.Element; disabled?: boolean; diff --git a/frontend/interfaces/mdm.ts b/frontend/interfaces/mdm.ts index a911f0c9e78b..3a75fc825866 100644 --- a/frontend/interfaces/mdm.ts +++ b/frontend/interfaces/mdm.ts @@ -67,7 +67,7 @@ export interface IMdmSummaryResponse { mobile_device_management_solution: IMdmSummaryMdmSolution[] | null; } -export type ProfilePlatform = "darwin" | "windows"; +export type ProfilePlatform = "darwin" | "windows" | "ios" | "ipados"; export interface IProfileLabel { name: string; diff --git a/frontend/interfaces/osquery_table.ts b/frontend/interfaces/osquery_table.ts index 3befc8f40305..c61faecba79d 100644 --- a/frontend/interfaces/osquery_table.ts +++ b/frontend/interfaces/osquery_table.ts @@ -1,5 +1,5 @@ import PropTypes from "prop-types"; -import { OsqueryPlatform } from "./platform"; +import { QueryablePlatform, QueryableDisplayPlatform } from "./platform"; export default PropTypes.shape({ columns: PropTypes.arrayOf( @@ -23,6 +23,8 @@ export type ColumnType = | "STRING" | "string"; // TODO: Why do we have type string, STRING, and text in schema.json? +// TODO: Replace with one or the other once osquery_fleet_schema.json follows one type or other +export type TableSchemaPlatform = QueryableDisplayPlatform | QueryablePlatform; export interface IQueryTableColumn { name: string; description: string; @@ -30,7 +32,7 @@ export interface IQueryTableColumn { hidden: boolean; required: boolean; index: boolean; - platforms?: OsqueryPlatform[]; + platforms?: TableSchemaPlatform[]; requires_user_context?: boolean; } @@ -38,7 +40,7 @@ export interface IOsQueryTable { name: string; description: string; url: string; - platforms: OsqueryPlatform[]; + platforms: TableSchemaPlatform[]; evented: boolean; cacheable: boolean; columns: IQueryTableColumn[]; diff --git a/frontend/interfaces/platform.ts b/frontend/interfaces/platform.ts index ec2e11efd3a4..5c9a9042cdd8 100644 --- a/frontend/interfaces/platform.ts +++ b/frontend/interfaces/platform.ts @@ -1,32 +1,44 @@ -export type OsqueryPlatform = - | "darwin" +export type DisplayPlatform = | "macOS" - | "windows" | "Windows" - | "linux" | "Linux" + | "ChromeOS" + | "iOS" + | "iPadOS"; + +export type Platform = + | "darwin" + | "windows" + | "linux" | "chrome" - | "ChromeOS"; + | "ios" + | "ipados"; -export type SupportedPlatform = "darwin" | "windows" | "linux" | "chrome"; +export type QueryableDisplayPlatform = Exclude< + DisplayPlatform, + "iOS" | "iPadOS" +>; -export const SUPPORTED_PLATFORMS: SupportedPlatform[] = [ +export type QueryablePlatform = Exclude; + +export const SUPPORTED_PLATFORMS: QueryablePlatform[] = [ "darwin", "windows", "linux", "chrome", ]; -export type SelectedPlatform = SupportedPlatform | "all"; + +export type SelectedPlatform = QueryablePlatform | "all"; export type SelectedPlatformString = | "" - | SupportedPlatform - | `${SupportedPlatform},${SupportedPlatform}` - | `${SupportedPlatform},${SupportedPlatform},${SupportedPlatform}` - | `${SupportedPlatform},${SupportedPlatform},${SupportedPlatform},${SupportedPlatform}`; + | QueryablePlatform + | `${QueryablePlatform},${QueryablePlatform}` + | `${QueryablePlatform},${QueryablePlatform},${QueryablePlatform}` + | `${QueryablePlatform},${QueryablePlatform},${QueryablePlatform},${QueryablePlatform}`; // TODO: revisit this approach pending resolution of https://github.com/fleetdm/fleet/issues/3555. -export const MACADMINS_EXTENSION_TABLES: Record = { +export const MACADMINS_EXTENSION_TABLES: Record = { file_lines: ["darwin", "linux", "windows"], filevault_users: ["darwin"], google_chrome_profiles: ["darwin", "linux", "windows"], diff --git a/frontend/interfaces/schedulable_query.ts b/frontend/interfaces/schedulable_query.ts index 89d56dc72699..23d2a9cd27d0 100644 --- a/frontend/interfaces/schedulable_query.ts +++ b/frontend/interfaces/schedulable_query.ts @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import { IFormField } from "./form_field"; import { IPack } from "./pack"; -import { SelectedPlatformString, SupportedPlatform } from "./platform"; +import { SelectedPlatformString, QueryablePlatform } from "./platform"; // Query itself export interface ISchedulableQuery { @@ -32,7 +32,7 @@ export interface ISchedulableQuery { export interface IEnhancedQuery extends ISchedulableQuery { performance: string; - platforms: SupportedPlatform[]; + platforms: QueryablePlatform[]; } export interface ISchedulableQueryStats { user_time_p50?: number | null; diff --git a/frontend/pages/DashboardPage/DashboardPage.tsx b/frontend/pages/DashboardPage/DashboardPage.tsx index 8a38f7a68757..321ba4c2c7db 100644 --- a/frontend/pages/DashboardPage/DashboardPage.tsx +++ b/frontend/pages/DashboardPage/DashboardPage.tsx @@ -29,7 +29,6 @@ import { IMdmSummaryResponse, IMdmSummaryMdmSolution, } from "interfaces/mdm"; -import { SelectedPlatform } from "interfaces/platform"; import { ISoftwareResponse, ISoftwareCountResponse } from "interfaces/software"; import { API_ALL_TEAMS_ID, ITeam } from "interfaces/team"; import { IConfig } from "interfaces/config"; @@ -50,8 +49,7 @@ import hosts from "services/entities/hosts"; import sortUtils from "utilities/sort"; import { DEFAULT_USE_QUERY_OPTIONS, - PLATFORM_DROPDOWN_OPTIONS, - PLATFORM_NAME_TO_LABEL_NAME, + PlatformValueOptions, } from "utilities/constants"; import { ITableQueryData } from "components/TableContainer/TableContainer"; @@ -64,6 +62,10 @@ import Dropdown from "components/forms/fields/Dropdown"; import MainContent from "components/MainContent"; import LastUpdatedText from "components/LastUpdatedText"; +import { + PLATFORM_DROPDOWN_OPTIONS, + PLATFORM_NAME_TO_LABEL_NAME, +} from "./helpers"; import useInfoCard from "./components/InfoCard"; import MissingHosts from "./cards/MissingHosts"; import LowDiskSpaceHosts from "./cards/LowDiskSpaceHosts"; @@ -124,9 +126,10 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => { includeNoTeam: false, }); - const [selectedPlatform, setSelectedPlatform] = useState( - "all" - ); + const [ + selectedPlatform, + setSelectedPlatform, + ] = useState("all"); const [ selectedPlatformLabelId, setSelectedPlatformLabelId, @@ -136,6 +139,8 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => { const [windowsCount, setWindowsCount] = useState(0); const [linuxCount, setLinuxCount] = useState(0); const [chromeCount, setChromeCount] = useState(0); + const [iosCount, setIosCount] = useState(0); + const [ipadosCount, setIpadosCount] = useState(0); const [missingCount, setMissingCount] = useState(0); const [lowDiskSpaceCount, setLowDiskSpaceCount] = useState(0); const [showActivityFeedTitle, setShowActivityFeedTitle] = useState(false); @@ -243,10 +248,20 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => { (platform: IHostSummaryPlatforms) => platform.platform === "chrome" ) || { platform: "chrome", hosts_count: 0 }; + const iphones = data.platforms?.find( + (platform: IHostSummaryPlatforms) => platform.platform === "ios" + ) || { platform: "ios", hosts_count: 0 }; + + const ipads = data.platforms?.find( + (platform: IHostSummaryPlatforms) => platform.platform === "ipados" + ) || { platform: "ipados", hosts_count: 0 }; + setMacCount(macHosts.hosts_count); setWindowsCount(windowsHosts.hosts_count); setLinuxCount(data.all_linux_count); setChromeCount(chromebooks.hosts_count); + setIosCount(iphones.hosts_count); + setIpadosCount(ipads.hosts_count); setShowHostsUI(true); }, } @@ -551,6 +566,8 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => { windowsCount={windowsCount} linuxCount={linuxCount} chromeCount={chromeCount} + iosCount={iosCount} + ipadosCount={ipadosCount} isLoadingHostsSummary={isHostSummaryFetching} builtInLabels={labels} showHostsUI={showHostsUI} @@ -756,6 +773,20 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => { ); + const iosLayout = () => ( + <> +
{OperatingSystemsCard}
+ {showMdmCard &&
{MDMCard}
} + + ); + + const ipadosLayout = () => ( + <> +
{OperatingSystemsCard}
+ {showMdmCard &&
{MDMCard}
} + + ); + const renderCards = () => { switch (selectedPlatform) { case "darwin": @@ -766,6 +797,10 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => { return linuxLayout(); case "chrome": return chromeLayout(); + case "ios": + return iosLayout(); + case "ipados": + return ipadosLayout(); default: return allLayout(); } @@ -850,7 +885,7 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => { className={`${baseClass}__platform_dropdown`} options={PLATFORM_DROPDOWN_OPTIONS} searchable={false} - onChange={(value: SelectedPlatform) => { + onChange={(value: PlatformValueOptions) => { const selectedPlatformOption = PLATFORM_DROPDOWN_OPTIONS.find( (platform) => platform.value === value ); @@ -870,12 +905,14 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => { )}
{HostsSummaryCard}
- {isPremiumTier && ( -
- {MissingHostsCard} - {LowDiskSpaceHostsCard} -
- )} + {isPremiumTier && + selectedPlatform !== "ios" && + selectedPlatform !== "ipados" && ( +
+ {MissingHostsCard} + {LowDiskSpaceHostsCard} +
+ )} {renderCards()} diff --git a/frontend/pages/DashboardPage/cards/HostsSummary/HostsSummary.tsx b/frontend/pages/DashboardPage/cards/HostsSummary/HostsSummary.tsx index e7115b0f1831..6b9ced88ed96 100644 --- a/frontend/pages/DashboardPage/cards/HostsSummary/HostsSummary.tsx +++ b/frontend/pages/DashboardPage/cards/HostsSummary/HostsSummary.tsx @@ -1,10 +1,10 @@ -import React from "react"; +import React, { useCallback } from "react"; import PATHS from "router/paths"; -import { PLATFORM_NAME_TO_LABEL_NAME } from "utilities/constants"; +import { PLATFORM_NAME_TO_LABEL_NAME } from "pages/DashboardPage/helpers"; import DataError from "components/DataError"; -import { SelectedPlatform } from "interfaces/platform"; import { IHostSummary } from "interfaces/host_summary"; +import { PlatformValueOptions } from "utilities/constants"; import SummaryTile from "./SummaryTile"; @@ -16,11 +16,13 @@ interface IHostSummaryProps { windowsCount: number; linuxCount: number; chromeCount: number; + iosCount: number; + ipadosCount: number; isLoadingHostsSummary: boolean; builtInLabels?: IHostSummary["builtin_labels"]; showHostsUI: boolean; errorHosts: boolean; - selectedPlatform?: SelectedPlatform; + selectedPlatform?: PlatformValueOptions; } const HostsSummary = ({ @@ -29,6 +31,8 @@ const HostsSummary = ({ windowsCount, linuxCount, chromeCount, + iosCount, + ipadosCount, isLoadingHostsSummary, builtInLabels, showHostsUI, @@ -41,10 +45,16 @@ const HostsSummary = ({ opacity = isLoadingHostsSummary ? { opacity: 0.4 } : { opacity: 1 }; } + const getBuiltinLabelId = useCallback( + (platformName: keyof typeof PLATFORM_NAME_TO_LABEL_NAME) => + builtInLabels?.find( + (builtin) => builtin.name === PLATFORM_NAME_TO_LABEL_NAME[platformName] + )?.id, + [builtInLabels] + ); + const renderMacCount = (teamId?: number) => { - const macLabelId = builtInLabels?.find((builtin) => { - return builtin.name === PLATFORM_NAME_TO_LABEL_NAME.darwin; - })?.id; + const macLabelId = getBuiltinLabelId("darwin"); if (isLoadingHostsSummary || macLabelId === undefined) { return <>; @@ -53,7 +63,6 @@ const HostsSummary = ({ return ( { - const windowsLabelId = builtInLabels?.find( - (builtin) => builtin.name === PLATFORM_NAME_TO_LABEL_NAME.windows - )?.id; + const windowsLabelId = getBuiltinLabelId("windows"); if (isLoadingHostsSummary || windowsLabelId === undefined) { return <>; @@ -76,7 +83,6 @@ const HostsSummary = ({ return ( { - const linuxLabelId = builtInLabels?.find( - (builtin) => builtin.name === PLATFORM_NAME_TO_LABEL_NAME.linux - )?.id; + const linuxLabelId = getBuiltinLabelId("linux"); if (isLoadingHostsSummary || linuxLabelId === undefined) { return <>; @@ -99,7 +103,6 @@ const HostsSummary = ({ return ( { - const chromeLabelId = builtInLabels?.find( - (builtin) => builtin.name === PLATFORM_NAME_TO_LABEL_NAME.chrome - )?.id; + const chromeLabelId = getBuiltinLabelId("chrome"); if (isLoadingHostsSummary || chromeLabelId === undefined) { return <>; @@ -123,7 +124,6 @@ const HostsSummary = ({ return ( { + const iosLabelId = getBuiltinLabelId("ios"); + + if (isLoadingHostsSummary || iosLabelId === undefined) { + return <>; + } + + return ( + + ); + }; + + const renderIpadosCount = (teamId?: number) => { + const ipadosLabelId = getBuiltinLabelId("ipados"); + + if (isLoadingHostsSummary || ipadosLabelId === undefined) { + return <>; + } + + return ( + + ); + }; + const renderCounts = (teamId?: number) => { switch (selectedPlatform) { case "darwin": @@ -145,6 +187,10 @@ const HostsSummary = ({ return renderLinuxCount(teamId); case "chrome": return renderChromeCount(teamId); + case "ios": + return renderIosCount(teamId); + case "ipados": + return renderIpadosCount(teamId); default: return ( <> @@ -152,6 +198,8 @@ const HostsSummary = ({ {renderWindowsCount(teamId)} {renderLinuxCount(teamId)} {renderChromeCount(teamId)} + {renderIosCount(teamId)} + {renderIpadosCount(teamId)} ); } diff --git a/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tests.tsx b/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tests.tsx index bb2689d948c7..cbbae35b2814 100644 --- a/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tests.tsx +++ b/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tests.tsx @@ -1,7 +1,6 @@ import React from "react"; import { fireEvent, render, screen } from "@testing-library/react"; -import { renderWithSetup } from "test/test-utils"; import paths from "router/paths"; import SummaryTile from "./SummaryTile"; @@ -16,7 +15,6 @@ describe("SummaryTile - component", () => { showUI={false} // tested title="Windows hosts" iconName="windows" - circledIcon tooltip="Hosts on any Windows device" path={paths.MANAGE_HOSTS_LABEL(10)} /> @@ -35,7 +33,6 @@ describe("SummaryTile - component", () => { showUI title="Windows hosts" iconName="windows" - circledIcon tooltip="Hosts on any Windows device" path={paths.MANAGE_HOSTS_LABEL(10)} /> @@ -55,7 +52,6 @@ describe("SummaryTile - component", () => { showUI title="Windows hosts" // tested iconName="windows" // tested - circledIcon tooltip="Hosts on any Windows device" path={paths.MANAGE_HOSTS_LABEL(10)} /> @@ -78,7 +74,6 @@ describe("SummaryTile - component", () => { showUI title="Windows hosts" iconName="windows" - circledIcon path={paths.MANAGE_HOSTS_LABEL(10)} /> ); @@ -89,14 +84,13 @@ describe("SummaryTile - component", () => { }); it("renders tooltip on title hover", async () => { - const { user } = renderWithSetup( + render( diff --git a/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tsx b/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tsx index 55e5954ab3a7..697d542fb89e 100644 --- a/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tsx +++ b/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tsx @@ -4,7 +4,6 @@ import { kebabCase } from "lodash"; import Icon from "components/Icon"; import { IconNames } from "components/icons"; -import PremiumFeatureIconWithTooltip from "components/PremiumFeatureIconWithTooltip"; import classnames from "classnames"; import { Colors } from "styles/var/colors"; import TooltipWrapper from "components/TooltipWrapper"; @@ -15,12 +14,9 @@ interface ISummaryTileProps { showUI: boolean; title: string; iconName: IconNames; - circledIcon?: boolean; iconColor?: Colors; path: string; tooltip?: string; - isSandboxMode?: boolean; - sandboxPremiumOnlyIcon?: boolean; notSupported?: boolean; } @@ -32,12 +28,9 @@ const SummaryTile = ({ showUI, // false on first load only title, iconName, - circledIcon, iconColor, path, tooltip, - isSandboxMode = false, - sandboxPremiumOnlyIcon = false, notSupported = false, }: ISummaryTileProps): JSX.Element => { const numberWithCommas = (x: number): string => { @@ -54,15 +47,13 @@ const SummaryTile = ({ }); const tile = ( <> -
+
-
-
{notSupported ? (
Not supported @@ -76,18 +67,13 @@ const SummaryTile = ({ {numberWithCommas(count)}
)} -
- {tooltip ? ( - {title} - ) : ( - title - )} - {isSandboxMode && sandboxPremiumOnlyIcon && ( - - )} -
+
+
+ {tooltip ? ( + {title} + ) : ( + title + )}
); diff --git a/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/_styles.scss b/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/_styles.scss index 6ab2f665963e..3b7b73e82458 100644 --- a/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/_styles.scss +++ b/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/_styles.scss @@ -1,5 +1,4 @@ .summary-tile { - width: 100%; display: flex; justify-content: space-around; @@ -12,6 +11,7 @@ &__tile { display: flex; + flex-direction: column; align-items: center; gap: px-to-rem(12); text-align: left; @@ -27,20 +27,11 @@ white-space: nowrap; } - &__circled-icon { + &__icon-count { display: flex; - justify-content: center; + flex-direction: row; align-items: center; - background-color: $core-vibrant-blue; - width: px-to-rem(48); - height: px-to-rem(48); - border-radius: 100%; - background: $ui-vibrant-blue-10; - - .icon { - margin-left: 0 !important; - margin-right: 0; - } + gap: 12px; } &__count { @@ -57,7 +48,7 @@ } &__description { - white-space: nowrap; + color: $ui-fleet-black-75; .component__tooltip-wrapper__element { font-size: $x-small; diff --git a/frontend/pages/DashboardPage/cards/HostsSummary/_styles.scss b/frontend/pages/DashboardPage/cards/HostsSummary/_styles.scss index 896e7e3131ba..d1a532e0ea34 100644 --- a/frontend/pages/DashboardPage/cards/HostsSummary/_styles.scss +++ b/frontend/pages/DashboardPage/cards/HostsSummary/_styles.scss @@ -3,19 +3,17 @@ width: 100%; display: flex; justify-content: space-around; + flex-wrap: wrap; font-size: $x-small; + gap: $pad-medium; - &__tile { - flex-grow: 1; - display: flex; - justify-content: center; - align-items: center; + .summary-tile { + flex: 1 1 30%; + } - &:first-of-type { - justify-content: flex-end; - } - &:last-of-type { - justify-content: flex-start; + @media (min-width: $break-md) { + .summary-tile { + flex: initial; } } diff --git a/frontend/pages/DashboardPage/cards/LowDiskSpaceHosts/LowDiskSpaceHosts.tsx b/frontend/pages/DashboardPage/cards/LowDiskSpaceHosts/LowDiskSpaceHosts.tsx index dadd27be467e..1ac6ebe4cd0e 100644 --- a/frontend/pages/DashboardPage/cards/LowDiskSpaceHosts/LowDiskSpaceHosts.tsx +++ b/frontend/pages/DashboardPage/cards/LowDiskSpaceHosts/LowDiskSpaceHosts.tsx @@ -14,7 +14,6 @@ interface IHostSummaryProps { showHostsUI: boolean; selectedPlatformLabelId?: number; currentTeamId?: number; - isSandboxMode?: boolean; notSupported: boolean; } @@ -25,7 +24,6 @@ const LowDiskSpaceHosts = ({ showHostsUI, selectedPlatformLabelId, currentTeamId, - isSandboxMode = false, notSupported = false, // default to supporting this feature }: IHostSummaryProps): JSX.Element => { // build the manage hosts URL filtered by low disk space only @@ -54,8 +52,6 @@ const LowDiskSpaceHosts = ({ title="Low disk space hosts" tooltip={tooltipText} path={path} - isSandboxMode={isSandboxMode} - sandboxPremiumOnlyIcon notSupported={notSupported} />
diff --git a/frontend/pages/DashboardPage/cards/MissingHosts/MissingHosts.tsx b/frontend/pages/DashboardPage/cards/MissingHosts/MissingHosts.tsx index 956b8a14fc48..2bb0e4201d1e 100644 --- a/frontend/pages/DashboardPage/cards/MissingHosts/MissingHosts.tsx +++ b/frontend/pages/DashboardPage/cards/MissingHosts/MissingHosts.tsx @@ -13,7 +13,6 @@ interface IHostSummaryProps { showHostsUI: boolean; selectedPlatformLabelId?: number; currentTeamId?: number; - isSandboxMode?: boolean; } const MissingHosts = ({ @@ -22,7 +21,6 @@ const MissingHosts = ({ showHostsUI, selectedPlatformLabelId, currentTeamId, - isSandboxMode = false, }: IHostSummaryProps): JSX.Element => { // build the manage hosts URL filtered by missing and platform const queryParams = { @@ -45,8 +43,6 @@ const MissingHosts = ({ title="Missing hosts" tooltip="Hosts that have not been online in 30 days or more." path={path} - isSandboxMode={isSandboxMode} - sandboxPremiumOnlyIcon /> ); diff --git a/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystems.tsx b/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystems.tsx index 86dbf573eb45..5d2ef7f5dafb 100644 --- a/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystems.tsx +++ b/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystems.tsx @@ -5,14 +5,16 @@ import { OS_END_OF_LIFE_LINK_BY_PLATFORM, OS_VENDOR_BY_PLATFORM, } from "interfaces/operating_system"; -import { SelectedPlatform } from "interfaces/platform"; import { getOSVersions, IGetOSVersionsQueryKey, IOSVersionsResponse, OS_VERSIONS_API_SUPPORTED_PLATFORMS, } from "services/entities/operating_systems"; -import { PLATFORM_DISPLAY_NAMES } from "utilities/constants"; +import { + PLATFORM_DISPLAY_NAMES, + PlatformValueOptions, +} from "utilities/constants"; import TableContainer from "components/TableContainer"; import Spinner from "components/Spinner"; @@ -26,7 +28,7 @@ import generateTableHeaders from "./OperatingSystemsTableConfig"; interface IOperatingSystemsCardProps { currentTeamId: number | undefined; - selectedPlatform: SelectedPlatform; + selectedPlatform: PlatformValueOptions; showTitle: boolean; /** controls the displaying of description text under the title. Defaults to `true` */ showDescription?: boolean; @@ -42,7 +44,7 @@ const DEFAULT_SORT_HEADER = "hosts_count"; const PAGE_SIZE = 8; const baseClass = "operating-systems"; -const EmptyOperatingSystems = (platform: SelectedPlatform): JSX.Element => ( +const EmptyOperatingSystems = (platform: PlatformValueOptions): JSX.Element => ( { const getPlatformName = () => { if (platform === "windows") return "Windows"; - return isDDM ? "macOS (declaration)" : "macOS"; + return isDDM ? "macOS (declaration)" : "macOS, iOS, iPadOS"; }; return ( @@ -57,7 +58,7 @@ const createProfileExtension = (profile: IMdmProfile) => { if (isDDMProfile(profile)) { return "json"; } - return profile.platform === "darwin" ? "mobileconfig" : "xml"; + return isAppleDevice(profile.platform) ? "mobileconfig" : "xml"; }; const createFileContent = async (profile: IMdmProfile) => { diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss index 550e2a9daedc..95436510d152 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss @@ -3,6 +3,7 @@ display: flex; flex-direction: row; gap: $pad-xsmall; + color: $ui-fleet-black-75; } &__list-item-details { diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileGraphic.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileGraphic.tsx index c75193dcb6cd..027bf94f46b6 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileGraphic.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileGraphic.tsx @@ -2,9 +2,6 @@ import React from "react"; import Graphic from "components/Graphic"; -const ALLOWED_FILE_TYPES_MESSAGE = - "Configuration profile (.mobileconfig and .json for macOS or .xml for Windows)"; - const ProfileGraphic = ({ baseClass, showMessage, @@ -14,13 +11,17 @@ const ProfileGraphic = ({ }) => (
{showMessage && ( - {ALLOWED_FILE_TYPES_MESSAGE} + Upload configuration profile +
+ .mobileconfig and .json for macOS, iOS, and iPadOS. +
+ .xml for Windows.
)}
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx index e8ff573ecefe..f1ed1acdd7b8 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx @@ -1,7 +1,6 @@ import React from "react"; import { IDropdownOption } from "interfaces/dropdownOption"; -import { snakeCase } from "lodash"; export const CUSTOM_TARGET_OPTIONS: IDropdownOption[] = [ { diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.tsx index 62727c9b2f84..4132a49a042a 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.tsx @@ -13,10 +13,10 @@ export const parseFile = async (file: File): Promise<[string, string]> => { return [name, "Windows"]; } case "mobileconfig": { - return [name, "macOS"]; + return [name, "macOS, iOS, iPadOS"]; } case "json": { - return [name, "macOS"]; + return [name, "macOS, iOS, iPadOS"]; } default: { throw new Error(`Invalid file type: ${ext}`); diff --git a/frontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsx b/frontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsx index 4e6d7ae31199..e18a4afca21e 100644 --- a/frontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsx +++ b/frontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsx @@ -18,7 +18,11 @@ import TurnOnMdmMessage from "../components/TurnOnMdmMessage/TurnOnMdmMessage"; import CurrentVersionSection from "./components/CurrentVersionSection"; import TargetSection from "./components/TargetSection"; -export type OSUpdatesSupportedPlatform = "darwin" | "windows"; +export type OSUpdatesSupportedPlatform = + | "darwin" + | "windows" + | "iOS" + | "iPadOS"; const baseClass = "os-updates"; @@ -89,6 +93,7 @@ const OSUpdates = ({ router, teamIdForApi }: IOSUpdates) => { // FIXME: Handle error states for app config and team config (need specifications for this). // mdm is not enabled for mac or windows. + if ( !config?.mdm.enabled_and_configured && !config?.mdm.windows_enabled_and_configured diff --git a/frontend/pages/ManageControlsPage/OSUpdates/components/CurrentVersionSection/CurrentVersionSection.tsx b/frontend/pages/ManageControlsPage/OSUpdates/components/CurrentVersionSection/CurrentVersionSection.tsx index f0c62ddc09bb..98ce7349ab73 100644 --- a/frontend/pages/ManageControlsPage/OSUpdates/components/CurrentVersionSection/CurrentVersionSection.tsx +++ b/frontend/pages/ManageControlsPage/OSUpdates/components/CurrentVersionSection/CurrentVersionSection.tsx @@ -79,10 +79,13 @@ const CurrentVersionSection = ({ return ; } - // We only want to show windows and mac versions atm. + // We only want to show windows mac, ios, ipados versions atm. const filteredOSVersionData = data.os_versions.filter((osVersion) => { return ( - osVersion.platform === "windows" || osVersion.platform === "darwin" + osVersion.platform === "windows" || + osVersion.platform === "darwin" || + osVersion.platform === "ios" || + osVersion.platform === "ipados" ); }) as IFilteredOperatingSystemVersion[]; diff --git a/frontend/pages/ManageControlsPage/OSUpdates/components/EmptyTargetForm/EmptyTargetForm.tsx b/frontend/pages/ManageControlsPage/OSUpdates/components/EmptyTargetForm/EmptyTargetForm.tsx new file mode 100644 index 000000000000..80d24adc39a3 --- /dev/null +++ b/frontend/pages/ManageControlsPage/OSUpdates/components/EmptyTargetForm/EmptyTargetForm.tsx @@ -0,0 +1,29 @@ +import React from "react"; + +import CustomLink from "components/CustomLink"; + +const baseClass = "empty-target-form"; + +interface IEmptyTargetFormProps { + targetPlatform: string; +} + +const EmptyTargetForm = ({ targetPlatform }: IEmptyTargetFormProps) => { + return ( +
+

+ {targetPlatform} updates are coming soon. +

+

+ Need to remotely encourage installation of {targetPlatform} updates?{" "} + +

+
+ ); +}; + +export default EmptyTargetForm; diff --git a/frontend/pages/ManageControlsPage/OSUpdates/components/EmptyTargetForm/index.ts b/frontend/pages/ManageControlsPage/OSUpdates/components/EmptyTargetForm/index.ts new file mode 100644 index 000000000000..284c5dbd3a0a --- /dev/null +++ b/frontend/pages/ManageControlsPage/OSUpdates/components/EmptyTargetForm/index.ts @@ -0,0 +1 @@ +export { default } from "./EmptyTargetForm"; diff --git a/frontend/pages/ManageControlsPage/OSUpdates/components/NudgePreview/NudgePreview.tsx b/frontend/pages/ManageControlsPage/OSUpdates/components/NudgePreview/NudgePreview.tsx index 813689d2051f..9f75ee13bb90 100644 --- a/frontend/pages/ManageControlsPage/OSUpdates/components/NudgePreview/NudgePreview.tsx +++ b/frontend/pages/ManageControlsPage/OSUpdates/components/NudgePreview/NudgePreview.tsx @@ -64,6 +64,12 @@ interface INudgePreviewProps { } const NudgePreview = ({ platform }: INudgePreviewProps) => { + const isSupportedPlatform = platform === "windows" || platform === "darwin"; + + if (!isSupportedPlatform) { + return null; + } + // FIXME: on slow connection the image loads after the text which looks weird and can cause a // mismatch between the text and the image when switching between platforms. We should load the // image first and then the text. diff --git a/frontend/pages/ManageControlsPage/OSUpdates/components/OSVersionTable/OSVersionTable.tsx b/frontend/pages/ManageControlsPage/OSUpdates/components/OSVersionTable/OSVersionTable.tsx index a0b3ef06b465..372430e0c036 100644 --- a/frontend/pages/ManageControlsPage/OSUpdates/components/OSVersionTable/OSVersionTable.tsx +++ b/frontend/pages/ManageControlsPage/OSUpdates/components/OSVersionTable/OSVersionTable.tsx @@ -39,7 +39,8 @@ const OSVersionTable = ({ defaultSortDirection={DEFAULT_SORT_DIRECTION} disableTableHeader disableCount - disablePagination + pageSize={8} + isClientSidePagination /> ); diff --git a/frontend/pages/ManageControlsPage/OSUpdates/components/PlatformTabs/PlatformTabs.tsx b/frontend/pages/ManageControlsPage/OSUpdates/components/PlatformTabs/PlatformTabs.tsx index af5432a941f7..3befce43aab7 100644 --- a/frontend/pages/ManageControlsPage/OSUpdates/components/PlatformTabs/PlatformTabs.tsx +++ b/frontend/pages/ManageControlsPage/OSUpdates/components/PlatformTabs/PlatformTabs.tsx @@ -5,6 +5,7 @@ import TabsWrapper from "components/TabsWrapper"; import MacOSTargetForm from "../MacOSTargetForm"; import WindowsTargetForm from "../WindowsTargetForm"; import { OSUpdatesSupportedPlatform } from "../../OSUpdates"; +import EmptyTargetForm from "../EmptyTargetForm"; const baseClass = "platform-tabs"; @@ -33,18 +34,40 @@ const PlatformTabs = ({ }: IPlatformTabsProps) => { // FIXME: This behaves unexpectedly when a user switches tabs or changes the teams dropdown while a form is // submitting. + + const PLATFORM_BY_INDEX: OSUpdatesSupportedPlatform[] = [ + "darwin", + "windows", + "iOS", + "iPadOS", + ]; + + const onTabChange = (index: number) => { + onSelectPlatform(PLATFORM_BY_INDEX[index]); + }; + return (
- onSelectPlatform(currentIndex === 0 ? "darwin" : "windows") - } + defaultIndex={PLATFORM_BY_INDEX.indexOf(selectedPlatform)} + onSelect={onTabChange} > - macOS - Windows + {/* Bolding text when the tab is active causes a layout shift so + we add a hidden pseudo element with the same text string */} + + macOS + + + Windows + + + iOS + + + iPadOS + + + + + + +
diff --git a/frontend/pages/ManageControlsPage/components/UploadList/_styles.scss b/frontend/pages/ManageControlsPage/components/UploadList/_styles.scss index 0f5017b10260..6a7cb20cd9ed 100644 --- a/frontend/pages/ManageControlsPage/components/UploadList/_styles.scss +++ b/frontend/pages/ManageControlsPage/components/UploadList/_styles.scss @@ -1,8 +1,12 @@ .upload-list { + border: 1px solid $ui-fleet-black-10; + border-radius: 4px; + &__header { padding: $pad-medium $pad-large; font-size: $x-small; border-bottom: 1px solid $ui-fleet-black-10; + background-color: $ui-off-white; } &__list { @@ -14,5 +18,9 @@ &__list-item { padding: $pad-medium $pad-large; border-bottom: 1px solid $ui-fleet-black-10; + + &:last-child { + border-bottom: initial; + } } } diff --git a/frontend/pages/SoftwarePage/helpers.ts b/frontend/pages/SoftwarePage/helpers.ts deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx index 367c76e113b1..90a3d609d929 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx @@ -465,11 +465,6 @@ const TeamDetailsWrapper = ({ enrollSecret={teamSecrets?.[0]?.secret} isAnyTeamSelected={isAnyTeamSelected} isLoading={isLoadingTeams} - // TODO: Currently, prepacked installers in Fleet Sandbox use the global enroll secret, - // and Fleet Sandbox runs Fleet Free so explicitly setting isSandboxMode here is an - // additional precaution/reminder to revisit this in connection with future changes. - // See https://github.com/fleetdm/fleet/issues/4970#issuecomment-1187679407. - isSandboxMode={false} onCancel={toggleAddHostsModal} openEnrollSecretModal={toggleManageEnrollSecretsModal} /> diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index 0c794a7cea83..d3d69af8d373 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -54,7 +54,7 @@ import { isValidSoftwareInstallStatus, SoftwareInstallStatus, } from "interfaces/software"; -import { API_NO_TEAM_ID, ITeam } from "interfaces/team"; +import { ITeam } from "interfaces/team"; import { IEmptyTableProps } from "interfaces/empty_table"; import { DiskEncryptionStatus, @@ -1258,21 +1258,15 @@ const ManageHostsPage = ({ ); const renderAddHostsModal = () => { - const enrollSecret = - // TODO: Currently, prepacked installers in Fleet Sandbox use the global enroll secret, - // and Fleet Sandbox runs Fleet Free so the isSandboxMode check here is an - // additional precaution/reminder to revisit this in connection with future changes. - // See https://github.com/fleetdm/fleet/issues/4970#issuecomment-1187679407. - isAnyTeamSelected && !isSandboxMode - ? teamSecrets?.[0].secret - : globalSecrets?.[0].secret; + const enrollSecret = isAnyTeamSelected + ? teamSecrets?.[0].secret + : globalSecrets?.[0].secret; return ( setShowEnrollSecretModal(true)} /> @@ -1734,7 +1728,6 @@ const ManageHostsPage = ({ } onClickEditLabel={onEditLabelClick} onClickDeleteLabel={toggleDeleteLabelModal} - isSandboxMode={isSandboxMode} /> {renderNoEnrollSecretBanner()} {renderTable()} diff --git a/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx index f44e71b6ca68..8f9fea2dfaf8 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx @@ -3,7 +3,6 @@ import ReactTooltip from "react-tooltip"; import classnames from "classnames"; import Button from "components/buttons/Button"; -import PremiumFeatureIconWithTooltip from "components/PremiumFeatureIconWithTooltip"; import Icon from "components/Icon"; import { IconNames } from "components/icons"; @@ -14,10 +13,7 @@ interface IFilterPillProps { onClear: () => void; icon?: IconNames; tooltipDescription?: string | ReactNode; - premiumFeatureTooltipDelayHide?: number; className?: string; - isSandboxMode?: boolean; - sandboxPremiumOnlyIcon?: boolean; } const baseClass = "filter-pill"; @@ -26,11 +22,8 @@ const FilterPill = ({ label, icon, tooltipDescription, - premiumFeatureTooltipDelayHide, className, onClear, - isSandboxMode = false, - sandboxPremiumOnlyIcon = false, }: IFilterPillProps) => { const baseClasses = classnames(baseClass, className); const labelClasses = classnames(`${baseClass}__label`, { @@ -47,12 +40,6 @@ const FilterPill = ({
{icon && } - {isSandboxMode && sandboxPremiumOnlyIcon && ( - - )} void; onClickEditLabel: (evt: React.MouseEvent) => void; onClickDeleteLabel: () => void; - isSandboxMode?: boolean; } /** @@ -137,7 +136,6 @@ const HostsFilterBlock = ({ onChangeSoftwareInstallStatusFilter, onClickEditLabel, onClickDeleteLabel, - isSandboxMode = false, }: IHostsFilterBlockProps) => { const renderLabelFilterPill = () => { if (selectedLabel) { @@ -402,10 +400,7 @@ const HostsFilterBlock = ({ handleClearFilter(["low_disk_space"])} - isSandboxMode={isSandboxMode} - sandboxPremiumOnlyIcon /> ); }; diff --git a/frontend/pages/hosts/details/HostDetailsPage/modals/DiskEncryptionKeyModal/DiskEncryptionKeyModal.tsx b/frontend/pages/hosts/details/HostDetailsPage/modals/DiskEncryptionKeyModal/DiskEncryptionKeyModal.tsx index 17a276686e64..9dae6b37ab3c 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/modals/DiskEncryptionKeyModal/DiskEncryptionKeyModal.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/modals/DiskEncryptionKeyModal/DiskEncryptionKeyModal.tsx @@ -8,14 +8,14 @@ import Modal from "components/Modal"; import Button from "components/buttons/Button"; import InputFieldHiddenContent from "components/forms/fields/InputFieldHiddenContent"; import DataError from "components/DataError"; -import { SupportedPlatform } from "interfaces/platform"; +import { QueryablePlatform } from "interfaces/platform"; const baseClass = "disk-encryption-key-modal"; // currently these are the only supported platforms for the disk encryption // key modal. export type ModalSupportedPlatform = Extract< - SupportedPlatform, + QueryablePlatform, "darwin" | "windows" >; diff --git a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTableConfig.tsx b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTableConfig.tsx index badef6d0b790..197c8603ccca 100644 --- a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTableConfig.tsx +++ b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTableConfig.tsx @@ -146,6 +146,10 @@ export const generateTableData = ( return makeWindowsRows(hostMDMData); case "darwin": return makeDarwinRows(hostMDMData); + case "ios": + return hostMDMData.profiles; + case "ipados": + return hostMDMData.profiles; default: return null; } diff --git a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx index bcb632f7c127..146809ad5628 100644 --- a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx +++ b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx @@ -432,7 +432,10 @@ const HostSummary = ({ renderIssues()} {isPremiumTier && renderHostTeam()} {/* Rendering of OS Settings data */} - {(platform === "darwin" || platform === "windows") && + {(platform === "darwin" || + platform === "windows" || + platform === "ios" || + platform === "ipados") && isPremiumTier && isConnectedToFleetMdm && // show if 1 - host is enrolled in Fleet MDM, and hostMdmProfiles && diff --git a/frontend/pages/hosts/details/cards/HostSummary/OSSettingsIndicator/OSSettingsIndicator.tsx b/frontend/pages/hosts/details/cards/HostSummary/OSSettingsIndicator/OSSettingsIndicator.tsx index 68043e52a7f2..bd129a20c5bb 100644 --- a/frontend/pages/hosts/details/cards/HostSummary/OSSettingsIndicator/OSSettingsIndicator.tsx +++ b/frontend/pages/hosts/details/cards/HostSummary/OSSettingsIndicator/OSSettingsIndicator.tsx @@ -31,9 +31,7 @@ type StatusDisplayOptions = Record< const STATUS_DISPLAY_OPTIONS: StatusDisplayOptions = { Verified: { iconName: "success", - tooltipText: - "The host applied all OS settings. Fleet verified with osquery. " + - "Declaration profiles are verified with DDM.", + tooltipText: "These hosts applied all OS settings. Fleet verified.", }, Verifying: { iconName: "success-outline", diff --git a/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx b/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx index dd4ed9ba99f2..f737f22cfd3b 100644 --- a/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx +++ b/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx @@ -14,7 +14,7 @@ import { QueryContext } from "context/query"; import { TableContext } from "context/table"; import { NotificationContext } from "context/notification"; import { getPerformanceImpactDescription } from "utilities/helpers"; -import { SupportedPlatform, SelectedPlatform } from "interfaces/platform"; +import { QueryablePlatform, SelectedPlatform } from "interfaces/platform"; import { IEnhancedQuery, IQueryKeyQueriesLoadAll, @@ -54,7 +54,7 @@ interface IManageQueriesPageProps { }; } -const getPlatforms = (queryString: string): SupportedPlatform[] => { +const getPlatforms = (queryString: string): QueryablePlatform[] => { const { platforms } = checkPlatformCompatibility(queryString); return platforms ?? []; diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx index 81ff22dcf883..b38b394735aa 100644 --- a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx +++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx @@ -12,7 +12,7 @@ import { IEnhancedQuery, ISchedulableQuery, } from "interfaces/schedulable_query"; -import { SupportedPlatform } from "interfaces/platform"; +import { QueryablePlatform } from "interfaces/platform"; import { API_ALL_TEAMS_ID } from "interfaces/team"; import Icon from "components/Icon"; @@ -81,7 +81,7 @@ interface IBoolCellProps extends IRowProps { } interface IPlatformCellProps extends IRowProps { cell: { - value: SupportedPlatform[]; + value: QueryablePlatform[]; }; } diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx index ae9931afb6e5..2d3c83a8d833 100644 --- a/frontend/router/index.tsx +++ b/frontend/router/index.tsx @@ -133,6 +133,8 @@ const routes = ( + + diff --git a/frontend/router/paths.ts b/frontend/router/paths.ts index ae2b39b2fd67..0aedc5ee3029 100644 --- a/frontend/router/paths.ts +++ b/frontend/router/paths.ts @@ -23,6 +23,8 @@ export default { DASHBOARD_MAC: `${URL_PREFIX}/dashboard/mac`, DASHBOARD_WINDOWS: `${URL_PREFIX}/dashboard/windows`, DASHBOARD_CHROME: `${URL_PREFIX}/dashboard/chrome`, + DASHBOARD_IOS: `${URL_PREFIX}/dashboard/ios`, + DASHBOARD_IPADOS: `${URL_PREFIX}/dashboard/ipados`, // Admin pages ADMIN_SETTINGS: `${URL_PREFIX}/settings`, diff --git a/frontend/services/entities/hosts.ts b/frontend/services/entities/hosts.ts index 259d9ae9a217..ba7e0dc7abf7 100644 --- a/frontend/services/entities/hosts.ts +++ b/frontend/services/entities/hosts.ts @@ -9,7 +9,6 @@ import { reconcileMutuallyExclusiveHostParams, reconcileMutuallyInclusiveHostParams, } from "utilities/url"; -import { SelectedPlatform } from "interfaces/platform"; import { IHostSoftware, ISoftware, @@ -23,7 +22,7 @@ import { MdmEnrollmentStatus, } from "interfaces/mdm"; import { IMunkiIssuesAggregate } from "interfaces/macadmins"; -import { PolicyResponse } from "utilities/constants"; +import { PlatformValueOptions, PolicyResponse } from "utilities/constants"; export interface ISortOption { key: string; @@ -206,7 +205,7 @@ const getSortParams = (sortOptions?: ISortOption[]) => { }; }; -const createMdmParams = (platform?: SelectedPlatform, teamId?: number) => { +const createMdmParams = (platform?: PlatformValueOptions, teamId?: number) => { if (platform === "all") { return buildQueryStringFromParams({ team_id: teamId }); } @@ -535,7 +534,7 @@ export default { return sendRequest("GET", HOST_MDM(id)); }, - getMdmSummary: (platform?: SelectedPlatform, teamId?: number) => { + getMdmSummary: (platform?: PlatformValueOptions, teamId?: number) => { const { MDM_SUMMARY } = endpoints; if (!platform || platform === "linux") { diff --git a/frontend/services/entities/operating_systems.ts b/frontend/services/entities/operating_systems.ts index 29ba06b14103..1611243bfd91 100644 --- a/frontend/services/entities/operating_systems.ts +++ b/frontend/services/entities/operating_systems.ts @@ -2,7 +2,7 @@ import sendRequest from "services"; import endpoints from "utilities/endpoints"; import { IOperatingSystemVersion } from "interfaces/operating_system"; -import { OsqueryPlatform } from "interfaces/platform"; +import { Platform } from "interfaces/platform"; import { buildQueryStringFromParams } from "utilities/url"; import { API_NO_TEAM_ID } from "interfaces/team"; @@ -11,10 +11,12 @@ export const OS_VERSIONS_API_SUPPORTED_PLATFORMS = [ "darwin", "windows", "chrome", + "ios", + "ipados", ]; export interface IGetOSVersionsQueryParams { - platform?: OsqueryPlatform; + platform?: Platform | ""; teamId?: number; os_name?: string; os_version?: string; diff --git a/frontend/utilities/constants.tsx b/frontend/utilities/constants.tsx index f8c484e9b980..b3267c4580a4 100644 --- a/frontend/utilities/constants.tsx +++ b/frontend/utilities/constants.tsx @@ -1,6 +1,5 @@ import URL_PREFIX from "router/url_prefix"; -import { OsqueryPlatform } from "interfaces/platform"; -import paths from "router/paths"; +import { DisplayPlatform, Platform } from "interfaces/platform"; import { ISchedulableQuery } from "interfaces/schedulable_query"; import React from "react"; import { IDropdownOption } from "interfaces/dropdownOption"; @@ -200,6 +199,8 @@ const PLATFORM_LABEL_NAMES_FROM_API = [ "Red Hat Linux", "Ubuntu Linux", "chrome", + "iOS", + "iPadOS", ] as const; type PlatformLabelNameFromAPI = typeof PLATFORM_LABEL_NAMES_FROM_API[number]; @@ -210,7 +211,7 @@ export const isPlatformLabelNameFromAPI = ( return PLATFORM_LABEL_NAMES_FROM_API.includes(s as PlatformLabelNameFromAPI); }; -export const PLATFORM_DISPLAY_NAMES: Record = { +export const PLATFORM_DISPLAY_NAMES: Record = { darwin: "macOS", macOS: "macOS", windows: "Windows", @@ -219,6 +220,8 @@ export const PLATFORM_DISPLAY_NAMES: Record = { Linux: "Linux", chrome: "ChromeOS", ChromeOS: "ChromeOS", + ios: "iOS", + ipados: "iPadOS", } as const; // as returned by the TARGETS API; based on display_text @@ -234,17 +237,10 @@ export const PLATFORM_LABEL_DISPLAY_NAMES: Record< "Red Hat Linux": "Red Hat Linux", "Ubuntu Linux": "Ubuntu Linux", chrome: "ChromeOS", + iOS: "iOS", + iPadOS: "iPadOS", } as const; -export const PLATFORM_LABEL_DISPLAY_ORDER = [ - "macOS", - "All Linux", - "CentOS Linux", - "Red Hat Linux", - "Ubuntu Linux", - "MS Windows", -] as const; - export const PLATFORM_LABEL_DISPLAY_TYPES: Record< PlatformLabelNameFromAPI, string @@ -257,12 +253,14 @@ export const PLATFORM_LABEL_DISPLAY_TYPES: Record< "Red Hat Linux": "platform", "Ubuntu Linux": "platform", chrome: "platform", + iOS: "platform", + iPadOS: "platform", } as const; export const PLATFORM_TYPE_ICONS: Record< Extract< PlatformLabelNameFromAPI, - "All Linux" | "macOS" | "MS Windows" | "chrome" + "All Linux" | "macOS" | "MS Windows" | "chrome" | "iOS" | "iPadOS" >, IconNames > = { @@ -270,47 +268,36 @@ export const PLATFORM_TYPE_ICONS: Record< macOS: "darwin", "MS Windows": "windows", chrome: "chrome", + iOS: "iOS", + iPadOS: "iPadOS", } as const; export const hasPlatformTypeIcon = ( s: string ): s is Extract< PlatformLabelNameFromAPI, - "All Linux" | "macOS" | "MS Windows" | "chrome" + "All Linux" | "macOS" | "MS Windows" | "chrome" | "iOS" | "iPadOS" > => { return !!PLATFORM_TYPE_ICONS[s as keyof typeof PLATFORM_TYPE_ICONS]; }; -interface IPlatformDropdownOptions { - label: "All" | "Windows" | "Linux" | "macOS" | "ChromeOS"; - value: "all" | "windows" | "linux" | "darwin" | "chrome" | ""; - path?: string; +export type PlatformLabelOptions = DisplayPlatform | "All"; + +export type PlatformValueOptions = Platform | "all"; + +/** Scheduled queries do not support ChromeOS, iOS, or iPadOS */ +interface ISchedulePlatformDropdownOptions { + label: Exclude; + value: Exclude | ""; } -export const PLATFORM_DROPDOWN_OPTIONS: IPlatformDropdownOptions[] = [ - { label: "All", value: "all", path: paths.DASHBOARD }, - { label: "macOS", value: "darwin", path: paths.DASHBOARD_MAC }, - { label: "Windows", value: "windows", path: paths.DASHBOARD_WINDOWS }, - { label: "Linux", value: "linux", path: paths.DASHBOARD_LINUX }, - { label: "ChromeOS", value: "chrome", path: paths.DASHBOARD_CHROME }, -]; -// Schedules does not support ChromeOS -export const SCHEDULE_PLATFORM_DROPDOWN_OPTIONS: IPlatformDropdownOptions[] = [ +export const SCHEDULE_PLATFORM_DROPDOWN_OPTIONS: ISchedulePlatformDropdownOptions[] = [ { label: "All", value: "" }, // API empty string runs on all platforms { label: "macOS", value: "darwin" }, { label: "Windows", value: "windows" }, { label: "Linux", value: "linux" }, ]; -// Builtin label names returned from API -export const PLATFORM_NAME_TO_LABEL_NAME = { - all: "", - darwin: "macOS", - windows: "MS Windows", - linux: "All Linux", - chrome: "chrome", -}; - export const HOSTS_SEARCH_BOX_PLACEHOLDER = "Search name, hostname, UUID, serial number, or private IP address"; diff --git a/frontend/utilities/sql_tools.ts b/frontend/utilities/sql_tools.ts index e8255767af90..edf845fc0d79 100644 --- a/frontend/utilities/sql_tools.ts +++ b/frontend/utilities/sql_tools.ts @@ -3,11 +3,11 @@ import sqliteParser from "sqlite-parser"; import { intersection, isPlainObject } from "lodash"; import { osqueryTablesAvailable } from "utilities/osquery_tables"; import { - OsqueryPlatform, MACADMINS_EXTENSION_TABLES, SUPPORTED_PLATFORMS, - SupportedPlatform, + QueryablePlatform, } from "interfaces/platform"; +import { TableSchemaPlatform } from "interfaces/osquery_table"; type IAstNode = Record; @@ -15,10 +15,10 @@ type IAstNode = Record; // TODO: Is it ever possible that osquery_tables.json would be missing name or platforms? interface IOsqueryTable { name: string; - platforms: OsqueryPlatform[]; + platforms: TableSchemaPlatform[]; } -type IPlatformDictionary = Record; +type IPlatformDictionary = Record; const platformsByTableDictionary: IPlatformDictionary = (osqueryTablesAvailable as IOsqueryTable[]).reduce( (dictionary: IPlatformDictionary, osqueryTable) => { @@ -57,7 +57,7 @@ const _visit = ( const filterCompatiblePlatforms = ( sqlTables: string[] -): SupportedPlatform[] => { +): QueryablePlatform[] => { if (!sqlTables.length) { return [...SUPPORTED_PLATFORMS]; // if a query has no tables but is still syntatically valid sql, it is treated as compatible with all platforms } @@ -147,7 +147,7 @@ export const checkTable = ( export const checkPlatformCompatibility = ( sqlString: string, includeCteTables = false -): { platforms: SupportedPlatform[] | null; error: Error | null } => { +): { platforms: QueryablePlatform[] | null; error: Error | null } => { let sqlTables: string[] | undefined; try { // get tables from str diff --git a/frontend/utilities/strings/stringUtils.ts b/frontend/utilities/strings/stringUtils.ts index e7e205d98eda..6d0c8756feee 100644 --- a/frontend/utilities/strings/stringUtils.ts +++ b/frontend/utilities/strings/stringUtils.ts @@ -20,6 +20,8 @@ const capitalizeRole = (str: UserRole): UserRole => { export const STYLIZATIONS_AND_ACRONYMS = [ "macOS", + "iOS", + "iPadOS", "osquery", "MySQL", "MDM", @@ -29,7 +31,7 @@ export const STYLIZATIONS_AND_ACRONYMS = [ ]; // fleetdm.com/handbook/marketing/content-style-guide#sentence-case -// * doesn't recognize proper nouns! +/** Does not recognize proper nouns! */ export const enforceFleetSentenceCasing = (s: string) => { const resArr = s.split(" ").map((word, i) => { if (!STYLIZATIONS_AND_ACRONYMS.includes(word)) {