Rémy
commited on
Commit
•
0694075
1
Parent(s):
d45df14
Improvement
Browse files- app.py +24 -6
- static/index.html +35 -12
app.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
-
from fastapi import FastAPI, HTTPException
|
2 |
from fastapi.middleware.cors import CORSMiddleware
|
3 |
from fastapi.staticfiles import StaticFiles
|
4 |
from fastapi.responses import FileResponse
|
5 |
from pydantic import BaseModel
|
6 |
import subprocess
|
7 |
import shlex
|
8 |
-
|
|
|
9 |
app = FastAPI()
|
10 |
|
11 |
# Add CORS middleware
|
@@ -28,8 +28,16 @@ class Command(BaseModel):
|
|
28 |
command: str
|
29 |
|
30 |
@app.post("/execute")
|
31 |
-
async def execute_command(command: Command):
|
32 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
# Parse the command to get the base command
|
34 |
base_command = shlex.split(command.command)[0]
|
35 |
|
@@ -37,12 +45,22 @@ async def execute_command(command: Command):
|
|
37 |
if base_command not in ALLOWED_COMMANDS:
|
38 |
raise HTTPException(status_code=403, detail=f"Command '{base_command}' is not allowed")
|
39 |
|
40 |
-
#
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
return {
|
43 |
"output": result.stdout,
|
44 |
"error": result.stderr,
|
45 |
-
"returncode": result.returncode
|
|
|
46 |
}
|
47 |
except subprocess.TimeoutExpired:
|
48 |
raise HTTPException(status_code=408, detail="Command execution timed out")
|
|
|
|
|
1 |
from fastapi.middleware.cors import CORSMiddleware
|
2 |
from fastapi.staticfiles import StaticFiles
|
3 |
from fastapi.responses import FileResponse
|
4 |
from pydantic import BaseModel
|
5 |
import subprocess
|
6 |
import shlex
|
7 |
+
import os
|
8 |
+
from fastapi import FastAPI, HTTPException, Request
|
9 |
app = FastAPI()
|
10 |
|
11 |
# Add CORS middleware
|
|
|
28 |
command: str
|
29 |
|
30 |
@app.post("/execute")
|
31 |
+
async def execute_command(command: Command, request: Request):
|
32 |
try:
|
33 |
+
# Get or create a session-specific directory
|
34 |
+
session = request.session
|
35 |
+
if 'working_dir' not in session:
|
36 |
+
session['working_dir'] = '/tmp/user_' + os.urandom(8).hex()
|
37 |
+
os.makedirs(session['working_dir'], exist_ok=True)
|
38 |
+
|
39 |
+
working_dir = session['working_dir']
|
40 |
+
|
41 |
# Parse the command to get the base command
|
42 |
base_command = shlex.split(command.command)[0]
|
43 |
|
|
|
45 |
if base_command not in ALLOWED_COMMANDS:
|
46 |
raise HTTPException(status_code=403, detail=f"Command '{base_command}' is not allowed")
|
47 |
|
48 |
+
# Handle 'cd' command specially
|
49 |
+
if base_command == 'cd':
|
50 |
+
new_dir = os.path.join(working_dir, shlex.split(command.command)[1])
|
51 |
+
if os.path.isdir(new_dir):
|
52 |
+
session['working_dir'] = new_dir
|
53 |
+
return {"output": "", "error": "", "returncode": 0, "currentDirectory": new_dir}
|
54 |
+
else:
|
55 |
+
return {"output": "", "error": "No such directory", "returncode": 1, "currentDirectory": working_dir}
|
56 |
+
|
57 |
+
# Execute the command in the controlled environment
|
58 |
+
result = subprocess.run(command.command, shell=True, capture_output=True, text=True, timeout=5, cwd=working_dir)
|
59 |
return {
|
60 |
"output": result.stdout,
|
61 |
"error": result.stderr,
|
62 |
+
"returncode": result.returncode,
|
63 |
+
"currentDirectory": working_dir
|
64 |
}
|
65 |
except subprocess.TimeoutExpired:
|
66 |
raise HTTPException(status_code=408, detail="Command execution timed out")
|
static/index.html
CHANGED
@@ -6,11 +6,11 @@
|
|
6 |
<title>Linux Practice Tool</title>
|
7 |
<style>
|
8 |
body {
|
9 |
-
font-family: monospace;
|
10 |
-
background-color: #
|
11 |
-
color: #
|
12 |
margin: 0;
|
13 |
-
padding:
|
14 |
}
|
15 |
#terminal {
|
16 |
width: 100%;
|
@@ -25,17 +25,23 @@
|
|
25 |
margin-top: 10px;
|
26 |
}
|
27 |
#prompt {
|
|
|
28 |
margin-right: 5px;
|
29 |
}
|
30 |
#command-input {
|
31 |
flex-grow: 1;
|
32 |
background-color: transparent;
|
33 |
border: none;
|
34 |
-
color: #
|
35 |
font-family: inherit;
|
36 |
font-size: inherit;
|
37 |
outline: none;
|
38 |
}
|
|
|
|
|
|
|
|
|
|
|
39 |
</style>
|
40 |
</head>
|
41 |
<body>
|
@@ -49,9 +55,9 @@
|
|
49 |
<script>
|
50 |
const output = document.getElementById('output');
|
51 |
const input = document.getElementById('command-input');
|
52 |
-
|
53 |
let commandHistory = [];
|
54 |
let historyIndex = -1;
|
|
|
55 |
|
56 |
input.addEventListener('keydown', async (event) => {
|
57 |
if (event.key === 'Enter') {
|
@@ -85,7 +91,7 @@
|
|
85 |
if (command.toLowerCase() === 'help') {
|
86 |
displayHelp();
|
87 |
} else {
|
88 |
-
output.innerHTML +=
|
89 |
|
90 |
try {
|
91 |
const response = await fetch('/execute', {
|
@@ -93,27 +99,44 @@
|
|
93 |
headers: {
|
94 |
'Content-Type': 'application/json',
|
95 |
},
|
96 |
-
body: JSON.stringify({ command }),
|
97 |
});
|
98 |
|
99 |
const data = await response.json();
|
100 |
|
101 |
if (response.ok) {
|
102 |
-
|
|
|
|
|
|
|
103 |
if (data.error) {
|
104 |
-
output.innerHTML +=
|
105 |
}
|
106 |
} else {
|
107 |
-
output.innerHTML +=
|
108 |
}
|
109 |
} catch (error) {
|
110 |
-
output.innerHTML +=
|
111 |
}
|
112 |
}
|
113 |
|
114 |
output.scrollTop = output.scrollHeight;
|
115 |
}
|
116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
function displayHelp() {
|
118 |
const helpText = `
|
119 |
Available commands:
|
|
|
6 |
<title>Linux Practice Tool</title>
|
7 |
<style>
|
8 |
body {
|
9 |
+
font-family: 'Courier New', monospace;
|
10 |
+
background-color: #300a24;
|
11 |
+
color: #ffffff;
|
12 |
margin: 0;
|
13 |
+
padding: 10px;
|
14 |
}
|
15 |
#terminal {
|
16 |
width: 100%;
|
|
|
25 |
margin-top: 10px;
|
26 |
}
|
27 |
#prompt {
|
28 |
+
color: #4e9a06;
|
29 |
margin-right: 5px;
|
30 |
}
|
31 |
#command-input {
|
32 |
flex-grow: 1;
|
33 |
background-color: transparent;
|
34 |
border: none;
|
35 |
+
color: #ffffff;
|
36 |
font-family: inherit;
|
37 |
font-size: inherit;
|
38 |
outline: none;
|
39 |
}
|
40 |
+
.directory { color: #3465a4; }
|
41 |
+
.executable { color: #4e9a06; }
|
42 |
+
.image { color: #75507b; }
|
43 |
+
.archive { color: #c4a000; }
|
44 |
+
.text { color: #cc0000; }
|
45 |
</style>
|
46 |
</head>
|
47 |
<body>
|
|
|
55 |
<script>
|
56 |
const output = document.getElementById('output');
|
57 |
const input = document.getElementById('command-input');
|
|
|
58 |
let commandHistory = [];
|
59 |
let historyIndex = -1;
|
60 |
+
let currentDirectory = '/';
|
61 |
|
62 |
input.addEventListener('keydown', async (event) => {
|
63 |
if (event.key === 'Enter') {
|
|
|
91 |
if (command.toLowerCase() === 'help') {
|
92 |
displayHelp();
|
93 |
} else {
|
94 |
+
output.innerHTML += `<span id="prompt">$</span> ${command}\n`;
|
95 |
|
96 |
try {
|
97 |
const response = await fetch('/execute', {
|
|
|
99 |
headers: {
|
100 |
'Content-Type': 'application/json',
|
101 |
},
|
102 |
+
body: JSON.stringify({ command, currentDirectory }),
|
103 |
});
|
104 |
|
105 |
const data = await response.json();
|
106 |
|
107 |
if (response.ok) {
|
108 |
+
if (command.startsWith('cd ')) {
|
109 |
+
currentDirectory = data.currentDirectory;
|
110 |
+
}
|
111 |
+
output.innerHTML += colorize(data.output);
|
112 |
if (data.error) {
|
113 |
+
output.innerHTML += `<span style="color: #cc0000;">Error: ${data.error}</span>\n`;
|
114 |
}
|
115 |
} else {
|
116 |
+
output.innerHTML += `<span style="color: #cc0000;">Error: ${data.detail}</span>\n`;
|
117 |
}
|
118 |
} catch (error) {
|
119 |
+
output.innerHTML += `<span style="color: #cc0000;">Error: ${error.message}</span>\n`;
|
120 |
}
|
121 |
}
|
122 |
|
123 |
output.scrollTop = output.scrollHeight;
|
124 |
}
|
125 |
|
126 |
+
function colorize(text) {
|
127 |
+
const lines = text.split('\n');
|
128 |
+
return lines.map(line => {
|
129 |
+
return line.replace(/(\S+)/g, (match) => {
|
130 |
+
if (match.endsWith('/')) return `<span class="directory">${match}</span>`;
|
131 |
+
if (match.endsWith('.exe') || match.endsWith('.sh')) return `<span class="executable">${match}</span>`;
|
132 |
+
if (match.match(/\.(jpg|jpeg|png|gif|bmp)$/i)) return `<span class="image">${match}</span>`;
|
133 |
+
if (match.match(/\.(zip|tar|gz|rar)$/i)) return `<span class="archive">${match}</span>`;
|
134 |
+
if (match.match(/\.(txt|md|log)$/i)) return `<span class="text">${match}</span>`;
|
135 |
+
return match;
|
136 |
+
});
|
137 |
+
}).join('\n');
|
138 |
+
}
|
139 |
+
|
140 |
function displayHelp() {
|
141 |
const helpText = `
|
142 |
Available commands:
|