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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -114
app.py CHANGED
@@ -227,7 +227,6 @@ 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
- .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,9 +261,9 @@ body.dark .modal-content { background: var(--card-bg-dark); }
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; }
@@ -559,13 +558,10 @@ def dashboard():
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">
@@ -701,20 +697,21 @@ def dashboard():
701
  }
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
  }
@@ -737,10 +734,9 @@ def dashboard():
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
 
@@ -751,15 +747,14 @@ def dashboard():
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
 
@@ -829,23 +824,27 @@ def create_folder():
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)
@@ -857,17 +856,14 @@ def download_file(file_id):
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,14 +885,11 @@ def download_file(file_id):
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,21 +1018,23 @@ def delete_folder(folder_id):
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,11 +1090,7 @@ def logout():
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,16 +1104,14 @@ 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,11 +1130,9 @@ def admin_panel():
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,18 +1150,18 @@ def admin_user_files(username):
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,7 +1239,7 @@ body.dark .file-item { background: var(--card-bg-dark); }
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,9 +1276,6 @@ def admin_delete_user(username):
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,62 +1293,35 @@ def admin_delete_user(username):
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
 
 
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
  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; position: relative; height: 20px; }
265
+ #progress-bar { width: 0%; height: 100%; background: var(--primary); border-radius: 10px; transition: width 0.3s ease; }
266
+ #progress-text { position: absolute; width: 100%; text-align: center; line-height: 20px; color: white; font-size: 0.9em; font-weight: bold; text-shadow: 1px 1px 1px rgba(0,0,0,0.5); }
267
  .breadcrumbs { margin-bottom: 20px; font-size: 1.1em; }
268
  .breadcrumbs a { color: var(--accent); text-decoration: none; }
269
  .breadcrumbs a:hover { text-decoration: underline; }
 
558
 
559
  <form id="upload-form" method="POST" enctype="multipart/form-data" action="{{ url_for('dashboard') }}">
560
  <input type="hidden" name="current_folder_id" value="{{ current_folder_id }}">
561
+ <input type="file" name="files" id="file-input" multiple required>
562
  <button type="submit" class="btn" id="upload-btn">Загрузить файлы сюда</button>
563
  </form>
564
+ <div id="progress-container"><div id="progress-bar"></div><div id="progress-text">0%</div></div>
 
 
 
565
 
566
  <h2>Содержимое папки: {{ current_folder.name if current_folder_id != 'root' else 'Главная' }}</h2>
567
  <div class="file-grid">
 
697
  }
698
 
699
  const form = document.getElementById('upload-form');
700
+ const fileInput = document.getElementById('file-input');
701
  const progressBar = document.getElementById('progress-bar');
702
  const progressText = document.getElementById('progress-text');
703
  const progressContainer = document.getElementById('progress-container');
704
  const uploadBtn = document.getElementById('upload-btn');
 
705
 
706
  form.addEventListener('submit', function(e) {
707
  e.preventDefault();
708
 
709
+ const files = fileInput.files;
710
+ if (files.length === 0) {
711
  alert('Пожалуйста, выберите файлы для загрузки.');
712
  return;
713
  }
714
+ if (files.length > 20) {
715
  alert('Максимум 20 файлов за раз!');
716
  return;
717
  }
 
734
  });
735
 
736
  xhr.addEventListener('load', function() {
737
+ uploadBtn.disabled = false;
738
+ uploadBtn.textContent = 'Загрузить файлы сюда';
739
+ progressContainer.style.display = 'none';
 
740
  window.location.reload();
741
  });
742
 
 
747
  progressContainer.style.display = 'none';
748
  });
749
 
750
+ xhr.addEventListener('abort', function() {
751
  alert('Загрузка отменена.');
752
  uploadBtn.disabled = false;
753
  uploadBtn.textContent = 'Загрузить файлы сюда';
754
  progressContainer.style.display = 'none';
755
  });
756
 
757
+ xhr.open('POST', form.action, true);
 
758
  xhr.send(formData);
759
  });
760
 
 
824
 
