FIX: Добавлен роут /files и обновлен шаблон файлового хранилища
This commit is contained in:
parent
d068e8137f
commit
05e5659204
2 changed files with 105 additions and 65 deletions
42
web/app.py
42
web/app.py
|
|
@ -365,6 +365,48 @@ async def api_get_digest(digest_id: int, username: str = Depends(get_current_adm
|
|||
}
|
||||
})
|
||||
|
||||
# ============================================================================
|
||||
# ФАЙЛЫ (FILES)
|
||||
# ============================================================================
|
||||
|
||||
@app.get("/files", response_class=HTMLResponse)
|
||||
async def files_page(request: Request, username: str = Depends(get_current_admin)):
|
||||
return templates.TemplateResponse("files.html", {"request": request, "username": username})
|
||||
|
||||
@app.get("/api/files/list")
|
||||
async def api_list_files(username: str = Depends(get_current_admin)):
|
||||
files_data = []
|
||||
|
||||
# 1. Экспорты
|
||||
export_path = BASE_DIR / "exports"
|
||||
if export_path.exists():
|
||||
for f in export_path.glob("*.json"):
|
||||
files_data.append({
|
||||
"filename": f.name,
|
||||
"file_type": "EXPORT",
|
||||
"size": f"{round(f.stat().st_size / 1024, 1)} KB",
|
||||
"path": f"/exports/{f.name}"
|
||||
})
|
||||
|
||||
# 2. Логи
|
||||
log_path = BASE_DIR / "logs"
|
||||
if log_path.exists():
|
||||
for f in log_path.glob("*.log"):
|
||||
files_data.append({
|
||||
"filename": f.name,
|
||||
"file_type": "LOG",
|
||||
"size": f"{round(f.stat().st_size / 1024, 1)} KB",
|
||||
"path": f"/api/files/download/logs/{f.name}"
|
||||
})
|
||||
|
||||
return JSONResponse({"success": True, "files": files_data})
|
||||
|
||||
@app.get("/api/files/download/logs/{filename}")
|
||||
async def download_log(filename: str, username: str = Depends(get_current_admin)):
|
||||
filepath = BASE_DIR / "logs" / filename
|
||||
if not filepath.exists(): raise HTTPException(status_code=404, detail="File not found")
|
||||
return FileResponse(filepath, filename=filename)
|
||||
|
||||
# ============================================================================
|
||||
# ЭКСПОРТ (JSON + GITEA)
|
||||
# ============================================================================
|
||||
|
|
|
|||
|
|
@ -1,97 +1,95 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Менеджер файлов - LKM37{% endblock %}
|
||||
{% block title %}Файловое хранилище - LKM37{% endblock %}
|
||||
{% block nav_files %}active{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2 class="mb-4" style="color: var(--matrix-green);">
|
||||
<h2 class="mb-4" style="color: var(--matrix-green); text-shadow: 0 0 10px var(--matrix-green);">
|
||||
>_ ХРАНИЛИЩЕ_АРТЕФАКТОВ (FILES)
|
||||
</h2>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card bg-black border-success">
|
||||
<div class="card-body">
|
||||
<h5 class="text-success mb-3">ЗАГРУЗИТЬ НОВЫЙ ФАЙЛ</h5>
|
||||
<form id="uploadForm" class="row g-3">
|
||||
<div class="col-md-8">
|
||||
<input type="file" id="fileInput" class="form-control bg-dark border-success text-success">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button type="button" onclick="uploadFile()" class="btn btn-matrix w-100">ЗАГРУЗИТЬ В СИСТЕМУ</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header border-success d-flex justify-content-between align-items-center">
|
||||
<span>СПИСОК_ДОСТУПНЫХ_ФАЙЛОВ</span>
|
||||
<button class="btn btn-outline-success btn-sm" onclick="loadFiles()">
|
||||
<i class="bi bi-arrow-clockwise"></i> ОБНОВИТЬ
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header border-success">СПИСОК_ЗАГРУЖЕННЫХ_ФАЙЛОВ</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-hover mb-0">
|
||||
<thead>
|
||||
<tr class="text-success opacity-50">
|
||||
<th>ИМЯ</th>
|
||||
<th>ТИП</th>
|
||||
<th>FILE_ID (TELEGRAM)</th>
|
||||
<th>ДЕЙСТВИЯ</th>
|
||||
<tr class="text-success opacity-50" style="font-size: 0.8em;">
|
||||
<th>ИМЯ ФАЙЛА</th>
|
||||
<th>КАТЕГОРИЯ</th>
|
||||
<th>РАЗМЕР</th>
|
||||
<th class="text-end">ДЕЙСТВИЯ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="filesTable">
|
||||
<!-- Будет заполняться через API -->
|
||||
<tr>
|
||||
<td colspan="4" class="text-center p-5">
|
||||
<div class="spinner-border text-success" role="status"></div>
|
||||
<div class="mt-2">Сканирование секторов...</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card border-info">
|
||||
<div class="card-header border-info text-info">ИНФОРМАЦИЯ</div>
|
||||
<div class="card-body small">
|
||||
<ul class="mb-0">
|
||||
<li>Категория <b>EXPORT</b>: JSON-выгрузки истории чата.</li>
|
||||
<li>Категория <b>LOG</b>: Системные логи бота и веб-панели.</li>
|
||||
<li>Все файлы доступны для прямого скачивания на локальный хост.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', loadFiles);
|
||||
|
||||
async function loadFiles() {
|
||||
const response = await fetch('/api/files/list');
|
||||
const result = await response.json();
|
||||
const table = document.getElementById('filesTable');
|
||||
table.innerHTML = '';
|
||||
try {
|
||||
const response = await fetch('/api/files/list');
|
||||
const result = await response.json();
|
||||
|
||||
result.files.forEach(f => {
|
||||
table.innerHTML += `
|
||||
<tr>
|
||||
<td>${f.filename}</td>
|
||||
<td><span class="badge bg-secondary">${f.file_type}</span></td>
|
||||
<td><code class="text-success">${f.file_id}</code></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-info" onclick="copyId('${f.file_id}')">КОПИРОВАТЬ ID</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
}
|
||||
if (!result.success) throw new Error(result.error);
|
||||
|
||||
function copyId(id) {
|
||||
navigator.clipboard.writeText(id);
|
||||
alert('FILE_ID скопирован!');
|
||||
}
|
||||
table.innerHTML = '';
|
||||
if (result.files.length === 0) {
|
||||
table.innerHTML = '<tr><td colspan="4" class="text-center p-4">Файлы не найдены</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
async function uploadFile() {
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
...
|
||||
|
||||
if (!fileInput.files[0]) return alert('Выберите файл');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', fileInput.files[0]);
|
||||
|
||||
const response = await fetch('/api/files/upload', { method: 'POST', body: formData });
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('Файл загружен и зарегистрирован в Telegram!');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Ошибка: ' + result.error);
|
||||
result.files.forEach(f => {
|
||||
const badgeClass = f.file_type === 'EXPORT' ? 'bg-primary' : 'bg-secondary';
|
||||
table.innerHTML += `
|
||||
<tr>
|
||||
<td class="font-monospace">${f.filename}</td>
|
||||
<td><span class="badge ${badgeClass}">${f.file_type}</span></td>
|
||||
<td class="text-muted">${f.size}</td>
|
||||
<td class="text-end">
|
||||
<a href="${f.path}" class="btn btn-sm btn-matrix" download>
|
||||
<i class="bi bi-download"></i> СКАЧАТЬ
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
} catch (error) {
|
||||
table.innerHTML = `<tr><td colspan="4" class="text-center text-danger p-4">Ошибка: ${error.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue