Aleksmorshen commited on
Commit
0ecac03
·
verified ·
1 Parent(s): 31b5ef8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +262 -219
app.py CHANGED
@@ -27,55 +27,84 @@ HTML_TEMPLATE = """
27
  <html lang="ru">
28
  <head>
29
  <meta charset="UTF-8">
30
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
31
- <title>Панель управления Android</title>
32
  <style>
33
- body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f0f2f5; color: #333; display: flex; min-height: 100vh; }
34
- .sidebar { width: 250px; background-color: #333; color: white; padding: 20px; box-sizing: border-box; display: flex; flex-direction: column; }
35
- .sidebar h2 { margin-top: 0; font-size: 1.2em; border-bottom: 1px solid #555; padding-bottom: 10px; }
 
 
 
 
 
36
  .sidebar ul { list-style: none; padding: 0; margin: 0; }
37
- .sidebar ul li a { color: #ddd; text-decoration: none; display: block; padding: 10px; border-radius: 4px; margin-bottom: 5px; }
38
  .sidebar ul li a:hover, .sidebar ul li a.active { background-color: #555; color: white; }
39
- .content { flex-grow: 1; padding: 20px; box-sizing: border-box; }
40
- .container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom:20px; }
41
- h1 { color: #333; text-align: center; margin-bottom: 20px;}
42
- .control-section { margin-bottom: 20px; padding: 15px; border: 1px solid #e0e0e0; border-radius: 6px; background-color: #f9f9f9; }
43
- label { display: block; margin-bottom: 8px; font-weight: bold; color: #555; }
44
- input[type="text"], textarea, input[type="file"], input[type="number"] { width: calc(100% - 24px); padding: 10px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
45
- button { padding: 10px 18px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.95em; margin-right: 5px; margin-bottom: 5px;}
 
46
  button:hover { background-color: #0056b3; }
47
- pre { background-color: #282c34; color: #abb2bf; padding: 15px; border-radius: 4px; white-space: pre-wrap; word-wrap: break-word; max-height: 350px; overflow-y: auto; font-family: 'Courier New', Courier, monospace;}
48
- .status { padding: 12px; border-radius: 4px; margin-bottom:15px; font-weight: bold; }
49
  .status.online { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
50
  .status.offline { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
51
  .file-browser ul { list-style: none; padding: 0; }
52
- .file-browser li { padding: 8px 0; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
 
 
53
  .file-browser li:last-child { border-bottom: none; }
54
  .file-browser a { text-decoration: none; color: #007bff; }
55
  .file-browser a:hover { text-decoration: underline; }
56
  .file-icon { margin-right: 8px; }
57
  .file-browser .dir a { font-weight: bold; }
58
- .file-browser .download-btn { font-size: 0.8em; padding: 3px 8px; background-color: #28a745; }
59
  .file-browser .download-btn:hover { background-color: #218838; }
 
 
60
  .hidden-section { display: none; }
61
- .status-item { margin-bottom: 10px; }
62
  .status-item strong { color: #333; }
63
- .list-container { max-height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background-color: #fdfdfd;}
64
- .list-item { border-bottom: 1px solid #eee; padding: 8px 0; margin-bottom: 8px; }
65
- .list-item:last-child { border-bottom: none; }
66
- .list-item strong { display: block; color: #555; }
67
- .list-item span { font-size: 0.9em; color: #777; }
68
- .list-item p {margin: 5px 0;}
 
 
 
 
 
 
 
 
69
  </style>
70
  <script>
71
  let currentView = 'dashboard';
72
 
 
 
 
 
 
73
  function showSection(sectionId) {
74
  document.querySelectorAll('.content > div.container').forEach(div => div.style.display = 'none');
75
  document.getElementById(sectionId).style.display = 'block';
76
  document.querySelectorAll('.sidebar a').forEach(a => a.classList.remove('active'));
77
- document.querySelector(`.sidebar a[href="#${sectionId}"]`).classList.add('active');
 
78
  currentView = sectionId;
 
 
 
 
 
79
  if (sectionId === 'files') refreshClientPathDisplay();
80
  if (sectionId === 'device_status') requestDeviceStatus();
81
  if (sectionId === 'notifications') requestNotifications();
@@ -142,7 +171,7 @@ HTML_TEMPLATE = """
142
  if (data.notifications && currentView === 'notifications') {
143
  renderNotifications(data.notifications);
144
  }
145
- if (data.contacts && currentView === 'contacts') {
146
  renderContacts(data.contacts);
147
  }
148
 
@@ -192,7 +221,8 @@ HTML_TEMPLATE = """
192
  const name = parts[2].trim();
193
 
194
  const li = document.createElement('li');
195
- const nameSpan = document.createElement('span');
 
196
  const a = document.createElement('a');
197
  a.href = '#';
198
 
@@ -200,21 +230,32 @@ HTML_TEMPLATE = """
200
  li.className = 'dir';
201
  a.innerHTML = `<span class="file-icon">📁</span> ${name}`;
202
  a.onclick = (e) => { e.preventDefault(); navigateTo(name); };
203
- nameSpan.appendChild(a);
 