825
  @app.route('/download/<file_id>')
826
  def download_file(file_id):
827
+ if 'username' not in session:
828
+ is_admin_route = request.referrer and 'admhosto' in request.referrer
829
+ if not is_admin_route:
830
+ flash('Пожалуйста, войдите в систему!')
831
+ return redirect(url_for('login'))
832
+ elif not is_admin():
833
+ flash('Доступ запрещен (Admin).')
834
+ return redirect(url_for('login'))
835
 
 
 
 
836
 
837
  data = load_data()
838
  file_node = None
839
+ username_context = None
840
 
841
+ if 'username' in session:
842
+ username_context = session['username']
843
  user_data = data['users'].get(username_context)
844
  if user_data:
845
+ file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
846
 
847
+ if not file_node and is_admin():
848
  logging.info(f"Admin searching for file ID {file_id} across all users.")
849
  for uname, udata in data.get('users', {}).items():
850
  node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
 
856
 
857
  if not file_node or file_node.get('type') != 'file':
858
  flash('Файл не найден!', 'error')
859
+ return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
 
 
860
 
861
  hf_path = file_node.get('path')
862
  original_filename = file_node.get('original_filename', 'downloaded_file')
863
 
864
  if not hf_path:
865
  flash('Ошибка: Путь к файлу не найден в метаданных.', 'error')
866
+ return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
 
867
 
868
  file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
869
 
 
885
  except requests.exceptions.RequestException as e:
886
  logging.error(f"Error downloading file from HF ({hf_path}): {e}")
887
  flash(f'Ошибка скачивания файла {original_filename}! ({e})', 'error')
888
+ return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
 
889
  except Exception as e:
890
  logging.error(f"Unexpected error during download ({hf_path}): {e}")
891
  flash('Произошла непредвиденная ошибка при скачивании файла.', 'error')
892
+ return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
 
 
893
 
894
 
895
  @app.route('/delete_file/<file_id>', methods=['POST'])
 
1018
 
1019
  @app.route('/get_text_content/<file_id>')
1020
  def get_text_content(file_id):
1021
+ if 'username' not in session:
1022
+ if not is_admin():
1023
+ return Response("Не авторизован", status=401)
1024
+ else:
1025
+ pass
1026
 
1027
  data = load_data()
1028
  file_node = None
1029
+ username_context = None
1030
 
1031
+ if 'username' in session:
1032
+ username_context = session['username']
1033
  user_data = data['users'].get(username_context)
1034
  if user_data:
1035
+ file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
1036
 
1037
+ if not file_node and is_admin():
1038
  logging.info(f"Admin searching for text file ID {file_id} across all users.")
1039
  for uname, udata in data.get('users', {}).items():
1040
  node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
 
1090
 
1091
 
1092
  def is_admin():
1093
+ return 'username' in session
 
 
 
 
1094
 
1095
  @app.route('/admhosto')
1096
  def admin_panel():
 
1104
  user_details = []
1105
  for uname, udata in users.items():
1106
  file_count = 0
