fleet/frontend/pages/hosts/details/HostDetailsPage/modals/RunScriptModal/RunScriptModal.tsx
Martin Angers ca435eb244
Queued scripts feature (#16300)
This is the feature branch for the [queued
scripts](https://github.com/fleetdm/fleet/issues/15529) story.

---------

Co-authored-by: Jahziel Villasana-Espinoza <jahziel@fleetdm.com>
Co-authored-by: Gabriel Hernandez <ghernandez345@gmail.com>
Co-authored-by: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com>
Co-authored-by: Roberto Dip <dip.jesusr@gmail.com>
2024-01-29 11:37:54 -03:00

176 lines
5.1 KiB
TypeScript

import React, { useCallback, useContext, useMemo, useState } from "react";
import { useQuery } from "react-query";
import { AxiosResponse } from "axios";
import { AppContext } from "context/app";
import { NotificationContext } from "context/notification";
import { IApiError } from "interfaces/errors";
import { IHost } from "interfaces/host";
import { IUser } from "interfaces/user";
import scriptsAPI, {
IHostScriptsQueryKey,
IHostScriptsResponse,
} from "services/entities/scripts";
import { IHostScript } from "interfaces/script";
import Button from "components/buttons/Button";
import DataError from "components/DataError/DataError";
import EmptyTable from "components/EmptyTable";
import Modal from "components/Modal";
import Spinner from "components/Spinner/Spinner";
import TableContainer, {
ITableQueryData,
} from "components/TableContainer/TableContainer";
import { generateTableColumnConfigs } from "./ScriptsTableConfig";
const baseClass = "run-script-modal";
interface IScriptsProps {
currentUser: IUser | null;
host: IHost;
scriptDetailsId: string;
setScriptDetailsId: React.Dispatch<React.SetStateAction<string>>;
onClose: () => void;
}
const EmptyComponent = () => <></>;
const RunScriptModal = ({
currentUser,
host,
scriptDetailsId,
setScriptDetailsId,
onClose,
}: IScriptsProps) => {
const [page, setPage] = useState<number>(0);
const [runScriptRequested, setRunScriptRequested] = useState(false);
const { renderFlash } = useContext(NotificationContext);
const { config } = useContext(AppContext);
const {
data: hostScriptResponse,
isError,
isLoading,
isFetching,
refetch: refetchHostScripts,
} = useQuery<
IHostScriptsResponse,
IApiError,
IHostScriptsResponse,
IHostScriptsQueryKey[]
>(
[{ scope: "host_scripts", host_id: host.id, page, per_page: 10 }],
({ queryKey }) => scriptsAPI.getHostScripts(queryKey[0]),
{
refetchOnWindowFocus: false,
retry: false,
staleTime: 3000,
onSuccess: () => {
setRunScriptRequested(false);
},
}
);
const onSelectAction = useCallback(
async (action: string, script: IHostScript) => {
switch (action) {
case "showDetails": {
setScriptDetailsId(script.last_execution?.execution_id || "");
break;
}
case "run": {
try {
setRunScriptRequested(true);
await scriptsAPI.runScript({
host_id: host.id,
script_id: script.script_id,
});
renderFlash("success", "Script successfully queued for execution");
refetchHostScripts();
} catch (e) {
const error = e as AxiosResponse<IApiError>;
console.log(error);
renderFlash("error", error.data.errors[0].reason);
setRunScriptRequested(false);
}
break;
}
default: // do nothing
}
},
[host.id, refetchHostScripts, renderFlash, setScriptDetailsId]
);
const onQueryChange = useCallback(({ pageIndex }: ITableQueryData) => {
setPage(pageIndex);
}, []);
const scriptColumnConfigs = useMemo(
() =>
generateTableColumnConfigs(
currentUser,
host.team_id,
!!config?.server_settings?.scripts_disabled,
onSelectAction
),
[currentUser, host.team_id, config, onSelectAction]
);
if (!config) return null;
const isShowingScriptDetails = !!scriptDetailsId; // used to set css visibility for this modal to hidden when the script details modal is open
const tableData = hostScriptResponse?.scripts;
return (
<Modal
title={"Run script"}
onExit={onClose}
onEnter={onClose}
className={`${baseClass}`}
isHidden={isShowingScriptDetails}
isLoading={runScriptRequested || isFetching || isLoading}
>
<>
<div className={`${baseClass}__modal-content`}>
{isLoading && <Spinner />}
{!isLoading && isError && <DataError />}
{!isLoading && !isError && tableData && tableData.length === 0 && (
<EmptyTable
header="No scripts are available for this host"
info="Expecting to see scripts? Try selecting “Refetch” to ask this host to report new vitals."
/>
)}
{!isLoading && !isError && tableData && tableData.length > 0 && (
<TableContainer
resultsTitle=""
emptyComponent={EmptyComponent}
showMarkAllPages={false}
isAllPagesSelected={false}
columnConfigs={scriptColumnConfigs}
data={tableData}
isLoading={runScriptRequested || isFetching}
onQueryChange={onQueryChange}
disableNextPage={!hostScriptResponse?.meta.has_next_results}
defaultPageIndex={page}
pageSize={10}
disableCount
disableTableHeader
/>
)}
</div>
<div className={`modal-cta-wrap`}>
<Button onClick={onClose} variant="brand">
Done
</Button>
</div>
</>
</Modal>
);
};
export default React.memo(RunScriptModal);