Eluza133 commited on
Commit
09b4841
·
verified ·
1 Parent(s): 63a3963

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +194 -106
app.py CHANGED
@@ -1,4 +1,4 @@
1
- from flask import Flask, render_template_string, request, redirect, url_for, session, flash
2
  from flask_caching import Cache
3
  import json
4
  import os
@@ -21,70 +21,53 @@ ADMIN_PASSWORD = "87132morflot"
21
  MAX_STORAGE_GB = 500
22
 
23
  cache = Cache(app, config={'CACHE_TYPE': 'simple'})
24
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
25
-
26
- # Проверка токена
27
- if not HF_TOKEN_WRITE:
28
- logging.error("HF_TOKEN_WRITE не установлен. Убедитесь, что переменная окружения HF_TOKEN задана.")
29
- else:
30
- logging.info(f"HF_TOKEN_WRITE установлен (первые 5 символов: {HF_TOKEN_WRITE[:5]}...)")
31
-
32
- # Инициализация файла базы данных только если он не существует
33
- def initialize_data_file():
34
- if not os.path.exists(DATA_FILE):
35
- logging.info(f"Файл {DATA_FILE} не найден, создается новый.")
36
- with open(DATA_FILE, 'w', encoding='utf-8') as f:
37
- json.dump({'users': {}, 'files': {}}, f)
38
 
 
39
  @cache.memoize(timeout=300)
40
  def load_data():
41
  try:
42
- # Сначала пытаемся загрузить локальный файл, если он существует
43
- if os.path.exists(DATA_FILE):
44
- with open(DATA_FILE, 'r', encoding='utf-8') as file:
45
- data = json.load(file)
46
- logging.info("Данные загружены из локального файла")
47
- else:
48
- # Если локального файла нет, скачиваем с Hugging Face
49
- download_db_from_hf()
50
- with open(DATA_FILE, 'r', encoding='utf-8') as file:
51
- data = json.load(file)
52
- logging.info("Данные загружены с Hugging Face")
53
-
54
- if not isinstance(data, dict):
55
- logging.warning("Данные не в формате dict, инициализация пустой базы")
56
- return {'users': {}, 'files': {}}
57
- data.setdefault('users', {})
58
- data.setdefault('files', {})
59
- for token, user_data in data['users'].items():
60
- if 'folders' in user_data:
61
- user_data['files'] = user_data['folders'].get('root', {}).get('files', [])
62
- del user_data['folders']
63
- if 'storage_used' not in user_data:
64
- user_data['storage_used'] = 0
65
- return data
66
  except Exception as e:
67
  logging.error(f"Ошибка при загрузке данных: {e}")
68
- initialize_data_file()
69
- with open(DATA_FILE, 'r', encoding='utf-8') as file:
70
- return json.load(file)
71
 
72
  def save_data(data):
73
  try:
74
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
75
  json.dump(data, file, ensure_ascii=False, indent=4)
76
- logging.info(f"Данные успешно сохранены в {DATA_FILE}")
77
  upload_db_to_hf()
78
  cache.clear()
 
79
  except Exception as e:
80
  logging.error(f"Ошибка при сохранении данных: {e}")
81
  raise
82
 
83
  def upload_db_to_hf():
84
  try:
85
- if not os.path.exists(DATA_FILE):
86
- logging.warning(f"Файл {DATA_FILE} не существует, пропускаем загрузку")
87
- return
88
  api = HfApi()
89
  api.upload_file(
90
  path_or_fileobj=DATA_FILE,
@@ -94,9 +77,9 @@ def upload_db_to_hf():
94
  token=HF_TOKEN_WRITE,
95
  commit_message=f"Бэкап {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
96
  )
97
- logging.info("База данных успешно загружена на Hugging Face")
98
  except Exception as e:
99
- logging.error(f"Ошибка при загрузке базы данных на Hugging Face: {e}")
100
 
101
  def download_db_from_hf():
102
  try:
@@ -108,20 +91,23 @@ def download_db_from_hf():
108
  local_dir=".",
