diff --git a/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss b/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss index ab09b65be1..939d0f9a5c 100644 --- a/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss +++ b/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss @@ -43,7 +43,7 @@ align-items: center; padding: 16px; gap: 16px; - width: 286px; + width: 247px; border: 1px solid #c5c7d1; border-radius: 4px; diff --git a/frontend/components/App/App.tsx b/frontend/components/App/App.tsx index 0c9f745d9d..b21ec7ef54 100644 --- a/frontend/components/App/App.tsx +++ b/frontend/components/App/App.tsx @@ -13,6 +13,7 @@ import useDeepEffect from "hooks/useDeepEffect"; import usersAPI from "services/entities/users"; import configAPI from "services/entities/config"; +import hostCountAPI from "services/entities/host_count"; import { ErrorBoundary } from "react-error-boundary"; // @ts-ignore @@ -46,6 +47,7 @@ const App = ({ children, location }: IAppProps): JSX.Element => { setConfig, setEnrollSecret, setSandboxExpiry, + setNoSandboxHosts, } = useContext(AppContext); const [isLoading, setIsLoading] = useState(false); @@ -56,6 +58,9 @@ const App = ({ children, location }: IAppProps): JSX.Element => { if (config.sandbox_enabled) { const timestamp = await configAPI.loadSandboxExpiry(); setSandboxExpiry(timestamp as string); + const hostCount = await hostCountAPI.load({}); + const noSandboxHosts = hostCount.count === 0; + setNoSandboxHosts(noSandboxHosts); } setConfig(config); } catch (error) { diff --git a/frontend/components/MainContent/MainContent.tsx b/frontend/components/MainContent/MainContent.tsx index 20df011a44..16c15dac54 100644 --- a/frontend/components/MainContent/MainContent.tsx +++ b/frontend/components/MainContent/MainContent.tsx @@ -27,9 +27,13 @@ const MainContent = ({ className, }: IMainContentProps): JSX.Element => { const classes = classnames(baseClass, className); - const { sandboxExpiry, config, isSandboxMode, isPremiumTier } = useContext( - AppContext - ); + const { + sandboxExpiry, + config, + isSandboxMode, + isPremiumTier, + noSandboxHosts, + } = useContext(AppContext); const isAppleBmTermsExpired = config?.mdm?.apple_bm_terms_expired; @@ -45,7 +49,10 @@ const MainContent = ({ )} ( - + )} /> {children} diff --git a/frontend/components/Sandbox/SandboxExpiryMessage/SandboxExpiryMessage.tsx b/frontend/components/Sandbox/SandboxExpiryMessage/SandboxExpiryMessage.tsx index 61ade45187..15668f9366 100644 --- a/frontend/components/Sandbox/SandboxExpiryMessage/SandboxExpiryMessage.tsx +++ b/frontend/components/Sandbox/SandboxExpiryMessage/SandboxExpiryMessage.tsx @@ -1,26 +1,60 @@ import React from "react"; +import { browserHistory } from "react-router"; +import PATHS from "router/paths"; -import ExternalLinkIcon from "../../../../assets/images/icon-external-link-black-12x12@2x.png"; +import Button from "components/buttons/Button"; +import Icon from "components/Icon"; const baseClass = "sandbox-expiry-message"; interface ISandboxExpiryMessageProps { expiry: string; + noSandboxHosts?: boolean; } -const SandboxExpiryMessage = ({ expiry }: ISandboxExpiryMessageProps) => { +const SandboxExpiryMessage = ({ + expiry, + noSandboxHosts, +}: ISandboxExpiryMessageProps) => { + const openAddHostModal = () => { + browserHistory.push(PATHS.MANAGE_HOSTS_ADD_HOSTS); + }; + + if (noSandboxHosts) { + return ( +
+

Your Fleet Sandbox expires in {expiry}.

+
+ +

+ Quick tip: Enroll a host to get started. +

+
+ +
+
+
+ ); + } + return (

Your Fleet Sandbox expires in {expiry}.

- - Learn how to deploy Fleet - Open external link - +

+ Learn how to use Fleet{" "} + +

); }; diff --git a/frontend/components/Sandbox/SandboxExpiryMessage/_styles.scss b/frontend/components/Sandbox/SandboxExpiryMessage/_styles.scss index afb56403c9..1352603a53 100644 --- a/frontend/components/Sandbox/SandboxExpiryMessage/_styles.scss +++ b/frontend/components/Sandbox/SandboxExpiryMessage/_styles.scss @@ -2,30 +2,27 @@ display: flex; justify-content: space-between; align-items: center; - padding: 12px $pad-large; + padding: $pad-large $pad-xlarge; margin-bottom: $pad-large; - background-color: $ui-yellow-banner; - border: 1px solid #ece0bb; + background-color: $ui-vibrant-blue-10; + border: 1px solid $ui-vibrant-blue-50; border-radius: $border-radius; - font-size: $xx-small; - + font-size: $x-small; color: $core-fleet-black; font-weight: $regular; - &:hover { - cursor: pointer; - } - p { margin: 0; } - span { + &__tip { display: flex; align-items: center; - color: $core-fleet-black; - font-weight: $bold; - text-align: right; + gap: $pad-small; + + button { + margin-left: $pad-small; + } } .button { diff --git a/frontend/components/buttons/Button/_styles.scss b/frontend/components/buttons/Button/_styles.scss index d96adcfba1..c79318e2b2 100644 --- a/frontend/components/buttons/Button/_styles.scss +++ b/frontend/components/buttons/Button/_styles.scss @@ -359,7 +359,6 @@ $base-class: "button"; .info { &__header { display: block; - width: 100%; text-align: left; } &__data { diff --git a/frontend/components/icons/Lightbulb.tsx b/frontend/components/icons/Lightbulb.tsx new file mode 100644 index 0000000000..8f45c99bfc --- /dev/null +++ b/frontend/components/icons/Lightbulb.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { COLORS, Colors } from "styles/var/colors"; + +interface ILightbulbProps { + color?: Colors; +} + +const Lightbulb = ({ color = "core-fleet-blue" }: ILightbulbProps) => { + return ( + + + + + + + + + + + + + + ); +}; + +export default Lightbulb; diff --git a/frontend/components/icons/index.ts b/frontend/components/icons/index.ts index 77510697f5..175c3f6327 100644 --- a/frontend/components/icons/index.ts +++ b/frontend/components/icons/index.ts @@ -21,6 +21,7 @@ import PremiumFeature from "./PremiumFeature"; import LowDiskSpaceHosts from "./LowDiskSpaceHosts"; import MissingHosts from "./MissingHosts"; +import Lightbulb from "./Lightbulb"; import Apple from "./Apple"; import Windows from "./Windows"; @@ -72,6 +73,7 @@ export const ICON_MAP = { "external-link": ExternalLink, "low-disk-space-hosts": LowDiskSpaceHosts, "missing-hosts": MissingHosts, + lightbulb: Lightbulb, issue: Issue, plus: Plus, clipboard: Clipboard, diff --git a/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tsx b/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tsx index 60b31ba85d..aaaffa98f4 100644 --- a/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tsx +++ b/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tsx @@ -40,6 +40,8 @@ const QuerySidePanel = ({ evented, } = selectedOsqueryTable; + const mdmRequired = name === "managed_policies"; + const onSelectTable = (value: string) => { onOsqueryTableSelect(value); }; @@ -80,6 +82,9 @@ const QuerySidePanel = ({ {renderTableSelect()} {evented && } + {mdmRequired && ( + Requires MDM + )}
diff --git a/frontend/components/side_panels/QuerySidePanel/_styles.scss b/frontend/components/side_panels/QuerySidePanel/_styles.scss index 7dcebdff19..17974e241d 100644 --- a/frontend/components/side_panels/QuerySidePanel/_styles.scss +++ b/frontend/components/side_panels/QuerySidePanel/_styles.scss @@ -52,23 +52,20 @@ } &__table-select { - // TODO: Remove these overrides when we do a style update for the core // dropdown component. &.Select { - // fixes up some padding issues with the scroll bar for the dropdown. .Select-menu-outer { padding: 0; .Select-menu { - padding: $pad-xsmall + padding: $pad-xsmall; } } // use to truncate selected long table names in the dropdown - &.has-value.Select--single>.Select-control { - + &.has-value.Select--single > .Select-control { .Select-value .Select-value-label { white-space: nowrap; overflow: hidden; @@ -84,4 +81,17 @@ font-size: $x-small; overflow-wrap: break-word; } + + &__mdm-required { + background-color: $core-vibrant-blue; + color: $core-white; + font-size: $x-small; + font-weight: 700; + padding: 2px 4px; + border-radius: 4px; + position: relative; + top: -2px; + min-width: 95px; + max-height: 19px; + } } diff --git a/frontend/context/app.tsx b/frontend/context/app.tsx index b0a82968b5..fb29a3c3bb 100644 --- a/frontend/context/app.tsx +++ b/frontend/context/app.tsx @@ -14,6 +14,7 @@ enum ACTIONS { SET_CONFIG = "SET_CONFIG", SET_ENROLL_SECRET = "SET_ENROLL_SECRET", SET_SANDBOX_EXPIRY = "SET_SANDBOX_EXPIRY", + SET_NO_SANDBOX_HOSTS = "SET_NO_SANDBOX_HOSTS", SET_FILTERED_HOSTS_PATH = "SET_FILTERED_HOSTS_PATH", } @@ -45,6 +46,11 @@ interface ISetSandboxExpiryAction { sandboxExpiry: string; } +interface ISetNoSandboxHostsAction { + type: ACTIONS.SET_NO_SANDBOX_HOSTS; + noSandboxHosts: boolean; +} + interface ISetFilteredHostsPathAction { type: ACTIONS.SET_FILTERED_HOSTS_PATH; filteredHostsPath: string; @@ -57,6 +63,7 @@ type IAction = | ISetCurrentUserAction | ISetEnrollSecretAction | ISetSandboxExpiryAction + | ISetNoSandboxHostsAction | ISetFilteredHostsPathAction; type Props = { @@ -88,6 +95,7 @@ type InitialStateType = { isOnlyObserver?: boolean; isNoAccess?: boolean; sandboxExpiry?: string; + noSandboxHosts?: boolean; filteredHostsPath?: string; setAvailableTeams: (availableTeams: ITeamSummary[]) => void; setCurrentUser: (user: IUser) => void; @@ -95,6 +103,7 @@ type InitialStateType = { setConfig: (config: IConfig) => void; setEnrollSecret: (enrollSecret: IEnrollSecret[]) => void; setSandboxExpiry: (sandboxExpiry: string) => void; + setNoSandboxHosts: (noSandboxHosts: boolean) => void; setFilteredHostsPath: (filteredHostsPath: string) => void; }; @@ -131,6 +140,7 @@ export const initialState = { setConfig: () => null, setEnrollSecret: () => null, setSandboxExpiry: () => null, + setNoSandboxHosts: () => null, setFilteredHostsPath: () => null, }; @@ -229,6 +239,13 @@ const reducer = (state: InitialStateType, action: IAction) => { sandboxExpiry, }; } + case ACTIONS.SET_NO_SANDBOX_HOSTS: { + const { noSandboxHosts } = action; + return { + ...state, + noSandboxHosts, + }; + } case ACTIONS.SET_FILTERED_HOSTS_PATH: { const { filteredHostsPath } = action; return { @@ -253,6 +270,7 @@ const AppProvider = ({ children }: Props): JSX.Element => { currentTeam: state.currentTeam, enrollSecret: state.enrollSecret, sandboxExpiry: state.sandboxExpiry, + noSandboxHosts: state.noSandboxHosts, filteredHostsPath: state.filteredHostsPath, isPreviewMode: detectPreview(), isSandboxMode: state.isSandboxMode, @@ -290,6 +308,12 @@ const AppProvider = ({ children }: Props): JSX.Element => { setSandboxExpiry: (sandboxExpiry: string) => { dispatch({ type: ACTIONS.SET_SANDBOX_EXPIRY, sandboxExpiry }); }, + setNoSandboxHosts: (noSandboxHosts: boolean) => { + dispatch({ + type: ACTIONS.SET_NO_SANDBOX_HOSTS, + noSandboxHosts, + }); + }, setFilteredHostsPath: (filteredHostsPath: string) => { dispatch({ type: ACTIONS.SET_FILTERED_HOSTS_PATH, filteredHostsPath }); }, diff --git a/frontend/interfaces/policy.ts b/frontend/interfaces/policy.ts index c389b25c63..2e715bedfc 100644 --- a/frontend/interfaces/policy.ts +++ b/frontend/interfaces/policy.ts @@ -94,4 +94,5 @@ export interface IPolicyNew { resolution: string; critical: boolean; platform: IPlatformString; + mdm_required?: boolean; } diff --git a/frontend/pages/DashboardPage/cards/Software/Software.tsx b/frontend/pages/DashboardPage/cards/Software/Software.tsx index b47165c2a2..a97c70670a 100644 --- a/frontend/pages/DashboardPage/cards/Software/Software.tsx +++ b/frontend/pages/DashboardPage/cards/Software/Software.tsx @@ -1,9 +1,10 @@ -import React from "react"; +import React, { useContext } from "react"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { Row } from "react-table"; import { InjectedRouter } from "react-router"; import PATHS from "router/paths"; +import { AppContext } from "context/app"; import { buildQueryStringFromParams } from "utilities/url"; import TabsWrapper from "components/TabsWrapper"; @@ -52,6 +53,8 @@ const Software = ({ software, router, }: ISoftwareCardProps): JSX.Element => { + const { noSandboxHosts } = useContext(AppContext); + const tableHeaders = generateTableHeaders(); const handleRowSelect = (row: IRowProps) => { @@ -96,6 +99,7 @@ const Software = ({ emptyComponent={() => ( )} showMarkAllPages={false} @@ -124,6 +128,7 @@ const Software = ({ emptyComponent={() => ( )} diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index 8926de0224..8bb6a22c7b 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -677,6 +677,9 @@ const ManageHostsPage = ({ // NOTE: used to reset page number to 0 when modifying filters useEffect(() => { setResetPageIndex(false); + if (queryParams.add_hosts === "true") { + setShowAddHostsModal(true); + } }, [queryParams]); // NOTE: this is called once on initial render and every time the query changes diff --git a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/AddPolicyModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/AddPolicyModal.tsx index b0cc1dcef8..54102a501b 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/AddPolicyModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/AddPolicyModal.tsx @@ -68,7 +68,12 @@ const AddPolicyModal = ({ onClick={() => onAddPolicy(policy)} > <> - {policy.name} +
+ {policy.name} + {policy.mdm_required && ( + Requires MDM + )} +
{policy.description} diff --git a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss index 983d6cd1f4..ac4b1c5f57 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss @@ -13,4 +13,31 @@ padding: $pad-large 0; height: 100%; } + + &__policy-name { + display: inline-flex; + width: 100%; + } + + &__mdm-policy { + background-color: $core-vibrant-blue; + color: $core-white; + font-size: $x-small; + font-weight: 700; + padding: 2px 4px; + border-radius: 4px; + margin-left: 0.5rem; + position: relative; + top: -2px; + min-width: 95px; + max-height: 19px; + } + + .children-wrapper { + align-items: left; + + .info__header { + width: initial; + } + } } diff --git a/frontend/pages/policies/constants.ts b/frontend/pages/policies/constants.ts index 31b90557d1..2352b0ab3a 100644 --- a/frontend/pages/policies/constants.ts +++ b/frontend/pages/policies/constants.ts @@ -72,6 +72,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that disables automatic login.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 5, @@ -141,6 +142,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that enables secure keyboard entry for the Terminal application.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 11, @@ -175,6 +177,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that enables screen lock.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 14, @@ -221,6 +224,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic updates.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 18, @@ -257,6 +261,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic security and data update installation.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 21, @@ -270,6 +275,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic installation of operating system updates.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 22, @@ -282,6 +288,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that enables automatic time and date configuration.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 23, @@ -294,6 +301,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that enables the screen saver after inactivity of 20 minutes or less.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 24, @@ -306,6 +314,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that prevents Internet sharing.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 25, @@ -318,6 +327,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that disables content caching.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 26, @@ -342,6 +352,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile to prevent iCloud Desktop and Documents sync.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 28, @@ -354,6 +365,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that enables firewall logging.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 29, @@ -366,6 +378,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that disables the guest account.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 30, @@ -378,6 +391,7 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ "Contact your IT administrator to ensure your Mac is receiving a profile that prevents guest access to shared folders.", critical: false, platform: "darwin", + mdm_required: true, }, { key: 31, @@ -465,7 +479,8 @@ export const DEFAULT_POLICIES: IPolicyNew[] = [ key: 38, query: "SELECT EXISTS(SELECT 1 FROM file WHERE filename like '%Emergency Kit%.pdf' AND (path LIKE '/Users/%%/Downloads/%%' OR path LIKE '/Users/%%/Desktop/%%')) as does_1p_ek_exist;", - name: "No 1Password emergency kit stored on desktop or in downloads (macOS)", + name: + "No 1Password emergency kit stored on desktop or in downloads (macOS)", description: "Looks for PDF files with file names typically used by 1Password for emergency recovery kits.", resolution: diff --git a/frontend/pages/software/ManageSoftwarePage/ManageSoftwarePage.tsx b/frontend/pages/software/ManageSoftwarePage/ManageSoftwarePage.tsx index ab7715629a..39c5080793 100644 --- a/frontend/pages/software/ManageSoftwarePage/ManageSoftwarePage.tsx +++ b/frontend/pages/software/ManageSoftwarePage/ManageSoftwarePage.tsx @@ -112,6 +112,7 @@ const ManageSoftwarePage = ({ isOnGlobalTeam, isPremiumTier, isSandboxMode, + noSandboxHosts, } = useContext(AppContext); const { renderFlash } = useContext(NotificationContext); @@ -559,6 +560,7 @@ const ManageSoftwarePage = ({ isSandboxMode={isSandboxMode} isCollectingSoftware={isCollectingInventory} isSearching={searchQuery !== ""} + noSandboxHosts={noSandboxHosts} /> )} defaultSortHeader={DEFAULT_SORT_HEADER} diff --git a/frontend/pages/software/components/EmptySoftware.tsx b/frontend/pages/software/components/EmptySoftware.tsx new file mode 100644 index 0000000000..4cd6b3dafe --- /dev/null +++ b/frontend/pages/software/components/EmptySoftware.tsx @@ -0,0 +1,68 @@ +// This component is used on ManageSoftwarePage.tsx and DashboardPage.tsx > Software.tsx card + +import React from "react"; + +import CustomLink from "components/CustomLink"; + +const baseClass = "manage-software-page"; + +type IEmptySoftwareProps = { + message: "disabled" | "collecting" | "default" | ""; + noSandboxHosts?: boolean; +}; + +const EmptySoftware = ({ + message, + noSandboxHosts, +}: IEmptySoftwareProps): JSX.Element => { + switch (message) { + case "disabled": { + return ( +
+
+

Software inventory is disabled.

+

+ Check out the Fleet documentation on{" "} + +

+
+
+ ); + } + case "collecting": { + return ( +
+
+

+ {noSandboxHosts + ? "Fleet begins collecting software inventory after a host is enrolled" + : "Fleet is collecting software inventory"} +

+

+ Try again in about{" "} + {noSandboxHosts + ? "15 minutes after host enrollment." + : "1 hour as the system catches up."} +

+
+
+ ); + } + default: { + return ( +
+
+

No software matches the current search criteria.

+

Try again in about 1 hour as the system catches up.

+
+
+ ); + } + } +}; + +export default EmptySoftware; diff --git a/frontend/pages/software/components/EmptySoftwareTable.tsx b/frontend/pages/software/components/EmptySoftwareTable.tsx index f1b950054c..c0fc4551d1 100644 --- a/frontend/pages/software/components/EmptySoftwareTable.tsx +++ b/frontend/pages/software/components/EmptySoftwareTable.tsx @@ -12,6 +12,7 @@ export interface IEmptySoftwareTableProps { isSandboxMode?: boolean; isCollectingSoftware?: boolean; isSearching?: boolean; + noSandboxHosts?: boolean; } const EmptySoftwareTable = ({ @@ -20,6 +21,7 @@ const EmptySoftwareTable = ({ isSandboxMode, isCollectingSoftware, isSearching, + noSandboxHosts, }: IEmptySoftwareTableProps): JSX.Element => { const emptySoftware: IEmptyTableProps = { header: `No ${ @@ -34,8 +36,9 @@ const EmptySoftwareTable = ({ emptySoftware.info = "This report is updated every hour to protect the performance of your devices."; if (isSandboxMode) { - emptySoftware.info = - "Fleet begins collecting software inventory after a host is enrolled."; + emptySoftware.info = noSandboxHosts + ? "Fleet begins collecting software inventory after a host is enrolled." + : "Fleet is collecting software inventory"; } } if (isSoftwareDisabled) { diff --git a/frontend/router/paths.ts b/frontend/router/paths.ts index 4833f4683b..3d322a0e39 100644 --- a/frontend/router/paths.ts +++ b/frontend/router/paths.ts @@ -52,6 +52,7 @@ export default { LOGIN: `${URL_PREFIX}/login`, LOGOUT: `${URL_PREFIX}/logout`, MANAGE_HOSTS: `${URL_PREFIX}/hosts/manage`, + MANAGE_HOSTS_ADD_HOSTS: `${URL_PREFIX}/hosts/manage/?add_hosts=true`, MANAGE_HOSTS_LABEL: (labelId: number | string): string => { return `${URL_PREFIX}/hosts/manage/labels/${labelId}`; },