@@ -14,12 +14,10 @@ const FilePickerTree: React.FC<{
|
||||
onRun?: (path: string) => void;
|
||||
}> = ({ node, level, onRun }) => {
|
||||
const expandedFolders = useFilePickerStore((s) => s.expandedFolders);
|
||||
const runningScripts = useFilePickerStore((s) => s.runningScripts);
|
||||
const toggleFolder = useFilePickerStore((s) => s.toggleFolder);
|
||||
|
||||
const nodePath = node.path || node.name;
|
||||
const isExpanded = expandedFolders.has(nodePath);
|
||||
const isRunning = node.type === "file" && runningScripts.has(nodePath);
|
||||
|
||||
if (node.type === "file") {
|
||||
return (
|
||||
@@ -27,7 +25,6 @@ const FilePickerTree: React.FC<{
|
||||
name={node.name}
|
||||
type="file"
|
||||
path={nodePath}
|
||||
isSelected={isRunning}
|
||||
level={level}
|
||||
onRun={onRun}
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,6 @@ interface FilePickerItemProps {
|
||||
name: string;
|
||||
type: "file" | "folder";
|
||||
path: string;
|
||||
isSelected?: boolean;
|
||||
isExpanded?: boolean;
|
||||
children?: React.ReactNode;
|
||||
level: number;
|
||||
@@ -24,7 +23,6 @@ export const FilePickerItem: React.FC<FilePickerItemProps> = ({
|
||||
name,
|
||||
type,
|
||||
path,
|
||||
isSelected,
|
||||
isExpanded,
|
||||
children,
|
||||
level,
|
||||
@@ -131,12 +129,10 @@ export const FilePickerItem: React.FC<FilePickerItemProps> = ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "4px",
|
||||
backgroundColor: isSelected ? "#238636" : "transparent",
|
||||
border: isSelected
|
||||
? "1px solid #2ea043"
|
||||
: "1px solid transparent",
|
||||
backgroundColor: "transparent",
|
||||
border: "1px solid transparent",
|
||||
borderRadius: "3px",
|
||||
color: isSelected ? "#ffffff" : "var(--text-secondary)",
|
||||
color: "var(--text-secondary)",
|
||||
cursor: "pointer",
|
||||
flexShrink: 0,
|
||||
transition: "all 0.15s",
|
||||
@@ -151,15 +147,9 @@ export const FilePickerItem: React.FC<FilePickerItemProps> = ({
|
||||
e.currentTarget.style.borderColor = "#2ea043";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = isSelected
|
||||
? "#238636"
|
||||
: "transparent";
|
||||
e.currentTarget.style.color = isSelected
|
||||
? "#ffffff"
|
||||
: "var(--text-secondary)";
|
||||
e.currentTarget.style.borderColor = isSelected
|
||||
? "#2ea043"
|
||||
: "transparent";
|
||||
e.currentTarget.style.backgroundColor = "transparent";
|
||||
e.currentTarget.style.color = "var(--text-secondary)";
|
||||
e.currentTarget.style.borderColor = "transparent";
|
||||
}}
|
||||
title="Run script"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { MdClose } from "react-icons/md";
|
||||
import { scriptsApi } from "../api/scripts.api";
|
||||
import { useTerminalStore } from "@/modules/terminal/store/useTerminalStore";
|
||||
import { useAgentStore } from "@/app/providers/layout/store/agent.store";
|
||||
|
||||
interface RunScriptModalProps {
|
||||
scriptPath: string;
|
||||
scriptId: number;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const RunScriptModal: React.FC<RunScriptModalProps> = ({
|
||||
scriptPath,
|
||||
scriptId,
|
||||
onClose,
|
||||
}) => {
|
||||
const [selectedAgentIdx, setSelectedAgentIdx] = useState(0);
|
||||
const [stdinValue, setStdinValue] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const inputRef = useRef<HTMLSelectElement>(null);
|
||||
|
||||
const agents = useAgentStore((s) => s.agents);
|
||||
const addJob = useTerminalStore((s) => s.addJob);
|
||||
const openTerminal = useTerminalStore((s) => s.openTerminal);
|
||||
|
||||
const selectedAgent = agents[selectedAgentIdx];
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleRun = async () => {
|
||||
if (!selectedAgent) {
|
||||
setError("No agents available");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// 1. Запускаем скрипт
|
||||
const runResult = await scriptsApi.runScript(scriptId, {
|
||||
stdin: stdinValue,
|
||||
token: selectedAgent.token,
|
||||
});
|
||||
|
||||
// 2. Добавляем джоб в терминал
|
||||
addJob({
|
||||
id: runResult.id,
|
||||
scriptPath,
|
||||
command: runResult.command,
|
||||
});
|
||||
|
||||
// 3. Открываем терминал
|
||||
openTerminal();
|
||||
|
||||
// 4. Ждём завершения по id
|
||||
const jobResult = await scriptsApi.waitJob(runResult.id);
|
||||
|
||||
// 5. Обновляем джоб
|
||||
addJob({
|
||||
id: jobResult.id,
|
||||
scriptPath,
|
||||
command: jobResult.command,
|
||||
stdin: jobResult.stdin,
|
||||
});
|
||||
|
||||
// Обновляем с финальным статусом
|
||||
const terminalStore = useTerminalStore.getState();
|
||||
terminalStore.updateJob(jobResult.id, {
|
||||
status: jobResult.status,
|
||||
stdout: jobResult.stdout,
|
||||
stderr: jobResult.stderr,
|
||||
stdin: jobResult.stdin,
|
||||
isRunning: false,
|
||||
});
|
||||
|
||||
onClose();
|
||||
} catch (e: any) {
|
||||
console.error("Failed to run script:", e);
|
||||
setError(e?.response?.data?.detail || "Failed to run script");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 2000,
|
||||
}}
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "var(--card-bg)",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: "8px",
|
||||
width: "420px",
|
||||
maxWidth: "90vw",
|
||||
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "16px 20px",
|
||||
borderBottom: "1px solid var(--border)",
|
||||
}}
|
||||
>
|
||||
<h3
|
||||
style={{
|
||||
margin: 0,
|
||||
color: "var(--text-primary)",
|
||||
fontSize: "14px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Run Script
|
||||
</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
color: "var(--text-secondary)",
|
||||
cursor: "pointer",
|
||||
padding: "4px",
|
||||
borderRadius: "4px",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<MdClose size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ padding: "20px" }}>
|
||||
{/* Script path */}
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
color: "var(--text-secondary)",
|
||||
fontSize: "12px",
|
||||
marginBottom: "6px",
|
||||
}}
|
||||
>
|
||||
Script
|
||||
</label>
|
||||
<div
|
||||
style={{
|
||||
padding: "8px 12px",
|
||||
backgroundColor: "var(--bg-secondary)",
|
||||
borderRadius: "4px",
|
||||
color: "var(--text-primary)",
|
||||
fontSize: "13px",
|
||||
fontFamily: "monospace",
|
||||
border: "1px solid var(--border)",
|
||||
}}
|
||||
>
|
||||
{scriptPath}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Agent selector */}
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
color: "var(--text-secondary)",
|
||||
fontSize: "12px",
|
||||
marginBottom: "6px",
|
||||
}}
|
||||
>
|
||||
Agent <span style={{ color: "#f44747" }}>*</span>
|
||||
</label>
|
||||
<select
|
||||
ref={inputRef}
|
||||
value={selectedAgentIdx}
|
||||
onChange={(e) => setSelectedAgentIdx(Number(e.target.value))}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px 12px",
|
||||
backgroundColor: "var(--input-bg)",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: "4px",
|
||||
color: "var(--text-primary)",
|
||||
fontSize: "13px",
|
||||
outline: "none",
|
||||
}}
|
||||
>
|
||||
{agents.length === 0 && (
|
||||
<option value="">No agents available</option>
|
||||
)}
|
||||
{agents.map((agent, idx) => (
|
||||
<option key={agent.label} value={idx}>
|
||||
{agent.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Stdin (optional) */}
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
color: "var(--text-secondary)",
|
||||
fontSize: "12px",
|
||||
marginBottom: "6px",
|
||||
}}
|
||||
>
|
||||
Stdin <span style={{ color: "#858585" }}>(optional)</span>
|
||||
</label>
|
||||
<textarea
|
||||
value={stdinValue}
|
||||
onChange={(e) => setStdinValue(e.target.value)}
|
||||
placeholder="Enter input data..."
|
||||
rows={4}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px 12px",
|
||||
backgroundColor: "var(--input-bg)",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: "4px",
|
||||
color: "var(--text-primary)",
|
||||
fontSize: "13px",
|
||||
fontFamily: "monospace",
|
||||
resize: "vertical",
|
||||
outline: "none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
padding: "8px 12px",
|
||||
backgroundColor: "rgba(244, 71, 71, 0.1)",
|
||||
border: "1px solid #f44747",
|
||||
borderRadius: "4px",
|
||||
color: "#f44747",
|
||||
fontSize: "12px",
|
||||
marginBottom: "16px",
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Run button */}
|
||||
<button
|
||||
onClick={handleRun}
|
||||
disabled={loading || !selectedAgent}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "10px",
|
||||
backgroundColor: loading || !selectedAgent ? "#555" : "#0e639c",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
color: "#ffffff",
|
||||
fontSize: "13px",
|
||||
fontWeight: 500,
|
||||
cursor: loading || !selectedAgent ? "not-allowed" : "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: "8px",
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
animation: "spin 1s linear infinite",
|
||||
}}
|
||||
>
|
||||
⏳
|
||||
</span>
|
||||
Running...
|
||||
</>
|
||||
) : (
|
||||
<>▶ Run</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user