e / app.py
Eluza133's picture
Update app.py
ee5bf70 verified
raw
history blame
15.8 kB
from flask import Flask, render_template_string, request, redirect, url_for, session, flash, 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 random
import string
app = Flask(__name__)
app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey")
DATA_FILE = 'cloud_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
ADMIN_PASSWORD = "87132morflot"
cache = Cache(app, config={'CACHE_TYPE': 'simple'})
logging.basicConfig(level=logging.INFO)
# Функции для работы с базой данных и Hugging Face
@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("Данные не в формате dict, инициализация пустой базы")
return {'users': {}, 'files': {}}
data.setdefault('users', {})
data.setdefault('files', {})
logging.info("Данные успешно загружены")
return data
except Exception as e:
logging.error(f"Ошибка при загрузке данных: {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("Данные сохранены и загружены на HF")
except Exception as e:
logging.error(f"Ошибка при сохранении данных: {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"Бэкап {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
)
logging.info("База данных загружена на Hugging Face")
except Exception as e:
logging.error(f"Ошибка при загрузке базы данных: {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("База данных скачана с Hugging Face")
except Exception as e:
logging.error(f"Ошибка при скачивании базы данных: {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) # Бэкап каждые 30 минут
# Генерация 13-значного токена
def generate_token():
return ''.join(random.choices(string.ascii_letters + string.digits, k=13))
# Определение типа файла для предпросмотра
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'
# Базовый стиль CSS
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;
}
* { 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;
}
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;
}
.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;
}
.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);
}
'''
# Регистрация через /admhosto
@app.route('/admhosto', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
password = request.form.get('password')
if password == ADMIN_PASSWORD:
token = generate_token()
data = load_data()
data['users'][token] = {
'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'files': []
}
save_data(data)
flash(f'Ваш токен: {token}. Сохраните его!')
return redirect(url_for('register'))
else:
flash('Неверный пароль!')
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Регистрация - Zues 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>Регистрация в Zues Cloud</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST">
<input type="password" name="password" placeholder="Введите пароль" required>
<button type="submit" class="btn">Зарегистрироваться</button>
</form>
</div>
</body>
</html>
'''
return render_template_string(html)
# Главная страница с вводом токена
@app.route('/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
token = request.form.get('token')
data = load_data()
if token in data['users'] and len(token) == 13:
session['token'] = token
return redirect(url_for('dashboard'))
else:
flash('Неверный токен! Токен должен быть 13 символов.')
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zues 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>Zues Cloud</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST">
<input type="text" name="token" placeholder="Введите ваш токен" required>
<button type="submit" class="btn">Войти</button>
</form>
<p style="margin-top: 20px;">Нет токена? <a href="{{ url_for('register') }}">Зарегистрируйтесь</a></p>
</div>
</body>
</html>
'''
return render_template_string(html)
# Личный dashboard
@app.route('/dashboard', methods=['GET', 'POST'])
def dashboard():
if 'token' not in session:
flash('Пожалуйста, войдите!')
return redirect(url_for('login'))
token = session['token']
data = load_data()
if token not in data['users']:
session.pop('token', None)
flash('Токен недействителен!')
return redirect(url_for('login'))
user_files = data['users'][token]['files']
if request.method == 'POST':
file = request.files.get('file')
if file and file.filename:
filename = secure_filename(file.filename)
temp_path = os.path.join('uploads', filename)
os.makedirs('uploads', exist_ok=True)
file.save(temp_path)
api = HfApi()
file_path = f"cloud_files/{token}/{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"Загружен файл для {token}"
)
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'][token]['files'].append(file_info)
save_data(data)
if os.path.exists(temp_path):
os.remove(temp_path)
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 - Zues 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>Zues Cloud Dashboard</h1>
<p>Токен: {{ token }}</p>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit" class="btn">Загрузить файл</button>
</form>
<h2 style="margin-top: 30px;">Ваши файлы</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><a href="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" target="_blank">{{ file['filename'] }}</a></p>
{% endif %}
<p>{{ file['upload_date'] }}</p>
<a href="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ file['path'] }}" class="btn download-btn" download="{{ file['filename'] }}">Скачать</a>
</div>
{% endfor %}
{% if not user_files %}
<p>У вас пока нет загруженных файлов.</p>
{% endif %}
</div>
<a href="{{ url_for('logout') }}" class="btn" style="margin-top: 20px;">Выйти</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, token=token, user_files=user_files, repo_id=REPO_ID)
# Выход
@app.route('/logout')
def logout():
session.pop('token', None)
return redirect(url_for('login'))
if __name__ == '__main__':
threading.Thread(target=periodic_backup, daemon=True).start()
app.run(debug=True, host='0.0.0.0', port=7860)