Update frontend to support software install activities (#18871)
This commit is contained in:
@@ -73,13 +73,15 @@ export enum ActivityType {
|
||||
ResentConfigurationProfile = "resent_configuration_profile",
|
||||
AddedSoftware = "added_software",
|
||||
DeletedSoftware = "deleted_software",
|
||||
InstalledSoftware = "installed_software",
|
||||
}
|
||||
|
||||
// This is a subset of ActivityType that are shown only for the host past activities
|
||||
export type IHostPastActivityType =
|
||||
export type IHostActivityType =
|
||||
| ActivityType.RanScript
|
||||
| ActivityType.LockedHost
|
||||
| ActivityType.UnlockedHost;
|
||||
| ActivityType.UnlockedHost
|
||||
| ActivityType.InstalledSoftware;
|
||||
|
||||
export interface IActivity {
|
||||
created_at: string;
|
||||
@@ -92,8 +94,9 @@ export interface IActivity {
|
||||
details?: IActivityDetails;
|
||||
}
|
||||
|
||||
export type IPastActivity = Omit<IActivity, "type"> & {
|
||||
type: IHostPastActivityType;
|
||||
export type IHostActivity = Omit<IActivity, "type" | "details"> & {
|
||||
type: IHostActivityType;
|
||||
details: IActivityDetails;
|
||||
};
|
||||
|
||||
export interface IActivityDetails {
|
||||
@@ -120,6 +123,7 @@ export interface IActivityDetails {
|
||||
host_display_name?: string;
|
||||
host_display_names?: string[];
|
||||
host_ids?: number[];
|
||||
host_id?: number;
|
||||
host_platform?: string;
|
||||
installed_from_dep?: boolean;
|
||||
mdm_platform?: "microsoft" | "apple";
|
||||
@@ -134,7 +138,8 @@ export interface IActivityDetails {
|
||||
deadline_days?: number;
|
||||
grace_period_days?: number;
|
||||
stats?: ISchedulableQueryStats;
|
||||
host_id?: number;
|
||||
software_title?: string;
|
||||
software_package?: string;
|
||||
status?: string;
|
||||
install_uuid?: string;
|
||||
}
|
||||
|
||||
@@ -148,3 +148,32 @@ export const formatSoftwareType = ({
|
||||
}
|
||||
return type;
|
||||
};
|
||||
/*
|
||||
* SoftwareInstallStatus represents the possible states of software install operations.
|
||||
*
|
||||
*/
|
||||
export type SoftwareInstallStatus = "pending" | "installed" | "failed";
|
||||
|
||||
/**
|
||||
*
|
||||
* ISoftwareInstallResult is the shape of a software install result object
|
||||
* returned by the Fleet API.
|
||||
*
|
||||
*/
|
||||
export interface ISoftwareInstallResult {
|
||||
install_uuid: string;
|
||||
software_title: string;
|
||||
software_title_id: number;
|
||||
software_package: string;
|
||||
host_id: number;
|
||||
host_display_name: string;
|
||||
status: SoftwareInstallStatus;
|
||||
detail: string;
|
||||
output: string;
|
||||
pre_install_query_output: string;
|
||||
post_install_script_output: string;
|
||||
}
|
||||
|
||||
export interface ISoftwareInstallResults {
|
||||
results: ISoftwareInstallResult;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ import Button from "components/buttons/Button";
|
||||
import Spinner from "components/Spinner";
|
||||
// @ts-ignore
|
||||
import FleetIcon from "components/icons/FleetIcon";
|
||||
|
||||
import { SoftwareInstallDetailsModal } from "pages/SoftwarePage/components/SoftwareInstallDetails";
|
||||
|
||||
import ActivityItem from "./ActivityItem";
|
||||
import ScriptDetailsModal from "./components/ScriptDetailsModal/ScriptDetailsModal";
|
||||
|
||||
@@ -35,6 +38,7 @@ const ActivityFeed = ({
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [showShowQueryModal, setShowShowQueryModal] = useState(false);
|
||||
const [showScriptDetailsModal, setShowScriptDetailsModal] = useState(false);
|
||||
const [installedSoftwareUuid, setInstalledSoftwareUuid] = useState("");
|
||||
const queryShown = useRef("");
|
||||
const queryImpact = useRef<string | undefined>(undefined);
|
||||
const scriptExecutionId = useRef("");
|
||||
@@ -81,6 +85,7 @@ const ActivityFeed = ({
|
||||
activityType: ActivityType,
|
||||
details: IActivityDetails
|
||||
) => {
|
||||
console.log("activityType", activityType);
|
||||
switch (activityType) {
|
||||
case ActivityType.LiveQuery:
|
||||
queryShown.current = details.query_sql ?? "";
|
||||
@@ -93,6 +98,11 @@ const ActivityFeed = ({
|
||||
scriptExecutionId.current = details.script_execution_id ?? "";
|
||||
setShowScriptDetailsModal(true);
|
||||
break;
|
||||
case ActivityType.InstalledSoftware:
|
||||
// installUuid.current = details.install_uuid ?? "";
|
||||
// console.log("installUuid.current", installUuid.current);
|
||||
setInstalledSoftwareUuid(details.install_uuid ?? "");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -184,6 +194,12 @@ const ActivityFeed = ({
|
||||
onCancel={() => setShowScriptDetailsModal(false)}
|
||||
/>
|
||||
)}
|
||||
{installedSoftwareUuid && (
|
||||
<SoftwareInstallDetailsModal
|
||||
installUuid={installedSoftwareUuid}
|
||||
onCancel={() => setInstalledSoftwareUuid("")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ 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";
|
||||
|
||||
@@ -854,6 +855,42 @@ const TAGGED_TEMPLATES = {
|
||||
</>
|
||||
);
|
||||
},
|
||||
installedSoftware: (
|
||||
activity: IActivity,
|
||||
onDetailsClick?: (type: ActivityType, details: IActivityDetails) => void
|
||||
) => {
|
||||
const { details } = activity;
|
||||
if (!details) {
|
||||
return TAGGED_TEMPLATES.defaultActivityTemplate(activity);
|
||||
}
|
||||
|
||||
console.log("onDetailsClick", onDetailsClick);
|
||||
|
||||
const {
|
||||
host_display_name: hostName,
|
||||
software_title: title,
|
||||
status,
|
||||
install_uuid,
|
||||
} = details;
|
||||
|
||||
return (
|
||||
<>
|
||||
{" "}
|
||||
{getSoftwareInstallStatusPredicate(status)} <b>{title}</b> software on{" "}
|
||||
<b>{hostName}</b>.{" "}
|
||||
<Button
|
||||
className={`${baseClass}__show-query-link`}
|
||||
variant="text-link"
|
||||
onClick={() =>
|
||||
onDetailsClick?.(ActivityType.InstalledSoftware, { install_uuid })
|
||||
}
|
||||
>
|
||||
Show details{" "}
|
||||
<Icon className={`${baseClass}__show-query-icon`} name="eye" />
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const getDetail = (
|
||||
@@ -1033,6 +1070,9 @@ const getDetail = (
|
||||
case ActivityType.DeletedSoftware: {
|
||||
return TAGGED_TEMPLATES.deletedSoftware(activity);
|
||||
}
|
||||
case ActivityType.InstalledSoftware: {
|
||||
return TAGGED_TEMPLATES.installedSoftware(activity, onDetailsClick);
|
||||
}
|
||||
|
||||
default: {
|
||||
return TAGGED_TEMPLATES.defaultActivityTemplate(activity);
|
||||
|
||||
+2
-2
@@ -41,9 +41,9 @@ const SoftwareDetailsSummary = ({
|
||||
<dl className={`${baseClass}__info`}>
|
||||
<h1>{title}</h1>
|
||||
<dl className={`${baseClass}__description-list`}>
|
||||
{type && <DataSet title="Type" value={type} />}
|
||||
{!!type && <DataSet title="Type" value={type} />}
|
||||
|
||||
{versions && <DataSet title="Versions" value={versions} />}
|
||||
{!!versions && <DataSet title="Versions" value={versions} />}
|
||||
<DataSet title="Hosts" value={hosts === 0 ? "---" : hosts} />
|
||||
</dl>
|
||||
</dl>
|
||||
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
import React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
import {
|
||||
ISoftwareInstallResult,
|
||||
ISoftwareInstallResults,
|
||||
SoftwareInstallStatus,
|
||||
} from "interfaces/software";
|
||||
import softwareAPI from "services/entities/software";
|
||||
|
||||
import Modal from "components/Modal";
|
||||
import Button from "components/buttons/Button";
|
||||
import Icon from "components/Icon";
|
||||
import Textarea from "components/Textarea";
|
||||
import DataError from "components/DataError/DataError";
|
||||
import Spinner from "components/Spinner/Spinner";
|
||||
import { IconNames } from "components/icons";
|
||||
|
||||
const baseClass = "software-install-details";
|
||||
|
||||
const STATUS_ICONS: Record<SoftwareInstallStatus, IconNames> = {
|
||||
pending: "pending-outline",
|
||||
installed: "success-outline",
|
||||
failed: "error-outline",
|
||||
} as const;
|
||||
|
||||
const STATUS_PREDICATES: Record<SoftwareInstallStatus, string> = {
|
||||
pending: "will install",
|
||||
installed: "installed",
|
||||
failed: "failed to install",
|
||||
} as const;
|
||||
|
||||
const StatusMessage = ({
|
||||
result: { host_display_name, software_package, software_title, status },
|
||||
}: {
|
||||
result: ISoftwareInstallResult;
|
||||
}) => {
|
||||
return (
|
||||
<div className={`${baseClass}__status-message`}>
|
||||
<Icon name={STATUS_ICONS[status]} />
|
||||
<span>
|
||||
Fleet {STATUS_PREDICATES[status]} <b>{software_title}</b> (
|
||||
{software_package}) on <b>{host_display_name}</b>
|
||||
{status === "pending" ? " when it comes online" : ""}.
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OUTPUT_DISPLAY_LABELS = {
|
||||
pre_install_query_output: "Pre-install condition",
|
||||
output: "Software install output",
|
||||
post_install_script_output: "Post-install script output",
|
||||
} as const;
|
||||
|
||||
const Output = ({
|
||||
displayKey,
|
||||
result,
|
||||
}: {
|
||||
displayKey: keyof typeof OUTPUT_DISPLAY_LABELS;
|
||||
result: ISoftwareInstallResult;
|
||||
}) => {
|
||||
return (
|
||||
<div className={`${baseClass}__script-output`}>
|
||||
{OUTPUT_DISPLAY_LABELS[displayKey]}:
|
||||
<Textarea className={`${baseClass}__output-textarea`}>
|
||||
{result[displayKey]}
|
||||
</Textarea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SoftwareInstallDetails = ({
|
||||
installUuid,
|
||||
}: {
|
||||
installUuid: string;
|
||||
}) => {
|
||||
const { data: result, isLoading, isError } = useQuery<
|
||||
ISoftwareInstallResults,
|
||||
Error,
|
||||
ISoftwareInstallResult
|
||||
>(
|
||||
["softwareInstallResults", installUuid],
|
||||
() => {
|
||||
return softwareAPI.getSoftwareInstallResult(installUuid);
|
||||
},
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
select: (data) => data.results,
|
||||
}
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner />;
|
||||
} else if (isError) {
|
||||
return <DataError description="Close this modal and try again." />;
|
||||
} else if (!result) {
|
||||
// FIXME: Find a better solution for this.
|
||||
return <DataError description="No data returned." />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`${baseClass}__software-install-details`}>
|
||||
<StatusMessage result={result} />
|
||||
{result.status !== "pending" && (
|
||||
<>
|
||||
{result.pre_install_query_output && (
|
||||
<Output displayKey="pre_install_query_output" result={result} />
|
||||
)}
|
||||
{result.output && <Output displayKey="output" result={result} />}
|
||||
{result.post_install_script_output && (
|
||||
<Output displayKey="post_install_script_output" result={result} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const SoftwareInstallDetailsModal = ({
|
||||
installUuid,
|
||||
onCancel,
|
||||
}: {
|
||||
installUuid: string;
|
||||
onCancel: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<Modal
|
||||
title="Install details"
|
||||
onExit={onCancel}
|
||||
onEnter={onCancel}
|
||||
className={baseClass}
|
||||
>
|
||||
<>
|
||||
<div className={`${baseClass}__modal-content`}>
|
||||
<SoftwareInstallDetails installUuid={installUuid} />
|
||||
</div>
|
||||
<div className="modal-cta-wrap">
|
||||
<Button onClick={onCancel} variant="brand">
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
.software-install-details {
|
||||
.modal__content {
|
||||
margin-top: $pad-xlarge;
|
||||
}
|
||||
&__status-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $pad-small;
|
||||
margin: 0;
|
||||
}
|
||||
&__script-output {
|
||||
padding-top: $pad-xlarge;
|
||||
.textarea {
|
||||
margin-top: $pad-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
SoftwareInstallDetails,
|
||||
SoftwareInstallDetailsModal,
|
||||
} from "./SoftwareInstallDetails";
|
||||
@@ -13,7 +13,7 @@ import { QueryContext } from "context/query";
|
||||
import { NotificationContext } from "context/notification";
|
||||
|
||||
import activitiesAPI, {
|
||||
IPastActivitiesResponse,
|
||||
IHostActivitiesResponse,
|
||||
IUpcomingActivitiesResponse,
|
||||
} from "services/entities/activities";
|
||||
import hostAPI from "services/entities/hosts";
|
||||
@@ -58,6 +58,7 @@ import TabsWrapper from "components/TabsWrapper";
|
||||
import MainContent from "components/MainContent";
|
||||
import BackLink from "components/BackLink";
|
||||
import ScriptDetailsModal from "pages/DashboardPage/cards/ActivityFeed/components/ScriptDetailsModal";
|
||||
import { SoftwareInstallDetailsModal } from "pages/SoftwarePage/components/SoftwareInstallDetails";
|
||||
|
||||
import HostSummaryCard from "../cards/HostSummary";
|
||||
import AboutCard from "../cards/About";
|
||||
@@ -171,6 +172,7 @@ const HostDetailsPage = ({
|
||||
const [selectedPolicy, setSelectedPolicy] = useState<IHostPolicy | null>(
|
||||
null
|
||||
);
|
||||
const [softwareInstallUuid, setSoftwareInstallUuid] = useState("");
|
||||
const [isUpdatingHost, setIsUpdatingHost] = useState(false);
|
||||
const [refetchStartTime, setRefetchStartTime] = useState<number | null>(null);
|
||||
const [showRefetchSpinner, setShowRefetchSpinner] = useState(false);
|
||||
@@ -369,9 +371,9 @@ const HostDetailsPage = ({
|
||||
isError: pastActivitiesIsError,
|
||||
refetch: refetchPastActivities,
|
||||
} = useQuery<
|
||||
IPastActivitiesResponse,
|
||||
IHostActivitiesResponse,
|
||||
Error,
|
||||
IPastActivitiesResponse,
|
||||
IHostActivitiesResponse,
|
||||
Array<{
|
||||
scope: string;
|
||||
pageIndex: number;
|
||||
@@ -552,6 +554,9 @@ const HostDetailsPage = ({
|
||||
case "ran_script":
|
||||
setScriptDetailsId(details?.script_execution_id || "");
|
||||
break;
|
||||
case "installed_software":
|
||||
setSoftwareInstallUuid(details?.install_uuid || "");
|
||||
break;
|
||||
default: // do nothing
|
||||
}
|
||||
},
|
||||
@@ -582,7 +587,11 @@ const HostDetailsPage = ({
|
||||
|
||||
const onCancelScriptDetailsModal = useCallback(() => {
|
||||
setScriptDetailsId("");
|
||||
}, [setScriptDetailsId]);
|
||||
}, []);
|
||||
|
||||
const onCancelSoftwareInstallDetailsModal = useCallback(() => {
|
||||
setSoftwareInstallUuid("");
|
||||
}, []);
|
||||
|
||||
const onTransferHostSubmit = async (team: ITeam) => {
|
||||
setIsUpdatingHost(true);
|
||||
@@ -967,6 +976,12 @@ const HostDetailsPage = ({
|
||||
onCancel={onCancelScriptDetailsModal}
|
||||
/>
|
||||
)}
|
||||
{!!softwareInstallUuid && (
|
||||
<SoftwareInstallDetailsModal
|
||||
installUuid={softwareInstallUuid}
|
||||
onCancel={onCancelSoftwareInstallDetailsModal}
|
||||
/>
|
||||
)}
|
||||
{showLockHostModal && (
|
||||
<LockModal
|
||||
id={host.id}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Tab, TabList, TabPanel, Tabs } from "react-tabs";
|
||||
|
||||
import { IActivityDetails } from "interfaces/activity";
|
||||
import {
|
||||
IPastActivitiesResponse,
|
||||
IHostActivitiesResponse,
|
||||
IUpcomingActivitiesResponse,
|
||||
} from "services/entities/activities";
|
||||
|
||||
@@ -48,7 +48,7 @@ const UpcomingTooltip = () => {
|
||||
|
||||
interface IActivityProps {
|
||||
activeTab: "past" | "upcoming";
|
||||
activities?: IPastActivitiesResponse | IUpcomingActivitiesResponse;
|
||||
activities?: IHostActivitiesResponse | IUpcomingActivitiesResponse;
|
||||
isLoading?: boolean;
|
||||
isError?: boolean;
|
||||
upcomingCount: number;
|
||||
@@ -101,7 +101,7 @@ const Activity = ({
|
||||
</TabList>
|
||||
<TabPanel>
|
||||
<PastActivityFeed
|
||||
activities={activities as IPastActivitiesResponse | undefined}
|
||||
activities={activities as IHostActivitiesResponse | undefined}
|
||||
onDetailsClick={onShowDetails}
|
||||
isError={isError}
|
||||
onNextPage={onNextPage}
|
||||
|
||||
@@ -2,8 +2,8 @@ import React from "react";
|
||||
|
||||
import {
|
||||
ActivityType,
|
||||
IHostPastActivityType,
|
||||
IPastActivity,
|
||||
IHostActivityType,
|
||||
IHostActivity,
|
||||
} from "interfaces/activity";
|
||||
|
||||
import { ShowActivityDetailsHandler } from "./Activity";
|
||||
@@ -11,10 +11,11 @@ import { ShowActivityDetailsHandler } from "./Activity";
|
||||
import RanScriptActivityItem from "./ActivityItems/RanScriptActivityItem";
|
||||
import LockedHostActivityItem from "./ActivityItems/LockedHostActivityItem";
|
||||
import UnlockedHostActivityItem from "./ActivityItems/UnlockedHostActivityItem";
|
||||
import InstalledSoftwareActivityItem from "./ActivityItems/InstalledSoftwareActivityItem";
|
||||
|
||||
/** The component props that all host activity items must adhere to */
|
||||
export interface IHostActivityItemComponentProps {
|
||||
activity: IPastActivity;
|
||||
activity: IHostActivity;
|
||||
}
|
||||
|
||||
/** Used for activity items component that need a show details handler */
|
||||
@@ -24,11 +25,12 @@ export interface IHostActivityItemComponentPropsWithShowDetails
|
||||
}
|
||||
|
||||
export const pastActivityComponentMap: Record<
|
||||
IHostPastActivityType,
|
||||
IHostActivityType,
|
||||
| React.FC<IHostActivityItemComponentProps>
|
||||
| React.FC<IHostActivityItemComponentPropsWithShowDetails>
|
||||
> = {
|
||||
[ActivityType.RanScript]: RanScriptActivityItem,
|
||||
[ActivityType.LockedHost]: LockedHostActivityItem,
|
||||
[ActivityType.UnlockedHost]: UnlockedHostActivityItem,
|
||||
[ActivityType.InstalledSoftware]: InstalledSoftwareActivityItem,
|
||||
};
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
|
||||
import { SoftwareInstallStatus } from "interfaces/software";
|
||||
|
||||
import { IHostActivityItemComponentPropsWithShowDetails } from "../../ActivityConfig";
|
||||
import HostActivityItem from "../../HostActivityItem";
|
||||
import ShowDetailsButton from "../../ShowDetailsButton";
|
||||
|
||||
const baseClass = "installed-software-activity-item";
|
||||
|
||||
const STATUS_PREDICATES: Record<SoftwareInstallStatus, string> = {
|
||||
failed: "failed to install",
|
||||
installed: "installed",
|
||||
pending: "told Fleet to install",
|
||||
} as const;
|
||||
|
||||
export const getSoftwareInstallStatusPredicate = (
|
||||
status: string | undefined
|
||||
) => {
|
||||
if (!status) {
|
||||
return STATUS_PREDICATES.pending;
|
||||
}
|
||||
return (
|
||||
STATUS_PREDICATES[status as SoftwareInstallStatus] ||
|
||||
STATUS_PREDICATES.pending
|
||||
);
|
||||
};
|
||||
|
||||
const InstalledSoftwareActivityItem = ({
|
||||
activity,
|
||||
onShowDetails,
|
||||
}: IHostActivityItemComponentPropsWithShowDetails) => {
|
||||
const { actor_full_name: actorName, details } = activity;
|
||||
|
||||
const { status, software_title: title } = details;
|
||||
|
||||
return (
|
||||
<HostActivityItem className={baseClass} activity={activity}>
|
||||
<b>{actorName}</b> {getSoftwareInstallStatusPredicate(status)}{" "}
|
||||
<b>{title}</b> software on this host.{" "}
|
||||
<ShowDetailsButton activity={activity} onShowDetails={onShowDetails} />
|
||||
</HostActivityItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstalledSoftwareActivityItem;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { default } from "./InstalledSoftwareActivityItem";
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { IPastActivity } from "interfaces/activity";
|
||||
import { IPastActivitiesResponse } from "services/entities/activities";
|
||||
import { IHostActivity } from "interfaces/activity";
|
||||
import { IHostActivitiesResponse } from "services/entities/activities";
|
||||
|
||||
// @ts-ignore
|
||||
import FleetIcon from "components/icons/FleetIcon";
|
||||
@@ -16,7 +16,7 @@ import { pastActivityComponentMap } from "../ActivityConfig";
|
||||
const baseClass = "past-activity-feed";
|
||||
|
||||
interface IPastActivityFeedProps {
|
||||
activities?: IPastActivitiesResponse;
|
||||
activities?: IHostActivitiesResponse;
|
||||
isError?: boolean;
|
||||
onDetailsClick: ShowActivityDetailsHandler;
|
||||
onNextPage: () => void;
|
||||
@@ -53,7 +53,7 @@ const PastActivityFeed = ({
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div>
|
||||
{activitiesList.map((activity: IPastActivity) => {
|
||||
{activitiesList.map((activity: IHostActivity) => {
|
||||
const ActivityItemComponent = pastActivityComponentMap[activity.type];
|
||||
return (
|
||||
<ActivityItemComponent
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { formatDistanceToNowStrict } from "date-fns";
|
||||
|
||||
import { IActivity } from "interfaces/activity";
|
||||
import { ActivityType, IHostActivity } from "interfaces/activity";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
import { DEFAULT_GRAVATAR_LINK } from "utilities/constants";
|
||||
import {
|
||||
@@ -19,10 +19,39 @@ import { ShowActivityDetailsHandler } from "../Activity";
|
||||
const baseClass = "upcoming-activity";
|
||||
|
||||
interface IUpcomingActivityProps {
|
||||
activity: IActivity;
|
||||
activity: IHostActivity;
|
||||
onDetailsClick: ShowActivityDetailsHandler;
|
||||
}
|
||||
|
||||
const formatPredicate = ({ type, details }: IHostActivity) => {
|
||||
switch (type) {
|
||||
case ActivityType.RanScript:
|
||||
return (
|
||||
<>
|
||||
told Fleet to run{" "}
|
||||
{formatScriptNameForActivityItem(details?.script_name)}
|
||||
</>
|
||||
);
|
||||
case ActivityType.InstalledSoftware:
|
||||
return (
|
||||
<>
|
||||
told Fleet to install{" "}
|
||||
{details?.software_title ? (
|
||||
<>
|
||||
<b>{details.software_title}</b>{" "}
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
software
|
||||
</>
|
||||
);
|
||||
default:
|
||||
// this should never happen
|
||||
return <>{type}</>;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Combine this with ./UpcomingActivity/UpcomingActivity.tsx and
|
||||
// frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx
|
||||
const UpcomingActivity = ({
|
||||
@@ -46,23 +75,16 @@ const UpcomingActivity = ({
|
||||
<div className={`${baseClass}__details-wrapper`}>
|
||||
<div className="activity-details">
|
||||
<span className={`${baseClass}__details-topline`}>
|
||||
<b>{activity.actor_full_name}</b>
|
||||
<>
|
||||
{" "}
|
||||
told Fleet to run{" "}
|
||||
{formatScriptNameForActivityItem(
|
||||
activity.details?.script_name
|
||||
)}{" "}
|
||||
on this host.{" "}
|
||||
<Button
|
||||
className={`${baseClass}__show-query-link`}
|
||||
variant="text-link"
|
||||
onClick={() => onDetailsClick?.(activity)}
|
||||
>
|
||||
Show details{" "}
|
||||
<Icon className={`${baseClass}__show-query-icon`} name="eye" />
|
||||
</Button>
|
||||
</>
|
||||
<b>{activity.actor_full_name}</b> {formatPredicate(activity)} on
|
||||
this host.{" "}
|
||||
<Button
|
||||
className={`${baseClass}__show-query-link`}
|
||||
variant="text-link"
|
||||
onClick={() => onDetailsClick?.(activity)}
|
||||
>
|
||||
Show details{" "}
|
||||
<Icon className={`${baseClass}__show-query-icon`} name="eye" />
|
||||
</Button>
|
||||
</span>
|
||||
<br />
|
||||
<span
|
||||
|
||||
+5
-4
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { IActivity } from "interfaces/activity";
|
||||
import { IActivitiesResponse } from "services/entities/activities";
|
||||
import { IHostActivity } from "interfaces/activity";
|
||||
import { IUpcomingActivitiesResponse } from "services/entities/activities";
|
||||
|
||||
// @ts-ignore
|
||||
import FleetIcon from "components/icons/FleetIcon";
|
||||
@@ -15,7 +15,7 @@ import { ShowActivityDetailsHandler } from "../Activity";
|
||||
const baseClass = "upcoming-activity-feed";
|
||||
|
||||
interface IUpcomingActivityFeedProps {
|
||||
activities?: IActivitiesResponse;
|
||||
activities?: IUpcomingActivitiesResponse;
|
||||
isError?: boolean;
|
||||
onDetailsClick: ShowActivityDetailsHandler;
|
||||
onNextPage: () => void;
|
||||
@@ -52,8 +52,9 @@ const UpcomingActivityFeed = ({
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div>
|
||||
{activitiesList.map((activity: IActivity) => (
|
||||
{activitiesList.map((activity: IHostActivity) => (
|
||||
<UpcomingActivity
|
||||
key={activity.id}
|
||||
activity={activity}
|
||||
onDetailsClick={onDetailsClick}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import endpoints from "utilities/endpoints";
|
||||
import { IActivity, IPastActivity } from "interfaces/activity";
|
||||
import { IActivity, IHostActivity } from "interfaces/activity";
|
||||
import sendRequest from "services";
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
|
||||
@@ -16,15 +16,15 @@ export interface IActivitiesResponse {
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPastActivitiesResponse {
|
||||
activities: IPastActivity[] | null;
|
||||
export interface IHostActivitiesResponse {
|
||||
activities: IHostActivity[] | null;
|
||||
meta: {
|
||||
has_next_results: boolean;
|
||||
has_previous_results: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IUpcomingActivitiesResponse extends IActivitiesResponse {
|
||||
export interface IUpcomingActivitiesResponse extends IHostActivitiesResponse {
|
||||
count: number;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
id: number,
|
||||
page = DEFAULT_PAGE,
|
||||
perPage = DEFAULT_PAGE_SIZE
|
||||
): Promise<IPastActivitiesResponse> => {
|
||||
): Promise<IHostActivitiesResponse> => {
|
||||
const { HOST_PAST_ACTIVITIES } = endpoints;
|
||||
|
||||
const queryParams = {
|
||||
|
||||
@@ -231,4 +231,10 @@ export default {
|
||||
})}`;
|
||||
return sendRequest("GET", path);
|
||||
},
|
||||
|
||||
getSoftwareInstallResult: (installUuid: string) => {
|
||||
const { SOFTWARE_INSTALL_RESULTS } = endpoints;
|
||||
const path = SOFTWARE_INSTALL_RESULTS(installUuid);
|
||||
return sendRequest("GET", path);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -131,6 +131,8 @@ export default {
|
||||
SOFTWARE_PACKAGE_ADD: `/${API_VERSION}/fleet/software/package`,
|
||||
SOFTWARE_PACKAGE: (id: number) =>
|
||||
`/${API_VERSION}/fleet/software/packages/${id}`,
|
||||
SOFTWARE_INSTALL_RESULTS: (uuid: string) =>
|
||||
`/${API_VERSION}/fleet/software/install/results/${uuid}`,
|
||||
|
||||
// AI endpoints
|
||||
AUTOFILL_POLICY: `/${API_VERSION}/fleet/autofill/policy`,
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
} from "utilities/constants";
|
||||
import { ISchedulableQueryStats } from "interfaces/schedulable_query";
|
||||
import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
import { IActivityDetails } from "interfaces/activity";
|
||||
|
||||
const ORG_INFO_ATTRS = ["org_name", "org_logo_url"];
|
||||
const ADMIN_ATTRS = ["email", "name", "password", "password_confirmation"];
|
||||
|
||||
Reference in New Issue
Block a user