import React, { useContext, useState } from "react"; import { useQuery } from "react-query"; import classnames from "classnames"; import { NotificationContext } from "context/notification"; import { AppContext } from "context/app"; import RunScriptHelpText from "pages/hosts/components/ScriptDetailsModal/RunScriptHelpText"; import scriptAPI from "services/entities/scripts"; import Button from "components/buttons/Button"; import DataError from "components/DataError"; import Editor from "components/Editor"; import Modal from "components/Modal"; import ModalFooter from "components/ModalFooter"; import Spinner from "components/Spinner"; import { ScriptContent } from "interfaces/script"; import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; import { getErrorMessage } from "../ScriptUploadModal/helpers"; const baseClass = "edit-script-modal"; interface IWarningModal { onExit: () => void; onSave: () => void; scriptName: string; isSubmitting: boolean; } const WarningModal = ({ onExit, onSave, scriptName, isSubmitting, }: IWarningModal) => { return (

The changes you are making will cancel any pending script runs for{" "} {scriptName}.

If this script is currently running on a host, it will complete, but results won't appear in Fleet.

You cannot undo this action.

); }; interface IEditScriptModal { onExit: () => void; scriptId: number; scriptName: string; } const validate = (scriptContent: string) => { if (scriptContent.trim() === "") { return "Script cannot be empty"; } return null; }; const EditScriptModal = ({ scriptId, scriptName, onExit, }: IEditScriptModal) => { const { renderFlash } = useContext(NotificationContext); const { currentTeam, isGlobalAdmin, isAnyTeamAdmin, isGlobalMaintainer, isAnyTeamMaintainer, isTeamTechnician, isGlobalTechnician, } = useContext(AppContext); const isTechnician = !!isTeamTechnician || !!isGlobalTechnician; const canRunScripts = !!( isGlobalAdmin || isAnyTeamAdmin || isGlobalMaintainer || isAnyTeamMaintainer ); // Editable script content const [scriptFormData, setScriptFormData] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [formError, setFormError] = useState(null); const [showConfirmChanges, setShowConfirmChanges] = useState(false); const { data: curScriptContent, error: isSelectedScriptContentError, isLoading: isLoadingSelectedScriptContent, } = useQuery( [scriptId], () => scriptAPI.downloadScript(scriptId), { ...DEFAULT_USE_QUERY_OPTIONS, onSuccess: (curScriptContent_) => { setScriptFormData(curScriptContent_); }, } ); const onChange = (value: string) => { setScriptFormData(value); const err = validate(value); if (!err && !!formError) { setFormError(validate(value)); } }; const onBlur = () => { setFormError(validate(scriptFormData)); }; const onSave = async () => { const err = validate(scriptFormData); setFormError(err); if (err || isSubmitting) { return; } // if contents have changed and not already showing the warning modal if (curScriptContent !== scriptFormData && !showConfirmChanges) { setShowConfirmChanges(true); return; } // show warning modal and abort this call try { setIsSubmitting(true); await scriptAPI.updateScript(scriptId, scriptFormData, scriptName); renderFlash("success", "Successfully saved script."); onExit(); } catch (e) { renderFlash("error", getErrorMessage(e)); } finally { setIsSubmitting(false); setShowConfirmChanges(false); } }; const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); onSave(); }; const renderContent = () => { if (isLoadingSelectedScriptContent) { return ; } if (isSelectedScriptContentError) { return ; } // Set editing mode based on the file extension. const mode = scriptName.match(/\.sh$/) ? "sh" : "powershell"; return ( <>
{canRunScripts && ( } /> )} ); }; const classes = classnames(baseClass, { [`${baseClass}__hide-main`]: !!showConfirmChanges, }); return ( <> {renderContent()} {!!showConfirmChanges && ( setShowConfirmChanges(false)} onSave={onSave} scriptName={scriptName} isSubmitting={isSubmitting} /> )} ); }; export default EditScriptModal;