Rémy commited on
Commit
0694075
1 Parent(s): d45df14

Improvement

Browse files
Files changed (2) hide show
  1. app.py +24 -6
  2. 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
- # Execute the command in a controlled environment
41
- result = subprocess.run(command.command, shell=True, capture_output=True, text=True, timeout=5)
 
 
 
 
 
 
 
 
 
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: #000;
11
- color: #0f0;
12
  margin: 0;
13
- padding: 20px;
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: #0f0;
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 += `$ ${command}\n`;
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
- output.innerHTML += data.output;
 
 
 
103
  if (data.error) {
104
- output.innerHTML += `Error: ${data.error}\n`;
105
  }
106
  } else {
107
- output.innerHTML += `Error: ${data.detail}\n`;
108
  }
109
  } catch (error) {
110
- output.innerHTML += `Error: ${error.message}\n`;
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: