Merge pull request #2 from ishikabhoyar/leetcode

Leetcode
This commit is contained in:
Ishika Bhoyar
2025-10-13 16:38:55 +05:30
committed by GitHub
21 changed files with 1625 additions and 34 deletions

56
Dockerfile Normal file
View File

@@ -0,0 +1,56 @@
# Stage 1: Build the React frontend
FROM node:18-alpine AS frontend-builder
WORKDIR /app/frontend
# Define a build-time argument for the API URL with a default value
ARG VITE_API_URL=""
# Set it as an environment variable for the build command
ENV VITE_API_URL=$VITE_API_URL
# Copy package files and install dependencies
COPY Frontend/package.json Frontend/package-lock.json* Frontend/yarn.lock* ./
RUN yarn install --frozen-lockfile
# Copy the rest of the frontend source code
COPY Frontend/ ./
# Build the static files. Vite will use the VITE_API_URL env var.
RUN yarn build
# Stage 2: Build the Go backend
FROM golang:1.19-alpine AS backend-builder
WORKDIR /app/backend
# Install git for dependency fetching
RUN apk update && apk add --no-cache git
# Copy go module files and download dependencies
COPY new-backend/go.mod new-backend/go.sum ./
RUN go mod download
# Copy the backend source code
COPY new-backend/ ./
# Build the Go binary
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-s -w" -o /monaco-backend .
# Stage 3: Create the final image with Nginx
FROM nginx:1.25-alpine
# Install Docker client for the backend
RUN apk update && apk add --no-cache docker-cli
# Copy the Go backend binary
COPY --from=backend-builder /monaco-backend /usr/local/bin/monaco-backend
# Copy the built frontend files to the Nginx html directory
COPY --from=frontend-builder /app/frontend/dist /usr/share/nginx/html
# Copy the Nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Expose the public port for Nginx
EXPOSE 80
# Start both the backend and Nginx
CMD sh -c 'monaco-backend & nginx -g "daemon off;"'

View File

@@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/public/favicon.ico" type="image/x-icon" />
<title>VSCode</title> <title>VSCode</title>
</head> </head>
<body> <body>

BIN
Frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,10 +1,18 @@
import VSCodeUI from "./components/VSCodeUI.jsx" import CodeChallenge from "./components/CodeChallenge.jsx"
import "./index.css" import "./index.css"
function App() { function App() {
return ( return (
<div className="App"> <div className="App">
<VSCodeUI /> <CodeChallenge />
<footer className="footer-bar fixed bottom-0 left-0 right-0 border-t border-slate-200/40 dark:border-gray-800/20 bg-black">
<div className="flex items-center justify-center h-7">
<span className="text-xs text-slate-400 dark:text-gray-400 flex items-center">
Copyright © 2025. Made with
by Ishika and Arnab.
</span>
</div>
</footer>
</div> </div>
) )
} }

View File