109
  local_dir_use_symlinks=False
110
  )
111
- logging.info("База данных успешно скачана с Hugging Face")
112
  except Exception as e:
113
  logging.error(f"Ошибка при скачивании базы данных: {e}")
114
  if not os.path.exists(DATA_FILE):
115
- initialize_data_file()
 
116
 
117
  def periodic_backup():
118
  while True:
119
  upload_db_to_hf()
120
- time.sleep(1800) # 30 минут
121
 
 
122
  def generate_token():
123
  return ''.join(random.choices(string.ascii_letters + string.digits, k=13))
124
 
 
125
  def get_file_type(filename):
126
  video_extensions = ('.mp4', '.mov', '.avi')
127
  image_extensions = ('.jpg', '.jpeg', '.png', '.gif')
@@ -131,6 +117,7 @@ def get_file_type(filename):
131
  return 'image'
132
  return 'other'
133
 
 
134
  BASE_STYLE = '''
135
  :root {
136
  --primary: #ff4d6d;
@@ -220,6 +207,22 @@ input:focus, textarea:focus, select:focus {
220
  .download-btn:hover {
221
  background: #00b8c5;
222
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  .flash {
224
  color: var(--secondary);
225
  text-align: center;
@@ -231,7 +234,7 @@ input:focus, textarea:focus, select:focus {
231
  gap: 20px;
232
  margin-top: 20px;
233
  }
234
- .file-item {
235
  background: var(--card-bg);
236
  padding: 15px;
237
  border-radius: 16px;
@@ -239,12 +242,18 @@ input:focus, textarea:focus, select:focus {
239
  text-align: center;
240
  transition: var(--transition);
241
  }
242
- body.dark .file-item {
243
  background: var(--card-bg-dark);
244
  }
245
- .file-item:hover {
246
  transform: translateY(-5px);
247
  }
 
 
 
 
 
 
248
  .file-preview {
249
  max-width: 100%;
250
  max-height: 200px;
@@ -253,15 +262,15 @@ body.dark .file-item {
253
  margin-bottom: 10px;
254
  loading: lazy;
255
  }
256
- .file-item p {
257
  font-size: 0.9em;
258
  margin: 5px 0;
259
  }
260
- .file-item a {
261
  color: var(--primary);
262
  text-decoration: none;
263
  }
264
- .file-item a:hover {
265
  color: var(--accent);
266
  }
267
  .modal {
@@ -332,6 +341,7 @@ body.dark .progress-text, body.dark .storage-text {
332
  }
333
  '''
334
 
 
335
  @app.route('/admhosto', methods=['GET', 'POST'])
336
  def register():
337
  if request.method == 'POST':
@@ -341,7 +351,7 @@ def register():
341
  data = load_data()
342
  data['users'][token] = {
343
  'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
344
- 'files': [],
345
  'storage_used': 0
346
  }
347
  save_data(data)
@@ -352,7 +362,7 @@ def register():
352
 
353
  html = '''
354
  <!DOCTYPE html>
355
- <html lang="ru">
356
  <head>
357
  <meta charset="UTF-8">
358
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -380,6 +390,7 @@ def register():
380
  '''
381
  return render_template_string(html)
382
 
 
383
  @app.route('/', methods=['GET', 'POST'])
384
  def login():
385
  if request.method == 'POST':
@@ -387,13 +398,14 @@ def login():
387
  data = load_data()
388
  if token in data['users'] and len(token) == 13:
389
  session['token'] = token
 
390
  return redirect(url_for('dashboard'))
391
  else:
392
  flash('Неверный токен! Токен должен быть 13 символов.')
393
 
394
  html = '''
395
  <!DOCTYPE html>
396
- <html lang="ru">
397
  <head>
398
  <meta charset="UTF-8">
399
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -428,8 +440,19 @@ def login():
428
  '''
429
  return render_template_string(html)
430
 
 
 
 
 
 
 
 
 
 
 
431
  @app.route('/dashboard', methods=['GET', 'POST'])
