e / app.py
Eluza133's picture
Update app.py
305ef12 verified
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)
# Database and Hugging Face functions
@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'))
# Admin panel
@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)