the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
at main 133 lines 4.6 kB view raw
1import { useNavigate } from "@tanstack/react-router"; 2import type { Sandbox } from "../../../types/sandbox"; 3import _ from "lodash"; 4import dayjs from "dayjs"; 5import { useState } from "react"; 6import { 7 useStartSandboxMutation, 8 useStopSandboxMutation, 9} from "../../../hooks/useSandbox"; 10import { useQueryClient } from "@tanstack/react-query"; 11import { useAtomValue } from "jotai"; 12import { profileAtom } from "../../../atoms/profile"; 13import TerminalModal from "./TerminalModal"; 14import ContextMenu from "../../../components/contextmenu"; 15 16export type ProjectProps = { 17 sandbox: Sandbox; 18}; 19 20function Project({ sandbox }: ProjectProps) { 21 const navigate = useNavigate(); 22 const queryClient = useQueryClient(); 23 const [modalOpen, setModalOpen] = useState(false); 24 const profile = useAtomValue(profileAtom); 25 const { mutateAsync: stopSandbox } = useStopSandboxMutation(); 26 const { mutateAsync: startSandbox } = useStartSandboxMutation(); 27 const [displayLoading, setDisplayLoading] = useState(false); 28 29 const onPlay = async (e: React.MouseEvent) => { 30 e.stopPropagation(); 31 setDisplayLoading(true); 32 await startSandbox(sandbox.id); 33 queryClient.invalidateQueries({ 34 queryKey: ["actorSandboxes", profile?.did], 35 }); 36 setDisplayLoading(false); 37 }; 38 39 const onStop = async (e: React.MouseEvent) => { 40 e.stopPropagation(); 41 setDisplayLoading(true); 42 await stopSandbox(sandbox.id); 43 queryClient.invalidateQueries({ 44 queryKey: ["actorSandboxes", profile?.did], 45 }); 46 setDisplayLoading(false); 47 }; 48 49 const onOpenTerminal = (e: React.MouseEvent) => { 50 e.stopPropagation(); 51 if (sandbox.status !== "RUNNING") return; 52 setModalOpen(true); 53 }; 54 55 const onOpenProject = () => { 56 navigate({ 57 to: `/${sandbox.uri.split("at://")[1].replace("io.pocketenv.", "")}`, 58 }); 59 }; 60 61 return ( 62 <tr className="cursor-pointer" onClick={onOpenProject}> 63 <td>{sandbox.name}</td> 64 <td>{sandbox.baseSandbox}</td> 65 <td> 66 <span 67 className={`badge badge-soft ${sandbox?.status === "RUNNING" ? "badge-success" : ""} rounded-full ${sandbox.status === "RUNNING" ? "bg-green-400/10" : "bg-white/15 rounded"}`} 68 > 69 {_.upperFirst(_.camelCase(sandbox.status))} 70 </span> 71 </td> 72 <td> 73 <span className="badge badge-soft badge-primary bg-blue-400/10 rounded-full"> 74 {sandbox?.vcpus} CPU 75 </span> 76 <span className="badge badge-soft badge-primary bg-blue-400/10 rounded-full ml-2"> 77 {sandbox?.memory} GiB RAM 78 </span> 79 </td> 80 <td>{dayjs(sandbox.createdAt).format("M/D/YYYY, h:mm:ss A")}</td> 81 <td className="align-middle"> 82 <div 83 className="flex items-center justify-center p-1" 84 onClick={(e) => e.stopPropagation()} 85 > 86 {!displayLoading && sandbox.status === "RUNNING" && ( 87 <button 88 className="btn btn-circle btn-text btn-sm bg-transparent outline-0" 89 onClick={onStop} 90 > 91 <span className="icon-[tabler--player-stop] size-5 hover:text-white"></span> 92 </button> 93 )} 94 {!displayLoading && sandbox.status !== "RUNNING" && ( 95 <button 96 className="btn btn-circle btn-text btn-sm bg-transparent outline-0" 97 onClick={onPlay} 98 > 99 <span className="icon-[tabler--player-play] size-5 hover:text-white"></span> 100 </button> 101 )} 102 {displayLoading && ( 103 <span className="loading loading-spinner loading-sm btn-text mr-[10px]"></span> 104 )} 105 <button 106 className={`btn btn-circle btn-text btn-sm bg-transparent outline-0 ${sandbox.status !== "RUNNING" ? "opacity-50" : ""}`} 107 onClick={onOpenTerminal} 108 > 109 <span 110 className={`icon-[mingcute--terminal-fill] size-5 ${sandbox.status !== "RUNNING" ? "" : "hover:text-white"}`} 111 ></span> 112 </button> 113 <ContextMenu sandboxId={sandbox.id} uri={sandbox.uri} /> 114 </div> 115 116 <TerminalModal 117 title={sandbox.name} 118 isOpen={modalOpen} 119 onClose={() => { 120 setModalOpen(false); 121 }} 122 sandboxId={sandbox.id} 123 isCloudflare={sandbox.provider === "cloudflare"} 124 isTty={["sprites", "vercel"].includes(sandbox.provider)} 125 pty={sandbox.provider === "vercel"} 126 worker={sandbox.baseSandbox} 127 /> 128 </td> 129 </tr> 130 ); 131} 132 133export default Project;