432
- def dashboard():
 
433
  if 'token' not in session:
434
  flash('Пожалуйста, войдите!')
435
  return redirect(url_for('login'))
@@ -441,7 +464,7 @@ def dashboard():
441
  flash('Токен недействителен!')
442
  return redirect(url_for('login'))
443
 
444
- user_files = data['users'][token]['files']
445
  storage_used = data['users'][token]['storage_used']
446
  max_storage_bytes = MAX_STORAGE_GB * 1024 * 1024 * 1024
447
  storage_percent = (storage_used / max_storage_bytes) * 100 if max_storage_bytes > 0 else 0
@@ -449,61 +472,78 @@ def dashboard():
449
  storage_remaining_gb = MAX_STORAGE_GB - storage_used_gb
450
 
451
  if request.method == 'POST':
452
- if 'upload_files' in request.files:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  files = request.files.getlist('files')
454
  total_size = sum(f.content_length for f in files if f)
455
  if storage_used + total_size > max_storage_bytes:
456
  flash('Недостаточно места для загрузки файлов!')
457
  else:
458
- uploads_dir = 'uploads'
459
- os.makedirs(uploads_dir, exist_ok=True)
460
  for file in files:
461
  if file and file.filename:
462
  filename = secure_filename(file.filename)
463
- temp_path = os.path.join(uploads_dir, filename)
 
464
  file.save(temp_path)
465
  file_size = os.path.getsize(temp_path)
466
 
467
  api = HfApi()
468
- file_path = f"cloud_files/{token}_{filename}"
469
- try:
470
- logging.info(f"Попытка загрузки файла: {filename} в {file_path}")
471
- api.upload_file(
472
- path_or_fileobj=temp_path,
473
- path_in_repo=file_path,
474
- repo_id=REPO_ID,
475
- repo_type="dataset",
476
- token=HF_TOKEN_WRITE,
477
- commit_message=f"Загружен файл {filename} для {token}"
478
- )
479
- logging.info(f"Файл {filename} успешно загружен в Hugging Face")
480
- except Exception as e:
481
- logging.error(f"Ошибка при загрузке файла на Hugging Face: {e}")
482
- flash(f"Ошибка при загрузке файла {filename}: {str(e)}")
483
- if os.path.exists(temp_path):
484
- os.remove(temp_path)
485
- continue
486
 
487
  file_info = {
488
  'filename': filename,
489
  'path': file_path,
490
- ' éxtra': get_file_type(filename),
491
  'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
492
  'size': file_size
493
  }
494
- user_files.append(file_info)
495
  data['users'][token]['storage_used'] += file_size
496
 
497
  if os.path.exists(temp_path):
498
  os.remove(temp_path)
499
  save_data(data)
500
- flash('Файлы успешно загружены!')
501
 
502
- return redirect(url_for('dashboard'))
503
 
504
  html = '''
505
  <!DOCTYPE html>
506
- <html lang="ru">
507
  <head>
508
  <meta charset="UTF-8">
509
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -515,6 +555,7 @@ def dashboard():
515
  <div class="container">
516
  <h1>Zues Cloud Dashboard</h1>
517
  <p>Токен: {{ token }}</p>
 
518
  <div class="storage-indicator">
519
  <p>Использовано места: {{ "%.2f" % storage_used_gb }} ГБ из {{ MAX_STORAGE_GB }} ГБ (Осталось: {{ "%.2f" % storage_remaining_gb }} ГБ)</p>
520
  <div class="storage-bar-container">
@@ -522,7 +563,16 @@ def dashboard():
522
  <span class="storage-text">{{ "%.1f" % storage_percent }}%</span>
523
  </div>
524
  </div>
 
 
 
525
 
 
 
 
 
 
 
526
  <h2 style="margin-top: 20px;">Загрузить файлы</h2>
527
  {% with messages = get_flashed_messages() %}
528
  {% if messages %}
@@ -542,9 +592,20 @@ def dashboard():
542
  </div>
543
  </div>
544
 
545
- <h2 style="margin-top: 30px;">Файлы</h2>
546
  <div class="file-grid">
547
- {% for file in user_files %}
 
 
 
 
 
 
 
 
 
 
 
548
  <div class="file-item">
549
  {% if file['type'] == 'video' %}
550
  <video class="file-preview" preload="metadata" muted loading="lazy" onclick="openModal('https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}', true)">
@@ -557,10 +618,20 @@ def dashboard():
557
  {% endif %}
558
  <p>{{ file['upload_date'] }} ({{ "%.2f" % (file['size'] / (1024 * 1024)) }} МБ)</p>
559
  <a href="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" class="btn download-btn" download="{{ file['filename'] }}">Скачать</a>
 
 
 
 
 
 
 
 
 
 
560
  </div>
561
  {% endfor %}
562
- {% if not user_files %}
563
- <p>Файлов нет.</p>
564
  {% endif %}
565
  </div>
566
  <a href="{{ url_for('logout') }}" class="btn" style="margin-top: 20px;">Выйти</a>
@@ -586,19 +657,36 @@ def dashboard():
586
  modal.innerHTML = '<div id="modalContent"></div>';
587
  }
588
  }
589
- document.getElementById('upload-form').onsubmit = function(e) {
 
 
 
 
590
  const progressContainer = document.getElementById('upload-progress');
591
- progressContainer.style.display = 'block';
592
  const progressBar = document.getElementById('progress-bar');
593
  const progressText = document.getElementById('progress-text');
594
- let percent = 0;
595
- const interval = setInterval(() => {
596
- percent += 10;
597
- if (percent > 100) percent = 100;
598
- progressBar.style.width = percent + '%';
599
- progressText.textContent = percent + '%';
600
- if (percent === 100) clearInterval(interval);
601
- }, 200);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  };
603
  </script>
604
  </body>
@@ -607,7 +695,8 @@ def dashboard():
607
  return render_template_string(
608
  html,
609
  token=token,
610
- user_files=user_files,
 
611
  repo_id=REPO_ID,
612
  storage_used_gb=storage_used_gb,
613
  storage_remaining_gb=storage_remaining_gb,
@@ -615,13 +704,12 @@ def dashboard():
615
  MAX_STORAGE_GB=MAX_STORAGE_GB
616
  )
617
 
 
618
  @app.route('/logout')
619
  def logout():
620
  session.pop('token', None)
621
  return redirect(url_for('login'))
622
 
623
  if __name__ == '__main__':
624
- # Инициализация только если файла нет
625
- initialize_data_file()
626
  threading.Thread(target=periodic_backup, daemon=True).start()
627
  app.run(debug=True, host='0.0.0.0', port=7860)
 
1
+ from flask import Flask, render_template_string, request, redirect, url_for, session, flash, jsonify
2
  from flask_caching import Cache
3
  import json
4
  import os
 
21
  MAX_STORAGE_GB = 500
22
 
23
  cache = Cache(app, config={'CACHE_TYPE': 'simple'})
24
+ logging.basicConfig(level=logging.INFO)
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ # Функции для работы с базой данных и Hugging Face
27
  @cache.memoize(timeout=300)
28
  def load_data():
29
  try:
30
+ download_db_from_hf()
31
+ with open(DATA_FILE, 'r', encoding='utf-8') as file:
32
+ data = json.load(file)
33
+ if not isinstance(data, dict):
34
+ logging.warning("Данные не в формате dict, инициализация пустой базы")
35
+ return {'users': {}, 'files': {}}
36
+ data.setdefault('users', {})
37
+ data.setdefault('files', {})
38
+ for token, user_data in data['users'].items():
39
+ if 'folders' not in user_data:
40
+ files = user_data.get('files', [])
41
+ user_data['folders'] = {
42
+ 'root': {
43
+ 'name': 'root',
44
+ 'files': files,
45
+ 'subfolders': {}
46
+ }
47
+ }
48
+ if 'files' in user_data:
49
+ del user_data['files']
50
+ if 'storage_used' not in user_data:
51
+ user_data['storage_used'] = 0
52
+ logging.info("Данные успешно загружены")
53
+ return data
54
  except Exception as e:
55
  logging.error(f"Ошибка при загрузке данных: {e}")
56
+ return {'users': {}, 'files': {}}
 
 
57
 
58
  def save_data(data):
59
  try:
60
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
61
  json.dump(data, file, ensure_ascii=False, indent=4)
 
62
  upload_db_to_hf()
63
  cache.clear()
64
+ logging.info("Данные сохранены и загружены на HF")
65
  except Exception as e:
66
  logging.error(f"Ошибка при сохранении данных: {e}")
67
  raise
68
 
69
  def upload_db_to_hf():
70
  try:
 
 
 
71
  api = HfApi()
72
  api.upload_file(
73
  path_or_fileobj=DATA_FILE,
 
77
  token=HF_TOKEN_WRITE,
78
  commit_message=f"Бэкап {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
79
  )
80
+ logging.info("База данных загружена на Hugging Face")
81
  except Exception as e:
82
+ logging.error(f"Ошибка при загрузке базы данных: {e}")
83
 
84
  def download_db_from_hf():
85
  try:
 
91
  local_dir=".",
92
  local_dir_use_symlinks=False
93
  )
94
+ logging.info("База данных скачана с Hugging Face")
95
  except Exception as e:
96
  logging.error(f"Ошибка при скачивании базы данных: {e}")
97
  if not os.path.exists(DATA_FILE):
98
+ with open(DATA_FILE, 'w', encoding='utf-8') as f:
99
+ json.dump({'users': {}, 'files': {}}, f)
100
 
101
  def periodic_backup():
102
  while True:
103
  upload_db_to_hf()
104
+ time.sleep(1800)
105
 
106
+ # Генерация 13-значного токена
107
  def generate_token():
108
  return ''.join(random.choices(string.ascii_letters + string.digits, k=13))
109
 
110
+ # Определение типа файла для предпросмотра
111
  def get_file_type(filename):
112
  video_extensions = ('.mp4', '.mov', '.avi')
113
  image_extensions = ('.jpg', '.jpeg', '.png', '.gif')
 
117
  return 'image'
118
  return 'other'
119
 
120
+ # Базовый стиль CSS
121
  BASE_STYLE = '''
122
  :root {
123
  --primary: #ff4d6d;
 
207
  .download-btn:hover {
208
  background: #00b8c5;
209
  }
210
+ .edit-btn {
211
+ background: var(--accent);
212
+ padding: 8px 16px;
213
+ font-size: 0.9em;
214
+ }
215
+ .edit-btn:hover {
216
+ background: #7a4de3;
217
+ }
218
+ .delete-btn {
219
+ background: #ff3b30;
220
+ padding: 8px 16px;
221
+ font-size: 0.9em;
222
+ }
223
+ .delete-btn:hover {
224
+ background: #e02a20;
225
+ }
226
  .flash {
227
  color: var(--secondary);
228
  text-align: center;
 
234
  gap: 20px;
235
  margin-top: 20px;
236
  }
237
+ .folder-item, .file-item {
238
  background: var(--card-bg);
239
  padding: 15px;
240
  border-radius: 16px;
 
242
  text-align: center;
243
  transition: var(--transition);
244
  }
245
+ body.dark .folder-item, body.dark .file-item {
246
  background: var(--card-bg-dark);
247
  }
248
+ .folder-item:hover, .file-item:hover {
249
  transform: translateY(-5px);
250
  }
251
+ .folder-item {
252
+ background: rgba(139, 92, 246, 0.1);
253
+ }
254
+ body.dark .folder-item {
255
+ background: rgba(139, 92, 246, 0.2);
256
+ }
257
  .file-preview {
258
  max-width: 100%;
259
  max-height: 200px;
 
262
  margin-bottom: 10px;
263
  loading: lazy;
264
  }
265
+ .folder-item p, .file-item p {
266
  font-size: 0.9em;
267
  margin: 5px 0;
268
  }
269
+ .folder-item a, .file-item a {
270
  color: var(--primary);
271
  text-decoration: none;
272
  }
273
+ .folder-item a:hover, .file-item a:hover {
274
  color: var(--accent);
275
  }
276
  .modal {
 
341
  }
342
  '''
343
 
344
+ # Регистрация через /admhosto
345
  @app.route('/admhosto', methods=['GET', 'POST'])
346
  def register():
347
  if request.method == 'POST':
 
351
  data = load_data()
352
  data['users'][token] = {
353
  'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
354
+ 'folders': {'root': {'name': 'root', 'files': [], 'subfolders': {}}},
355
  'storage_used': 0
356
  }
357
  save_data(data)
 
362
 
363
  html = '''
364
  <!DOCTYPE html>
365
+ <html lang="en">
366
  <head>
367
  <meta charset="UTF-8">
368
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
390
  '''
391
  return render_template_string(html)
392
 
393
+ # Главная страница с вводом токена
394
  @app.route('/', methods=['GET', 'POST'])
395
  def login():
396
  if request.method == 'POST':
 
398
  data = load_data()
399
  if token in data['users'] and len(token) == 13:
400
  session['token'] = token
401
+ # Сохранение токена в localStorage перенесено в JavaScript
402
  return redirect(url_for('dashboard'))
403
  else:
404
  flash('Неверный токен! Токен должен быть 13 символов.')
405
 
406
  html = '''
407
  <!DOCTYPE html>
408
+ <html lang="en">
409
  <head>
410
  <meta charset="UTF-8">
411
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
440
  '''
441
  return render_template_string(html)
442
 
443
+ # Функция для получения папки по пути
444
+ def get_folder(data, token, folder_path):
445
+ current = data['users'][token]['folders']
446
+ if folder_path == 'root':
447
+ return current['root']
448
+ for folder_name in folder_path.split('/')[1:]:
449
+ current = current['subfolders'][folder_name]
450
+ return current
451
+
452
+ # Личный dashboard
453
  @app.route('/dashboard', methods=['GET', 'POST'])
454
+ @app.route('/dashboard/<path:folder_path>', methods=['GET', 'POST'])
455
+ def dashboard(folder_path='root'):
456
  if 'token' not in session:
457
  flash('Пожалуйста, войдите!')
458
  return redirect(url_for('login'))
 
464
  flash('Токен недействителен!')
465
  return redirect(url_for('login'))
466
 
467
+ current_folder = get_folder(data, token, folder_path)
468
  storage_used = data['users'][token]['storage_used']
469
  max_storage_bytes = MAX_STORAGE_GB * 1024 * 1024 * 1024
470
  storage_percent = (storage_used / max_storage_bytes) * 100 if max_storage_bytes > 0 else 0
 
472
  storage_remaining_gb = MAX_STORAGE_GB - storage_used_gb
473
 
474
  if request.method == 'POST':
475
+ if 'create_folder' in request.form:
476
+ folder_name = request.form.get('folder_name')
477
+ if folder_name and folder_name not in current_folder['subfolders']:
478
+ current_folder['subfolders'][folder_name] = {'name': folder_name, 'files': [], 'subfolders': {}}
479
+ save_data(data)
480
+
481
+ elif 'edit_folder' in request.form:
482
+ old_name = request.form.get('old_name')
483
+ new_name = request.form.get('new_name')
484
+ if old_name in current_folder['subfolders'] and new_name and new_name not in current_folder['subfolders']:
485
+ current_folder['subfolders'][new_name] = current_folder['subfolders'].pop(old_name)
486
+ current_folder['subfolders'][new_name]['name'] = new_name
487
+ save_data(data)
488
+
489
+ elif 'delete_folder' in request.form:
490
+ folder_name = request.form.get('folder_name')
491
+ if folder_name in current_folder['subfolders']:
492
+ del current_folder['subfolders'][folder_name]
493
+ save_data(data)
494
+
495
+ elif 'move_file' in request.form:
496
+ file_index = int(request.form.get('file_index'))
497
+ target_folder = request.form.get('target_folder')
498
+ file = current_folder['files'].pop(file_index)
499
+ target = get_folder(data, token, target_folder)
500
+ target['files'].append(file)
501
+ save_data(data)
502
+
503
+ elif 'upload_files' in request.files:
504
  files = request.files.getlist('files')
505
  total_size = sum(f.content_length for f in files if f)
506
  if storage_used + total_size > max_storage_bytes:
507
  flash('Недостаточно места для загрузки файлов!')
508
  else:
 
 
509
  for file in files:
510
  if file and file.filename:
511
  filename = secure_filename(file.filename)
512
+ temp_path = os.path.join('uploads', filename)
513
+ os.makedirs('uploads', exist_ok=True)
514
  file.save(temp_path)
515
  file_size = os.path.getsize(temp_path)
516
 
517
  api = HfApi()
518
+ file_path = f"cloud_files/{token}/{folder_path}/{filename}"
519
+ api.upload_file(
520
+ path_or_fileobj=temp_path,
521
+ path_in_repo=file_path,
522
+ repo_id=REPO_ID,
523
+ repo_type="dataset",
524
+ token=HF_TOKEN_WRITE,
525
+ commit_message=f"Загружен файл для {token} в {folder_path}"
526
+ )
 
 
 
 
 
 
 
 
 
527
 
528
  file_info = {
529
  'filename': filename,
530
  'path': file_path,
531
+ 'type': get_file_type(filename),
532
  'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
533
  'size': file_size
534
  }
535
+ current_folder['files'].append(file_info)
536
  data['users'][token]['storage_used'] += file_size
537
 
538
  if os.path.exists(temp_path):
539
  os.remove(temp_path)
540
  save_data(data)
 
541
 
542
+ return redirect(url_for('dashboard', folder_path=folder_path))
543
 
544
  html = '''
545
  <!DOCTYPE html>
546
+ <html lang="en">
547
  <head>
548
  <meta charset="UTF-8">
549
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
555
  <div class="container">
556
  <h1>Zues Cloud Dashboard</h1>
557
  <p>Токен: {{ token }}</p>
558
+ <p>Текущая папка: {{ folder_path }}</p>
559
  <div class="storage-indicator">
560
  <p>Использовано места: {{ "%.2f" % storage_used_gb }} ГБ из {{ MAX_STORAGE_GB }} ГБ (Осталось: {{ "%.2f" % storage_remaining_gb }} ГБ)</p>
561
  <div class="storage-bar-container">
 
563
  <span class="storage-text">{{ "%.1f" % storage_percent }}%</span>
564
  </div>
565
  </div>
566
+ {% if folder_path != 'root' %}
567
+ <a href="{{ url_for('dashboard', folder_path='/'.join(folder_path.split('/')[:-1]) or 'root') }}" class="btn">Назад</a>
568
+ {% endif %}
569
 
570
+ <h2 style="margin-top: 20px;">Создать папку</h2>
571
+ <form method="POST">
572
+ <input type="text" name="folder_name" placeholder="Название папки" required>
573
+ <button type="submit" name="create_folder" class="btn">Создать</button>
574
+ </form>
575
+
576
  <h2 style="margin-top: 20px;">Загрузить файлы</h2>
577
  {% with messages = get_flashed_messages() %}
578
  {% if messages %}
 
592
  </div>
593
  </div>
594
 
595
+ <h2 style="margin-top: 30px;">Папки и файлы</h2>
596
  <div class="file-grid">
597
+ {% for folder_name, folder in current_folder['subfolders'].items() %}
598
+ <div class="folder-item">
599
+ <p><a href="{{ url_for('dashboard', folder_path=folder_path + '/' + folder_name) }}">{{ folder_name }}</a></p>
600
+ <form method="POST" style="margin-top: 10px;">
601
+ <input type="text" name="new_name" placeholder="Новое имя" value="{{ folder_name }}">
602
+ <input type="hidden" name="old_name" value="{{ folder_name }}">
603
+ <button type="submit" name="edit_folder" class="btn edit-btn">Переименовать</button>
604
+ <button type="submit" name="delete_folder" class="btn delete-btn" onclick="return confirm('Удалить папку?');">Удалить</button>
605
+ </form>
606
+ </div>
607
+ {% endfor %}
608
+ {% for i, file in enumerate(current_folder['files']) %}
609
  <div class="file-item">
610
  {% if file['type'] == 'video' %}
611
  <video class="file-preview" preload="metadata" muted loading="lazy" onclick="openModal('https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}', true)">
 
618
  {% endif %}
619
  <p>{{ file['upload_date'] }} ({{ "%.2f" % (file['size'] / (1024 * 1024)) }} МБ)</p>
620
  <a href="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" class="btn download-btn" download="{{ file['filename'] }}">Скачать</a>
621
+ <form method="POST" style="margin-top: 10px;">
622
+ <input type="hidden" name="file_index" value="{{ i }}">
623
+ <select name="target_folder">
624
+ <option value="">Переместить в...</option>
625
+ {% for f_name in current_folder['subfolders'] %}
626
+ <option value="{{ folder_path + '/' + f_name }}">{{ f_name }}</option>
627
+ {% endfor %}
628
+ </select>
629
+ <button type="submit" name="move_file" class="btn edit-btn">Переместить</button>
630
+ </form>
631
  </div>
632
  {% endfor %}
633
+ {% if not current_folder['files'] and not current_folder['subfolders'] %}
634
+ <p>Папка пуста.</p>
635
  {% endif %}
636
  </div>
637
  <a href="{{ url_for('logout') }}" class="btn" style="margin-top: 20px;">Выйти</a>
 
657
  modal.innerHTML = '<div id="modalContent"></div>';
658
  }
