input channel mapping update
This commit is contained in:
@@ -527,6 +527,8 @@ Happy coding!`;
|
||||
|
||||
const newOutput = [
|
||||
{ type: 'command', content: `$ run ${activeFile.id}` },
|
||||
{ type: 'output', content: '------- PROGRAM EXECUTION -------' },
|
||||
{ type: 'output', content: `Language: ${language}` },
|
||||
{ type: 'output', content: 'Waiting for input (press Enter if no input is needed)...' }
|
||||
];
|
||||
setTerminalOutput(newOutput);
|
||||
@@ -541,10 +543,17 @@ Happy coding!`;
|
||||
setWaitingForInput(false);
|
||||
|
||||
// Add message that we're running with the input
|
||||
setTerminalOutput(prev => [
|
||||
...prev,
|
||||
{ type: 'output', content: userInput ? `Using input: "${userInput}"` : 'Running without input...' }
|
||||
]);
|
||||
if (userInput) {
|
||||
setTerminalOutput(prev => [
|
||||
...prev,
|
||||
{ type: 'input', content: userInput }
|
||||
]);
|
||||
} else {
|
||||
setTerminalOutput(prev => [
|
||||
...prev,
|
||||
{ type: 'output', content: 'Running without input...' }
|
||||
]);
|
||||
}
|
||||
|
||||
// Use API URL from environment variable
|
||||
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080';
|
||||
@@ -609,19 +618,34 @@ Happy coding!`;
|
||||
const { output } = await resultResponse.json();
|
||||
|
||||
// Format and display output
|
||||
const outputLines = output.split('\n').map(line => ({
|
||||
const outputLines = [];
|
||||
|
||||
// Add a header
|
||||
outputLines.push({
|
||||
type: status === 'failed' ? 'warning' : 'output',
|
||||
content: line
|
||||
}));
|
||||
content: status === 'failed'
|
||||
? '------- EXECUTION FAILED -------'
|
||||
: '------- EXECUTION RESULT -------'
|
||||
});
|
||||
|
||||
// Process the output line by line
|
||||
output.split('\n').forEach(line => {
|
||||
// Check if this is an input line
|
||||
if (line.startsWith('[Input] ')) {
|
||||
outputLines.push({
|
||||
type: 'input',
|
||||
content: line.substring(8) // Remove the '[Input] ' prefix
|
||||
});
|
||||
} else {
|
||||
outputLines.push({
|
||||
type: status === 'failed' ? 'warning' : 'output',
|
||||
content: line
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setTerminalOutput(prev => [
|
||||
...prev,
|
||||
{
|
||||
type: status === 'failed' ? 'warning' : 'output',
|
||||
content: status === 'failed'
|
||||
? '------- EXECUTION FAILED -------'
|
||||
: '------- EXECUTION RESULT -------'
|
||||
},
|
||||
...outputLines
|
||||
]);
|
||||
|
||||
|
||||
@@ -28,26 +28,35 @@ const Panel = ({
|
||||
// Render output from EditorArea when available
|
||||
<>
|
||||
{terminalOutput.map((line, index) => (
|
||||
<div key={index} className={`terminal-line ${line.type === 'warning' ? 'terminal-warning' : 'terminal-output'}`}>
|
||||
{line.type === 'command' ? <span className="terminal-prompt">$</span> : ''} {line.content}
|
||||
<div key={index} className={`terminal-line ${line.type === 'warning' ? 'terminal-warning' : line.type === 'input' ? 'terminal-input-line' : 'terminal-output'}`}>
|
||||
{line.type === 'command' ? <span className="terminal-prompt">$</span> : ''}
|
||||
{line.type === 'input' ? <span className="terminal-input-marker">[Input]</span> : ''}
|
||||
{line.content}
|
||||
</div>
|
||||
))}
|
||||
{waitingForInput && (
|
||||
<div className="terminal-line">
|
||||
<span className="terminal-prompt">Input:</span>
|
||||
<input
|
||||
type="text"
|
||||
className="terminal-input"
|
||||
value={userInput}
|
||||
onChange={(e) => onUserInputChange && onUserInputChange(e.target.value)}
|
||||
placeholder="Enter input for your program here..."
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && onInputSubmit) {
|
||||
onInputSubmit();
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="terminal-line terminal-input-container">
|
||||
<div className="terminal-input-header">
|
||||
<span className="terminal-input-marker">Input Required:</span>
|
||||
</div>
|
||||
<div className="terminal-input-wrapper">
|
||||
<input
|
||||
type="text"
|
||||
className="terminal-input"
|
||||
value={userInput}
|
||||
onChange={(e) => onUserInputChange && onUserInputChange(e.target.value)}
|
||||
placeholder="Enter input for your program here..."
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && onInputSubmit) {
|
||||
onInputSubmit();
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="terminal-input-help">
|
||||
Press Enter to submit input
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -435,6 +435,19 @@ body {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.terminal-input-marker {
|
||||
color: #4ec9b0;
|
||||
margin-right: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.terminal-input-line {
|
||||
color: #4ec9b0;
|
||||
background-color: rgba(78, 201, 176, 0.1);
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.terminal-output {
|
||||
color: #888888;
|
||||
color: #cccccc;
|
||||
@@ -925,18 +938,43 @@ body {
|
||||
}
|
||||
|
||||
.terminal-input {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
background-color: rgba(78, 201, 176, 0.1);
|
||||
border: 1px solid rgba(78, 201, 176, 0.3);
|
||||
border-radius: 3px;
|
||||
color: #4ec9b0;
|
||||
font-family: monospace;
|
||||
font-size: inherit;
|
||||
margin-left: 8px;
|
||||
outline: none;
|
||||
width: calc(100% - 60px);
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.terminal-input:focus {
|
||||
outline: none;
|
||||
border-color: rgba(78, 201, 176, 0.6);
|
||||
}
|
||||
|
||||
.terminal-input-container {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: rgba(78, 201, 176, 0.05);
|
||||
border-radius: 5px;
|
||||
border-left: 3px solid #4ec9b0;
|
||||
}
|
||||
|
||||
.terminal-input-header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.terminal-input-wrapper {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.terminal-input-help {
|
||||
font-size: 12px;
|
||||
color: #888888;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.terminal-line.info {
|
||||
|
||||
137
Readme.md
137
Readme.md
@@ -1,22 +1,125 @@
|
||||
# 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 Online Code Compiler
|
||||
|
||||
A full-featured online code compiler with a VS Code-like interface. This project allows users to write, edit, and execute code in multiple programming languages directly in the browser.
|
||||
|
||||
## Features
|
||||
- Multi-language support: Run code in Python, Java, C, and C++
|
||||
- Secure execution: All code runs in isolated Docker containers
|
||||
- Resource limits: Memory, CPU, and file descriptor limits to prevent abuse
|
||||
- Concurrent processing: Efficient job queue for handling multiple requests
|
||||
- Simple REST API: Easy to integrate with any frontend
|
||||
|
||||
## Architecture
|
||||
Monaco consists of several components:
|
||||
- **VS Code-like Interface**: Familiar editor experience with syntax highlighting, tabs, and file explorer
|
||||
- **Multi-language Support**: Run code in Python, JavaScript, Go, Java, C, and C++
|
||||
- **Input/Output Handling**: Enter input for your programs and see the output in real-time
|
||||
- **Secure Execution**: Code runs in isolated Docker containers on the backend
|
||||
- **File Management**: Create, edit, and organize files and folders
|
||||
|
||||
- HTTP Handlers (handler/handler.go): Processes API requests
|
||||
- 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
|
||||
## Project Structure
|
||||
|
||||
## Requirements
|
||||
- Go 1.22.3 or higher
|
||||
- Docker
|
||||
- Network connectivity for container image pulling
|
||||
- **Frontend**: React-based UI with Monaco Editor
|
||||
- **Backend**: Go-based code execution service with Docker integration
|
||||
- HTTP Handlers (internal/api/handlers): Processes API requests
|
||||
- Execution Service (internal/executor): Manages code execution in containers
|
||||
- Job Queue (internal/queue): Handles concurrent execution of code submissions
|
||||
- Submission Model (internal/models): Defines the data structure for code submissions
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+ for the frontend
|
||||
- Go 1.22+ for the backend
|
||||
- Docker for code execution
|
||||
|
||||
### Running the Frontend
|
||||
|
||||
```bash
|
||||
cd Frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The frontend will be available at http://localhost:5173
|
||||
|
||||
### Running the Backend
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
go build -o monaco ./cmd/server
|
||||
./monaco
|
||||
```
|
||||
|
||||
The backend API will be available at http://localhost:8080
|
||||
|
||||
## Using the Online Compiler
|
||||
|
||||
1. **Create a File**: Click the "+" button in the editor tabs or use the file explorer
|
||||
2. **Write Code**: Use the Monaco editor to write your code
|
||||
3. **Run Code**: Click the "Play" button in the top right corner
|
||||
4. **Enter Input**: If your program requires input, enter it in the terminal panel
|
||||
5. **View Output**: See the execution results in the terminal panel
|
||||
|
||||
## Supported Languages
|
||||
|
||||
- **Python** (.py)
|
||||
- **JavaScript** (.js)
|
||||
- **Go** (.go)
|
||||
- **Java** (.java)
|
||||
- **C** (.c)
|
||||
- **C++** (.cpp)
|
||||
|
||||
## Examples
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
name = input("Enter your name: ")
|
||||
print(f"Hello, {name}!")
|
||||
for i in range(5):
|
||||
print(f"Count: {i}")
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
const readline = require('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.question('Enter your name: ', (name) => {
|
||||
console.log(`Hello, ${name}!`);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
console.log(`Count: ${i}`);
|
||||
}
|
||||
rl.close();
|
||||
});
|
||||
```
|
||||
|
||||
### Go
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Print("Enter your name: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
name, _ := reader.ReadString('\n')
|
||||
name = strings.TrimSpace(name)
|
||||
fmt.Printf("Hello, %s!\n", name)
|
||||
for i := 0; i < 5; i++ {
|
||||
fmt.Printf("Count: %d\n", i)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- All code is executed in isolated Docker containers
|
||||
- Network access is disabled
|
||||
- Memory and CPU limits are enforced
|
||||
- Execution timeouts prevent infinite loops
|
||||
@@ -2,7 +2,6 @@ package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -144,6 +143,7 @@ func (h *Handler) ResultHandler(w http.ResponseWriter, r *http.Request) {
|
||||
"status": submission.Status,
|
||||
"language": submission.Language,
|
||||
"output": submission.Output,
|
||||
"input": submission.Input,
|
||||
}
|
||||
|
||||
// Add error information if available
|
||||
@@ -190,6 +190,55 @@ func (h *Handler) QueueStatsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// SubmitInputHandler handles interactive input submission
|
||||
func (h *Handler) SubmitInputHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Only allow POST method
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the request body
|
||||
var inputRequest struct {
|
||||
ID string `json:"id"`
|
||||
Input string `json:"input"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&inputRequest); err != nil {
|
||||
http.Error(w, "Invalid request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the request
|
||||
if inputRequest.ID == "" {
|
||||
http.Error(w, "ID is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the submission from the map
|
||||
h.mu.Lock()
|
||||
submission, exists := h.submissions[inputRequest.ID]
|
||||
h.mu.Unlock()
|
||||
|
||||
if !exists {
|
||||
http.Error(w, "Submission not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the submission is waiting for input
|
||||
if submission.Status != "waiting_for_input" {
|
||||
http.Error(w, "Submission is not waiting for input", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Send the input to the execution service
|
||||
h.executionService.SubmitInput(submission, inputRequest.Input)
|
||||
|
||||
// Return success response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "input_submitted"})
|
||||
}
|
||||
|
||||
// HealthCheckHandler handles health check requests
|
||||
func (h *Handler) HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Only allow GET method
|
||||
|
||||
@@ -24,6 +24,7 @@ func SetupRoutes() http.Handler {
|
||||
mux.HandleFunc("/submit", h.SubmitHandler)
|
||||
mux.HandleFunc("/status", h.StatusHandler)
|
||||
mux.HandleFunc("/result", h.ResultHandler)
|
||||
mux.HandleFunc("/submit-input", h.SubmitInputHandler)
|
||||
mux.HandleFunc("/queue-stats", h.QueueStatsHandler)
|
||||
mux.HandleFunc("/health", h.HealthCheckHandler)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/arnab-afk/monaco/internal/models"
|
||||
@@ -19,6 +20,9 @@ import (
|
||||
// ExecutionService manages code execution
|
||||
type ExecutionService struct {
|
||||
queue *queue.JobQueue
|
||||
mu sync.Mutex
|
||||
// Map of submission ID to input channel for interactive programs
|
||||
inputChannels map[string]chan string
|
||||
}
|
||||
|
||||
// CodeExecutionJob represents a code execution job
|
||||
@@ -30,7 +34,8 @@ type CodeExecutionJob struct {
|
||||
// NewExecutionService creates a new execution service
|
||||
func NewExecutionService() *ExecutionService {
|
||||
return &ExecutionService{
|
||||
queue: queue.NewJobQueue(5), // 5 concurrent workers
|
||||
queue: queue.NewJobQueue(5), // 5 concurrent workers
|
||||
inputChannels: make(map[string]chan string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,6 +606,25 @@ func (s *ExecutionService) updateSubmissionResult(submission *models.CodeSubmiss
|
||||
submission.Output = formattedOutput + rawOutput
|
||||
}
|
||||
|
||||
// SubmitInput submits input to a running interactive program
|
||||
func (s *ExecutionService) SubmitInput(submission *models.CodeSubmission, input string) {
|
||||
s.mu.Lock()
|
||||
inputChan, exists := s.inputChannels[submission.ID]
|
||||
s.mu.Unlock()
|
||||
|
||||
if !exists {
|
||||
log.Printf("[ERROR] No input channel found for submission %s", submission.ID)
|
||||
return
|
||||
}
|
||||
|
||||
// Send the input to the channel
|
||||
inputChan <- input
|
||||
|
||||
// Update the submission status
|
||||
submission.Status = "running"
|
||||
submission.Output += "[Input] " + input + "\n"
|
||||
}
|
||||
|
||||
// GetQueueStats returns statistics about the job queue
|
||||
func (s *ExecutionService) GetQueueStats() models.QueueStats {
|
||||
return s.queue.GetStats()
|
||||
|
||||
@@ -4,16 +4,18 @@ import "time"
|
||||
|
||||
// CodeSubmission represents a code submission for execution
|
||||
type CodeSubmission struct {
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Language string `json:"language"`
|
||||
Input string `json:"input"`
|
||||
Status string `json:"status"` // "pending", "queued", "running", "completed", "failed"
|
||||
QueuedAt time.Time `json:"queuedAt,omitempty"`
|
||||
StartedAt time.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt time.Time `json:"completedAt,omitempty"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Language string `json:"language"`
|
||||
Input string `json:"input"`
|
||||
Status string `json:"status"` // "pending", "queued", "running", "waiting_for_input", "completed", "failed"
|
||||
QueuedAt time.Time `json:"queuedAt,omitempty"`
|
||||
StartedAt time.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt time.Time `json:"completedAt,omitempty"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
IsInteractive bool `json:"isInteractive,omitempty"` // Whether the program requires interactive input
|
||||
CurrentPrompt string `json:"currentPrompt,omitempty"` // Current input prompt if waiting for input
|
||||
}
|
||||
|
||||
// ExecutionResult represents the result of code execution
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user