upd
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Manga Downloader</title>
|
||||
<link rel="icon" type="image/png" href="/static/favicon.png"/>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
body { background: #0f1117; color: #e2e8f0; font-family: 'Segoe UI', system-ui, sans-serif; }
|
||||
@@ -61,9 +62,9 @@
|
||||
<!-- Login screen -->
|
||||
<div id="login-screen">
|
||||
<div class="login-card">
|
||||
<div class="flex items-center gap-3 mb-8 justify-center">
|
||||
<span class="text-3xl">📚</span>
|
||||
<h1 class="text-xl font-bold text-white">Manga Downloader</h1>
|
||||
<div class="flex flex-col items-center gap-2 mb-8 justify-center">
|
||||
<img src="/static/logo.png" alt="Manga Downloader" class="h-16 w-auto">
|
||||
<span class="text-white font-bold text-xl tracking-wide">Manga Downloader</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
@@ -87,8 +88,8 @@
|
||||
<!-- Header -->
|
||||
<header class="border-b border-gray-800 px-6 py-4 flex items-center justify-between sticky top-0 z-50" style="background:#0f1117ee;backdrop-filter:blur(12px)">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-2xl">📚</span>
|
||||
<h1 class="text-xl font-bold text-white">Manga Downloader</h1>
|
||||
<img src="/static/logo.png" alt="Manga Downloader" class="h-10 w-auto">
|
||||
<span class="text-white font-semibold text-lg tracking-wide">Manga Downloader</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div id="ws-status" class="flex items-center gap-2 text-sm text-gray-400">
|
||||
@@ -412,6 +413,7 @@ window.fetch = async function(...args) {
|
||||
|
||||
// ── WebSocket ────────────────────────────────
|
||||
let ws, wsReconnectTimer;
|
||||
let _pingInterval = null;
|
||||
|
||||
function connectWS() {
|
||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
@@ -421,14 +423,15 @@ function connectWS() {
|
||||
document.getElementById('ws-dot').className = 'w-2 h-2 rounded-full bg-green-400';
|
||||
document.getElementById('ws-text').textContent = 'Подключено';
|
||||
clearTimeout(wsReconnectTimer);
|
||||
// Keepalive
|
||||
setInterval(() => { if(ws && ws.readyState===1) ws.send('ping'); }, 20000);
|
||||
// Keepalive — один интервал, предыдущий убираем
|
||||
if(_pingInterval) clearInterval(_pingInterval);
|
||||
_pingInterval = setInterval(() => { if(ws && ws.readyState === 1) ws.send('ping'); }, 20000);
|
||||
};
|
||||
|
||||
ws.onclose = (e) => {
|
||||
document.getElementById('ws-dot').className = 'w-2 h-2 rounded-full bg-red-500';
|
||||
if(_pingInterval) { clearInterval(_pingInterval); _pingInterval = null; }
|
||||
if(e.code === 4401) {
|
||||
// Сессия истекла или не авторизован
|
||||
document.getElementById('ws-text').textContent = 'Нет доступа';
|
||||
showLoginScreen();
|
||||
return;
|
||||
@@ -449,6 +452,9 @@ function handleEvent(msg) {
|
||||
msg.mangas.forEach(m => { state.mangas[m.url] = m; });
|
||||
renderList();
|
||||
loadStats();
|
||||
// Дополнительно запрашиваем свежие данные с сервера — на случай если
|
||||
// пока WS был отключён, статусы изменились и события были потеряны
|
||||
_refreshMangaList();
|
||||
break;
|
||||
|
||||
case 'manga_queued':
|
||||
@@ -476,11 +482,15 @@ function handleEvent(msg) {
|
||||
break;
|
||||
|
||||
case 'manga_start':
|
||||
if(state.mangas[msg.url]) {
|
||||
if(!state.mangas[msg.url]) {
|
||||
// Манга не в state — берём свежие данные с сервера
|
||||
_refreshMangaList();
|
||||
} else {
|
||||
state.mangas[msg.url].status = 'downloading';
|
||||
if(msg.started_at) state.mangas[msg.url].started_at = msg.started_at;
|
||||
state.mangas[msg.url].finished_at = null;
|
||||
renderList();
|
||||
}
|
||||
renderList();
|
||||
break;
|
||||
|
||||
case 'manga_info':
|
||||
@@ -685,7 +695,6 @@ function switchTab(tab) {
|
||||
if(tab === 'news') { newsUnreadCount = 0; updateNewsBadge(); loadNews(); }
|
||||
if(tab === 'settings') loadSources();
|
||||
}
|
||||
}
|
||||
|
||||
function updateNewsBadge() {
|
||||
const badge = document.getElementById('news-unread-badge');
|
||||
@@ -1513,11 +1522,6 @@ function _rowButtons(m) {
|
||||
title="${m.errors_count} проблем при загрузке"
|
||||
style="background:#450a0a;color:#fca5a5;padding:4px 8px;border-radius:6px;font-size:0.75rem;cursor:pointer">⚠️ ${m.errors_count}</button>`
|
||||
: ''}
|
||||
${!isActive
|
||||
? `<button onclick="event.stopPropagation(); openSwitchSourceModal('${u}')"
|
||||
title="Сменить источник"
|
||||
style="background:#1e3a2e;color:#6ee7b7;padding:4px 8px;border-radius:6px;font-size:0.75rem;cursor:pointer">↔ Источник</button>`
|
||||
: ''}
|
||||
${isActive
|
||||
? `<button onclick="stopManga('${u}')" class="btn-danger" title="Остановить" style="background:#7c2d12;color:#fdba74">⏸</button>`
|
||||
: ''}
|
||||
@@ -1788,6 +1792,12 @@ function renderModalBody(data) {
|
||||
style="background:#0c1a2e;color:#93c5fd;border:1px solid #1e3a5f">
|
||||
↺ Скачать заново
|
||||
</button>` : ''}
|
||||
${data.status !== 'downloading' ? `
|
||||
<button onclick="openSwitchSourceModal('${escHtml(data.url)}')"
|
||||
class="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold transition-colors"
|
||||
style="background:#0f2a1e;color:#6ee7b7;border:1px solid #1e3a2e">
|
||||
↔ Сменить источник
|
||||
</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2007,20 +2017,24 @@ async function saveRenameFolder() {
|
||||
}
|
||||
|
||||
// ── Init ─────────────────────────────────────
|
||||
async function _refreshMangaList() {
|
||||
try {
|
||||
const r = await fetch('/api/mangas');
|
||||
if(!r.ok) return;
|
||||
const mangas = await r.json();
|
||||
mangas.forEach(m => { state.mangas[m.url] = m; });
|
||||
renderList();
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
async function initApp() {
|
||||
_initDeleteModal();
|
||||
await loadStats();
|
||||
await loadSources();
|
||||
connectWS();
|
||||
// Загружаем список манги
|
||||
try {
|
||||
const r = await fetch('/api/mangas');
|
||||
if(r.ok) {
|
||||
const mangas = await r.json();
|
||||
mangas.forEach(m => { state.mangas[m.url] = m; });
|
||||
renderList();
|
||||
}
|
||||
} catch(e) {}
|
||||
await _refreshMangaList();
|
||||
// Периодически синхронизируем список манг — подстраховка от потерянных WS событий
|
||||
setInterval(_refreshMangaList, 20000);
|
||||
setInterval(loadStats, 15000);
|
||||
}
|
||||
|
||||
|
||||
BIN
frontend/static/favicon.png
Normal file
BIN
frontend/static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
frontend/static/logo.png
Normal file
BIN
frontend/static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 477 KiB |
Reference in New Issue
Block a user