Rebase sandcastle onto main (#10317)

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [ ] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [ ] Documented any API changes (docs/Using-Fleet/REST-API.md or
docs/Contributing/API-for-contributors.md)
- [ ] Documented any permissions changes
- [ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for
new osquery data ingestion features.
- [ ] Added/updated tests
- [ ] Manual QA for all new/changed functionality
  - For Orbit and Fleet Desktop changes:
- [ ] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- [ ] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).

---------

Co-authored-by: RachelElysia <71795832+RachelElysia@users.noreply.github.com>
This commit is contained in:
Zachary Winnerman
2023-03-30 11:22:41 -07:00
committed by GitHub
co-authored by RachelElysia
parent 70a429327f
commit b5e37ce056
21 changed files with 281 additions and 36 deletions
@@ -43,7 +43,7 @@
align-items: center;
padding: 16px;
gap: 16px;
width: 286px;
width: 247px;
border: 1px solid #c5c7d1;
border-radius: 4px;
+5
View File
@@ -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) {
@@ -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 = ({
)}
<SandboxGate
fallbackComponent={() => (
<SandboxExpiryMessage expiry={sandboxExpiryTime} />
<SandboxExpiryMessage
expiry={sandboxExpiryTime}
noSandboxHosts={noSandboxHosts}
/>
)}
/>
{children}
@@ -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 (
<div className={baseClass}>
<p>Your Fleet Sandbox expires in {expiry}.</p>
<div className={`${baseClass}__tip`}>
<Icon name="lightbulb" />
<p>
<b>Quick tip: </b> Enroll a host to get started.
</p>
<form>
<Button
onClick={openAddHostModal}
className={`${baseClass}__add-hosts`}
variant="brand"
>
<span>Add hosts</span>
</Button>
</form>
</div>
</div>
);
}
return (
<a
href="https://fleetdm.com/docs/deploying"
href="https://fleetdm.com/docs/using-fleet/learn-how-to-use-fleet#how-to-add-your-device-to-fleet"
target="_blank"
rel="noreferrer"
className={baseClass}
>
<p>Your Fleet Sandbox expires in {expiry}.</p>
<span>
Learn how to deploy Fleet
<img alt="Open external link" src={ExternalLinkIcon} />
</span>
<p>
<b>Learn how to use Fleet</b>{" "}
<Icon name="external-link" color="core-fleet-black" />
</p>
</a>
);
};
@@ -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 {
@@ -359,7 +359,6 @@ $base-class: "button";
.info {
&__header {
display: block;
width: 100%;
text-align: left;
}
&__data {
+32
View File
@@ -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 (
<svg
width="28"
height="32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 28 33"
>
<g clipPath="url(#a)" fill={COLORS[color]}>
<path d="M11.15 27.79c-1 0-1.84-.73-1.99-1.72l-.47-3.13c-.2-1.35-.74-2.67-1.58-3.93a8.418 8.418 0 0 1-1.44-5.2C5.89 9.48 9.31 6 13.63 5.72c.19-.01.39-.02.58-.02 4.71 0 8.55 3.83 8.55 8.55 0 1.72-.51 3.38-1.48 4.8-.83 1.22-1.37 2.66-1.62 4.27l-.43 2.85c-.13.89-.98 1.62-1.88 1.62H11.15ZM14.2 7.38c-.15 0-.31 0-.47.02a6.83 6.83 0 0 0-4.42 2.04 6.819 6.819 0 0 0-1.97 4.45c-.07 1.5.33 2.94 1.16 4.18.99 1.47 1.61 3.02 1.85 4.62l.47 3.13c.02.17.16.29.33.29H17.34c.07 0 .2-.12.21-.18l.43-2.85c.28-1.87.92-3.54 1.89-4.97a6.853 6.853 0 0 0 1.18-3.85c0-3.78-3.08-6.86-6.86-6.86l.01-.02ZM10.89 30.21c-.39 0-.72-.32-.72-.71 0-.39.32-.71.72-.71h6.62c.39 0 .71.32.71.71 0 .39-.32.71-.71.71h-6.62ZM12.61 32.62c-.39 0-.72-.32-.72-.71 0-.39.32-.72.72-.72h3.11c.39 0 .72.32.72.72 0 .4-.32.71-.72.71h-3.11Z" />
<path d="M13.16 26.65h-.01c-.17 0-.3-.15-.3-.32 0-.08.31-8.12-1.36-10.75a.32.32 0 0 1 .09-.43.32.32 0 0 1 .43.09c1.78 2.79 1.47 10.77 1.46 11.11 0 .17-.14.3-.31.3Z" />
<path d="M11.74 17.16c-.26 0-.55 0-.82-.1-.43-.15-.7-.36-.86-.7-.22-.45-.1-1.13.46-1.45.45-.25 1.13-.2 1.48.31.1.14.06.33-.08.43-.14.1-.33.06-.43-.08-.17-.25-.5-.22-.68-.12-.24.13-.3.44-.2.64.08.16.19.28.5.38.19.06.43.06.66.06h2.73c.17 0 .31.14.31.31 0 .17-.14.31-.31.31h-2.77l.01.01ZM15.23 26.65c-.16 0-.3-.13-.31-.3-.01-.34-.32-8.31 1.46-11.11.09-.14.28-.19.43-.09.14.09.19.28.09.43-1.67 2.63-1.37 10.67-1.36 10.75 0 .17-.13.31-.3.32h-.01Z" />
<path d="M16.66 17.16h-2.77c-.17 0-.31-.14-.31-.31 0-.17.14-.31.31-.31h2.73c.23 0 .47 0 .66-.06.32-.11.43-.23.5-.38.1-.2.04-.5-.2-.64-.17-.1-.51-.12-.68.12-.1.14-.29.17-.43.08a.317.317 0 0 1-.08-.43c.36-.51 1.04-.56 1.48-.31.56.31.68.99.46 1.45-.16.34-.43.55-.86.7-.27.09-.56.1-.82.1l.01-.01ZM14.2 4.13c-.36 0-.65-.29-.65-.65V1.03c0-.36.29-.65.65-.65.36 0 .65.29.65.65v2.45c0 .36-.29.65-.65.65ZM6.64 7.26a.66.66 0 0 1-.46-.19L4.27 5.16a.658.658 0 0 1 0-.92.66.66 0 0 1 .46-.19c.17 0 .34.07.46.19L7.1 6.15c.25.25.25.67 0 .92a.66.66 0 0 1-.46.19ZM21.76 7.26a.66.66 0 0 1-.46-.19.66.66 0 0 1-.19-.46c0-.17.07-.34.19-.46l1.91-1.91a.66.66 0 0 1 .46-.19c.17 0 .34.07.46.19s.19.29.19.46c0 .17-.07.34-.19.46l-1.91 1.91a.66.66 0 0 1-.46.19ZM24.9 14.8c-.36 0-.65-.29-.65-.65 0-.36.29-.65.65-.65h2.45c.36 0 .65.29.65.65 0 .36-.29.65-.65.65H24.9ZM1.06 14.88c-.36 0-.65-.29-.65-.65 0-.36.29-.65.65-.65h2.45c.36 0 .65.29.65.65 0 .36-.29.65-.65.65H1.06Z" />
</g>
<defs>
<clipPath id="a">
<path fill="#fff" d="M.41.38H28v32.24H.41z" />
</clipPath>
</defs>
</svg>
);
};
export default Lightbulb;
+2
View File
@@ -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,
@@ -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()}
</div>
{evented && <EventedTableTag selectedTableName={name} />}
{mdmRequired && (
<span className={`${baseClass}__mdm-required`}>Requires MDM</span>
)}
<div className={`${baseClass}__description`}>
<FleetMarkdown markdown={description} />
</div>
@@ -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;
}
}
+24
View File
@@ -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 });
},
+1
View File
@@ -94,4 +94,5 @@ export interface IPolicyNew {
resolution: string;
critical: boolean;
platform: IPlatformString;
mdm_required?: boolean;
}
@@ -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={() => (
<EmptySoftwareTable
isCollectingSoftware={isCollectingInventory}
noSandboxHosts={noSandboxHosts}
/>
)}
showMarkAllPages={false}
@@ -124,6 +128,7 @@ const Software = ({
emptyComponent={() => (
<EmptySoftwareTable
isCollectingSoftware={isCollectingInventory}
noSandboxHosts={noSandboxHosts}
isFilterVulnerable
/>
)}
@@ -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
@@ -68,7 +68,12 @@ const AddPolicyModal = ({
onClick={() => onAddPolicy(policy)}
>
<>
<span className="info__header">{policy.name}</span>
<div className={`${baseClass}__policy-name`}>
<span className="info__header">{policy.name}</span>
{policy.mdm_required && (
<span className={`${baseClass}__mdm-policy`}>Requires MDM</span>
)}
</div>
<span className="info__data">{policy.description}</span>
</>
</Button>
@@ -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;
}
}
}
+16 -1
View File
@@ -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:
@@ -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}
@@ -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 (
<div className={`${baseClass}__empty-software`}>
<div className="empty-software__inner">
<h1>Software inventory is disabled.</h1>
<p>
Check out the Fleet documentation on{" "}
<CustomLink
url="https://fleetdm.com/docs/using-fleet/vulnerability-processing#configuration"
text="how to configure software inventory"
newTab
/>
</p>
</div>
</div>
);
}
case "collecting": {
return (
<div className={`${baseClass}__empty-software`}>
<div className="empty-software__inner">
<h1>
{noSandboxHosts
? "Fleet begins collecting software inventory after a host is enrolled"
: "Fleet is collecting software inventory"}
</h1>
<p>
Try again in about{" "}
{noSandboxHosts
? "15 minutes after host enrollment."
: "1 hour as the system catches up."}
</p>
</div>
</div>
);
}
default: {
return (
<div className={`${baseClass}__empty-software`}>
<div className="empty-software__inner">
<h1>No software matches the current search criteria.</h1>
<p>Try again in about 1 hour as the system catches up.</p>
</div>
</div>
);
}
}
};
export default EmptySoftware;
@@ -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) {
+1
View File
@@ -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}`;
},