-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(frontend): Code Engine preview, support preview project right now (
#121) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
- Loading branch information
1 parent
6a5c749
commit 9a65a54
Showing
11 changed files
with
275 additions
and
9 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
FROM node:18 | ||
|
||
WORKDIR /app | ||
|
||
COPY package*.json ./ | ||
|
||
RUN npm install --frozen-lockfile | ||
|
||
COPY . . | ||
|
||
RUN chmod +x /app/node_modules/.bin/vite | ||
|
||
EXPOSE 5173 | ||
|
||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
version: '3' | ||
|
||
services: | ||
reverse-proxy: | ||
# The official v3 Traefik docker image | ||
image: traefik:v3.3 | ||
# Enables the web UI and tells Traefik to listen to docker | ||
command: | ||
- '--api.insecure=true' | ||
- '--providers.docker' | ||
- '--entrypoints.web.address=:80' | ||
ports: | ||
# The HTTP port | ||
- '80:80' | ||
# The Web UI (enabled by --api.insecure=true) | ||
- '9001:8080' | ||
volumes: | ||
# So that Traefik can listen to the Docker events | ||
- /var/run/docker.sock:/var/run/docker.sock | ||
networks: | ||
- traefik_network | ||
|
||
networks: | ||
traefik_network: | ||
external: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { NextResponse } from 'next/server'; | ||
import { exec } from 'child_process'; | ||
import * as path from 'path'; | ||
import * as crypto from 'crypto'; | ||
import * as net from 'net'; | ||
import { getProjectPath } from 'codefox-common'; | ||
|
||
const runningContainers = new Map< | ||
string, | ||
{ domain: string; containerId: string } | ||
>(); | ||
const allocatedPorts = new Set<number>(); | ||
|
||
function findAvailablePort( | ||
minPort: number = 38000, | ||
maxPort: number = 42000 | ||
): Promise<number> { | ||
return new Promise((resolve, reject) => { | ||
function checkPort(port: number): Promise<boolean> { | ||
return new Promise((resolve) => { | ||
if (allocatedPorts.has(port)) { | ||
return resolve(false); | ||
} | ||
const server = net.createServer(); | ||
server.listen(port, '127.0.0.1', () => { | ||
server.close(() => resolve(true)); | ||
}); | ||
server.on('error', () => resolve(false)); | ||
}); | ||
} | ||
|
||
async function scanPorts() { | ||
for (let port = minPort; port <= maxPort; port++) { | ||
if (await checkPort(port)) { | ||
allocatedPorts.add(port); | ||
return resolve(port); | ||
} | ||
} | ||
reject(new Error('No available ports found.')); | ||
} | ||
|
||
scanPorts(); | ||
}); | ||
} | ||
|
||
async function buildAndRunDocker( | ||
projectPath: string | ||
): Promise<{ domain: string; containerId: string }> { | ||
console.log(runningContainers); | ||
if (runningContainers.has(projectPath)) { | ||
console.log(`Container for project ${projectPath} is already running.`); | ||
return runningContainers.get(projectPath)!; | ||
} | ||
const traefikDomain = process.env.TRAEFIK_DOMAIN || 'docker.localhost'; | ||
const directory = path.join(getProjectPath(projectPath), 'frontend'); | ||
const imageName = projectPath.toLowerCase(); | ||
const containerId = crypto.randomUUID(); | ||
const containerName = `container-${containerId}`; | ||
|
||
const subdomain = projectPath.replace(/[^\w-]/g, '').toLowerCase(); | ||
const domain = `${subdomain}.${traefikDomain}`; | ||
const exposedPort = await findAvailablePort(); | ||
return new Promise((resolve, reject) => { | ||
exec( | ||
`docker build -t ${imageName} ${directory}`, | ||
(buildErr, buildStdout, buildStderr) => { | ||
if (buildErr) { | ||
console.error(`Error during Docker build: ${buildStderr}`); | ||
return reject(buildErr); | ||
} | ||
|
||
console.log(`Docker build output:\n${buildStdout}`); | ||
console.log(`Running Docker container: ${containerName}`); | ||
|
||
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=traefik_network -p ${exposedPort}:5173 ${imageName}`; | ||
console.log(runCommand); | ||
|
||
exec(runCommand, (runErr, runStdout, runStderr) => { | ||
if (runErr) { | ||
console.error(`Error during Docker run: ${runStderr}`); | ||
return reject(runErr); | ||
} | ||
|
||
const containerActualId = runStdout.trim(); | ||
runningContainers.set(projectPath, { | ||
domain, | ||
containerId: containerActualId, | ||
}); | ||
|
||
console.log( | ||
`Container ${containerName} is now running at http://${domain}` | ||
); | ||
|
||
resolve({ domain, containerId: containerActualId }); | ||
}); | ||
} | ||
); | ||
}); | ||
} | ||
export async function GET(req: Request) { | ||
const { searchParams } = new URL(req.url); | ||
const projectPath = searchParams.get('projectPath'); | ||
|
||
if (!projectPath) { | ||
return NextResponse.json( | ||
{ error: 'Missing required parameters' }, | ||
{ status: 400 } | ||
); | ||
} | ||
|
||
try { | ||
const { domain, containerId } = await buildAndRunDocker(projectPath); | ||
return NextResponse.json({ | ||
message: 'Docker container started', | ||
domain, | ||
containerId, | ||
}); | ||
} catch (error) { | ||
return NextResponse.json( | ||
{ error: error.message || 'Failed to start Docker container' }, | ||
{ status: 500 } | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { useContext, useEffect, useRef, useState } from 'react'; | ||
import { ProjectContext } from './project-context'; | ||
|
||
export default function WebPreview() { | ||
const { curProject } = useContext(ProjectContext); | ||
const [url, setUrl] = useState(''); | ||
const iframeRef = useRef(null); | ||
|
||
useEffect(() => { | ||
const getWebUrl = async () => { | ||
const projectPath = curProject.projectPath; | ||
try { | ||
const response = await fetch( | ||
`/api/runProject?projectPath=${encodeURIComponent(projectPath)}`, | ||
{ | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
} | ||
); | ||
const json = await response.json(); | ||
console.log(json); | ||
await new Promise((resolve) => setTimeout(resolve, 10000)); | ||
setUrl(`http://${json.domain}/`); | ||
} catch (error) { | ||
console.error('fetching url error:', error); | ||
} | ||
}; | ||
|
||
getWebUrl(); | ||
}, [curProject]); | ||
|
||
useEffect(() => { | ||
if (iframeRef.current) { | ||
iframeRef.current.src = url; | ||
} | ||
}, [url]); | ||
|
||
const refreshIframe = () => { | ||
if (iframeRef.current) { | ||
iframeRef.current.src = url; | ||
} | ||
}; | ||
|
||
const enterFullScreen = () => { | ||
if (iframeRef.current) { | ||
iframeRef.current.requestFullscreen(); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="flex flex-col items-center gap-4 p-4 w-full"> | ||
<div className="flex gap-2 w-full max-w-2xl"> | ||
<input | ||
type="text" | ||
value={url} | ||
onChange={(e) => setUrl(e.target.value)} | ||
className="flex-1 p-2 border rounded" | ||
/> | ||
<button | ||
onClick={refreshIframe} | ||
className="p-2 bg-blue-500 text-white rounded" | ||
> | ||
refresh | ||
</button> | ||
<button | ||
onClick={enterFullScreen} | ||
className="p-2 bg-green-500 text-white rounded" | ||
> | ||
fullscreen | ||
</button> | ||
</div> | ||
|
||
<div className="w-full h-full max-w-5xl border rounded-lg overflow-hidden"> | ||
<iframe | ||
ref={iframeRef} | ||
src={url} | ||
className="w-full h-full border-none" | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters