Eluza133 commited on
Commit
d9eaa97
·
verified ·
1 Parent(s): ee5bf70

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +252 -53
app.py CHANGED
@@ -14,7 +14,7 @@ import string
14
  app = Flask(__name__)
15
  app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey")
16
  DATA_FILE = 'cloud_data.json'
17
- REPO_ID = "Eluza133/Z1e1u" # Новый репозиторий
18
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
19
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE
20
  ADMIN_PASSWORD = "87132morflot"
@@ -86,7 +86,7 @@ def download_db_from_hf():
86
  def periodic_backup():
87
  while True:
88
  upload_db_to_hf()
89
- time.sleep(1800) # Бэкап каждые 30 минут
90
 
91
  # Генерация 13-значного токена
92
  def generate_token():
@@ -149,7 +149,7 @@ h1 {
149
  -webkit-background-clip: text;
150
  color: transparent;
151
  }
152
- input, textarea {
153
  width: 100%;
154
  padding: 14px;
155
  margin: 12px 0;
@@ -160,10 +160,10 @@ input, textarea {
160
  font-size: 1.1em;
161
  box-shadow: inset 0 3px 10px rgba(0, 0, 0, 0.1);
162
  }
163
- body.dark input, body.dark textarea {
164
  color: var(--text-dark);
165
  }
166
- input:focus, textarea:focus {
167
  outline: none;
168
  box-shadow: 0 0 0 4px var(--primary);
169
  }
@@ -192,6 +192,22 @@ input:focus, textarea:focus {
192
  .download-btn:hover {
193
  background: #00b8c5;
194
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  .flash {
196
  color: var(--secondary);
197
  text-align: center;
@@ -199,11 +215,11 @@ input:focus, textarea:focus {
199
  }
200
  .file-grid {
201
  display: grid;
202
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
203
  gap: 20px;
204
  margin-top: 20px;
205
  }
206
- .file-item {
207
  background: var(--card-bg);
208
  padding: 15px;
209
  border-radius: 16px;
@@ -211,12 +227,18 @@ input:focus, textarea:focus {
211
  text-align: center;
212
  transition: var(--transition);
213
  }
214
- body.dark .file-item {
215
  background: var(--card-bg-dark);
216
  }
217
- .file-item:hover {
218
  transform: translateY(-5px);
219
  }
 
 
 
 
 
 
220
  .file-preview {
221
  max-width: 100%;
222
  max-height: 200px;
@@ -225,15 +247,15 @@ body.dark .file-item {
225
  margin-bottom: 10px;
226
  loading: lazy;
227
  }
228
- .file-item p {
229
  font-size: 0.9em;
230
  margin: 5px 0;
231
  }
232
- .file-item a {
233
  color: var(--primary);
234
  text-decoration: none;
235
  }
236
- .file-item a:hover {
237
  color: var(--accent);
238
  }
239
  .modal {
@@ -255,6 +277,51 @@ body.dark .file-item {
255
  border-radius: 20px;
256
  box-shadow: var(--shadow);
257
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  '''
259
 
260
  # Регистрация через /admhosto
@@ -267,7 +334,7 @@ def register():
267
  data = load_data()
268
  data['users'][token] = {
269
  'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
270
- 'files': []
271
  }
272
  save_data(data)
273
  flash(f'Ваш токен: {token}. Сохраните его!')
@@ -300,6 +367,12 @@ def register():
300
  <button type="submit" class="btn">Зарегистрироваться</button>
301
  </form>
302
  </div>
 
 
 
 
 
 
303
  </body>
304
  </html>
305
  '''
@@ -337,20 +410,40 @@ def login():
337
  {% endfor %}
338
  {% endif %}
339
  {% endwith %}
340
- <form method="POST">
341
  <input type="text" name="token" placeholder="Введите ваш токен" required>
342
  <button type="submit" class="btn">Войти</button>
343
  </form>
344
  <p style="margin-top: 20px;">Нет токена? <a href="{{ url_for('register') }}">Зарегистрируйтесь</a></p>
345
  </div>
 
 
 
 
 
 
 
 
 
 
346
  </body>
347
  </html>
348
  '''
349
  return render_template_string(html)
350
 
 
 
 
 
 
 
 
 
 
351
  # Личный dashboard
352
  @app.route('/dashboard', methods=['GET', 'POST'])
353
- def dashboard():
 
354
  if 'token' not in session:
355
  flash('Пожалуйста, войдите!')
356
  return redirect(url_for('login'))
@@ -362,40 +455,70 @@ def dashboard():
362
  flash('Токен недействителен!')
363
  return redirect(url_for('login'))
364
 
365
- user_files = data['users'][token]['files']
366
 
367
  if request.method == 'POST':
368
- file = request.files.get('file')
369
- if file and file.filename:
370
- filename = secure_filename(file.filename)
371
- temp_path = os.path.join('uploads', filename)
372
- os.makedirs('uploads', exist_ok=True)
373
- file.save(temp_path)
374
-
375
- api = HfApi()
376
- file_path = f"cloud_files/{token}/{filename}"
377
- api.upload_file(
378
- path_or_fileobj=temp_path,
379
- path_in_repo=file_path,
380
- repo_id=REPO_ID,
381
- repo_type="dataset",
382
- token=HF_TOKEN_WRITE,
383
- commit_message=f"Загружен файл для {token}"
384
- )
385
-
386
- file_info = {
387
- 'filename': filename,
388
- 'path': file_path,
389
- 'type': get_file_type(filename),
390
- 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
391
- }
392
- data['users'][token]['files'].append(file_info)
 
393
  save_data(data)
394
-
395
- if os.path.exists(temp_path):
396
- os.remove(temp_path)
397
 
398
- return redirect(url_for('dashboard'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
  html = '''
401
  <!DOCTYPE html>
@@ -411,13 +534,43 @@ def dashboard():
411
  <div class="container">
412
  <h1>Zues Cloud Dashboard</h1>
413
  <p>Токен: {{ token }}</p>
414
- <form method="POST" enctype="multipart/form-data">
415
- <input type="file" name="file" required>
416
- <button type="submit" class="btn">Загрузить файл</button>
 
 
 
 
 
 
 
 
 
 
 
 
417
  </form>
418
- <h2 style="margin-top: 30px;">Ваши файлы</h2>
 
 
 
 
 
 
 
419
  <div class="file-grid">
420
- {% for file in user_files %}
 
 
 
 
 
 
 
 
 
 
 
421
  <div class="file-item">
422
  {% if file['type'] == 'video' %}
423
  <video class="file-preview" preload="metadata" muted loading="lazy" onclick="openModal('https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}', true)">
@@ -430,10 +583,20 @@ def dashboard():
430
  {% endif %}
431
  <p>{{ file['upload_date'] }}</p>
432
  <a href="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" class="btn download-btn" download="{{ file['filename'] }}">Скачать</a>
 
 
 
 
 
 
 
 
 
 
433
  </div>
434
  {% endfor %}
435
- {% if not user_files %}
436
- <p вас пока нет загруженных файлов.</p>
437
  {% endif %}
438
  </div>
439
  <a href="{{ url_for('logout') }}" class="btn" style="margin-top: 20px;">Выйти</a>
@@ -459,11 +622,47 @@ def dashboard():
459
  modal.innerHTML = '<div id="modalContent"></div>';
460
  }
461
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  </script>
463
  </body>
464
  </html>
465
  '''
466
- return render_template_string(html, token=token, user_files=user_files, repo_id=REPO_ID)
467
 
468
  # Выход
469
  @app.route('/logout')
 
14
  app = Flask(__name__)
15
  app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey")
16
  DATA_FILE = 'cloud_data.json'
17
+ REPO_ID = "Eluza133/Z1e1u"
18
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
19
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE
20
  ADMIN_PASSWORD = "87132morflot"
 
86
  def periodic_backup():
87
  while True:
88
  upload_db_to_hf()
89
+ time.sleep(1800)
90
 
91
  # Генерация 13-значного токена
92
  def generate_token():
 
149
  -webkit-background-clip: text;
150
  color: transparent;
151
  }
152
+ input, textarea, select {
153
  width: 100%;
154
  padding: 14px;
155
  margin: 12px 0;
 
160
  font-size: 1.1em;
161
  box-shadow: inset 0 3px 10px rgba(0, 0, 0, 0.1);
162
  }
163
+ body.dark input, body.dark textarea, body.dark select {
164
  color: var(--text-dark);
165
  }
166
+ input:focus, textarea:focus, select:focus {
167
  outline: none;
168
  box-shadow: 0 0 0 4px var(--primary);
169
  }
 
192
  .download-btn:hover {
193
  background: #00b8c5;
194
  }
195
+ .edit-btn {
196
+ background: var(--accent);
197
+ padding: 8px 16px;
198
+ font-size: 0.9em;
199
+ }
200
+ .edit-btn:hover {
201
+ background: #7a4de3;
202
+ }
203
+ .delete-btn {
204
+ background: #ff3b30;
205
+ padding: 8px 16px;
206
+ font-size: 0.9em;
207
+ }
208
+ .delete-btn:hover {
209
+ background: #e02a20;
210
+ }
211
  .flash {
212
  color: var(--secondary);
213
  text-align: center;
 
215
  }
216
  .file-grid {
217
  display: grid;
218
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
219
  gap: 20px;
220
  margin-top: 20px;
221
  }
222
+ .folder-item, .file-item {
223
  background: var(--card-bg);
224
  padding: 15px;
225
  border-radius: 16px;
 
227
  text-align: center;
228
  transition: var(--transition);
229
  }
230
+ body.dark .folder-item, body.dark .file-item {
231
  background: var(--card-bg-dark);
232
  }
233
+ .folder-item:hover, .file-item:hover {
234
  transform: translateY(-5px);
235
  }
236
+ .folder-item {
237
+ background: rgba(139, 92, 246, 0.1);
238
+ }
239
+ body.dark .folder-item {
240
+ background: rgba(139, 92, 246, 0.2);
241
+ }
242
  .file-preview {
243
  max-width: 100%;
244
  max-height: 200px;
 
247
  margin-bottom: 10px;
248
  loading: lazy;
249
  }
250
+ .folder-item p, .file-item p {
251
  font-size: 0.9em;
252
  margin: 5px 0;
253
  }
254
+ .folder-item a, .file-item a {
255
  color: var(--primary);
256
  text-decoration: none;
257
  }
258
+ .folder-item a:hover, .file-item a:hover {
259
  color: var(--accent);
260
  }
261
  .modal {
 
277
  border-radius: 20px;
278
  box-shadow: var(--shadow);
279
  }
280
+ .upload-progress {
281
+ width: 100%;
282
+ margin: 20px 0;
283
+ display: none;
284
+ }
285
+ .progress-bar-container {
286
+ width: 100%;
287
+ height: 20px;
288
+ background: var(--glass-bg);
289
+ border-radius: 10px;
290
+ overflow: hidden;
291
+ position: relative;
292
+ }
293
+ .progress-bar {
294
+ height: 100%;
295
+ width: 0%;
296
+ background: linear-gradient(90deg, var(--primary), var(--accent));
297
+ transition: width 0.3s ease;
298
+ }
299
+ .progress-text {
300
+ position: absolute;
301
+ width: 100%;
302
+ text-align: center;
303
+ font-size: 0.9em;
304
+ color: var(--text-light);
305
+ top: 50%;
306
+ transform: translateY(-50%);
307
+ }
308
+ body.dark .progress-text {
309
+ color: var(--text-dark);
310
+ }
311
+ @media (max-width: 768px) {
312
+ .file-grid {
313
+ grid-template-columns: repeat(2, 1fr);
314
+ }
315
+ }
316
+ @media (max-width: 480px) {
317
+ .file-grid {
318
+ grid-template-columns: 1fr;
319
+ }
320
+ .btn {
321
+ padding: 12px 20px;
322
+ font-size: 1em;
323
+ }
324
+ }
325
  '''
326
 
327
  # Регистрация через /admhosto
 
334
  data = load_data()
335
  data['users'][token] = {
336
  'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
337
+ 'folders': {'root': {'name': 'root', 'files': [], 'subfolders': {}}}
338
  }
339
  save_data(data)
340
  flash(f'Ваш токен: {token}. Сохраните его!')
 
367
  <button type="submit" class="btn">Зарегистрироваться</button>
368
  </form>
369
  </div>
370
+ <script>
371
+ window.onload = () => {
372
+ const token = localStorage.getItem('token');
373
+ if (token) window.location = '/dashboard';
374
+ };
375
+ </script>
376
  </body>
377
  </html>
378
  '''
 
410
  {% endfor %}
411
  {% endif %}
412
  {% endwith %}
413
+ <form method="POST" id="login-form">
414
  <input type="text" name="token" placeholder="Введите ваш токен" required>
415
  <button type="submit" class="btn">Войти</button>
416
  </form>
417
  <p style="margin-top: 20px;">Нет токена? <a href="{{ url_for('register') }}">Зарегистрируйтесь</a></p>
418
  </div>
419
+ <script>
420
+ window.onload = () => {
421
+ const token = localStorage.getItem('token');
422
+ if (token) window.location = '/dashboard';
423
+ document.getElementById('login-form').onsubmit = (e) => {
424
+ const token = e.target.querySelector('input[name="token"]').value;
425
+ localStorage.setItem('token', token);
426
+ };
427
+ };
428
+ </script>
429
  </body>
430
  </html>
431
  '''
432
  return render_template_string(html)
433
 
434
+ # Функция для получения папки по пути
435
+ def get_folder(data, token, folder_path):
436
+ current = data['users'][token]['folders']
437
+ if folder_path == 'root':
438
+ return current['root']
439
+ for folder_name in folder_path.split('/')[1:]:
440
+ current = current['subfolders'][folder_name]
441
+ return current
442
+
443
  # Личный dashboard
444
  @app.route('/dashboard', methods=['GET', 'POST'])
445
+ @app.route('/dashboard/<path:folder_path>', methods=['GET', 'POST'])
446
+ def dashboard(folder_path='root'):
447
  if 'token' not in session:
448
  flash('Пожалуйста, войдите!')
449
  return redirect(url_for('login'))
 
455
  flash('Токен недействителен!')
456
  return redirect(url_for('login'))
457
 
458
+ current_folder = get_folder(data, token, folder_path)
459
 
460
  if request.method == 'POST':
461
+ if 'create_folder' in request.form:
462
+ folder_name = request.form.get('folder_name')
463
+ if folder_name and folder_name not in current_folder['subfolders']:
464
+ current_folder['subfolders'][folder_name] = {'name': folder_name, 'files': [], 'subfolders': {}}
465
+ save_data(data)
466
+
467
+ elif 'edit_folder' in request.form:
468
+ old_name = request.form.get('old_name')
469
+ new_name = request.form.get('new_name')
470
+ if old_name in current_folder['subfolders'] and new_name and new_name not in current_folder['subfolders']:
471
+ current_folder['subfolders'][new_name] = current_folder['subfolders'].pop(old_name)
472
+ current_folder['subfolders'][new_name]['name'] = new_name
473
+ save_data(data)
474
+
475
+ elif 'delete_folder' in request.form:
476
+ folder_name = request.form.get('folder_name')
477
+ if folder_name in current_folder['subfolders']:
478
+ del current_folder['subfolders'][folder_name]
479
+ save_data(data)
480
+
481
+ elif 'move_file' in request.form:
482
+ file_index = int(request.form.get('file_index'))
483
+ target_folder = request.form.get('target_folder')
484
+ file = current_folder['files'].pop(file_index)
485
+ target = get_folder(data, token, target_folder)
486
+ target['files'].append(file)
487
  save_data(data)
 
 
 
488
 
489
+ elif 'upload_files' in request.files:
490
+ files = request.files.getlist('files')
491
+ for file in files:
492
+ if file and file.filename:
493
+ filename = secure_filename(file.filename)
494
+ temp_path = os.path.join('uploads', filename)
495
+ os.makedirs('uploads', exist_ok=True)
496
+ file.save(temp_path)
497
+
498
+ api = HfApi()
499
+ file_path = f"cloud_files/{token}/{folder_path}/{filename}"
500
+ api.upload_file(
501
+ path_or_fileobj=temp_path,
502
+ path_in_repo=file_path,
503
+ repo_id=REPO_ID,
504
+ repo_type="dataset",
505
+ token=HF_TOKEN_WRITE,
506
+ commit_message=f"Загружен файл для {token} в {folder_path}"
507
+ )
508
+
509
+ file_info = {
510
+ 'filename': filename,
511
+ 'path': file_path,
512
+ 'type': get_file_type(filename),
513
+ 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
514
+ }
515
+ current_folder['files'].append(file_info)
516
+
517
+ if os.path.exists(temp_path):
518
+ os.remove(temp_path)
519
+ save_data(data)
520
+
521
+ return redirect(url_for('dashboard', folder_path=folder_path))
522
 
523
  html = '''
524
  <!DOCTYPE html>
 
534
  <div class="container">
535
  <h1>Zues Cloud Dashboard</h1>
536
  <p>Токен: {{ token }}</p>
537
+ <p>Текущая папка: {{ folder_path }}</p>
538
+ {% if folder_path != 'root' %}
539
+ <a href="{{ url_for('dashboard', folder_path='/'.join(folder_path.split('/')[:-1]) or 'root') }}" class="btn">Назад</a>
540
+ {% endif %}
541
+
542
+ <h2 style="margin-top: 20px;">Создать папку</h2>
543
+ <form method="POST">
544
+ <input type="text" name="folder_name" placeholder="Название папки" required>
545
+ <button type="submit" name="create_folder" class="btn">Создать</button>
546
+ </form>
547
+
548
+ <h2 style="margin-top: 20px;">Загрузить файлы</h2>
549
+ <form method="POST" enctype="multipart/form-data" id="upload-form">
550
+ <input type="file" name="files" multiple required>
551
+ <button type="submit" name="upload_files" class="btn">Загрузить</button>
552
  </form>
553
+ <div class="upload-progress" id="upload-progress">
554
+ <div class="progress-bar-container">
555
+ <div class="progress-bar" id="progress-bar"></div>
556
+ <span class="progress-text" id="progress-text">0%</span>
557
+ </div>
558
+ </div>
559
+
560
+ <h2 style="margin-top: 30px;">Папки и файлы</h2>
561
  <div class="file-grid">
562
+ {% for folder_name, folder in current_folder['subfolders'].items() %}
563
+ <div class="folder-item">
564
+ <p><a href="{{ url_for('dashboard', folder_path=folder_path + '/' + folder_name) }}">{{ folder_name }}</a></p>
565
+ <form method="POST" style="margin-top: 10px;">
566
+ <input type="text" name="new_name" placeholder="Новое имя" value="{{ folder_name }}">
567
+ <input type="hidden" name="old_name" value="{{ folder_name }}">
568
+ <button type="submit" name="edit_folder" class="btn edit-btn">Переименовать</button>
569
+ <button type="submit" name="delete_folder" class="btn delete-btn" onclick="return confirm('Удалить папку?');">Удалить</button>
570
+ </form>
571
+ </div>
572
+ {% endfor %}
573
+ {% for i, file in enumerate(current_folder['files']) %}
574
  <div class="file-item">
575
  {% if file['type'] == 'video' %}
576
  <video class="file-preview" preload="metadata" muted loading="lazy" onclick="openModal('https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}', true)">
 
583
  {% endif %}
584
  <p>{{ file['upload_date'] }}</p>
585
  <a href="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" class="btn download-btn" download="{{ file['filename'] }}">Скачать</a>
586
+ <form method="POST" style="margin-top: 10px;">
587
+ <input type="hidden" name="file_index" value="{{ i }}">
588
+ <select name="target_folder">
589
+ <option value="">Переместить в...</option>
590
+ {% for f_name in current_folder['subfolders'] %}
591
+ <option value="{{ folder_path + '/' + f_name }}">{{ f_name }}</option>
592
+ {% endfor %}
593
+ </select>
594
+ <button type="submit" name="move_file" class="btn edit-btn">Переместить</button>
595
+ </form>
596
  </div>
597
  {% endfor %}
598
+ {% if not current_folder['files'] and not current_folder['subfolders'] %}
599
+ <p>Папка пуста.</p>
600
  {% endif %}
601
  </div>
602
  <a href="{{ url_for('logout') }}" class="btn" style="margin-top: 20px;">Выйти</a>
 
622
  modal.innerHTML = '<div id="modalContent"></div>';
623
  }
624
  }
625
+ document.getElementById('upload-form').onsubmit = async function(e) {
626
+ e.preventDefault();
627
+ const formData = new FormData(this);
628
+ const files = formData.getAll('files');
629
+ const totalFiles = files.length;
630
+ const progressContainer = document.getElementById('upload-progress');
631
+ const progressBar = document.getElementById('progress-bar');
632
+ const progressText = document.getElementById('progress-text');
633
+
634
+ progressContainer.style.display = 'block';
635
+ let uploadedFiles = 0;
636
+
637
+ const xhr = new XMLHttpRequest();
638
+ xhr.open('POST', window.location.pathname, true);
639
+ xhr.upload.onprogress = function(event) {
640
+ if (event.lengthComputable) {
641
+ const percent = Math.round((event.loaded / event.total) * 100);
642
+ progressBar.style.width = percent + '%';
643
+ progressText.textContent = `Загрузка ${uploadedFiles + 1}/${totalFiles} (${percent}%)`;
644
+ }
645
+ };
646
+ xhr.onload = function() {
647
+ uploadedFiles++;
648
+ if (uploadedFiles === totalFiles) {
649
+ window.location.reload();
650
+ } else {
651
+ progressText.textContent = `Загружено ${uploadedFiles}/${totalFiles}`;
652
+ }
653
+ };
654
+ xhr.send(formData);
655
+ };
656
+ window.onload = () => {
657
+ const token = localStorage.getItem('token');
658
+ if (!token) window.location = '/';
659
+ else sessionStorage.setItem('token', token);
660
+ };
661
  </script>
662
  </body>
663
  </html>
664
  '''
665
+ return render_template_string(html, token=token, current_folder=current_folder, folder_path=folder_path, repo_id=REPO_ID)
666
 
667
  # Выход
668
  @app.route('/logout')