mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 18:05:03 +00:00
48c25f13d4
- parse_address(address, crawler) в sources/sources.py: telegram (t.me/name, @name, https://t.me/s/name) → slug/channel/url; rss → feed_url/url/slug из домена. - GET /sources/preview: telegram — название через Telethon get_entity (title), rss — через feedparser (feed.title); slug и name возвращаются в JSON. - _form_source: единое поле address вместо url/channel/feed_url; slug из адреса. - /sources: direction datalist из DIRECTIONS + уникальные значения из БД, свободный ввод остаётся. - Форма: поле Address + кнопка «Найти» (JS fetch → заполнение slug/name), direction datalist динамический. Также в sources.py входит незакоммиченная ранее веб-CRUD-логика (upsert_source_yaml, delete_source_yaml, sync_yaml_to_db_and_back и др.) — работала на проде, теперь в git.
168 lines
8.4 KiB
HTML
168 lines
8.4 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Источники{% endblock %}
|
||
{% block content %}
|
||
<h4 class="mb-3">📡 Источники <a href="/crawlers" class="btn btn-sm btn-outline-secondary ms-2">Статус краулеров →</a></h4>
|
||
|
||
{% if did %}
|
||
<div class="alert alert-{{ 'success' if not did.startswith('error') else 'danger' }} py-2 small">
|
||
{{ did }}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Форма добавления -->
|
||
<div class="card mb-3">
|
||
<div class="card-header py-2"><b>➕ Добавить источник</b></div>
|
||
<div class="card-body py-2">
|
||
<form method="post" action="/sources/add" class="row g-2 align-items-end">
|
||
<div class="col-auto">
|
||
<label class="form-label mb-0 small">slug</label>
|
||
<input type="text" name="slug" class="form-control form-control-sm" placeholder="my_channel" required>
|
||
</div>
|
||
<div class="col-auto">
|
||
<label class="form-label mb-0 small">Название</label>
|
||
<input type="text" name="name" class="form-control form-control-sm" placeholder="My Channel">
|
||
</div>
|
||
<div class="col-auto">
|
||
<label class="form-label mb-0 small">Address</label>
|
||
<input type="text" name="address" class="form-control form-control-sm"
|
||
placeholder="@handle, t.me/name, https://t.me/name или https://.../rss" id="add_address">
|
||
</div>
|
||
<div class="col-auto">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" id="btn_fetch"
|
||
onclick="fetchSourcePreview()">🔎 Найти</button>
|
||
</div>
|
||
<div class="col-auto">
|
||
<label class="form-label mb-0 small">crawler</label>
|
||
<select name="crawler" class="form-select form-select-sm">
|
||
<option value="telegram">telegram</option>
|
||
<option value="rss">rss</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-auto">
|
||
<label class="form-label mb-0 small">direction</label>
|
||
<input type="text" name="direction" class="form-control form-control-sm" placeholder="linux" list="dirs">
|
||
<datalist id="dirs">
|
||
{% for d in directions %}<option value="{{ d }}">{% endfor %}
|
||
</datalist>
|
||
</div>
|
||
<div class="col-auto">
|
||
<label class="form-label mb-0 small">priority</label>
|
||
<select name="priority" class="form-select form-select-sm">
|
||
<option value="P0">P0</option><option value="P1" selected>P1</option>
|
||
<option value="P2">P2</option><option value="P3">P3</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-auto">
|
||
<div class="form-check form-switch mt-3">
|
||
<input class="form-check-input" type="checkbox" name="enabled" id="add_en" checked>
|
||
<label class="form-check-label small" for="add_en">enabled</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-auto"><button class="btn btn-sm btn-primary">Добавить</button></div>
|
||
</form>
|
||
<script>
|
||
const addCrawler = document.querySelector('form[action="/sources/add"] select[name="crawler"]');
|
||
const addAddress = document.getElementById('add_address');
|
||
const addSlug = document.querySelector('form[action="/sources/add"] input[name="slug"]');
|
||
const addName = document.querySelector('form[action="/sources/add"] input[name="name"]');
|
||
|
||
if (addCrawler && addAddress) {
|
||
function toggleAddress() {
|
||
addAddress.placeholder = addCrawler.value === 'telegram'
|
||
? '@handle, t.me/name, https://t.me/name'
|
||
: 'https://.../rss или http://...';
|
||
}
|
||
addCrawler.addEventListener('change', toggleAddress);
|
||
toggleAddress();
|
||
}
|
||
|
||
async function fetchSourcePreview() {
|
||
const addr = addAddress.value.trim();
|
||
if (!addr) { alert('Введите адрес'); return; }
|
||
try {
|
||
const resp = await fetch(`/sources/preview?address=${encodeURIComponent(addr)}&crawler=${addCrawler.value}`);
|
||
const data = await resp.json();
|
||
if (data.error) { alert(data.error); return; }
|
||
if (data.slug) addSlug.value = data.slug;
|
||
if (data.name) { addName.value = data.name; }
|
||
else if (addName.value === '') addName.placeholder = 'Название не найдено — введите вручную';
|
||
} catch (e) { alert('Ошибка запроса: ' + e); }
|
||
}
|
||
</script>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Таблица источников -->
|
||
<div class="card">
|
||
<div class="card-header py-2"><b>🗂️ Таблица ({{ sources|length }})</b>
|
||
<span class="text-muted small">источник истины: sources.yaml (правки пишутся в yaml + БД)</span>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-hover align-middle mb-0">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>slug</th><th>Название</th><th>crawler</th><th>direction</th><th>lang</th>
|
||
<th>priority</th><th>enabled</th><th class="text-end">статус</th><th>послед. запуск</th><th class="text-end">действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for s in sources %}
|
||
<tr class="{{ 'text-muted' if not s['enabled'] }}">
|
||
<td><code>{{ s['slug'] }}</code></td>
|
||
<td>
|
||
<form method="post" action="/sources/{{ s['id'] }}/edit" class="row g-1 align-items-center">
|
||
<div class="col-auto">
|
||
<input type="text" name="name" value="{{ s['name'] or '' }}" class="form-control form-control-sm source-name" style="width:170px">
|
||
</div>
|
||
<div class="col-auto"><button class="btn btn-sm btn-outline-secondary">💾</button></div>
|
||
</form>
|
||
</td>
|
||
<td><span class="badge text-bg-secondary">{{ s['crawler'] }}</span>
|
||
{% if s['crawler'] == 'rss' and s['feed_url'] %}<br><small class="text-muted">{{ s['feed_url'] }}</small>{% endif %}
|
||
{% if s['crawler'] == 'telegram' and s['channel'] %}<br><small class="text-muted">{{ s['channel'] }}</small>{% endif %}
|
||
</td>
|
||
<td>{{ s['direction'] or '—' }}
|
||
{% if s['error_count'] %}<br><small class="text-danger">{{ s['error_count'] }} err</small>{% endif %}
|
||
</td>
|
||
<td>{{ s['lang'] or 'ru' }}</td>
|
||
<td>
|
||
<form method="post" action="/sources/{{ s['id'] }}/edit" class="d-inline">
|
||
<select name="priority" class="form-select form-select-sm" style="width:auto" onchange="this.form.submit()">
|
||
{% for p in ['P0','P1','P2','P3'] %}
|
||
<option value="{{ p }}" {{ 'selected' if s['priority'] == p else '' }}>P{{ loop.index0 }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</form>
|
||
</td>
|
||
<td>
|
||
<span class="badge {{ 'text-bg-success' if s['enabled'] else 'text-bg-secondary' }}">
|
||
{{ 'вкл' if s['enabled'] else 'пауза' }}
|
||
</span>
|
||
</td>
|
||
<td class="text-end">
|
||
{% if s['status'] == 'dead' %}<span class="badge text-bg-danger">dead</span>
|
||
{% elif s['status'] == 'alive' %}<span class="badge text-bg-success">alive</span>
|
||
{% elif s['status'] == 'new' %}<span class="badge text-bg-info">new</span>
|
||
{% else %}<span class="badge text-bg-secondary">{{ s['status'] }}</span>{% endif %}
|
||
{% if s['enabled'] == 0 %}<span class="badge text-bg-warning">пауза</span>{% endif %}
|
||
</td>
|
||
<td class="small text-muted">{{ s['last_fetch'] or '—' }}{% if s['last_error'] %}<br><span class="text-danger">{{ s['last_error'][:40] }}</span>{% endif %}</td>
|
||
<td class="text-end">
|
||
<form method="post" action="/sources/{{ s['id'] }}/toggle" class="d-inline">
|
||
<button class="btn btn-sm {{ 'btn-outline-warning' if s['enabled'] else 'btn-outline-success' }}"
|
||
title="{{ 'Пауза' if s['enabled'] else 'Возобновить' }}">
|
||
{{ '⏸' if s['enabled'] else '▶' }}
|
||
</button>
|
||
</form>
|
||
<form method="post" action="/sources/{{ s['id'] }}/delete" class="d-inline"
|
||
onsubmit="return confirm('Удалить источник {{ s['slug'] }}? Это уберёт его из sources.yaml и БД (посты останутся).')">
|
||
<button class="btn btn-sm btn-outline-danger" title="Удалить">🗑</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% endblock %} |