204
  } else {
205
  li.className = 'file';
206
  a.innerHTML = `<span class="file-icon">📄</span> ${name}`;
207
  a.onclick = (e) => { e.preventDefault(); };
208
- nameSpan.appendChild(a);
 
 
 
 
209
 
210
  const downloadBtn = document.createElement('button');
211
  downloadBtn.className = 'download-btn';
212
  downloadBtn.textContent = 'Скачать';
213
  downloadBtn.onclick = (e) => { e.preventDefault(); requestDownloadFile(name); };
214
- li.appendChild(nameSpan);
215
- li.appendChild(downloadBtn);
 
 
 
 
 
 
 
216
  }
217
- if (type === '[D]') li.appendChild(nameSpan);
218
  fileListUl.appendChild(li);
219
  });
220
  }
@@ -225,7 +266,7 @@ HTML_TEMPLATE = """
225
  if (notifications && notifications.length > 0) {
226
  notifications.forEach(n => {
227
  const itemDiv = document.createElement('div');
228
- itemDiv.className = 'list-item';
229
  itemDiv.innerHTML = `
230
  <strong>${n.title || 'Без заголовка'} (ID: ${n.id || 'N/A'})</strong>
231
  <span><strong>Приложение:</strong> ${n.packageName || 'N/A'}</span>
@@ -243,19 +284,21 @@ HTML_TEMPLATE = """
243
  function renderContacts(contacts) {
244
  const contactListDiv = document.getElementById('contactList');
245
  contactListDiv.innerHTML = '';
246
- if (contacts && contacts.length > 0) {
247
  contacts.forEach(c => {
248
  const itemDiv = document.createElement('div');
249
- itemDiv.className = 'list-item';
250
- // Termux API returns "name: number"
251
- // We split it for better display
252
- const parts = c.split(':');
253
- const name = parts.length > 1 ? parts[0].trim() : "Без имени";
254
- const number = parts.length > 1 ? parts.slice(1).join(':').trim() : c.trim();
255
-
 
 
256
  itemDiv.innerHTML = `
257
- <strong>${name}</strong>
258
- <span>${number}</span>
259
  `;
260
  contactListDiv.appendChild(itemDiv);
261
  });
@@ -303,6 +346,12 @@ HTML_TEMPLATE = """
303
  sendGenericCommand({ command_type: 'request_download_file', filename: filename });
304
  document.getElementById('outputArea').innerText = `Запрос на скачивание файла ${filename}... Ожидайте появления в разделе "Загрузки с клиента".`;
305
  }
 
 
 
 
 
 
306
 
307
  function refreshClientPathDisplay(){
308
  if (document.getElementById('currentPathDisplay')) {
@@ -316,6 +365,9 @@ HTML_TEMPLATE = """
316
  showSection('dashboard');
317
  setInterval(refreshOutput, 4000);
318
  refreshOutput();
 
 
 
319
  };
320
 
321
  function submitShellCommand(event) {
@@ -325,15 +377,11 @@ HTML_TEMPLATE = """
325
  document.getElementById('command_str').value = '';
326
  }
327
 
328
- function submitMediaCommand(type, param1Name, param1ValueId, param2Name, param2ValueId) {
329
  let payload = { command_type: type };
330
- if (param1Name && param1ValueId) {
331
- const value = document.getElementById(param1ValueId).value;
332
- if (value) payload[param1Name] = value;
333
- }
334
- if (param2Name && param2ValueId) { // For second parameter (e.g., video duration)
335
- const value = document.getElementById(param2ValueId).value;
336
- if (value) payload[param2Name] = value;
337
  }
338
  sendGenericCommand(payload);
339
  }
@@ -412,186 +460,182 @@ HTML_TEMPLATE = """
412
  </script>
413
  </head>
414
  <body>
415
- <div class="sidebar">
416
- <h2>Меню</h2>
417
- <ul>
418
- <li><a href="#dashboard" onclick="showSection('dashboard')" class="active">Панель</a></li>
419
- <li><a href="#device_status" onclick="showSection('device_status')">Статус устройства</a></li>
420
- <li><a href="#notifications" onclick="showSection('notifications')">Уведомления</a></li>
421
- <li><a href="#contacts" onclick="showSection('contacts')">Контакты</a></li>
422
- <li><a href="#files" onclick="showSection('files')">Файлы</a></li>
423
- <li><a href="#shell" onclick="showSection('shell')">Терминал</a></li>
424
- <li><a href="#media" onclick="showSection('media')">Медиа</a></li>
425
- <li><a href="#clipboard" onclick="showSection('clipboard')">Буфер обмена</a></li>
426
- <li><a href="#utils" onclick="showSection('utils')">Утилиты</a></li>
427
- <li><a href="#uploads" onclick="showSection('uploads')">Загрузки с клиента</a></li>
428
- </ul>
429
- <div style="margin-top: auto; font-size: 0.8em; color: #aaa; text-align:center; padding-bottom:10px;">
430
- Remote Control Panel
 
 
 
431
  </div>
432
- </div>
433
-
434
- <div class="content">
435
- <h1>Панель управления Android</h1>
436
- <div id="clientStatus" class="status offline">Статус клиента неизвестен</div>
437
-
438
- <div id="dashboard" class="container">
439
- <h2>Общая информация</h2>
440
- <p>Добро пожаловать в панель управления. Выберите действие из меню слева.</p>
441
- <div class="control-section">
442
- <h3>Вывод последней операции:</h3>
443
- <pre id="outputArea">Ожидание вывода...</pre>
444
  </div>
445
- </div>
446
 
447
- <div id="device_status" class="container hidden-section">
448
- <h2>Статус устройства</h2>
449
- <button onclick="requestDeviceStatus()">Обновить все</button>
450
- <div class="control-section">
451
- <h3>Батарея</h3>
452
- <div id="batteryStatus" class="status-item"><strong>Заряд:</strong> Н/Д</div>
453
- <button onclick="requestDeviceStatus('battery')">Обновить батарею</button>
454
- </div>
455
- <div class="control-section">
456
- <h3>Геолокация</h3>
457
- <div id="locationStatus" class="status-item"><strong>Локация:</strong> Н/Д</div>
458
- <div id="locationMapLink" class="status-item"></div>
459
- <button onclick="requestDeviceStatus('location')">Обновить геолокацию</button>
 
 
 
 
 
 
460
  </div>
461
- <div class="control-section">
462
- <h3>Запущенные процессы (пользователя Termux)</h3>
463
- <div id="processesStatus" class="status-item"><strong>Процессы:</strong> Н/Д</div>
464
- <button onclick="requestDeviceStatus('processes')">Обновить процессы</button>
 
 
 
 
 
 
465
  </div>
466
- </div>
467
-
468
- <div id="notifications" class="container hidden-section">
469
- <h2>Уведомления устройства</h2>
470
- <button onclick="requestNotifications()">Обновить уведомления</button>
471
- <div class="control-section">
472
- <h3>Список уведомлений:</h3>
473
- <div id="notificationList" class="list-container">
474
- <p>Запросите список уведомлений.</p>
475
  </div>
476
  </div>
477
- </div>
478
 
479
- <div id="contacts" class="container hidden-section">
480
- <h2>Контакты</h2>
481
- <button onclick="requestContacts()">Запросить список контактов</button>
482
- <div class="control-section">
483
- <h3>Список контактов:</h3>
484
- <div id="contactList" class="list-container">
485
- <p>Запросите список контактов.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  </div>
487
  </div>
488
- </div>
489
 
490
- <div id="files" class="container hidden-section">
491
- <h2>Файловый менеджер (Клиент)</h2>
492
- <p>Текущий путь на клиенте: <strong id="currentPathDisplay">~</strong></p>
493
- <button onclick="navigateTo('~')">Домашняя папка (~)</button>
494
- <button onclick="navigateTo('/sdcard/')">Внутренняя память (/sdcard/)</button>
495
- <button onclick="navigateTo(document.getElementById('customPathInput').value)">Перейти к пути:</button>
496
- <input type="text" id="customPathInput" placeholder="/sdcard/Download" style="width:auto; display:inline-block; margin-left:5px;">
497
- <div class="file-browser control-section">
498
- <h3>Содержимое директории:</h3>
499
- <ul id="fileList">
500
- <li>Запросите содержимое директории.</li>
501
- </ul>
502
- </div>
503
- <div class="control-section">
504
- <h3>Загрузить файл НА устройство</h3>
505
- <form id="uploadForm" onsubmit="handleUploadToServer(event)">
506
- <label for="fileToUploadToDevice">Выберите файл:</label>
507
- <input type="file" id="fileToUploadToDevice" name="file_to_device" required>
508
- <label for="targetDevicePath">Путь для сохранения на устройстве (например, `/sdcard/Download/` или `~/`):</label>
509
- <input type="text" id="targetDevicePath" name="target_path_on_device" value="/sdcard/Download/" required>
510
- <button type="submit">Загрузить на устройство</button>
511
  </form>
512
- <p id="uploadToDeviceStatus"></p>
 
 
 
513
  </div>
514
- </div>
515
 
516
- <div id="shell" class="container hidden-section">
517
- <h2>В��полнить команду на устройстве</h2>
518
- <form onsubmit="submitShellCommand(event)">
519
- <label for="command_str">Команда (например, `ls -l /sdcard/Download` или `pwd`):</label>
520
- <input type="text" id="command_str" name="command_str" required>
521
- <button type="submit">Отправить</button>
522
- </form>
523
- <div class="control-section" style="margin-top:20px;">
524
- <h3>Вывод команды:</h3>
525
- <pre id="outputAreaShellCopy"></pre>
526
- </div>
527
- </div>
528
-
529
- <div id="media" class="container hidden-section">
530
- <h2>Мультимедиа</h2>
531
- <div class="control-section">
532
- <button onclick="submitMediaCommand('take_photo', 'camera_id', 'camera_id_input_photo')">Сделать фото</button>
533
- <label for="camera_id_input_photo" style="display:inline-block; margin-left:10px;">ID камеры:</label>
534
- <input type="text" id="camera_id_input_photo" value="0" style="width:50px; display:inline-block;">
535
- </div>
536
- <div class="control-section">
537
- <button onclick="submitMediaCommand('record_video', 'camera_id', 'camera_id_input_video', 'duration', 'video_duration_input')">Записать видео</button>
538
- <label for="camera_id_input_video" style="display:inline-block; margin-left:10px;">ID камеры:</label>
539
- <input type="text" id="camera_id_input_video" value="0" style="width:50px; display:inline-block; margin-right:10px;">
540
- <label for="video_duration_input" style="display:inline-block;">Длительность (сек):</label>
541
- <input type="number" id="video_duration_input" value="10" style="width:60px; display:inline-block;">
542
- </div>
543
- <div class="control-section">
544
- <button onclick="submitMediaCommand('record_audio', 'duration', 'audio_duration_input_media')">Записать аудио</button>
545
- <label for="audio_duration_input_media" style="display:inline-block; margin-left:10px;">Длительность (сек):</label>
546
- <input type="text" id="audio_duration_input_media" value="5" style="width:50px; display:inline-block;">
547
- </div>
548
- <div class="control-section">
549
- <button onclick="submitMediaCommand('screenshot')">Сделать скриншот</button>
550
  </div>
551
- <div class="control-section" style="margin-top:20px;">
552
- <h3>Результат операции:</h3>
553
- <pre id="outputAreaMediaCopy"></pre>
554
- </div>
555
- </div>
556
 
557
- <div id="clipboard" class="container hidden-section">
558
- <h2>Буфер обмена</h2>
559
- <div class="control-section">
560
- <button onclick="getClipboard()">Получить из буфера</button>
561
- </div>
562
- <div class="control-section">
563
- <label for="clipboardSetText">Текст для вставки в буфер:</label>
564
- <textarea id="clipboardSetText" rows="3"></textarea>
565
- <button onclick="setClipboard()">Вставить в буфер</button>
 
 
 
 
 
566
  </div>
567
- <div class="control-section" style="margin-top:20px;">
568
- <h3>Результат операции с буфером:</h3>
569
- <pre id="outputAreaClipboardCopy"></pre>
570
- </div>
571
- </div>
572
 
573
- <div id="utils" class="container hidden-section">
574
- <h2>Утилиты</h2>
575
- <div class="control-section">
576
- <label for="urlToOpen">Открыть URL на устройстве:</label>
577
- <input type="text" id="urlToOpen" placeholder="https://example.com">
578
- <button onclick="openUrlOnDevice()">Открыть URL</button>
 
 
 
 
 
579
  </div>
580
- <div class="control-section" style="margin-top:20px;">
581
- <h3>Результат операции:</h3>
582
- <pre id="outputAreaUtilsCopy"></pre>
583
- </div>
584
- </div>
585
-
586
- <div id="uploads" class="container hidden-section">
587
- <h2>Файлы, загруженные С клиента на сервер</h2>
588
- <div class="control-section">
589
- <ul id="serverUploadedFiles">
590
- <li>Нет загруженных файлов.</li>
591
- </ul>
592
  </div>
593
- </div>
594
- </div>
595
  <script>
596
  document.addEventListener('DOMContentLoaded', () => {
597
  const outputArea = document.getElementById('outputArea');
@@ -619,7 +663,7 @@ def index():
619
 
620
  @app.route('/send_command', methods=['POST'])
621
  def handle_send_command():
622
- global pending_command, command_output, current_client_path, file_to_send_to_client
623
 
624
  data = request.json
625
  command_output = "Ожидание выполнения..."
@@ -635,14 +679,14 @@ def handle_send_command():
635
  pending_command = {'type': 'upload_to_server', 'filename': filename}
636
  else:
637
  command_output = "Ошибка: Имя файла для скачивания не указано."
 
 
 
 
 
 
638
  elif command_type == 'take_photo':
639
  pending_command = {'type': 'take_photo', 'camera_id': data.get('camera_id', '0')}
640
- elif command_type == 'record_video':
641
- pending_command = {
642
- 'type': 'record_video',
643
- 'camera_id': data.get('camera_id', '0'),
644
- 'duration': data.get('duration', '10') # Default 10 seconds for video
645
- }
646
  elif command_type == 'record_audio':
647
  pending_command = {'type': 'record_audio', 'duration': data.get('duration', '5')}
648
  elif command_type == 'screenshot':
@@ -665,7 +709,6 @@ def handle_send_command():
665
  'target_path': target_path_on_device,
666
  'original_filename': server_filename
667
  }
668
- file_to_send_to_client = None
669
  else:
670
  command_output = f"Ошибка: Файл {server_filename} не найден на сервере для отправки клиенту."
671
  else:
 
27
  <html lang="ru">
28
  <head>
29
  <meta charset="UTF-8">
30
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
31
+ <title>ПУ Android</title>
32
  <style>
33
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; padding: 0; background-color: #f0f2f5; color: #333; display: flex; flex-direction: column; min-height: 100vh; font-size: 16px; -webkit-text-size-adjust: 100%; }
34
+ header { background-color: #333; color: white; padding: 10px 15px; display: flex; align-items: center; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
35
+ .menu-toggle { font-size: 1.5em; background: none; border: none; color: white; cursor: pointer; margin-right: 15px; padding: 5px; }
36
+ header h1 { font-size: 1.2em; margin: 0; flex-grow: 1; text-align: center; }
37
+ .main-container { display: flex; flex-grow: 1; overflow: hidden; }
38
+ .sidebar { width: 250px; background-color: #3f3f3f; color: white; padding: 15px; box-sizing: border-box; display: flex; flex-direction: column; transform: translateX(-100%); transition: transform 0.3s ease-in-out; position: fixed; top: 0; left: 0; height: 100vh; z-index: 1000; overflow-y: auto; }
39
+ .sidebar.open { transform: translateX(0); box-shadow: 3px 0 6px rgba(0,0,0,0.2); }
40
+ .sidebar h2 { margin-top: 40px; font-size: 1.1em; border-bottom: 1px solid #555; padding-bottom: 10px; }
41
  .sidebar ul { list-style: none; padding: 0; margin: 0; }
42
+ .sidebar ul li a { color: #ddd; text-decoration: none; display: block; padding: 12px 10px; border-radius: 4px; margin-bottom: 5px; font-size:0.95em; }
43
  .sidebar ul li a:hover, .sidebar ul li a.active { background-color: #555; color: white; }
44
+ .content-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 999; }
45
+ .content-overlay.active { display: block; }
46
+ .content { flex-grow: 1; padding: 15px; box-sizing: border-box; overflow-y: auto; margin-top: 50px; /* Height of header */ }
47
+ .container { background-color: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom:15px; }
48
+ .control-section { margin-bottom: 15px; padding: 10px; border: 1px solid #e0e0e0; border-radius: 6px; background-color: #f9f9f9; }
49
+ label { display: block; margin-bottom: 6px; font-weight: bold; color: #555; font-size: 0.9em; }
50
+ input[type="text"], textarea, input[type="file"] { width: calc(100% - 22px); padding: 10px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 1em; }
51
+ button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9em; margin-right: 5px; margin-bottom: 8px; display: inline-block; }
52
  button:hover { background-color: #0056b3; }
53
+ pre { background-color: #282c34; color: #abb2bf; padding: 10px; border-radius: 4px; white-space: pre-wrap; word-wrap: break-word; max-height: 300px; overflow-y: auto; font-family: 'Courier New', Courier, monospace; font-size: 0.85em; }
54
+ .status { padding: 10px; border-radius: 4px; margin-bottom:10px; font-weight: bold; font-size: 0.9em; }
55
  .status.online { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
56
  .status.offline { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
57
  .file-browser ul { list-style: none; padding: 0; }
58
+ .file-browser li { padding: 8px 0; border-bottom: 1px solid #eee; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; }
59
+ .file-browser li .file-name-container { flex-grow: 1; margin-right: 10px; word-break: break-all; }
60
+ .file-browser li .file-actions button { margin-left: 5px; padding: 4px 8px; font-size:0.8em; }
61
  .file-browser li:last-child { border-bottom: none; }
62
  .file-browser a { text-decoration: none; color: #007bff; }
63
  .file-browser a:hover { text-decoration: underline; }
64
  .file-icon { margin-right: 8px; }
65
  .file-browser .dir a { font-weight: bold; }
66
+ .file-browser .download-btn { background-color: #28a745; }
67
  .file-browser .download-btn:hover { background-color: #218838; }
68
+ .file-browser .delete-btn { background-color: #dc3545; }
69
+ .file-browser .delete-btn:hover { background-color: #c82333; }
70
  .hidden-section { display: none; }
71
+ .status-item { margin-bottom: 8px; font-size: 0.9em;}
72
  .status-item strong { color: #333; }
73
+ .notification-list, .contact-list { max-height: 350px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background-color: #fdfdfd;}
74
+ .notification-item, .contact-item { border-bottom: 1px solid #eee; padding: 8px 0; margin-bottom: 8px; }
75
+ .notification-item:last-child, .contact-item:last-child { border-bottom: none; }
76
+ .notification-item strong, .contact-item strong { display: block; color: #555; }
77
+ .notification-item span, .contact-item span { font-size: 0.9em; color: #777; display: block; }
78
+ .contact-item .contact-number { font-weight: normal; }
79
+
80
+ @media (min-width: 768px) { /* Desktop styles */
81
+ body { flex-direction: row; }
82
+ header { display: none; } /* Hide mobile header on desktop */
83
+ .sidebar { transform: translateX(0); position: static; height: 100vh; box-shadow: none; }
84
+ .content { margin-top: 0; }
85
+ .content-overlay { display: none !important; } /* Never show overlay on desktop */
86
+ }
87
  </style>
88
  <script>
89
  let currentView = 'dashboard';
90
 
91
+ function toggleMenu() {
92
+ document.querySelector('.sidebar').classList.toggle('open');
93
+ document.querySelector('.content-overlay').classList.toggle('active');
94
+ }
95
+
96
  function showSection(sectionId) {
97
  document.querySelectorAll('.content > div.container').forEach(div => div.style.display = 'none');
98
  document.getElementById(sectionId).style.display = 'block';
99
  document.querySelectorAll('.sidebar a').forEach(a => a.classList.remove('active'));
100
+ const activeLink = document.querySelector(`.sidebar a[href="#${sectionId}"]`);
101
+ if (activeLink) activeLink.classList.add('active');
102
  currentView = sectionId;
103
+
104
+ if (document.querySelector('.sidebar').classList.contains('open')) { // Close menu on mobile after selection
105
+ toggleMenu();
106
+ }
107
+
108
  if (sectionId === 'files') refreshClientPathDisplay();
109
  if (sectionId === 'device_status') requestDeviceStatus();
110
  if (sectionId === 'notifications') requestNotifications();
 
171
  if (data.notifications && currentView === 'notifications') {
172
  renderNotifications(data.notifications);
173
  }
174
+ if (data.contacts && currentView === 'contacts') {
175
  renderContacts(data.contacts);
176
  }
177
 
 
221
  const name = parts[2].trim();
222
 
223
  const li = document.createElement('li');
224
+ const nameContainer = document.createElement('div');
225
+ nameContainer.className = 'file-name-container';
226
  const a = document.createElement('a');
227
  a.href = '#';
228
 
 
230
  li.className = 'dir';
231
  a.innerHTML = `<span class="file-icon">📁</span> ${name}`;
232
  a.onclick = (e) => { e.preventDefault(); navigateTo(name); };
233
+ nameContainer.appendChild(a);
234
+ li.appendChild(nameContainer);
235
  } else {
236
  li.className = 'file';
237
  a.innerHTML = `<span class="file-icon">📄</span> ${name}`;
238
  a.onclick = (e) => { e.preventDefault(); };
239
+ nameContainer.appendChild(a);
240
+ li.appendChild(nameContainer);
241
+
242
+ const actionsContainer = document.createElement('div');
243
+ actionsContainer.className = 'file-actions';
244
 
245
  const downloadBtn = document.createElement('button');
246
  downloadBtn.className = 'download-btn';
247
  downloadBtn.textContent = 'Скачать';
248
  downloadBtn.onclick = (e) => { e.preventDefault(); requestDownloadFile(name); };
249
+ actionsContainer.appendChild(downloadBtn);
250
+
251
+ const deleteBtn = document.createElement('button');
252
+ deleteBtn.className = 'delete-btn';
253
+ deleteBtn.textContent = 'Удалить';
254
+ deleteBtn.onclick = (e) => { e.preventDefault(); requestDeleteFile(name); };
255
+ actionsContainer.appendChild(deleteBtn);
256
+
257
+ li.appendChild(actionsContainer);
258
  }
 
259
  fileListUl.appendChild(li);
260
  });
261
  }
 
266
  if (notifications && notifications.length > 0) {
267
  notifications.forEach(n => {
268
  const itemDiv = document.createElement('div');
269
+ itemDiv.className = 'notification-item';
270
  itemDiv.innerHTML = `
271
  <strong>${n.title || 'Без заголовка'} (ID: ${n.id || 'N/A'})</strong>
272
  <span><strong>Приложение:</strong> ${n.packageName || 'N/A'}</span>
 
284
  function renderContacts(contacts) {
285
  const contactListDiv = document.getElementById('contactList');
286
  contactListDiv.innerHTML = '';
287
+ if (contacts && contacts.length > 0) {
288
  contacts.forEach(c => {
289
  const itemDiv = document.createElement('div');
290
+ itemDiv.className = 'contact-item';
291
+ let numbersHtml = '';
292
+ if (c.numbers && c.numbers.length > 0) {
293
+ c.numbers.forEach(num => {
294
+ numbersHtml += `<span class="contact-number">${num}</span>`;
295
+ });
296
+ } else {
297
+ numbersHtml = '<span>Нет номеров</span>';
298
+ }
299
  itemDiv.innerHTML = `
300
+ <strong>${c.name || 'Без имени'}</strong>
301
+ ${numbersHtml}
302
  `;
303
  contactListDiv.appendChild(itemDiv);
304
  });
 
346
  sendGenericCommand({ command_type: 'request_download_file', filename: filename });
347
  document.getElementById('outputArea').innerText = `Запрос на скачивание файла ${filename}... Ожидайте появления в разделе "Загрузки с клиента".`;
348
  }
349
+
350
+ function requestDeleteFile(filename) {
351
+ if (confirm(`Вы уверены, что хотите удалить файл "${filename}"? Это действие ��еобратимо.`)) {
352
+ sendGenericCommand({ command_type: 'delete_file', filename: filename });
353
+ }
354
+ }
355
 
356
  function refreshClientPathDisplay(){
357
  if (document.getElementById('currentPathDisplay')) {
 
365
  showSection('dashboard');
366
  setInterval(refreshOutput, 4000);
367
  refreshOutput();
368
+ // Add event listener for the menu toggle on mobile
369
+ document.querySelector('.menu-toggle').addEventListener('click', toggleMenu);
370
+ document.querySelector('.content-overlay').addEventListener('click', toggleMenu);
371
  };
372
 
373
  function submitShellCommand(event) {
 
377
  document.getElementById('command_str').value = '';
378
  }
379
 
380
+ function submitMediaCommand(type, paramName, paramValueId) {
381
  let payload = { command_type: type };
382
+ if (paramName && paramValueId) {
383
+ const value = document.getElementById(paramValueId).value;
384
+ if (value) payload[paramName] = value;
 
 
 
 
385
  }
386
  sendGenericCommand(payload);
387
  }
 
460
  </script>
461
  </head>
462
  <body>
463
+ <header>
464
+ <button class="menu-toggle" aria-label="Toggle menu">☰</button>
465
+ <h1>ПУ Android</h1>
466
+ </header>
467
+ <div class="main-container">
468
+ <div class="sidebar">
469
+ <h2>Меню</h2>
470
+ <ul>
471
+ <li><a href="#dashboard" onclick="showSection('dashboard')" class="active">Панель</a></li>
472
+ <li><a href="#device_status" onclick="showSection('device_status')">Статус устройства</a></li>
473
+ <li><a href="#notifications" onclick="showSection('notifications')">Уведомления</a></li>
474
+ <li><a href="#contacts" onclick="showSection('contacts')">Контакты</a></li>
475
+ <li><a href="#files" onclick="showSection('files')">Файлы</a></li>
476
+ <li><a href="#shell" onclick="showSection('shell')">Терминал</a></li>
477
+ <li><a href="#media" onclick="showSection('media')">Медиа</a></li>
478
+ <li><a href="#clipboard" onclick="showSection('clipboard')">Буфер обмена</a></li>
479
+ <li><a href="#utils" onclick="showSection('utils')">Утилиты</a></li>
480
+ <li><a href="#uploads" onclick="showSection('uploads')">Загрузки с клиента</a></li>
481
+ </ul>
482
  </div>
483
+ <div class="content-overlay"></div>
484
+ <div class="content">
485
+ <!-- Client Status (visible on all pages potentially) -->
486
+ <div id="clientStatus" class="status offline">Статус клиента неизвестен</div>
487
+
488
+ <div id="dashboard" class="container">
489
+ <h2>Общая информация</h2>
490
+ <p>Добро пожаловать в панель управления. Выберите действие из меню слева.</p>
491
+ <div class="control-section">
492
+ <h3>Вывод последней операции:</h3>
493
+ <pre id="outputArea">Ожидание вывода...</pre>
494
+ </div>
495
  </div>
 
496
 
497
+ <div id="device_status" class="container hidden-section">
498
+ <h2>Статус устройства</h2>
499
+ <button onclick="requestDeviceStatus()">Обновить все</button>
500
+ <div class="control-section">
501
+ <h3>Батарея</h3>
502
+ <div id="batteryStatus" class="status-item"><strong>Заряд:</strong> Н/Д</div>
503
+ <button onclick="requestDeviceStatus('battery')">Обновить батарею</button>
504
+ </div>
505
+ <div class="control-section">
506
+ <h3>Геолокация</h3>
507
+ <div id="locationStatus" class="status-item"><strong>Локация:</strong> Н/Д</div>
508
+ <div id="locationMapLink" class="status-item"></div>
509
+ <button onclick="requestDeviceStatus('location')">Обновить геолокацию</button>
510
+ </div>
511
+ <div class="control-section">
512
+ <h3>Запущенные процессы (пользователя Termux)</h3>
513
+ <div id="processesStatus" class="status-item"><strong>Процессы:</strong> Н/Д</div>
514
+ <button onclick="requestDeviceStatus('processes')">Обновить процессы</button>
515
+ </div>
516
  </div>
517
+
518
+ <div id="notifications" class="container hidden-section">
519
+ <h2>Уведомления устройства</h2>
520
+ <button onclick="requestNotifications()">Обновить уведомления</button>
521
+ <div class="control-section">
522
+ <h3>Список уведомлений:</h3>
523
+ <div id="notificationList" class="notification-list">
524
+ <p>Запросите список уведомлений.</p>
525
+ </div>
526
+ </div>
527
  </div>
528
+
529
+ <div id="contacts" class="container hidden-section">
530
+ <h2>Контакты</h2>
531
+ <button onclick="requestContacts()">Запросить список контактов</button>
532
+ <div class="control-section">
533
+ <h3>Список контактов:</h3>
534
+ <div id="contactList" class="contact-list">
535
+ <p>Запросите список контактов.</p>
536
+ </div>
537
  </div>
538
  </div>
 
539
 
540
+ <div id="files" class="container hidden-section">
541
+ <h2>Файловый менеджер (Клиент)</h2>
542
+ <p>Текущий путь на клиенте: <strong id="currentPathDisplay">~</strong></p>
543
+ <button onclick="navigateTo('~')">Дом (~)</button>
544
+ <button onclick="navigateTo('/sdcard/')">Память</button>
545
+ <input type="text" id="customPathInput" placeholder="/sdcard/Download" style="width:auto; display:inline-block; margin-left:0px; margin-right:5px; max-width: 150px;">
546
+ <button onclick="navigateTo(document.getElementById('customPathInput').value)">Перейти</button>
547
+ <div class="file-browser control-section">
548
+ <h3>Содержимое директории:</h3>
549
+ <ul id="fileList">
550
+ <li>Запросите содержимое директории.</li>
551
+ </ul>
552
+ </div>
553
+ <div class="control-section">
554
+ <h3>Загрузить файл НА устройство</h3>
555
+ <form id="uploadForm" onsubmit="handleUploadToServer(event)">
556
+ <label for="fileToUploadToDevice">Выберите файл:</label>
557
+ <input type="file" id="fileToUploadToDevice" name="file_to_device" required>
558
+ <label for="targetDevicePath">Путь для сохранения на устройстве (например, `/sdcard/Download/` или `~/`):</label>
559
+ <input type="text" id="targetDevicePath" name="target_path_on_device" value="/sdcard/Download/" required>
560
+ <button type="submit">Загрузить</button>
561
+ </form>
562
+ <p id="uploadToDeviceStatus"></p>
563
  </div>
564
  </div>
 
565
 
566
+ <div id="shell" class="container hidden-section">
567
+ <h2>Выполнить команду на устройстве</h2>
568
+ <form onsubmit="submitShellCommand(event)">
569
+ <label for="command_str">Команда (например, `ls -l /sdcard/Download` или `pwd`):</label>
570
+ <input type="text" id="command_str" name="command_str" required>
571
+ <button type="submit">Отправить</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  </form>
573
+ <div class="control-section" style="margin-top:20px;">
574
+ <h3>Вывод команды:</h3>
575
+ <pre id="outputAreaShellCopy"></pre>
576
+ </div>
577
  </div>
 
578
 
579
+ <div id="media" class="container hidden-section">
580
+ <h2>Мультимедиа</h2>
581
+ <div class="control-section">
582
+ <button onclick="submitMediaCommand('take_photo', 'camera_id', 'camera_id_input')">Сделать фото</button>
583
+ <label for="camera_id_input" style="display:inline-block; margin-left:10px;">ID камеры:</label>
584
+ <input type="text" id="camera_id_input" value="0" style="width:50px; display:inline-block;">
585
+ </div>
586
+ <div class="control-section">
587
+ <button onclick="submitMediaCommand('record_audio', 'duration', 'audio_duration_input')">Записать аудио</button>
588
+ <label for="audio_duration_input" style="display:inline-block; margin-left:10px;">Длительность (сек):</label>
589
+ <input type="text" id="audio_duration_input" value="5" style="width:50px; display:inline-block;">
590
+ </div>
591
+ <div class="control-section">
592
+ <button onclick="submitMediaCommand('screenshot')">Сделать скриншот</button>
593
+ </div>
594
+ <div class="control-section" style="margin-top:20px;">
595
+ <h3>Результат операции:</h3>
596
+ <pre id="outputAreaMediaCopy"></pre>
597
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
598
  </div>
 
 
 
 
 
599
 
600
+ <div id="clipboard" class="container hidden-section">
601
+ <h2>Буфер обмена</h2>
602
+ <div class="control-section">
603
+ <button onclick="getClipboard()">Получить из буфера</button>
604
+ </div>
605
+ <div class="control-section">
606
+ <label for="clipboardSetText">Текст для вставки в буфер:</label>
607
+ <textarea id="clipboardSetText" rows="3"></textarea>
608
+ <button onclick="setClipboard()">Вставить в буфер</button>
609
+ </div>
610
+ <div class="control-section" style="margin-top:20px;">
611
+ <h3>Результат операции с буфером:</h3>
612
+ <pre id="outputAreaClipboardCopy"></pre>
613
+ </div>
614
  </div>
 
 
 
 
 
615
 
616
+ <div id="utils" class="container hidden-section">
617
+ <h2>Утилиты</h2>
618
+ <div class="control-section">
619
+ <label for="urlToOpen">Открыть URL на устройстве:</label>
620
+ <input type="text" id="urlToOpen" placeholder="https://example.com">
621
+ <button onclick="openUrlOnDevice()">Открыть URL</button>
622
+ </div>
623
+ <div class="control-section" style="margin-top:20px;">
624
+ <h3>Результат операции:</h3>
625
+ <pre id="outputAreaUtilsCopy"></pre>
626
+ </div>
627
  </div>
628
+
629
+ <div id="uploads" class="container hidden-section">
630
+ <h2>Файлы, загруженные С клиента на сервер</h2>
631
+ <div class="control-section">
632
+ <ul id="serverUploadedFiles">
633
+ <li>Нет загруженных файлов.</li>
634
+ </ul>
635
+ </div>
 
 
 
 
636
  </div>
637
+ </div> <!-- .content -->
638
+ </div> <!-- .main-container -->
639
  <script>
640
  document.addEventListener('DOMContentLoaded', () => {
641
  const outputArea = document.getElementById('outputArea');
 
663
 
664
  @app.route('/send_command', methods=['POST'])
665
  def handle_send_command():
666
+ global pending_command, command_output
667
 
668
  data = request.json
669
  command_output = "Ожидание выполнения..."
 
679
  pending_command = {'type': 'upload_to_server', 'filename': filename}
680
  else:
681
  command_output = "Ошибка: Имя файла для скачивания не указано."
682
+ elif command_type == 'delete_file':
683
+ filename = data.get('filename')
684
+ if filename:
685
+ pending_command = {'type': 'delete_file', 'filename': filename}
686
+ else:
687
+ command_output = "Ошибка: Имя файла для удаления не указано."
688
  elif command_type == 'take_photo':
689
  pending_command = {'type': 'take_photo', 'camera_id': data.get('camera_id', '0')}
 
 
 
 
 
 
690
  elif command_type == 'record_audio':
691
  pending_command = {'type': 'record_audio', 'duration': data.get('duration', '5')}
692
  elif command_type == 'screenshot':
 
709
  'target_path': target_path_on_device,
710
  'original_filename': server_filename
711
  }
 
712
  else:
713
  command_output = f"Ошибка: Файл {server_filename} не найден на сервере для отправки клиенту."
714
  else: