import React, { useCallback, useContext, useState } from "react"; import { useQuery } from "react-query"; import classnames from "classnames"; import { NotificationContext } from "context/notification"; import { IScript } from "interfaces/script"; import { getErrorReason } from "interfaces/errors"; import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; import Modal from "components/Modal"; import scriptsAPI, { IListScriptsQueryKey, IScriptBatchSupportedFilters, IScriptsResponse, } from "services/entities/scripts"; import ScriptDetailsModal from "pages/hosts/components/ScriptDetailsModal"; import Spinner from "components/Spinner"; import EmptyTable from "components/EmptyTable"; import Button from "components/buttons/Button"; import RunScriptBatchPaginatedList from "../RunScriptBatchPaginatedList"; import { IPaginatedListScript } from "../RunScriptBatchPaginatedList/RunScriptBatchPaginatedList"; const baseClass = "run-script-batch-modal"; interface IRunScriptBatchModal { runByFilters: boolean; // otherwise, by selectedHostIds // since teamId has multiple uses in this component, it's passed in as its own prop and added to // `filters` as needed filters: Omit; teamId: number; totalFilteredHostsCount: number; selectedHostIds: number[]; onCancel: () => void; } const RunScriptBatchModal = ({ runByFilters = false, filters, totalFilteredHostsCount, selectedHostIds, teamId, onCancel, }: IRunScriptBatchModal) => { const { renderFlash } = useContext(NotificationContext); const [isUpdating, setIsUpdating] = useState(false); const [scriptForDetails, setScriptForDetails] = useState< IPaginatedListScript | undefined >(undefined); // just used to get total number of scripts, could be optimized by implementing a dedicated scriptsCount endpoint const { data: scripts } = useQuery< IScriptsResponse, Error, IScript[], IListScriptsQueryKey[] >( [ { scope: "scripts", team_id: teamId, }, ], ({ queryKey }) => { return scriptsAPI.getScripts(queryKey[0]); }, { ...DEFAULT_USE_QUERY_OPTIONS, keepPreviousData: true, select: (data) => data.scripts || [], } ); const onRunScriptBatch = useCallback( async (script: IScript) => { setIsUpdating(true); const body = runByFilters ? // satisfy IScriptBatchSupportedFilters { script_id: script.id, filters: { ...filters, team_id: teamId } } : { script_id: script.id, host_ids: selectedHostIds }; try { await scriptsAPI.runScriptBatch(body); renderFlash( "success", `Script is running on ${ runByFilters ? totalFilteredHostsCount.toLocaleString() : selectedHostIds.length.toLocaleString() } hosts, or will run as each host comes online. See host details for individual results.` ); } catch (error) { let errorMessage = "Could not run script."; if (getErrorReason(error).includes("too many hosts")) { errorMessage = "Could not run script: too many hosts targeted. Please try again with fewer hosts."; } renderFlash("error", errorMessage); // can determine more specific error case with additional call to upcoming summary endpoint } finally { setIsUpdating(false); } }, [renderFlash, selectedHostIds] ); const renderModalContent = () => { if (scripts === undefined) { return ; } if (!scripts.length) { return ( You can add saved scripts{" "} here. } /> ); } const targetCount = runByFilters ? totalFilteredHostsCount : selectedHostIds.length; return ( <>

Will run on{" "} {targetCount.toLocaleString()} host{targetCount > 1 ? "s" : ""} . You can see individual script results on the host details page.

); }; const classes = classnames(baseClass, { [`${baseClass}__hide-main`]: !!scriptForDetails, }); return ( <> <> {renderModalContent()}
{!!scriptForDetails && ( setScriptForDetails(undefined)} selectedScriptDetails={scriptForDetails} suppressSecondaryActions customPrimaryButtons={
} /> )} ); }; export default RunScriptBatchModal;