- {OUTPUT_DISPLAY_LABELS[displayKey]}:
+ {SOFTWARE_INSTALL_OUTPUT_DISPLAY_LABELS[displayKey]}:
diff --git a/frontend/pages/SoftwarePage/components/SoftwareInstallDetails/_styles.scss b/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/_styles.scss
similarity index 82%
rename from frontend/pages/SoftwarePage/components/SoftwareInstallDetails/_styles.scss
rename to frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/_styles.scss
index 5e8df96d67..8cdc2ef600 100644
--- a/frontend/pages/SoftwarePage/components/SoftwareInstallDetails/_styles.scss
+++ b/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/_styles.scss
@@ -7,6 +7,10 @@
align-items: center;
gap: $pad-small;
margin: 0;
+ .icon {
+ padding-top: 3px;
+ align-self: flex-start;
+ }
}
&__script-output {
padding-top: $pad-xlarge;
diff --git a/frontend/pages/SoftwarePage/components/SoftwareInstallDetails/index.ts b/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/index.ts
similarity index 100%
rename from frontend/pages/SoftwarePage/components/SoftwareInstallDetails/index.ts
rename to frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetails/index.ts
diff --git a/frontend/components/ActivityDetails/InstallDetails/constants.ts b/frontend/components/ActivityDetails/InstallDetails/constants.ts
new file mode 100644
index 0000000000..b3cd75cf79
--- /dev/null
+++ b/frontend/components/ActivityDetails/InstallDetails/constants.ts
@@ -0,0 +1,39 @@
+import { IconNames } from "components/icons";
+import { SoftwareInstallStatus } from "interfaces/software";
+
+export const INSTALL_DETAILS_STATUS_ICONS: Record<
+ SoftwareInstallStatus,
+ IconNames
+> = {
+ pending: "pending-outline",
+ installed: "success-outline",
+ failed: "error-outline",
+} as const;
+
+const INSTALL_DETAILS_STATUS_PREDICATES: Record<
+ SoftwareInstallStatus,
+ string
+> = {
+ pending: "will install",
+ installed: "installed",
+ failed: "failed to install",
+} as const;
+
+export const getInstallDetailsStatusPredicate = (
+ status: string | undefined
+) => {
+ if (!status) {
+ return INSTALL_DETAILS_STATUS_PREDICATES.pending;
+ }
+ return (
+ INSTALL_DETAILS_STATUS_PREDICATES[
+ status.toLowerCase() as SoftwareInstallStatus
+ ] || INSTALL_DETAILS_STATUS_PREDICATES.pending
+ );
+};
+
+export const SOFTWARE_INSTALL_OUTPUT_DISPLAY_LABELS = {
+ pre_install_query_output: "Pre-install condition",
+ output: "Software install output",
+ post_install_script_output: "Post-install script output",
+} as const;
diff --git a/frontend/components/FileUploader/FileUploader.tsx b/frontend/components/FileUploader/FileUploader.tsx
index 9c22930f44..a79955df28 100644
--- a/frontend/components/FileUploader/FileUploader.tsx
+++ b/frontend/components/FileUploader/FileUploader.tsx
@@ -20,6 +20,7 @@ type ISupportedGraphicNames = Extract<
| "file-pkg"
| "file-p7m"
| "file-pem"
+ | "file-vpp"
>;
export const FileDetails = ({
diff --git a/frontend/components/FileUploader/_styles.scss b/frontend/components/FileUploader/_styles.scss
index 2862466e17..b94ca3ef7c 100644
--- a/frontend/components/FileUploader/_styles.scss
+++ b/frontend/components/FileUploader/_styles.scss
@@ -2,7 +2,7 @@
display: flex;
flex-direction: column;
align-items: center;
- border-radius: $border-radius;
+ border-radius: $border-radius-medium;
background-color: $ui-fleet-blue-10;
border: 1px solid $ui-fleet-black-10;
padding: $pad-xlarge $pad-large;
@@ -43,6 +43,7 @@
}
&__message {
margin: 0;
+ color: $ui-fleet-black-75;
}
&__additional-info {
diff --git a/frontend/components/TableContainer/DataTable/SoftwareNameCell/SoftwareNameCell.tsx b/frontend/components/TableContainer/DataTable/SoftwareNameCell/SoftwareNameCell.tsx
index 38fc05db82..d8301b9b8b 100644
--- a/frontend/components/TableContainer/DataTable/SoftwareNameCell/SoftwareNameCell.tsx
+++ b/frontend/components/TableContainer/DataTable/SoftwareNameCell/SoftwareNameCell.tsx
@@ -61,6 +61,7 @@ interface ISoftwareNameCellProps {
router?: InjectedRouter;
hasPackage?: boolean;
isSelfService?: boolean;
+ iconUrl?: string;
}
const SoftwareNameCell = ({
@@ -70,6 +71,7 @@ const SoftwareNameCell = ({
router,
hasPackage = false,
isSelfService = false,
+ iconUrl,
}: ISoftwareNameCellProps) => {
// NO path or router means it's not clickable. return
// a non-clickable cell early
@@ -95,7 +97,7 @@ const SoftwareNameCell = ({
customOnClick={onClickSoftware}
value={
<>
-
+
{name}
{hasPackage && (
diff --git a/frontend/components/forms/fields/Radio/Radio.tsx b/frontend/components/forms/fields/Radio/Radio.tsx
index cf38349bac..de8a153df5 100644
--- a/frontend/components/forms/fields/Radio/Radio.tsx
+++ b/frontend/components/forms/fields/Radio/Radio.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { ReactNode } from "react";
import classnames from "classnames";
import TooltipWrapper from "components/TooltipWrapper";
@@ -6,7 +6,7 @@ import TooltipWrapper from "components/TooltipWrapper";
const baseClass = "radio";
export interface IRadioProps {
- label: string;
+ label: ReactNode;
value: string;
id: string;
onChange: (value: string) => void;
diff --git a/frontend/components/graphics/FileVpp.tsx b/frontend/components/graphics/FileVpp.tsx
new file mode 100644
index 0000000000..35b0abaa5e
--- /dev/null
+++ b/frontend/components/graphics/FileVpp.tsx
@@ -0,0 +1,59 @@
+import React from "react";
+
+const FileVpp = () => {
+ return (
+
+ );
+};
+
+export default FileVpp;
diff --git a/frontend/components/graphics/index.ts b/frontend/components/graphics/index.ts
index 84e10a3711..cfcafe4562 100644
--- a/frontend/components/graphics/index.ts
+++ b/frontend/components/graphics/index.ts
@@ -12,6 +12,7 @@ import FilePdf from "./FilePdf";
import FilePkg from "./FilePkg";
import FileP7m from "./FileP7m";
import FilePem from "./FilePem";
+import FileVpp from "./FileVpp";
import EmptyHosts from "./EmptyHosts";
import EmptyTeams from "./EmptyTeams";
import EmptyPacks from "./EmptyPacks";
@@ -39,6 +40,7 @@ export const GRAPHIC_MAP = {
"file-pkg": FilePkg,
"file-p7m": FileP7m,
"file-pem": FilePem,
+ "file-vpp": FileVpp,
// Other graphics
"collecting-results": CollectingResults,
};
diff --git a/frontend/interfaces/activity.ts b/frontend/interfaces/activity.ts
index 2ca026aa47..127945310b 100644
--- a/frontend/interfaces/activity.ts
+++ b/frontend/interfaces/activity.ts
@@ -77,6 +77,11 @@ export enum ActivityType {
AddedSoftware = "added_software",
DeletedSoftware = "deleted_software",
InstalledSoftware = "installed_software",
+ EnabledVpp = "enabled_vpp",
+ DisabledVpp = "disabled_vpp",
+ AddedAppStoreApp = "added_app_store_app",
+ DeletedAppStoreApp = "deleted_app_store_app",
+ InstalledAppStoreApp = "installed_app_store_app",
}
// This is a subset of ActivityType that are shown only for the host past activities
@@ -84,12 +89,14 @@ export type IHostPastActivityType =
| ActivityType.RanScript
| ActivityType.LockedHost
| ActivityType.UnlockedHost
- | ActivityType.InstalledSoftware;
+ | ActivityType.InstalledSoftware
+ | ActivityType.InstalledAppStoreApp;
// This is a subset of ActivityType that are shown only for the host upcoming activities
export type IHostUpcomingActivityType =
| ActivityType.RanScript
- | ActivityType.InstalledSoftware;
+ | ActivityType.InstalledSoftware
+ | ActivityType.InstalledAppStoreApp;
export interface IActivity {
created_at: string;
@@ -156,4 +163,6 @@ export interface IActivityDetails {
status?: string;
install_uuid?: string;
self_service?: boolean;
+ command_uuid?: string;
+ app_store_id?: number;
}
diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts
index f52cfd3925..e4118b83d1 100644
--- a/frontend/interfaces/config.ts
+++ b/frontend/interfaces/config.ts
@@ -37,6 +37,9 @@ interface ICustomSetting {
export interface IMdmConfig {
enable_disk_encryption: boolean;
+ /** `enabled_and_configured` only tells us if Apples MDM has been enabled and
+ configured correctly. The naming is slightly confusing but at one point we
+ only supported apple mdm, so thats why it's name the way it is. */
enabled_and_configured: boolean;
apple_bm_default_team?: string;
apple_bm_terms_expired: boolean;
diff --git a/frontend/interfaces/mdm.ts b/frontend/interfaces/mdm.ts
index 3a75fc8258..496cc1c8b9 100644
--- a/frontend/interfaces/mdm.ts
+++ b/frontend/interfaces/mdm.ts
@@ -160,3 +160,22 @@ export enum BootstrapPackageStatus {
PENDING = "pending",
FAILED = "failed",
}
+
+/**
+ * IMdmCommandResult is the shape of an mdm command result object
+ * returned by the Fleet API.
+ */
+export interface IMdmCommandResult {
+ host_uuid: string;
+ command_uuid: string;
+ /** Status is the status of the command. It can be one of Acknowledged, Error, or NotNow for
+ // Apple, or 200, 400, etc for Windows. */
+ status: string;
+ updated_at: string;
+ request_type: string;
+ hostname: string;
+ /** Payload is a base64-encoded string containing the MDM command request */
+ payload: string;
+ /** Result is a base64-enconded string containing the MDM command response */
+ result: string;
+}
diff --git a/frontend/interfaces/software.ts b/frontend/interfaces/software.ts
index 91e45130bc..cedd6a67db 100644
--- a/frontend/interfaces/software.ts
+++ b/frontend/interfaces/software.ts
@@ -1,5 +1,8 @@
import { startCase } from "lodash";
import PropTypes from "prop-types";
+
+import { IconNames } from "components/icons";
+
import vulnerabilityInterface from "./vulnerability";
export default PropTypes.shape({
@@ -58,6 +61,7 @@ export interface ISoftwarePackage {
pre_install_query?: string;
post_install_script?: string;
self_service: boolean;
+ icon_url: string | null;
status: {
installed: number;
pending: number;
@@ -65,28 +69,46 @@ export interface ISoftwarePackage {
};
}
-interface ISoftwareTitle {
+export const isSoftwarePackage = (
+ data: ISoftwarePackage | IAppStoreApp
+): data is ISoftwarePackage =>
+ (data as ISoftwarePackage).install_script !== undefined;
+
+export interface IAppStoreApp {
+ name: string;
+ app_store_id: number;
+ latest_version: string;
+ icon_url: string;
+ status: {
+ installed: number;
+ pending: number;
+ failed: number;
+ };
+}
+
+export interface ISoftwareTitle {
id: number;
name: string;
- software_package: ISoftwarePackage | string | null;
versions_count: number;
source: string;
hosts_count: number;
versions: ISoftwareTitleVersion[] | null;
- browser: string;
- self_service?: boolean;
-}
-
-export interface ISoftwareTitleWithPackageName
- extends Omit
{
- software_package: string | null;
- self_service: boolean;
-}
-
-export interface ISoftwareTitleWithPackageDetail
- extends Omit {
software_package: ISoftwarePackage | null;
- self_service?: never;
+ app_store_app: IAppStoreApp | null;
+ browser?: string;
+}
+
+export interface ISoftwareTitleDetails {
+ id: number;
+ name: string;
+ software_package: ISoftwarePackage | null;
+ app_store_app: IAppStoreApp | null;
+ source: string;
+ hosts_count: number;
+ versions: ISoftwareTitleVersion[] | null;
+ bundle_identifier?: string;
+ browser?: string;
+ versions_count?: number;
}
export interface ISoftwareVulnerability {
@@ -213,6 +235,11 @@ export interface ISoftwareLastInstall {
installed_at: string;
}
+export interface IAppLastInstall {
+ command_uuid: string;
+ installed_at: string;
+}
+
export interface ISoftwareInstallVersion {
version: string;
last_opened_at: string | null;
@@ -220,24 +247,77 @@ export interface ISoftwareInstallVersion {
installed_paths: string[];
}
+export interface IHostSoftwarePackage {
+ name: string;
+ self_service: boolean;
+ icon_url: string;
+ version: string;
+ last_install: ISoftwareLastInstall | null;
+}
+
+export interface IHostAppStoreApp {
+ app_store_id: string;
+ self_service: boolean;
+ icon_url: string;
+ version: string;
+ last_install: IAppLastInstall | null;
+}
+
export interface IHostSoftware {
id: number;
name: string;
- package_available_for_install?: string | null;
- self_service: boolean;
+ software_package: IHostSoftwarePackage | null;
+ app_store_app: IHostAppStoreApp | null;
source: string;
bundle_identifier?: string;
status: SoftwareInstallStatus | null;
- last_install: ISoftwareLastInstall | null;
installed_versions: ISoftwareInstallVersion[] | null;
}
-export type IDeviceSoftware = Omit<
- IHostSoftware,
- "package_available_for_install"
-> & {
- package: {
- name: string;
- version: string;
- };
+export type IDeviceSoftware = IHostSoftware;
+
+const INSTALL_STATUS_PREDICATES: Record = {
+ failed: "failed to install",
+ installed: "installed",
+ pending: "told Fleet to install",
+} as const;
+
+export const getInstallStatusPredicate = (status: string | undefined) => {
+ if (!status) {
+ return INSTALL_STATUS_PREDICATES.pending;
+ }
+ return (
+ INSTALL_STATUS_PREDICATES[status.toLowerCase() as SoftwareInstallStatus] ||
+ INSTALL_STATUS_PREDICATES.pending
+ );
+};
+
+export const INSTALL_STATUS_ICONS: Record = {
+ pending: "pending-outline",
+ installed: "success-outline",
+ failed: "error-outline",
+} as const;
+
+type IHostSoftwarePackageWithLastInstall = IHostSoftwarePackage & {
+ last_install: ISoftwareLastInstall;
+};
+
+export const hasHostSoftwarePackageLastInstall = (
+ software: IHostSoftware
+): software is IHostSoftware & {
+ software_package: IHostSoftwarePackageWithLastInstall;
+} => {
+ return !!software.software_package?.last_install;
+};
+
+type IHostAppWithLastInstall = IHostAppStoreApp & {
+ last_install: IAppLastInstall;
+};
+
+export const hasHostSoftwareAppLastInstall = (
+ software: IHostSoftware
+): software is IHostSoftware & {
+ app_store_app: IHostAppWithLastInstall;
+} => {
+ return !!software.app_store_app?.last_install;
};
diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsx
index b51946d0f3..3a5d520d27 100644
--- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsx
+++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsx
@@ -16,7 +16,8 @@ import Spinner from "components/Spinner";
// @ts-ignore
import FleetIcon from "components/icons/FleetIcon";
-import { SoftwareInstallDetailsModal } from "pages/SoftwarePage/components/SoftwareInstallDetails";
+import { AppInstallDetailsModal } from "components/ActivityDetails/InstallDetails/AppInstallDetails";
+import { SoftwareInstallDetailsModal } from "components/ActivityDetails/InstallDetails/SoftwareInstallDetails/SoftwareInstallDetails";
import ActivityItem from "./ActivityItem";
import ScriptDetailsModal from "./components/ScriptDetailsModal/ScriptDetailsModal";
@@ -37,6 +38,10 @@ const ActivityFeed = ({
const [showShowQueryModal, setShowShowQueryModal] = useState(false);
const [showScriptDetailsModal, setShowScriptDetailsModal] = useState(false);
const [installedSoftwareUuid, setInstalledSoftwareUuid] = useState("");
+ const [
+ appInstallDetails,
+ setAppInstallDetails,
+ ] = useState(null);
const queryShown = useRef("");
const queryImpact = useRef(undefined);
const scriptExecutionId = useRef("");
@@ -97,10 +102,11 @@ const ActivityFeed = ({
setShowScriptDetailsModal(true);
break;
case ActivityType.InstalledSoftware:
- // installUuid.current = details.install_uuid ?? "";
- // console.log("installUuid.current", installUuid.current);
setInstalledSoftwareUuid(details.install_uuid ?? "");
break;
+ case ActivityType.InstalledAppStoreApp:
+ setAppInstallDetails(details);
+ break;
default:
break;
}
@@ -197,6 +203,12 @@ const ActivityFeed = ({
onCancel={() => setInstalledSoftwareUuid("")}
/>
)}
+ {appInstallDetails && (
+ setAppInstallDetails(null)}
+ />
+ )}
);
};
diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx
index 387fc30dde..18530b33f5 100644
--- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx
+++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx
@@ -3,6 +3,7 @@ import { find, lowerCase, noop } from "lodash";
import { formatDistanceToNowStrict } from "date-fns";
import { ActivityType, IActivity, IActivityDetails } from "interfaces/activity";
+import { getInstallStatusPredicate } from "interfaces/software";
import {
addGravatarUrlToResource,
formatScriptNameForActivityItem,
@@ -16,7 +17,6 @@ import Icon from "components/Icon";
import ReactTooltip from "react-tooltip";
import PremiumFeatureIconWithTooltip from "components/PremiumFeatureIconWithTooltip";
import { COLORS } from "styles/var/colors";
-import { getSoftwareInstallStatusPredicate } from "pages/hosts/details/cards/Activity/ActivityItems/InstalledSoftwareActivityItem/InstalledSoftwareActivityItem";
const baseClass = "activity-item";
@@ -795,7 +795,7 @@ const TAGGED_TEMPLATES = {
<>
{" "}
added