@@ -0,0 +1,750 @@
import React, { useState, useEffect, useRef } from 'react';
import Editor from "@monaco-editor/react";
import { Play, Send } from 'lucide-react';
const CodeChallenge = () => {
const [activeQuestion, setActiveQuestion] = useState("Q.1");
const [language, setLanguage] = useState("JavaScript");
const [code, setCode] = useState("");
const [isRunning, setIsRunning] = useState(false);
const [terminalOutput, setTerminalOutput] = useState([]);
const [autoSelected, setAutoSelected] = useState(true);
const [activeSocket, setActiveSocket] = useState(null);
const [submissionId, setSubmissionId] = useState(null);
const socketRef = useRef(null);
// Map frontend language names to backend language identifiers
const getLanguageIdentifier = (uiLanguage) => {
const languageMap = {
'javascript': 'javascript',
'python': 'python',
'java': 'java',
'c++': 'cpp',
'c': 'c'
};
// Important: make sure we convert to lowercase to match the backend's expected format
return languageMap[uiLanguage.toLowerCase()] || uiLanguage.toLowerCase();
};
// Reset execution state to allow rerunning
const resetExecutionState = () => {
setIsRunning(false);
// Properly close the socket if it exists and is open
if (socketRef.current) {
if (socketRef.current.readyState === WebSocket.OPEN) {
socketRef.current.close();
}
socketRef.current = null;
}
// Ensure activeSocket is also nullified
setActiveSocket(null);
console.log('Execution state reset, buttons should be enabled');
};
// Example problem data
const problems = {
"Q.1": {
id: "two-sum",
title: "Two Sum",
description: "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.",
constraints: "You may assume that each input would have exactly one solution, and you may not use the same element twice.",
examples: [
{
input: "nums = [2,7,11,15], target = 9",
output: "[0,1]",
explanation: "Because nums[0] + nums[1] == 9, we return [0, 1]."
},
{
input: "nums = [3,2,4], target = 6",
output: "[1,2]"
},
{
input: "nums = [3,3], target = 6",
output: "[0,1]"
}
],
starterCode: `/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
// Write your solution here
};`
},
"Q.2": {
id: "palindrome-number",
title: "Palindrome Number",
description: "Given an integer x, return true if x is a palindrome, and false otherwise.",
examples: [
{
input: "x = 121",
output: "true"
},
{
input: "x = -121",
output: "false",
explanation: "From left to right, it reads -121. From right to left, it reads 121-. Therefore it is not a palindrome."
}
],
starterCode: `/**
* @param {number} x
* @return {boolean}
*/
var isPalindrome = function(x) {
// Write your solution here
};`
},
"Q.3": {
id: "valid-parentheses",
title: "Valid Parentheses",
description: "Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.",
constraints: "An input string is valid if: Open brackets must be closed by the same type of brackets. Open brackets must be closed in the correct order.",
examples: [
{
input: 's = "()"',
output: "true"
},
{
input: 's = "()[]{}"',
output: "true"
},
{
input: 's = "(]"',
output: "false"
}
],
starterCode: `/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
// Write your solution here
};`
}
};
// Get appropriate starter code based on language
const getStarterCode = (problem, lang) => {
// Language-specific starter code templates
const templates = {
'JavaScript': problem.starterCode,
'C': `#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// ${problem.title} solution
int main() {
// Write your solution here
return 0;
}`,
'Python': `# ${problem.title}
def solution():
# Write your solution here
# Use input() for user input in Python
# Example: name = input("Enter your name: ")
pass
if __name__ == "__main__":
solution()`,
'Java': `public class Solution {
// ${problem.title}
public static void main(String[] args) {
// Write your solution here
}
}`,
'C++': `#include <iostream>
#include <vector>
using namespace std;
// ${problem.title} solution
int main() {
// Write your solution here
return 0;
}`
};
return templates[lang] || problem.starterCode;
};
// Set initial code based on active problem
useEffect(() => {
if (problems[activeQuestion]) {
setCode(getStarterCode(problems[activeQuestion], language));
}
}, [activeQuestion, language]);
// Cleanup WebSocket connection on unmount
useEffect(() => {
return () => {
if (socketRef.current) {
socketRef.current.close();
}
};
}, []);
// Set a safety timeout to ensure buttons are re-enabled if execution hangs
useEffect(() => {
let safetyTimer = null;
if (isRunning) {
// If execution is running for more than 30 seconds, reset state
safetyTimer = setTimeout(() => {
console.log('Safety timeout reached, re-enabling buttons');
resetExecutionState();
}, 30000);
}
return () => {
if (safetyTimer) clearTimeout(safetyTimer);
};
}, [isRunning]);
// Connect to WebSocket
const connectToWebSocket = (id) => {
console.log('Connecting to WebSocket with ID:', id);
// Force close any existing connections
if (socketRef.current) {
console.log('Closing existing socket, state:', socketRef.current.readyState);
socketRef.current.onclose = null; // Remove existing handler to avoid conflicts
socketRef.current.onerror = null;
socketRef.current.onmessage = null;
if (socketRef.current.readyState !== WebSocket.CLOSED) {
socketRef.current.close();
}
socketRef.current = null;
}
if (activeSocket) {
console.log('Clearing active socket reference');
setActiveSocket(null);
}
console.log('Creating new WebSocket connection');
const wsUrl = `ws://localhost:8080/api/ws/terminal/${id}`;
const socket = new WebSocket(wsUrl);
socket.onopen = () => {
console.log('WebSocket connection established');
setActiveSocket(socket);
};
socket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
console.log('WebSocket message received:', message, 'Current isRunning state:', isRunning);
switch (message.type) {
case 'output':
// Handle output message based on the format seen in the screenshot
setTerminalOutput(prev => [
...prev,
{
type: message.content.isError ? 'error' : 'output',
content: message.content.text
}
]);
break;
case 'input_prompt':
// Handle input prompt message (e.g., "Enter your name:")
setTerminalOutput(prev => [
...prev,
{ type: 'output', content: message.content }
]);
break;
case 'status':
let statusText = '';
let statusValue = '';
if (typeof message.content === 'object') {
statusText = `Status: ${message.content.status}`;
statusValue = message.content.status;
} else {
statusText = `Status: ${message.content}`;
statusValue = message.content;
}
setTerminalOutput(prev => [
...prev,
{ type: 'system', content: statusText }
]);
// If status contains "completed" or "failed", stop running
if (statusValue.includes('completed') || statusValue.includes('failed')) {
console.log('Execution completed or failed, stopping');
setTimeout(() => {
setIsRunning(false);
}, 500); // Small delay to ensure UI updates properly
}
break;
case 'error':
let errorContent = '';
if (typeof message.content === 'object' && message.content.message) {
errorContent = message.content.message;
} else {
errorContent = String(message.content);
}
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: errorContent }
]);
console.log('Error received, enabling buttons');
setTimeout(() => {
setIsRunning(false);
}, 500); // Small delay to ensure UI updates properly
break;
case 'system':
const systemContent = String(message.content);
setTerminalOutput(prev => [
...prev,
{ type: 'system', content: systemContent }
]);
// Check for connection closing message which indicates execution is complete
if (systemContent.includes('Connection will close') ||
systemContent.includes('completed successfully') ||
systemContent.includes('Execution completed')) {
console.log('System message indicates completion, enabling buttons');
setTimeout(() => {
setIsRunning(false);
}, 500);
}
break;
default:
// Handle any other message types or direct string content
console.log('Unknown message type:', message);
if (typeof message === 'object') {
setTerminalOutput(prev => [
...prev,
{ type: 'output', content: JSON.stringify(message) }
]);
}
}
} catch (error) {
console.error('Error parsing WebSocket message:', error);
}
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: 'WebSocket connection error' }
]);
console.log('WebSocket error, enabling buttons');
setTimeout(() => {
setIsRunning(false);
}, 500); // Small delay to ensure UI updates properly
};
socket.onclose = () => {
console.log('WebSocket connection closed');
setActiveSocket(null);
// Ensure buttons are re-enabled when the connection closes
setTimeout(() => {
resetExecutionState();
}, 100);
};
// Set the socket reference early to ensure we can clean it up if needed
socketRef.current = socket;
return socket;
};
// Handle code execution
const runCode = async () => {
console.log('Run button clicked, current state:', {
isRunning,
socketState: activeSocket ? activeSocket.readyState : 'no socket',
socketRefState: socketRef.current ? socketRef.current.readyState : 'no socket ref'
});
// First make sure previous connections are fully closed
resetExecutionState();
// Increase the delay to ensure clean state before starting new execution
setTimeout(async () => {
// Double-check socket state before proceeding
if (activeSocket || socketRef.current) {
console.warn('Socket still exists after reset, forcing cleanup');
if (activeSocket && activeSocket.readyState !== WebSocket.CLOSED) {
activeSocket.close();
}
if (socketRef.current && socketRef.current.readyState !== WebSocket.CLOSED) {
socketRef.current.close();
}
socketRef.current = null;
setActiveSocket(null);
// Extra delay to ensure socket is fully closed
await new Promise(resolve => setTimeout(resolve, 100));
}
setIsRunning(true);
setTerminalOutput([
{ type: 'system', content: `Running ${problems[activeQuestion].id}...` }
]);
try {
// Submit code to the backend
const response = await fetch('http://localhost:8080/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: code,
language: getLanguageIdentifier(language),
input: '',
}),
});
if (!response.ok) {
throw new Error(`Error: ${response.statusText}`);
}
const data = await response.json();
setSubmissionId(data.id);
// Connect to WebSocket for real-time updates
connectToWebSocket(data.id);
} catch (error) {
console.error('Error submitting code:', error);
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Error: ${error.message}` }
]);
resetExecutionState();
}
}, 200); // Increased delay to ensure clean state
};
// Handle code submission
const submitCode = async () => {
setIsRunning(true);
setTerminalOutput([
{ type: 'system', content: `Submitting solution for ${problems[activeQuestion].id}...` }
]);
try {
// Submit code to the backend
const response = await fetch('http://localhost:8080/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: code,
language: getLanguageIdentifier(language),
input: '',
problemId: problems[activeQuestion].id
}),
});
if (!response.ok) {
throw new Error(`Error: ${response.statusText}`);
}
const data = await response.json();
setSubmissionId(data.id);
// Connect to WebSocket for real-time updates
connectToWebSocket(data.id);
} catch (error) {
console.error('Error submitting solution:', error);
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Error: ${error.message}` }
]);
setIsRunning(false);
}
};
// Render the current problem
const renderProblem = () => {
const problem = problems[activeQuestion];
if (!problem) return null;
return (
<div className="problem-container">
<h1>{problem.title}</h1>
<div className="problem-description">
<p>{problem.description}</p>
{problem.constraints && <p>{problem.constraints}</p>}
</div>
{/* Test cases section removed */}
</div>
);
};
// Add this useEffect to monitor socket state
useEffect(() => {
// If we have an active socket but aren't running, we should clean up
if (activeSocket && !isRunning) {
console.log('Cleaning up inactive socket');
if (activeSocket.readyState === WebSocket.OPEN) {
activeSocket.close();
}
setActiveSocket(null);
}
}, [activeSocket, isRunning]);
return (
<div className="code-challenge-container">
<header className="code-challenge-header">
<h1>OnScreen Test</h1>
<button className="sign-in-btn">Sign In</button>
</header>
{/* <div className="code-challenge-problem-nav">
<h3 className="problem-number">1. {problems["Q.1"].title}</h3>
</div> */}
<div className="code-challenge-main">
<div className="problem-tabs">
<button
className={activeQuestion === "Q.1" ? "tab-active" : ""}
onClick={() => setActiveQuestion("Q.1")}
>
Q.1
</button>
<button
className={activeQuestion === "Q.2" ? "tab-active" : ""}
onClick={() => setActiveQuestion("Q.2")}
>
Q.2
</button>
<button
className={activeQuestion === "Q.3" ? "tab-active" : ""}
onClick={() => setActiveQuestion("Q.3")}
>
Q.3
</button>
</div>
<div className="problem-content">
{renderProblem()}
</div>
<div className="editor-section">
<div className="editor-header">
<div className="editor-controls">
<select
value={language}
onChange={(e) => setLanguage(e.target.value)}
className="language-selector"
>
<option value="JavaScript">JavaScript</option>
<option value="Python">Python</option>
<option value="Java">Java</option>
<option value="C++">C++</option>
<option value="C">C</option>
</select>
</div>
<div className="editor-actions">
<button
className="run-btn"
onClick={runCode}
disabled={isRunning}
title={isRunning ? "Code execution in progress..." : "Run code"}
>
{isRunning ? (
<>
<span className="loading-spinner"></span> Running...
</>
) : (
<>
<Play size={16} /> Run
</>
)}
</button>
<button
className="submit-btn"
onClick={submitCode}
disabled={isRunning}
title={isRunning ? "Code execution in progress..." : "Submit solution"}
>
{isRunning ? (
<>
<span className="loading-spinner"></span> Submitting...
</>
) : (
<>
<Send size={16} /> Submit
</>
)}
</button>
</div>
</div>
<div className="editor-container">
<Editor
height="100%"
defaultLanguage="python"
language={language.toLowerCase() === 'c++' ? 'cpp' : language.toLowerCase()}
value={code}
onChange={(value) => setCode(value)}
theme="hc-black"
options={{
fontSize: 14,
minimap: { enabled: false },
scrollBeyondLastLine: false,
automaticLayout: true,
}}
/>
</div>
</div>
</div>
<div className="terminal-section">
<div className="terminal-header">
<span>Terminal</span>
{/* <div className="terminal-controls">
<button className="terminal-btn">⊞</button>
<button className="terminal-btn">□</button>
<button className="terminal-btn">✕</button>
</div> */}
</div>
<div className="terminal-content">
{terminalOutput.map((line, index) => (
<div
key={index}
className={`terminal-line ${line.type}`}
>
{line.content}
</div>
))}
<div className="terminal-prompt">
<span className="prompt-symbol">$</span>
<input
type="text"
className="terminal-input"
placeholder="Type here..."
disabled={!isRunning}
// Update the ref callback
ref={(inputEl) => {
// Auto-focus input when isRunning changes to true
if (inputEl && isRunning) {
inputEl.focus();
// Clear any previous input
inputEl.value = '';
}
}}
onKeyDown={(e) => { // Change from onKeyPress to onKeyDown for better cross-browser support
if (e.key === 'Enter') {
e.preventDefault(); // Prevent default to avoid form submissions
const input = e.target.value.trim();
if (!input) return; // Skip empty input
if (activeSocket && activeSocket.readyState === WebSocket.OPEN) {
try {
// Send input to server
activeSocket.send(JSON.stringify({
"type": "input",
"content": input
}));
// Add input to terminal output
setTerminalOutput(prev => [
...prev,
{ type: 'system', content: `$ ${input}` }
]);
// Clear the input field
e.target.value = '';
} catch (error) {
console.error("Error sending input:", error);
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Failed to send input: ${error.message}` }
]);
}
} else {
// Better error message with socket state information
const socketState = activeSocket ?
['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][activeSocket.readyState] :
'NO_SOCKET';
console.log(`Cannot send input: Socket state is ${socketState}`);
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Cannot send input: connection not available (${socketState})` }
]);
}
}
}}
onKeyPress={(e) => {
if (e.key === 'Enter' && activeSocket && activeSocket.readyState === WebSocket.OPEN) {
const input = e.target.value;
// Send input to WebSocket with the correct format
try {
activeSocket.send(JSON.stringify({
"type": "input",
"content": input
}));
// Add input to terminal output
setTerminalOutput(prev => [
...prev,
{ type: 'system', content: `$ ${input}` }
]);
// Clear the input field
e.target.value = '';
} catch (error) {
console.error("Error sending input:", error);
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Failed to send input: ${error.message}` }
]);
}
} else if (e.key === 'Enter') {
// Inform user if socket isn't available
if (!activeSocket || activeSocket.readyState !== WebSocket.OPEN) {
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Cannot send input: connection closed` }
]);
}
}
}}
/>
</div>
</div>
</div>
</div>
);
};
export default CodeChallenge;

