Админка в master: секретный путь, ссылка, интервал, force, блокировка входа, история
Админка нужна на «голом» сервере, поэтому возвращаю её в основную ветку. Она выключена, пока не заданы ADMIN_LOGIN/ADMIN_PASSWORD: маршруты /api/admin/* не регистрируются, страницы нет — обычная установка ничего не замечает. Панель (открывается по секретному пути ADMIN_PATH, кнопки входа на сайте нет, вход по логину/паролю): - состояние: группы, когда обновлялось, дата файла на Диске, изменился ли файл при последнем чтении (sha256), ошибки, путь файла настроек; - новая ссылка на таблицу: проверяется скачиванием, при ошибке откат; - интервал обновления (TTL) в минутах; - «Обновить сейчас»: принудительно перечитывает таблицу (качает файл всегда, интервал — только если на Диске изменилась дата); - история входов: последняя попытка (откуда, когда, успешна ли) и последний успешный вход — и в панели, и в логе службы. Защита входа: ADMIN_MAX_ATTEMPTS неудач (по умолчанию 3), затем блокировка на ADMIN_LOCKOUT_MINUTES минут (по умолчанию 15) — всё из .env. Реализация: - backend/admin.py: cookie-сессия на HMAC (ключ — пароль, 12 ч, HttpOnly, SameSite, Secure за https), сравнение за постоянное время, реальный IP из X-Forwarded-For, логирование каждой попытки; - schedule/settings.py: ссылка, интервал и история входов в JSON; путь по умолчанию data/settings.json (в docker — том schedule_data), при недоступности файла настройки живут в памяти и панель предупреждает; - cache.py: apply_link (с проверкой и откатом), force_reload, state(); - frontend: страница админки по ADMIN_PATH (вне static, noindex) и прокси /api/admin/* с телом и Set-Cookie; - install.sh пишет переменные админки в .env, Dockerfile копирует admin.py. Тесты: 156. Проверено локально (страница, 3 попытки -> блокировка, история) и в собранном docker-образе (том настроек, запись истории).
This commit is contained in:
50
README.md
50
README.md
@@ -294,6 +294,11 @@ PyPI не раздаёт вовсе. Зеркала отстают по-разн
|
||||
| `SCHEDULE_RETRY_AFTER` | `60` | api | пауза между попытками при сбое источника |
|
||||
| `SCHEDULE_API` | `http://127.0.0.1:8000` | frontend | адрес бэкенда (в docker: `http://api:8000`) |
|
||||
| `SCHEDULE_PROXY_TIMEOUT` | `150` | frontend | таймаут прокси к бэкенду, сек |
|
||||
| `ADMIN_PATH` | — | api+frontend | секретный путь админки; пусто = админка выключена |
|
||||
| `ADMIN_LOGIN` / `ADMIN_PASSWORD` | — | api | логин и пароль администратора |
|
||||
| `ADMIN_MAX_ATTEMPTS` | `3` | api | сколько неудачных попыток входа допускается |
|
||||
| `ADMIN_LOCKOUT_MINUTES` | `15` | api | на сколько минут блокировать вход после них |
|
||||
| `SETTINGS_FILE` | `data/settings.json` | api | где хранить ссылку, интервал и историю входов |
|
||||
| `API_PORT` | `8000` | docker | внешний порт api (публикуется на 127.0.0.1) |
|
||||
| `FRONTEND_PORT` | `8080` | docker | внешний порт frontend (публикуется на 127.0.0.1) |
|
||||
|
||||
@@ -301,6 +306,42 @@ PyPI не раздаёт вовсе. Зеркала отстают по-разн
|
||||
`.env` в корне репозитория; `load_dotenv` не перезаписывает уже выставленные
|
||||
переменные окружения.
|
||||
|
||||
## Админка
|
||||
|
||||
Необязательная страница для управления источником данных. Отдельного входа на
|
||||
сайте нет: панель открывается по секретному пути `ADMIN_PATH`, а пока
|
||||
`ADMIN_LOGIN`/`ADMIN_PASSWORD` пусты, админка выключена и наружу ничего не
|
||||
торчит (маршруты `/api/admin/*` не регистрируются).
|
||||
|
||||
Включить: заполнить в `.env` четыре переменные и перезапустить службу.
|
||||
|
||||
```ini
|
||||
ADMIN_PATH=/panel-8f3a91x7 # придумайте длинный и неочевидный
|
||||
ADMIN_LOGIN=admin
|
||||
ADMIN_PASSWORD=<длинный пароль>
|
||||
```
|
||||
|
||||
Что умеет:
|
||||
|
||||
- **посмотреть состояние**: сколько групп, когда обновлялось, дата файла на
|
||||
Диске, изменился ли файл при последнем чтении (`sha256`), есть ли ошибки;
|
||||
- **вставить новую ссылку** на таблицу: она проверяется скачиванием и разбором
|
||||
до сохранения — нерабочая не применится, данные на сайте не пострадают;
|
||||
- **задать интервал обновления** в минутах (`1…1440`);
|
||||
- **обновить сейчас**: скачивает таблицу принудительно, не дожидаясь интервала
|
||||
(по интервалу файл перечитывается, только если на Диске изменилась дата);
|
||||
- **видеть входы**: последняя попытка (откуда, когда, успешна ли) и последний
|
||||
успешный вход — и в панели, и в логе службы.
|
||||
|
||||
Защита входа: после `ADMIN_MAX_ATTEMPTS` неудач (по умолчанию 3) вход с этого
|
||||
адреса блокируется на `ADMIN_LOCKOUT_MINUTES` минут (по умолчанию 15). Сессия —
|
||||
подписанная cookie на 12 часов.
|
||||
|
||||
Настройки (ссылка, интервал, история входов) хранятся в `SETTINGS_FILE`
|
||||
(по умолчанию `data/settings.json` в корне проекта; в docker — в томе
|
||||
`schedule_data`). Если файл недоступен для записи, изменения живут в памяти до
|
||||
перезапуска, а панель предупредит об этом.
|
||||
|
||||
## Структура кода
|
||||
|
||||
```
|
||||
@@ -308,16 +349,19 @@ install.sh интерактивная установка на сер
|
||||
src/
|
||||
backend/
|
||||
main.py FastAPI: маршруты, create_app(cache), /healthz
|
||||
admin.py админка: cookie-сессия, блокировка входа, ссылка, интервал, force
|
||||
schedule/
|
||||
xlsx.py чтение xlsx: общие строки, объединённые ячейки,
|
||||
скрытые столбцы, красный текст, серийные даты
|
||||
source.py скачивание файла по публичной ссылке Я.Диска
|
||||
lessons.py разбор ячеек занятий (общий конвейер ролей)
|
||||
parsing.py лист → расписание, недели, индексы преподавателей/аудиторий
|
||||
cache.py ScheduleCache: блокировка, интервал, stale-while-error
|
||||
cache.py ScheduleCache: блокировка, интервал, stale-while-error, force
|
||||
settings.py хранилище настроек админки (ссылка, интервал, история входов)
|
||||
errors.py ScheduleError
|
||||
frontend/
|
||||
main.py FastAPI: статика + прокси /api/* (таймаут 150 c)
|
||||
admin.html страница админки (вне static: прямой доступ закрыт)
|
||||
static/
|
||||
index.html, app.js, style.css
|
||||
manifest.json PWA: имя, иконки, standalone-режим
|
||||
@@ -335,9 +379,13 @@ tests/
|
||||
test_xlsx.py чтение книги
|
||||
test_parsing.py разбор листов, недели, индексы
|
||||
test_api.py API и поведение кэша (интервал/stale, 404/400/502)
|
||||
test_admin.py админка: вход, блокировка, история, ссылка/интервал/force
|
||||
test_settings.py хранилище настроек админки
|
||||
test_deploy.py проверка, что образы копируют все нужные файлы
|
||||
```
|
||||
|
||||
Данные админки (`data/settings.json`) — рабочие, в git не попадают.
|
||||
|
||||
## Как устроена таблица-источник
|
||||
|
||||
Лист «Бакалавры и магистры» — это сетка:
|
||||
|
||||
Reference in New Issue
Block a user