fleet/frontend/components/ActivityDetails/InstallDetails/SoftwareInstallDetailsModal/SoftwareInstallDetailsModal.tsx
RachelElysia 9bfef5dec3
Fleet UI: Remove incorrect copy (#42586)
## Issue
Closes #40683 

## Description
- Removed from 2 modals that were rendering it

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
2026-03-28 15:27:28 -05:00

470 lines
13 KiB
TypeScript

/** For script-only packages (e.g. software source is sh_packages or ps1_packages)
* we use SoftwareScriptDetailsModal
* For iOS/iPadOS packages (e.g. .ipa packages software source is ios_apps or ipados_apps)
* we use SoftwareIpaInstallDetailsModal with the command_uuid
* For VPP iOS/iPadOS packages, we use VppInstallDetailsModal
* For Android Google Play Store apps, we also use THIS modal
* For all other apps, we use THIS modal */
import React, { useState } from "react";
import { useQuery } from "react-query";
import { formatDistanceToNow } from "date-fns";
import { AxiosError } from "axios";
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
import {
IHostSoftware,
ISoftwareInstallResult,
ISoftwareInstallResults,
} from "interfaces/software";
import softwareAPI from "services/entities/software";
import deviceUserAPI from "services/entities/device_user";
import InventoryVersions from "pages/hosts/details/components/InventoryVersions";
import { getDisplayedSoftwareName } from "pages/SoftwarePage/helpers";
import Modal from "components/Modal";
import ModalFooter from "components/ModalFooter";
import Button from "components/buttons/Button";
import IconStatusMessage from "components/IconStatusMessage";
import Textarea from "components/Textarea";
import DataError from "components/DataError/DataError";
import DeviceUserError from "components/DeviceUserError";
import Spinner from "components/Spinner/Spinner";
import RevealButton from "components/buttons/RevealButton";
import CustomLink from "components/CustomLink";
import {
INSTALL_DETAILS_STATUS_ICONS,
getInstallDetailsStatusPredicate,
} from "../constants";
const baseClass = "software-install-details-modal";
export type IPackageInstallDetails = {
host_display_name?: string;
install_uuid?: string; // not actually optional
};
export const renderContactOption = (url?: string) => (
<>
{" "}
or{" "}
{url ? (
<CustomLink url={url} text="contact your IT admin" newTab />
) : (
"contact your IT admin"
)}
</>
);
interface IInstallStatusMessage {
softwareName: string;
installResult?: ISoftwareInstallResult;
isMyDevicePage: boolean;
contactUrl?: string;
/** Used only for overriding failed_install/failed_uninstall -> "is installed."
- From Host -> Software: override based on inventory.
- From Activity feed: never override (always show the failure).
Parity with VPPInstallDetailsModal/SoftwareIpaInstallDetailsModal */
canOverrideFailureWithInstalled?: boolean;
}
// TODO - match VppInstallDetailsModal status to this, still accounting for MDM-specific cases
// present there
export const StatusMessage = ({
softwareName,
installResult,
isMyDevicePage,
contactUrl,
canOverrideFailureWithInstalled = false,
}: IInstallStatusMessage) => {
// the case when software is installed by the user and not by Fleet
if (!installResult) {
return (
<IconStatusMessage
className={`${baseClass}__status-message`}
iconName="success"
message={
<span>
<b>{softwareName}</b> is installed.
</span>
}
/>
);
}
const {
host_display_name,
software_package,
software_title,
status,
updated_at,
created_at,
} = installResult;
// Treat failed_install/failed_uninstall with installed versions as installed
// as the host still reports installed versions (4.82 #31663)
const overrideFailureWithInstalled =
canOverrideFailureWithInstalled &&
["failed_install", "failed_uninstall"].includes(status || "");
if (overrideFailureWithInstalled) {
return (
<IconStatusMessage
className={`${baseClass}__status-message`}
iconName="success"
message={
<span>
<b>{softwareName}</b> is installed.
</span>
}
/>
);
}
const formattedHost = host_display_name ? (
<b>{host_display_name}</b>
) : (
"the host"
);
const displayTimeStamp = ["failed_install", "installed"].includes(
status || ""
)
? ` (${formatDistanceToNow(new Date(updated_at || created_at), {
includeSeconds: true,
addSuffix: true,
})})`
: "";
const renderStatusCopy = () => {
const prefix = (
<>
Fleet {getInstallDetailsStatusPredicate(status)} <b>{software_title}</b>
</>
);
const middle = isMyDevicePage ? (
<>
{" "}
{displayTimeStamp}
{status === "failed_install" && (
<>. You can retry{renderContactOption(contactUrl)}</>
)}
</>
) : (
<>
{" "}
({software_package}) on {formattedHost}
{status === "pending_install"
? " when it comes online"
: displayTimeStamp}
</>
);
return (
<span>
{prefix}
{middle}
{"."}
</span>
);
};
return (
<IconStatusMessage
className={`${baseClass}__status-message`}
iconName={
INSTALL_DETAILS_STATUS_ICONS[status || "pending_install"] ??
"pending-outline"
}
message={renderStatusCopy()}
/>
);
};
interface IModalButtonsProps {
deviceAuthToken?: string;
status?: string;
hostSoftwareId?: number;
onRetry?: (id: number) => void;
onCancel: () => void;
}
export const ModalButtons = ({
deviceAuthToken,
status,
hostSoftwareId,
onRetry,
onCancel,
}: IModalButtonsProps) => {
if (deviceAuthToken && status === "failed_install") {
const onClickRetry = () => {
// on My Device Page, where this is relevant, both will be defined
if (onRetry && hostSoftwareId) {
onRetry(hostSoftwareId);
}
onCancel();
};
return (
<ModalFooter
primaryButtons={
<>
<Button variant="inverse" onClick={onCancel}>
Cancel
</Button>
<Button type="submit" onClick={onClickRetry}>
Retry
</Button>
</>
}
/>
);
}
return (
<ModalFooter primaryButtons={<Button onClick={onCancel}>Close</Button>} />
);
};
interface ISoftwareInstallDetailsProps {
/** note that details.install_uuid is present in hostSoftware, but since it is always needed for
this modal while hostSoftware is not, as in the case of the activity feeds, it is specifically
necessary in the details prop */
details: IPackageInstallDetails;
hostSoftware?: IHostSoftware; // for inventory versions, and software name when not Fleet installed (not present on activity feeds)
deviceAuthToken?: string; // My Device Page only
onCancel: () => void;
onRetry?: (id: number) => void; // My Device Page only
contactUrl?: string; // My Device Page only
}
export const SoftwareInstallDetailsModal = ({
details: detailsFromProps,
onCancel,
hostSoftware,
deviceAuthToken,
onRetry,
contactUrl,
}: ISoftwareInstallDetailsProps) => {
// will always be present
const installUUID = detailsFromProps.install_uuid ?? "";
const [showInstallDetails, setShowInstallDetails] = useState(false);
const toggleInstallDetails = () => {
setShowInstallDetails((prev) => !prev);
};
const isInstalledByFleet = hostSoftware
? !!hostSoftware.software_package?.last_install
: true; // if no hostSoftware passed in, can assume this is the activity feed, meaning this can only refer to a Fleet-handled install
const { data: swInstallResult, isLoading, isError, error } = useQuery<
ISoftwareInstallResults,
AxiosError,
ISoftwareInstallResult
>(
["softwareInstallResults", installUUID],
() => {
return deviceAuthToken
? deviceUserAPI.getSoftwareInstallResult(deviceAuthToken, installUUID)
: softwareAPI.getSoftwareInstallResult(installUUID);
},
{
enabled: !!isInstalledByFleet,
...DEFAULT_USE_QUERY_OPTIONS,
staleTime: 3000,
select: (data) => data.results,
}
);
const renderInventoryVersionsSection = () => {
if (hostSoftware?.installed_versions?.length) {
return <InventoryVersions hostSoftware={hostSoftware} />;
}
return null;
};
const renderInstallDetailsSection = () => {
const outputs = [
{
label: "Pre-install query output:",
value: swInstallResult?.pre_install_query_output,
},
{
label: "Install script output:",
value: swInstallResult?.output,
},
{
label: "Post-install script output:",
value: swInstallResult?.post_install_script_output,
},
];
// Only show details button if there's details to display
const showDetailsButton =
(!!swInstallResult?.post_install_script_output ||
!!swInstallResult?.output ||
!!swInstallResult?.pre_install_query_output) &&
swInstallResult?.status !== "pending_install";
return (
<>
{showDetailsButton && (
<RevealButton
isShowing={showInstallDetails}
showText="Details"
hideText="Details"
caretPosition="after"
onClick={toggleInstallDetails}
/>
)}
{showInstallDetails &&
outputs.map(
({ label, value }) =>
value && (
<Textarea key={label} label={label} variant="code">
{value}
</Textarea>
)
)}
</>
);
};
const hostDisplayname =
swInstallResult?.host_display_name || detailsFromProps.host_display_name;
const installResultWithHostDisplayName = swInstallResult
? {
...swInstallResult,
host_display_name: hostDisplayname,
}
: undefined;
// True when host inventory reports at least one installed version for this app.
const inventoryReportsInstalled = !!hostSoftware?.installed_versions?.length;
// This modal is opened in two contexts:
// - From Host -> Software: hostSoftware is defined (we trust inventory to override failures).
// - From the Activity feed: hostSoftware is undefined (we trust install result status).
const openedFromHostSoftwarePage = !!hostSoftware;
// Used only for overriding failed_install/failed_uninstall -> "is installed."
// - From Host -> Software: override based on inventory.
// - From Activity feed: never override (always show the failure).
const canOverrideFailureWithInstalled = openedFromHostSoftwarePage
? inventoryReportsInstalled
: false;
// Treat failed_install / failed_uninstall with installed versions as installed
const overrideFailedMessageWithInstalledMessage =
canOverrideFailureWithInstalled &&
["failed_install", "failed_uninstall"].includes(
swInstallResult?.status || "" || ""
);
// Hide version section from pending installs or failures that aren't overridden to installed (4.82 #31663)
const shouldShowInventoryVersions =
(!!hostSoftware &&
deviceAuthToken &&
![
"pending_install",
"failed_install",
"failed_uninstall",
"pending",
].includes(swInstallResult?.status || "")) ||
overrideFailedMessageWithInstalledMessage;
const renderContent = () => {
if (isInstalledByFleet) {
if (isLoading) {
return <Spinner />;
}
if (isError) {
if (error?.status === 404) {
return deviceAuthToken ? (
<DeviceUserError />
) : (
<DataError
description="Couldn't get install details"
excludeIssueLink
/>
);
}
if (error?.status === 401) {
return deviceAuthToken ? (
<DeviceUserError />
) : (
<DataError description="Close this modal and try again." />
);
}
}
if (!swInstallResult) {
return deviceAuthToken ? (
<DeviceUserError />
) : (
<DataError description="No data returned." />
);
}
if (
!["installed", "pending_install", "failed_install"].includes(
swInstallResult.status
)
) {
return (
<DataError
description={`Unexpected software install status ${swInstallResult.status}`}
/>
);
}
}
return (
<div className={`${baseClass}__modal-content`}>
<StatusMessage
installResult={installResultWithHostDisplayName}
softwareName={getDisplayedSoftwareName(
hostSoftware?.name,
hostSoftware?.display_name
)}
isMyDevicePage={!!deviceAuthToken}
contactUrl={contactUrl}
canOverrideFailureWithInstalled={canOverrideFailureWithInstalled}
/>
{shouldShowInventoryVersions && renderInventoryVersionsSection()}
{isInstalledByFleet &&
!overrideFailedMessageWithInstalledMessage &&
renderInstallDetailsSection()}
</div>
);
};
return (
<Modal
title="Install details"
onExit={onCancel}
onEnter={onCancel}
className={baseClass}
>
{renderContent()}
<ModalButtons
deviceAuthToken={deviceAuthToken}
status={swInstallResult?.status}
hostSoftwareId={hostSoftware?.id}
onRetry={onRetry}
onCancel={onCancel}
/>
</Modal>
);
};
export default SoftwareInstallDetailsModal;