Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# --- START OF FILE app (
|
| 2 |
from flask import Flask, render_template_string, request, redirect, url_for, session, flash, send_file, jsonify, Response
|
| 3 |
from flask_caching import Cache
|
| 4 |
import json
|
|
@@ -227,6 +227,7 @@ body.dark input, body.dark textarea { color: var(--text-dark); }
|
|
| 227 |
input:focus, textarea:focus { outline: none; box-shadow: 0 0 0 4px var(--primary); }
|
| 228 |
.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; margin-top: 5px; margin-right: 5px; }
|
| 229 |
.btn:hover { transform: scale(1.05); background: #e6415f; }
|
|
|
|
| 230 |
.download-btn { background: var(--secondary); }
|
| 231 |
.download-btn:hover { background: #00b8c5; }
|
| 232 |
.delete-btn { background: var(--delete-color); }
|
|
@@ -261,8 +262,9 @@ body.dark .modal-content { background: var(--card-bg-dark); }
|
|
| 261 |
body.dark .modal pre { background: #2b2a33; color: var(--text-dark); }
|
| 262 |
.modal-close-btn { position: absolute; top: 15px; right: 25px; font-size: 30px; color: #aaa; cursor: pointer; background: rgba(0,0,0,0.5); border-radius: 50%; width: 30px; height: 30px; line-height: 30px; text-align: center; }
|
| 263 |
body.dark .modal-close-btn { color: #555; background: rgba(255,255,255,0.2); }
|
| 264 |
-
#progress-container { width: 100%; background: var(--glass-bg); border-radius: 10px; margin: 15px 0; display: none; }
|
| 265 |
-
#progress-bar { width: 0%; height:
|
|
|
|
| 266 |
.breadcrumbs { margin-bottom: 20px; font-size: 1.1em; }
|
| 267 |
.breadcrumbs a { color: var(--accent); text-decoration: none; }
|
| 268 |
.breadcrumbs a:hover { text-decoration: underline; }
|
|
@@ -555,12 +557,15 @@ def dashboard():
|
|
| 555 |
</form>
|
| 556 |
</div>
|
| 557 |
|
| 558 |
-
<form id="upload-form" method="POST" enctype="multipart/form-data">
|
| 559 |
<input type="hidden" name="current_folder_id" value="{{ current_folder_id }}">
|
| 560 |
<input type="file" name="files" multiple required>
|
| 561 |
<button type="submit" class="btn" id="upload-btn">Загрузить файлы сюда</button>
|
| 562 |
</form>
|
| 563 |
-
<div id="progress-container"
|
|
|
|
|
|
|
|
|
|
| 564 |
|
| 565 |
<h2>Содержимое папки: {{ current_folder.name if current_folder_id != 'root' else 'Главная' }}</h2>
|
| 566 |
<div class="file-grid">
|
|
@@ -697,27 +702,65 @@ def dashboard():
|
|
| 697 |
|
| 698 |
const form = document.getElementById('upload-form');
|
| 699 |
const progressBar = document.getElementById('progress-bar');
|
|
|
|
| 700 |
const progressContainer = document.getElementById('progress-container');
|
| 701 |
const uploadBtn = document.getElementById('upload-btn');
|
|
|
|
| 702 |
|
| 703 |
form.addEventListener('submit', function(e) {
|
| 704 |
-
|
| 705 |
-
|
|
|
|
| 706 |
alert('Пожалуйста, выберите файлы для загрузки.');
|
| 707 |
-
e.preventDefault();
|
| 708 |
return;
|
| 709 |
}
|
| 710 |
-
if (files.length > 20) {
|
| 711 |
alert('Максимум 20 файлов за раз!');
|
| 712 |
-
e.preventDefault();
|
| 713 |
return;
|
| 714 |
}
|
| 715 |
|
| 716 |
progressContainer.style.display = 'block';
|
| 717 |
-
progressBar.style.width = '
|
|
|
|
| 718 |
uploadBtn.disabled = true;
|
| 719 |
uploadBtn.textContent = 'Загрузка...';
|
| 720 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
});
|
| 722 |
|
| 723 |
document.getElementById('logout-btn').addEventListener('click', function(e) {
|
|
@@ -786,47 +829,45 @@ def create_folder():
|
|
| 786 |
|
| 787 |
@app.route('/download/<file_id>')
|
| 788 |
def download_file(file_id):
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
flash('Доступ запрещен (Admin).')
|
| 796 |
-
return redirect(url_for('login'))
|
| 797 |
-
# Admin access allowed to proceed
|
| 798 |
|
| 799 |
data = load_data()
|
| 800 |
file_node = None
|
| 801 |
-
username_context =
|
| 802 |
|
| 803 |
-
if
|
| 804 |
-
username_context = session['username']
|
| 805 |
user_data = data['users'].get(username_context)
|
| 806 |
if user_data:
|
| 807 |
-
file_node, _ = find_node_by_id(user_data
|
| 808 |
|
| 809 |
-
|
| 810 |
-
if not file_node and is_admin():
|
| 811 |
logging.info(f"Admin searching for file ID {file_id} across all users.")
|
| 812 |
for uname, udata in data.get('users', {}).items():
|
| 813 |
node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
|
| 814 |
if node and node.get('type') == 'file':
|
| 815 |
file_node = node
|
| 816 |
-
username_context = uname
|
| 817 |
logging.info(f"Admin found file ID {file_id} belonging to user {username_context}")
|
| 818 |
break
|
| 819 |
|
| 820 |
if not file_node or file_node.get('type') != 'file':
|
| 821 |
flash('Файл не найден!', 'error')
|
| 822 |
-
|
|
|
|
|
|
|
| 823 |
|
| 824 |
hf_path = file_node.get('path')
|
| 825 |
original_filename = file_node.get('original_filename', 'downloaded_file')
|
| 826 |
|
| 827 |
if not hf_path:
|
| 828 |
flash('Ошибка: Путь к файлу не найден в метаданных.', 'error')
|
| 829 |
-
|
|
|
|
| 830 |
|
| 831 |
file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
|
| 832 |
|
|
@@ -848,11 +889,14 @@ def download_file(file_id):
|
|
| 848 |
except requests.exceptions.RequestException as e:
|
| 849 |
logging.error(f"Error downloading file from HF ({hf_path}): {e}")
|
| 850 |
flash(f'Ошибка скачивания файла {original_filename}! ({e})', 'error')
|
| 851 |
-
|
|
|
|
| 852 |
except Exception as e:
|
| 853 |
logging.error(f"Unexpected error during download ({hf_path}): {e}")
|
| 854 |
flash('Произошла непредвиденная ошибка при скачивании файла.', 'error')
|
| 855 |
-
|
|
|
|
|
|
|
| 856 |
|
| 857 |
|
| 858 |
@app.route('/delete_file/<file_id>', methods=['POST'])
|
|
@@ -981,27 +1025,21 @@ def delete_folder(folder_id):
|
|
| 981 |
|
| 982 |
@app.route('/get_text_content/<file_id>')
|
| 983 |
def get_text_content(file_id):
|
| 984 |
-
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
|
| 988 |
-
# Admin is accessing, proceed to find file globally
|
| 989 |
-
else:
|
| 990 |
-
# Regular user access
|
| 991 |
-
pass
|
| 992 |
|
| 993 |
data = load_data()
|
| 994 |
file_node = None
|
| 995 |
-
username_context =
|
| 996 |
|
| 997 |
-
if
|
| 998 |
-
username_context = session['username']
|
| 999 |
user_data = data['users'].get(username_context)
|
| 1000 |
if user_data:
|
| 1001 |
-
file_node, _ = find_node_by_id(user_data
|
| 1002 |
|
| 1003 |
-
|
| 1004 |
-
if not file_node and is_admin():
|
| 1005 |
logging.info(f"Admin searching for text file ID {file_id} across all users.")
|
| 1006 |
for uname, udata in data.get('users', {}).items():
|
| 1007 |
node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
|
|
@@ -1057,9 +1095,11 @@ def logout():
|
|
| 1057 |
|
| 1058 |
|
| 1059 |
def is_admin():
|
| 1060 |
-
#
|
| 1061 |
-
#
|
| 1062 |
-
|
|
|
|
|
|
|
| 1063 |
|
| 1064 |
@app.route('/admhosto')
|
| 1065 |
def admin_panel():
|
|
@@ -1073,14 +1113,16 @@ def admin_panel():
|
|
| 1073 |
user_details = []
|
| 1074 |
for uname, udata in users.items():
|
| 1075 |
file_count = 0
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
-
|
| 1080 |
-
|
| 1081 |
-
|
| 1082 |
-
|
| 1083 |
-
|
|
|
|
|
|
|
| 1084 |
user_details.append({
|
| 1085 |
'username': uname,
|
| 1086 |
'created_at': udata.get('created_at', 'N/A'),
|
|
@@ -1099,9 +1141,11 @@ def admin_panel():
|
|
| 1099 |
<a href="{{ url_for('admin_user_files', username=user.username) }}">{{ user.username }}</a>
|
| 1100 |
<p>Зарегистрирован: {{ user.created_at }}</p>
|
| 1101 |
<p>Файлов: {{ user.file_count }}</p>
|
|
|
|
| 1102 |
<form method="POST" action="{{ url_for('admin_delete_user', username=user.username) }}" style="display: inline; margin-left: 10px;" onsubmit="return confirm('УДАЛИТЬ пользователя {{ user.username }} и ВСЕ его файлы? НЕОБРАТИМО!');">
|
| 1103 |
<button type="submit" class="btn delete-btn" style="padding: 5px 10px; font-size: 0.9em;">Удалить</button>
|
| 1104 |
</form>
|
|
|
|
| 1105 |
</div>
|
| 1106 |
{% else %}<p>Пользователей нет.</p>{% endfor %}</div></div></body></html>'''
|
| 1107 |
return render_template_string(html, user_details=user_details)
|
|
@@ -1119,18 +1163,18 @@ def admin_user_files(username):
|
|
| 1119 |
return redirect(url_for('admin_panel'))
|
| 1120 |
|
| 1121 |
all_files = []
|
| 1122 |
-
def
|
| 1123 |
-
|
| 1124 |
-
|
| 1125 |
|
| 1126 |
for item in folder.get('children', []):
|
| 1127 |
if item.get('type') == 'file':
|
| 1128 |
-
item['parent_path_str'] =
|
| 1129 |
all_files.append(item)
|
| 1130 |
elif item.get('type') == 'folder':
|
| 1131 |
-
|
| 1132 |
|
| 1133 |
-
|
| 1134 |
all_files.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
|
| 1135 |
|
| 1136 |
|
|
@@ -1208,7 +1252,7 @@ body.dark .file-item { background: var(--card-bg-dark); }
|
|
| 1208 |
const response = await fetch(srcOrUrl);
|
| 1209 |
if (!response.ok) throw new Error(`Ошибка загрузки текста: ${response.statusText}`);
|
| 1210 |
const text = await response.text();
|
| 1211 |
-
const escapedText = text.replace(/</g, "<").replace(/>/g, ">");
|
| 1212 |
modalContent.innerHTML = `<pre>${escapedText}</pre>`;
|
| 1213 |
} else {
|
| 1214 |
modalContent.innerHTML = '<p>Предпросмотр для этого типа файла не поддерживается.</p>';
|
|
@@ -1245,6 +1289,9 @@ def admin_delete_user(username):
|
|
| 1245 |
if not is_admin():
|
| 1246 |
flash('Доступ запрещен.', 'error')
|
| 1247 |
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
| 1248 |
if not HF_TOKEN_WRITE:
|
| 1249 |
flash('Удаление невозможно: токен для записи не настроен.', 'error')
|
| 1250 |
return redirect(url_for('admin_panel'))
|
|
@@ -1262,35 +1309,62 @@ def admin_delete_user(username):
|
|
| 1262 |
user_folder_path_on_hf = f"cloud_files/{username}"
|
| 1263 |
|
| 1264 |
logging.info(f"Attempting to delete HF Hub folder: {user_folder_path_on_hf} for user {username}")
|
| 1265 |
-
|
| 1266 |
-
|
| 1267 |
-
|
| 1268 |
-
|
| 1269 |
-
|
| 1270 |
-
|
| 1271 |
-
|
| 1272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1273 |
|
| 1274 |
except hf_utils.HfHubHTTPError as e:
|
| 1275 |
-
|
| 1276 |
-
|
|
|
|
| 1277 |
else:
|
| 1278 |
-
logging.error(f"Error deleting user folder {user_folder_path_on_hf} from HF Hub for {username}: {e}")
|
| 1279 |
flash(f'Ошибка при удалении файлов пользователя {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
|
| 1280 |
return redirect(url_for('admin_panel'))
|
| 1281 |
except Exception as e:
|
| 1282 |
-
logging.error(f"Unexpected error during HF Hub folder deletion for {username}: {e}")
|
| 1283 |
flash(f'Неожиданная ошибка при удалении файлов {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
|
| 1284 |
return redirect(url_for('admin_panel'))
|
| 1285 |
|
|
|
|
| 1286 |
try:
|
| 1287 |
del data['users'][username]
|
| 1288 |
save_data(data)
|
| 1289 |
-
flash(f'Пользователь {username} и его файлы (запрос на удаление
|
| 1290 |
logging.info(f"ADMIN ACTION: Successfully deleted user {username} from database.")
|
| 1291 |
except Exception as e:
|
| 1292 |
logging.error(f"Error saving data after deleting user {username}: {e}")
|
| 1293 |
-
flash(f'Файлы пользователя {username} удалены с
|
| 1294 |
|
| 1295 |
return redirect(url_for('admin_panel'))
|
| 1296 |
|
|
@@ -1396,4 +1470,4 @@ if __name__ == '__main__':
|
|
| 1396 |
|
| 1397 |
app.run(debug=False, host='0.0.0.0', port=7860)
|
| 1398 |
|
| 1399 |
-
# --- END OF FILE app (
|
|
|
|
| 1 |
+
# --- START OF FILE app (11).py ---
|
| 2 |
from flask import Flask, render_template_string, request, redirect, url_for, session, flash, send_file, jsonify, Response
|
| 3 |
from flask_caching import Cache
|
| 4 |
import json
|
|
|
|
| 227 |
input:focus, textarea:focus { outline: none; box-shadow: 0 0 0 4px var(--primary); }
|
| 228 |
.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; margin-top: 5px; margin-right: 5px; }
|
| 229 |
.btn:hover { transform: scale(1.05); background: #e6415f; }
|
| 230 |
+
.btn:disabled { background: #aaa; cursor: not-allowed; }
|
| 231 |
.download-btn { background: var(--secondary); }
|
| 232 |
.download-btn:hover { background: #00b8c5; }
|
| 233 |
.delete-btn { background: var(--delete-color); }
|
|
|
|
| 262 |
body.dark .modal pre { background: #2b2a33; color: var(--text-dark); }
|
| 263 |
.modal-close-btn { position: absolute; top: 15px; right: 25px; font-size: 30px; color: #aaa; cursor: pointer; background: rgba(0,0,0,0.5); border-radius: 50%; width: 30px; height: 30px; line-height: 30px; text-align: center; }
|
| 264 |
body.dark .modal-close-btn { color: #555; background: rgba(255,255,255,0.2); }
|
| 265 |
+
#progress-container { width: 100%; background: var(--glass-bg); border-radius: 10px; margin: 15px 0; display: none; overflow: hidden; height: 25px; position: relative;}
|
| 266 |
+
#progress-bar { width: 0%; height: 100%; background: linear-gradient(135deg, var(--primary), var(--accent)); border-radius: 10px; transition: width 0.3s ease; }
|
| 267 |
+
#progress-text { position: absolute; top: 0; left: 0; width: 100%; height: 100%; text-align: center; line-height: 25px; color: white; font-weight: bold; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
|
| 268 |
.breadcrumbs { margin-bottom: 20px; font-size: 1.1em; }
|
| 269 |
.breadcrumbs a { color: var(--accent); text-decoration: none; }
|
| 270 |
.breadcrumbs a:hover { text-decoration: underline; }
|
|
|
|
| 557 |
</form>
|
| 558 |
</div>
|
| 559 |
|
| 560 |
+
<form id="upload-form" method="POST" enctype="multipart/form-data" action="{{ url_for('dashboard') }}">
|
| 561 |
<input type="hidden" name="current_folder_id" value="{{ current_folder_id }}">
|
| 562 |
<input type="file" name="files" multiple required>
|
| 563 |
<button type="submit" class="btn" id="upload-btn">Загрузить файлы сюда</button>
|
| 564 |
</form>
|
| 565 |
+
<div id="progress-container">
|
| 566 |
+
<div id="progress-bar"></div>
|
| 567 |
+
<div id="progress-text">0%</div>
|
| 568 |
+
</div>
|
| 569 |
|
| 570 |
<h2>Содержимое папки: {{ current_folder.name if current_folder_id != 'root' else 'Главная' }}</h2>
|
| 571 |
<div class="file-grid">
|
|
|
|
| 702 |
|
| 703 |
const form = document.getElementById('upload-form');
|
| 704 |
const progressBar = document.getElementById('progress-bar');
|
| 705 |
+
const progressText = document.getElementById('progress-text');
|
| 706 |
const progressContainer = document.getElementById('progress-container');
|
| 707 |
const uploadBtn = document.getElementById('upload-btn');
|
| 708 |
+
const fileInput = form.querySelector('input[type="file"]');
|
| 709 |
|
| 710 |
form.addEventListener('submit', function(e) {
|
| 711 |
+
e.preventDefault();
|
| 712 |
+
|
| 713 |
+
if (fileInput.files.length === 0) {
|
| 714 |
alert('Пожалуйста, выберите файлы для загрузки.');
|
|
|
|
| 715 |
return;
|
| 716 |
}
|
| 717 |
+
if (fileInput.files.length > 20) {
|
| 718 |
alert('Максимум 20 файлов за раз!');
|
|
|
|
| 719 |
return;
|
| 720 |
}
|
| 721 |
|
| 722 |
progressContainer.style.display = 'block';
|
| 723 |
+
progressBar.style.width = '0%';
|
| 724 |
+
progressText.textContent = '0%';
|
| 725 |
uploadBtn.disabled = true;
|
| 726 |
uploadBtn.textContent = 'Загрузка...';
|
| 727 |
|
| 728 |
+
const formData = new FormData(form);
|
| 729 |
+
const xhr = new XMLHttpRequest();
|
| 730 |
+
|
| 731 |
+
xhr.upload.addEventListener('progress', function(event) {
|
| 732 |
+
if (event.lengthComputable) {
|
| 733 |
+
const percentComplete = Math.round((event.loaded / event.total) * 100);
|
| 734 |
+
progressBar.style.width = percentComplete + '%';
|
| 735 |
+
progressText.textContent = percentComplete + '%';
|
| 736 |
+
}
|
| 737 |
+
});
|
| 738 |
+
|
| 739 |
+
xhr.addEventListener('load', function() {
|
| 740 |
+
progressBar.style.width = '100%';
|
| 741 |
+
progressText.textContent = 'Обработка...';
|
| 742 |
+
// Reload the page to show updated file list and flash messages
|
| 743 |
+
// The server handles the redirect implicitly after successful POST
|
| 744 |
+
window.location.reload();
|
| 745 |
+
});
|
| 746 |
+
|
| 747 |
+
xhr.addEventListener('error', function() {
|
| 748 |
+
alert('Произошла ошибка во время загрузки.');
|
| 749 |
+
uploadBtn.disabled = false;
|
| 750 |
+
uploadBtn.textContent = 'Загрузить файлы сюда';
|
| 751 |
+
progressContainer.style.display = 'none';
|
| 752 |
+
});
|
| 753 |
+
|
| 754 |
+
xhr.addEventListener('abort', function() {
|
| 755 |
+
alert('Загрузка отменена.');
|
| 756 |
+
uploadBtn.disabled = false;
|
| 757 |
+
uploadBtn.textContent = 'Загрузить файлы сюда';
|
| 758 |
+
progressContainer.style.display = 'none';
|
| 759 |
+
});
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
xhr.open('POST', form.action);
|
| 763 |
+
xhr.send(formData);
|
| 764 |
});
|
| 765 |
|
| 766 |
document.getElementById('logout-btn').addEventListener('click', function(e) {
|
|
|
|
| 829 |
|
| 830 |
@app.route('/download/<file_id>')
|
| 831 |
def download_file(file_id):
|
| 832 |
+
is_admin_route = request.referrer and 'admhosto' in request.referrer
|
| 833 |
+
is_logged_in_admin = 'username' in session and is_admin()
|
| 834 |
+
|
| 835 |
+
if 'username' not in session and not (is_admin_route and is_logged_in_admin):
|
| 836 |
+
flash('Пожалуйста, войдите в систему!')
|
| 837 |
+
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
| 838 |
|
| 839 |
data = load_data()
|
| 840 |
file_node = None
|
| 841 |
+
username_context = session.get('username') # Default to current user if logged in
|
| 842 |
|
| 843 |
+
if username_context:
|
|
|
|
| 844 |
user_data = data['users'].get(username_context)
|
| 845 |
if user_data:
|
| 846 |
+
file_node, _ = find_node_by_id(user_data.get('filesystem', {}), file_id)
|
| 847 |
|
| 848 |
+
if not file_node and is_logged_in_admin:
|
|
|
|
| 849 |
logging.info(f"Admin searching for file ID {file_id} across all users.")
|
| 850 |
for uname, udata in data.get('users', {}).items():
|
| 851 |
node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
|
| 852 |
if node and node.get('type') == 'file':
|
| 853 |
file_node = node
|
| 854 |
+
username_context = uname
|
| 855 |
logging.info(f"Admin found file ID {file_id} belonging to user {username_context}")
|
| 856 |
break
|
| 857 |
|
| 858 |
if not file_node or file_node.get('type') != 'file':
|
| 859 |
flash('Файл не найден!', 'error')
|
| 860 |
+
redirect_url = url_for('admin_panel') if is_logged_in_admin and is_admin_route else url_for('dashboard') if 'username' in session else url_for('login')
|
| 861 |
+
return redirect(request.referrer or redirect_url)
|
| 862 |
+
|
| 863 |
|
| 864 |
hf_path = file_node.get('path')
|
| 865 |
original_filename = file_node.get('original_filename', 'downloaded_file')
|
| 866 |
|
| 867 |
if not hf_path:
|
| 868 |
flash('Ошибка: Путь к файлу не найден в метаданных.', 'error')
|
| 869 |
+
redirect_url = url_for('admin_user_files', username=username_context) if is_logged_in_admin and is_admin_route and username_context else url_for('dashboard') if 'username' in session else url_for('login')
|
| 870 |
+
return redirect(request.referrer or redirect_url)
|
| 871 |
|
| 872 |
file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
|
| 873 |
|
|
|
|
| 889 |
except requests.exceptions.RequestException as e:
|
| 890 |
logging.error(f"Error downloading file from HF ({hf_path}): {e}")
|
| 891 |
flash(f'Ошибка скачивания файла {original_filename}! ({e})', 'error')
|
| 892 |
+
redirect_url = url_for('admin_user_files', username=username_context) if is_logged_in_admin and is_admin_route and username_context else url_for('dashboard') if 'username' in session else url_for('login')
|
| 893 |
+
return redirect(request.referrer or redirect_url)
|
| 894 |
except Exception as e:
|
| 895 |
logging.error(f"Unexpected error during download ({hf_path}): {e}")
|
| 896 |
flash('Произошла непредвиденная ошибка при скачивании файла.', 'error')
|
| 897 |
+
redirect_url = url_for('admin_user_files', username=username_context) if is_logged_in_admin and is_admin_route and username_context else url_for('dashboard') if 'username' in session else url_for('login')
|
| 898 |
+
return redirect(request.referrer or redirect_url)
|
| 899 |
+
|
| 900 |
|
| 901 |
|
| 902 |
@app.route('/delete_file/<file_id>', methods=['POST'])
|
|
|
|
| 1025 |
|
| 1026 |
@app.route('/get_text_content/<file_id>')
|
| 1027 |
def get_text_content(file_id):
|
| 1028 |
+
is_logged_in_admin = 'username' in session and is_admin()
|
| 1029 |
+
|
| 1030 |
+
if 'username' not in session and not is_logged_in_admin:
|
| 1031 |
+
return Response("Не авторизован", status=401)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1032 |
|
| 1033 |
data = load_data()
|
| 1034 |
file_node = None
|
| 1035 |
+
username_context = session.get('username') # Default to current user if logged in
|
| 1036 |
|
| 1037 |
+
if username_context:
|
|
|
|
| 1038 |
user_data = data['users'].get(username_context)
|
| 1039 |
if user_data:
|
| 1040 |
+
file_node, _ = find_node_by_id(user_data.get('filesystem', {}), file_id)
|
| 1041 |
|
| 1042 |
+
if not file_node and is_logged_in_admin:
|
|
|
|
| 1043 |
logging.info(f"Admin searching for text file ID {file_id} across all users.")
|
| 1044 |
for uname, udata in data.get('users', {}).items():
|
| 1045 |
node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
|
|
|
|
| 1095 |
|
| 1096 |
|
| 1097 |
def is_admin():
|
| 1098 |
+
# SECURITY WARNING: This is NOT a secure way to check for admin status.
|
| 1099 |
+
# Replace this with a proper check, e.g., checking against a list of admin usernames
|
| 1100 |
+
# stored securely, or checking a specific flag in the user's data.
|
| 1101 |
+
# return session.get('username') == 'admin' # Example
|
| 1102 |
+
return 'username' in session and session.get('username') == 'admhosto' # Example: only user 'admhosto' is admin
|
| 1103 |
|
| 1104 |
@app.route('/admhosto')
|
| 1105 |
def admin_panel():
|
|
|
|
| 1113 |
user_details = []
|
| 1114 |
for uname, udata in users.items():
|
| 1115 |
file_count = 0
|
| 1116 |
+
# Correctly implement recursive file counting for nested folders
|
| 1117 |
+
stack = [udata.get('filesystem', {})]
|
| 1118 |
+
while stack:
|
| 1119 |
+
current_node = stack.pop()
|
| 1120 |
+
if current_node.get('type') == 'file':
|
| 1121 |
+
file_count += 1
|
| 1122 |
+
elif current_node.get('type') == 'folder' and 'children' in current_node:
|
| 1123 |
+
for child in current_node['children']:
|
| 1124 |
+
stack.append(child)
|
| 1125 |
+
|
| 1126 |
user_details.append({
|
| 1127 |
'username': uname,
|
| 1128 |
'created_at': udata.get('created_at', 'N/A'),
|
|
|
|
| 1141 |
<a href="{{ url_for('admin_user_files', username=user.username) }}">{{ user.username }}</a>
|
| 1142 |
<p>Зарегистрирован: {{ user.created_at }}</p>
|
| 1143 |
<p>Файлов: {{ user.file_count }}</p>
|
| 1144 |
+
{% if user.username != 'admhosto' %} {# Prevent deleting the admin user itself #}
|
| 1145 |
<form method="POST" action="{{ url_for('admin_delete_user', username=user.username) }}" style="display: inline; margin-left: 10px;" onsubmit="return confirm('УДАЛИТЬ пользователя {{ user.username }} и ВСЕ его файлы? НЕОБРАТИМО!');">
|
| 1146 |
<button type="submit" class="btn delete-btn" style="padding: 5px 10px; font-size: 0.9em;">Удалить</button>
|
| 1147 |
</form>
|
| 1148 |
+
{% endif %}
|
| 1149 |
</div>
|
| 1150 |
{% else %}<p>Пользователей нет.</p>{% endfor %}</div></div></body></html>'''
|
| 1151 |
return render_template_string(html, user_details=user_details)
|
|
|
|
| 1163 |
return redirect(url_for('admin_panel'))
|
| 1164 |
|
| 1165 |
all_files = []
|
| 1166 |
+
def collect_files_recursive(folder, current_path_id_str='root'):
|
| 1167 |
+
if not folder or not isinstance(folder, dict): return
|
| 1168 |
+
parent_path_display = get_node_path_string(user_data['filesystem'], current_path_id_str)
|
| 1169 |
|
| 1170 |
for item in folder.get('children', []):
|
| 1171 |
if item.get('type') == 'file':
|
| 1172 |
+
item['parent_path_str'] = parent_path_display or "Root"
|
| 1173 |
all_files.append(item)
|
| 1174 |
elif item.get('type') == 'folder':
|
| 1175 |
+
collect_files_recursive(item, item.get('id'))
|
| 1176 |
|
| 1177 |
+
collect_files_recursive(user_data.get('filesystem', {}))
|
| 1178 |
all_files.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
|
| 1179 |
|
| 1180 |
|
|
|
|
| 1252 |
const response = await fetch(srcOrUrl);
|
| 1253 |
if (!response.ok) throw new Error(`Ошибка загрузки текста: ${response.statusText}`);
|
| 1254 |
const text = await response.text();
|
| 1255 |
+
const escapedText = text.replace(/</g, "<").replace(/>/g, ">"); // Correct escaping
|
| 1256 |
modalContent.innerHTML = `<pre>${escapedText}</pre>`;
|
| 1257 |
} else {
|
| 1258 |
modalContent.innerHTML = '<p>Предпросмотр для этого типа файла не поддерживается.</p>';
|
|
|
|
| 1289 |
if not is_admin():
|
| 1290 |
flash('Доступ запрещен.', 'error')
|
| 1291 |
return redirect(url_for('login'))
|
| 1292 |
+
if username == 'admhosto': # Add protection against deleting the admin itself
|
| 1293 |
+
flash('Нельзя удалить основного администратора.', 'error')
|
| 1294 |
+
return redirect(url_for('admin_panel'))
|
| 1295 |
if not HF_TOKEN_WRITE:
|
| 1296 |
flash('Удаление невозможно: токен для записи не настроен.', 'error')
|
| 1297 |
return redirect(url_for('admin_panel'))
|
|
|
|
| 1309 |
user_folder_path_on_hf = f"cloud_files/{username}"
|
| 1310 |
|
| 1311 |
logging.info(f"Attempting to delete HF Hub folder: {user_folder_path_on_hf} for user {username}")
|
| 1312 |
+
# Listing files first to handle potential large number of files / non-empty folders robustly
|
| 1313 |
+
repo_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_READ or HF_TOKEN_WRITE)
|
| 1314 |
+
user_files_to_delete = [f for f in repo_files if f.startswith(user_folder_path_on_hf + '/')]
|
| 1315 |
+
|
| 1316 |
+
if user_files_to_delete:
|
| 1317 |
+
delete_operations = [hf_utils.DeleteUploadedFile(path_in_repo=f) for f in user_files_to_delete]
|
| 1318 |
+
# Also attempt to delete the folder itself if HF Hub API supports direct folder deletion well by now
|
| 1319 |
+
# If not, deleting all files might be sufficient or might require individual file deletion commits.
|
| 1320 |
+
# The delete_folder function might be better now, let's try it first.
|
| 1321 |
+
try:
|
| 1322 |
+
api.delete_folder(
|
| 1323 |
+
folder_path=user_folder_path_on_hf,
|
| 1324 |
+
repo_id=REPO_ID,
|
| 1325 |
+
repo_type="dataset",
|
| 1326 |
+
token=HF_TOKEN_WRITE,
|
| 1327 |
+
commit_message=f"ADMIN ACTION: Deleted all files/folders for user {username}"
|
| 1328 |
+
)
|
| 1329 |
+
logging.info(f"Successfully initiated deletion of folder {user_folder_path_on_hf} on HF Hub.")
|
| 1330 |
+
except Exception as folder_del_err:
|
| 1331 |
+
logging.warning(f"Direct folder deletion failed ({folder_del_err}), attempting individual file deletion.")
|
| 1332 |
+
if delete_operations:
|
| 1333 |
+
api.delete_files(
|
| 1334 |
+
repo_id=REPO_ID,
|
| 1335 |
+
paths_in_repo=[op.path_in_repo for op in delete_operations],
|
| 1336 |
+
repo_type="dataset",
|
| 1337 |
+
token=HF_TOKEN_WRITE,
|
| 1338 |
+
commit_message=f"ADMIN ACTION: Deleted files for user {username}"
|
| 1339 |
+
)
|
| 1340 |
+
logging.info(f"Successfully deleted {len(delete_operations)} individual files for user {username} on HF Hub.")
|
| 1341 |
+
|
| 1342 |
+
else:
|
| 1343 |
+
logging.info(f"No files found in HF Hub folder {user_folder_path_on_hf} for user {username}.")
|
| 1344 |
+
|
| 1345 |
|
| 1346 |
except hf_utils.HfHubHTTPError as e:
|
| 1347 |
+
# Handle case where the folder might not exist (already deleted or never created)
|
| 1348 |
+
if e.response.status_code == 404 or "EntryNotFoundError" in str(e) or "not found" in str(e).lower():
|
| 1349 |
+
logging.warning(f"User folder {user_folder_path_on_hf} or files not found on HF Hub for user {username}. Skipping HF deletion.")
|
| 1350 |
else:
|
| 1351 |
+
logging.error(f"Error deleting user folder/files {user_folder_path_on_hf} from HF Hub for {username}: {e}")
|
| 1352 |
flash(f'Ошибка при удалении файлов пользователя {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
|
| 1353 |
return redirect(url_for('admin_panel'))
|
| 1354 |
except Exception as e:
|
| 1355 |
+
logging.error(f"Unexpected error during HF Hub folder/file deletion for {username}: {e}")
|
| 1356 |
flash(f'Неожиданная ошибка при удалении файлов {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
|
| 1357 |
return redirect(url_for('admin_panel'))
|
| 1358 |
|
| 1359 |
+
# Proceed with deleting user from database only if HF deletion was successful or skipped reasonably
|
| 1360 |
try:
|
| 1361 |
del data['users'][username]
|
| 1362 |
save_data(data)
|
| 1363 |
+
flash(f'Пользователь {username} и его файлы (запрос на удаление с сервера отправлен/пропущен) успешно удалены из базы данных!')
|
| 1364 |
logging.info(f"ADMIN ACTION: Successfully deleted user {username} from database.")
|
| 1365 |
except Exception as e:
|
| 1366 |
logging.error(f"Error saving data after deleting user {username}: {e}")
|
| 1367 |
+
flash(f'Файлы пользователя {username} удалены с сервера (или не найдены), но произошла ошибка при удалении пользователя из базы данных: {e}', 'error')
|
| 1368 |
|
| 1369 |
return redirect(url_for('admin_panel'))
|
| 1370 |
|
|
|
|
| 1470 |
|
| 1471 |
app.run(debug=False, host='0.0.0.0', port=7860)
|
| 1472 |
|
| 1473 |
+
# --- END OF FILE app (11).py ---
|