View File

@@ -136,11 +136,6 @@ const EditorArea = ({
useEffect(() => { useEffect(() => {
localStorage.setItem("vscode-clone-files", JSON.stringify(files)); localStorage.setItem("vscode-clone-files", JSON.stringify(files));
}, [files]); }, [files]);
useEffect(() => {
localStorage.setItem("vscode-clone-structure", JSON.stringify(fileStructure));
}, [fileStructure]);
// Add this effect to handle editor resize when sidebar changes // Add this effect to handle editor resize when sidebar changes
useEffect(() => { useEffect(() => {
// Force editor to readjust layout when sidebar visibility changes // Force editor to readjust layout when sidebar visibility changes

View File

@@ -1,17 +1,17 @@
:root { :root {
--vscode-background: #1e1e1e; --vscode-background: #000000;
--vscode-foreground: #d4d4d4; --vscode-foreground: #d4d4d4;
--vscode-activityBar-background: #333333; --vscode-activityBar-background: #333333;
--vscode-activityBar-foreground: #ffffff; --vscode-activityBar-foreground: #ffffff;
--vscode-activityBar-inactiveForeground: #ffffff80; --vscode-activityBar-inactiveForeground: #ffffff80;
--vscode-sideBar-background: #252526; --vscode-sideBar-background: #252526;
--vscode-sideBar-foreground: #cccccc; --vscode-sideBar-foreground: #cccccc;
--vscode-editor-background: #1e1e1e; --vscode-editor-background: #000000;
--vscode-statusBar-background: #007acc; --vscode-statusBar-background: #007acc;
--vscode-statusBar-foreground: #ffffff; --vscode-statusBar-foreground: #ffffff;
--vscode-panel-background: #1e1e1e; --vscode-panel-background: #000000;
--vscode-panel-border: #80808059; --vscode-panel-border: #80808059;
--vscode-tab-activeBackground: #1e1e1e; --vscode-tab-activeBackground: #000000;
--vscode-tab-inactiveBackground: #2d2d2d; --vscode-tab-inactiveBackground: #2d2d2d;
--vscode-tab-activeForeground: #ffffff; --vscode-tab-activeForeground: #ffffff;
--vscode-tab-inactiveForeground: #ffffff80; --vscode-tab-inactiveForeground: #ffffff80;
@@ -422,15 +422,36 @@ body {
padding: 8px; padding: 8px;
font-family: monospace; font-family: monospace;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden;
height: calc(100% - 36px); /* Adjust based on your header height */ height: calc(100% - 36px); /* Adjust based on your header height */
background-color: #1e1e1e; background-color: #1e1e1e;
color: #ddd; color: #ddd;
outline: none; /* Remove focus outline */ outline: none; /* Remove focus outline */
scrollbar-width: thin;
scrollbar-color: #555555 #1e1e1e;
}
.panel-terminal::-webkit-scrollbar {
width: 8px;
}
.panel-terminal::-webkit-scrollbar-track {
background: #1e1e1e;
}
.panel-terminal::-webkit-scrollbar-thumb {
background: #555555;
border-radius: 4px;
}
.panel-terminal::-webkit-scrollbar-thumb:hover {
background: #666666;
} }
.panel-terminal .terminal-line { .panel-terminal .terminal-line {
white-space: pre-wrap; white-space: pre-wrap;
margin-bottom: 3px; margin-bottom: 3px;
word-wrap: break-word; /* Ensure long words don't cause horizontal overflow */
} }
.terminal-line { .terminal-line {
@@ -1026,3 +1047,408 @@ body {
.panel-close-btn:hover { .panel-close-btn:hover {
opacity: 1; opacity: 1;
} }
/* Code Challenge Component Styles */
.code-challenge-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: var(--vscode-background);
color: var(--vscode-foreground);
}
.code-challenge-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background-color: var(--vscode-background);
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
}
.code-challenge-header h1 {
margin: 0;
font-size: 25px;
font-weight: 400;
font-weight: bold;
}
.sign-in-btn {
background-color: transparent;
color: var(--vscode-foreground);
border: 1px solid rgba(128, 128, 128, 0.5);
padding: 4px 12px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
.sign-in-btn:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.code-challenge-problem-nav {
padding: 8px 16px;
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
}
.problem-number {
margin: 0;
font-size: 16px;
font-weight: 400;
}
.code-challenge-main {
display: flex;
height: 60vh;
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
}
.problem-tabs {
display: flex;
flex-direction: column;
width: 80px;
border-right: 1px solid rgba(128, 128, 128, 0.35);
}
.problem-tabs button {
padding: 16px;
background-color: transparent;
color: var(--vscode-foreground);
border: none;
text-align: left;
cursor: pointer;
border-bottom: 1px solid rgba(128, 128, 128, 0.2);
}
.problem-tabs button.tab-active {
background-color: var(--vscode-background);
font-weight: 500;
border-left: 2px solid #007acc;
}
.problem-tabs button:hover:not(.tab-active) {
background-color: rgba(255, 255, 255, 0.05);
}
.problem-content {
flex: 1;
overflow-y: auto;
padding: 16px;
border-right: 1px solid rgba(128, 128, 128, 0.35);
width: 50%;
}
.problem-container h1 {
margin-top: 0;
font-size: 22px;
margin-bottom: 16px;
}
.problem-description {
margin-bottom: 20px;
font-size: 14px;
line-height: 1.5;
}
.problem-examples h2 {
font-size: 16px;
margin-top: 24px;
margin-bottom: 12px;
}
.example-box {
background-color: rgba(0, 0, 0, 0.3);
padding: 12px;
border-radius: 6px;
margin-bottom: 12px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 14px;
line-height: 1.5;
}
.editor-section {
width: 50%;
display: flex;
flex-direction: column;
}
.editor-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background-color: #252526;
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
}
.editor-controls {
display: flex;
align-items: center;
}
.language-selector {
background-color: #3c3c3c;
color: #d4d4d4;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 4px 8px;
font-size: 13px;
margin-right: 8px;
}
.auto-btn {
background-color: #3c3c3c;
color: #d4d4d4;
border: none;
border-radius: 4px;
padding: 4px 12px;
font-size: 13px;
cursor: pointer;
}
.auto-selected {
background-color: #4d4d4d;
}
.editor-actions {
display: flex;
gap: 8px;
}
.run-btn {
display: flex;
align-items: center;
gap: 4px;
background-color: #3c3c3c;
color: #d4d4d4;
border: none;
border-radius: 4px;
padding: 4px 12px;
font-size: 13px;
cursor: pointer;
}
.submit-btn {
display: flex;
align-items: center;
gap: 4px;
background-color: #0e639c;
color: #ffffff;
border: none;
border-radius: 4px;
padding: 4px 12px;
font-size: 13px;
cursor: pointer;
}
.run-btn:hover {
background-color: #4d4d4d;
}
.submit-btn:hover {
background-color: #1177bb;
}
.editor-container {
flex: 1;
}
.terminal-section {
flex: 1;
display: flex;
flex-direction: column;
background-color: var(--vscode-panel-background);
padding-bottom: 30px; /* Reduced padding to keep buttons just above footer */
}
.terminal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 12px;
background-color: #252526;
font-size: 13px;
}
.terminal-controls {
display: flex;
gap: 4px;
}
.terminal-btn {
background-color: transparent;
color: #d4d4d4;
border: none;
cursor: pointer;
font-size: 12px;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.terminal-content {
flex: 1;
padding: 8px 12px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 14px;
overflow-y: auto;
overflow-x: hidden;
white-space: pre-wrap;
max-height: calc(100% - 10px); /* Ensure content doesn't expand beyond container */
scrollbar-width: thin;
scrollbar-color: #555555 #1e1e1e;
}
.terminal-content::-webkit-scrollbar {
width: 8px;
}
.terminal-content::-webkit-scrollbar-track {
background: #1e1e1e;
}
.terminal-content::-webkit-scrollbar-thumb {
background: #555555;
border-radius: 4px;
}
.terminal-content::-webkit-scrollbar-thumb:hover {
background: #666666;
}
.terminal-line {
margin-bottom: 4px;
line-height: 1.4;
}
.terminal-line.system {
color: #569cd6;
}
.terminal-line.error {
color: #f48771;
}
.terminal-prompt {
display: flex;
align-items: center;
margin-top: 8px;
}
.prompt-symbol {
color: #569cd6;
margin-right: 8px;
}
.terminal-input {
background-color: transparent;
color: #d4d4d4;
border: none;
outline: none;
flex: 1;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 14px;
}
/* Footer Styles */
.fixed {
position: fixed;
}
.bottom-0 {
bottom: 0;
}
.left-0 {
left: 0;
}
.right-0 {
right: 0;
}
.border-t {
border-top-width: 1px;
border-top-style: solid;
}
.border-slate-200\/40 {
border-color: rgba(226, 232, 240, 0.4);
}
.dark\:border-gray-800\/20 {
border-color: rgba(31, 41, 55, 0.2);
}
.bg-white {
background-color: #ffffff;
}
.dark\:bg-\[\#070c1f\] {
background-color: #000000;
}
.footer-bar {
background-color: #000000;
z-index: 1000;
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.h-7 {
height: 1.75rem;
}
.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}
.text-slate-500 {
color: #64748b;
}
.dark\:text-gray-500 {
color: #6b7280;
}
.text-slate-400 {
color: #94a3b8;
}
.dark\:text-gray-400 {
color: #9ca3af;
}
.text-red-400 {
color: #f87171;
}
.dark\:text-red-500 {
color: #ef4444;
}
.mx-0\.5 {
margin-left: 0.125rem;
margin-right: 0.125rem;
}
/* Make sure the footer appears on top of other elements */
footer {
z-index: 1000;
}

View File

@@ -0,0 +1,74 @@
FROM golang:1.19-alpine AS builder
# Install git and required dependencies
RUN apk update && apk add --no-cache git
# Set working directory
WORKDIR /app
# Copy go mod and sum files
COPY go.mod go.sum* ./
# Download dependencies
RUN go mod download
# Copy source code
COPY . .
# Build the application with optimizations
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-s -w" -o monaco-backend .
# Final stage
FROM alpine:latest
# Install Docker client and supervisor
RUN apk update && apk add --no-cache docker-cli supervisor wget
# Get cloudflared directly from GitHub (more reliable than the tarball)
RUN wget -O cloudflared https://github.com/cloudflare/cloudflared/releases/download/2023.5.0/cloudflared-linux-amd64 && \
chmod +x cloudflared && \
mv cloudflared /usr/local/bin/
# Create directories for cloudflared
RUN mkdir -p /etc/cloudflared
# Copy the certificate file and config
COPY cert.pem /etc/cloudflared/cert.pem
COPY credentials.json /etc/cloudflared/credentials.json
COPY config.json /etc/cloudflared/config.json
# Setup DNS routing for the tunnel (only needs to be done once)
RUN cloudflared tunnel route dns 5d2682ef-0b5b-47e5-b0fa-ad48968ce016 api.ishikabhoyar.tech || echo "DNS routing already set up or failed - continuing anyway"
# Copy the binary from builder
COPY --from=builder /app/monaco-backend /monaco-backend
# Create supervisord config
RUN mkdir -p /etc/supervisor/conf.d/
RUN echo "[supervisord]" > /etc/supervisor/conf.d/supervisord.conf && \
echo "nodaemon=true" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "user=root" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "[program:backend]" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "command=/monaco-backend" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "autostart=true" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "autorestart=true" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "stdout_logfile=/dev/stdout" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "stdout_logfile_maxbytes=0" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "stderr_logfile=/dev/stderr" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "stderr_logfile_maxbytes=0" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "[program:cloudflared]" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "command=cloudflared tunnel --config /etc/cloudflared/config.json run" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "autostart=true" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "autorestart=true" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "stdout_logfile=/dev/stdout" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "stdout_logfile_maxbytes=0" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "stderr_logfile=/dev/stderr" >> /etc/supervisor/conf.d/supervisord.conf && \
echo "stderr_logfile_maxbytes=0" >> /etc/supervisor/conf.d/supervisord.conf
# Expose port for local access
EXPOSE 8080
# Use supervisord to manage processes
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

View File

@@ -0,0 +1,66 @@
FROM golang:1.19-alpine AS builder
# Install git and required dependencies
RUN apk update && apk add --no-cache git
# Set working directory
WORKDIR /app
# Copy go mod and sum files
COPY go.mod go.sum* ./
# Download dependencies
RUN go mod download
# Copy source code
COPY . .
# Build the application with optimizations
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-s -w" -o monaco-backend .
# Final stage
FROM alpine:latest
# Install Docker client and tools
RUN apk update && apk add --no-cache docker-cli bash wget
# Get cloudflared directly
RUN wget -O cloudflared https://github.com/cloudflare/cloudflared/releases/download/2023.7.3/cloudflared-linux-amd64 && \
chmod +x cloudflared && \
mv cloudflared /usr/local/bin/
# Create directories for cloudflared
RUN mkdir -p /etc/cloudflared
# Copy the certificate file and config
COPY cert.pem /etc/cloudflared/cert.pem
COPY config.json /etc/cloudflared/config.json
# Copy the binary from builder
COPY --from=builder /app/monaco-backend /monaco-backend
# Create a simple bash script to run both services
RUN echo '#!/bin/bash\n\
# Start the backend in the background\n\
/monaco-backend &\n\
BACKEND_PID=$!\n\
echo "Backend started with PID: $BACKEND_PID"\n\
\n\
# Wait for backend to start\n\
echo "Waiting for backend to initialize..."\n\
sleep 5\n\
\n\
# Start cloudflared with proper arguments\n\
echo "Starting Cloudflare tunnel to api.ishikabhoyar.tech..."\n\
# Use the specific tunnel name from config.json\n\
cloudflared tunnel run monaco-backend-tunnel\n\
\n\
# If cloudflared exits, kill the backend\n\
kill $BACKEND_PID\n\
' > /start.sh && chmod +x /start.sh
# Expose port for local access
EXPOSE 8080
# Run the startup script
CMD ["/start.sh"]

21
new-backend/Makefile Normal file
View File

@@ -0,0 +1,21 @@
.PHONY: build run run-detached stop logs
# Build the image
build:
docker-compose -f docker-compose.tunnel.yml build
# Run the container
run: build
docker-compose -f docker-compose.tunnel.yml up
# Run in detached mode
run-detached: build
docker-compose -f docker-compose.tunnel.yml up -d
# Stop the container
stop:
docker-compose -f docker-compose.tunnel.yml down
# View logs
logs:
docker-compose -f docker-compose.tunnel.yml logs -f

View File

@@ -0,0 +1,46 @@
# Backend with Cloudflare Tunnel
This setup runs the Monaco backend service and establishes a Cloudflare tunnel, exposing the service to the internet securely via api.ishikabhoyar.tech.
## Prerequisites
- Docker and Docker Compose installed
- The Cloudflare tunnel certificate (cert.pem) in the same directory as the Dockerfile.tunnel
## Files
- `Dockerfile.tunnel`: Dockerfile that builds the backend and sets up Cloudflare tunnel
- `cert.pem`: Cloudflare tunnel certificate
- `config.json`: Cloudflare tunnel configuration that routes traffic to api.ishikabhoyar.tech
- `docker-compose.tunnel.yml`: Docker Compose configuration for easy deployment
## How to Run
```bash
# Build and start the container
docker-compose -f docker-compose.tunnel.yml up -d
# Check logs
docker-compose -f docker-compose.tunnel.yml logs -f
```
## How it Works
1. The Dockerfile builds the Go backend application
2. It installs the Cloudflare tunnel client (cloudflared)
3. On container start:
- The backend server starts on port 8080
- The Cloudflare tunnel connects to Cloudflare's edge network using the config.json
- External traffic to api.ishikabhoyar.tech is routed through the tunnel to the backend
- The cloudflared runs entirely within the container, isolated from any host cloudflared instance
## Environment Variables
You can customize the behavior by modifying the environment variables in the docker-compose.tunnel.yml file:
- `PORT`: The port the backend server listens on (default: 8080)
- `CONCURRENT_EXECUTIONS`: Number of concurrent code executions (default: 5)
- `QUEUE_CAPACITY`: Maximum queue capacity for code executions (default: 100)
- `DEFAULT_TIMEOUT`: Default timeout for code execution in seconds (default: 30)
- `SANDBOX_NETWORK_DISABLED`: Whether to disable network in sandbox containers (default: true)
- `SANDBOX_PIDS_LIMIT`: Process ID limit for sandbox containers (default: 50)

15
new-backend/config.json Normal file
View File

@@ -0,0 +1,15 @@
{
"tunnel": "5d2682ef-0b5b-47e5-b0fa-ad48968ce016",
"credentials-file": "/etc/cloudflared/credentials.json",
"ingress": [
{
"hostname": "api.ishikabhoyar.tech",
"service": "http://localhost:8080"
},
{
"service": "http_status:404"
}
],
"protocol": "http2",
"loglevel": "info"
}

View File

@@ -0,0 +1,13 @@
{
"tunnel": "monaco-backend-tunnel",
"credentials-file": "/etc/cloudflared/cert.pem",
"ingress": [
{
"hostname": "api.ishikabhoyar.tech",
"service": "http://localhost:8080"
},
{
"service": "http_status:404"
}
]
}

View File

@@ -56,11 +56,11 @@ func GetConfig() *Config {
Port: getEnv("PORT", "8080"), Port: getEnv("PORT", "8080"),
ReadTimeout: time.Duration(getEnvAsInt("READ_TIMEOUT", 15)) * time.Second, ReadTimeout: time.Duration(getEnvAsInt("READ_TIMEOUT", 15)) * time.Second,
WriteTimeout: time.Duration(getEnvAsInt("WRITE_TIMEOUT", 15)) * time.Second, WriteTimeout: time.Duration(getEnvAsInt("WRITE_TIMEOUT", 15)) * time.Second,
IdleTimeout: time.Duration(getEnvAsInt("IDLE_TIMEOUT", 60)) * time.Second, IdleTimeout: time.Duration(getEnvAsInt("IDLE_TIMEOUT", 90)) * time.Second,
}, },
Executor: ExecutorConfig{ Executor: ExecutorConfig{
ConcurrentExecutions: getEnvAsInt("CONCURRENT_EXECUTIONS", 5), ConcurrentExecutions: getEnvAsInt("CONCURRENT_EXECUTIONS", 100),
QueueCapacity: getEnvAsInt("QUEUE_CAPACITY", 100), QueueCapacity: getEnvAsInt("QUEUE_CAPACITY", 1000),
DefaultTimeout: time.Duration(getEnvAsInt("DEFAULT_TIMEOUT", 30)) * time.Second, DefaultTimeout: time.Duration(getEnvAsInt("DEFAULT_TIMEOUT", 30)) * time.Second,
}, },
Languages: getLanguageConfigs(), Languages: getLanguageConfigs(),
@@ -80,7 +80,7 @@ func getLanguageConfigs() map[string]LanguageConfig {
Image: "python:3.9-slim", Image: "python:3.9-slim",
MemoryLimit: "100m", MemoryLimit: "100m",
CPULimit: "0.1", CPULimit: "0.1",
TimeoutSec: 30, TimeoutSec: 90,
RunCmd: []string{"python", "-c"}, RunCmd: []string{"python", "-c"},
FileExt: ".py", FileExt: ".py",
VersionCmd: []string{"python", "--version"}, VersionCmd: []string{"python", "--version"},
@@ -90,7 +90,7 @@ func getLanguageConfigs() map[string]LanguageConfig {
Image: "eclipse-temurin:11-jdk", Image: "eclipse-temurin:11-jdk",
MemoryLimit: "400m", MemoryLimit: "400m",
CPULimit: "0.5", CPULimit: "0.5",
TimeoutSec: 60, TimeoutSec: 100,
CompileCmd: []string{"javac"}, CompileCmd: []string{"javac"},
RunCmd: []string{"java"}, RunCmd: []string{"java"},
FileExt: ".java", FileExt: ".java",
@@ -101,7 +101,7 @@ func getLanguageConfigs() map[string]LanguageConfig {
Image: "gcc:latest", Image: "gcc:latest",
MemoryLimit: "100m", MemoryLimit: "100m",
CPULimit: "0.1", CPULimit: "0.1",
TimeoutSec: 30, TimeoutSec: 90,
CompileCmd: []string{"gcc", "-o", "program"}, CompileCmd: []string{"gcc", "-o", "program"},
RunCmd: []string{"./program"}, RunCmd: []string{"./program"},
FileExt: ".c", FileExt: ".c",
@@ -112,7 +112,7 @@ func getLanguageConfigs() map[string]LanguageConfig {
Image: "gcc:latest", Image: "gcc:latest",
MemoryLimit: "100m", MemoryLimit: "100m",
CPULimit: "0.1", CPULimit: "0.1",
TimeoutSec: 30, TimeoutSec: 90,
CompileCmd: []string{"g++", "-o", "program"}, CompileCmd: []string{"g++", "-o", "program"},
RunCmd: []string{"./program"}, RunCmd: []string{"./program"},
FileExt: ".cpp", FileExt: ".cpp",
@@ -123,7 +123,7 @@ func getLanguageConfigs() map[string]LanguageConfig {
Image: "node:16-alpine", Image: "node:16-alpine",
MemoryLimit: "100m", MemoryLimit: "100m",
CPULimit: "0.1", CPULimit: "0.1",
TimeoutSec: 30, TimeoutSec: 90,
RunCmd: []string{"node", "-e"}, RunCmd: []string{"node", "-e"},
FileExt: ".js", FileExt: ".js",
VersionCmd: []string{"node", "--version"}, VersionCmd: []string{"node", "--version"},
@@ -133,7 +133,7 @@ func getLanguageConfigs() map[string]LanguageConfig {
Image: "golang:1.19-alpine", Image: "golang:1.19-alpine",
MemoryLimit: "100m", MemoryLimit: "100m",
CPULimit: "0.1", CPULimit: "0.1",
TimeoutSec: 30, TimeoutSec: 90,
CompileCmd: []string{"go", "build", "-o", "program"}, CompileCmd: []string{"go", "build", "-o", "program"},
RunCmd: []string{"./program"}, RunCmd: []string{"./program"},
FileExt: ".go", FileExt: ".go",

View File

@@ -0,0 +1 @@
{"AccountTag":"453afb9373a00a55876e6127cf0efd97","TunnelSecret":"02VClcBt+1nxxu8ioUzw/UizXtKKh4X7UUpneVbfB/Y=","TunnelID":"5d2682ef-0b5b-47e5-b0fa-ad48968ce016"}

View File

@@ -0,0 +1,28 @@
services:
backend:
build:
context: .
dockerfile: Dockerfile.tunnel
restart: unless-stopped
volumes:
- //var/run/docker.sock:/var/run/docker.sock
# Port is only exposed locally, traffic comes through the tunnel
ports:
- "127.0.0.1:8080:8080"
environment:
- PORT=8080
- CONCURRENT_EXECUTIONS=5
- QUEUE_CAPACITY=100
- DEFAULT_TIMEOUT=30
- SANDBOX_NETWORK_DISABLED=true
- SANDBOX_PIDS_LIMIT=50
# Define cloudflared environment variables
- TUNNEL_ORIGIN_CERT=/etc/cloudflared/cert.pem
- NO_AUTOUPDATE=true
# Isolated network to prevent conflicts with host cloudflared
networks:
- monaco-backend-network
networks:
monaco-backend-network:
driver: bridge

16
new-backend/start.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
# Start the backend
/monaco-backend &
BACKEND_PID=$!
echo "Backend started with PID: $BACKEND_PID"
# Wait for backend to start
echo "Waiting for backend to initialize..."
sleep 5
# Start cloudflared tunnel using config file
echo "Starting Cloudflare tunnel to api.ishikabhoyar.tech..."
cloudflared tunnel --no-autoupdate run --config /etc/cloudflared/config.json
# If cloudflared exits, kill the backend too
kill $BACKEND_PID

View File

@@ -0,0 +1,21 @@
[supervisord]
nodaemon=true
user=root
[program:backend]
command=/monaco-backend
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:cloudflared]
command=cloudflared tunnel --no-autoupdate run --config /etc/cloudflared/config.json
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

29
nginx.conf Normal file
View File

@@ -0,0 +1,29 @@
events {}
http {
include /etc/nginx/mime.types; # <-- ADD THIS LINE
server {
listen 80;
server_name localhost;
# Serve the static frontend files
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# Proxy API requests to the Go backend
location /api/ {
proxy_pass http://localhost:8081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

26
nginx/nginx.conf Normal file
View File

@@ -0,0 +1,26 @@
events {}
http {
include /etc/nginx/mime.types; # <-- ADD THIS LINE
server {
listen 80;
server_name localhost;
# Serve the static frontend files
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# Proxy API requests to the Go backend
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}