1107
+ q = [udata.get('filesystem', {}).get('children', [])]
1108
+ while q:
1109
+ current_level = q.pop(0)
1110
+ for item in current_level:
1111
+ if item.get('type') == 'file':
1112
+ file_count += 1
1113
+ elif item.get('type') == 'folder' and 'children' in item:
1114
+ q.append(item.get('children', []))
 
 
1115
  user_details.append({
1116
  'username': uname,
1117
  'created_at': udata.get('created_at', 'N/A'),
 
1130
  <a href="{{ url_for('admin_user_files', username=user.username) }}">{{ user.username }}</a>
1131
  <p>Зарегистрирован: {{ user.created_at }}</p>
1132
  <p>Файлов: {{ user.file_count }}</p>
 
1133
  <form method="POST" action="{{ url_for('admin_delete_user', username=user.username) }}" style="display: inline; margin-left: 10px;" onsubmit="return confirm('УДАЛИТЬ пользователя {{ user.username }} и ВСЕ его файлы? НЕОБРАТИМО!');">
1134
  <button type="submit" class="btn delete-btn" style="padding: 5px 10px; font-size: 0.9em;">Удалить</button>
1135
  </form>
 
1136
  </div>
1137
  {% else %}<p>Пользователей нет.</p>{% endfor %}</div></div></body></html>'''
1138
  return render_template_string(html, user_details=user_details)
 
1150
  return redirect(url_for('admin_panel'))
1151
 
1152
  all_files = []
1153
+ def collect_files(folder, current_path_id='root'):
1154
+ parent_node, _ = find_node_by_id(user_data['filesystem'], current_path_id)
1155
+ parent_path_str = get_node_path_string(user_data['filesystem'], current_path_id)
1156
 
1157
  for item in folder.get('children', []):
1158
  if item.get('type') == 'file':
1159
+ item['parent_path_str'] = parent_path_str
1160
  all_files.append(item)
1161
  elif item.get('type') == 'folder':
1162
+ collect_files(item, item.get('id'))
1163
 
1164
+ collect_files(user_data.get('filesystem', {}))
1165
  all_files.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
1166
 
1167
 
 
1239
  const response = await fetch(srcOrUrl);
1240
  if (!response.ok) throw new Error(`Ошибка загрузки текста: ${response.statusText}`);
1241
  const text = await response.text();
1242
+ const escapedText = text.replace(/</g, "<").replace(/>/g, ">");
1243
  modalContent.innerHTML = `<pre>${escapedText}</pre>`;
1244
  } else {
1245
  modalContent.innerHTML = '<p>Предпросмотр для этого типа файла не поддерживается.</p>';
 
1276
  if not is_admin():
1277
  flash('Доступ запрещен.', 'error')
1278
  return redirect(url_for('login'))
 
 
 
1279
  if not HF_TOKEN_WRITE:
1280
  flash('Удаление невозможно: токен для записи не настроен.', 'error')
1281
  return redirect(url_for('admin_panel'))
 
1293
  user_folder_path_on_hf = f"cloud_files/{username}"
1294
 
1295
  logging.info(f"Attempting to delete HF Hub folder: {user_folder_path_on_hf} for user {username}")
1296
+ api.delete_folder(
1297
+ folder_path=user_folder_path_on_hf,
1298
+ repo_id=REPO_ID,
1299
+ repo_type="dataset",
1300
+ token=HF_TOKEN_WRITE,
1301
+ commit_message=f"ADMIN ACTION: Deleted all files/folders for user {username}"
1302
+ )
1303
+ logging.info(f"Successfully initiated deletion of folder {user_folder_path_on_hf} on HF Hub.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1304
 
1305
  except hf_utils.HfHubHTTPError as e:
1306
+ if e.response.status_code == 404:
1307
+ logging.warning(f"User folder {user_folder_path_on_hf} not found on HF Hub for user {username}. Skipping HF deletion.")
 
1308
  else:
1309
+ logging.error(f"Error deleting user folder {user_folder_path_on_hf} from HF Hub for {username}: {e}")
1310
  flash(f'Ошибка при удалении файлов пользователя {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
1311
  return redirect(url_for('admin_panel'))
1312
  except Exception as e:
1313
+ logging.error(f"Unexpected error during HF Hub folder deletion for {username}: {e}")
1314
  flash(f'Неожиданная ошибка при удалении файлов {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
1315
  return redirect(url_for('admin_panel'))
1316
 
 
1317
  try:
1318
  del data['users'][username]
1319
  save_data(data)
1320
+ flash(f'Пользователь {username} и его файлы (запрос на удаление отправлен) успешно удалены из базы данных!')
1321
  logging.info(f"ADMIN ACTION: Successfully deleted user {username} from database.")
1322
  except Exception as e:
1323
  logging.error(f"Error saving data after deleting user {username}: {e}")
1324
+ flash(f'Файлы пользователя {username} удалены с сервера, но произошла ошибка при удалении пользователя из базы данных: {e}', 'error')
1325
 
1326
  return redirect(url_for('admin_panel'))
1327