From 70fdfa7fa1a0122983e6d5c70c69dba2d5a8a4f5 Mon Sep 17 00:00:00 2001 From: Narwhal Date: Mon, 3 Mar 2025 19:44:13 -0600 Subject: [PATCH 1/3] fix: fixing bug of cannot running docker container after container is shut down --- frontend/src/app/api/file/route.ts | 2 + frontend/src/app/api/runProject/route.ts | 95 ++++++++++++++++--- frontend/src/components/chat/chat-layout.tsx | 15 ++- .../chat/code-engine/code-engine.tsx | 12 ++- .../components/chat/code-engine/web-view.tsx | 84 +++++++++++++++- frontend/src/components/root/root-layout.tsx | 28 +++--- frontend/src/graphql/schema.gql | 6 +- 7 files changed, 193 insertions(+), 49 deletions(-) diff --git a/frontend/src/app/api/file/route.ts b/frontend/src/app/api/file/route.ts index 7205aaa2..9892ac00 100644 --- a/frontend/src/app/api/file/route.ts +++ b/frontend/src/app/api/file/route.ts @@ -59,6 +59,8 @@ function getFileType(filePath: string): string { const extension = filePath.split('.').pop()?.toLowerCase() || ''; const typeMap: { [key: string]: string } = { + //TODO: Add more file types + tsx: 'typescriptreact', txt: 'text', md: 'markdown', json: 'json', diff --git a/frontend/src/app/api/runProject/route.ts b/frontend/src/app/api/runProject/route.ts index 45b5d909..cb7269b1 100644 --- a/frontend/src/app/api/runProject/route.ts +++ b/frontend/src/app/api/runProject/route.ts @@ -128,6 +128,9 @@ async function buildAndRunDocker( return new Promise((resolve, reject) => { // 2. Build the Docker image + console.log( + `Starting Docker build for image: ${imageName} in directory: ${directory}` + ); exec( `docker build -t ${imageName} ${directory}`, (buildErr, buildStdout, buildStderr) => { @@ -141,19 +144,61 @@ async function buildAndRunDocker( // 3. Run the Docker container const runCommand = `docker run -d --name ${containerName} -l "traefik.enable=true" \ - -l "traefik.http.routers.${subdomain}.rule=Host(\\"${domain}\\")" \ - -l "traefik.http.services.${subdomain}.loadbalancer.server.port=5173" \ - --network=codefox_traefik_network -p ${exposedPort}:5173 \ - -v "${directory}:/app" \ - ${imageName}`; + -l "traefik.http.routers.${subdomain}.rule=Host(\\"${domain}\\")" \ + -l "traefik.http.services.${subdomain}.loadbalancer.server.port=5173" \ + --network=codefox_traefik_network -p ${exposedPort}:5173 \ + -v "${directory}:/app" \ + ${imageName}`; - console.log(runCommand); + console.log(`Executing run command: ${runCommand}`); exec(runCommand, (runErr, runStdout, runStderr) => { if (runErr) { // If the container name already exists + console.error(`Error during Docker run: ${runStderr}`); if (runStderr.includes('Conflict. The container name')) { - resolve({ domain, containerId: containerName }); + console.log( + `Container name conflict detected. Removing existing container ${containerName}.` + ); + // Remove the existing container + exec( + `docker rm -f ${containerName}`, + (removeErr, removeStdout, removeStderr) => { + if (removeErr) { + console.error( + `Error removing existing container: ${removeStderr}` + ); + return reject(removeErr); + } + console.log( + `Existing container ${containerName} removed. Retrying to run the container.` + ); + + // Retry running the Docker container + exec( + runCommand, + (retryRunErr, retryRunStdout, retryRunStderr) => { + if (retryRunErr) { + console.error( + `Error during Docker run: ${retryRunStderr}` + ); + return reject(retryRunErr); + } + + const containerActualId = retryRunStdout.trim(); + runningContainers.set(projectPath, { + domain, + containerId: containerActualId, + }); + + console.log( + `Container ${containerName} is now running at http://${domain}` + ); + resolve({ domain, containerId: containerActualId }); + } + ); + } + ); return; } console.error(`Error during Docker run: ${runStderr}`); @@ -169,7 +214,6 @@ async function buildAndRunDocker( console.log( `Container ${containerName} is now running at http://${domain}` ); - resolve({ domain, containerId: containerActualId }); }); } @@ -204,11 +248,38 @@ export async function GET(req: Request) { // Check if a container is already running const existingContainer = runningContainers.get(projectPath); if (existingContainer) { - return NextResponse.json({ - message: 'Docker container already running', - domain: existingContainer.domain, - containerId: existingContainer.containerId, + // Check if the container is running + const containerStatus = await new Promise((resolve) => { + exec( + `docker inspect -f "{{.State.Running}}" ${existingContainer.containerId}`, + (err, stdout) => { + if (err) { + resolve('not found'); + } else { + resolve(stdout.trim()); + } + } + ); }); + + if (containerStatus === 'true') { + return NextResponse.json({ + message: 'Docker container already running', + domain: existingContainer.domain, + containerId: existingContainer.containerId, + }); + } else { + // Remove the existing container if it's not running + exec(`docker rm -f ${existingContainer.containerId}`, (removeErr) => { + if (removeErr) { + console.error(`Error removing existing container: ${removeErr}`); + } else { + console.log( + `Removed existing container: ${existingContainer.containerId}` + ); + } + }); + } } // Prevent duplicate builds diff --git a/frontend/src/components/chat/chat-layout.tsx b/frontend/src/components/chat/chat-layout.tsx index 55663614..0f13d951 100644 --- a/frontend/src/components/chat/chat-layout.tsx +++ b/frontend/src/components/chat/chat-layout.tsx @@ -5,7 +5,6 @@ import ProjectModal from '@/components/chat/project-modal'; import { useQuery } from '@apollo/client'; import { GET_USER_PROJECTS } from '@/graphql/request'; import { useAuthContext } from '@/providers/AuthProvider'; -import { ProjectProvider } from './code-engine/project-context'; export default function ChatLayout({ children, @@ -30,14 +29,12 @@ export default function ChatLayout({ return (
- - setIsModalOpen(false)} - refetchProjects={refetch} - /> -
{children}
-
+ setIsModalOpen(false)} + refetchProjects={refetch} + /> +
{children}
); } diff --git a/frontend/src/components/chat/code-engine/code-engine.tsx b/frontend/src/components/chat/code-engine/code-engine.tsx index 0e0ab493..64f732e7 100644 --- a/frontend/src/components/chat/code-engine/code-engine.tsx +++ b/frontend/src/components/chat/code-engine/code-engine.tsx @@ -82,12 +82,16 @@ export function CodeEngine({ // Effect: Fetch file structure when projectId changes useEffect(() => { async function fetchFiles() { - if (!curProject?.projectPath) return; + if (!curProject?.projectPath) { + console.log('no project path found'); + return; + } try { const response = await fetch( `/api/project?path=${curProject.projectPath}` ); + console.log('loading file structure'); const data = await response.json(); setFileStructureData(data.res || {}); } catch (error) { @@ -270,12 +274,12 @@ export function CodeEngine({ // Render the CodeEngine layout return ( -
+
{/* Header Bar */} {/* Main Content Area with Loading */} -
+
{!isProjectReady && ( (['/']); const [currentIndex, setCurrentIndex] = useState(0); + const [scale, setScale] = useState(0.7); const iframeRef = useRef(null); const containerRef = useRef<{ projectPath: string; domain: string } | null>( null @@ -49,13 +53,34 @@ export default function WebPreview() { ); const json = await response.json(); - await new Promise((resolve) => setTimeout(resolve, 200)); + await new Promise((resolve) => setTimeout(resolve, 100)); containerRef.current = { projectPath, domain: json.domain, }; - setBaseUrl(`http://${json.domain}`); + + const checkUrlStatus = async (url: string) => { + let status = 0; + while (status !== 200) { + try { + const res = await fetch(url, { method: 'HEAD' }); + status = res.status; + if (status !== 200) { + console.log(`URL status: ${status}. Retrying...`); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } catch (err) { + console.error('Error checking URL status:', err); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } + }; + + const baseUrl = `http://${json.domain}`; + await checkUrlStatus(baseUrl); + + setBaseUrl(baseUrl); setDisplayPath('/'); } catch (error) { console.error('fetching url error:', error); @@ -109,6 +134,21 @@ export default function WebPreview() { setDisplayPath(history[currentIndex + 1]); } }; + const reloadIframe = () => { + const iframe = document.getElementById('myIframe') as HTMLIFrameElement; + if (iframe) { + iframe.src = iframe.src; + setScale(0.7); + } + }; + + const zoomIn = () => { + setScale((prevScale) => Math.min(prevScale + 0.1, 2)); // 最大缩放比例为 2 + }; + + const zoomOut = () => { + setScale((prevScale) => Math.max(prevScale - 0.1, 0.5)); // 最小缩放比例为 0.5 + }; return (
@@ -119,7 +159,7 @@ export default function WebPreview() { +
{/* URL Input */} @@ -150,6 +198,24 @@ export default function WebPreview() { {/* Actions */}
+ +