Update dashboard, manage hosts, and host details UI for Android MDM feature (#26577)

This commit is contained in:
Sarah Gillespie
2025-02-26 12:03:56 -06:00
committed by GitHub
parent e336cdebba
commit 7a95f59f4a
26 changed files with 388 additions and 174 deletions
@@ -48,6 +48,31 @@ const displayIncompatibilityText = (err: Error) => {
}
};
// const tipContent = (
// <>
// Estimated compatibility based on the <br />
// tables used in the query. Querying <br />
// iPhones, iPads, and Android hosts is not <br />
// supported.
// </>
// );
// TODO(android): replace with the above tipContent when Android feature flag is removed
const tipContent = (
<>
Estimated compatibility based on the <br />
tables used in the query. Check the <br />
table documentation (schema) to verify <br />
compatibility of individual columns.
<br />
<br />
Only live queries are supported on ChromeOS.
<br />
<br />
Querying iPhones & iPads is not supported.
</>
);
const PlatformCompatibility = ({
compatiblePlatforms,
error,
@@ -84,22 +109,7 @@ const PlatformCompatibility = ({
return (
<div className={baseClass}>
<b>
<TooltipWrapper
tipContent={
<>
Estimated compatibility based on the <br />
tables used in the query. Check the <br />
table documentation (schema) to verify <br />
compatibility of individual columns.
<br />
<br />
Only live queries are supported on ChromeOS.
<br />
<br />
Querying iPhones & iPads is not supported.
</>
}
>
<TooltipWrapper tipContent={tipContent}>
Compatible with:
</TooltipWrapper>
</b>
+30
View File
@@ -0,0 +1,30 @@
import React from "react";
import { COLORS, Colors } from "styles/var/colors";
import { ICON_SIZES, IconSizes } from "styles/var/icon_sizes";
const Android = ({
size = "medium",
color = "ui-fleet-black-75",
}: {
size: IconSizes;
color?: Colors;
}) => {
return (
<svg
width={ICON_SIZES[size]}
height={ICON_SIZES[size]}
viewBox="0 0 37 36"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M36.2229 27.609C36.2264 27.6311 36.2299 27.6532 36.2334 27.6755L36.2308 27.6771C36.2708 27.9395 36.3052 28.2038 36.334 28.4697H0.333984C0.362614 28.2048 0.397264 27.9406 0.437906 27.6773L0.441441 27.6544C0.451523 27.589 0.461687 27.5231 0.472641 27.4579C0.547458 27.0082 0.638975 26.5615 0.747 26.1186C1.09124 24.714 1.60318 23.356 2.2718 22.0737C2.65903 21.3295 3.09792 20.6134 3.58523 19.9305C4.47919 18.6776 5.52815 17.5429 6.70711 16.5534C7.3662 16.0006 8.06372 15.4953 8.79441 15.0413C8.79303 15.0386 8.7914 15.0359 8.78975 15.0332C8.7882 15.0307 8.78663 15.0281 8.78527 15.0255C8.60378 14.7119 8.42216 14.3985 8.24054 14.0851C8.05879 13.7715 7.87703 13.4578 7.69542 13.144C7.59975 12.979 7.50411 12.814 7.40846 12.649C7.14896 12.2014 6.88945 11.7538 6.62948 11.3061C6.37513 10.8658 6.12018 10.4259 5.86462 9.98624C5.8043 9.88189 5.75719 9.77389 5.72077 9.66336C5.59985 9.28573 5.61806 8.8773 5.77209 8.51192C5.80163 8.44077 5.83692 8.37144 5.87644 8.30478C6.01706 8.06962 6.21412 7.87324 6.44977 7.73342C6.66437 7.60538 6.90529 7.52786 7.1543 7.50674C7.4671 7.47933 7.78122 7.54172 8.0598 7.68659C8.33838 7.83146 8.56986 8.05279 8.72705 8.32461C8.98116 8.76435 9.23625 9.20492 9.49191 9.64452L10.5573 11.4825C10.747 11.8104 10.9371 12.138 11.1271 12.4657C11.3007 12.7651 11.4744 13.0645 11.6478 13.364C11.6703 13.4037 11.6933 13.4433 11.7164 13.4829C11.7306 13.5074 11.7449 13.5318 11.7589 13.5563C11.9646 13.4752 12.1718 13.3982 12.3805 13.3254C14.1903 12.6954 16.1302 12.3442 18.1492 12.3241C18.2118 12.3233 18.2732 12.3233 18.3354 12.3233C20.6062 12.3233 22.7786 12.7409 24.7811 13.5045C24.8326 13.5241 24.8836 13.5446 24.9351 13.5652L24.9459 13.5695C24.9738 13.5204 25.0021 13.4718 25.0302 13.4233C25.0419 13.4033 25.0535 13.3832 25.0651 13.3632C25.2549 13.0353 25.4449 12.7077 25.6349 12.38C25.8086 12.0806 25.9823 11.7812 26.1557 11.4816L27.2211 9.64382L27.238 9.61462C27.4869 9.18471 27.7363 8.75391 27.9858 8.32391C28.1165 8.09796 28.299 7.90625 28.5183 7.76452C28.7375 7.62278 28.9872 7.53506 29.2469 7.50857C29.3506 7.49806 29.455 7.49722 29.5588 7.50603C29.8074 7.52787 30.0478 7.60524 30.2625 7.73244C30.5665 7.91219 30.8043 8.18522 30.9407 8.51094C31.0404 8.74712 31.0838 9.00322 31.0677 9.25905C31.0515 9.51489 30.9762 9.76348 30.8476 9.98525C30.5937 10.4248 30.3386 10.8656 30.0829 11.3052L29.0175 13.1428C28.8348 13.4587 28.6517 13.7742 28.4687 14.0898C28.288 14.4013 28.1074 14.7128 27.927 15.0244C27.9235 15.0302 27.9203 15.0361 27.9171 15.0421C27.9139 15.048 27.9108 15.0539 27.9073 15.0596C29.0636 15.781 30.135 16.6304 31.1013 17.5915C32.4247 18.9062 33.5371 20.4175 34.399 22.0718C35.2795 23.7602 35.8867 25.5775 36.1981 27.4561C36.2067 27.507 36.2147 27.5578 36.2229 27.609ZM27.9322 21.0133C28.5237 21.9021 28.4189 23.0116 27.6987 23.4911C26.9788 23.9706 25.9154 23.6388 25.3243 22.7497C24.7329 21.8609 24.8378 20.7513 25.5581 20.2718C26.2781 19.7924 27.3412 20.1241 27.9322 21.0133ZM11.1423 20.275C11.8626 20.7546 11.9675 21.8639 11.376 22.7528C10.785 23.6419 9.72172 23.9737 9.00144 23.4942C8.28144 23.0147 8.17667 21.9053 8.76772 21.0161C9.35919 20.1274 10.4219 19.7955 11.1423 20.275Z"
fill={COLORS[color]}
/>
</svg>
);
};
export default Android;
+2
View File
@@ -69,6 +69,7 @@ import AutomaticSelfService from "./AutomaticSelfService";
import User from "./User";
import InfoOutline from "./InfoOutline";
import GitOpsMode from "./GitOpsMode";
import Android from "./Android";
// a mapping of the usable names of icons to the icon source.
export const ICON_MAP = {
@@ -133,6 +134,7 @@ export const ICON_MAP = {
iPadOS,
ios: iOS,
iOS,
android: Android,
"premium-feature": PremiumFeature,
profile: Profile,
download: Download,
+28 -10
View File
@@ -11,24 +11,27 @@ export const PLATFORM_DISPLAY_NAMES = {
windows: "Windows",
linux: "Linux",
chrome: "ChromeOS",
android: "Android",
...APPLE_PLATFORM_DISPLAY_NAMES,
} as const;
export const QUERYABLE_PLATFORMS = [
"darwin",
"windows",
"linux",
"chrome",
] as const;
export const NON_QUERYABLE_PLATFORMS = ["ios", "ipados", "android"] as const;
export type Platform = keyof typeof PLATFORM_DISPLAY_NAMES;
export type DisplayPlatform = typeof PLATFORM_DISPLAY_NAMES[keyof typeof PLATFORM_DISPLAY_NAMES];
export type QueryableDisplayPlatform = Exclude<
DisplayPlatform,
"iOS" | "iPadOS"
typeof PLATFORM_DISPLAY_NAMES[typeof NON_QUERYABLE_PLATFORMS[number]]
>;
export type QueryablePlatform = Exclude<Platform, "ios" | "ipados">;
export const QUERYABLE_PLATFORMS: QueryablePlatform[] = [
"darwin",
"windows",
"linux",
"chrome",
];
export type QueryablePlatform = typeof QUERYABLE_PLATFORMS[number];
export const isQueryablePlatform = (
platform: string | undefined
@@ -111,7 +114,8 @@ export type HostPlatform =
| typeof HOST_LINUX_PLATFORMS[number]
| typeof HOST_APPLE_PLATFORMS[number]
| "windows"
| "chrome";
| "chrome"
| "android";
/**
* Checks if the provided platform is a Linux-like OS. We can recieve many
@@ -133,6 +137,14 @@ export const isAppleDevice = (platform: string) => {
export const isIPadOrIPhone = (platform: string | HostPlatform) =>
["ios", "ipados"].includes(platform);
export const isAndroid = (
platform: string | HostPlatform
): platform is "android" => platform === "android";
/** isMobilePlatform checks if the platform is an iPad or iPhone or Android. */
export const isMobilePlatform = (platform: string | HostPlatform) =>
isIPadOrIPhone(platform) || isAndroid(platform);
export const DISK_ENCRYPTION_SUPPORTED_LINUX_PLATFORMS = [
"ubuntu", // covers Kubuntu
"rhel", // *included here to support Fedora systems. Necessary to cross-check with `os_versions` as well to confrim host is Fedora and not another, non-support rhel-like platform.
@@ -161,6 +173,9 @@ export const platformSupportsDiskEncryption = (
/** os_version necessary to differentiate Fedora from other rhel-like platforms */
os_version?: string
) => {
if (isAndroid(platform)) {
return false;
}
if (platform === "rhel") {
return !!os_version && os_version.toLowerCase().includes("fedora");
}
@@ -179,6 +194,9 @@ export const isOsSettingsDisplayPlatform = (
platform: HostPlatform,
os_version: string
) => {
if (isAndroid(platform)) {
return false;
}
if (platform === "rhel") {
return !!os_version && os_version.toLowerCase().includes("fedora");
}
+1 -1
View File
@@ -9,7 +9,7 @@ export interface IScript {
}
export const isScriptSupportedPlatform = (hostPlatform: string) =>
["darwin", "windows", ...HOST_LINUX_PLATFORMS].includes(hostPlatform); // excludes chrome, ios, ipados see also https://github.com/fleetdm/fleet/blob/5a21e2cfb029053ddad0508869eb9f1f23997bf2/server/fleet/hosts.go#L775
["darwin", "windows", ...HOST_LINUX_PLATFORMS].includes(hostPlatform); // excludes chrome, ios, ipados, android see also https://github.com/fleetdm/fleet/blob/5a21e2cfb029053ddad0508869eb9f1f23997bf2/server/fleet/hosts.go#L775
export type IScriptExecutionStatus = "ran" | "pending" | "error";
+25 -1
View File
@@ -137,6 +137,7 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
const [chromeCount, setChromeCount] = useState(0);
const [iosCount, setIosCount] = useState(0);
const [ipadosCount, setIpadosCount] = useState(0);
const [androidCount, setAndroidCount] = useState(0);
const [missingCount, setMissingCount] = useState(0);
const [lowDiskSpaceCount, setLowDiskSpaceCount] = useState(0);
const [showActivityFeedTitle, setShowActivityFeedTitle] = useState(false);
@@ -191,6 +192,14 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
IConfig
>(["config"], () => configAPI.loadAll(), { ...DEFAULT_USE_QUERY_OPTIONS });
// TODO(android): remove this when the feature flag is removed
const platformOptions = useMemo(() => {
if (!config?.android_enabled) {
return PLATFORM_DROPDOWN_OPTIONS.filter((o) => o.value !== "android");
}
return [...PLATFORM_DROPDOWN_OPTIONS];
}, [config?.android_enabled]);
const { data: teams, isLoading: isLoadingTeams } = useQuery<
ILoadTeamsResponse,
Error,
@@ -242,12 +251,17 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
(platform: IHostSummaryPlatforms) => platform.platform === "ipados"
) || { platform: "ipados", hosts_count: 0 };
const android = data.platforms?.find(
(platform: IHostSummaryPlatforms) => platform.platform === "android"
) || { platform: "android", 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);
setAndroidCount(android.hosts_count);
setShowHostsUI(true);
},
}
@@ -540,6 +554,7 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
const HostCountCards = (
<>
<PlatformHostCounts
androidDevEnabled={!!config?.android_enabled}
currentTeamId={teamIdForApi}
macCount={macCount}
windowsCount={windowsCount}
@@ -547,6 +562,7 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
chromeCount={chromeCount}
iosCount={iosCount}
ipadosCount={ipadosCount}
androidCount={androidCount}
builtInLabels={labels}
selectedPlatform={selectedPlatform}
errorHosts={!!errorHosts}
@@ -764,6 +780,12 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
</>
);
const androidLayout = () => (
<>
{showMdmCard && <div className={`${baseClass}__section`}>{MDMCard}</div>}
</>
);
const renderCards = () => {
switch (selectedPlatform) {
case "darwin":
@@ -778,6 +800,8 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
return iosLayout();
case "ipados":
return ipadosLayout();
case "android":
return androidLayout();
default:
return allLayout();
}
@@ -861,7 +885,7 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
name="platform-filter"
value={selectedPlatform || ""}
className={`${baseClass}__platform-filter`}
options={PLATFORM_DROPDOWN_OPTIONS}
options={platformOptions}
onChange={(option: SingleValue<CustomOptionType>) => {
const selectedPlatformOption = PLATFORM_DROPDOWN_OPTIONS.find(
(platform) => platform.value === option?.value
+5 -14
View File
@@ -1,18 +1,7 @@
import paths from "router/paths";
import {
PlatformLabelOptions,
PlatformValueOptions,
} from "utilities/constants";
interface IPlatformDropdownOptions {
label: PlatformLabelOptions;
value: PlatformValueOptions;
path: string;
}
/** Select platform */
export const PLATFORM_DROPDOWN_OPTIONS: IPlatformDropdownOptions[] = [
export const PLATFORM_DROPDOWN_OPTIONS = [
{ label: "All", value: "all", path: paths.DASHBOARD },
{ label: "macOS", value: "darwin", path: paths.DASHBOARD_MAC },
{ label: "Windows", value: "windows", path: paths.DASHBOARD_WINDOWS },
@@ -20,7 +9,8 @@ export const PLATFORM_DROPDOWN_OPTIONS: IPlatformDropdownOptions[] = [
{ label: "ChromeOS", value: "chrome", path: paths.DASHBOARD_CHROME },
{ label: "iOS", value: "ios", path: paths.DASHBOARD_IOS },
{ label: "iPadOS", value: "ipados", path: paths.DASHBOARD_IPADOS },
];
{ label: "Android", value: "android", path: paths.DASHBOARD_ANDROID },
] as const;
/** Selected platform value mapped to built in label name */
export const PLATFORM_NAME_TO_LABEL_NAME = {
@@ -30,7 +20,8 @@ export const PLATFORM_NAME_TO_LABEL_NAME = {
chrome: "chrome",
ios: "iOS",
ipados: "iPadOS",
};
android: "Android",
} as const;
/** Premium feature, Gb must be set between 1-100 */
export const LOW_DISK_SPACE_GB = 32;
@@ -66,7 +66,8 @@ const MetricsHostCounts = ({
{selectedPlatform === "all" && TotalHostsCard}
{isPremiumTier &&
selectedPlatform !== "ios" &&
selectedPlatform !== "ipados" && (
selectedPlatform !== "ipados" &&
selectedPlatform !== "android" && (
<>
{MissingHostsCard}
{LowDiskSpaceHostsCard}
@@ -12,6 +12,7 @@ import HostCountCard from "../../cards/HostCountCard";
const baseClass = "platform-host-counts";
interface IPlatformHostCountsProps {
androidDevEnabled: boolean; // TODO(android): remove when feature flag is removed
currentTeamId: number | undefined;
macCount: number;
windowsCount: number;
@@ -19,6 +20,7 @@ interface IPlatformHostCountsProps {
chromeCount: number;
iosCount: number;
ipadosCount: number;
androidCount: number;
builtInLabels?: IHostSummary["builtin_labels"];
errorHosts: boolean;
selectedPlatform?: PlatformValueOptions;
@@ -26,6 +28,7 @@ interface IPlatformHostCountsProps {
}
const PlatformHostCounts = ({
androidDevEnabled,
currentTeamId,
macCount,
windowsCount,
@@ -33,6 +36,7 @@ const PlatformHostCounts = ({
chromeCount,
iosCount,
ipadosCount,
androidCount,
builtInLabels,
errorHosts,
selectedPlatform,
@@ -187,6 +191,34 @@ const PlatformHostCounts = ({
);
};
const renderAndroidCount = (teamId?: number) => {
if (!androidDevEnabled) {
// TODO(android): remove when feature flag is removed
return null;
}
const androidLabelId = getBuiltinLabelId("android");
if (hidePlatformCard(androidCount)) {
return null;
}
if (androidLabelId === undefined) {
return <></>;
}
return (
<HostCountCard
iconName="android"
count={androidCount}
title="Android"
path={PATHS.MANAGE_HOSTS_LABEL(androidLabelId).concat(
teamId !== undefined ? `?team_id=${teamId}` : ""
)}
/>
);
};
const renderCounts = (teamId?: number) => {
switch (selectedPlatform) {
case "darwin":
@@ -201,7 +233,10 @@ const PlatformHostCounts = ({
return renderIosCount(teamId);
case "ipados":
return renderIpadosCount(teamId);
case "android":
return renderAndroidCount(teamId);
default:
// TODO(android): responsive layout with variable column widths (see figma for 2x2x3 grid)
return (
<>
{renderMacCard(teamId)}
@@ -210,6 +245,7 @@ const PlatformHostCounts = ({
{renderChromeCard(teamId)}
{renderIosCount(teamId)}
{renderIpadosCount(teamId)}
{renderAndroidCount(teamId)}
</>
);
}
@@ -6,7 +6,7 @@ import { AppContext } from "context/app";
import { IConfig } from "interfaces/config";
import { ITeamConfig } from "interfaces/team";
import { ApplePlatform } from "interfaces/platform";
import { ApplePlatform, isAndroid } from "interfaces/platform";
import configAPI from "services/entities/config";
import teamsAPI, { ILoadTeamResponse } from "services/entities/teams";
@@ -22,11 +22,13 @@ import { parseOSUpdatesCurrentVersionsQueryParams } from "./components/CurrentVe
export type OSUpdatesSupportedPlatform = ApplePlatform | "windows";
export type OSUpdatesTargetPlatform = OSUpdatesSupportedPlatform | "android"; // used for displaying "coming soon" messaging
const baseClass = "os-updates";
const getSelectedPlatform = (
const getDefaultSelectedPlatform = (
appConfig: IConfig | null
): OSUpdatesSupportedPlatform => {
): OSUpdatesTargetPlatform => {
// We dont have the data ready yet so we default to mac.
// This is usually when the users first comes to this page.
if (appConfig === null) return "darwin";
@@ -34,7 +36,7 @@ const getSelectedPlatform = (
// if the mac mdm is enable and configured we check the app config to see if
// the mdm for mac is enabled. If it is, it does not matter if windows is
// enabled and configured and we will always return "mac".
return appConfig.mdm.enabled_and_configured ? "darwin" : "windows";
return appConfig.mdm.enabled_and_configured ? "darwin" : "windows"; // TODO(android): adjust this when android is supported
};
interface IOSUpdates {
@@ -49,7 +51,7 @@ const OSUpdates = ({ router, teamIdForApi, queryParams }: IOSUpdates) => {
const [
selectedPlatformTab,
setSelectedPlatformTab,
] = useState<OSUpdatesSupportedPlatform | null>(null);
] = useState<OSUpdatesTargetPlatform | null>(null);
const {
isError: isErrorConfig,
@@ -102,7 +104,8 @@ const OSUpdates = ({ router, teamIdForApi, queryParams }: IOSUpdates) => {
// If the user has not selected a platform yet, we default to the platform that
// is enabled and configured.
const selectedPlatform = selectedPlatformTab || getSelectedPlatform(config);
const selectedPlatform =
selectedPlatformTab || getDefaultSelectedPlatform(config);
return (
<div className={baseClass}>
@@ -131,9 +134,11 @@ const OSUpdates = ({ router, teamIdForApi, queryParams }: IOSUpdates) => {
refetchTeamConfig={refetchTeamConfig}
/>
</div>
<div className={`${baseClass}__nudge-preview`}>
<EndUserOSRequirementPreview platform={selectedPlatform} />
</div>
{!isAndroid(selectedPlatform) && (
<div className={`${baseClass}__nudge-preview`}>
<EndUserOSRequirementPreview platform={selectedPlatform} />
</div>
)}
</div>
</div>
);
@@ -1,9 +1,11 @@
import React from "react";
import { Tab, TabList, TabPanel, Tabs } from "react-tabs";
import TabsWrapper from "components/TabsWrapper";
import CustomLink from "components/CustomLink";
import { SUPPORT_LINK } from "utilities/constants";
import WindowsTargetForm from "../WindowsTargetForm";
import { OSUpdatesSupportedPlatform } from "../../OSUpdates";
import { OSUpdatesTargetPlatform } from "../../OSUpdates";
import AppleOSTargetForm from "../AppleOSTargetForm";
const baseClass = "platform-tabs";
@@ -18,11 +20,12 @@ interface IPlatformTabsProps {
defaultIPadOSDeadline: string;
defaultWindowsDeadlineDays: string;
defaultWindowsGracePeriodDays: string;
selectedPlatform: OSUpdatesSupportedPlatform;
onSelectPlatform: (platform: OSUpdatesSupportedPlatform) => void;
selectedPlatform: OSUpdatesTargetPlatform;
onSelectPlatform: (platform: OSUpdatesTargetPlatform) => void;
refetchAppConfig: () => void;
refetchTeamConfig: () => void;
isWindowsMdmEnabled: boolean;
isAndroidMdmEnabled: boolean;
}
const PlatformTabs = ({
@@ -40,23 +43,28 @@ const PlatformTabs = ({
refetchAppConfig,
refetchTeamConfig,
isWindowsMdmEnabled,
isAndroidMdmEnabled,
}: 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[] = isWindowsMdmEnabled
const platformByIndex: OSUpdatesTargetPlatform[] = isWindowsMdmEnabled
? ["darwin", "windows", "ios", "ipados"]
: ["darwin", "ios", "ipados"];
if (isAndroidMdmEnabled) {
platformByIndex.push("android");
}
const onTabChange = (index: number) => {
onSelectPlatform(PLATFORM_BY_INDEX[index]);
onSelectPlatform(platformByIndex[index]);
};
return (
<div className={baseClass}>
<TabsWrapper>
<Tabs
defaultIndex={PLATFORM_BY_INDEX.indexOf(selectedPlatform)}
defaultIndex={platformByIndex.indexOf(selectedPlatform)}
onSelect={onTabChange}
>
<TabList>
@@ -76,6 +84,11 @@ const PlatformTabs = ({
<Tab key="iPadOS" data-text="iPadOS">
iPadOS
</Tab>
{isAndroidMdmEnabled && (
<Tab key="Android" data-text="Android">
Android
</Tab>
)}
</TabList>
<TabPanel>
<AppleOSTargetForm
@@ -122,6 +135,19 @@ const PlatformTabs = ({
refetchTeamConfig={refetchTeamConfig}
/>
</TabPanel>
{isAndroidMdmEnabled && (
<TabPanel>
<div className={`${baseClass}__coming-soon`}>
<p>
<b>Android updates are coming soon.</b>
</p>
<p>
Need to encourage installation of Android updates?{" "}
<CustomLink url={SUPPORT_LINK} text="Let us know" newTab />
</p>
</div>
</TabPanel>
)}
</Tabs>
</TabsWrapper>
</div>
@@ -2,4 +2,10 @@
.react-tabs__tab-list {
margin-bottom: $pad-large;
}
&__coming-soon {
p {
margin: 0;
padding-bottom: $pad-small;
}
}
}
@@ -9,7 +9,7 @@ import Spinner from "components/Spinner";
import WindowsTargetForm from "../WindowsTargetForm";
import PlatformTabs from "../PlatformTabs";
import { OSUpdatesSupportedPlatform } from "../../OSUpdates";
import { OSUpdatesTargetPlatform } from "../../OSUpdates";
const baseClass = "os-updates-target-section";
@@ -86,9 +86,9 @@ interface ITargetSectionProps {
appConfig: IConfig;
currentTeamId: number;
isFetching: boolean;
selectedPlatform: OSUpdatesSupportedPlatform;
selectedPlatform: OSUpdatesTargetPlatform;
teamConfig?: ITeamConfig;
onSelectPlatform: (platform: OSUpdatesSupportedPlatform) => void;
onSelectPlatform: (platform: OSUpdatesTargetPlatform) => void;
refetchAppConfig: () => void;
refetchTeamConfig: () => void;
}
@@ -107,6 +107,8 @@ const TargetSection = ({
return <Spinner />;
}
const isAndroidMdmEnabled = appConfig.mdm.android_enabled_and_configured;
const isAppleMdmEnabled = appConfig.mdm.enabled_and_configured;
const isWindowsMdmEnabled = appConfig.mdm.windows_enabled_and_configured;
@@ -161,33 +163,34 @@ const TargetSection = ({
});
const renderTargetForms = () => {
if (isAppleMdmEnabled) {
if (isWindowsMdmEnabled && !isAppleMdmEnabled && !isAndroidMdmEnabled) {
return (
<PlatformTabs
<WindowsTargetForm
currentTeamId={currentTeamId}
defaultMacOSVersion={defaultMacOSVersion}
defaultMacOSDeadline={defaultMacOSDeadline}
defaultIOSVersion={defaultIOSVersion}
defaultIOSDeadline={defaultIOSDeadline}
defaultIPadOSVersion={defaultIPadOSOSVersion}
defaultIPadOSDeadline={defaultIPadOSDeadline}
defaultWindowsDeadlineDays={defaultWindowsDeadlineDays}
defaultWindowsGracePeriodDays={defaultWindowsGracePeriodDays}
selectedPlatform={selectedPlatform}
onSelectPlatform={onSelectPlatform}
defaultDeadlineDays={defaultWindowsDeadlineDays}
defaultGracePeriodDays={defaultWindowsGracePeriodDays}
refetchAppConfig={refetchAppConfig}
refetchTeamConfig={refetchTeamConfig}
isWindowsMdmEnabled={isWindowsMdmEnabled}
/>
);
}
return (
<WindowsTargetForm
<PlatformTabs
currentTeamId={currentTeamId}
defaultDeadlineDays={defaultWindowsDeadlineDays}
defaultGracePeriodDays={defaultWindowsGracePeriodDays}
defaultMacOSVersion={defaultMacOSVersion}
defaultMacOSDeadline={defaultMacOSDeadline}
defaultIOSVersion={defaultIOSVersion}
defaultIOSDeadline={defaultIOSDeadline}
defaultIPadOSVersion={defaultIPadOSOSVersion}
defaultIPadOSDeadline={defaultIPadOSDeadline}
defaultWindowsDeadlineDays={defaultWindowsDeadlineDays}
defaultWindowsGracePeriodDays={defaultWindowsGracePeriodDays}
selectedPlatform={selectedPlatform}
onSelectPlatform={onSelectPlatform}
refetchAppConfig={refetchAppConfig}
refetchTeamConfig={refetchTeamConfig}
isWindowsMdmEnabled={isWindowsMdmEnabled}
isAndroidMdmEnabled={isAndroidMdmEnabled}
/>
);
};
@@ -6,6 +6,8 @@ import { CellProps, Column } from "react-table";
import ReactTooltip from "react-tooltip";
import { IDeviceUser, IHost } from "interfaces/host";
import { isAndroid, isMobilePlatform } from "interfaces/platform";
import Checkbox from "components/forms/fields/Checkbox";
import DiskSpaceIndicator from "pages/hosts/components/DiskSpaceIndicator";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
@@ -225,10 +227,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
accessor: "status",
id: "status",
Cell: (cellProps: IHostTableStringCellProps) => {
if (
cellProps.row.original.platform === "ios" ||
cellProps.row.original.platform === "ipados"
) {
if (isMobilePlatform(cellProps.row.original.platform)) {
return NotSupported;
}
const value = cellProps.cell.value;
@@ -247,10 +246,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
id: "issues",
sortDescFirst: true,
Cell: (cellProps: IIssuesCellProps) => {
if (
cellProps.row.original.platform === "ios" ||
cellProps.row.original.platform === "ipados"
) {
if (isMobilePlatform(cellProps.row.original.platform)) {
return NotSupported;
}
return (
@@ -302,6 +298,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
accessor: "os_version",
id: "os_version",
Cell: (cellProps: IHostTableStringCellProps) => (
// TODO(android): is Android supported? what about the os versions endpoint and dashboard card?
<TextCell value={cellProps.cell.value} />
),
},
@@ -316,10 +313,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
accessor: "osquery_version",
id: "osquery_version",
Cell: (cellProps: IHostTableStringCellProps) => {
if (
cellProps.row.original.platform === "ios" ||
cellProps.row.original.platform === "ipados"
) {
if (isMobilePlatform(cellProps.row.original.platform)) {
return NotSupported;
}
return <TextCell value={cellProps.cell.value} />;
@@ -332,6 +326,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
accessor: "device_mapping",
id: "device_mapping",
Cell: (cellProps: IDeviceUserCellProps) => {
// TODO(android): is android supported?
const numUsers = cellProps.cell.value?.length || 0;
const users = condenseDeviceUsers(cellProps.cell.value || []);
if (users.length > 1) {
@@ -364,10 +359,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
accessor: "primary_ip",
id: "primary_ip",
Cell: (cellProps: IHostTableStringCellProps) => {
if (
cellProps.row.original.platform === "ios" ||
cellProps.row.original.platform === "ipados"
) {
if (isMobilePlatform(cellProps.row.original.platform)) {
return NotSupported;
}
return <TextCell value={cellProps.cell.value} />;
@@ -442,10 +434,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
accessor: "public_ip",
id: "public_ip",
Cell: (cellProps: IHostTableStringCellProps) => {
if (
cellProps.row.original.platform === "ios" ||
cellProps.row.original.platform === "ipados"
) {
if (isMobilePlatform(cellProps.row.original.platform)) {
return NotSupported;
}
return (
@@ -478,6 +467,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
accessor: "detail_updated_at",
id: "detail_updated_at",
Cell: (cellProps: IHostTableStringCellProps) => (
// TODO(android): android doesn't support refetch?
<TextCell
value={{ timeString: cellProps.cell.value }}
formatter={HumanTimeDiffWithFleetLaunchCutoff}
@@ -509,10 +499,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
accessor: "seen_time",
id: "seen_time",
Cell: (cellProps: IHostTableStringCellProps) => {
if (
cellProps.row.original.platform === "ios" ||
cellProps.row.original.platform === "ipados"
) {
if (isMobilePlatform(cellProps.row.original.platform)) {
return NotSupported;
}
return (
@@ -546,11 +533,7 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
Cell: (cellProps: IHostTableStringCellProps) => {
const { platform, last_restarted_at } = cellProps.row.original;
if (
platform === "ios" ||
platform === "ipados" ||
platform === "chrome"
) {
if (isMobilePlatform(platform) || platform === "chrome") {
return NotSupported;
}
return (
@@ -608,9 +591,13 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
),
accessor: "primary_mac",
id: "primary_mac",
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
Cell: (cellProps: IHostTableStringCellProps) => {
// TODO(android): is iOS/iPadOS supported?
if (isAndroid(cellProps.row.original.platform)) {
return NotSupported;
}
return <TextCell value={cellProps.cell.value} />;
},
},
{
title: "Serial number",
@@ -622,9 +609,13 @@ const allHostTableHeaders: IHostTableColumnConfig[] = [
),
accessor: "hardware_serial",
id: "hardware_serial",
Cell: (cellProps: IHostTableStringCellProps) => (
<TextCell value={cellProps.cell.value} />
),
Cell: (cellProps: IHostTableStringCellProps) => {
// TODO(android): is iOS/iPadOS supported?
if (isAndroid(cellProps.row.original.platform)) {
return NotSupported;
}
return <TextCell value={cellProps.cell.value} />;
},
},
{
title: "Hardware model",
@@ -77,7 +77,11 @@ const DeleteHostModal = ({
newTab
/>
</li>
<li>iOS and iPadOS hosts will re-appear unless MDM is turned off.</li>
<li>
{/* TODO(android): iOS, iPadOS, and Android hosts will re-appear unless MDM is turned
off. */}
iOS and iPadOS hosts will re-appear unless MDM is turned off.
</li>
</ul>
<div className="modal-cta-wrap">
<Button
@@ -2,7 +2,12 @@ import React from "react";
import { cloneDeep } from "lodash";
import { IDropdownOption } from "interfaces/dropdownOption";
import { isLinuxLike, isAppleDevice } from "interfaces/platform";
import {
isLinuxLike,
isAppleDevice,
isMobilePlatform,
isAndroid,
} from "interfaces/platform";
import { isScriptSupportedPlatform } from "interfaces/script";
import {
@@ -96,6 +101,7 @@ const canEditMdm = (config: IHostActionConfigOptions) => {
isMacMdmEnabledAndConfigured,
} = config;
return (
!isAndroid(hostPlatform) && // TODO(android): confirm can't turn off MDM for windows, iOS, iPadOS?
hostPlatform === "darwin" &&
isMacMdmEnabledAndConfigured &&
isEnrolledInMdm &&
@@ -105,10 +111,8 @@ const canEditMdm = (config: IHostActionConfigOptions) => {
};
const canQueryHost = ({ hostPlatform }: IHostActionConfigOptions) => {
// Currently we cannot query iOS or iPadOS
const isIosOrIpadosHost = hostPlatform === "ios" || hostPlatform === "ipados";
return !isIosOrIpadosHost;
// cannot query iOS, iPadOS, or Android hosts
return !isMobilePlatform(hostPlatform);
};
const canLockHost = ({
@@ -132,6 +136,7 @@ const canLockHost = ({
return (
isPremiumTier &&
!isAndroid(hostPlatform) &&
hostMdmDeviceStatus === "unlocked" &&
(hostPlatform === "windows" ||
isLinuxLike(hostPlatform) ||
@@ -164,6 +169,7 @@ const canWipeHost = ({
return (
isPremiumTier &&
!isAndroid(hostPlatform) &&
hostMdmDeviceStatus === "unlocked" &&
(isLinuxLike(hostPlatform) || canWipeWindowsOrAppleOS) &&
(isGlobalAdmin || isGlobalMaintainer || isTeamAdmin || isTeamMaintainer)
@@ -197,6 +203,7 @@ const canUnlock = ({
return (
isPremiumTier &&
!isAndroid(hostPlatform) &&
isValidState &&
(isGlobalAdmin || isGlobalMaintainer || isTeamAdmin || isTeamMaintainer) &&
(canUnlockDarwin || hostPlatform === "windows" || isLinuxLike(hostPlatform))
@@ -215,11 +222,10 @@ const canDeleteHost = (config: IHostActionConfigOptions) => {
const canShowDiskEncryption = (config: IHostActionConfigOptions) => {
const { isPremiumTier, doesStoreEncryptionKey, hostPlatform } = config;
// Currently we cannot show disk encryption key for iOS or iPadOS
const isIosOrIpadosHost = hostPlatform === "ios" || hostPlatform === "ipados";
return isPremiumTier && doesStoreEncryptionKey && !isIosOrIpadosHost;
if (isMobilePlatform(hostPlatform)) {
return false;
}
return isPremiumTier && doesStoreEncryptionKey;
};
const canRunScript = ({
@@ -42,7 +42,7 @@ import {
HOST_OSQUERY_DATA,
} from "utilities/constants";
import { isIPadOrIPhone } from "interfaces/platform";
import { isAndroid, isIPadOrIPhone } from "interfaces/platform";
import Spinner from "components/Spinner";
import TabsWrapper from "components/TabsWrapper";
@@ -224,7 +224,7 @@ const HostDetailsPage = ({
["deviceMapping", hostIdFromURL],
() => hostAPI.loadHostDetailsExtension(hostIdFromURL, "device_mapping"),
{
enabled: !!hostIdFromURL,
enabled: !!hostIdFromURL, // TODO(android): disable for unsupported platforms?
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
@@ -253,7 +253,7 @@ const HostDetailsPage = ({
["macadmins", hostIdFromURL],
() => hostAPI.loadHostDetailsExtension(hostIdFromURL, "macadmins"),
{
enabled: !!hostIdFromURL,
enabled: !!hostIdFromURL, // TODO(android): disable for unsupported platforms?
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
@@ -290,7 +290,10 @@ const HostDetailsPage = ({
returnedHost.mdm.pending_action
)
);
if (returnedHost.refetch_requested) {
if (
returnedHost.refetch_requested &&
!isAndroid(returnedHost.platform)
) {
// If the API reports that a Fleet refetch request is pending, we want to check back for fresh
// host details. Here we set a one second timeout and poll the API again using
// fullyReloadHost. We will repeat this process with each onSuccess cycle for a total of
@@ -11,13 +11,13 @@ import {
IDeviceUser,
mapDeviceUsersForDisplay,
} from "interfaces/host";
import { isAndroid, isIPadOrIPhone } from "interfaces/platform";
import {
DEFAULT_EMPTY_CELL_VALUE,
MDM_STATUS_TOOLTIP,
BATTERY_TOOLTIP,
} from "utilities/constants";
import DataSet from "components/DataSet";
import classnames from "classnames";
const getDeviceUserTipContent = (deviceMapping: IDeviceUser[]) => {
if (deviceMapping.length === 0) {
@@ -49,8 +49,8 @@ const About = ({
munki,
mdm,
}: IAboutProps): JSX.Element => {
const isIosOrIpadosHost =
aboutData.platform === "ios" || aboutData.platform === "ipados";
const isIosOrIpadosHost = isIPadOrIPhone(aboutData.platform);
const isAndroidHost = isAndroid(aboutData.platform);
const renderHardwareSerialAndIPs = () => {
if (isIosOrIpadosHost) {
@@ -62,6 +62,12 @@ const About = ({
);
}
if (isAndroidHost) {
return (
<DataSet title="Hardware model" value={aboutData.hardware_model} />
);
}
return (
<>
<DataSet title="Hardware model" value={aboutData.hardware_model} />
@@ -205,6 +211,8 @@ const About = ({
);
};
// TODO(android): confirm visible fields using actual android device data
return (
<Card
borderRadiusSize="xxlarge"
@@ -222,7 +230,7 @@ const About = ({
/>
}
/>
{!isIosOrIpadosHost && (
{!isIosOrIpadosHost && !isAndroidHost && (
<DataSet
title="Last restarted"
value={
@@ -12,6 +12,7 @@ import { IOSSettings, IHostMaintenanceWindow } from "interfaces/host";
import { IAppleDeviceUpdates } from "interfaces/config";
import {
DiskEncryptionSupportedPlatform,
isAndroid,
isDiskEncryptionSupportedLinuxPlatform,
isOsSettingsDisplayPlatform,
platformSupportsDiskEncryption,
@@ -211,10 +212,15 @@ const HostSummary = ({
disk_encryption_enabled: diskEncryptionEnabled,
} = summaryData;
const isAndroidHost = isAndroid(platform);
const isChromeHost = platform === "chrome";
const isIosOrIpadosHost = platform === "ios" || platform === "ipados";
const renderRefetch = () => {
if (isAndroidHost) {
return null;
}
const isOnline = summaryData.status === "online";
let isDisabled = false;
let tooltip;
@@ -340,7 +346,7 @@ const HostSummary = ({
};
const renderOperatingSystemSummary = () => {
// No tooltip if minimum version is not set, including all Windows, Linux, ChromeOS operating systems
// No tooltip if minimum version is not set, including all Windows, Linux, ChromeOS, Android operating systems
if (!osVersionRequirement?.minimum_version) {
return (
<DataSet title="Operating system" value={summaryData.os_version} />
@@ -384,12 +390,12 @@ const HostSummary = ({
};
const renderAgentSummary = () => {
if (isChromeHost) {
return <DataSet title="Agent" value={summaryData.osquery_version} />;
if (isIosOrIpadosHost || isAndroidHost) {
return null;
}
if (isIosOrIpadosHost) {
return null;
if (isChromeHost) {
return <DataSet title="Agent" value={summaryData.osquery_version} />;
}
if (summaryData.orbit_version !== DEFAULT_EMPTY_CELL_VALUE) {
@@ -499,7 +505,7 @@ const HostSummary = ({
largePadding
className={`${baseClass}-card`}
>
{!isIosOrIpadosHost && (
{!isIosOrIpadosHost && !isAndroidHost && (
<DataSet
title="Status"
value={
@@ -515,6 +521,7 @@ const HostSummary = ({
)}
{summaryData.issues?.total_issues_count > 0 &&
!isIosOrIpadosHost &&
!isAndroidHost &&
renderIssues()}
{isPremiumTier && renderHostTeam()}
{/* Rendering of OS Settings data */}
@@ -532,17 +539,19 @@ const HostSummary = ({
}
/>
)}
{bootstrapPackageData?.status && !isIosOrIpadosHost && (
<DataSet
title="Bootstrap package"
value={
<BootstrapPackageIndicator
status={bootstrapPackageData.status}
onClick={toggleBootstrapPackageModal}
/>
}
/>
)}
{bootstrapPackageData?.status &&
!isIosOrIpadosHost &&
!isAndroidHost && (
<DataSet
title="Bootstrap package"
value={
<BootstrapPackageIndicator
status={bootstrapPackageData.status}
onClick={toggleBootstrapPackageModal}
/>
}
/>
)}
{!isChromeHost && renderDiskSpaceSummary()}
{renderDiskEncryptionSummary()}
{!isIosOrIpadosHost && (
@@ -555,7 +564,7 @@ const HostSummary = ({
<DataSet title="Processor type" value={summaryData.cpu_type} />
)}
{renderOperatingSystemSummary()}
{!isIosOrIpadosHost && renderAgentSummary()}
{renderAgentSummary()}
{isPremiumTier &&
// TODO - refactor normalizeEmptyValues pattern
!!summaryData.maintenance_window &&
@@ -3,6 +3,7 @@ import { InjectedRouter } from "react-router";
import { Row } from "react-table";
import { noop } from "lodash";
import { isAndroid } from "interfaces/platform";
import { IHostPolicy } from "interfaces/policy";
import { PolicyResponse, SUPPORT_LINK } from "utilities/constants";
import { createHostsByPolicyPath } from "utilities/helpers";
@@ -89,6 +90,20 @@ const Policies = ({
);
}
if (isAndroid(hostPlatform)) {
return (
<EmptyTable
header={<>Policies are not supported for this host</>}
info={
<>
Interested in detecting device health issues on Android hosts?{" "}
<CustomLink url={SUPPORT_LINK} text="Let us know" newTab />
</>
}
/>
);
}
if (policies.length === 0) {
return (
<EmptyTable
@@ -1,5 +1,6 @@
import React, { useCallback, useMemo } from "react";
import { isAndroid } from "interfaces/platform";
import { IQueryStats } from "interfaces/query_stats";
import { SUPPORT_LINK } from "utilities/constants";
import TableContainer from "components/TableContainer";
@@ -74,6 +75,20 @@ const HostQueries = ({
);
}
if (isAndroid(hostPlatform)) {
return (
<EmptyTable
header="Queries are not supported for this host"
info={
<>
Interested in querying Android hosts?{" "}
<CustomLink url={SUPPORT_LINK} text="Let us know" newTab />
</>
}
/>
);
}
return (
<EmptyTable
header="No queries are scheduled to run on this host"
@@ -12,7 +12,7 @@ import deviceAPI, {
IGetDeviceSoftwareResponse,
} from "services/entities/device_user";
import { IHostSoftware, ISoftware } from "interfaces/software";
import { HostPlatform, isIPadOrIPhone } from "interfaces/platform";
import { HostPlatform, isAndroid, isIPadOrIPhone } from "interfaces/platform";
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
import { NotificationContext } from "context/notification";
import { AppContext } from "context/app";
@@ -35,7 +35,7 @@ export interface ITableSoftware extends Omit<ISoftware, "vulnerabilities"> {
interface IHostSoftwareProps {
/** This is the host id or the device token */
id: number | string;
platform?: HostPlatform;
platform: HostPlatform;
softwareUpdatedAt?: string;
hostCanWriteSoftware: boolean;
router: InjectedRouter;
@@ -100,8 +100,8 @@ const HostSoftware = ({
hostMDMEnrolled,
}: IHostSoftwareProps) => {
const { renderFlash } = useContext(NotificationContext);
const vulnFilterAndNotSupported =
isIPadOrIPhone(platform ?? "") && queryParams.vulnerable;
const isUnsupported =
isAndroid(platform) || (isIPadOrIPhone(platform) && queryParams.vulnerable); // no Android software and no vulnerable software for iOS
const {
isGlobalAdmin,
isGlobalMaintainer,
@@ -139,8 +139,7 @@ const HostSoftware = ({
},
{
...DEFAULT_USE_QUERY_OPTIONS,
enabled:
isSoftwareEnabled && !isMyDevicePage && !vulnFilterAndNotSupported,
enabled: isSoftwareEnabled && !isMyDevicePage && !isUnsupported,
keepPreviousData: true,
staleTime: 7000,
}
@@ -13,6 +13,7 @@ import {
APPLE_PLATFORM_DISPLAY_NAMES,
HostPlatform,
isIPadOrIPhone,
isAndroid,
} from "interfaces/platform";
import TableContainer from "components/TableContainer";
@@ -26,6 +27,9 @@ import TableCount from "components/TableContainer/TableCount";
import { VulnsNotSupported } from "pages/SoftwarePage/components/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable";
import { Row } from "react-table";
import { IHostSoftware } from "interfaces/software";
import EmptyTable from "components/EmptyTable";
import CustomLink from "components/CustomLink";
import { SUPPORT_LINK } from "utilities/constants";
const DEFAULT_PAGE_SIZE = 20;
@@ -239,6 +243,20 @@ const HostSoftwareTable = ({
[onShowSoftwareDetails]
);
if (isAndroid(platform)) {
return (
<EmptyTable
header="Software is not supported for this host"
info={
<>
Interested in viewing software for Android hosts?{" "}
<CustomLink url={SUPPORT_LINK} text="Let us know" newTab />
</>
}
/>
);
}
return (
<div className={baseClass}>
<TableContainer
+1
View File
@@ -166,6 +166,7 @@ const routes = (
<Route path="chrome" component={DashboardPage} />
<Route path="ios" component={DashboardPage} />
<Route path="ipados" component={DashboardPage} />
<Route path="android" component={DashboardPage} />
</Route>
<Route path="settings" component={AuthAnyAdminRoutes}>
<IndexRedirect to="organization/info" />
+1
View File
@@ -29,6 +29,7 @@ export default {
DASHBOARD_CHROME: `${URL_PREFIX}/dashboard/chrome`,
DASHBOARD_IOS: `${URL_PREFIX}/dashboard/ios`,
DASHBOARD_IPADOS: `${URL_PREFIX}/dashboard/ipados`,
DASHBOARD_ANDROID: `${URL_PREFIX}/dashboard/android`,
/**
* Admin pages
+11 -19
View File
@@ -215,6 +215,7 @@ const PLATFORM_LABEL_NAMES_FROM_API = [
"chrome",
"iOS",
"iPadOS",
"Android",
] as const;
export type PlatformLabelNameFromAPI = typeof PLATFORM_LABEL_NAMES_FROM_API[number];
@@ -236,6 +237,8 @@ export const PLATFORM_DISPLAY_NAMES: Record<string, DisplayPlatform> = {
ChromeOS: "ChromeOS",
ios: "iOS",
ipados: "iPadOS",
android: "Android",
Android: "Android",
} as const;
// as returned by the TARGETS API; based on display_text
@@ -253,6 +256,7 @@ export const PLATFORM_LABEL_DISPLAY_NAMES: Record<
chrome: "ChromeOS",
iOS: "iOS",
iPadOS: "iPadOS",
Android: "Android",
} as const;
export const PLATFORM_LABEL_DISPLAY_TYPES: Record<
@@ -269,6 +273,7 @@ export const PLATFORM_LABEL_DISPLAY_TYPES: Record<
chrome: "platform",
iOS: "platform",
iPadOS: "platform",
Android: "platform",
} as const;
// For some builtin labels, display different strings than what API returns
@@ -281,27 +286,19 @@ export const LABEL_DISPLAY_MAP: Partial<
"MS Windows": "Windows",
};
export const PLATFORM_TYPE_ICONS: Record<
Extract<
PlatformLabelNameFromAPI,
"All Linux" | "macOS" | "MS Windows" | "chrome" | "iOS" | "iPadOS"
>,
IconNames
> = {
export const PLATFORM_TYPE_ICONS = {
"All Linux": "linux",
macOS: "darwin",
"MS Windows": "windows",
chrome: "chrome",
iOS: "iOS",
iPadOS: "iPadOS",
Android: "android",
} as const;
export const hasPlatformTypeIcon = (
s: string
): s is Extract<
PlatformLabelNameFromAPI,
"All Linux" | "macOS" | "MS Windows" | "chrome" | "iOS" | "iPadOS"
> => {
): s is Extract<PlatformLabelNameFromAPI, keyof typeof PLATFORM_TYPE_ICONS> => {
return !!PLATFORM_TYPE_ICONS[s as keyof typeof PLATFORM_TYPE_ICONS];
};
@@ -309,18 +306,13 @@ export type PlatformLabelOptions = DisplayPlatform | "All";
export type PlatformValueOptions = Platform | "all";
/** Scheduled queries do not support ChromeOS, iOS, or iPadOS */
interface ISchedulePlatformDropdownOptions {
label: Exclude<PlatformLabelOptions, "ChromeOS" | "iOS" | "iPadOS">;
value: Exclude<PlatformValueOptions, "chrome" | "ios" | "ipados"> | "";
}
export const SCHEDULE_PLATFORM_DROPDOWN_OPTIONS: ISchedulePlatformDropdownOptions[] = [
/** Scheduled queries do not support ChromeOS, iOS, iPadOS, or Android */
export const SCHEDULE_PLATFORM_DROPDOWN_OPTIONS = [
{ label: "All", value: "" }, // API empty string runs on all platforms
{ label: "macOS", value: "darwin" },
{ label: "Windows", value: "windows" },
{ label: "Linux", value: "linux" },
];
] as const;
export const HOSTS_SEARCH_BOX_PLACEHOLDER =
"Search name, hostname, UUID, serial number, or private IP address";