Files
rss-ntfy/app/templates/index.html
T

376 lines
21 KiB
HTML
Raw Normal View History

2026-06-02 21:11:57 +08:00
{% extends "base.html" %}
{% block title %}RSS → ntfy{% endblock %}
{% block body %}
<header class="topbar">
<div class="brand"><span class="logo">📡</span> RSS&nbsp;&nbsp;ntfy</div>
<nav class="tabs">
<button class="tab active" data-tab="feeds" data-i18n="nav.feeds">Ленты</button>
<button class="tab" data-tab="reader" data-i18n="nav.reader">Чтение</button>
2026-06-02 21:11:57 +08:00
<button class="tab" data-tab="history" data-i18n="nav.history">История</button>
<button class="tab admin-only" data-tab="categories" data-i18n="nav.categories">Категории</button>
2026-06-02 21:11:57 +08:00
<button class="tab admin-only" data-tab="users" data-i18n="nav.users">Пользователи</button>
<button class="tab admin-only" data-tab="settings" data-i18n="nav.settings">Настройки</button>
</nav>
<div class="topbar-actions">
<span id="whoami" class="muted"></span>
<select id="lang-select" class="lang-select">
<option value="ru">RU</option>
<option value="en">EN</option>
</select>
<button class="icon-btn" id="theme-btn" data-i18n-title="theme.toggle">🌓</button>
<a class="btn ghost" href="/logout" id="logout-btn" data-i18n="topbar.logout" style="display:none">Выйти</a>
</div>
</header>
<main class="container">
<!-- ===================== FEEDS ===================== -->
<section id="tab-feeds" class="tab-panel active">
<div id="stats" class="stats"></div>
<div id="chart-wrap" class="chart-card hidden">
<div class="chart-head">
<span data-i18n="chart.title">Активность за 14 дней</span>
<span class="chart-legend">
<i class="lg sent"></i><span data-i18n="chart.sent">Отправлено</span>
<i class="lg failed"></i><span data-i18n="chart.failed">Сбои</span>
</span>
</div>
<div id="chart"></div>
</div>
<div class="panel-head">
<h2 data-i18n="feeds.heading">RSS-ленты</h2>
<div class="panel-head-actions">
<button class="btn ghost" id="check-all" data-i18n="feeds.checkAll">↻ Проверить все</button>
<button class="btn ghost admin-only" id="import-btn" data-i18n="feeds.import">⬆ Импорт OPML</button>
<button class="btn ghost admin-only" id="backup-btn" data-i18n="feeds.backup">💾 Бэкап</button>
<button class="btn ghost admin-only" id="restore-btn" data-i18n="feeds.restore">📥 Восстановить</button>
<input type="file" id="restore-file" accept=".db" hidden>
2026-06-02 21:11:57 +08:00
<button class="btn ghost" id="export-btn" data-i18n="feeds.export">⬇ Экспорт OPML</button>
<button class="btn primary admin-only" id="add-feed" data-i18n="feeds.add">+ Добавить ленту</button>
<input type="file" id="opml-file" accept=".opml,.xml,text/xml" hidden>
</div>
</div>
<div id="feeds-list" class="cards"></div>
<div id="feeds-empty" class="empty hidden">
<div class="empty-icon">🗞️</div>
<p data-i18n="feeds.empty"></p>
</div>
</section>
<!-- ===================== READER ===================== -->
<section id="tab-reader" class="tab-panel">
<div class="reader-layout">
<aside class="reader-sidebar">
<div class="reader-cats" id="reader-cat-list">
<div class="reader-cat active" data-cat="all" data-i18n="reader.all">Все</div>
</div>
</aside>
<div class="reader-main">
<div class="panel-head">
<h2 data-i18n="nav.reader">Чтение</h2>
<button class="btn ghost" id="reader-mark-all" data-i18n="reader.markAll">Отметить все прочитанными</button>
</div>
<div id="reader-list"></div>
<div id="reader-detail" class="hidden"></div>
<div id="reader-empty" class="empty hidden">
<div class="empty-icon">📖</div>
<p data-i18n="reader.empty">Статей пока нет. Добавьте ленты, чтобы начать читать.</p>
</div>
</div>
</div>
</section>
<!-- ===================== HISTORY ===================== -->
2026-06-02 21:11:57 +08:00
<section id="tab-history" class="tab-panel">
<div class="panel-head">
<h2 data-i18n="history.heading">История уведомлений</h2>
<div class="panel-head-actions">
<button class="btn ghost" id="history-refresh" data-i18n="history.refresh">↻ Обновить</button>
<button class="btn danger admin-only" id="history-clear" data-i18n="history.clear">Очистить</button>
</div>
</div>
<div class="history-toolbar">
<input type="search" id="history-search" data-i18n-ph="history.search" placeholder="Поиск…">
<label class="check-inline"><input type="checkbox" id="history-errors">
<span data-i18n="history.onlyErrors">Только ошибки</span></label>
</div>
<div id="history-list" class="history"></div>
<div id="history-empty" class="empty hidden">
<div class="empty-icon">📭</div>
<p data-i18n="history.empty">История пуста.</p>
</div>
</section>
<!-- ===================== CATEGORIES ===================== -->
<section id="tab-categories" class="tab-panel">
<div class="panel-head">
<h2 data-i18n="categories.heading">Категории</h2>
<button class="btn primary" id="add-category" data-i18n="categories.add">+ Добавить категорию</button>
</div>
<div id="categories-list" class="cards"></div>
<div id="categories-empty" class="empty hidden">
<div class="empty-icon">🏷️</div>
<p data-i18n="categories.empty">Категорий пока нет.</p>
</div>
</section>
<!-- ===================== USERS ===================== -->
2026-06-02 21:11:57 +08:00
<section id="tab-users" class="tab-panel">
<div class="panel-head">
<h2 data-i18n="users.heading">Пользователи</h2>
<button class="btn primary" id="add-user" data-i18n="users.add">+ Добавить пользователя</button>
</div>
<div id="users-list" class="cards"></div>
</section>
<!-- ===================== SETTINGS ===================== -->
<section id="tab-settings" class="tab-panel">
<div class="panel-head"><h2 data-i18n="settings.heading">Настройки</h2></div>
<form id="settings-form" class="settings-card">
<h3 data-i18n="settings.ntfy">ntfy</h3>
<label><span data-i18n="settings.defaultServer">Сервер ntfy по умолчанию</span>
<input type="text" name="default_ntfy_server" placeholder="https://ntfy.sh">
<small class="muted" data-i18n="settings.defaultServerHint"></small>
</label>
<details class="adv">
<summary data-i18n="settings.ntfyAuth">Авторизация на сервере ntfy</summary>
<label><span data-i18n="feed.token">Access token</span> <small class="muted" data-i18n="feed.tokenHint"></small>
<input type="text" name="default_ntfy_token" placeholder="tk_..."></label>
<div class="grid-2">
<label><span data-i18n="feed.login">Логин</span>
<input type="text" name="default_ntfy_username" autocomplete="off"></label>
<label><span data-i18n="feed.password">Пароль</span>
<input type="password" name="default_ntfy_password" autocomplete="new-password"></label>
</div>
<small class="muted" data-i18n="settings.ntfyAuthHint"></small>
</details>
2026-06-02 21:11:57 +08:00
<div class="inline-test">
<input type="text" id="test-topic" data-i18n-ph="settings.testPh">
<button type="button" class="btn ghost" id="test-btn" data-i18n="settings.testBtn">Отправить тест</button>
</div>
<h3 data-i18n="settings.check">Проверка</h3>
<label><span data-i18n="settings.proxyUrl">URL прокси</span> <small class="muted" data-i18n="settings.proxyHint"></small>
<input type="text" name="proxy_url" placeholder="http://proxy:8080"></label>
<h3 data-i18n="settings.template">Шаблон уведомлений</h3>
<label><span data-i18n="settings.templateHint">Переменные: {title}, {body}, {link}, {source}, {image_url}</span>
<textarea name="notification_template" rows="3" style="width:100%;margin-top:4px;padding:8px;background:var(--bg-soft);border:1px solid var(--border);border-radius:8px;color:var(--text);font-family:monospace;font-size:13px;resize:vertical"></textarea></label>
2026-06-02 21:11:57 +08:00
<h3 data-i18n="settings.check">Проверка</h3>
<label><span data-i18n="settings.interval">Интервал проверки по умолчанию (минуты)</span>
<input type="number" name="check_interval" min="1" value="5">
<small class="muted" data-i18n="settings.intervalHint"></small>
</label>
<h3 data-i18n="settings.feedDefaults">Значения по умолчанию для новых лент</h3>
<div class="grid-2">
<label><span data-i18n="feed.priority">Приоритет</span>
<select name="default_priority">
<option value="1" data-i18n="feed.p1"></option>
<option value="2" data-i18n="feed.p2"></option>
<option value="3" selected data-i18n="feed.p3"></option>
<option value="4" data-i18n="feed.p4"></option>
<option value="5" data-i18n="feed.p5"></option>
</select>
</label>
<label><span data-i18n="feed.intervalMin">Интервал, мин</span> <small class="muted" data-i18n="feed.intervalHint"></small>
<input type="number" name="default_interval" min="0" value="0"></label>
</div>
<label><span data-i18n="feed.tags">Теги / эмодзи</span> <small class="muted" data-i18n="feed.commaHint"></small>
<input type="text" name="default_tags" placeholder="newspaper,fire"></label>
<label class="switch-row"><span data-i18n="feed.attach">Прикреплять картинку</span>
<input type="checkbox" name="default_attach_image" class="switch" checked></label>
<h3 data-i18n="settings.telegram">Telegram</h3>
2026-06-02 21:11:57 +08:00
<label class="switch-row"><span data-i18n="settings.tgEnable">Включить доставку в Telegram</span>
<input type="checkbox" name="telegram_enabled" class="switch"></label>
<div class="grid-2">
<label><span data-i18n="settings.tgToken">Bot Token</span>
<input type="text" name="telegram_token" placeholder="123456:ABC-..."></label>
<label><span data-i18n="settings.tgChat">Chat ID</span>
<input type="text" name="telegram_chat_id" placeholder="-1001234567890"></label>
</div>
<small class="muted" data-i18n="settings.tgHint"></small>
<h3 data-i18n="settings.webhook">Webhook</h3>
<label class="switch-row"><span data-i18n="settings.whEnable">Включить доставку через webhook</span>
<input type="checkbox" name="webhook_enabled" class="switch"></label>
<label><span data-i18n="settings.whUrl">URL webhook</span>
<input type="text" name="webhook_url" placeholder="https://example.com/hook">
<small class="muted" data-i18n="settings.whHint"></small></label>
<h3 data-i18n="settings.alerts">Оповещения администратора</h3>
<label class="switch-row"><span data-i18n="settings.alertEnable">Уведомлять, если лента «упала»</span>
<input type="checkbox" name="alerts_enabled" class="switch"></label>
<div class="grid-2">
<label><span data-i18n="settings.alertTopic">Тема ntfy для алертов</span>
<input type="text" name="alert_topic" placeholder="rss-alerts"></label>
<label><span data-i18n="settings.alertThreshold">Порог (ошибок подряд)</span>
<input type="number" name="alert_threshold" min="1" value="3"></label>
</div>
<h3 data-i18n="settings.auth">Авторизация</h3>
<label class="switch-row"><span data-i18n="settings.authRequire">Требовать вход в веб-панель</span>
<input type="checkbox" name="auth_enabled" class="switch"></label>
<small class="muted" data-i18n="settings.authHint"></small>
<div class="form-actions">
<button type="submit" class="btn primary" data-i18n="settings.save">Сохранить настройки</button>
</div>
</form>
</section>
</main>
<!-- ===================== FEED MODAL ===================== -->
<div id="modal" class="modal-backdrop hidden">
<form class="modal" id="feed-form">
<div class="modal-head">
<h3 id="modal-title" data-i18n="modal.addFeed">Добавить ленту</h3>
<button type="button" class="icon-btn" id="modal-close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id">
<label><span data-i18n="feed.url">URL ленты *</span>
<input type="url" name="url" placeholder="https://example.com/feed.xml" required>
</label>
<label><span data-i18n="feed.title">Название</span> <small class="muted" data-i18n="feed.titleOpt"></small>
<input type="text" name="title">
</label>
<label><span data-i18n="feed.category">Категория</span>
<select name="category_id">
<option value="" data-i18n="feed.categoryNone">— без категории —</option>
</select>
</label>
2026-06-02 21:11:57 +08:00
<div class="grid-2">
<label><span data-i18n="feed.server">Сервер ntfy</span> <small class="muted" data-i18n="feed.serverHint"></small>
<input type="text" name="ntfy_server" placeholder="https://ntfy.sh"></label>
<label><span data-i18n="feed.topic">Тема ntfy</span>
<input type="text" name="ntfy_topic" placeholder="my-news"></label>
</div>
<details class="adv">
<summary data-i18n="feed.priv">Приватный ntfy-сервер (авторизация)</summary>
<label><span data-i18n="feed.token">Access token</span> <small class="muted" data-i18n="feed.tokenHint"></small>
<input type="text" name="ntfy_token" placeholder="tk_..."></label>
<div class="grid-2">
<label><span data-i18n="feed.login">Логин</span>
<input type="text" name="ntfy_username" autocomplete="off"></label>
<label><span data-i18n="feed.password">Пароль</span>
<input type="password" name="ntfy_password" autocomplete="new-password"></label>
</div>
</details>
<div class="grid-2">
<label><span data-i18n="feed.priority">Приоритет</span>
<select name="priority">
<option value="1" data-i18n="feed.p1"></option>
<option value="2" data-i18n="feed.p2"></option>
<option value="3" selected data-i18n="feed.p3"></option>
<option value="4" data-i18n="feed.p4"></option>
<option value="5" data-i18n="feed.p5"></option>
</select>
</label>
<label><span data-i18n="feed.intervalMin">Интервал, мин</span> <small class="muted" data-i18n="feed.intervalHint"></small>
<input type="number" name="interval" min="0" value="0"></label>
</div>
<details class="adv">
<summary data-i18n="feed.digest">Дайджест</summary>
<label class="switch-row"><span data-i18n="feed.digestEnable">Накапливать записи (дайджест)</span>
<input type="checkbox" name="digest_enabled" class="switch"></label>
<label><span data-i18n="feed.digestPeriod">Период дайджеста (часы)</span>
<input type="number" name="digest_period_hours" min="1" value="24"></label>
</details>
2026-06-02 21:11:57 +08:00
<label><span data-i18n="feed.tags">Теги / эмодзи</span> <small class="muted" data-i18n="feed.commaHint"></small>
<input type="text" name="tags" placeholder="newspaper,fire"></label>
<div class="grid-2">
<label><span data-i18n="feed.filterInc">Фильтр: только с этими словами</span> <small class="muted" data-i18n="feed.commaHint"></small>
<input type="text" name="filter_include" placeholder="python, ai"></label>
<label><span data-i18n="feed.filterExc">Фильтр: исключить слова</span> <small class="muted" data-i18n="feed.commaHint"></small>
<input type="text" name="filter_exclude" placeholder="sponsored"></label>
</div>
<div class="switch-grid">
<label class="switch-row"><span data-i18n="feed.attach">Прикреплять картинку</span>
<input type="checkbox" name="attach_image" class="switch" checked></label>
<label class="switch-row"><span data-i18n="feed.fetchArticle">Загружать полную статью (trafilatura)</span>
<input type="checkbox" name="fetch_full_article" class="switch"></label>
<small class="muted" data-i18n="feed.fetchArticleHint" style="margin-top:-8px;display:block">Загружает страницу статьи и извлекает основной текст.</small>
<label class="switch-row"><span data-i18n="feed.fullContent">Отправлять полный контент</span>
<input type="checkbox" name="send_full_content" class="switch"></label>
<small class="muted" data-i18n="feed.fullContentHint" style="margin-top:-8px;display:block">Весь текст, все картинки и видео. Для ntfy — Markdown.</small>
2026-06-02 21:11:57 +08:00
<label class="switch-row"><span data-i18n="feed.dupTg">Дублировать в Telegram</span>
<input type="checkbox" name="to_telegram" class="switch"></label>
<label class="switch-row"><span data-i18n="feed.toWebhook">Отправлять в webhook</span>
<input type="checkbox" name="to_webhook" class="switch"></label>
<label class="switch-row"><span data-i18n="feed.enabled">Лента включена</span>
<input type="checkbox" name="enabled" class="switch" checked></label>
</div>
<div class="preview-block">
<button type="button" class="btn ghost small" id="preview-btn" data-i18n="feed.preview">👁 Предпросмотр</button>
<div id="preview-area"></div>
</div>
</div>
<div class="modal-foot">
<button type="button" class="btn ghost" id="modal-cancel" data-i18n="modal.cancel">Отмена</button>
<button type="submit" class="btn primary" data-i18n="modal.save">Сохранить</button>
</div>
</form>
</div>
<!-- ===================== CATEGORY MODAL ===================== -->
<div id="cat-modal" class="modal-backdrop hidden">
<form class="modal" id="cat-form">
<div class="modal-head">
<h3 id="cat-modal-title" data-i18n="cat.addTitle">Добавить категорию</h3>
<button type="button" class="icon-btn" id="cat-modal-close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id">
<label><span data-i18n="cat.name">Название *</span>
<input type="text" name="name" required></label>
<label><span data-i18n="cat.sortOrder">Порядок сортировки</span>
<input type="number" name="sort_order" min="0" value="0"></label>
</div>
<div class="modal-foot">
<button type="button" class="btn ghost" id="cat-modal-cancel" data-i18n="modal.cancel">Отмена</button>
<button type="submit" class="btn primary" data-i18n="modal.save">Сохранить</button>
</div>
</form>
</div>
2026-06-02 21:11:57 +08:00
<!-- ===================== USER MODAL ===================== -->
<div id="user-modal" class="modal-backdrop hidden">
<form class="modal" id="user-form">
<div class="modal-head">
<h3 id="user-modal-title" data-i18n="user.addTitle">Добавить пользователя</h3>
<button type="button" class="icon-btn" id="user-modal-close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id">
<label><span data-i18n="user.login">Логин *</span>
<input type="text" name="username" autocomplete="off" required></label>
<label><span data-i18n="user.password">Пароль</span> <small class="muted" id="pw-hint"></small>
<input type="password" name="password" autocomplete="new-password"></label>
<label><span data-i18n="user.role">Роль</span>
<select name="role">
<option value="admin" data-i18n="user.roleAdmin"></option>
<option value="viewer" data-i18n="user.roleViewer"></option>
</select></label>
</div>
<div class="modal-foot">
<button type="button" class="btn ghost" id="user-modal-cancel" data-i18n="modal.cancel">Отмена</button>
<button type="submit" class="btn primary" data-i18n="modal.save">Сохранить</button>
</div>
</form>
</div>
<div id="toast" class="toast hidden"></div>
{% endblock %}
{% block scripts %}<script src="/static/app.js"></script>{% endblock %}