|
|
from flask import Flask, render_template_string, request, redirect, url_for, session, flash, send_file, jsonify |
|
|
from flask_caching import Cache |
|
|
import json |
|
|
import os |
|
|
import logging |
|
|
import threading |
|
|
import time |
|
|
from datetime import datetime |
|
|
from huggingface_hub import HfApi, hf_hub_download |
|
|
from werkzeug.utils import secure_filename |
|
|
import requests |
|
|
from io import BytesIO |
|
|
|
|
|
app = Flask(__name__) |
|
|
app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey") |
|
|
DATA_FILE = 'cloudeng_data.json' |
|
|
REPO_ID = "Eluza133/Z1e1u" |
|
|
HF_TOKEN_WRITE = os.getenv("HF_TOKEN") |
|
|
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE |
|
|
|
|
|
cache = Cache(app, config={'CACHE_TYPE': 'simple'}) |
|
|
logging.basicConfig(level=logging.INFO) |
|
|
|
|
|
|
|
|
@cache.memoize(timeout=300) |
|
|
def load_data(): |
|
|
try: |
|
|
download_db_from_hf() |
|
|
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': {}} |
|
|
data.setdefault('users', {}) |
|
|
data.setdefault('files', {}) |
|
|
logging.info("Data successfully loaded") |
|
|
return data |
|
|
except Exception as e: |
|
|
logging.error(f"Error loading data: {e}") |
|
|
return {'users': {}, 'files': {}} |
|
|
|
|
|
def save_data(data): |
|
|
try: |
|
|
with open(DATA_FILE, 'w', encoding='utf-8') as file: |
|
|
json.dump(data, file, ensure_ascii=False, indent=4) |
|
|
upload_db_to_hf() |
|
|
cache.clear() |
|
|
logging.info("Data saved and uploaded to HF") |
|
|
except Exception as e: |
|
|
logging.error(f"Error saving data: {e}") |
|
|
raise |
|
|
|
|
|
def upload_db_to_hf(): |
|
|
try: |
|
|
api = HfApi() |
|
|
api.upload_file( |
|
|
path_or_fileobj=DATA_FILE, |
|
|
path_in_repo=DATA_FILE, |
|
|
repo_id=REPO_ID, |
|
|
repo_type="dataset", |
|
|
token=HF_TOKEN_WRITE, |
|
|
commit_message=f"Backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" |
|
|
) |
|
|
logging.info("Database uploaded to Hugging Face") |
|
|
except Exception as e: |
|
|
logging.error(f"Error uploading database: {e}") |
|
|
|
|
|
def download_db_from_hf(): |
|
|
try: |
|
|
hf_hub_download( |
|
|
repo_id=REPO_ID, |
|
|
filename=DATA_FILE, |
|
|
repo_type="dataset", |
|
|
token=HF_TOKEN_READ, |
|
|
local_dir=".", |
|
|
local_dir_use_symlinks=False |
|
|
) |
|
|
logging.info("Database downloaded from Hugging Face") |
|
|
except Exception as e: |
|
|
logging.error(f"Error downloading database: {e}") |
|
|
if not os.path.exists(DATA_FILE): |
|
|
with open(DATA_FILE, 'w', encoding='utf-8') as f: |
|
|
json.dump({'users': {}, 'files': {}}, f) |
|
|
|
|
|
def periodic_backup(): |
|
|
while True: |
|
|
upload_db_to_hf() |
|
|
time.sleep(1800) |
|
|
|
|
|
def get_file_type(filename): |
|
|
video_extensions = ('.mp4', '.mov', '.avi') |
|
|
image_extensions = ('.jpg', '.jpeg', '.png', '.gif') |
|
|
if filename.lower().endswith(video_extensions): |
|
|
return 'video' |
|
|
elif filename.lower().endswith(image_extensions): |
|
|
return 'image' |
|
|
return 'other' |
|
|
|
|
|
BASE_STYLE = ''' |
|
|
:root { |
|
|
--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; |
|
|
} |
|
|
* { 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; |
|
|
color: var(--text-light); |
|
|
} |
|
|
body.dark h2 { |
|
|
color: var(--text-dark); |
|
|
} |
|
|
input, textarea { |
|
|
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 { |
|
|
color: var(--text-dark); |
|
|
} |
|
|
input:focus, textarea: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; |
|
|
} |
|
|
.btn:hover { |
|
|
transform: scale(1.05); |
|
|
background: #e6415f; |
|
|
} |
|
|
.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 { |
|
|
color: var(--secondary); |
|
|
text-align: center; |
|
|
margin-bottom: 15px; |
|
|
} |
|
|
.file-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); |
|
|
gap: 20px; |
|
|
margin-top: 20px; |
|
|
} |
|
|
.user-list { |
|
|
margin-top: 20px; |
|
|
} |
|
|
.user-item { |
|
|
padding: 15px; |
|
|
background: var(--card-bg); |
|
|
border-radius: 16px; |
|
|
margin-bottom: 10px; |
|
|
box-shadow: var(--shadow); |
|
|
transition: var(--transition); |
|
|
} |
|
|
body.dark .user-item { |
|
|
background: var(--card-bg-dark); |
|
|
} |
|
|
.user-item:hover { |
|
|
transform: translateY(-5px); |
|
|
} |
|
|
.user-item a { |
|
|
color: var(--primary); |
|
|
text-decoration: none; |
|
|
font-weight: 600; |
|
|
} |
|
|
.user-item a:hover { |
|
|
color: var(--accent); |
|
|
} |
|
|
@media (max-width: 768px) { |
|
|
.file-grid { |
|
|
grid-template-columns: repeat(2, 1fr); |
|
|
} |
|
|
} |
|
|
@media (max-width: 480px) { |
|
|
.file-grid { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
} |
|
|
.file-item { |
|
|
background: var(--card-bg); |
|
|
padding: 15px; |
|
|
border-radius: 16px; |
|
|
box-shadow: var(--shadow); |
|
|
text-align: center; |
|
|
transition: var(--transition); |
|
|
} |
|
|
body.dark .file-item { |
|
|
background: var(--card-bg-dark); |
|
|
} |
|
|
.file-item:hover { |
|
|
transform: translateY(-5px); |
|
|
} |
|
|
.file-preview { |
|
|
max-width: 100%; |
|
|
max-height: 200px; |
|
|
object-fit: cover; |
|
|
border-radius: 10px; |
|
|
margin-bottom: 10px; |
|
|
loading: lazy; |
|
|
} |
|
|
.file-item p { |
|
|
font-size: 0.9em; |
|
|
margin: 5px 0; |
|
|
} |
|
|
.file-item a { |
|
|
color: var(--primary); |
|
|
text-decoration: none; |
|
|
} |
|
|
.file-item a:hover { |
|
|
color: var(--accent); |
|
|
} |
|
|
.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; |
|
|
} |
|
|
.modal img, .modal video { |
|
|
max-width: 95%; |
|
|
max-height: 95%; |
|
|
object-fit: contain; |
|
|
border-radius: 20px; |
|
|
box-shadow: var(--shadow); |
|
|
} |
|
|
#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; |
|
|
} |
|
|
''' |
|
|
|
|
|
@app.route('/register', methods=['GET', 'POST']) |
|
|
def register(): |
|
|
if request.method == 'POST': |
|
|
username = request.form.get('username') |
|
|
password = request.form.get('password') |
|
|
|
|
|
data = load_data() |
|
|
|
|
|
if username in data['users']: |
|
|
flash('A user with this username already exists!') |
|
|
return redirect(url_for('register')) |
|
|
|
|
|
if not username or not password: |
|
|
flash('Username and password are required!') |
|
|
return redirect(url_for('register')) |
|
|
|
|
|
data['users'][username] = { |
|
|
'password': password, |
|
|
'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), |
|
|
'files': [] |
|
|
} |
|
|
save_data(data) |
|
|
session['username'] = username |
|
|
flash('Registration successful!') |
|
|
return redirect(url_for('dashboard')) |
|
|
|
|
|
html = ''' |
|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Register - Zeus Cloud</title> |
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet"> |
|
|
<style>''' + BASE_STYLE + '''</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>Register in Zeus Cloud</h1> |
|
|
{% with messages = get_flashed_messages() %} |
|
|
{% if messages %} |
|
|
{% for message in messages %} |
|
|
<div class="flash">{{ message }}</div> |
|
|
{% endfor %} |
|
|
{% endif %} |
|
|
{% endwith %} |
|
|
<form method="POST" id="register-form"> |
|
|
<input type="text" name="username" placeholder="Enter username" required> |
|
|
<input type="password" name="password" placeholder="Enter password" required> |
|
|
<button type="submit" class="btn">Register</button> |
|
|
</form> |
|
|
<p style="margin-top: 20px;">Already have an account? <a href="{{ url_for('login') }}">Log in</a></p> |
|
|
</div> |
|
|
</body> |
|
|
</html> |
|
|
''' |
|
|
return render_template_string(html) |
|
|
|
|
|
@app.route('/', methods=['GET', 'POST']) |
|
|
def login(): |
|
|
if request.method == 'POST': |
|
|
username = request.form.get('username') |
|
|
password = request.form.get('password') |
|
|
data = load_data() |
|
|
|
|
|
if username in data['users'] and data['users'][username]['password'] == password: |
|
|
session['username'] = username |
|
|
return jsonify({'status': 'success', 'redirect': url_for('dashboard')}) |
|
|
else: |
|
|
return jsonify({'status': 'error', 'message': 'Invalid username or password!'}) |
|
|
|
|
|
html = ''' |
|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Zeus Cloud</title> |
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet"> |
|
|
<style>''' + BASE_STYLE + '''</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>Zeus Cloud</h1> |
|
|
<div id="flash-messages"> |
|
|
{% with messages = get_flashed_messages() %} |
|
|
{% if messages %} |
|
|
{% for message in messages %} |
|
|
<div class="flash">{{ message }}</div> |
|
|
{% endfor %} |
|
|
{% endif %} |
|
|
{% endwith %} |
|
|
</div> |
|
|
<form method="POST" id="login-form"> |
|
|
<input type="text" name="username" placeholder="Enter username" required> |
|
|
<input type="password" name="password" placeholder="Enter password" required> |
|
|
<button type="submit" class="btn">Log in</button> |
|
|
</form> |
|
|
<p style="margin-top: 20px;">No account? <a href="{{ url_for('register') }}">Register</a></p> |
|
|
</div> |
|
|
<script> |
|
|
// Check localStorage and auto-login |
|
|
const savedCredentials = JSON.parse(localStorage.getItem('zeusCredentials')); |
|
|
if (savedCredentials) { |
|
|
fetch('/', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/x-www-form-urlencoded', |
|
|
}, |
|
|
body: `username=${encodeURIComponent(savedCredentials.username)}&password=${encodeURIComponent(savedCredentials.password)}` |
|
|
}) |
|
|
.then(response => response.json()) |
|
|
.then(data => { |
|
|
if (data.status === 'success') { |
|
|
window.location.href = data.redirect; |
|
|
} |
|
|
}) |
|
|
.catch(error => console.error('Auto-login error:', error)); |
|
|
} |
|
|
|
|
|
// Handle login form submission |
|
|
document.getElementById('login-form').addEventListener('submit', function(e) { |
|
|
e.preventDefault(); |
|
|
const formData = new FormData(this); |
|
|
fetch('/', { |
|
|
method: 'POST', |
|
|
body: formData |
|
|
}) |
|
|
.then(response => response.json()) |
|
|
.then(data => { |
|
|
if (data.status === 'success') { |
|
|
const username = formData.get('username'); |
|
|
const password = formData.get('password'); |
|
|
localStorage.setItem('zeusCredentials', JSON.stringify({ username, password })); |
|
|
window.location.href = data.redirect; |
|
|
} else { |
|
|
document.getElementById('flash-messages').innerHTML = `<div class="flash">${data.message}</div>`; |
|
|
} |
|
|
}) |
|
|
.catch(error => { |
|
|
document.getElementById('flash-messages').innerHTML = `<div class="flash">Connection error!</div>`; |
|
|
}); |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
''' |
|
|
return render_template_string(html) |
|
|
|
|
|
@app.route('/dashboard', methods=['GET', 'POST']) |
|
|
def dashboard(): |
|
|
if 'username' not in session: |
|
|
flash('Please log in!') |
|
|
return redirect(url_for('login')) |
|
|
|
|
|
username = session['username'] |
|
|
data = load_data() |
|
|
if username not in data['users']: |
|
|
session.pop('username', None) |
|
|
flash('User not found!') |
|
|
return redirect(url_for('login')) |
|
|
|
|
|
user_files = sorted(data['users'][username]['files'], key=lambda x: x['upload_date'], reverse=True) |
|
|
|
|
|
if request.method == 'POST': |
|
|
files = request.files.getlist('files') |
|
|
if files and len(files) > 20: |
|
|
flash('Maximum 20 files at a time!') |
|
|
return redirect(url_for('dashboard')) |
|
|
|
|
|
if files: |
|
|
os.makedirs('uploads', exist_ok=True) |
|
|
api = HfApi() |
|
|
temp_files = [] |
|
|
|
|
|
for file in files: |
|
|
if file and file.filename: |
|
|
filename = secure_filename(file.filename) |
|
|
temp_path = os.path.join('uploads', filename) |
|
|
file.save(temp_path) |
|
|
temp_files.append((temp_path, filename)) |
|
|
|
|
|
for temp_path, filename in temp_files: |
|
|
file_path = f"cloud_files/{username}/{filename}" |
|
|
api.upload_file( |
|
|
path_or_fileobj=temp_path, |
|
|
path_in_repo=file_path, |
|
|
repo_id=REPO_ID, |
|
|
repo_type="dataset", |
|
|
token=HF_TOKEN_WRITE, |
|
|
commit_message=f"Uploaded file for {username}" |
|
|
) |
|
|
|
|
|
file_info = { |
|
|
'filename': filename, |
|
|
'path': file_path, |
|
|
'type': get_file_type(filename), |
|
|
'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
|
|
} |
|
|
data['users'][username]['files'].append(file_info) |
|
|
|
|
|
if os.path.exists(temp_path): |
|
|
os.remove(temp_path) |
|
|
|
|
|
save_data(data) |
|
|
flash('Files uploaded successfully!') |
|
|
|
|
|
return redirect(url_for('dashboard')) |
|
|
|
|
|
html = ''' |
|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Dashboard - Zeus Cloud</title> |
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet"> |
|
|
<style>''' + BASE_STYLE + '''</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>Zeus Cloud Dashboard</h1> |
|
|
<p>User: {{ username }}</p> |
|
|
{% with messages = get_flashed_messages() %} |
|
|
{% if messages %} |
|
|
{% for message in messages %} |
|
|
<div class="flash">{{ message }}</div> |
|
|
{% endfor %} |
|
|
{% endif %} |
|
|
{% endwith %} |
|
|
<form id="upload-form" method="POST" enctype="multipart/form-data"> |
|
|
<input type="file" name="files" multiple required accept="image/*,video/*"> |
|
|
<button type="submit" class="btn" id="upload-btn">Upload Files</button> |
|
|
</form> |
|
|
<div id="progress-container"> |
|
|
<div id="progress-bar"></div> |
|
|
</div> |
|
|
<h2 style="margin-top: 30px;">Your Files</h2> |
|
|
<div class="file-grid"> |
|
|
{% for file in user_files %} |
|
|
<div class="file-item"> |
|
|
{% if file['type'] == 'video' %} |
|
|
<video class="file-preview" preload="metadata" muted loading="lazy" onclick="openModal('https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}', true)"> |
|
|
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" type="video/mp4"> |
|
|
</video> |
|
|
{% elif file['type'] == 'image' %} |
|
|
<img class="file-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" alt="{{ file['filename'] }}" loading="lazy" onclick="openModal(this.src, false)"> |
|
|
{% else %} |
|
|
<p>{{ file['filename'] }}</p> |
|
|
{% endif %} |
|
|
<p>{{ file['upload_date'] }}</p> |
|
|
<a href="{{ url_for('download_file', file_path=file['path'], filename=file['filename']) }}" class="btn download-btn">Download</a> |
|
|
<a href="{{ url_for('delete_file', file_path=file['path']) }}" class="btn delete-btn" onclick="return confirm('Are you sure you want to delete this file?');">Delete</a> |
|
|
</div> |
|
|
{% endfor %} |
|
|
{% if not user_files %} |
|
|
<p>You haven't uploaded any files yet.</p> |
|
|
{% endif %} |
|
|
</div> |
|
|
<a href="{{ url_for('logout') }}" class="btn" style="margin-top: 20px;" id="logout-btn">Log out</a> |
|
|
</div> |
|
|
<div class="modal" id="mediaModal" onclick="closeModal(event)"> |
|
|
<div id="modalContent"></div> |
|
|
</div> |
|
|
<script> |
|
|
function openModal(src, isVideo) { |
|
|
const modal = document.getElementById('mediaModal'); |
|
|
const modalContent = document.getElementById('modalContent'); |
|
|
if (isVideo) { |
|
|
modalContent.innerHTML = `<video controls><source src="${src}" type="video/mp4"></video>`; |
|
|
} else { |
|
|
modalContent.innerHTML = `<img src="${src}">`; |
|
|
} |
|
|
modal.style.display = 'flex'; |
|
|
} |
|
|
function closeModal(event) { |
|
|
if (event.target.tagName !== 'IMG' && event.target.tagName !== 'VIDEO') { |
|
|
const modal = document.getElementById('mediaModal'); |
|
|
modal.style.display = 'none'; |
|
|
modal.innerHTML = '<div id="modalContent"></div>'; |
|
|
} |
|
|
} |
|
|
|
|
|
const form = document.getElementById('upload-form'); |
|
|
const progressBar = document.getElementById('progress-bar'); |
|
|
const progressContainer = document.getElementById('progress-container'); |
|
|
const uploadBtn = document.getElementById('upload-btn'); |
|
|
|
|
|
form.addEventListener('submit', function(e) { |
|
|
e.preventDefault(); |
|
|
const files = form.querySelector('input[type="file"]').files; |
|
|
if (files.length > 20) { |
|
|
alert('Maximum 20 files at a time!'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const formData = new FormData(form); |
|
|
progressContainer.style.display = 'block'; |
|
|
progressBar.style.width = '0%'; |
|
|
uploadBtn.disabled = true; |
|
|
|
|
|
const xhr = new XMLHttpRequest(); |
|
|
xhr.open('POST', '/dashboard', true); |
|
|
|
|
|
xhr.upload.onprogress = function(event) { |
|
|
if (event.lengthComputable) { |
|
|
const percent = (event.loaded / event.total) * 100; |
|
|
progressBar.style.width = percent + '%'; |
|
|
} |
|
|
}; |
|
|
|
|
|
xhr.onload = function() { |
|
|
if (xhr.status === 200) { |
|
|
location.reload(); |
|
|
} else { |
|
|
alert('Upload error!'); |
|
|
progressContainer.style.display = 'none'; |
|
|
uploadBtn.disabled = false; |
|
|
} |
|
|
}; |
|
|
|
|
|
xhr.onerror = function() { |
|
|
alert('Connection error!'); |
|
|
progressContainer.style.display = 'none'; |
|
|
uploadBtn.disabled = false; |
|
|
}; |
|
|
|
|
|
xhr.send(formData); |
|
|
}); |
|
|
|
|
|
// Handle logout |
|
|
document.getElementById('logout-btn').addEventListener('click', function(e) { |
|
|
e.preventDefault(); |
|
|
localStorage.removeItem('zeusCredentials'); |
|
|
window.location.href = '/logout'; |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
''' |
|
|
return render_template_string(html, username=username, user_files=user_files, repo_id=REPO_ID) |
|
|
|
|
|
@app.route('/download/<path:file_path>/<filename>') |
|
|
def download_file(file_path, filename): |
|
|
if 'username' not in session: |
|
|
flash('Please log in!') |
|
|
return redirect(url_for('login')) |
|
|
|
|
|
username = session['username'] |
|
|
data = load_data() |
|
|
if username not in data['users']: |
|
|
session.pop('username', None) |
|
|
flash('User not found!') |
|
|
return redirect(url_for('login')) |
|
|
|
|
|
user_files = data['users'][username]['files'] |
|
|
if not any(file['path'] == file_path for file in user_files): |
|
|
flash('You do not have access to this file!') |
|
|
return redirect(url_for('dashboard')) |
|
|
|
|
|
file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{file_path}" |
|
|
response = requests.get(file_url) |
|
|
|
|
|
if response.status_code == 200: |
|
|
file_content = BytesIO(response.content) |
|
|
return send_file( |
|
|
file_content, |
|
|
as_attachment=True, |
|
|
download_name=filename, |
|
|
mimetype='application/octet-stream' |
|
|
) |
|
|
else: |
|
|
flash('Error downloading file!') |
|
|
return redirect(url_for('dashboard')) |
|
|
|
|
|
@app.route('/delete/<path:file_path>') |
|
|
def delete_file(file_path): |
|
|
if 'username' not in session: |
|
|
flash('Please log in!') |
|
|
return redirect(url_for('login')) |
|
|
|
|
|
username = session['username'] |
|
|
data = load_data() |
|
|
if username not in data['users']: |
|
|
session.pop('username', None) |
|
|
flash('User not found!') |
|
|
return redirect(url_for('login')) |
|
|
|
|
|
user_files = data['users'][username]['files'] |
|
|
file_to_delete = next((file for file in user_files if file['path'] == file_path), None) |
|
|
|
|
|
if not file_to_delete: |
|
|
flash('File not found!') |
|
|
return redirect(url_for('dashboard')) |
|
|
|
|
|
try: |
|
|
api = HfApi() |
|
|
api.delete_file( |
|
|
path_in_repo=file_path, |
|
|
repo_id=REPO_ID, |
|
|
repo_type="dataset", |
|
|
token=HF_TOKEN_WRITE, |
|
|
commit_message=f"Deleted file {file_path} for {username}" |
|
|
) |
|
|
data['users'][username]['files'] = [f for f in user_files if f['path'] != file_path] |
|
|
save_data(data) |
|
|
flash('File deleted successfully!') |
|
|
except Exception as e: |
|
|
logging.error(f"Error deleting file: {e}") |
|
|
flash('Error deleting file!') |
|
|
|
|
|
return redirect(url_for('dashboard')) |
|
|
|
|
|
@app.route('/logout') |
|
|
def logout(): |
|
|
session.pop('username', None) |
|
|
return redirect(url_for('login')) |
|
|
|
|
|
|
|
|
@app.route('/admhosto') |
|
|
def admin_panel(): |
|
|
data = load_data() |
|
|
users = data['users'] |
|
|
|
|
|
html = ''' |
|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Admin Panel - Zeus Cloud</title> |
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet"> |
|
|
<style>''' + BASE_STYLE + '''</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>Zeus Cloud Admin Panel</h1> |
|
|
<h2>User List</h2> |
|
|
<div class="user-list"> |
|
|
{% for username, user_data in users.items() %} |
|
|
<div class="user-item"> |
|
|
<a href="{{ url_for('admin_user_files', username=username) }}">{{ username }}</a> |
|
|
<p>Registration Date: {{ user_data['created_at'] }}</p> |
|
|
<p>File Count: {{ user_data['files'] | length }}</p> |
|
|
</div> |
|
|
{% endfor %} |
|
|
{% if not users %} |
|
|
<p>No users yet.</p> |
|
|
{% endif %} |
|
|
</div> |
|
|
</div> |
|
|
</body> |
|
|
</html> |
|
|
''' |
|
|
return render_template_string(html, users=users) |
|
|
|
|
|
@app.route('/admhosto/user/<username>') |
|
|
def admin_user_files(username): |
|
|
data = load_data() |
|
|
if username not in data['users']: |
|
|
flash('User not found!') |
|
|
return redirect(url_for('admin_panel')) |
|
|
|
|
|
user_files = sorted(data['users'][username]['files'], key=lambda x: x['upload_date'], reverse=True) |
|
|
|
|
|
html = ''' |
|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>User {{ username }} Files - Zeus Cloud</title> |
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet"> |
|
|
<style>''' + BASE_STYLE + '''</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>User Files: {{ username }}</h1> |
|
|
{% with messages = get_flashed_messages() %} |
|
|
{% if messages %} |
|
|
{% for message in messages %} |
|
|
<div class="flash">{{ message }}</div> |
|
|
{% endfor %} |
|
|
{% endif %} |
|
|
{% endwith %} |
|
|
<div class="file-grid"> |
|
|
{% for file in user_files %} |
|
|
<div class="file-item"> |
|
|
{% if file['type'] == 'video' %} |
|
|
<video class="file-preview" preload="metadata" muted loading="lazy" onclick="openModal('https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}', true)"> |
|
|
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" type="video/mp4"> |
|
|
</video> |
|
|
{% elif file['type'] == 'image' %} |
|
|
<img class="file-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" alt="{{ file['filename'] }}" loading="lazy" onclick="openModal(this.src, false)"> |
|
|
{% else %} |
|
|
<p>{{ file['filename'] }}</p> |
|
|
{% endif %} |
|
|
<p>{{ file['upload_date'] }}</p> |
|
|
<a href="{{ url_for('download_file', file_path=file['path'], filename=file['filename']) }}" class="btn download-btn">Download</a> |
|
|
</div> |
|
|
{% endfor %} |
|
|
{% if not user_files %} |
|
|
<p>This user has no files yet.</p> |
|
|
{% endif %} |
|
|
</div> |
|
|
<a href="{{ url_for('admin_panel') }}" class="btn" style="margin-top: 20px;">Back to User List</a> |
|
|
</div> |
|
|
<div class="modal" id="mediaModal" onclick="closeModal(event)"> |
|
|
<div id="modalContent"></div> |
|
|
</div> |
|
|
<script> |
|
|
function openModal(src, isVideo) { |
|
|
const modal = document.getElementById('mediaModal'); |
|
|
const modalContent = document.getElementById('modalContent'); |
|
|
if (isVideo) { |
|
|
modalContent.innerHTML = `<video controls><source src="${src}" type="video/mp4"></video>`; |
|
|
} else { |
|
|
modalContent.innerHTML = `<img src="${src}">`; |
|
|
} |
|
|
modal.style.display = 'flex'; |
|
|
} |
|
|
function closeModal(event) { |
|
|
if (event.target.tagName !== 'IMG' && event.target.tagName !== 'VIDEO') { |
|
|
const modal = document.getElementById('mediaModal'); |
|
|
modal.style.display = 'none'; |
|
|
modal.innerHTML = '<div id="modalContent"></div>'; |
|
|
} |
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
''' |
|
|
return render_template_string(html, username=username, user_files=user_files, repo_id=REPO_ID) |
|
|
|
|
|
if __name__ == '__main__': |
|
|
threading.Thread(target=periodic_backup, daemon=True).start() |
|
|
app.run(debug=False, host='0.0.0.0', port=7860) |