// frontend/components/AdminTabs/SandboxTab.jsx import React, { useCallback, useEffect, useState } from "react"; import { apiUrl } from "../../utils/api.js"; import MatrixLabInstallModal from "./MatrixLabInstallModal.jsx"; /** * Sandbox tab — enterprise default view. * * Surfaces a single MatrixLab addon card plus a clean Local-sandbox * card. Implementation knobs (Runner URL, token, image, network, * timeout, pass-through, lifecycle env flag) live behind the install * modal's Advanced disclosure — the primary view never shows them. * * Status pill is driven off /api/matrixlab/status so we never end up * with the "Unreachable + Running" contradiction the legacy panel * produced. */ function StatusPill({ status }) { const map = { not_installed: { label: "Not installed", bg: "#374151", fg: "#d1d5db" }, installing: { label: "Installing", bg: "#0d3320", fg: "#86efac" }, starting: { label: "Starting", bg: "#0d3320", fg: "#86efac" }, stopping: { label: "Stopping", bg: "#3d2d11", fg: "#fde68a" }, checking: { label: "Checking", bg: "#0d3320", fg: "#86efac" }, ready: { label: "Ready", bg: "#0d3320", fg: "#86efac" }, needs_attention: { label: "Needs attention", bg: "#3d2d11", fg: "#fde68a" }, failed: { label: "Failed", bg: "#3d1111", fg: "#fca5a5" }, }; const pill = map[status] || map.not_installed; return ( {pill.label} ); } export default function SandboxTab({ showToast }) { const [matrixlab, setMatrixlab] = useState(null); // /api/matrixlab/status payload const [sandbox, setSandbox] = useState(null); // /api/sandbox/status payload const [loading, setLoading] = useState(true); const [modalOpen, setModalOpen] = useState(false); const load = useCallback(async () => { try { const [ml, sb] = await Promise.all([ fetch(apiUrl("/api/matrixlab/status")).then((r) => r.json()).catch(() => null), fetch(apiUrl("/api/sandbox/status")).then((r) => r.json()).catch(() => null), ]); setMatrixlab(ml); setSandbox(sb); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const useLocal = useCallback(async () => { try { const r = await fetch(apiUrl("/api/sandbox/config"), { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ backend: "subprocess" }), }); const data = await r.json(); if (r.ok) { setSandbox(data); showToast?.("Switched to Local sandbox", "Code runs in a host subprocess with a workspace jail."); } } catch (err) { // surfaced through the next load() } }, [showToast]); if (loading) { return (

Sandbox

Loading sandbox status…
); } const activeBackend = (sandbox?.backend || "subprocess").toLowerCase(); const mlStatus = matrixlab?.status || "not_installed"; return (

Sandbox

Pick where GitPilot executes code: the local subprocess sandbox or the MatrixLab Runner. The choice applies to the Run button on chat code blocks and the agent's EXECUTE action.

{/* MatrixLab addon card */}

MatrixLab Addon

{activeBackend === "matrixlab" && ( ACTIVE )}

Run code safely in isolated, temporary containers. Recommended for enterprise deployments and untrusted code.

{mlStatus !== "not_installed" && matrixlab?.message && (

{matrixlab.message}

)}
{/* Local sandbox card */}

Local sandbox

{activeBackend === "subprocess" && ( ACTIVE )}

Host subprocess with a workspace jail. No Docker required — best for trying simple snippets quickly.

{modalOpen && ( { setModalOpen(false); load(); }} onActivated={(data) => { showToast?.("MatrixLab ready", "Code now runs in MatrixLab sandboxes."); // Refresh both panels so the ACTIVE badge moves to the addon card. load(); }} /> )}
); } const cardStyle = { background: "#1a1b26", borderRadius: 8, padding: 16, border: "1px solid #2a2b36", marginBottom: 12, };