Eluza133 commited on
Commit
e7f68b0
·
verified ·
1 Parent(s): 507dfa7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +152 -78
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # --- START OF FILE app (9).py ---
2
  from flask import Flask, render_template_string, request, redirect, url_for, session, flash, send_file, jsonify, Response
3
  from flask_caching import Cache
4
  import json
@@ -227,6 +227,7 @@ body.dark input, body.dark textarea { color: var(--text-dark); }
227
  input:focus, textarea:focus { outline: none; box-shadow: 0 0 0 4px var(--primary); }
228
  .btn { padding: 14px 28px; background: var(--primary); color: white; border: none; border-radius: 14px; cursor: pointer; font-size: 1.1em; font-weight: 600; transition: var(--transition); box-shadow: var(--shadow); display: inline-block; text-decoration: none; margin-top: 5px; margin-right: 5px; }
229
  .btn:hover { transform: scale(1.05); background: #e6415f; }
 
230
  .download-btn { background: var(--secondary); }
231
  .download-btn:hover { background: #00b8c5; }
232
  .delete-btn { background: var(--delete-color); }
@@ -261,8 +262,9 @@ body.dark .modal-content { background: var(--card-bg-dark); }
261
  body.dark .modal pre { background: #2b2a33; color: var(--text-dark); }
262
  .modal-close-btn { position: absolute; top: 15px; right: 25px; font-size: 30px; color: #aaa; cursor: pointer; background: rgba(0,0,0,0.5); border-radius: 50%; width: 30px; height: 30px; line-height: 30px; text-align: center; }
263
  body.dark .modal-close-btn { color: #555; background: rgba(255,255,255,0.2); }
264
- #progress-container { width: 100%; background: var(--glass-bg); border-radius: 10px; margin: 15px 0; display: none; }
265
- #progress-bar { width: 0%; height: 20px; background: var(--primary); border-radius: 10px; transition: width 0.3s ease; }
 
266
  .breadcrumbs { margin-bottom: 20px; font-size: 1.1em; }
267
  .breadcrumbs a { color: var(--accent); text-decoration: none; }
268
  .breadcrumbs a:hover { text-decoration: underline; }
@@ -555,12 +557,15 @@ def dashboard():
555
  </form>
556
  </div>
557
 
558
- <form id="upload-form" method="POST" enctype="multipart/form-data">
559
  <input type="hidden" name="current_folder_id" value="{{ current_folder_id }}">
560
  <input type="file" name="files" multiple required>
561
  <button type="submit" class="btn" id="upload-btn">Загрузить файлы сюда</button>
562
  </form>
563
- <div id="progress-container"><div id="progress-bar"></div></div>
 
 
 
564
 
565
  <h2>Содержимое папки: {{ current_folder.name if current_folder_id != 'root' else 'Главная' }}</h2>
566
  <div class="file-grid">
@@ -697,27 +702,65 @@ def dashboard():
697
 
698
  const form = document.getElementById('upload-form');
699
  const progressBar = document.getElementById('progress-bar');
 
700
  const progressContainer = document.getElementById('progress-container');
701
  const uploadBtn = document.getElementById('upload-btn');
 
702
 
703
  form.addEventListener('submit', function(e) {
704
- const files = form.querySelector('input[type="file"]').files;
705
- if (files.length === 0) {
 
706
  alert('Пожалуйста, выберите файлы для загрузки.');
707
- e.preventDefault();
708
  return;
709
  }
710
- if (files.length > 20) {
711
  alert('Максимум 20 файлов за раз!');
712
- e.preventDefault();
713
  return;
714
  }
715
 
716
  progressContainer.style.display = 'block';
717
- progressBar.style.width = '50%';
 
718
  uploadBtn.disabled = true;
719
  uploadBtn.textContent = 'Загрузка...';
720
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
721
  });
722
 
723
  document.getElementById('logout-btn').addEventListener('click', function(e) {
@@ -786,47 +829,45 @@ def create_folder():
786
 
787
  @app.route('/download/<file_id>')
788
  def download_file(file_id):
789
- if 'username' not in session:
790
- is_admin_route = request.referrer and 'admhosto' in request.referrer
791
- if not is_admin_route:
792
- flash('Пожалуйста, войдите в систему!')
793
- return redirect(url_for('login'))
794
- elif not is_admin():
795
- flash('Доступ запрещен (Admin).')
796
- return redirect(url_for('login'))
797
- # Admin access allowed to proceed
798
 
799
  data = load_data()
800
  file_node = None
801
- username_context = None # Track which user the file belongs to
802
 
803
- if 'username' in session:
804
- username_context = session['username']
805
  user_data = data['users'].get(username_context)
806
  if user_data:
807
- file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
808
 
809
- # If not found in current user's context OR if admin is accessing, search all users
810
- if not file_node and is_admin():
811
  logging.info(f"Admin searching for file ID {file_id} across all users.")
812
  for uname, udata in data.get('users', {}).items():
813
  node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
814
  if node and node.get('type') == 'file':
815
  file_node = node
816
- username_context = uname # Found the file under this user
817
  logging.info(f"Admin found file ID {file_id} belonging to user {username_context}")
818
  break
819
 
820
  if not file_node or file_node.get('type') != 'file':
821
  flash('Файл не найден!', 'error')
822
- return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
 
 
823
 
824
  hf_path = file_node.get('path')
825
  original_filename = file_node.get('original_filename', 'downloaded_file')
826
 
827
  if not hf_path:
828
  flash('Ошибка: Путь к файлу не найден в метаданных.', 'error')
829
- return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
 
830
 
831
  file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
832
 
@@ -848,11 +889,14 @@ def download_file(file_id):
848
  except requests.exceptions.RequestException as e:
849
  logging.error(f"Error downloading file from HF ({hf_path}): {e}")
850
  flash(f'Ошибка скачивания файла {original_filename}! ({e})', 'error')
851
- return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
 
852
  except Exception as e:
853
  logging.error(f"Unexpected error during download ({hf_path}): {e}")
854
  flash('Произошла непредвиденная ошибка при скачивании файла.', 'error')
855
- return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
 
 
856
 
857
 
858
  @app.route('/delete_file/<file_id>', methods=['POST'])
@@ -981,27 +1025,21 @@ def delete_folder(folder_id):
981
 
982
  @app.route('/get_text_content/<file_id>')
983
  def get_text_content(file_id):
984
- if 'username' not in session:
985
- # Allow admin access if authenticated as admin
986
- if not is_admin():
987
- return Response("Не авторизован", status=401)
988
- # Admin is accessing, proceed to find file globally
989
- else:
990
- # Regular user access
991
- pass
992
 
993
  data = load_data()
994
  file_node = None
995
- username_context = None
996
 
997
- if 'username' in session:
998
- username_context = session['username']
999
  user_data = data['users'].get(username_context)
1000
  if user_data:
1001
- file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
1002
 
1003
- # If not found in user context or if admin is accessing, search all
1004
- if not file_node and is_admin():
1005
  logging.info(f"Admin searching for text file ID {file_id} across all users.")
1006
  for uname, udata in data.get('users', {}).items():
1007
  node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
@@ -1057,9 +1095,11 @@ def logout():
1057
 
1058
 
1059
  def is_admin():
1060
- # !! INSECURE PLACEHOLDER - REPLACE WITH ACTUAL ADMIN LOGIC !!
1061
- # Example: return session.get('username') == 'admin_user'
1062
- return 'username' in session # Allows any logged-in user, change this!
 
 
1063
 
1064
  @app.route('/admhosto')
1065
  def admin_panel():
@@ -1073,14 +1113,16 @@ def admin_panel():
1073
  user_details = []
1074
  for uname, udata in users.items():
1075
  file_count = 0
1076
- q = [udata.get('filesystem', {}).get('children', [])]
1077
- while q:
1078
- current_level = q.pop(0)
1079
- for item in current_level:
1080
- if item.get('type') == 'file':
1081
- file_count += 1
1082
- elif item.get('type') == 'folder' and 'children' in item:
1083
- q.append(item.get('children', [])) # Ensure list is appended
 
 
1084
  user_details.append({
1085
  'username': uname,
1086
  'created_at': udata.get('created_at', 'N/A'),
@@ -1099,9 +1141,11 @@ def admin_panel():
1099
  <a href="{{ url_for('admin_user_files', username=user.username) }}">{{ user.username }}</a>
1100
  <p>Зарегистрирован: {{ user.created_at }}</p>
1101
  <p>Файлов: {{ user.file_count }}</p>
 
1102
  <form method="POST" action="{{ url_for('admin_delete_user', username=user.username) }}" style="display: inline; margin-left: 10px;" onsubmit="return confirm('УДАЛИТЬ пользователя {{ user.username }} и ВСЕ его файлы? НЕОБРАТИМО!');">
1103
  <button type="submit" class="btn delete-btn" style="padding: 5px 10px; font-size: 0.9em;">Удалить</button>
1104
  </form>
 
1105
  </div>
1106
  {% else %}<p>Пользователей нет.</p>{% endfor %}</div></div></body></html>'''
1107
  return render_template_string(html, user_details=user_details)
@@ -1119,18 +1163,18 @@ def admin_user_files(username):
1119
  return redirect(url_for('admin_panel'))
1120
 
1121
  all_files = []
1122
- def collect_files(folder, current_path_id='root'):
1123
- parent_node, _ = find_node_by_id(user_data['filesystem'], current_path_id)
1124
- parent_path_str = get_node_path_string(user_data['filesystem'], current_path_id)
1125
 
1126
  for item in folder.get('children', []):
1127
  if item.get('type') == 'file':
1128
- item['parent_path_str'] = parent_path_str
1129
  all_files.append(item)
1130
  elif item.get('type') == 'folder':
1131
- collect_files(item, item.get('id'))
1132
 
1133
- collect_files(user_data.get('filesystem', {}))
1134
  all_files.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
1135
 
1136
 
@@ -1208,7 +1252,7 @@ body.dark .file-item { background: var(--card-bg-dark); }
1208
  const response = await fetch(srcOrUrl);
1209
  if (!response.ok) throw new Error(`Ошибка загрузки текста: ${response.statusText}`);
1210
  const text = await response.text();
1211
- const escapedText = text.replace(/</g, "<").replace(/>/g, ">");
1212
  modalContent.innerHTML = `<pre>${escapedText}</pre>`;
1213
  } else {
1214
  modalContent.innerHTML = '<p>Предпросмотр для этого типа файла не поддерживается.</p>';
@@ -1245,6 +1289,9 @@ def admin_delete_user(username):
1245
  if not is_admin():
1246
  flash('Доступ запрещен.', 'error')
1247
  return redirect(url_for('login'))
 
 
 
1248
  if not HF_TOKEN_WRITE:
1249
  flash('Удаление невозможно: токен для записи не настроен.', 'error')
1250
  return redirect(url_for('admin_panel'))
@@ -1262,35 +1309,62 @@ def admin_delete_user(username):
1262
  user_folder_path_on_hf = f"cloud_files/{username}"
1263
 
1264
  logging.info(f"Attempting to delete HF Hub folder: {user_folder_path_on_hf} for user {username}")
1265
- api.delete_folder(
1266
- folder_path=user_folder_path_on_hf,
1267
- repo_id=REPO_ID,
1268
- repo_type="dataset",
1269
- token=HF_TOKEN_WRITE,
1270
- commit_message=f"ADMIN ACTION: Deleted all files/folders for user {username}"
1271
- )
1272
- logging.info(f"Successfully initiated deletion of folder {user_folder_path_on_hf} on HF Hub.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1273
 
1274
  except hf_utils.HfHubHTTPError as e:
1275
- if e.response.status_code == 404:
1276
- logging.warning(f"User folder {user_folder_path_on_hf} not found on HF Hub for user {username}. Skipping HF deletion.")
 
1277
  else:
1278
- logging.error(f"Error deleting user folder {user_folder_path_on_hf} from HF Hub for {username}: {e}")
1279
  flash(f'Ошибка при удалении файлов пользователя {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
1280
  return redirect(url_for('admin_panel'))
1281
  except Exception as e:
1282
- logging.error(f"Unexpected error during HF Hub folder deletion for {username}: {e}")
1283
  flash(f'Неожиданная ошибка при удалении файлов {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
1284
  return redirect(url_for('admin_panel'))
1285
 
 
1286
  try:
1287
  del data['users'][username]
1288
  save_data(data)
1289
- flash(f'Пользователь {username} и его файлы (запрос на удаление отправлен) успешно удалены из базы данных!')
1290
  logging.info(f"ADMIN ACTION: Successfully deleted user {username} from database.")
1291
  except Exception as e:
1292
  logging.error(f"Error saving data after deleting user {username}: {e}")
1293
- flash(f'Файлы пользователя {username} удалены с сервера, но произошла ошибка при удалении пользователя из базы данных: {e}', 'error')
1294
 
1295
  return redirect(url_for('admin_panel'))
1296
 
@@ -1396,4 +1470,4 @@ if __name__ == '__main__':
1396
 
1397
  app.run(debug=False, host='0.0.0.0', port=7860)
1398
 
1399
- # --- END OF FILE app (9).py ---
 
1
+ # --- START OF FILE app (11).py ---
2
  from flask import Flask, render_template_string, request, redirect, url_for, session, flash, send_file, jsonify, Response
3
  from flask_caching import Cache
4
  import json
 
227
  input:focus, textarea:focus { outline: none; box-shadow: 0 0 0 4px var(--primary); }
228
  .btn { padding: 14px 28px; background: var(--primary); color: white; border: none; border-radius: 14px; cursor: pointer; font-size: 1.1em; font-weight: 600; transition: var(--transition); box-shadow: var(--shadow); display: inline-block; text-decoration: none; margin-top: 5px; margin-right: 5px; }
229
  .btn:hover { transform: scale(1.05); background: #e6415f; }
230
+ .btn:disabled { background: #aaa; cursor: not-allowed; }
231
  .download-btn { background: var(--secondary); }
232
  .download-btn:hover { background: #00b8c5; }
233
  .delete-btn { background: var(--delete-color); }
 
262
  body.dark .modal pre { background: #2b2a33; color: var(--text-dark); }
263
  .modal-close-btn { position: absolute; top: 15px; right: 25px; font-size: 30px; color: #aaa; cursor: pointer; background: rgba(0,0,0,0.5); border-radius: 50%; width: 30px; height: 30px; line-height: 30px; text-align: center; }
264
  body.dark .modal-close-btn { color: #555; background: rgba(255,255,255,0.2); }
265
+ #progress-container { width: 100%; background: var(--glass-bg); border-radius: 10px; margin: 15px 0; display: none; overflow: hidden; height: 25px; position: relative;}
266
+ #progress-bar { width: 0%; height: 100%; background: linear-gradient(135deg, var(--primary), var(--accent)); border-radius: 10px; transition: width 0.3s ease; }
267
+ #progress-text { position: absolute; top: 0; left: 0; width: 100%; height: 100%; text-align: center; line-height: 25px; color: white; font-weight: bold; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
268
  .breadcrumbs { margin-bottom: 20px; font-size: 1.1em; }
269
  .breadcrumbs a { color: var(--accent); text-decoration: none; }
270
  .breadcrumbs a:hover { text-decoration: underline; }
 
557
  </form>
558
  </div>
559
 
560
+ <form id="upload-form" method="POST" enctype="multipart/form-data" action="{{ url_for('dashboard') }}">
561
  <input type="hidden" name="current_folder_id" value="{{ current_folder_id }}">
562
  <input type="file" name="files" multiple required>
563
  <button type="submit" class="btn" id="upload-btn">Загрузить файлы сюда</button>
564
  </form>
565
+ <div id="progress-container">
566
+ <div id="progress-bar"></div>
567
+ <div id="progress-text">0%</div>
568
+ </div>
569
 
570
  <h2>Содержимое папки: {{ current_folder.name if current_folder_id != 'root' else 'Главная' }}</h2>
571
  <div class="file-grid">
 
702
 
703
  const form = document.getElementById('upload-form');
704
  const progressBar = document.getElementById('progress-bar');
705
+ const progressText = document.getElementById('progress-text');
706
  const progressContainer = document.getElementById('progress-container');
707
  const uploadBtn = document.getElementById('upload-btn');
708
+ const fileInput = form.querySelector('input[type="file"]');
709
 
710
  form.addEventListener('submit', function(e) {
711
+ e.preventDefault();
712
+
713
+ if (fileInput.files.length === 0) {
714
  alert('Пожалуйста, выберите файлы для загрузки.');
 
715
  return;
716
  }
717
+ if (fileInput.files.length > 20) {
718
  alert('Максимум 20 файлов за раз!');
 
719
  return;
720
  }
721
 
722
  progressContainer.style.display = 'block';
723
+ progressBar.style.width = '0%';
724
+ progressText.textContent = '0%';
725
  uploadBtn.disabled = true;
726
  uploadBtn.textContent = 'Загрузка...';
727
 
728
+ const formData = new FormData(form);
729
+ const xhr = new XMLHttpRequest();
730
+
731
+ xhr.upload.addEventListener('progress', function(event) {
732
+ if (event.lengthComputable) {
733
+ const percentComplete = Math.round((event.loaded / event.total) * 100);
734
+ progressBar.style.width = percentComplete + '%';
735
+ progressText.textContent = percentComplete + '%';
736
+ }
737
+ });
738
+
739
+ xhr.addEventListener('load', function() {
740
+ progressBar.style.width = '100%';
741
+ progressText.textContent = 'Обработка...';
742
+ // Reload the page to show updated file list and flash messages
743
+ // The server handles the redirect implicitly after successful POST
744
+ window.location.reload();
745
+ });
746
+
747
+ xhr.addEventListener('error', function() {
748
+ alert('Произошла ошибка во время загрузки.');
749
+ uploadBtn.disabled = false;
750
+ uploadBtn.textContent = 'Загрузить файлы сюда';
751
+ progressContainer.style.display = 'none';
752
+ });
753
+
754
+ xhr.addEventListener('abort', function() {
755
+ alert('Загрузка отменена.');
756
+ uploadBtn.disabled = false;
757
+ uploadBtn.textContent = 'Загрузить файлы сюда';
758
+ progressContainer.style.display = 'none';
759
+ });
760
+
761
+
762
+ xhr.open('POST', form.action);
763
+ xhr.send(formData);
764
  });
765
 
766
  document.getElementById('logout-btn').addEventListener('click', function(e) {
 
829
 
830
  @app.route('/download/<file_id>')
831
  def download_file(file_id):
832
+ is_admin_route = request.referrer and 'admhosto' in request.referrer
833
+ is_logged_in_admin = 'username' in session and is_admin()
834
+
835
+ if 'username' not in session and not (is_admin_route and is_logged_in_admin):
836
+ flash('Пожалуйста, войдите в систему!')
837
+ return redirect(url_for('login'))
 
 
 
838
 
839
  data = load_data()
840
  file_node = None
841
+ username_context = session.get('username') # Default to current user if logged in
842
 
843
+ if username_context:
 
844
  user_data = data['users'].get(username_context)
845
  if user_data:
846
+ file_node, _ = find_node_by_id(user_data.get('filesystem', {}), file_id)
847
 
848
+ if not file_node and is_logged_in_admin:
 
849
  logging.info(f"Admin searching for file ID {file_id} across all users.")
850
  for uname, udata in data.get('users', {}).items():
851
  node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
852
  if node and node.get('type') == 'file':
853
  file_node = node
854
+ username_context = uname
855
  logging.info(f"Admin found file ID {file_id} belonging to user {username_context}")
856
  break
857
 
858
  if not file_node or file_node.get('type') != 'file':
859
  flash('Файл не найден!', 'error')
860
+ redirect_url = url_for('admin_panel') if is_logged_in_admin and is_admin_route else url_for('dashboard') if 'username' in session else url_for('login')
861
+ return redirect(request.referrer or redirect_url)
862
+
863
 
864
  hf_path = file_node.get('path')
865
  original_filename = file_node.get('original_filename', 'downloaded_file')
866
 
867
  if not hf_path:
868
  flash('Ошибка: Путь к файлу не найден в метаданных.', 'error')
869
+ redirect_url = url_for('admin_user_files', username=username_context) if is_logged_in_admin and is_admin_route and username_context else url_for('dashboard') if 'username' in session else url_for('login')
870
+ return redirect(request.referrer or redirect_url)
871
 
872
  file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
873
 
 
889
  except requests.exceptions.RequestException as e:
890
  logging.error(f"Error downloading file from HF ({hf_path}): {e}")
891
  flash(f'Ошибка скачивания файла {original_filename}! ({e})', 'error')
892
+ redirect_url = url_for('admin_user_files', username=username_context) if is_logged_in_admin and is_admin_route and username_context else url_for('dashboard') if 'username' in session else url_for('login')
893
+ return redirect(request.referrer or redirect_url)
894
  except Exception as e:
895
  logging.error(f"Unexpected error during download ({hf_path}): {e}")
896
  flash('Произошла непредвиденная ошибка при скачивании файла.', 'error')
897
+ redirect_url = url_for('admin_user_files', username=username_context) if is_logged_in_admin and is_admin_route and username_context else url_for('dashboard') if 'username' in session else url_for('login')
898
+ return redirect(request.referrer or redirect_url)
899
+
900
 
901
 
902
  @app.route('/delete_file/<file_id>', methods=['POST'])
 
1025
 
1026
  @app.route('/get_text_content/<file_id>')
1027
  def get_text_content(file_id):
1028
+ is_logged_in_admin = 'username' in session and is_admin()
1029
+
1030
+ if 'username' not in session and not is_logged_in_admin:
1031
+ return Response("Не авторизован", status=401)
 
 
 
 
1032
 
1033
  data = load_data()
1034
  file_node = None
1035
+ username_context = session.get('username') # Default to current user if logged in
1036
 
1037
+ if username_context:
 
1038
  user_data = data['users'].get(username_context)
1039
  if user_data:
1040
+ file_node, _ = find_node_by_id(user_data.get('filesystem', {}), file_id)
1041
 
1042
+ if not file_node and is_logged_in_admin:
 
1043
  logging.info(f"Admin searching for text file ID {file_id} across all users.")
1044
  for uname, udata in data.get('users', {}).items():
1045
  node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
 
1095
 
1096
 
1097
  def is_admin():
1098
+ # SECURITY WARNING: This is NOT a secure way to check for admin status.
1099
+ # Replace this with a proper check, e.g., checking against a list of admin usernames
1100
+ # stored securely, or checking a specific flag in the user's data.
1101
+ # return session.get('username') == 'admin' # Example
1102
+ return 'username' in session and session.get('username') == 'admhosto' # Example: only user 'admhosto' is admin
1103
 
1104
  @app.route('/admhosto')
1105
  def admin_panel():
 
1113
  user_details = []
1114
  for uname, udata in users.items():
1115
  file_count = 0
1116
+ # Correctly implement recursive file counting for nested folders
1117
+ stack = [udata.get('filesystem', {})]
1118
+ while stack:
1119
+ current_node = stack.pop()
1120
+ if current_node.get('type') == 'file':
1121
+ file_count += 1
1122
+ elif current_node.get('type') == 'folder' and 'children' in current_node:
1123
+ for child in current_node['children']:
1124
+ stack.append(child)
1125
+
1126
  user_details.append({
1127
  'username': uname,
1128
  'created_at': udata.get('created_at', 'N/A'),
 
1141
  <a href="{{ url_for('admin_user_files', username=user.username) }}">{{ user.username }}</a>
1142
  <p>Зарегистрирован: {{ user.created_at }}</p>
1143
  <p>Файлов: {{ user.file_count }}</p>
1144
+ {% if user.username != 'admhosto' %} {# Prevent deleting the admin user itself #}
1145
  <form method="POST" action="{{ url_for('admin_delete_user', username=user.username) }}" style="display: inline; margin-left: 10px;" onsubmit="return confirm('УДАЛИТЬ пользователя {{ user.username }} и ВСЕ его файлы? НЕОБРАТИМО!');">
1146
  <button type="submit" class="btn delete-btn" style="padding: 5px 10px; font-size: 0.9em;">Удалить</button>
1147
  </form>
1148
+ {% endif %}
1149
  </div>
1150
  {% else %}<p>Пользователей нет.</p>{% endfor %}</div></div></body></html>'''
1151
  return render_template_string(html, user_details=user_details)
 
1163
  return redirect(url_for('admin_panel'))
1164
 
1165
  all_files = []
1166
+ def collect_files_recursive(folder, current_path_id_str='root'):
1167
+ if not folder or not isinstance(folder, dict): return
1168
+ parent_path_display = get_node_path_string(user_data['filesystem'], current_path_id_str)
1169
 
1170
  for item in folder.get('children', []):
1171
  if item.get('type') == 'file':
1172
+ item['parent_path_str'] = parent_path_display or "Root"
1173
  all_files.append(item)
1174
  elif item.get('type') == 'folder':
1175
+ collect_files_recursive(item, item.get('id'))
1176
 
1177
+ collect_files_recursive(user_data.get('filesystem', {}))
1178
  all_files.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
1179
 
1180
 
 
1252
  const response = await fetch(srcOrUrl);
1253
  if (!response.ok) throw new Error(`Ошибка загрузки текста: ${response.statusText}`);
1254
  const text = await response.text();
1255
+ const escapedText = text.replace(/</g, "<").replace(/>/g, ">"); // Correct escaping
1256
  modalContent.innerHTML = `<pre>${escapedText}</pre>`;
1257
  } else {
1258
  modalContent.innerHTML = '<p>Предпросмотр для этого типа файла не поддерживается.</p>';
 
1289
  if not is_admin():
1290
  flash('Доступ запрещен.', 'error')
1291
  return redirect(url_for('login'))
1292
+ if username == 'admhosto': # Add protection against deleting the admin itself
1293
+ flash('Нельзя удалить основного администратора.', 'error')
1294
+ return redirect(url_for('admin_panel'))
1295
  if not HF_TOKEN_WRITE:
1296
  flash('Удаление невозможно: токен для записи не настроен.', 'error')
1297
  return redirect(url_for('admin_panel'))
 
1309
  user_folder_path_on_hf = f"cloud_files/{username}"
1310
 
1311
  logging.info(f"Attempting to delete HF Hub folder: {user_folder_path_on_hf} for user {username}")
1312
+ # Listing files first to handle potential large number of files / non-empty folders robustly
1313
+ repo_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_READ or HF_TOKEN_WRITE)
1314
+ user_files_to_delete = [f for f in repo_files if f.startswith(user_folder_path_on_hf + '/')]
1315
+
1316
+ if user_files_to_delete:
1317
+ delete_operations = [hf_utils.DeleteUploadedFile(path_in_repo=f) for f in user_files_to_delete]
1318
+ # Also attempt to delete the folder itself if HF Hub API supports direct folder deletion well by now
1319
+ # If not, deleting all files might be sufficient or might require individual file deletion commits.
1320
+ # The delete_folder function might be better now, let's try it first.
1321
+ try:
1322
+ api.delete_folder(
1323
+ folder_path=user_folder_path_on_hf,
1324
+ repo_id=REPO_ID,
1325
+ repo_type="dataset",
1326
+ token=HF_TOKEN_WRITE,
1327
+ commit_message=f"ADMIN ACTION: Deleted all files/folders for user {username}"
1328
+ )
1329
+ logging.info(f"Successfully initiated deletion of folder {user_folder_path_on_hf} on HF Hub.")
1330
+ except Exception as folder_del_err:
1331
+ logging.warning(f"Direct folder deletion failed ({folder_del_err}), attempting individual file deletion.")
1332
+ if delete_operations:
1333
+ api.delete_files(
1334
+ repo_id=REPO_ID,
1335
+ paths_in_repo=[op.path_in_repo for op in delete_operations],
1336
+ repo_type="dataset",
1337
+ token=HF_TOKEN_WRITE,
1338
+ commit_message=f"ADMIN ACTION: Deleted files for user {username}"
1339
+ )
1340
+ logging.info(f"Successfully deleted {len(delete_operations)} individual files for user {username} on HF Hub.")
1341
+
1342
+ else:
1343
+ logging.info(f"No files found in HF Hub folder {user_folder_path_on_hf} for user {username}.")
1344
+
1345
 
1346
  except hf_utils.HfHubHTTPError as e:
1347
+ # Handle case where the folder might not exist (already deleted or never created)
1348
+ if e.response.status_code == 404 or "EntryNotFoundError" in str(e) or "not found" in str(e).lower():
1349
+ logging.warning(f"User folder {user_folder_path_on_hf} or files not found on HF Hub for user {username}. Skipping HF deletion.")
1350
  else:
1351
+ logging.error(f"Error deleting user folder/files {user_folder_path_on_hf} from HF Hub for {username}: {e}")
1352
  flash(f'Ошибка при удалении файлов пользователя {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
1353
  return redirect(url_for('admin_panel'))
1354
  except Exception as e:
1355
+ logging.error(f"Unexpected error during HF Hub folder/file deletion for {username}: {e}")
1356
  flash(f'Неожиданная ошибка при удалении файлов {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
1357
  return redirect(url_for('admin_panel'))
1358
 
1359
+ # Proceed with deleting user from database only if HF deletion was successful or skipped reasonably
1360
  try:
1361
  del data['users'][username]
1362
  save_data(data)
1363
+ flash(f'Пользователь {username} и его файлы (запрос на удаление с сервера отправлен/пропущен) успешно удалены из базы данных!')
1364
  logging.info(f"ADMIN ACTION: Successfully deleted user {username} from database.")
1365
  except Exception as e:
1366
  logging.error(f"Error saving data after deleting user {username}: {e}")
1367
+ flash(f'Файлы пользователя {username} удалены с сервера (или не найдены), но произошла ошибка при удалении пользователя из базы данных: {e}', 'error')
1368
 
1369
  return redirect(url_for('admin_panel'))
1370
 
 
1470
 
1471
  app.run(debug=False, host='0.0.0.0', port=7860)
1472
 
1473
+ # --- END OF FILE app (11).py ---