659
  }
660
+ document.getElementById('upload-form').onsubmit = async function(e) {
661
+ e.preventDefault();
662
+ const formData = new FormData(this);
663
+ const files = formData.getAll('files');
664
+ const totalFiles = files.length;
665
  const progressContainer = document.getElementById('upload-progress');
 
666
  const progressBar = document.getElementById('progress-bar');
667
  const progressText = document.getElementById('progress-text');
668
+
669
+ progressContainer.style.display = 'block';
670
+ let uploadedFiles = 0;
671
+
672
+ const xhr = new XMLHttpRequest();
673
+ xhr.open('POST', window.location.pathname, true);
674
+ xhr.upload.onprogress = function(event) {
675
+ if (event.lengthComputable) {
676
+ const percent = Math.round((event.loaded / event.total) * 100);
677
+ progressBar.style.width = percent + '%';
678
+ progressText.textContent = `Загрузка ${uploadedFiles + 1}/${totalFiles} (${percent}%)`;
679
+ }
680
+ };
681
+ xhr.onload = function() {
682
+ uploadedFiles++;
683
+ if (uploadedFiles === totalFiles) {
684
+ window.location.reload();
685
+ } else {
686
+ progressText.textContent = `Загружено ${uploadedFiles}/${totalFiles}`;
687
+ }
688
+ };
689
+ xhr.send(formData);
690
  };
691
  </script>
692
  </body>
 
695
  return render_template_string(
696
  html,
697
  token=token,
698
+ current_folder=current_folder,
699
+ folder_path=folder_path,
700
  repo_id=REPO_ID,
701
  storage_used_gb=storage_used_gb,
702
  storage_remaining_gb=storage_remaining_gb,
 
704
  MAX_STORAGE_GB=MAX_STORAGE_GB
705
  )
706
 
707
+ # Выход
708
  @app.route('/logout')
709
  def logout():
710
  session.pop('token', None)
711
  return redirect(url_for('login'))
712
 
713
  if __name__ == '__main__':
 
 
714
  threading.Thread(target=periodic_backup, daemon=True).start()
715
  app.run(debug=True, host='0.0.0.0', port=7860)