diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,79 +1,81 @@ +# --- START OF FILE app (8).py --- + +from flask import Flask, render_template_string, request, redirect, url_for, session, flash, send_file, jsonify +from flask_caching import Cache import json -import logging -import mimetypes import os +import logging import threading import time -import uuid from datetime import datetime -from io import BytesIO - -import requests -from flask import (Flask, flash, jsonify, redirect, render_template_string, - request, send_file, session, url_for) -from flask_caching import Cache -from huggingface_hub import HfApi, hf_hub_download +from huggingface_hub import HfApi, hf_hub_download, HfFileSystem from werkzeug.utils import secure_filename +import requests +from io import BytesIO +import uuid +from pathlib import Path app = Flask(__name__) -app.secret_key = os.getenv("FLASK_SECRET_KEY", "a-very-secret-and-complex-key") -DATA_FILE = 'cloudeng_data_v2.json' -REPO_ID = "Eluza133/Z1e1u" +app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey_change_me_please") # Changed default key +DATA_FILE = 'cloudeng_data.json' +REPO_ID = "Eluza133/Z1e1u" # Make sure this repo exists and you have access HF_TOKEN_WRITE = os.getenv("HF_TOKEN") HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE -UPLOAD_FOLDER = 'temp_uploads' -os.makedirs(UPLOAD_FOLDER, exist_ok=True) +HF_FS = HfFileSystem(token=HF_TOKEN_READ) # Filesystem object for easier interaction + +# Basic check for necessary tokens +if not HF_TOKEN_WRITE: + logging.warning("HF_TOKEN (write access) is not set. File/folder uploads and deletions will fail.") +if not HF_TOKEN_READ: + logging.warning("HF_TOKEN_READ is not set. Falling back to HF_TOKEN. File/folder access might fail for private repos if HF_TOKEN is not set.") + cache = Cache(app, config={'CACHE_TYPE': 'simple'}) logging.basicConfig(level=logging.INFO) -@cache.memoize(timeout=300) +@cache.memoize(timeout=60) # Shorter cache timeout def load_data(): try: - download_db_from_hf() + if HF_TOKEN_READ: # Only download if token exists + download_db_from_hf() + else: + logging.warning("HF_TOKEN_READ not available, skipping DB download from HF.") + if not os.path.exists(DATA_FILE): + with open(DATA_FILE, 'w', encoding='utf-8') as f: + json.dump({'users': {}, 'files': {}}, f) # Initialize if missing + with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) if not isinstance(data, dict): logging.warning("Data is not in dict format, initializing empty database") - return {'users': {}, 'files': {}} + return {'users': {}} # Simplified structure: only users, files are inferred from paths data.setdefault('users', {}) - data.setdefault('files', {}) - for user_data in data['users'].values(): - user_data.setdefault('files', []) - user_data.setdefault('folders', []) logging.info("Data successfully loaded") return data except FileNotFoundError: logging.warning(f"{DATA_FILE} not found. Initializing empty database.") - return {'users': {}, 'files': {}} - except json.JSONDecodeError: - logging.error(f"Error decoding JSON from {DATA_FILE}. Initializing empty database.") - return {'users': {}, 'files': {}} + return {'users': {}} except Exception as e: logging.error(f"Error loading data: {e}") - return {'users': {}, 'files': {}} + return {'users': {}} def save_data(data): try: - # Ensure default structures exist before saving - data.setdefault('users', {}) - data.setdefault('files', {}) # Keep top-level files if needed, or remove if unused - for user_data in data['users'].values(): - user_data.setdefault('files', []) - user_data.setdefault('folders', []) - with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) - upload_db_to_hf() + if HF_TOKEN_WRITE: # Only upload if token exists + upload_db_to_hf() + else: + logging.warning("HF_TOKEN_WRITE not available, skipping DB upload to HF.") cache.clear() - logging.info("Data saved and uploaded to HF") + logging.info("Data saved locally") except Exception as e: logging.error(f"Error saving data: {e}") - raise + # Do not raise here to avoid crashing on save errors, just log it. def upload_db_to_hf(): if not HF_TOKEN_WRITE: - logging.warning("HF Write Token not set. Skipping database upload.") + logging.warning("Skipping HF DB upload: Write token not configured.") return try: api = HfApi() @@ -87,15 +89,11 @@ def upload_db_to_hf(): ) logging.info("Database uploaded to Hugging Face") except Exception as e: - logging.error(f"Error uploading database: {e}") + logging.error(f"Error uploading database to HF: {e}") def download_db_from_hf(): if not HF_TOKEN_READ: - logging.warning("HF Read Token not set. Skipping database download attempt.") - # If file doesn't exist locally, create an empty one - if not os.path.exists(DATA_FILE): - with open(DATA_FILE, 'w', encoding='utf-8') as f: - json.dump({'users': {}, 'files': {}}, f) + logging.warning("Skipping HF DB download: Read token not configured.") return try: hf_hub_download( @@ -105,382 +103,129 @@ def download_db_from_hf(): token=HF_TOKEN_READ, local_dir=".", local_dir_use_symlinks=False, - force_download=True, # Force download to get latest version - resume_download=False # Start fresh + force_download=True # Force download to get the latest version ) logging.info("Database downloaded from Hugging Face") - except Exception as e: - logging.error(f"Error downloading database: {e}") + except Exception as e: # More specific exceptions could be caught (e.g., hf_hub.utils.RepositoryNotFoundError) + logging.error(f"Error downloading database from HF: {e}. Checking if local file exists.") if not os.path.exists(DATA_FILE): - logging.warning(f"Creating empty {DATA_FILE} as download failed and file doesn't exist.") + logging.warning(f"{DATA_FILE} not found locally after download error. Initializing empty database.") with open(DATA_FILE, 'w', encoding='utf-8') as f: - json.dump({'users': {}, 'files': {}}, f) + json.dump({'users': {}}, f) def periodic_backup(): + if not HF_TOKEN_WRITE: + logging.warning("Periodic backup disabled: Write token not configured.") + return while True: time.sleep(1800) # Backup every 30 minutes logging.info("Starting periodic backup...") - upload_db_to_hf() + # It might be better to save data only if there were changes, + # but for simplicity, we just upload the current state. + if os.path.exists(DATA_FILE): + upload_db_to_hf() + else: + logging.warning("Skipping periodic backup: data file does not exist.") + def get_file_type(filename): - ext = os.path.splitext(filename)[1].lower() - if ext in ('.mp4', '.mov', '.avi', '.webm', '.mkv', '.flv'): + filename_lower = filename.lower() + if filename_lower.endswith(('.mp4', '.mov', '.avi', '.webm', '.mkv')): return 'video' - elif ext in ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'): + elif filename_lower.endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg')): return 'image' - elif ext == '.pdf': + elif filename_lower.endswith('.pdf'): return 'pdf' - elif ext == '.txt': + elif filename_lower.endswith('.txt'): return 'text' - return 'other' - -def generate_unique_filename(original_filename): - _, ext = os.path.splitext(original_filename) - safe_base = secure_filename(os.path.splitext(original_filename)[0]) - if not safe_base: # Handle cases like ".bashrc" - safe_base = "file" - unique_id = uuid.uuid4().hex[:8] # Shorter UUID part - return f"{safe_base}_{unique_id}{ext}" - -def get_folder_icon(): - return '''''' - -def get_file_icon(file_type): - # Basic file icon, could be expanded with specific icons per type - return '''''' + # Add more types as needed + # elif filename_lower.endswith(('.doc', '.docx')): + # return 'document' + else: + return 'other' + +def get_user_base_path(username): + return f"cloud_files/{username}" + +def get_hf_fs_path(username, current_path, filename=""): + # Ensure current_path is relative and clean + rel_path = Path(current_path.strip('/')) + base_repo_path = f"{REPO_ID}/datasets/{get_user_base_path(username)}" + full_fs_path = f"{base_repo_path}/{rel_path}/{filename}" if filename else f"{base_repo_path}/{rel_path}" + # Clean up potential double slashes, except for the protocol part if present (though HF paths don't use http://) + full_fs_path = full_fs_path.replace("//", "/") + return full_fs_path + +def get_hf_api_path(username, current_path, unique_filename): + # Path used for API operations like upload/delete + rel_path = Path(current_path.strip('/')) + api_path = Path(get_user_base_path(username)) / rel_path / unique_filename + return str(api_path).replace('\\', '/') # Ensure forward slashes + +def get_hf_resolve_url(api_path): + # Construct the URL for direct access/preview + # Ensure the path doesn't start with a slash if REPO_ID already has one + return f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{api_path}" + BASE_STYLE = ''' :root { - --primary: #ff4d6d; /* Hot Pink */ - --secondary: #00ddeb; /* Cyan */ - --accent: #8b5cf6; /* Violet */ - --success: #10b981; /* Emerald */ - --warning: #f59e0b; /* Amber */ - --danger: #ef4444; /* Red */ - --info: #3b82f6; /* Blue */ - --background-light: #f8fafc; /* Slate 50 */ - --background-dark: #0f172a; /* Slate 900 */ - --card-bg-light: #ffffff; - --card-bg-dark: #1e293b; /* Slate 800 */ - --text-light: #334155; /* Slate 700 */ - --text-dark: #e2e8f0; /* Slate 200 */ - --border-light: #e2e8f0; /* Slate 200 */ - --border-dark: #334155; /* Slate 700 */ - --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --radius-sm: 0.25rem; - --radius: 0.5rem; - --radius-lg: 0.75rem; - --transition: all 0.2s ease-in-out; -} -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'); -body { - font-family: 'Inter', sans-serif; - background-color: var(--background-light); - color: var(--text-light); - line-height: 1.6; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} -body.dark { - background-color: var(--background-dark); - color: var(--text-dark); -} -.container { - max-width: 1300px; - margin: 2rem auto; - padding: 1.5rem 2rem; -} -.card { - background-color: var(--card-bg-light); - border-radius: var(--radius-lg); - box-shadow: var(--shadow); - padding: 2rem; - margin-bottom: 1.5rem; -} -body.dark .card { background-color: var(--card-bg-dark); } -h1 { - font-size: 2.25rem; /* 36px */ - font-weight: 800; - text-align: center; - margin-bottom: 2rem; - background: linear-gradient(135deg, var(--primary), var(--accent)); - -webkit-background-clip: text; - color: transparent; -} -h2 { - font-size: 1.5rem; /* 24px */ - font-weight: 700; - margin-top: 2rem; - margin-bottom: 1rem; - border-bottom: 1px solid var(--border-light); - padding-bottom: 0.5rem; -} -body.dark h2 { color: var(--text-dark); border-bottom-color: var(--border-dark); } -input[type="text"], input[type="password"], input[type="file"], textarea { - width: 100%; - padding: 0.75rem 1rem; - margin: 0.5rem 0 1rem 0; - border: 1px solid var(--border-light); - border-radius: var(--radius); - background-color: var(--background-light); - color: var(--text-light); - font-size: 1rem; - transition: var(--transition); -} -body.dark input[type="text"], body.dark input[type="password"], body.dark input[type="file"], body.dark textarea { - background-color: var(--background-dark); - color: var(--text-dark); - border-color: var(--border-dark); -} -input:focus, textarea:focus { - outline: none; - border-color: var(--primary); - box-shadow: 0 0 0 3px rgba(255, 77, 109, 0.3); /* primary with opacity */ -} -input[type="file"] { padding: 0.5rem; } -input::file-selector-button { - padding: 0.5rem 1rem; - border: none; - background-color: var(--accent); - color: white; - border-radius: var(--radius-sm); - cursor: pointer; - margin-right: 1rem; - transition: var(--transition); -} -input::file-selector-button:hover { background-color: #7c3aed; } /* Darker accent */ -.btn { - padding: 0.75rem 1.5rem; - border: none; - border-radius: var(--radius); - cursor: pointer; - font-size: 1rem; - font-weight: 600; - transition: var(--transition); - text-decoration: none; - display: inline-block; - text-align: center; - box-shadow: var(--shadow-sm); -} -.btn-primary { background-color: var(--primary); color: white; } -.btn-primary:hover { background-color: #e6415f; transform: translateY(-2px); box-shadow: var(--shadow); } -.btn-secondary { background-color: var(--secondary); color: var(--text-light); } -.btn-secondary:hover { background-color: #00b8c5; transform: translateY(-2px); box-shadow: var(--shadow); } -.btn-accent { background-color: var(--accent); color: white; } -.btn-accent:hover { background-color: #7c3aed; transform: translateY(-2px); box-shadow: var(--shadow); } -.btn-danger { background-color: var(--danger); color: white; } -.btn-danger:hover { background-color: #dc2626; transform: translateY(-2px); box-shadow: var(--shadow); } -.btn-sm { padding: 0.5rem 1rem; font-size: 0.875rem; } -.flash { - padding: 1rem; - margin-bottom: 1rem; - border-radius: var(--radius); - text-align: center; - font-weight: 500; -} -.flash-success { background-color: #d1fae5; color: #065f46; } /* Green */ -.flash-error { background-color: #fee2e2; color: #991b1b; } /* Red */ -.flash-info { background-color: #dbeafe; color: #1e40af; } /* Blue */ -body.dark .flash-success { background-color: #059669; color: #d1fae5; } -body.dark .flash-error { background-color: #b91c1c; color: #fee2e2; } -body.dark .flash-info { background-color: #2563eb; color: #dbeafe; } - -.breadcrumb { - display: flex; - align-items: center; - margin-bottom: 1.5rem; - font-size: 0.9rem; - color: #64748b; /* Slate 500 */ - background-color: var(--card-bg-light); - padding: 0.75rem 1.5rem; - border-radius: var(--radius); - box-shadow: var(--shadow-sm); -} -body.dark .breadcrumb { background-color: var(--card-bg-dark); color: #94a3b8; } /* Slate 400 */ -.breadcrumb a { color: var(--primary); text-decoration: none; font-weight: 500; } -.breadcrumb a:hover { text-decoration: underline; } -.breadcrumb span { margin: 0 0.5rem; } - -.file-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 1.5rem; - margin-top: 1.5rem; -} -.grid-item { - background-color: var(--card-bg-light); - border-radius: var(--radius-lg); - padding: 1rem; - box-shadow: var(--shadow); - text-align: center; - transition: var(--transition); - position: relative; - overflow: hidden; - cursor: pointer; -} -body.dark .grid-item { background-color: var(--card-bg-dark); } -.grid-item:hover { transform: translateY(-5px); box-shadow: var(--shadow-lg); } -.grid-item-icon { margin-bottom: 0.5rem; } -.grid-item-icon img, .grid-item-icon video { - width: 100%; - height: 120px; - object-fit: cover; - border-radius: var(--radius-sm); - background-color: #e2e8f0; /* Placeholder bg */ -} -.grid-item-icon .file-placeholder-icon svg { - width: 64px; height: 64px; margin: 28px auto; /* Center vertically */ - color: #94a3b8; /* Slate 400 */ -} -body.dark .grid-item-icon .file-placeholder-icon svg { color: #64748b; } /* Slate 500 */ -.grid-item-name { - font-weight: 500; - font-size: 0.9rem; - margin-bottom: 0.5rem; - word-break: break-all; /* Prevent long names overflowing */ - line-height: 1.3; - height: 2.6em; /* Limit to 2 lines */ - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; -} -.grid-item-actions { - margin-top: 0.75rem; - display: flex; - justify-content: center; - gap: 0.5rem; -} -.grid-item-actions .btn { padding: 0.3rem 0.6rem; font-size: 0.75rem; } - -.modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; top: 0; - width: 100%; height: 100%; - overflow: auto; - background-color: rgba(0,0,0,0.85); - align-items: center; - justify-content: center; - padding: 20px; -} -.modal-content { - position: relative; - margin: auto; - padding: 0; - background-color: var(--card-bg-dark); /* Dark background for better contrast */ - border-radius: var(--radius-lg); - max-width: 90vw; - max-height: 90vh; - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; -} -.modal img, .modal video, .modal iframe, .modal pre { - max-width: 100%; - max-height: calc(90vh - 40px); /* Account for padding */ - display: block; - object-fit: contain; - border-radius: var(--radius-sm); -} -.modal iframe { width: 80vw; height: 85vh; background-color: #fff; } -.modal pre { - background-color: #f1f5f9; /* Light bg for text */ - color: #1e293b; - padding: 1.5rem; - border-radius: var(--radius-sm); - white-space: pre-wrap; - word-wrap: break-word; - overflow: auto; - font-family: 'Courier New', Courier, monospace; - font-size: 0.9rem; - max-height: calc(90vh - 60px); -} -.modal-close { - position: absolute; - top: 15px; - right: 25px; - color: #f1f1f1; - font-size: 40px; - font-weight: bold; - transition: 0.3s; - cursor: pointer; - z-index: 1010; -} -.modal-close:hover, .modal-close:focus { color: #bbb; text-decoration: none; } - -#progress-container { - width: 100%; - background-color: var(--border-light); - border-radius: var(--radius); - margin: 1rem 0; - overflow: hidden; - display: none; -} -body.dark #progress-container { background-color: var(--border-dark); } -#progress-bar { - width: 0%; - height: 15px; - background-color: var(--primary); - border-radius: var(--radius); - transition: width 0.4s ease; - text-align: center; - line-height: 15px; - color: white; - font-size: 0.75rem; -} - -.user-list { margin-top: 1.5rem; } -.user-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem 1.5rem; - background-color: var(--card-bg-light); - border-radius: var(--radius); - margin-bottom: 1rem; - box-shadow: var(--shadow-sm); - transition: var(--transition); -} -body.dark .user-item { background-color: var(--card-bg-dark); } -.user-item:hover { box-shadow: var(--shadow); } -.user-item a { color: var(--primary); text-decoration: none; font-weight: 600; font-size: 1.1rem;} -.user-item a:hover { color: var(--accent); } -.user-item p { font-size: 0.9rem; color: #64748b; } -body.dark .user-item p { color: #94a3b8; } -.user-item .actions { display: flex; gap: 0.5rem; } - -@media (max-width: 768px) { - .file-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1rem; } - .grid-item { padding: 0.75rem; } - .grid-item-icon img, .grid-item-icon video { height: 100px; } - .grid-item-icon .file-placeholder-icon svg { width: 48px; height: 48px; margin: 26px auto; } - h1 { font-size: 1.75rem; } - h2 { font-size: 1.25rem; } - .container { padding: 1rem; margin: 1rem auto; } - .card { padding: 1.5rem; } - .user-item { flex-direction: column; align-items: flex-start; gap: 0.5rem;} - .user-item .actions { margin-top: 0.5rem; } -} -@media (max-width: 480px) { - .file-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 0.75rem; } - .grid-item-name { font-size: 0.8rem; height: 2.4em; } - .btn { padding: 0.6rem 1.2rem; font-size: 0.9rem; } - .breadcrumb { font-size: 0.8rem; padding: 0.5rem 1rem; } - .modal-content { max-width: 95vw; } + --primary: #ff4d6d; --secondary: #00ddeb; --accent: #8b5cf6; + --background-light: #f5f6fa; --background-dark: #1a1625; + --card-bg: rgba(255, 255, 255, 0.95); --card-bg-dark: rgba(40, 35, 60, 0.95); + --text-light: #2a1e5a; --text-dark: #e8e1ff; + --shadow: 0 10px 30px rgba(0, 0, 0, 0.2); --glass-bg: rgba(255, 255, 255, 0.15); + --transition: all 0.3s ease; --delete-color: #ff4444; --folder-color: #ffc107; } +* { margin: 0; padding: 0; box-sizing: border-box; } +body { font-family: 'Inter', sans-serif; background: var(--background-light); color: var(--text-light); line-height: 1.6; } +body.dark { background: var(--background-dark); color: var(--text-dark); } +.container { margin: 20px auto; max-width: 1200px; padding: 25px; background: var(--card-bg); border-radius: 20px; box-shadow: var(--shadow); } +body.dark .container { background: var(--card-bg-dark); } +h1 { font-size: 2em; font-weight: 800; text-align: center; margin-bottom: 25px; background: linear-gradient(135deg, var(--primary), var(--accent)); -webkit-background-clip: text; color: transparent; } +h2 { font-size: 1.5em; margin-top: 30px; margin-bottom: 15px; color: var(--text-light); } +body.dark h2 { color: var(--text-dark); } +input, textarea, select { width: 100%; padding: 14px; margin: 12px 0; border: none; border-radius: 14px; background: var(--glass-bg); color: var(--text-light); font-size: 1.1em; box-shadow: inset 0 3px 10px rgba(0, 0, 0, 0.1); } +body.dark input, body.dark textarea, body.dark select { color: var(--text-dark); background: rgba(0,0,0,0.2); } +input:focus, textarea:focus, select:focus { outline: none; box-shadow: 0 0 0 4px var(--primary); } +.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; text-align: center; } +.btn:hover { transform: scale(1.05); background: #e6415f; } +.btn-small { padding: 8px 16px; font-size: 0.9em; border-radius: 10px; } +.download-btn { background: var(--secondary); margin-top: 10px; } +.download-btn:hover { background: #00b8c5; } +.delete-btn { background: var(--delete-color); margin-top: 10px; } +.delete-btn:hover { background: #cc3333; } +.flash { padding: 15px; margin-bottom: 15px; border-radius: 10px; text-align: center; } +.flash.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } +.flash.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } +.flash.info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; } +.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; margin-top: 20px; } +.item { background: var(--card-bg); padding: 15px; border-radius: 16px; box-shadow: var(--shadow); text-align: center; transition: var(--transition); position: relative; } +body.dark .item { background: var(--card-bg-dark); } +.item:hover { transform: translateY(-5px); } +.item-preview { max-width: 100%; height: 150px; object-fit: cover; border-radius: 10px; margin-bottom: 10px; cursor: pointer; background-color: #eee; display: flex; align-items: center; justify-content: center; } +body.dark .item-preview { background-color: #333; } +.item-preview img, .item-preview video { max-width: 100%; max-height: 100%; border-radius: 10px; } +.item-preview .file-icon { font-size: 4em; color: #aaa; } /* Placeholder for generic icons */ +.item p { font-size: 0.9em; margin: 5px 0; word-break: break-all; } +.item a { color: var(--primary); text-decoration: none; } +.item a:hover { color: var(--accent); } +.folder-icon { font-size: 4em; color: var(--folder-color); line-height: 150px; } /* Specific style for folder icon */ +.item.folder { background: #fffacd; } /* Light yellow background for folders */ +body.dark .item.folder { background: #5f5b3a; } +.item.folder a { color: #8b4513; font-weight: bold; text-decoration: none; display: block; } +.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.85); z-index: 2000; justify-content: center; align-items: center; padding: 20px; } +.modal-content { max-width: 95%; max-height: 95%; background: white; padding: 10px; border-radius: 15px; overflow: hidden; } +.modal-content iframe { width: 80vw; height: 80vh; border: none; } +.modal img, .modal video { max-width: 100%; max-height: 90vh; object-fit: contain; border-radius: 10px; display: block; margin: auto; } +.modal-close { position: absolute; top: 15px; right: 30px; font-size: 2em; color: white; cursor: pointer; z-index: 2010; } +#progress-container { width: 100%; background: var(--glass-bg); border-radius: 10px; margin: 15px 0; display: none; } +#progress-bar { width: 0%; height: 20px; background: var(--primary); border-radius: 10px; transition: width 0.3s ease; } +.breadcrumbs { margin-bottom: 20px; font-size: 1.1em; } +.breadcrumbs a { color: var(--accent); text-decoration: none; } +.breadcrumbs span { margin: 0 5px; } +#create-folder-section { margin-top: 20px; padding: 15px; background: var(--glass-bg); border-radius: 15px; } +body.dark #create-folder-section { background: rgba(0,0,0,0.2); } ''' @app.route('/register', methods=['GET', 'POST']) @@ -492,12 +237,13 @@ def register(): if not username or not password: flash('Имя пользователя и пароль обязательны!', 'error') return redirect(url_for('register')) - + # Basic validation (add more robust checks as needed) - if len(username) < 3 or len(password) < 6: - flash('Имя пользователя должно быть не менее 3 символов, пароль - не менее 6.', 'error') + if not username.isalnum() or len(username) < 3: + flash('Имя пользователя должно быть не менее 3 символов и содержать только буквы и цифры.', 'error') return redirect(url_for('register')) + data = load_data() if username in data['users']: @@ -505,21 +251,31 @@ def register(): return redirect(url_for('register')) data['users'][username] = { - 'password': password, # Store password directly (INSECURE! Use hashing in production) - 'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - 'files': [], - 'folders': [] # Initialize folders list + 'password': password, # Consider hashing passwords in a real application + 'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + # 'files' list is removed, file info inferred from HF repo } try: save_data(data) session['username'] = username flash('Регистрация прошла успешно!', 'success') + # Optionally create user's base folder on HF here, although it gets created on first upload too + # try: + # user_base_fs_path = get_hf_fs_path(username, '') # Path to user's root dir + # if HF_TOKEN_WRITE and not HF_FS.exists(user_base_fs_path): + # HF_FS.mkdir(user_base_fs_path, create_parents=True) + # logging.info(f"Created base directory for user {username} on HF.") + # except Exception as e: + # logging.error(f"Could not create base directory for {username} on HF: {e}") return redirect(url_for('dashboard')) except Exception as e: - flash('Ошибка сохранения данных при регистрации.', 'error') - logging.error(f"Registration save error: {e}") + flash(f'Ошибка сохранения данных: {e}', 'error') + # Rollback user creation if save failed? Complex, maybe just log. + if username in data['users']: + del data['users'][username] # Attempt rollback in memory return redirect(url_for('register')) + html = ''' @@ -527,72 +283,52 @@ def register():
Уже есть аккаунт? Войти
-Уже есть аккаунт? Войти