UNPKG

testeranto

Version:

the AI powered BDD test framework for typescript projects

238 lines (237 loc) 15.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ProcessManagerView = void 0; /* eslint-disable @typescript-eslint/no-unused-vars */ const react_1 = __importStar(require("react")); const react_bootstrap_1 = require("react-bootstrap"); const react_router_dom_1 = require("react-router-dom"); const ProcessManagerView = ({ processes, onRefresh, onBack, loading, onKillProcess, }) => { const navigate = (0, react_router_dom_1.useNavigate)(); const [selectedProcess, setSelectedProcess] = (0, react_1.useState)(null); const [ws, setWs] = (0, react_1.useState)(null); const [processLogs, setProcessLogs] = (0, react_1.useState)([]); const [autoScroll, setAutoScroll] = (0, react_1.useState)(true); const logsContainerRef = (0, react_1.useRef)(null); const [isNavbarCollapsed, setIsNavbarCollapsed] = (0, react_1.useState)(false); // Set up WebSocket connection (0, react_1.useEffect)(() => { const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:"; const wsUrl = `${wsProtocol}//${window.location.host}`; const websocket = new WebSocket(wsUrl); setWs(websocket); websocket.onmessage = (event) => { try { const data = JSON.parse(event.data); // Handle process data response if (data.type === "processData" && selectedProcess && data.processId === selectedProcess.processId) { setSelectedProcess((prev) => prev ? Object.assign(Object.assign({}, prev), { logs: data.logs || [] }) : null); setProcessLogs(data.logs || []); } // Handle new log messages else if ((data.type === "processStdout" || data.type === "processStderr") && selectedProcess && data.processId === selectedProcess.processId) { setProcessLogs((prev) => [...prev, data.data]); } } catch (error) { console.error("Error parsing WebSocket message:", error); } }; return () => { websocket.close(); }; }, [selectedProcess]); // Request process data when selected (0, react_1.useEffect)(() => { if (selectedProcess && ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: "getProcess", processId: selectedProcess.processId, })); } }, [selectedProcess, ws]); // Auto-scroll to bottom when new logs arrive and autoScroll is enabled (0, react_1.useEffect)(() => { if (autoScroll && logsContainerRef.current) { logsContainerRef.current.scrollTop = logsContainerRef.current.scrollHeight; } }, [processLogs, autoScroll]); // Handle scroll events to determine if we should auto-scroll const handleLogsScroll = () => { if (logsContainerRef.current) { const { scrollTop, scrollHeight, clientHeight } = logsContainerRef.current; const isAtBottom = scrollHeight - scrollTop <= clientHeight + 10; // 10px threshold setAutoScroll(isAtBottom); } }; const getStatusBadge = (process) => { switch (process.status) { case "running": return react_1.default.createElement(react_bootstrap_1.Badge, { bg: "success" }, "Running"); case "exited": return react_1.default.createElement(react_bootstrap_1.Badge, { bg: "secondary" }, "Exited (", process.exitCode, ")"); case "error": return react_1.default.createElement(react_bootstrap_1.Badge, { bg: "danger" }, "Error"); default: return react_1.default.createElement(react_bootstrap_1.Badge, { bg: "warning" }, "Unknown"); } }; const handleSelectProcess = (process) => { setSelectedProcess(process); setProcessLogs(process.logs || []); }; const handleInput = (data) => { if (ws && ws.readyState === WebSocket.OPEN && (selectedProcess === null || selectedProcess === void 0 ? void 0 : selectedProcess.status) === "running") { ws.send(JSON.stringify({ type: "stdin", processId: selectedProcess.processId, data: data, })); } }; return (react_1.default.createElement(react_bootstrap_1.Container, { fluid: true, className: "px-0 h-100" }, react_1.default.createElement(react_bootstrap_1.Row, { className: "g-0", style: { height: "calc(100vh - 56px)" } }, react_1.default.createElement(react_bootstrap_1.Col, { sm: 3, className: "border-end", style: { height: "100%", overflow: "auto", backgroundColor: "#f8f9fa", } }, react_1.default.createElement("div", { className: "p-1" }, processes.map((process) => (react_1.default.createElement("div", { key: process.processId, className: `p-2 mb-1 rounded ${(selectedProcess === null || selectedProcess === void 0 ? void 0 : selectedProcess.processId) === process.processId ? "bg-primary text-white" : "bg-white border"}`, style: { cursor: "pointer" }, onClick: () => handleSelectProcess(process), title: process.command }, react_1.default.createElement("div", { className: "d-flex justify-content-between align-items-start" }, react_1.default.createElement("div", { className: "flex-grow-1", style: { minWidth: 0 } }, react_1.default.createElement("div", { className: "fw-bold text-truncate small" }, process.command.split(" ")[0]), react_1.default.createElement("div", { className: `text-truncate ${(selectedProcess === null || selectedProcess === void 0 ? void 0 : selectedProcess.processId) === process.processId ? "text-white-50" : "text-muted"}`, style: { fontSize: "0.7rem" } }, "PID: ", process.pid || "N/A", " |", " ", new Date(process.timestamp).toLocaleTimeString())), react_1.default.createElement("div", { className: "ms-2" }, getStatusBadge(process))), process.error && (react_1.default.createElement("div", { className: `mt-1 ${(selectedProcess === null || selectedProcess === void 0 ? void 0 : selectedProcess.processId) === process.processId ? "text-warning" : "text-danger"}`, style: { fontSize: "0.7rem" } }, react_1.default.createElement("div", { className: "text-truncate" }, "Error: ", process.error)))))), processes.length === 0 && !loading && (react_1.default.createElement("div", { className: "p-2 text-center text-muted small" }, "No active processes")), loading && processes.length === 0 && (react_1.default.createElement("div", { className: "p-2 text-center small" }, react_1.default.createElement("div", { className: "spinner-border spinner-border-sm", role: "status" }, react_1.default.createElement("span", { className: "visually-hidden" }, "Loading...")))))), react_1.default.createElement(react_bootstrap_1.Col, { sm: 5, className: "border-end p-3", style: { height: "100%", overflow: "auto" } }, selectedProcess ? (react_1.default.createElement("div", null, react_1.default.createElement("div", { className: "mb-3" }, react_1.default.createElement("strong", null, "Command:"), react_1.default.createElement("code", { className: "bg-light p-2 rounded d-block mt-1", style: { fontSize: "0.8rem" } }, selectedProcess.command)), react_1.default.createElement("div", { className: "mb-2" }, react_1.default.createElement("strong", null, "Status:"), react_1.default.createElement("div", { className: "mt-1" }, getStatusBadge(selectedProcess))), react_1.default.createElement("div", { className: "mb-2" }, react_1.default.createElement("strong", null, "PID:"), react_1.default.createElement("div", { className: "text-muted" }, selectedProcess.pid || "N/A")), react_1.default.createElement("div", { className: "mb-2" }, react_1.default.createElement("strong", null, "Started:"), react_1.default.createElement("div", { className: "text-muted" }, new Date(selectedProcess.timestamp).toLocaleString())), selectedProcess.exitCode !== undefined && (react_1.default.createElement("div", { className: "mb-2" }, react_1.default.createElement("strong", null, "Exit Code:"), react_1.default.createElement("div", { className: "text-muted" }, selectedProcess.exitCode))), selectedProcess.error && (react_1.default.createElement("div", { className: "mt-3" }, react_1.default.createElement("strong", { className: "text-danger" }, "Error:"), react_1.default.createElement("div", { className: "text-danger small mt-1" }, selectedProcess.error))))) : (react_1.default.createElement("div", { className: "text-center text-muted mt-5" }, react_1.default.createElement("i", null, "Select a process to view details")))), react_1.default.createElement(react_bootstrap_1.Col, { sm: 4, className: "p-0", style: { height: "100%", overflow: "hidden" } }, selectedProcess ? (react_1.default.createElement("div", { className: "d-flex flex-column h-100" }, react_1.default.createElement("div", { ref: logsContainerRef, className: "bg-dark text-light flex-grow-1", style: { overflowY: "auto", fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace', fontSize: "14px", lineHeight: "1.4", // padding: '1rem' }, onScroll: handleLogsScroll }, processLogs.length > 0 ? (react_1.default.createElement("pre", { className: "mb-0", style: { whiteSpace: "pre-wrap", wordBreak: "break-word", backgroundColor: "transparent", border: "none", color: "inherit", } }, processLogs.join(""))) : (react_1.default.createElement("div", { className: "text-muted text-center py-4" }, react_1.default.createElement("i", null, "No output yet"))), !autoScroll && (react_1.default.createElement("div", { className: "position-sticky bottom-0 d-flex justify-content-center mb-2" }, react_1.default.createElement(react_bootstrap_1.Button, { variant: "primary", size: "sm", onClick: () => { setAutoScroll(true); if (logsContainerRef.current) { logsContainerRef.current.scrollTop = logsContainerRef.current.scrollHeight; } } }, "Scroll to Bottom")))), selectedProcess.status === "running" && (react_1.default.createElement("div", { className: "border-top bg-white p-3", style: { flexShrink: 0 } }, onKillProcess && (react_1.default.createElement("div", { className: "mb-3" }, react_1.default.createElement(react_bootstrap_1.Button, { variant: "danger", size: "sm", onClick: () => onKillProcess(selectedProcess.processId), className: "w-100" }, "\u23F9\uFE0F Stop Process"))), react_1.default.createElement("div", { className: "input-group" }, react_1.default.createElement("input", { type: "text", className: "form-control", placeholder: "Type input and press Enter...", onKeyPress: (e) => { if (e.key === "Enter") { const target = e.target; const inputValue = target.value; if (inputValue.trim()) { handleInput(inputValue + "\n"); target.value = ""; } } }, autoFocus: true }), react_1.default.createElement("button", { className: "btn btn-primary", type: "button", onClick: () => { const input = document.querySelector("input"); const inputValue = input.value; if (inputValue.trim()) { handleInput(inputValue + "\n"); input.value = ""; } } }, "Send")), react_1.default.createElement("small", { className: "text-muted" }, "\uD83D\uDCA1 Press Enter to send input to the process"))))) : (react_1.default.createElement("div", { className: "p-3 text-center text-muted mt-5" }, react_1.default.createElement("i", null, "Live logs will appear here when a process is selected"))))))); }; exports.ProcessManagerView = ProcessManagerView;