Update app.py
Browse files
app.py
CHANGED
|
@@ -227,7 +227,6 @@ 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 |
-
.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,9 +261,9 @@ body.dark .modal-content { background: var(--card-bg-dark); }
|
|
| 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;
|
| 266 |
-
#progress-bar { width: 0%; height: 100%; background:
|
| 267 |
-
#progress-text { position: absolute;
|
| 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; }
|
|
@@ -559,13 +558,10 @@ def dashboard():
|
|
| 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">
|
|
@@ -701,20 +697,21 @@ def dashboard():
|
|
| 701 |
}
|
| 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 |
-
|
|
|
|
| 714 |
alert('Пожалуйста, выберите файлы для загрузки.');
|
| 715 |
return;
|
| 716 |
}
|
| 717 |
-
if (
|
| 718 |
alert('Максимум 20 файлов за раз!');
|
| 719 |
return;
|
| 720 |
}
|
|
@@ -737,10 +734,9 @@ def dashboard():
|
|
| 737 |
});
|
| 738 |
|
| 739 |
xhr.addEventListener('load', function() {
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
// The server handles the redirect implicitly after successful POST
|
| 744 |
window.location.reload();
|
| 745 |
});
|
| 746 |
|
|
@@ -751,15 +747,14 @@ def dashboard():
|
|
| 751 |
progressContainer.style.display = 'none';
|
| 752 |
});
|
| 753 |
|
| 754 |
-
|
| 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 |
|
|
@@ -829,23 +824,27 @@ def create_folder():
|
|
| 829 |
|
| 830 |
@app.route('/download/<file_id>')
|
| 831 |
def download_file(file_id):
|
| 832 |
-
|
| 833 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 842 |
|
| 843 |
-
if
|
|
|
|
| 844 |
user_data = data['users'].get(username_context)
|
| 845 |
if user_data:
|
| 846 |
-
file_node, _ = find_node_by_id(user_data
|
| 847 |
|
| 848 |
-
if not file_node and
|
| 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)
|
|
@@ -857,17 +856,14 @@ def download_file(file_id):
|
|
| 857 |
|
| 858 |
if not file_node or file_node.get('type') != 'file':
|
| 859 |
flash('Файл не найден!', 'error')
|
| 860 |
-
|
| 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 |
-
|
| 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,14 +885,11 @@ def download_file(file_id):
|
|
| 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 |
-
|
| 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 |
-
|
| 898 |
-
return redirect(request.referrer or redirect_url)
|
| 899 |
-
|
| 900 |
|
| 901 |
|
| 902 |
@app.route('/delete_file/<file_id>', methods=['POST'])
|
|
@@ -1025,21 +1018,23 @@ def delete_folder(folder_id):
|
|
| 1025 |
|
| 1026 |
@app.route('/get_text_content/<file_id>')
|
| 1027 |
def get_text_content(file_id):
|
| 1028 |
-
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
|
|
|
| 1032 |
|
| 1033 |
data = load_data()
|
| 1034 |
file_node = None
|
| 1035 |
-
username_context =
|
| 1036 |
|
| 1037 |
-
if
|
|
|
|
| 1038 |
user_data = data['users'].get(username_context)
|
| 1039 |
if user_data:
|
| 1040 |
-
file_node, _ = find_node_by_id(user_data
|
| 1041 |
|
| 1042 |
-
if not file_node and
|
| 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,11 +1090,7 @@ def logout():
|
|
| 1095 |
|
| 1096 |
|
| 1097 |
def is_admin():
|
| 1098 |
-
|
| 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,16 +1104,14 @@ def admin_panel():
|
|
| 1113 |
user_details = []
|
| 1114 |
for uname, udata in users.items():
|
| 1115 |
file_count = 0
|
| 1116 |
-
|
| 1117 |
-
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
-
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
stack.append(child)
|
| 1125 |
-
|
| 1126 |
user_details.append({
|
| 1127 |
'username': uname,
|
| 1128 |
'created_at': udata.get('created_at', 'N/A'),
|
|
@@ -1141,11 +1130,9 @@ def admin_panel():
|
|
| 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,18 +1150,18 @@ def admin_user_files(username):
|
|
| 1163 |
return redirect(url_for('admin_panel'))
|
| 1164 |
|
| 1165 |
all_files = []
|
| 1166 |
-
def
|
| 1167 |
-
|
| 1168 |
-
|
| 1169 |
|
| 1170 |
for item in folder.get('children', []):
|
| 1171 |
if item.get('type') == 'file':
|
| 1172 |
-
item['parent_path_str'] =
|
| 1173 |
all_files.append(item)
|
| 1174 |
elif item.get('type') == 'folder':
|
| 1175 |
-
|
| 1176 |
|
| 1177 |
-
|
| 1178 |
all_files.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
|
| 1179 |
|
| 1180 |
|
|
@@ -1252,7 +1239,7 @@ body.dark .file-item { background: var(--card-bg-dark); }
|
|
| 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, ">");
|
| 1256 |
modalContent.innerHTML = `<pre>${escapedText}</pre>`;
|
| 1257 |
} else {
|
| 1258 |
modalContent.innerHTML = '<p>Предпросмотр для этого типа файла не поддерживается.</p>';
|
|
@@ -1289,9 +1276,6 @@ def admin_delete_user(username):
|
|
| 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,62 +1293,35 @@ def admin_delete_user(username):
|
|
| 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 |
-
|
| 1313 |
-
|
| 1314 |
-
|
| 1315 |
-
|
| 1316 |
-
|
| 1317 |
-
|
| 1318 |
-
|
| 1319 |
-
|
| 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 |
-
|
| 1348 |
-
|
| 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
|
| 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
|
| 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} удалены с
|
| 1368 |
|
| 1369 |
return redirect(url_for('admin_panel'))
|
| 1370 |
|
|
|
|
| 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 |
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; position: relative; height: 20px; }
|
| 265 |
+
#progress-bar { width: 0%; height: 100%; background: var(--primary); border-radius: 10px; transition: width 0.3s ease; }
|
| 266 |
+
#progress-text { position: absolute; width: 100%; text-align: center; line-height: 20px; color: white; font-size: 0.9em; font-weight: bold; text-shadow: 1px 1px 1px rgba(0,0,0,0.5); }
|
| 267 |
.breadcrumbs { margin-bottom: 20px; font-size: 1.1em; }
|
| 268 |
.breadcrumbs a { color: var(--accent); text-decoration: none; }
|
| 269 |
.breadcrumbs a:hover { text-decoration: underline; }
|
|
|
|
| 558 |
|
| 559 |
<form id="upload-form" method="POST" enctype="multipart/form-data" action="{{ url_for('dashboard') }}">
|
| 560 |
<input type="hidden" name="current_folder_id" value="{{ current_folder_id }}">
|
| 561 |
+
<input type="file" name="files" id="file-input" multiple required>
|
| 562 |
<button type="submit" class="btn" id="upload-btn">Загрузить файлы сюда</button>
|
| 563 |
</form>
|
| 564 |
+
<div id="progress-container"><div id="progress-bar"></div><div id="progress-text">0%</div></div>
|
|
|
|
|
|
|
|
|
|
| 565 |
|
| 566 |
<h2>Содержимое папки: {{ current_folder.name if current_folder_id != 'root' else 'Главная' }}</h2>
|
| 567 |
<div class="file-grid">
|
|
|
|
| 697 |
}
|
| 698 |
|
| 699 |
const form = document.getElementById('upload-form');
|
| 700 |
+
const fileInput = document.getElementById('file-input');
|
| 701 |
const progressBar = document.getElementById('progress-bar');
|
| 702 |
const progressText = document.getElementById('progress-text');
|
| 703 |
const progressContainer = document.getElementById('progress-container');
|
| 704 |
const uploadBtn = document.getElementById('upload-btn');
|
|
|
|
| 705 |
|
| 706 |
form.addEventListener('submit', function(e) {
|
| 707 |
e.preventDefault();
|
| 708 |
|
| 709 |
+
const files = fileInput.files;
|
| 710 |
+
if (files.length === 0) {
|
| 711 |
alert('Пожалуйста, выберите файлы для загрузки.');
|
| 712 |
return;
|
| 713 |
}
|
| 714 |
+
if (files.length > 20) {
|
| 715 |
alert('Максимум 20 файлов за раз!');
|
| 716 |
return;
|
| 717 |
}
|
|
|
|
| 734 |
});
|
| 735 |
|
| 736 |
xhr.addEventListener('load', function() {
|
| 737 |
+
uploadBtn.disabled = false;
|
| 738 |
+
uploadBtn.textContent = 'Загрузить файлы сюда';
|
| 739 |
+
progressContainer.style.display = 'none';
|
|
|
|
| 740 |
window.location.reload();
|
| 741 |
});
|
| 742 |
|
|
|
|
| 747 |
progressContainer.style.display = 'none';
|
| 748 |
});
|
| 749 |
|
| 750 |
+
xhr.addEventListener('abort', function() {
|
| 751 |
alert('Загрузка отменена.');
|
| 752 |
uploadBtn.disabled = false;
|
| 753 |
uploadBtn.textContent = 'Загрузить файлы сюда';
|
| 754 |
progressContainer.style.display = 'none';
|
| 755 |
});
|
| 756 |
|
| 757 |
+
xhr.open('POST', form.action, true);
|
|
|
|
| 758 |
xhr.send(formData);
|
| 759 |
});
|
| 760 |
|
|
|
|
| 824 |
|
| 825 |
@app.route('/download/<file_id>')
|
| 826 |
def download_file(file_id):
|
| 827 |
+
if 'username' not in session:
|
| 828 |
+
is_admin_route = request.referrer and 'admhosto' in request.referrer
|
| 829 |
+
if not is_admin_route:
|
| 830 |
+
flash('Пожалуйста, войдите в систему!')
|
| 831 |
+
return redirect(url_for('login'))
|
| 832 |
+
elif not is_admin():
|
| 833 |
+
flash('Доступ запрещен (Admin).')
|
| 834 |
+
return redirect(url_for('login'))
|
| 835 |
|
|
|
|
|
|
|
|
|
|
| 836 |
|
| 837 |
data = load_data()
|
| 838 |
file_node = None
|
| 839 |
+
username_context = None
|
| 840 |
|
| 841 |
+
if 'username' in session:
|
| 842 |
+
username_context = session['username']
|
| 843 |
user_data = data['users'].get(username_context)
|
| 844 |
if user_data:
|
| 845 |
+
file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
|
| 846 |
|
| 847 |
+
if not file_node and is_admin():
|
| 848 |
logging.info(f"Admin searching for file ID {file_id} across all users.")
|
| 849 |
for uname, udata in data.get('users', {}).items():
|
| 850 |
node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
|
|
|
|
| 856 |
|
| 857 |
if not file_node or file_node.get('type') != 'file':
|
| 858 |
flash('Файл не найден!', 'error')
|
| 859 |
+
return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
|
|
|
|
|
|
|
| 860 |
|
| 861 |
hf_path = file_node.get('path')
|
| 862 |
original_filename = file_node.get('original_filename', 'downloaded_file')
|
| 863 |
|
| 864 |
if not hf_path:
|
| 865 |
flash('Ошибка: Путь к файлу не найден в метаданных.', 'error')
|
| 866 |
+
return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
|
|
|
|
| 867 |
|
| 868 |
file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
|
| 869 |
|
|
|
|
| 885 |
except requests.exceptions.RequestException as e:
|
| 886 |
logging.error(f"Error downloading file from HF ({hf_path}): {e}")
|
| 887 |
flash(f'Ошибка скачивания файла {original_filename}! ({e})', 'error')
|
| 888 |
+
return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
|
|
|
|
| 889 |
except Exception as e:
|
| 890 |
logging.error(f"Unexpected error during download ({hf_path}): {e}")
|
| 891 |
flash('Произошла непредвиденная ошибка при скачивании файла.', 'error')
|
| 892 |
+
return redirect(request.referrer or url_for('dashboard' if 'username' in session else 'login'))
|
|
|
|
|
|
|
| 893 |
|
| 894 |
|
| 895 |
@app.route('/delete_file/<file_id>', methods=['POST'])
|
|
|
|
| 1018 |
|
| 1019 |
@app.route('/get_text_content/<file_id>')
|
| 1020 |
def get_text_content(file_id):
|
| 1021 |
+
if 'username' not in session:
|
| 1022 |
+
if not is_admin():
|
| 1023 |
+
return Response("Не авторизован", status=401)
|
| 1024 |
+
else:
|
| 1025 |
+
pass
|
| 1026 |
|
| 1027 |
data = load_data()
|
| 1028 |
file_node = None
|
| 1029 |
+
username_context = None
|
| 1030 |
|
| 1031 |
+
if 'username' in session:
|
| 1032 |
+
username_context = session['username']
|
| 1033 |
user_data = data['users'].get(username_context)
|
| 1034 |
if user_data:
|
| 1035 |
+
file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
|
| 1036 |
|
| 1037 |
+
if not file_node and is_admin():
|
| 1038 |
logging.info(f"Admin searching for text file ID {file_id} across all users.")
|
| 1039 |
for uname, udata in data.get('users', {}).items():
|
| 1040 |
node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
|
|
|
|
| 1090 |
|
| 1091 |
|
| 1092 |
def is_admin():
|
| 1093 |
+
return 'username' in session
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1094 |
|
| 1095 |
@app.route('/admhosto')
|
| 1096 |
def admin_panel():
|
|
|
|
| 1104 |
user_details = []
|
| 1105 |
for uname, udata in users.items():
|
| 1106 |
file_count = 0
|
| 1107 |
+
q = [udata.get('filesystem', {}).get('children', [])]
|
| 1108 |
+
while q:
|
| 1109 |
+
current_level = q.pop(0)
|
| 1110 |
+
for item in current_level:
|
| 1111 |
+
if item.get('type') == 'file':
|
| 1112 |
+
file_count += 1
|
| 1113 |
+
elif item.get('type') == 'folder' and 'children' in item:
|
| 1114 |
+
q.append(item.get('children', []))
|
|
|
|
|
|
|
| 1115 |
user_details.append({
|
| 1116 |
'username': uname,
|
| 1117 |
'created_at': udata.get('created_at', 'N/A'),
|
|
|
|
| 1130 |
<a href="{{ url_for('admin_user_files', username=user.username) }}">{{ user.username }}</a>
|
| 1131 |
<p>Зарегистрирован: {{ user.created_at }}</p>
|
| 1132 |
<p>Файлов: {{ user.file_count }}</p>
|
|
|
|
| 1133 |
<form method="POST" action="{{ url_for('admin_delete_user', username=user.username) }}" style="display: inline; margin-left: 10px;" onsubmit="return confirm('УДАЛИТЬ пользователя {{ user.username }} и ВСЕ его файлы? НЕОБРАТИМО!');">
|
| 1134 |
<button type="submit" class="btn delete-btn" style="padding: 5px 10px; font-size: 0.9em;">Удалить</button>
|
| 1135 |
</form>
|
|
|
|
| 1136 |
</div>
|
| 1137 |
{% else %}<p>Пользователей нет.</p>{% endfor %}</div></div></body></html>'''
|
| 1138 |
return render_template_string(html, user_details=user_details)
|
|
|
|
| 1150 |
return redirect(url_for('admin_panel'))
|
| 1151 |
|
| 1152 |
all_files = []
|
| 1153 |
+
def collect_files(folder, current_path_id='root'):
|
| 1154 |
+
parent_node, _ = find_node_by_id(user_data['filesystem'], current_path_id)
|
| 1155 |
+
parent_path_str = get_node_path_string(user_data['filesystem'], current_path_id)
|
| 1156 |
|
| 1157 |
for item in folder.get('children', []):
|
| 1158 |
if item.get('type') == 'file':
|
| 1159 |
+
item['parent_path_str'] = parent_path_str
|
| 1160 |
all_files.append(item)
|
| 1161 |
elif item.get('type') == 'folder':
|
| 1162 |
+
collect_files(item, item.get('id'))
|
| 1163 |
|
| 1164 |
+
collect_files(user_data.get('filesystem', {}))
|
| 1165 |
all_files.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
|
| 1166 |
|
| 1167 |
|
|
|
|
| 1239 |
const response = await fetch(srcOrUrl);
|
| 1240 |
if (!response.ok) throw new Error(`Ошибка загрузки текста: ${response.statusText}`);
|
| 1241 |
const text = await response.text();
|
| 1242 |
+
const escapedText = text.replace(/</g, "<").replace(/>/g, ">");
|
| 1243 |
modalContent.innerHTML = `<pre>${escapedText}</pre>`;
|
| 1244 |
} else {
|
| 1245 |
modalContent.innerHTML = '<p>Предпросмотр для этого типа файла не поддерживается.</p>';
|
|
|
|
| 1276 |
if not is_admin():
|
| 1277 |
flash('Доступ запрещен.', 'error')
|
| 1278 |
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
| 1279 |
if not HF_TOKEN_WRITE:
|
| 1280 |
flash('Удаление невозможно: токен для записи не настроен.', 'error')
|
| 1281 |
return redirect(url_for('admin_panel'))
|
|
|
|
| 1293 |
user_folder_path_on_hf = f"cloud_files/{username}"
|
| 1294 |
|
| 1295 |
logging.info(f"Attempting to delete HF Hub folder: {user_folder_path_on_hf} for user {username}")
|
| 1296 |
+
api.delete_folder(
|
| 1297 |
+
folder_path=user_folder_path_on_hf,
|
| 1298 |
+
repo_id=REPO_ID,
|
| 1299 |
+
repo_type="dataset",
|
| 1300 |
+
token=HF_TOKEN_WRITE,
|
| 1301 |
+
commit_message=f"ADMIN ACTION: Deleted all files/folders for user {username}"
|
| 1302 |
+
)
|
| 1303 |
+
logging.info(f"Successfully initiated deletion of folder {user_folder_path_on_hf} on HF Hub.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1304 |
|
| 1305 |
except hf_utils.HfHubHTTPError as e:
|
| 1306 |
+
if e.response.status_code == 404:
|
| 1307 |
+
logging.warning(f"User folder {user_folder_path_on_hf} not found on HF Hub for user {username}. Skipping HF deletion.")
|
|
|
|
| 1308 |
else:
|
| 1309 |
+
logging.error(f"Error deleting user folder {user_folder_path_on_hf} from HF Hub for {username}: {e}")
|
| 1310 |
flash(f'Ошибка при удалении файлов пользователя {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
|
| 1311 |
return redirect(url_for('admin_panel'))
|
| 1312 |
except Exception as e:
|
| 1313 |
+
logging.error(f"Unexpected error during HF Hub folder deletion for {username}: {e}")
|
| 1314 |
flash(f'Неожиданная ошибка при удалении файлов {username} с сервера: {e}. Пользователь НЕ удален из базы.', 'error')
|
| 1315 |
return redirect(url_for('admin_panel'))
|
| 1316 |
|
|
|
|
| 1317 |
try:
|
| 1318 |
del data['users'][username]
|
| 1319 |
save_data(data)
|
| 1320 |
+
flash(f'Пользователь {username} и его файлы (запрос на удаление отправлен) успешно удалены из базы данных!')
|
| 1321 |
logging.info(f"ADMIN ACTION: Successfully deleted user {username} from database.")
|
| 1322 |
except Exception as e:
|
| 1323 |
logging.error(f"Error saving data after deleting user {username}: {e}")
|
| 1324 |
+
flash(f'Файлы пользователя {username} удалены с сервера, но произошла ошибка при удалении пользователя из базы данных: {e}', 'error')
|
| 1325 |
|
| 1326 |
return redirect(url_for('admin_panel'))
|
| 1327 |
|