upd
This commit is contained in:
214
ARCHITECTURE.md
214
ARCHITECTURE.md
@@ -7,6 +7,7 @@
|
||||
3. [Стек технологий](#3-стек-технологий)
|
||||
4. [Схема архитектуры](#4-схема-архитектуры)
|
||||
5. [Модули бэкенда](#5-модули-бэкенда)
|
||||
- [auth.py](#authpy)
|
||||
- [browser.py](#browserpy)
|
||||
- [scraper.py](#scraperpy)
|
||||
- [exporter.py](#exporterpy)
|
||||
@@ -29,7 +30,7 @@
|
||||
|
||||
Приложение скачивает мангу с сайтов типа readmanga.ru, обходя JS-защиту (DDoS-Guard, антибот) с помощью управляемого браузера Chromium. Поддерживает два режима работы:
|
||||
|
||||
- **Веб-интерфейс** — FastAPI-сервер + SPA на чистом JS (порт 8000). Позволяет управлять загрузками через браузер с realtime-прогрессом через WebSocket.
|
||||
- **Веб-интерфейс** — FastAPI-сервер + SPA на чистом JS (порт 8000). Позволяет управлять загрузками через браузер с realtime-прогрессом через WebSocket. Требует входа — поддерживает многопользовательский режим с ролями `admin` / `user`.
|
||||
- **CLI** — консольные команды `download` и `analyze` для запуска через `docker compose run`.
|
||||
|
||||
---
|
||||
@@ -41,6 +42,7 @@ manga/
|
||||
├── src/ # Весь бэкенд-код (Python-пакет)
|
||||
│ ├── __init__.py
|
||||
│ ├── api.py # FastAPI-приложение, REST + WebSocket
|
||||
│ ├── auth.py # Хеширование паролей, генерация токенов сессий
|
||||
│ ├── browser.py # Обёртка над Playwright/Chromium
|
||||
│ ├── cli.py # CLI-команды (click)
|
||||
│ ├── downloader.py # (legacy, не используется в web-режиме)
|
||||
@@ -100,6 +102,7 @@ manga/
|
||||
┌─────────────────────────────────┐
|
||||
│ FastAPI (api.py) │
|
||||
│ │
|
||||
│ Auth middleware (cookie/session)│
|
||||
│ REST endpoints WebSocket /ws │
|
||||
│ │ │ │
|
||||
│ asyncio.Queue ws_manager │
|
||||
@@ -129,6 +132,25 @@ manga/
|
||||
|
||||
## 5. Модули бэкенда
|
||||
|
||||
### auth.py
|
||||
|
||||
**Отвечает за:** хеширование паролей и генерацию токенов сессий.
|
||||
|
||||
**Константы:**
|
||||
|
||||
```python
|
||||
COOKIE_NAME = "manga_session"
|
||||
COOKIE_MAX_AGE = 30 * 24 * 3600 # 30 дней
|
||||
```
|
||||
|
||||
**Функции:**
|
||||
|
||||
- `hash_password(password)` → `str` — хеширует пароль методом PBKDF2-SHA256, 260 000 итераций, случайная соль. Формат результата: `pbkdf2:260000:<salt_hex>:<key_hex>`.
|
||||
- `verify_password(password, hashed)` → `bool` — проверяет пароль по хешу через `hmac.compare_digest` (защита от timing-атак).
|
||||
- `generate_session_token()` → `str` — генерирует 48-байтный URL-safe токен (`secrets.token_urlsafe`).
|
||||
|
||||
---
|
||||
|
||||
### browser.py
|
||||
|
||||
**Отвечает за:** запуск и управление Playwright Chromium.
|
||||
@@ -248,9 +270,11 @@ class MangaInfo:
|
||||
| `status` | TEXT | `queued` / `downloading` / `done` / `failed` / `stopped` |
|
||||
| `format` | TEXT | `cbz` / `pdf` / `epub` / `all` |
|
||||
| `chapters_total` | INTEGER | Кол-во глав (из scraper) |
|
||||
| `chapters_done` | INTEGER | **Стальной счётчик** — не используется в API напрямую¹ |
|
||||
| `chapters_done` | INTEGER | Денормализованный счётчик¹ |
|
||||
| `last_checked_at` | TEXT | Время последней проверки новых глав |
|
||||
| `folder_name` | TEXT | Кастомное имя папки на диске (NULL → вычисляется из `title_ru`) |
|
||||
| `source_id` | INTEGER | FK → sources.id — источник манги |
|
||||
| `added_by` | INTEGER | FK → users.id — кто добавил мангу |
|
||||
|
||||
¹ API всегда пересчитывает `chapters_done` запросом `SELECT COUNT(*) FROM chapters WHERE status='done'`.
|
||||
|
||||
@@ -275,15 +299,59 @@ class MangaInfo:
|
||||
| `new_chapter_found` | Найдена новая глава при проверке |
|
||||
| `check_started` / `check_done` | Начало/конец проверки обновлений |
|
||||
|
||||
**`sources`** — источники (определяются в коде приложения):
|
||||
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|---------|
|
||||
| `slug` | TEXT UNIQUE | Идентификатор источника (`readmanga`) |
|
||||
| `display_name` | TEXT | Отображаемое название |
|
||||
| `settings` | TEXT | JSON-настройки источника |
|
||||
|
||||
**`source_domains`** — домены, привязанные к источникам:
|
||||
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|---------|
|
||||
| `source_id` | INTEGER | FK → sources.id |
|
||||
| `domain` | TEXT UNIQUE | Домен без схемы и www (`readmanga.ru`) |
|
||||
|
||||
При первом запуске домены ReadManga автоматически засеиваются из списка `_DEFAULT_READMANGA_DOMAINS` в коде.
|
||||
|
||||
**`users`** — пользователи:
|
||||
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|---------|
|
||||
| `id` | INTEGER PK | |
|
||||
| `username` | TEXT UNIQUE | Логин |
|
||||
| `password` | TEXT | Хеш пароля (pbkdf2:iterations:salt:key) |
|
||||
| `role` | TEXT | `admin` / `user` |
|
||||
| `is_env_admin` | INTEGER | 1 — системный администратор из `AUTH_LOGIN`/`AUTH_PASSWORD`; его пароль нельзя изменить через интерфейс |
|
||||
| `created_at` / `updated_at` | TEXT | ISO-8601 timestamp |
|
||||
|
||||
**`sessions`** — активные сессии:
|
||||
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|---------|
|
||||
| `token` | TEXT PK | URL-safe токен (48 байт) |
|
||||
| `user_id` | INTEGER | FK → users.id ON DELETE CASCADE |
|
||||
| `created_at` / `expires_at` | TEXT | Сессия действительна 30 дней |
|
||||
|
||||
#### Ключевые методы
|
||||
|
||||
- `add_manga(url, fmt)` → `bool` — добавляет, возвращает `False` если уже есть.
|
||||
- `add_manga(url, fmt, source_id, added_by)` → `bool` — добавляет, возвращает `False` если уже есть.
|
||||
- `upsert_chapter(...)` — INSERT OR UPDATE (по `chapter_url`).
|
||||
- `chapter_status(chapter_url)` → `str | None`.
|
||||
- `sync_chapters_done(url)` — пересчитывает и сохраняет `chapters_done` из таблицы chapters, возвращает число.
|
||||
- `sync_chapters_done(url)` — пересчитывает и сохраняет `chapters_done` из таблицы chapters.
|
||||
- `get_autos()` — манги с `auto_update=1` не в статусе `downloading`.
|
||||
- `update_manga_meta_fields(url, title_ru, title_full)` — обновляет пользовательские метаданные (название), не меняет папку.
|
||||
- `set_folder_name(url, folder_name)` — устанавливает кастомное имя папки.
|
||||
- `create_user(username, hashed_password, role, is_env_admin)` → `dict` — создаёт пользователя.
|
||||
- `get_user_by_id(id)` / `get_user_by_username(username)` → `dict | None`.
|
||||
- `get_all_users()` → `list[dict]` — все пользователи без поля `password`.
|
||||
- `update_user(user_id, **kwargs)` — обновляет разрешённые поля (`username`, `password`, `role`).
|
||||
- `delete_user(user_id)` — удаляет пользователя и все его сессии.
|
||||
- `create_session(token, user_id, expires_at)` — создаёт сессию.
|
||||
- `get_session(token)` → `dict | None` — возвращает только не истёкшие сессии.
|
||||
- `cleanup_expired_sessions()` → `int` — удаляет истёкшие сессии.
|
||||
|
||||
---
|
||||
|
||||
@@ -341,7 +409,17 @@ async with sem: # ограничение параллельности
|
||||
|
||||
### api.py
|
||||
|
||||
**Отвечает за:** FastAPI-приложение — HTTP-сервер, очередь загрузок, планировщик обновлений.
|
||||
**Отвечает за:** FastAPI-приложение — HTTP-сервер, аутентификация, очередь загрузок, планировщик обновлений.
|
||||
|
||||
#### Auth-зависимости (Depends)
|
||||
|
||||
```python
|
||||
get_current_user(request) # читает токен из cookie, валидирует сессию → dict пользователя
|
||||
require_admin(user) # get_current_user + проверка role == "admin"
|
||||
_check_manga_access(manga, user) # admin: полный доступ; user: только свои манги
|
||||
```
|
||||
|
||||
Все endpoint'ы (кроме `/api/login` и `/api/auth/check`) требуют валидной сессии.
|
||||
|
||||
#### Глобальное состояние
|
||||
|
||||
@@ -354,14 +432,17 @@ ws_manager: ConnectionManager # set активных WebSocket-соедине
|
||||
#### Вспомогательные функции
|
||||
|
||||
- `_safe_name(s)` — транслитерирует строку в безопасное имя папки (strip спецсимволов, пробелы → `_`, max 80 символов).
|
||||
- `_manga_folder(m)` — возвращает `Path` к папке манги: если `m["folder_name"]` задан — использует его, иначе вычисляет из `title` через `_safe_name()`. Используется везде: `_enrich_manga`, `_manga_detail`, `delete_manga`, `rename_folder`.
|
||||
- `_broadcast_queue_positions()` — отправляет всем WS-клиентам событие `queue_positions` с актуальным словарём `{url: позиция}`. Вызывается после любого изменения очереди: старт/конец задачи в воркере, `prioritize`, `stop`, `resume`, `add_to_queue`, `force_redownload`.
|
||||
- `_manga_folder(m)` — возвращает `Path` к папке манги: если `m["folder_name"]` задан — использует его, иначе вычисляет из `title` через `_safe_name()`.
|
||||
- `_broadcast_queue_positions()` — отправляет всем WS-клиентам событие `queue_positions`. Вызывается при любом изменении очереди.
|
||||
|
||||
#### Жизненный цикл при старте (`startup_event`)
|
||||
|
||||
1. Запускает `queue_worker()` как фоновую Task.
|
||||
2. Запускает `update_scheduler()` как фоновую Task.
|
||||
3. Восстанавливает из БД незавершённые задачи (status `queued`/`downloading` → снова в очередь).
|
||||
1. Синхронизирует `sources` из кода реестра с БД (`sync_sources`).
|
||||
2. Авто-мигрирует `source_id` для манг без него (`migrate_manga_sources`).
|
||||
3. Удаляет истёкшие сессии (`cleanup_expired_sessions`).
|
||||
4. **Bootstrap-admin:** если таблица `users` пуста — создаёт пользователя из `AUTH_LOGIN`/`AUTH_PASSWORD` с ролью `admin` и флагом `is_env_admin=True`.
|
||||
5. Запускает `queue_worker()` и `update_scheduler()` как фоновые Task.
|
||||
6. Восстанавливает незавершённые задачи из БД (status `queued`/`downloading` → снова в очередь).
|
||||
|
||||
#### `queue_worker()`
|
||||
|
||||
@@ -373,18 +454,15 @@ ws_manager: ConnectionManager # set активных WebSocket-соедине
|
||||
- Читает расписание через `_parse_schedule()`: приоритет `UPDATE_SCHEDULE` (cron-строка) → `UPDATE_INTERVAL_HOURS` (legacy, число часов → конвертируется в cron автоматически). Если обе переменные пусты — планировщик не запускается.
|
||||
- Вычисляет время до следующего слота через `croniter.get_next()` и спит ровно до него.
|
||||
- Запускает `_run_auto_updates_with_retry()` — обёртка с **тремя попытками**: при ошибке ждёт 5 минут и повторяет; после трёх неудач логирует и ждёт следующего слота. Цикл **никогда не прерывается**.
|
||||
- `_run_auto_updates()` — сама логика: вызывает `check_for_updates()` для каждой манги с `auto_update=1`, ставит новые главы в очередь.
|
||||
|
||||
#### `_enrich_manga(m, db)`
|
||||
|
||||
Вспомогательная функция: обогащает строку из `mangas` реальными данными:
|
||||
- `chapters_done` — `COUNT(*)` из таблицы `chapters` (не стальная колонка).
|
||||
- `chapters_done` — `COUNT(*)` из таблицы `chapters` (не денормализованная колонка).
|
||||
- `size_bytes` / `size_human` — размер папки на диске.
|
||||
- `is_active` — есть ли Task в `active_tasks`.
|
||||
- `errors_count` — сумма failed и partial глав.
|
||||
|
||||
Используется в `/api/mangas` и в WebSocket snapshot — гарантирует консистентность данных.
|
||||
|
||||
---
|
||||
|
||||
### cli.py
|
||||
@@ -429,6 +507,36 @@ CLI использует те же `BrowserManager`, `scraper`, `exporter`, `Sta
|
||||
|
||||
Базовый URL: `http://localhost:8000`
|
||||
|
||||
Все endpoint'ы, кроме `/api/login` и `/api/auth/check`, требуют валидной сессии (cookie `manga_session`). Endpoint'ы с пометкой `[admin]` доступны только пользователям с ролью `admin`.
|
||||
|
||||
### Аутентификация
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
|-------|------|---------|
|
||||
| `GET` | `/api/auth/check` | Проверить текущую сессию. Возвращает `{authenticated, user: {id, username, role, is_env_admin}}` |
|
||||
| `POST` | `/api/login` | Войти `{login, password}`. Устанавливает cookie сессии на 30 дней |
|
||||
| `POST` | `/api/logout` | Выйти. Удаляет сессию из БД и cookie |
|
||||
|
||||
### Управление пользователями `[admin]`
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
|-------|------|---------|
|
||||
| `GET` | `/api/users` | Список всех пользователей (без паролей) |
|
||||
| `POST` | `/api/users` | Создать пользователя `{username, password, role}` |
|
||||
| `PATCH` | `/api/users/{user_id}` | Изменить `{username?, password?, role?}`. Для `is_env_admin=1` смена пароля заблокирована. Обычный пользователь может менять только свой пароль |
|
||||
| `DELETE` | `/api/users/{user_id}` | Удалить пользователя. Системного администратора и последнего admin удалить нельзя |
|
||||
|
||||
### Источники
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
|-------|------|---------|
|
||||
| `GET` | `/api/sources` | Список источников с доменами и настройками |
|
||||
| `GET` | `/api/resolve-source?url=` | Определить источник по URL манги |
|
||||
| `POST` | `/api/sources/{id}/domains` `[admin]` | Добавить домен к источнику `{domain}` |
|
||||
| `DELETE` | `/api/sources/{id}/domains/{domain}` `[admin]` | Удалить домен у источника |
|
||||
|
||||
### Манги
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
|-------|------|---------|
|
||||
| `GET` | `/api/mangas` | Список всех манг с реальными счётчиками |
|
||||
@@ -436,15 +544,21 @@ CLI использует те же `BrowserManager`, `scraper`, `exporter`, `Sta
|
||||
| `POST` | `/api/queue` | Добавить мангу(и) в очередь `{urls: [...], format: "cbz"}` |
|
||||
| `POST` | `/api/mangas/stop?url=` | Остановить загрузку |
|
||||
| `POST` | `/api/mangas/resume?url=` | Возобновить |
|
||||
| `POST` | `/api/mangas/prioritize?url=` | Переместить в начало очереди (вытесняет текущую) |
|
||||
| `POST` | `/api/mangas/prioritize?url=` `[admin]` | Переместить в начало очереди (вытесняет текущую) |
|
||||
| `POST` | `/api/mangas/retry_errors?url=` | Сбросить failed/partial главы → pending |
|
||||
| `POST` | `/api/mangas/auto_update?url=&enabled=` | Вкл/выкл авто-обновление |
|
||||
| `POST` | `/api/mangas/check_now?url=` | Немедленно проверить новые главы |
|
||||
| `POST` | `/api/mangas/update_meta` | Изменить `title_ru`/`title_full` и применить к метаданным файлов `{url, title_ru, title_full}` |
|
||||
| `POST` | `/api/mangas/rename_folder` | Переименовать папку на диске и обновить пути в БД `{url, folder_name}` |
|
||||
| `POST` | `/api/mangas/rename_folder` | Переименовать папку на диске `{url, folder_name}` |
|
||||
| `POST` | `/api/mangas/refresh_meta?url=` | Обновить метаданные в уже скачанных файлах |
|
||||
| `POST` | `/api/mangas/force_redownload?url=` | Сбросить все главы и поставить в очередь заново |
|
||||
| `DELETE` | `/api/mangas?url=` | Удалить мангу из БД |
|
||||
| `POST` | `/api/mangas/force_redownload?url=` `[admin]` | Сбросить все главы и поставить в очередь заново |
|
||||
| `POST` | `/api/mangas/switch-source` `[admin]` | Сменить источник манги `{manga_url, source_id}` |
|
||||
| `DELETE` | `/api/mangas?url=` `[admin]` | Удалить мангу из БД |
|
||||
|
||||
### Прочее
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
|-------|------|---------|
|
||||
| `GET` | `/api/stats` | Глобальная статистика (кол-во по статусам, размер) |
|
||||
| `GET` | `/api/history?limit=&manga_url=` | История событий |
|
||||
| `GET` | `/api/news?limit=` | Только события `downloaded`/`auto_downloaded` |
|
||||
@@ -469,7 +583,7 @@ CLI использует те же `BrowserManager`, `scraper`, `exporter`, `Sta
|
||||
| `manga_preview` | `{url, title, chapters_total, ...}` | Быстрый предпросмотр после добавления |
|
||||
| `manga_meta_updated` | `{url, title, title_ru, title_full}` | Метаданные отредактированы пользователем |
|
||||
| `manga_folder_renamed` | `{url, folder_name}` | Папка переименована |
|
||||
| `queue_positions` | `{positions: {url: номер}}` | Актуальные позиции в очереди — отправляется при любом изменении очереди |
|
||||
| `queue_positions` | `{positions: {url: номер}}` | Актуальные позиции в очереди — при любом изменении очереди |
|
||||
| `chapter_start` | `{url, chapter_url, chapter_number, chapters_done, chapters_total}` | Начало главы |
|
||||
| `chapter_done` | `{url, chapter_url, chapters_done, chapters_total}` | Глава готова |
|
||||
| `chapter_skipped` | `{url, chapter_url, chapters_done}` | Глава пропущена (уже скачана) |
|
||||
@@ -479,6 +593,8 @@ CLI использует те же `BrowserManager`, `scraper`, `exporter`, `Sta
|
||||
| `new_chapter_found` | `{url, chapter_url, chapter_number}` | Найдена новая глава |
|
||||
| `auto_update_changed` | `{url, auto_update}` | Изменён флаг авто-обновления |
|
||||
| `meta_refreshed` | `{url, updated, failed}` | Метаданные файлов обновлены |
|
||||
| `source_domain_added` | `{source_id, domain}` | Добавлен домен к источнику |
|
||||
| `source_domain_removed` | `{source_id, domain}` | Домен удалён у источника |
|
||||
|
||||
### Клиент → Сервер
|
||||
|
||||
@@ -490,16 +606,24 @@ CLI использует те же `BrowserManager`, `scraper`, `exporter`, `Sta
|
||||
|
||||
## 9. Фронтенд
|
||||
|
||||
**Файл:** `frontend/index.html` — весь фронтенд в одном HTML-файле (~1500 строк).
|
||||
**Файл:** `frontend/index.html` — весь фронтенд в одном HTML-файле (~2350 строк).
|
||||
|
||||
**Стек:** Tailwind CSS (CDN), Vanilla JS (без фреймворков и сборщика).
|
||||
|
||||
### Экран входа
|
||||
|
||||
При загрузке приложение вызывает `GET /api/auth/check`. Если сессия невалидна — показывается экран входа (`#login-screen`). После успешного `POST /api/login` сессия сохраняется в cookie, экран входа скрывается.
|
||||
|
||||
`state.currentUser` содержит `{id, username, role, is_env_admin}` — используется для скрытия/показа элементов интерфейса в зависимости от роли.
|
||||
|
||||
### Архитектура состояния
|
||||
|
||||
```javascript
|
||||
const state = {
|
||||
mangas: {}, // url → объект манги (из snapshot/API + WS-обновления)
|
||||
chapters: {}, // url → массив глав (загружается по запросу в модалке)
|
||||
mangas: {}, // url → объект манги (из snapshot/API + WS-обновления)
|
||||
chapters: {}, // url → массив глав (загружается по запросу в модалке)
|
||||
currentUser: null, // {id, username, role, is_env_admin}
|
||||
sources: [], // список источников
|
||||
};
|
||||
```
|
||||
|
||||
@@ -508,24 +632,41 @@ const state = {
|
||||
```
|
||||
DOMContentLoaded
|
||||
│
|
||||
├─ loadStats() ──────────────────────► GET /api/stats
|
||||
├─ checkAuth() ──────────────────────► GET /api/auth/check
|
||||
│ │ (если ok → initApp())
|
||||
│ └─ showLoginScreen()
|
||||
│
|
||||
├─ connectWS() ──────────────────────► WS /ws
|
||||
│ │
|
||||
│ └─ snapshot event ──────────► state.mangas = enriched list
|
||||
│ + live events ──────────► state.mangas[url].* обновляется
|
||||
│
|
||||
└─ fetch('/api/mangas') ─────────────► state.mangas = полный список
|
||||
(перезаписывает snapshot если пришёл раньше)
|
||||
└─ initApp()
|
||||
├─ loadStats() ──────────────────► GET /api/stats
|
||||
├─ connectWS() ──────────────────► WS /ws
|
||||
│ │
|
||||
│ └─ snapshot event ───────► state.mangas = enriched list
|
||||
│ + live events ───────► state.mangas[url].* обновляется
|
||||
│
|
||||
└─ fetch('/api/mangas') ──────────► state.mangas = полный список
|
||||
```
|
||||
|
||||
**Важный нюанс порядка:** `connectWS()` не awaited, поэтому snapshot может прийти после `fetch('/api/mangas')` и перезаписать правильные значения устаревшими из БД. Именно поэтому WS snapshot теперь тоже использует `_enrich_manga()` с пересчётом из таблицы `chapters`.
|
||||
**Важный нюанс порядка:** `connectWS()` не awaited, поэтому snapshot может прийти после `fetch('/api/mangas')` и перезаписать правильные значения устаревшими из БД. Именно поэтому WS snapshot тоже использует `_enrich_manga()` с пересчётом из таблицы `chapters`.
|
||||
|
||||
### Вкладки
|
||||
|
||||
- **Манга** — список всех манг, добавление, управление.
|
||||
- **Новости** — события `downloaded`/`auto_downloaded` (что скачалось).
|
||||
- **История** — все события из таблицы `history`.
|
||||
- **Настройки** — управление источниками, пользователями (только admin), смена своего пароля.
|
||||
|
||||
### Вкладка «Настройки»
|
||||
|
||||
При открытии загружает:
|
||||
- Список источников (`GET /api/sources`) с управлением доменами (только admin).
|
||||
- Список пользователей (`GET /api/users`, только admin) — в разделе **Пользователи**.
|
||||
- Раздел **Сменить пароль** — скрыт для системного администратора (`is_env_admin=true`).
|
||||
|
||||
**Управление пользователями (только admin):**
|
||||
- Создание: кнопка «+ Добавить» → модалка с логином, паролем, ролью.
|
||||
- Редактирование: кнопка ✏️ → модалка. Для системного администратора (`is_env_admin`) поле пароля скрыто.
|
||||
- Удаление: кнопка ✕ (недоступна для системного администратора и для самого себя).
|
||||
- Системный администратор помечен иконкой 🔒 с тултипом.
|
||||
|
||||
### Модальное окно детали
|
||||
|
||||
@@ -533,7 +674,7 @@ DOMContentLoaded
|
||||
- **✏️ Редактировать название** — открывает модалку изменения `title_ru` / `title_full`. Папка не переименовывается, но метаданные всех файлов обновляются автоматически через `_do_refresh_meta`.
|
||||
- **📁 Переименовать папку** — открывает модалку смены имени папки на диске (доступна при статусе не `downloading`). Физически переименовывает папку, обновляет пути в `chapters.output_*`, сохраняет `folder_name` в `mangas`.
|
||||
- **🏷 Обновить метатеги** — принудительно обновляет метаданные в уже скачанных файлах (для манги в статусе `done`).
|
||||
- **↺ Скачать заново** — сбрасывает все главы и ставит в очередь повторно.
|
||||
- **↺ Скачать заново** `[admin]` — сбрасывает все главы и ставит в очередь повторно.
|
||||
|
||||
### Карточки манги (кнопки)
|
||||
|
||||
@@ -544,7 +685,7 @@ DOMContentLoaded
|
||||
| ⏸ | `status` = `downloading` или `queued` | Остановить загрузку |
|
||||
| ▶ | `status` = `stopped` или `failed` | Возобновить |
|
||||
| 🚀 | `status` = `queued` (только в очереди, не активная) | Переместить в начало очереди |
|
||||
| ✕ | всегда | Удалить |
|
||||
| ✕ `[admin]` | всегда | Удалить |
|
||||
|
||||
### Позиции в очереди
|
||||
|
||||
@@ -558,10 +699,11 @@ DOMContentLoaded
|
||||
|
||||
| Переменная | Default | Описание |
|
||||
|------------|---------|---------|
|
||||
| `AUTH_LOGIN` | — | Логин системного администратора. Создаётся при первом старте, если таблица `users` пуста |
|
||||
| `AUTH_PASSWORD` | — | Пароль системного администратора. Для смены — изменить переменную и пересоздать контейнер |
|
||||
| `CHAPTER_CONCURRENCY` | `3` | Кол-во глав, загружаемых параллельно |
|
||||
| `UPDATE_SCHEDULE` | — | Расписание авто-проверки (cron-синтаксис). Пример: `0 */6 * * *`. Если пусто — планировщик отключён |
|
||||
| `UPDATE_INTERVAL_HOURS` | — | Устаревший аналог `UPDATE_SCHEDULE`: число часов → конвертируется в cron автоматически |
|
||||
| `AUTH_LOGIN` / `AUTH_PASSWORD` | — | Логин и пароль для веб-интерфейса. Если оба заданы — включается авторизация |
|
||||
| `PYTHONUNBUFFERED` | `1` | Немедленный вывод логов (Docker) |
|
||||
|
||||
### Пути (hardcoded в коде)
|
||||
@@ -602,7 +744,7 @@ ports:
|
||||
shm_size: "2gb" # Chromium требует shared memory
|
||||
environment:
|
||||
- UPDATE_SCHEDULE=0 */6 * * * # каждые 6 часов (cron)
|
||||
- AUTH_LOGIN=...
|
||||
- AUTH_LOGIN=... # системный администратор
|
||||
- AUTH_PASSWORD=...
|
||||
|
||||
restart: unless-stopped # Автоперезапуск при падении
|
||||
@@ -624,7 +766,7 @@ docker compose run --rm --entrypoint "" manga \
|
||||
|
||||
После остановки контейнера все данные сохраняются на хосте:
|
||||
- `./output/` — скачанные файлы.
|
||||
- `./state/progress.db` — состояние БД (что скачано, что в очереди).
|
||||
- `./state/progress.db` — состояние БД (что скачано, что в очереди, пользователи, сессии).
|
||||
- `./state/manga.log` — логи.
|
||||
|
||||
При следующем запуске `startup_event` восстанавливает незавершённые задачи из БД в очередь.
|
||||
|
||||
Reference in New Issue
Block a user