Rémy commited on
Commit
f716e02
1 Parent(s): 3c64ab0

Add Linux Practice Tool files

Browse files
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
+
12
+ COPY --chown=user . /app
13
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from pydantic import BaseModel
4
+ import subprocess
5
+ import shlex
6
+
7
+ app = FastAPI()
8
+
9
+ # Add CORS middleware
10
+ app.add_middleware(
11
+ CORSMiddleware,
12
+ allow_origins=["*"], # Allows all origins
13
+ allow_credentials=True,
14
+ allow_methods=["*"], # Allows all methods
15
+ allow_headers=["*"], # Allows all headers
16
+ )
17
+
18
+ ALLOWED_COMMANDS = {
19
+ 'ls', 'cd', 'pwd', 'echo', 'cat', 'grep', 'find', 'touch', 'mkdir', 'rm', 'cp', 'mv'
20
+ }
21
+
22
+ class Command(BaseModel):
23
+ command: str
24
+
25
+ @app.post("/execute")
26
+ async def execute_command(command: Command):
27
+ try:
28
+ # Parse the command to get the base command
29
+ base_command = shlex.split(command.command)[0]
30
+
31
+ # Check if the base command is allowed
32
+ if base_command not in ALLOWED_COMMANDS:
33
+ raise HTTPException(status_code=403, detail=f"Command '{base_command}' is not allowed")
34
+
35
+ # Execute the command in a controlled environment
36
+ result = subprocess.run(command.command, shell=True, capture_output=True, text=True, timeout=5)
37
+ return {
38
+ "output": result.stdout,
39
+ "error": result.stderr,
40
+ "returncode": result.returncode
41
+ }
42
+ except subprocess.TimeoutExpired:
43
+ raise HTTPException(status_code=408, detail="Command execution timed out")
44
+ except Exception as e:
45
+ raise HTTPException(status_code=500, detail=str(e))
46
+
47
+ @app.get("/")
48
+ async def root():
49
+ return {"message": "Welcome to the Linux Practice Tool API"}
backend/Dockerfile ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY app.py .
9
+
10
+ CMD ["python", "app.py"]
backend/app.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ from flask_cors import CORS
3
+ import subprocess
4
+ import shlex
5
+
6
+ app = Flask(__name__)
7
+ CORS(app)
8
+
9
+ ALLOWED_COMMANDS = {
10
+ 'ls', 'cd', 'pwd', 'echo', 'cat', 'grep', 'find', 'touch', 'mkdir', 'rm', 'cp', 'mv'
11
+ }
12
+
13
+ @app.route('/execute', methods=['POST'])
14
+ def execute_command():
15
+ command = request.json['command']
16
+ try:
17
+ # Parse the command to get the base command
18
+ base_command = shlex.split(command)[0]
19
+
20
+ # Check if the base command is allowed
21
+ if base_command not in ALLOWED_COMMANDS:
22
+ return jsonify({'error': f"Command '{base_command}' is not allowed"}), 403
23
+
24
+ # Execute the command in a controlled environment
25
+ result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=5)
26
+ return jsonify({
27
+ 'output': result.stdout,
28
+ 'error': result.stderr,
29
+ 'returncode': result.returncode
30
+ })
31
+ except subprocess.TimeoutExpired:
32
+ return jsonify({'error': 'Command execution timed out'}), 408
33
+ except Exception as e:
34
+ return jsonify({'error': str(e)}), 500
35
+
36
+ if __name__ == '__main__':
37
+ app.run(host='0.0.0.0', port=5000)
backend/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ flask==2.0.1
2
+ flask-cors==3.0.10
frontend/index.html ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Linux Practice Tool</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ </head>
9
+ <body>
10
+ <div id="terminal">
11
+ <div id="output"></div>
12
+ <div id="input-line">
13
+ <span id="prompt">$</span>
14
+ <input type="text" id="command-input" autofocus>
15
+ </div>
16
+ </div>
17
+ <script src="script.js"></script>
18
+ </body>
19
+ </html>
frontend/script.js ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const output = document.getElementById('output');
2
+ const input = document.getElementById('command-input');
3
+
4
+ let commandHistory = [];
5
+ let historyIndex = -1;
6
+
7
+ input.addEventListener('keydown', async (event) => {
8
+ if (event.key === 'Enter') {
9
+ event.preventDefault();
10
+ const command = input.value.trim();
11
+ if (command) {
12
+ commandHistory.push(command);
13
+ historyIndex = commandHistory.length;
14
+ await executeCommand(command);
15
+ }
16
+ input.value = '';
17
+ } else if (event.key === 'ArrowUp') {
18
+ event.preventDefault();
19
+ if (historyIndex > 0) {
20
+ historyIndex--;
21
+ input.value = commandHistory[historyIndex];
22
+ }
23
+ } else if (event.key === 'ArrowDown') {
24
+ event.preventDefault();
25
+ if (historyIndex < commandHistory.length - 1) {
26
+ historyIndex++;
27
+ input.value = commandHistory[historyIndex];
28
+ } else {
29
+ historyIndex = commandHistory.length;
30
+ input.value = '';
31
+ }
32
+ }
33
+ });
34
+
35
+ async function executeCommand(command) {
36
+ output.innerHTML += `$ ${command}\n`;
37
+
38
+ try {
39
+ const response = await fetch('http://localhost:5000/execute', {
40
+ method: 'POST',
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ },
44
+ body: JSON.stringify({ command }),
45
+ });
46
+
47
+ const data = await response.json();
48
+
49
+ if (response.ok) {
50
+ output.innerHTML += data.output;
51
+ if (data.error) {
52
+ output.innerHTML += `Error: ${data.error}\n`;
53
+ }
54
+ } else {
55
+ output.innerHTML += `Error: ${data.error}\n`;
56
+ }
57
+ } catch (error) {
58
+ output.innerHTML += `Error: ${error.message}\n`;
59
+ }
60
+
61
+ output.scrollTop = output.scrollHeight;
62
+ }
63
+
64
+ // Focus on input when clicking anywhere in the terminal
65
+ document.getElementById('terminal').addEventListener('click', () => {
66
+ input.focus();
67
+ });
68
+
69
+ // Initial welcome message
70
+ output.innerHTML = "Welcome to the Linux Practice Tool!\nType 'help' for a list of available commands.\n\n";
frontend/styles.css ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: monospace;
3
+ background-color: #000;
4
+ color: #0f0;
5
+ margin: 0;
6
+ padding: 20px;
7
+ }
8
+
9
+ #terminal {
10
+ width: 100%;
11
+ height: 100vh;
12
+ overflow-y: auto;
13
+ }
14
+
15
+ #output {
16
+ white-space: pre-wrap;
17
+ }
18
+
19
+ #input-line {
20
+ display: flex;
21
+ margin-top: 10px;
22
+ }
23
+
24
+ #prompt {
25
+ margin-right: 5px;
26
+ }
27
+
28
+ #command-input {
29
+ flex-grow: 1;
30
+ background-color: transparent;
31
+ border: none;
32
+ color: #0f0;
33
+ font-family: inherit;
34
+ font-size: inherit;
35
+ outline: none;
36
+ }
index.html ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
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%;
17
+ height: 100vh;
18
+ overflow-y: auto;
19
+ }
20
+ #output {
21
+ white-space: pre-wrap;
22
+ }
23
+ #input-line {
24
+ display: flex;
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>
42
+ <div id="terminal">
43
+ <div id="output"></div>
44
+ <div id="input-line">
45
+ <span id="prompt">$</span>
46
+ <input type="text" id="command-input" autofocus>
47
+ </div>
48
+ </div>
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') {
58
+ event.preventDefault();
59
+ const command = input.value.trim();
60
+ if (command) {
61
+ commandHistory.push(command);
62
+ historyIndex = commandHistory.length;
63
+ await executeCommand(command);
64
+ }
65
+ input.value = '';
66
+ } else if (event.key === 'ArrowUp') {
67
+ event.preventDefault();
68
+ if (historyIndex > 0) {
69
+ historyIndex--;
70
+ input.value = commandHistory[historyIndex];
71
+ }
72
+ } else if (event.key === 'ArrowDown') {
73
+ event.preventDefault();
74
+ if (historyIndex < commandHistory.length - 1) {
75
+ historyIndex++;
76
+ input.value = commandHistory[historyIndex];
77
+ } else {
78
+ historyIndex = commandHistory.length;
79
+ input.value = '';
80
+ }
81
+ }
82
+ });
83
+
84
+ async function executeCommand(command) {
85
+ if (command.toLowerCase() === 'help') {
86
+ displayHelp();
87
+ } else {
88
+ output.innerHTML += `$ ${command}\n`;
89
+
90
+ try {
91
+ const response = await fetch('/execute', {
92
+ method: 'POST',
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:
120
+ - ls: List directory contents
121
+ - cd: Change directory
122
+ - pwd: Print working directory
123
+ - echo: Display a line of text
124
+ - cat: Concatenate files and print on the standard output
125
+ - grep: Print lines that match patterns
126
+ - find: Search for files in a directory hierarchy
127
+ - touch: Change file timestamps
128
+ - mkdir: Make directories
129
+ - rm: Remove files or directories
130
+ - cp: Copy files and directories
131
+ - mv: Move (rename) files
132
+
133
+ Type 'help' to see this message again.
134
+ `;
135
+ output.innerHTML += helpText;
136
+ }
137
+
138
+ // Focus on input when clicking anywhere in the terminal
139
+ document.getElementById('terminal').addEventListener('click', () => {
140
+ input.focus();
141
+ });
142
+
143
+ // Initial welcome message
144
+ output.innerHTML = "Welcome to the Linux Practice Tool!\nType 'help' for a list of available commands.\n\n";
145
+ </script>
146
+ </body>
147
+ </html>
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastapi
2
+ uvicorn[standard]