15 Commits

Author SHA1 Message Date
4a737744df Update README.md to include WebSocket support and enhance installation instructions 2025-03-30 21:34:56 +05:30
1cbb4f3c35 Implement execution status polling and enhance input handling in EditorArea and Panel components 2025-03-30 21:34:45 +05:30
ishikabhoyar
918b323cda Add warning indicator and language mode to StatusBar component 2025-03-30 11:15:26 +05:30
ishikabhoyar
208655c9bc Refactor StatusBar component and adjust CSS for improved layout and styling 2025-03-30 02:06:27 +05:30
ishikabhoyar
48a14f674d Refactor Panel and StatusBar components by adding comments for clarity and improving accessibility with aria-labels 2025-03-30 01:59:12 +05:30
ishikabhoyar
697c4b8460 Enhance Panel component with additional tabs and keyboard input handling for terminal 2025-03-30 01:51:21 +05:30
ishikabhoyar
99e12a7355 Add custom icon for README.md file in Sidebar component 2025-03-30 01:26:27 +05:30
ishikabhoyar
3cc73e786a Refactor Sidebar component by removing unused folder icon and cleaning up code 2025-03-30 01:18:15 +05:30
ishikabhoyar
80a713cc56 Remove Navbar component and associated styles 2025-03-30 01:11:21 +05:30
ishikabhoyar
648391e6ba Update README and EditorArea components with project description and functionalities 2025-03-30 00:45:05 +05:30
3a75000e12 Implement WebSocket support for terminal connections and enhance terminal UI 2025-03-30 00:13:32 +05:30
305650925e Add file download functionality to EditorArea component 2025-03-27 12:43:18 +05:30
d3e0ef95d4 Merge branch 'main' of https://github.com/Arnab-Afk/monaco 2025-03-27 10:54:59 +05:30
809505ec84 input changes updated 2025-03-27 10:54:56 +05:30
73fa23e434 Create main.yml 2025-03-26 21:04:38 +05:30
17 changed files with 1338 additions and 537 deletions

47
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: Release Monaco
on:
push:
tags:
- 'v*' # Triggers on version tags like v1.0.0, v1.1.0-beta, etc.
jobs:
build-and-release:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.22.3] # Adjust to your Go version
os: [linux, darwin, windows]
arch: [amd64, arm64]
exclude:
- os: darwin
arch: arm64
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Build binary
env:
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
run: |
cd backend
go mod tidy
GOOS=$GOOS GOARCH=$GOARCH go build -o monaco-${{ matrix.os }}-${{ matrix.arch }} .
- name: Create Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
backend/monaco-${{ matrix.os }}-${{ matrix.arch }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1 +1 @@
VITE_API_URL="https://monacoapi.thearnab.tech" VITE_API_URL="http://localhost:8080"

View File

@@ -1,13 +1,24 @@
# VSCode Clone with React and Vite # VS Code Clone Project
This project is a VSCode-like code editor built with React and Vite. It features a customizable UI with an activity bar, sidebar, editor area, panel, and status bar, mimicking the look and feel of Visual Studio Code. ## Authors
- Arnab Bhowmik
- Ishika Bhoyar
## Features ## Description
This project is a VS Code Clone built with React and Monaco Editor. It features a file tree navigation, tab management, code editing with syntax highlighting, and a terminal panel for running code. It mimics the core functionalities of Visual Studio Code in a browser-based environment.
- **Activity Bar**: Switch between different views like Explorer, Search, Source Control, etc. ## Frontend Functionalities
- **Sidebar**: Displays file explorer, search results, and source control information. - Built with React and Monaco Editor.
- **Editor Area**: Code editor with syntax highlighting and multiple tabs. - File tree navigation for managing files and folders.
- **Panel**: Terminal, Problems, and Output views. - Tab management for opening multiple files simultaneously.
- **Status Bar**: Displays status information and provides quick actions. - Code editing with syntax highlighting and language support.
- Terminal panel for running code and viewing output.
- Persistent file structure and content using localStorage.
## Project Structure ## Backend Functionalities
- Built with Go and Docker for secure code execution.
- Supports multiple programming languages (Python, Java, C/C++).
- Executes code in isolated Docker containers with resource limits.
- RESTful API for submitting code, checking status, and retrieving results.
- Job queue system for managing concurrent executions.
- Enforces timeouts and resource limits for security and performance.

View File

@@ -9,6 +9,32 @@ import {
import Sidebar from "./Sidebar"; import Sidebar from "./Sidebar";
import Panel from "./Panel"; // Import Panel component import Panel from "./Panel"; // Import Panel component
// Add this function to map file extensions to language identifiers
const getLanguageFromExtension = (extension) => {
const extensionMap = {
'js': 'javascript',
'jsx': 'javascript',
'ts': 'typescript',
'tsx': 'typescript',
'py': 'python',
'java': 'java',
'c': 'c',
'cpp': 'cpp',
'h': 'c',
'hpp': 'cpp',
'cs': 'csharp',
'go': 'go',
'rb': 'ruby',
'php': 'php',
'html': 'html',
'css': 'css',
'json': 'json',
'md': 'markdown'
};
return extensionMap[extension] || 'text';
};
const EditorArea = ({ const EditorArea = ({
sidebarVisible = true, sidebarVisible = true,
activeView = "explorer", activeView = "explorer",
@@ -60,6 +86,11 @@ const EditorArea = ({
const [terminalOutput, setTerminalOutput] = useState([]); const [terminalOutput, setTerminalOutput] = useState([]);
const [activeRunningFile, setActiveRunningFile] = useState(null); const [activeRunningFile, setActiveRunningFile] = useState(null);
// Add a new state for user input
const [userInput, setUserInput] = useState("");
// Add socket state to track the connection
const [activeSocket, setActiveSocket] = useState(null);
// Focus the input when new file modal opens // Focus the input when new file modal opens
useEffect(() => { useEffect(() => {
if (isNewFileModalOpen && newFileInputRef.current) { if (isNewFileModalOpen && newFileInputRef.current) {
@@ -127,6 +158,41 @@ const EditorArea = ({
} }
}, [panelVisible]); }, [panelVisible]);
// Add this useEffect for cleanup
useEffect(() => {
// Cleanup function to close socket when component unmounts
return () => {
if (activeSocket) {
activeSocket.close();
}
};
}, []);
// Add interval to poll execution status
useEffect(() => {
const checkInterval = setInterval(() => {
// Poll execution status
if (activeSocket && activeRunningFile) {
// Check if socket is still connected
if (activeSocket.readyState !== WebSocket.OPEN) {
console.warn("Socket not in OPEN state:", activeSocket.readyState);
setTerminalOutput(prev => [...prev, {
type: 'warning',
content: `Terminal connection lost, attempting to reconnect...`
}]);
// Could implement reconnection logic here
}
}
}, 5000);
// Clean up interval when component unmounts
return () => {
if (checkInterval) {
clearInterval(checkInterval);
}
};
}, [activeSocket, activeRunningFile]);
const handleEditorDidMount = (editor) => { const handleEditorDidMount = (editor) => {
editorRef.current = editor; editorRef.current = editor;
}; };
@@ -474,21 +540,31 @@ const EditorArea = ({
case "README.md": case "README.md":
return `# VS Code Clone Project return `# VS Code Clone Project
## Overview ## Authors
This is a simple VS Code clone built with React and Monaco Editor. - Arnab Bhowmik
- Ishika Bhoyar
## Features ## Description
- File tree navigation This project is a VS Code Clone built with React and Monaco Editor. It features a file tree navigation, tab management, code editing with syntax highlighting, and a terminal panel for running code. It mimics the core functionalities of Visual Studio Code in a browser-based environment.
- Tab management
- Code editing with Monaco Editor ## Frontend Functionalities
- Syntax highlighting - Built with React and Monaco Editor.
- File tree navigation for managing files and folders.
- Tab management for opening multiple files simultaneously.
- Code editing with syntax highlighting and language support.
- Terminal panel for running code and viewing output.
- Persistent file structure and content using localStorage.
## Backend Functionalities
- Built with Go and Docker for secure code execution.
- Supports multiple programming languages (Python, Java, C/C++).
- Executes code in isolated Docker containers with resource limits.
- RESTful API for submitting code, checking status, and retrieving results.
- Job queue system for managing concurrent executions.
- Enforces timeouts and resource limits for security and performance.
`;
## Getting Started
1. Create a new file using the + button in the sidebar
2. Edit your code in the editor
3. Save changes using the save button
Happy coding!`;
default: default:
return ""; return "";
} }
@@ -502,7 +578,7 @@ Happy coding!`;
width: `calc(100% - ${sidebarVisible ? sidebarWidth : 0}px)` width: `calc(100% - ${sidebarVisible ? sidebarWidth : 0}px)`
}; };
// Update the run code function to work with backend // Update the handleRunCode function
const handleRunCode = async () => { const handleRunCode = async () => {
if (!activeFile) return; if (!activeFile) return;
@@ -512,24 +588,27 @@ Happy coding!`;
setPanelVisible(true); setPanelVisible(true);
} }
// Set running state
setIsRunning(true);
setActiveRunningFile(activeFile.id);
// Clear previous output and add new command // Clear previous output and add new command
const fileExtension = activeFile.id.split('.').pop().toLowerCase(); const fileExtension = activeFile.id.split('.').pop().toLowerCase();
const language = getLanguageFromExtension(fileExtension); const language = getLanguageFromExtension(fileExtension);
const newOutput = [ const newOutput = [
{ type: 'command', content: `$ run ${activeFile.id}` } { type: 'command', content: `$ run ${activeFile.id}` },
{ type: 'output', content: 'Submitting code...' }
]; ];
setTerminalOutput(newOutput); setTerminalOutput(newOutput);
try {
// Close any existing socket
if (activeSocket) {
activeSocket.close();
setActiveSocket(null);
}
// Use API URL from environment variable // Use API URL from environment variable
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080'; const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080';
try { // Submit the code to get an execution ID
// Step 1: Submit code to backend
const submitResponse = await fetch(`${apiUrl}/submit`, { const submitResponse = await fetch(`${apiUrl}/submit`, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -537,7 +616,8 @@ Happy coding!`;
}, },
body: JSON.stringify({ body: JSON.stringify({
language: language, language: language,
code: activeFile.content code: activeFile.content,
input: "" // Explicitly passing empty input, no user input handling
}), }),
}); });
@@ -548,87 +628,189 @@ Happy coding!`;
const { id } = await submitResponse.json(); const { id } = await submitResponse.json();
setTerminalOutput(prev => [...prev, { type: 'output', content: `Job submitted with ID: ${id}` }]); setTerminalOutput(prev => [...prev, { type: 'output', content: `Job submitted with ID: ${id}` }]);
// Step 2: Poll for status until completed or failed // Set active running file
let status = 'pending'; setActiveRunningFile(activeFile.id);
while (status !== 'completed' && status !== 'failed') {
// Add a small delay between polls
await new Promise(resolve => setTimeout(resolve, 1000));
// Connect to WebSocket with the execution ID
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsBaseUrl = apiUrl.replace(/^https?:\/\//, '');
const wsUrl = `${wsProtocol}//${wsBaseUrl}/ws/terminal?id=${id}`;
setTerminalOutput(prev => [...prev, { type: 'output', content: `Connecting to: ${wsUrl}` }]);
// Create a new WebSocket
const newSocket = new WebSocket(wsUrl);
// Set up event handlers
newSocket.onopen = () => {
console.log("WebSocket connected");
setTerminalOutput(prev => [...prev, { type: 'output', content: 'Connected to execution terminal' }]);
setIsRunning(true);
};
newSocket.onmessage = (event) => {
console.log("WebSocket message received:", event.data);
setTerminalOutput(prev => [...prev, { type: 'output', content: event.data }]);
// Check if this message is likely asking for input (prompt detection)
const isPrompt =
event.data.includes("input") ||
event.data.includes("?") ||
event.data.endsWith(":") ||
event.data.endsWith("> ");
if (isPrompt) {
console.log("Input prompt detected, focusing terminal");
// Force terminal to focus after a prompt is detected
setTimeout(() => {
document.querySelector('.panel-terminal')?.focus();
}, 100);
}
};
// Add polling for job status
let statusCheckInterval;
if (id) {
// Start polling the status endpoint every 2 seconds
statusCheckInterval = setInterval(async () => {
try {
const statusResponse = await fetch(`${apiUrl}/status?id=${id}`); const statusResponse = await fetch(`${apiUrl}/status?id=${id}`);
if (!statusResponse.ok) { if (statusResponse.ok) {
throw new Error(`Status check failed: ${statusResponse.status}`); const statusData = await statusResponse.json();
// If the process is completed or failed, stop polling and update UI
if (statusData.status === 'completed' || statusData.status === 'failed') {
clearInterval(statusCheckInterval);
console.log("Process status:", statusData.status);
// Update the UI to show process is no longer running
setIsRunning(false);
// Display the final result if WebSocket didn't capture it
if (statusData.output && statusData.output.length > 0) {
setTerminalOutput(prev => {
// Check if the output is already in the terminal
const lastOutput = prev[prev.length - 1]?.content || "";
if (!lastOutput.includes(statusData.output)) {
return [...prev, {
type: 'output',
content: `\n[System] Final output:\n${statusData.output}`
}];
}
return prev;
});
} }
const statusData = await statusResponse.json(); // Close socket if it's still open
status = statusData.status; if (newSocket && newSocket.readyState === WebSocket.OPEN) {
newSocket.close();
}
}
}
} catch (error) {
console.error("Status check error:", error);
}
}, 2000);
// Update terminal with status (for any status type) // Clean up interval when component unmounts or when socket closes
setTerminalOutput(prev => { newSocket.addEventListener('close', () => {
// Update the last status message or add a new one if (statusCheckInterval) {
const hasStatus = prev.some(line => line.content.includes('Status:')); clearInterval(statusCheckInterval);
if (hasStatus) {
return prev.map(line =>
line.content.includes('Status:')
? { ...line, content: `Status: ${status}` }
: line
);
} else {
return [...prev, { type: 'output', content: `Status: ${status}` }];
} }
}); });
} }
// Get the result for both completed and failed status newSocket.onclose = (event) => {
const resultResponse = await fetch(`${apiUrl}/result?id=${id}`); console.log("WebSocket closed:", event);
if (!resultResponse.ok) { setIsRunning(false);
throw new Error(`Result fetch failed: ${resultResponse.status}`); setActiveSocket(null);
const reason = event.reason ? `: ${event.reason}` : '';
const code = event.code ? ` (code: ${event.code})` : '';
setTerminalOutput(prev => [...prev, {
type: 'warning',
content: `Terminal connection closed${reason}${code}`
}]);
// Clean up interval
if (statusCheckInterval) {
clearInterval(statusCheckInterval);
} }
};
const { output } = await resultResponse.json(); newSocket.onerror = (event) => {
console.error("WebSocket error:", event);
setTerminalOutput(prev => [...prev, {
type: 'warning',
content: `WebSocket error occurred`
}]);
};
// Format and display output // Set the active socket after all handlers are defined
const outputLines = output.split('\n').map(line => ({ setActiveSocket(newSocket);
type: status === 'failed' ? 'warning' : 'output',
content: line
}));
setTerminalOutput(prev => [
...prev,
{
type: status === 'failed' ? 'warning' : 'output',
content: status === 'failed'
? '------- EXECUTION FAILED -------'
: '------- EXECUTION RESULT -------'
},
...outputLines
]);
if (status === 'failed') {
console.error('Code execution failed:', output);
}
} catch (error) { } catch (error) {
console.error("Run code error:", error);
setTerminalOutput(prev => [...prev, { type: 'warning', content: `Error: ${error.message}` }]); setTerminalOutput(prev => [...prev, { type: 'warning', content: `Error: ${error.message}` }]);
} finally {
// Set running state to false
setIsRunning(false); setIsRunning(false);
// Also add cleanup in the error handler
if (statusCheckInterval) {
clearInterval(statusCheckInterval);
}
} }
}; };
// Helper function to convert file extension to language identifier for API // Update handleInputSubmit to ensure the input is sent properly
const getLanguageFromExtension = (extension) => { const handleInputSubmit = (input) => {
const languageMap = { // Use the direct input parameter instead of relying on userInput state
'java': 'java', const textToSend = input || userInput;
'c': 'c',
'cpp': 'cpp',
'py': 'python',
'js': 'javascript',
'jsx': 'javascript',
'ts': 'typescript',
'tsx': 'typescript'
};
return languageMap[extension] || extension; console.log("Input submit called, active socket state:",
activeSocket ? activeSocket.readyState : "no socket",
"input:", textToSend);
if (!activeSocket) {
console.warn("Cannot send input: No active socket");
setTerminalOutput(prev => [...prev, {
type: 'warning',
content: `Cannot send input: No active connection`
}]);
return;
}
if (activeSocket.readyState !== WebSocket.OPEN) {
console.warn("Socket not in OPEN state:", activeSocket.readyState);
setTerminalOutput(prev => [...prev, {
type: 'warning',
content: `Cannot send input: Connection not open (state: ${activeSocket.readyState})`
}]);
return;
}
if (!textToSend.trim()) {
console.warn("Cannot send empty input");
return;
}
try {
// Add the input to the terminal display
setTerminalOutput(prev => [...prev, { type: 'command', content: `> ${textToSend}` }]);
// Send the input via WebSocket with a newline character
console.log("Sending input:", textToSend);
activeSocket.send(textToSend + "\n");
// Clear the input field
setUserInput("");
} catch (error) {
console.error("Error sending input:", error);
setTerminalOutput(prev => [...prev, {
type: 'warning',
content: `Error sending input: ${error.message}`
}]);
}
}; };
// Update this function to also update parent state // Update this function to also update parent state
@@ -640,6 +822,37 @@ Happy coding!`;
} }
}; };
// Add this function above the return statement
const handleDownloadFile = () => {
if (!activeFile) return;
// Create a blob with the file content
const blob = new Blob([activeFile.content], { type: 'text/plain' });
// Create a URL for the blob
const url = URL.createObjectURL(blob);
// Create a temporary anchor element
const a = document.createElement('a');
a.href = url;
// Get just the filename without path
const fileName = activeFile.id.includes('/') ?
activeFile.id.split('/').pop() :
activeFile.id;
// Set the download attribute with the filename
a.download = fileName;
// Append to the document, click it, and then remove it
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Release the object URL
URL.revokeObjectURL(url);
};
return ( return (
<div className="editor-container"> <div className="editor-container">
{sidebarVisible && ( {sidebarVisible && (
@@ -787,11 +1000,15 @@ Happy coding!`;
isRunning={isRunning} isRunning={isRunning}
activeRunningFile={activeRunningFile} activeRunningFile={activeRunningFile}
initialTab="terminal" initialTab="terminal"
onClose={togglePanel} // Use the new function onClose={togglePanel}
userInput={userInput}
onUserInputChange={setUserInput}
onInputSubmit={handleInputSubmit}
/> />
</> </>
)} )}
{/* Modify the editor-actions div to include the download button */}
<div className="editor-actions"> <div className="editor-actions">
<button <button
className="editor-action-button" className="editor-action-button"
@@ -801,6 +1018,30 @@ Happy coding!`;
> >
<Save size={16} /> <Save size={16} />
</button> </button>
{/* Add download button */}
<button
className="editor-action-button"
onClick={handleDownloadFile}
disabled={!activeTab}
title="Download file"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
</button>
</div> </div>
{isNewFileModalOpen && ( {isNewFileModalOpen && (
@@ -860,6 +1101,61 @@ Happy coding!`;
<Edit size={14} className="mr-1" /> <Edit size={14} className="mr-1" />
Rename Rename
</div> </div>
{/* Add download option - only show for files */}
{contextMenuTarget?.type === 'file' && (
<div className="context-menu-item" onClick={() => {
// Find the file in the files array
const file = files.find(f => f.id === contextMenuTarget.path);
if (file) {
// Create a blob with the file content
const blob = new Blob([file.content], { type: 'text/plain' });
// Create a URL for the blob
const url = URL.createObjectURL(blob);
// Create a temporary anchor element
const a = document.createElement('a');
a.href = url;
// Get just the filename without path
const fileName = file.id.includes('/') ?
file.id.split('/').pop() :
file.id;
// Set the download attribute with the filename
a.download = fileName;
// Append to the document, click it, and then remove it
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Release the object URL
URL.revokeObjectURL(url);
}
closeContextMenu();
}}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-1"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Download
</div>
)}
<div className="context-menu-item delete" onClick={() => { <div className="context-menu-item delete" onClick={() => {
deleteItem(contextMenuTarget.path, contextMenuTarget.type); deleteItem(contextMenuTarget.path, contextMenuTarget.type);
closeContextMenu(); closeContextMenu();

View File

@@ -1,150 +0,0 @@
import React from "react"
"use client"
import Link from "next/link"
import { ChevronDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
export function Navbar() {
return (
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container flex h-16 items-center justify-between">
<Link href="/" className="flex items-center space-x-2">
<span className="text-xl font-bold">*Azzle</span>
</Link>
<NavigationMenu className="hidden md:flex">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>
Demo <ChevronDown className="h-4 w-4" />
</NavigationMenuTrigger>
<NavigationMenuContent>
<div className="grid gap-3 p-6 w-[400px]">
<NavigationMenuLink asChild>
<Link
href="/demo/features"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Features</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Explore all the features our platform has to offer
</p>
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link
href="/demo/pricing"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Pricing</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
View our flexible pricing plans
</p>
</Link>
</NavigationMenuLink>
</div>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/about" legacyBehavior passHref>
<NavigationMenuLink className="group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50">
About
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>
Services <ChevronDown className="h-4 w-4" />
</NavigationMenuTrigger>
<NavigationMenuContent>
<div className="grid gap-3 p-6 w-[400px]">
<NavigationMenuLink asChild>
<Link
href="/services/consulting"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Consulting</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Expert guidance for your business needs
</p>
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link
href="/services/implementation"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Implementation</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Full-service implementation and support
</p>
</Link>
</NavigationMenuLink>
</div>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>
Pages <ChevronDown className="h-4 w-4" />
</NavigationMenuTrigger>
<NavigationMenuContent>
<div className="grid gap-3 p-6 w-[400px]">
<NavigationMenuLink asChild>
<Link
href="/blog"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Blog</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Read our latest articles and updates
</p>
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link
href="/resources"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Resources</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Helpful guides and documentation
</p>
</Link>
</NavigationMenuLink>
</div>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/contact" legacyBehavior passHref>
<NavigationMenuLink className="group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50">
Contact
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<div className="flex items-center space-x-4">
<Button variant="ghost" asChild>
<Link href="/login">Login</Link>
</Button>
<Button asChild>
<Link href="/signup">Sign up free</Link>
</Button>
</div>
</div>
</header>
)
}
export default Navbar

View File

@@ -1,69 +1,116 @@
import React from "react"; import React, { useState, useEffect, useRef } from "react";
import { useState, useEffect } from "react"; import { X, Maximize2, ChevronDown, Plus } from "lucide-react";
import { X } from "lucide-react";
const Panel = ({ const Panel = ({
height, height,
terminalOutput = [], terminalOutput = [],
isRunning = false, isRunning = false,
waitingForInput = false,
activeRunningFile = null, activeRunningFile = null,
initialTab = "terminal", initialTab = "terminal",
onClose onClose,
userInput = "",
onUserInputChange,
onInputSubmit,
}) => { }) => {
const [activeTab, setActiveTab] = useState(initialTab); const [activeTab, setActiveTab] = useState(initialTab);
const terminalRef = useRef(null);
const [inputBuffer, setInputBuffer] = useState("");
// Set active tab when initialTab changes // Update active tab when initialTab changes
useEffect(() => { useEffect(() => {
setActiveTab(initialTab); setActiveTab(initialTab);
}, [initialTab]); }, [initialTab]);
const renderTerminal = () => { // Auto-scroll terminal to the bottom when content changes
return ( useEffect(() => {
<div className="panel-terminal"> if (terminalRef.current) {
terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
}
}, [terminalOutput]);
// Handle keyboard input for the terminal
useEffect(() => {
const handleKeyDown = (e) => {
if (!isRunning) return;
if (e.key === "Enter") {
if (inputBuffer.trim() && onInputSubmit) {
e.preventDefault();
// Update parent's userInput state directly and call submit in the same function
// instead of using setTimeout which creates a race condition
onUserInputChange(inputBuffer);
onInputSubmit(inputBuffer); // Pass inputBuffer directly to avoid race condition
setInputBuffer("");
}
} else if (e.key === "Backspace") {
setInputBuffer((prev) => prev.slice(0, -1));
} else if (e.key.length === 1) {
setInputBuffer((prev) => prev + e.key);
}
};
const terminalElement = terminalRef.current;
terminalElement?.addEventListener("keydown", handleKeyDown);
return () => {
terminalElement?.removeEventListener("keydown", handleKeyDown);
};
}, [isRunning, inputBuffer, onInputSubmit, onUserInputChange]);
// Render the terminal tab
const renderTerminal = () => (
<div
className="panel-terminal"
ref={terminalRef}
tabIndex={0} // Make div focusable
onClick={() => terminalRef.current?.focus()} // Focus when clicked
>
{terminalOutput.length > 0 ? ( {terminalOutput.length > 0 ? (
// Render output from EditorArea when available
<> <>
{terminalOutput.map((line, index) => ( {terminalOutput.map((line, index) => {
<div key={index} className={`terminal-line ${line.type === 'warning' ? 'terminal-warning' : 'terminal-output'}`}> const typeClass =
{line.type === 'command' ? <span className="terminal-prompt">$</span> : ''} {line.content} line.type === "warning"
? "terminal-warning"
: line.type === "error"
? "terminal-error"
: "terminal-output";
return (
<div key={index} className={`terminal-line ${typeClass}`}>
{line.timestamp && (
<span className="terminal-timestamp">{line.timestamp} </span>
)}
{line.type === "command" && <span className="terminal-prompt">$</span>}
{line.content}
</div> </div>
))} );
})}
{isRunning && ( {isRunning && (
<div className="terminal-line"> <div className="terminal-line terminal-input-line">
<span className="terminal-prompt">$</span> {inputBuffer}
<span className="terminal-cursor"></span> <span className="terminal-cursor"></span>
</div> </div>
)} )}
</> </>
) : ( ) : (
// Default terminal content when no output
<>
<div className="terminal-line">
<span className="terminal-prompt">$</span> npm start
</div>
<div className="terminal-line terminal-output">Starting the development server...</div>
<div className="terminal-line terminal-output">Compiled successfully!</div>
<div className="terminal-line terminal-output">You can now view vscode-clone in the browser.</div>
<div className="terminal-line terminal-output">Local: http://localhost:3000</div>
<div className="terminal-line terminal-output">On Your Network: http://192.168.1.5:3000</div>
<div className="terminal-line"> <div className="terminal-line">
<span className="terminal-prompt">$</span> <span className="terminal-prompt">$</span>
<span className="terminal-cursor"></span>
</div> </div>
</>
)} )}
</div> </div>
); );
};
const renderProblems = () => { // Render other tabs
return ( const renderProblems = () => (
<div className="panel-problems"> <div className="panel-problems">
<div className="panel-empty-message">No problems have been detected in the workspace.</div> <div className="panel-empty-message">No problems have been detected in the workspace.</div>
</div> </div>
); );
};
const renderOutput = () => { const renderOutput = () => (
return (
<div className="panel-output"> <div className="panel-output">
<div className="output-line">[Extension Host] Extension host started.</div> <div className="output-line">[Extension Host] Extension host started.</div>
<div className="output-line">[Language Server] Language server started.</div> <div className="output-line">[Language Server] Language server started.</div>
@@ -72,8 +119,27 @@ const Panel = ({
)} )}
</div> </div>
); );
};
const renderDebugConsole = () => (
<div className="panel-debug-console">
<div className="debug-line">Debug session not yet started.</div>
<div className="debug-line">Press F5 to start debugging.</div>
</div>
);
const renderPorts = () => (
<div className="panel-ports">
<div className="ports-line">No forwarded ports detected.</div>
</div>
);
const renderComments = () => (
<div className="panel-comments">
<div className="comments-line">No comments have been added to this workspace.</div>
</div>
);
// Get content for the active tab
const getTabContent = () => { const getTabContent = () => {
switch (activeTab) { switch (activeTab) {
case "terminal": case "terminal":
@@ -82,6 +148,12 @@ const Panel = ({
return renderProblems(); return renderProblems();
case "output": case "output":
return renderOutput(); return renderOutput();
case "debug":
return renderDebugConsole();
case "ports":
return renderPorts();
case "comments":
return renderComments();
default: default:
return <div>Unknown tab</div>; return <div>Unknown tab</div>;
} }
@@ -90,76 +162,29 @@ const Panel = ({
return ( return (
<div className="panel" style={{ height: `${height}px` }}> <div className="panel" style={{ height: `${height}px` }}>
<div className="panel-tabs"> <div className="panel-tabs">
{["problems", "output", "debug", "terminal", "ports", "comments"].map((tab) => (
<div <div
className={`panel-tab ${activeTab === "problems" ? "active" : ""}`} key={tab}
onClick={() => setActiveTab("problems")} className={`panel-tab ${activeTab === tab ? "active" : ""}`}
onClick={() => setActiveTab(tab)}
> >
<span className="tab-icon"> <span className="tab-name">{tab.toUpperCase()}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
</span>
<span className="tab-name">Problems</span>
</div>
<div className={`panel-tab ${activeTab === "output" ? "active" : ""}`} onClick={() => setActiveTab("output")}>
<span className="tab-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</span>
<span className="tab-name">Output</span>
</div>
<div
className={`panel-tab ${activeTab === "terminal" ? "active" : ""}`}
onClick={() => setActiveTab("terminal")}
>
<span className="tab-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="4 17 10 11 4 5"></polyline>
<line x1="12" y1="19" x2="20" y2="19"></line>
</svg>
</span>
<span className="tab-name">Terminal</span>
</div> </div>
))}
{/* Add close button */}
<div className="panel-actions"> <div className="panel-actions">
{/* <button className="panel-action-btn">
<span className="current-terminal">node - frontend</span>
<ChevronDown size={16} />
</button>
<button className="panel-action-btn">
<Plus size={16} />
</button>
<button className="panel-action-btn">
<Maximize2 size={16} />
</button> */}
<button className="panel-close-btn" onClick={onClose}> <button className="panel-close-btn" onClick={onClose}>
<X size={14} /> <X size={16} />
</button> </button>
</div> </div>
</div> </div>
@@ -170,4 +195,3 @@ const Panel = ({
}; };
export default Panel; export default Panel;

View File

@@ -75,21 +75,6 @@ const Sidebar = ({
</svg> </svg>
)} )}
</span> </span>
<span className="folder-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#75beff"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
</span>
<span className="folder-name">{name}</span> <span className="folder-name">{name}</span>
</div> </div>
{isExpanded && ( {isExpanded && (
@@ -184,10 +169,38 @@ const Sidebar = ({
</div> </div>
); );
}; };
const getFileIcon = (fileName) => { const getFileIcon = (fileName) => {
const extension = fileName.split('.').pop().toLowerCase(); const extension = fileName.split('.').pop().toLowerCase();
if (fileName.toLowerCase() === 'readme.md') {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#007acc" /* Blue color for the circle */
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" fill="none" stroke="#007acc" />
<text
x="12"
y="15"
textAnchor="middle"
fontSize="10"
fill="#007acc"
fontFamily="Arial, sans-serif"
fontWeight="bold"
>
i
</text>
</svg>
);
}
if (['jsx', 'js', 'ts', 'tsx'].includes(extension)) { if (['jsx', 'js', 'ts', 'tsx'].includes(extension)) {
return ( return (
<svg <svg

View File

@@ -1,10 +1,11 @@
import React from "react"; import React from "react";
"use client"
const StatusBar = ({ togglePanel, panelVisible }) => { const StatusBar = ({ togglePanel, panelVisible }) => {
return ( return (
<div className="status-bar"> <div className="status-bar">
{/* Left Section of the Status Bar */}
<div className="status-bar-left"> <div className="status-bar-left">
{/* Branch Indicator */}
<div className="status-item"> <div className="status-item">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -16,6 +17,7 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
aria-label="Branch Icon"
> >
<line x1="6" y1="3" x2="6" y2="15"></line> <line x1="6" y1="3" x2="6" y2="15"></line>
<circle cx="18" cy="6" r="3"></circle> <circle cx="18" cy="6" r="3"></circle>
@@ -25,6 +27,7 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
<span>main</span> <span>main</span>
</div> </div>
{/* Error Indicator */}
<div className="status-item"> <div className="status-item">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -36,30 +39,14 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
aria-label="Error Icon"
> >
<polyline points="20 6 9 17 4 12"></polyline> <polyline points="20 6 9 17 4 12"></polyline>
</svg> </svg>
<span>0 errors</span> <span>0 errors</span>
</div> </div>
<button className="status-item status-button" onClick={togglePanel}> {/* Warning Indicator */}
<span>{panelVisible ? "Hide Terminal" : "Show Terminal"}</span>
</button>
</div>
<div className="status-bar-right">
<div className="status-item">
<span>Ln 1, Col 1</span>
</div>
<div className="status-item">
<span>Spaces: 2</span>
</div>
<div className="status-item">
<span>UTF-8</span>
</div>
<div className="status-item"> <div className="status-item">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -71,6 +58,65 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
aria-label="Warning Icon"
>
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
<line x1="12" y1="9" x2="12" y2="13"></line>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>
<span>0 warnings</span>
</div>
{/* Toggle Terminal Button */}
<button
className="status-item status-button"
onClick={togglePanel}
aria-label="Toggle Terminal"
>
<span>{panelVisible ? "Hide Terminal" : "Show Terminal"}</span>
</button>
</div>
{/* Right Section of the Status Bar */}
<div className="status-bar-right">
{/* Line and Column Indicator */}
<div className="status-item">
<span>Ln 1, Col 1</span>
</div>
{/* Spaces Indicator */}
<div className="status-item">
<span>Spaces: 2</span>
</div>
{/* Encoding Indicator */}
<div className="status-item">
<span>UTF-8</span>
</div>
{/* Language Mode */}
<div className="status-item">
<span>JavaScript</span>
</div>
{/* EOL (End of Line) Indicator */}
<div className="status-item">
<span>LF</span>
</div>
{/* Connection Status */}
<div className="status-item">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-label="Connection Icon"
> >
<path d="M5 12.55a11 11 0 0 1 14.08 0"></path> <path d="M5 12.55a11 11 0 0 1 14.08 0"></path>
<path d="M1.42 9a16 16 0 0 1 21.16 0"></path> <path d="M1.42 9a16 16 0 0 1 21.16 0"></path>
@@ -80,6 +126,7 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
<span>Connected</span> <span>Connected</span>
</div> </div>
{/* Bell Icon */}
<div className="status-item"> <div className="status-item">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -91,6 +138,7 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
aria-label="Bell Icon"
> >
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path> <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path> <path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
@@ -98,8 +146,7 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
</div> </div>
</div> </div>
</div> </div>
) );
} };
export default StatusBar
export default StatusBar;

View File

@@ -57,12 +57,12 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--vscode-activityBar-background); background-color: var(--vscode-activityBar-background);
z-index: 10; z-index: 10; /* Lower z-index than the StatusBar */
width: 50px; position: fixed;
height: 100%;
position: fixed; /* Change to fixed to avoid layout issues */
top: 0; top: 0;
left: 0; left: 0;
height: calc(100% - 22px); /* Subtract the height of the StatusBar */
width: 50px;
} }
.activity-bar button { .activity-bar button {
@@ -404,7 +404,7 @@ body {
flex: 1; flex: 1;
overflow: auto; overflow: auto;
font-family: "Consolas", "Courier New", monospace; font-family: "Consolas", "Courier New", monospace;
font-size: 13px; font-size: 10px;
padding: 8px; padding: 8px;
padding: 10px; padding: 10px;
font-family: 'Consolas', 'Courier New', monospace; font-family: 'Consolas', 'Courier New', monospace;
@@ -418,6 +418,16 @@ body {
height: 100%; height: 100%;
} }
.panel-terminal {
padding: 8px;
font-family: monospace;
overflow-y: auto;
height: calc(100% - 36px); /* Adjust based on your header height */
background-color: #1e1e1e;
color: #ddd;
outline: none; /* Remove focus outline */
}
.panel-terminal .terminal-line { .panel-terminal .terminal-line {
white-space: pre-wrap; white-space: pre-wrap;
margin-bottom: 3px; margin-bottom: 3px;
@@ -426,22 +436,20 @@ body {
.terminal-line { .terminal-line {
white-space: pre-wrap; white-space: pre-wrap;
line-height: 1.5; line-height: 1.5;
margin-bottom: 2px;
} }
.terminal-prompt { .terminal-prompt {
color: #0f0; color: #0a84ff;
margin-right: 8px; margin-right: 8px;
color: #569cd6;
margin-right: 6px;
} }
.terminal-output { .terminal-output {
color: #888888; color: #ddd;
color: #cccccc;
} }
.terminal-warning { .terminal-warning {
color: #ddb100; color: #ffa500;
} }
.output-line { .output-line {
@@ -463,9 +471,8 @@ body {
} }
@keyframes blink { @keyframes blink {
50% { 0%, 100% { opacity: 1; }
opacity: 0; 50% { opacity: 0; }
}
} }
.panel-empty-message { .panel-empty-message {
@@ -924,6 +931,21 @@ body {
color: #569cd6; color: #569cd6;
} }
.terminal-input {
background-color: transparent;
border: none;
color: inherit;
font-family: monospace;
font-size: inherit;
margin-left: 8px;
outline: none;
width: calc(100% - 60px);
}
.terminal-input:focus {
outline: none;
}
.terminal-line.info { .terminal-line.info {
color: #75beff; color: #75beff;
} }
@@ -949,9 +971,8 @@ body {
} }
@keyframes blink { @keyframes blink {
50% { 0%, 100% { opacity: 1; }
opacity: 0; 50% { opacity: 0; }
}
} }
/* Make sure the monaco container adjusts when terminal is shown */ /* Make sure the monaco container adjusts when terminal is shown */

238
Readme.md
View File

@@ -1,22 +1,240 @@
# Monaco Code Execution Engine # Monaco Code Execution Engine
Monaco is a secure, containerized code execution engine that allows you to run code in multiple programming languages through a simple REST API.
Monaco is a secure, containerized code execution engine that allows you to run code in multiple programming languages through a simple REST API and WebSocket connections for real-time terminal interaction.
## Features ## Features
- Multi-language support: Run code in Python, Java, C, and C++
- Secure execution: All code runs in isolated Docker containers - **Multi-language support**: Run code in Python, Java, C, and C++
- Resource limits: Memory, CPU, and file descriptor limits to prevent abuse - **Secure execution**: All code runs in isolated Docker containers
- Concurrent processing: Efficient job queue for handling multiple requests - **Resource limits**: Memory, CPU, and file descriptor limits to prevent abuse
- Simple REST API: Easy to integrate with any frontend - **Concurrent processing**: Efficient job queue for handling multiple requests
- **Simple REST API**: Easy to integrate with any frontend
- **Interactive terminal**: Real-time code execution with input/output via WebSockets
- **VS Code-like interface**: Modern editor with syntax highlighting and file management
## Architecture ## Architecture
Monaco consists of several components: Monaco consists of several components:
- HTTP Handlers (handler/handler.go): Processes API requests ### Backend Components
- Execution Service (service/execution.go): Manages code execution in containers
- Job Queue (queue/queue.go): Handles concurrent execution of code submissions - **HTTP Handlers** (`handler/handler.go`): Processes API requests and WebSocket connections
- Submission Model (model/submission.go): Defines the data structure for code submissions - **Execution Service** (`service/execution.go`): Manages code execution in containers
- **Job Queue** (`queue/queue.go`): Handles concurrent execution of code submissions
- **Submission Model** (`model/submission.go`): Defines the data structure for code submissions
### Frontend Components
- **Editor Area** (`EditorArea.jsx`): Main code editor with Monaco editor integration
- **Terminal Panel** (`Panel.jsx`): Interactive terminal for code execution and input
- **Sidebar** (`Sidebar.jsx`): File explorer and project structure navigation
- **Status Bar** (`StatusBar.jsx`): Information display and quick actions
### Communication Flow
1. Frontend submits code to backend via REST API
2. Backend assigns a unique ID and queues the execution
3. Frontend connects to WebSocket endpoint with the execution ID
4. Backend sends real-time execution output through WebSocket
5. Frontend can send user input back through WebSocket
6. Results are stored and retrievable via REST endpoints
## Requirements ## Requirements
- **Backend**:
- Go 1.22.3 or higher - Go 1.22.3 or higher
- Docker - Docker
- Network connectivity for container image pulling - Network connectivity for container image pulling
- **Frontend**:
- Node.js and npm/yarn
- Modern web browser
## Installation
### Backend Setup
1. Clone the repository:
```bash
git clone https://github.com/arnab-afk/monaco.git
cd monaco/backend
2.Install Go dependencies:
```bash
go mod download
```
3.Build the application:
```bash
go build -o monaco
```
4.Run the service
```bash
./monaco
```
The backend service will start on port 8080 by default.
### Frontend Setup
1. Navigate to the Frontend directory:
```bash
cd Frontend
```
2. Install dependencies:
```bash
npm install
```
3. Set up environment variables: Create a ```.env``` or ```.env.local.``` file with:
```bash
VITE_API_URL=http://localhost:8080
```
4. Start the development server:
```bash
npm run dev
```
The frontend will be available at http://localhost:5173 by default.
### API Reference
### REST Endpoints
```POST /submit```
Submits code for execution
```json
{
"language": "python",
"code": "print('Hello, World!')",
"input": ""
}
```
Response:
```json
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1"
}
```
```GET /status?id={submissionId}```
Checks the status of submission:
```json
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
"status": "completed",
"queuedAt": "2025-03-25T14:30:00Z",
"startedAt": "2025-03-25T14:30:01Z",
"completedAt": "2025-03-25T14:30:02Z",
"executionTime": 1000
}
```
```GET /result?id={submissionId}```
Gets the execution result of a submission.
Response:
```json
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
"status": "completed",
"language": "python",
"output": "Hello, World!",
"queuedAt": "2025-03-25T14:30:00Z",
"startedAt": "2025-03-25T14:30:01Z",
"completedAt": "2025-03-25T14:30:02Z",
"executionTime": 1000,
"executionTimeFormatted": "1.0s",
"totalTime": 2000,
"totalTimeFormatted": "2.0s"
}
```
```GET /queue-stats```
Gets the statistics about the job queue.
Response:
```json
{
"queue_stats": {
"queue_length": 5,
"max_workers": 3,
"running_jobs": 3
},
"submissions": 42
}
```
### WebSocket Endpoints
```ws://localhost:8080/ws/terminal?id={submissionId}```
Establishes a real-time connection for terminal interaction.
- The server sends execution output as plain text messages.
- The client can send input as plain text messages (with newline).
- Connection automatically closes when execution completes or fails.
### Terminal Input Handling
The system supports interactive programs requiring user input:
1. The frontend detects possible input prompts by looking for patterns
2. When detected, it focuses the terminal and allows user input
3. User input is captured in the terminal component's inputBuffer
4. When the user presses Enter, the input is:
- Sent to the backend via WebSocket.
- Displayed in the terminal.
- Buffer is cleared for next input.
5. The input is processed by the running program in real-time.
Troubleshooting tips:
- Ensure WebSocket connection is established before sending input
- Check for WebSocket errors in console
- Verify input reaches the backend by checking server logs
- Ensure newline characters are properly appended to input.
### Language Support
### Python
- **Version**: Python 3.9
- **Input Handling**: Direct stdin piping
- **Limitations**: No file I/O, no package imports outside standard library
- **Resource Limits**: 100MB memory, 10% CPU
### Java
- **Version**: Java 11 (Eclipse Temurin)
- **Class Detection**: Extracts class name from code using regex.
- **Memory Settings**: 64MB min heap, 256MB max heap
- **Resource Limits**: 400MB memory, 50% CPU
C
- **Version**: Latest GCC
- **Compilation Flags**: Default GCC settings
- **Resource Limits**: 100MB memory, 10% CPU
### C++
- **Version**: Latest G++
- **Standard**: C++17
- **Resource Limits**: 100MB memory, 10% CPU
### Security Considerations
All code execution happens within isolated Docker containers with:
- No network access (```--network=none```)
- Limited CPU and memory resources
- Limited file system access
- No persistent storage
- Execution time limits (10-15 seconds)
### Debugging
Check backend logs for execution details
Use browser developer tools to debug WebSocket connections
Terminal panel shows WebSocket connection status and errors
Check Docker logs for container-related issues.
### Contributing
Contributions are welcome! Please feel free to submit a Pull Request.

56
backend/data.py Normal file
View File

@@ -0,0 +1,56 @@
import os
import aiohttp
import asyncio
from datetime import datetime, timedelta
# Base URL template
BASE_URL = "https://bhuvan-app3.nrsc.gov.in/isroeodatadownloadutility/tiledownloadnew_cfr_new.php?f=nices_ssm2_{}_{}.zip&se=NICES&u=arnabafk"
# Directory to save files
SAVE_DIR = "data"
os.makedirs(SAVE_DIR, exist_ok=True)
async def download_file(session, file_url, file_path):
"""Download a file asynchronously."""
print(f"Downloading {file_url}...")
try:
async with session.get(file_url) as response:
if response.status == 200:
with open(file_path, 'wb') as file:
while chunk := await response.content.read(1024):
file.write(chunk)
print(f"Downloaded: {file_path}")
else:
print(f"Failed to download: {file_path}, Status Code: {response.status}")
except Exception as e:
print(f"Error downloading {file_url}: {e}")
async def fetch_data_for_year(session, year):
"""Fetch and download data for a given year."""
year_dir = os.path.join(SAVE_DIR, str(year))
os.makedirs(year_dir, exist_ok=True)
start_date = datetime(year, 1, 1)
end_date = datetime(year, 12, 31)
delta = timedelta(days=2)
tasks = []
date = start_date
while date <= end_date:
date_str = date.strftime("%Y%m%d")
file_url = BASE_URL.format(date_str, "NICES")
file_name = f"nices_ssm2_{date_str}.zip"
file_path = os.path.join(year_dir, file_name)
tasks.append(download_file(session, file_url, file_path))
date += delta
await asyncio.gather(*tasks)
async def main():
"""Main function to download data for multiple years."""
async with aiohttp.ClientSession() as session:
await asyncio.gather(*(fetch_data_for_year(session, year) for year in range(2002, 2025)))
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -6,6 +6,7 @@ require github.com/stretchr/testify v1.9.0
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/kr/pretty v0.3.0 // indirect github.com/kr/pretty v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect

View File

@@ -1,6 +1,8 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=

View File

@@ -2,12 +2,15 @@ package handler
import ( import (
"encoding/json" "encoding/json"
"fmt"
"log"
"net/http" "net/http"
"sync" "sync"
"time" "time"
"github.com/arnab-afk/monaco/model" "github.com/arnab-afk/monaco/model"
"github.com/arnab-afk/monaco/service" "github.com/arnab-afk/monaco/service"
"github.com/gorilla/websocket"
) )
// Handler manages HTTP requests for code submissions // Handler manages HTTP requests for code submissions
@@ -179,6 +182,63 @@ func (h *Handler) QueueStatsHandler(w http.ResponseWriter, r *http.Request) {
}) })
} }
// ConnectTerminal connects a WebSocket to a running execution
func (h *Handler) ConnectTerminal(conn *websocket.Conn, executionID string) {
// Get submission from storage
h.mu.Lock()
submission, found := h.submissions[executionID]
status := "not found"
if found {
status = submission.Status
}
h.mu.Unlock()
log.Printf("[WS-%s] Terminal connection request, submission status: %s", executionID, status)
if !found {
log.Printf("[WS-%s] Execution not found", executionID)
conn.WriteMessage(websocket.TextMessage, []byte("Execution not found"))
conn.Close()
return
}
// If execution is already completed, send stored output and close
if submission.Status == "completed" || submission.Status == "failed" {
log.Printf("[WS-%s] Execution already %s, sending stored output (length: %d)",
executionID, submission.Status, len(submission.Output))
conn.WriteMessage(websocket.TextMessage, []byte(submission.Output))
conn.Close()
return
}
log.Printf("[WS-%s] Registering connection for real-time updates, current status: %s",
executionID, submission.Status)
// Register this connection with the execution service for real-time updates
h.executionService.RegisterTerminalConnection(executionID, conn)
// Send initial connection confirmation
initialMsg := fmt.Sprintf("[System] Connected to process (ID: %s, Status: %s)\n",
executionID, submission.Status)
conn.WriteMessage(websocket.TextMessage, []byte(initialMsg))
// Handle incoming messages from the terminal (for stdin)
go func() {
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Printf("[WS-%s] Read error: %v", executionID, err)
h.executionService.UnregisterTerminalConnection(executionID, conn)
break
}
log.Printf("[WS-%s] Received input from client: %s", executionID, string(message))
// Send input to the execution if it's waiting for input
h.executionService.SendInput(executionID, string(message))
}
}()
}
// generateID creates a unique ID for submissions // generateID creates a unique ID for submissions
func (h *Handler) generateID() string { func (h *Handler) generateID() string {
return service.GenerateUUID() return service.GenerateUUID()

View File

@@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/arnab-afk/monaco/handler" "github.com/arnab-afk/monaco/handler"
"github.com/gorilla/websocket"
) )
func main() { func main() {
@@ -46,7 +47,40 @@ func main() {
} }
} }
// Register handlers with logging and CORS middleware // Configure WebSocket upgrader
upgrader := websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// Allow connections from any origin
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// WebSocket handler for terminal connection
http.HandleFunc("/ws/terminal", func(w http.ResponseWriter, r *http.Request) {
// Get execution ID from query parameters
executionID := r.URL.Query().Get("id")
if executionID == "" {
log.Println("[WS] Missing execution ID")
http.Error(w, "Missing execution ID", http.StatusBadRequest)
return
}
// Upgrade HTTP connection to WebSocket
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("[WS] Failed to upgrade connection: %v", err)
return
}
log.Printf("[WS] Terminal connection established for execution ID: %s", executionID)
// Connect this WebSocket to the execution service for real-time streaming
h.ConnectTerminal(conn, executionID)
})
// Register REST API handlers with logging and CORS middleware
http.HandleFunc("/submit", corsMiddleware(loggingMiddleware(h.SubmitHandler))) http.HandleFunc("/submit", corsMiddleware(loggingMiddleware(h.SubmitHandler)))
http.HandleFunc("/status", corsMiddleware(loggingMiddleware(h.StatusHandler))) http.HandleFunc("/status", corsMiddleware(loggingMiddleware(h.StatusHandler)))
http.HandleFunc("/result", corsMiddleware(loggingMiddleware(h.ResultHandler))) http.HandleFunc("/result", corsMiddleware(loggingMiddleware(h.ResultHandler)))

View File

@@ -1,6 +1,8 @@
package service package service
import ( import (
"bytes"
"context"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -8,25 +10,101 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"sync" "sync"
"time" "time"
"github.com/arnab-afk/monaco/model" "github.com/arnab-afk/monaco/model"
"github.com/arnab-afk/monaco/queue" "github.com/arnab-afk/monaco/queue"
"github.com/gorilla/websocket"
) )
// ExecutionService handles code execution for multiple languages // ExecutionService handles code execution for multiple languages
type ExecutionService struct { type ExecutionService struct {
mu sync.Mutex mu sync.Mutex
queue *queue.JobQueue queue *queue.JobQueue
terminalConnections map[string][]*websocket.Conn // Map of executionID to WebSocket connections
execInputChannels map[string]chan string // Map of executionID to input channels
} }
// NewExecutionService creates a new execution service // NewExecutionService creates a new execution service
func NewExecutionService() *ExecutionService { func NewExecutionService() *ExecutionService {
log.Println("Initializing execution service with 3 concurrent workers") log.Println("Initializing execution service with 3 concurrent workers")
return &ExecutionService{ return &ExecutionService{
queue: queue.NewJobQueue(35), // 3 concurrent executions max queue: queue.NewJobQueue(3), // 3 concurrent executions max
terminalConnections: make(map[string][]*websocket.Conn),
execInputChannels: make(map[string]chan string),
}
}
// RegisterTerminalConnection registers a WebSocket connection for an execution
func (s *ExecutionService) RegisterTerminalConnection(executionID string, conn *websocket.Conn) {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.terminalConnections[executionID]; !exists {
s.terminalConnections[executionID] = make([]*websocket.Conn, 0)
}
s.terminalConnections[executionID] = append(s.terminalConnections[executionID], conn)
log.Printf("[WS-%s] Terminal connection registered, total connections: %d",
executionID, len(s.terminalConnections[executionID]))
}
// UnregisterTerminalConnection removes a WebSocket connection
func (s *ExecutionService) UnregisterTerminalConnection(executionID string, conn *websocket.Conn) {
s.mu.Lock()
defer s.mu.Unlock()
connections, exists := s.terminalConnections[executionID]
if !exists {
return
}
// Remove the specific connection
for i, c := range connections {
if c == conn {
s.terminalConnections[executionID] = append(connections[:i], connections[i+1:]...)
break
}
}
// If no more connections, clean up
if len(s.terminalConnections[executionID]) == 0 {
delete(s.terminalConnections, executionID)
}
log.Printf("[WS-%s] Terminal connection unregistered", executionID)
}
// SendOutputToTerminals sends output to all connected terminals for an execution
func (s *ExecutionService) SendOutputToTerminals(executionID string, output string) {
s.mu.Lock()
connections := s.terminalConnections[executionID]
s.mu.Unlock()
for _, conn := range connections {
if err := conn.WriteMessage(websocket.TextMessage, []byte(output)); err != nil {
log.Printf("[WS-%s] Error sending to terminal: %v", executionID, err)
// Unregister this connection on error
s.UnregisterTerminalConnection(executionID, conn)
}
}
}
// SendInput sends user input to a running process
func (s *ExecutionService) SendInput(executionID string, input string) {
s.mu.Lock()
inputChan, exists := s.execInputChannels[executionID]
s.mu.Unlock()
if exists {
select {
case inputChan <- input:
log.Printf("[WS-%s] Sent input to execution: %s", executionID, input)
default:
log.Printf("[WS-%s] Execution not ready for input", executionID)
}
} else {
log.Printf("[WS-%s] No input channel for execution", executionID)
} }
} }
@@ -110,48 +188,123 @@ func (s *ExecutionService) executeLanguageSpecific(submission *model.CodeSubmiss
func (s *ExecutionService) executeWithInput(cmd *exec.Cmd, input string, timeout time.Duration, submissionID string) ([]byte, error) { func (s *ExecutionService) executeWithInput(cmd *exec.Cmd, input string, timeout time.Duration, submissionID string) ([]byte, error) {
log.Printf("[TIMEOUT-%s] Setting execution timeout: %v", submissionID, timeout) log.Printf("[TIMEOUT-%s] Setting execution timeout: %v", submissionID, timeout)
// Set up input pipe if input is provided // Create pipes for stdin, stdout, and stderr
if input != "" { stdin, stdinErr := cmd.StdinPipe()
stdin, err := cmd.StdinPipe() if stdinErr != nil {
return nil, fmt.Errorf("failed to create stdin pipe: %v", stdinErr)
}
stdout, stdoutErr := cmd.StdoutPipe()
if stdoutErr != nil {
return nil, fmt.Errorf("failed to create stdout pipe: %v", stdoutErr)
}
stderr, stderrErr := cmd.StderrPipe()
if stderrErr != nil {
return nil, fmt.Errorf("failed to create stderr pipe: %v", stderrErr)
}
// Create an input channel and register it
inputChan := make(chan string, 10)
s.mu.Lock()
s.execInputChannels[submissionID] = inputChan
s.mu.Unlock()
// Clean up the input channel when done
defer func() {
s.mu.Lock()
delete(s.execInputChannels, submissionID)
s.mu.Unlock()
close(inputChan)
}()
// Start the command
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start process: %v", err)
}
// Create a buffer to collect all output
var outputBuffer bytes.Buffer
// Handle stdout in a goroutine
go func() {
buffer := make([]byte, 1024)
for {
n, err := stdout.Read(buffer)
if n > 0 {
data := buffer[:n]
outputBuffer.Write(data)
// Send real-time output to connected terminals
s.SendOutputToTerminals(submissionID, string(data))
}
if err != nil { if err != nil {
log.Printf("[ERROR-%s] Failed to create stdin pipe: %v", submissionID, err) break
return nil, err }
} }
// Write input in a goroutine to avoid blocking
go func() {
defer stdin.Close()
io.WriteString(stdin, input)
}() }()
log.Printf("[INPUT-%s] Providing input to process", submissionID) // Handle stderr in a goroutine
}
done := make(chan struct{})
var output []byte
var err error
go func() { go func() {
log.Printf("[EXEC-%s] Starting command execution: %v", submissionID, cmd.Args) buffer := make([]byte, 1024)
output, err = cmd.CombinedOutput() for {
close(done) n, err := stderr.Read(buffer)
if n > 0 {
data := buffer[:n]
outputBuffer.Write(data)
// Send real-time output to connected terminals
s.SendOutputToTerminals(submissionID, string(data))
}
if err != nil {
break
}
}
}() }()
// Write initial input if provided
if input != "" {
io.WriteString(stdin, input+"\n")
}
// Process is in a separate context, but it needs to be killed if timeout occurs
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Handle additional input from WebSocket in a goroutine
go func() {
for {
select {
case additionalInput, ok := <-inputChan:
if !ok {
return
}
log.Printf("[INPUT-%s] Received input from WebSocket: %s", submissionID, additionalInput)
io.WriteString(stdin, additionalInput+"\n")
case <-ctx.Done():
return
}
}
}()
// Wait for the command to complete with timeout
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
// Wait for completion or timeout
select { select {
case <-time.After(timeout): case <-time.After(timeout):
cancel() // Stop the input handler
log.Printf("[TIMEOUT-%s] Execution timed out after %v seconds", submissionID, timeout.Seconds()) log.Printf("[TIMEOUT-%s] Execution timed out after %v seconds", submissionID, timeout.Seconds())
if err := cmd.Process.Kill(); err != nil { if err := cmd.Process.Kill(); err != nil {
log.Printf("[TIMEOUT-%s] Failed to kill process: %v", submissionID, err) log.Printf("[TIMEOUT-%s] Failed to kill process: %v", submissionID, err)
return nil, fmt.Errorf("timeout reached but failed to kill process: %v", err)
} }
return nil, fmt.Errorf("execution timed out after %v seconds", timeout.Seconds()) s.SendOutputToTerminals(submissionID, fmt.Sprintf("\n[System] Process killed after timeout of %v seconds", timeout.Seconds()))
case <-done: return outputBuffer.Bytes(), fmt.Errorf("execution timed out after %v seconds", timeout.Seconds())
if err != nil { case err := <-done:
log.Printf("[EXEC-%s] Command execution failed: %v", submissionID, err) cancel() // Stop the input handler
} else { s.SendOutputToTerminals(submissionID, "\n[System] Process completed")
log.Printf("[EXEC-%s] Command execution completed successfully", submissionID) return outputBuffer.Bytes(), err
}
return output, err
} }
} }
@@ -169,15 +322,9 @@ func (s *ExecutionService) executePython(submission *model.CodeSubmission) {
"python:3.9", "python", "-c", submission.Code) "python:3.9", "python", "-c", submission.Code)
log.Printf("[PYTHON-%s] Executing Python code with timeout: 10s", submission.ID) log.Printf("[PYTHON-%s] Executing Python code with timeout: 10s", submission.ID)
var output []byte
var err error
if submission.Input != "" { // Use the enhanced executeWithInput method for all executions
cmd.Stdin = strings.NewReader(submission.Input) output, err := s.executeWithInput(cmd, submission.Input, 100*time.Second, submission.ID)
output, err = cmd.CombinedOutput()
} else {
output, err = s.executeWithTimeout(cmd, 10*time.Second, submission.ID)
}
elapsed := time.Since(startTime) elapsed := time.Since(startTime)
log.Printf("[PYTHON-%s] Python execution completed in %v", submission.ID, elapsed) log.Printf("[PYTHON-%s] Python execution completed in %v", submission.ID, elapsed)
@@ -255,7 +402,7 @@ func (s *ExecutionService) executeJava(submission *model.CodeSubmission) {
log.Printf("[JAVA-%s] Compilation successful", submission.ID) log.Printf("[JAVA-%s] Compilation successful", submission.ID)
// Now run the compiled class // Now run the compiled class with the enhanced executeWithInput method
runCmd := exec.Command("docker", "run", "--rm", "-i", runCmd := exec.Command("docker", "run", "--rm", "-i",
"--network=none", // No network access "--network=none", // No network access
"--memory=400m", // Memory limit "--memory=400m", // Memory limit
@@ -267,17 +414,8 @@ func (s *ExecutionService) executeJava(submission *model.CodeSubmission) {
"-Xverify:none", "-Xms64m", "-Xmx256m", "-Xverify:none", "-Xms64m", "-Xmx256m",
"-cp", "/code", className) "-cp", "/code", className)
// Add input if provided log.Printf("[JAVA-%s] Executing Java code", submission.ID)
var output []byte output, err := s.executeWithInput(runCmd, submission.Input, 15*time.Second, submission.ID)
if submission.Input != "" {
log.Printf("[JAVA-%s] Executing Java code with input", submission.ID)
runCmd.Stdin = strings.NewReader(submission.Input)
output, err = runCmd.CombinedOutput()
} else {
log.Printf("[JAVA-%s] Executing Java code without input", submission.ID)
output, err = s.executeWithTimeout(runCmd, 15*time.Second, submission.ID)
}
elapsed := time.Since(startTime) elapsed := time.Since(startTime)
log.Printf("[JAVA-%s] Java execution completed in %v", submission.ID, elapsed) log.Printf("[JAVA-%s] Java execution completed in %v", submission.ID, elapsed)
@@ -327,7 +465,7 @@ func (s *ExecutionService) executeC(submission *model.CodeSubmission) {
log.Printf("[C-%s] Compilation successful", submission.ID) log.Printf("[C-%s] Compilation successful", submission.ID)
// Run C executable // Run C executable using executeWithInput to support WebSockets
runCmd := exec.Command("docker", "run", "--rm", "-i", runCmd := exec.Command("docker", "run", "--rm", "-i",
"--network=none", // No network access "--network=none", // No network access
"--memory=100m", // Memory limit "--memory=100m", // Memory limit
@@ -336,17 +474,8 @@ func (s *ExecutionService) executeC(submission *model.CodeSubmission) {
"-v", tempDir+":/code", // Mount code directory "-v", tempDir+":/code", // Mount code directory
"gcc:latest", "/code/solution") "gcc:latest", "/code/solution")
// Add input if provided log.Printf("[C-%s] Executing C code", submission.ID)
var output []byte output, err := s.executeWithInput(runCmd, submission.Input, 30*time.Second, submission.ID)
// Don't redeclare err here - use the existing variable
if submission.Input != "" {
log.Printf("[C-%s] Executing C code with input", submission.ID)
runCmd.Stdin = strings.NewReader(submission.Input)
output, err = runCmd.CombinedOutput() // Use the existing err variable
} else {
log.Printf("[C-%s] Executing C code without input", submission.ID)
output, err = s.executeWithTimeout(runCmd, 10*time.Second, submission.ID) // Use the existing err variable
}
elapsed := time.Since(startTime) elapsed := time.Since(startTime)
log.Printf("[C-%s] C execution completed in %v", submission.ID, elapsed) log.Printf("[C-%s] C execution completed in %v", submission.ID, elapsed)
@@ -396,7 +525,7 @@ func (s *ExecutionService) executeCpp(submission *model.CodeSubmission) {
log.Printf("[CPP-%s] Compilation successful", submission.ID) log.Printf("[CPP-%s] Compilation successful", submission.ID)
// Run C++ executable // Run C++ executable using executeWithInput to support WebSockets
runCmd := exec.Command("docker", "run", "--rm", "-i", runCmd := exec.Command("docker", "run", "--rm", "-i",
"--network=none", // No network access "--network=none", // No network access
"--memory=100m", // Memory limit "--memory=100m", // Memory limit
@@ -405,16 +534,8 @@ func (s *ExecutionService) executeCpp(submission *model.CodeSubmission) {
"-v", tempDir+":/code", // Mount code directory "-v", tempDir+":/code", // Mount code directory
"gcc:latest", "/code/solution") "gcc:latest", "/code/solution")
// Add input if provided log.Printf("[CPP-%s] Executing C++ code", submission.ID)
var output []byte output, err := s.executeWithInput(runCmd, submission.Input, 100*time.Second, submission.ID)
if submission.Input != "" {
log.Printf("[CPP-%s] Executing C++ code with input", submission.ID)
runCmd.Stdin = strings.NewReader(submission.Input)
output, err = runCmd.CombinedOutput()
} else {
log.Printf("[CPP-%s] Executing C++ code without input", submission.ID)
output, err = s.executeWithTimeout(runCmd, 10*time.Second, submission.ID)
}
elapsed := time.Since(startTime) elapsed := time.Since(startTime)
log.Printf("[CPP-%s] C++ execution completed in %v", submission.ID, elapsed) log.Printf("[CPP-%s] C++ execution completed in %v", submission.ID, elapsed)

BIN
backend/tmp/main.exe Normal file

Binary file not shown.