feat: complete UI migration to Tailwind CSS + HTMX
This commit is contained in:
@@ -1,123 +1,155 @@
|
||||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="toolbar">
|
||||
<div>
|
||||
<h1>Источники</h1>
|
||||
<div class="muted">VK-источники для парсинга. Название идёт в prompt, тэг — в будущие хэштеги.</div>
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold">Источники</h1>
|
||||
<div class="text-base-content/60 mt-1">VK-источники для парсинга. Название идёт в prompt, тэг — в будущие хэштеги.</div>
|
||||
</div>
|
||||
|
||||
<div class="collapse collapse-arrow bg-base-100 shadow-sm border border-base-200 mb-6 {% if source_preview %}collapse-open{% endif %}">
|
||||
<input type="checkbox" {% if source_preview %}checked{% endif %} />
|
||||
<div class="collapse-title text-lg font-bold">
|
||||
Добавить источники
|
||||
</div>
|
||||
<div class="collapse-content border-t border-base-200 pt-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
|
||||
<!-- Single Add -->
|
||||
<div>
|
||||
<h3 class="font-bold mb-4">Добавить один</h3>
|
||||
<form method="post" action="/sources/new" class="flex flex-col gap-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<input type="hidden" name="platform" value="vk">
|
||||
<input type="hidden" name="priority" value="100">
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Название</span></label>
|
||||
<input name="name" class="input input-bordered input-sm" placeholder="Можно оставить как в VK">
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Тэг</span></label>
|
||||
<input name="tag" class="input input-bordered input-sm" placeholder="academy_gear">
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Ссылка</span></label>
|
||||
<input name="url" class="input input-bordered input-sm" placeholder="https://vk.com/academy_gear" required>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-2">
|
||||
<input name="active" type="checkbox" class="checkbox checkbox-primary checkbox-sm" checked>
|
||||
<span class="label-text">Включить сразу</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-sm mt-2" type="submit">Добавить</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Bulk Add -->
|
||||
<div>
|
||||
<h3 class="font-bold mb-4">Массовое добавление</h3>
|
||||
<form method="post" action="/sources/preview" class="flex flex-col gap-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<input type="hidden" name="q" value="{{ q }}">
|
||||
<input type="hidden" name="status_filter" value="{{ status_filter }}">
|
||||
<input type="hidden" name="per_page" value="{{ pagination.per_page }}">
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Список (Название Тэг Ссылка)</span></label>
|
||||
<textarea name="bulk_text" class="textarea textarea-bordered font-mono text-xs" rows="5" placeholder="Academy Gear academy_gear https://vk.com/academy_gear A2 Technologies a2technologies https://vk.com/a2technologies">{{ bulk_text }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-2">
|
||||
<input name="bulk_active" type="checkbox" class="checkbox checkbox-primary checkbox-sm" {% if bulk_active %}checked{% endif %}>
|
||||
<span class="label-text">Включить после добавления</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline btn-sm mt-2" type="submit">Проверить список</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if source_preview %}
|
||||
<div class="mt-8 pt-4 border-t border-base-200">
|
||||
<h3 class="font-bold mb-4">Предпросмотр массового добавления</h3>
|
||||
<div class="overflow-x-auto bg-base-200 rounded-lg">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th># строки</th>
|
||||
<th>Название</th>
|
||||
<th>Тэг</th>
|
||||
<th>Ссылка</th>
|
||||
<th>VK ID</th>
|
||||
<th>Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in source_preview %}
|
||||
<tr>
|
||||
<td>{{ item.line_no }}</td>
|
||||
<td>{{ item.name or "—" }}</td>
|
||||
<td class="font-mono text-xs">#{{ item.tag or "—" }}</td>
|
||||
<td><a href="{{ item.url }}" target="_blank" class="link">{{ item.url }}</a></td>
|
||||
<td class="font-mono text-xs text-base-content/70">
|
||||
{{ item.external_id }}<br>
|
||||
{{ item.external_owner_id or "" }}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.ok %}
|
||||
<span class="badge badge-success badge-sm">Готов</span>
|
||||
{% else %}
|
||||
<span class="badge badge-error badge-sm" title="{{ item.error }}">Ошибка</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/sources/bulk" class="mt-4 flex items-center gap-4">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<input type="hidden" name="bulk_payload" value='{{ source_preview|tojson }}'>
|
||||
<button class="btn btn-primary btn-sm" type="submit">Добавить прошедшие проверку</button>
|
||||
<span class="text-xs text-base-content/60">Строки с ошибками и дубли будут пропущены.</span>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details class="panel" {% if source_preview %}open{% endif %}>
|
||||
<summary><strong>Добавить источники</strong></summary>
|
||||
<div class="source-add-grid">
|
||||
<form method="post" action="/sources/new">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<input type="hidden" name="platform" value="vk">
|
||||
<input type="hidden" name="priority" value="100">
|
||||
<label><span>Название</span><input name="name" placeholder="Можно оставить как в VK"></label>
|
||||
<label><span>Тэг</span><input name="tag" placeholder="academy_gear"></label>
|
||||
<label><span>Ссылка</span><input name="url" placeholder="https://vk.com/academy_gear" required></label>
|
||||
<label class="checkline"><input name="active" type="checkbox" checked> <span>Включить сразу</span></label>
|
||||
<button class="btn primary" type="submit">Добавить</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/sources/preview">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<input type="hidden" name="q" value="{{ q }}">
|
||||
<input type="hidden" name="status_filter" value="{{ status_filter }}">
|
||||
<input type="hidden" name="per_page" value="{{ pagination.per_page }}">
|
||||
<label><span>Список источников</span>
|
||||
<textarea name="bulk_text" rows="7" placeholder="Academy Gear academy_gear https://vk.com/academy_gear A2 Technologies a2technologies https://vk.com/a2technologies">{{ bulk_text }}</textarea>
|
||||
</label>
|
||||
<label class="checkline"><input name="bulk_active" type="checkbox" {% if bulk_active %}checked{% endif %}> <span>Включить после добавления</span></label>
|
||||
<button class="btn" type="submit">Проверить список</button>
|
||||
<div class="card bg-base-100 shadow-sm border border-base-200 mb-6">
|
||||
<div class="card-body p-4 flex flex-col md:flex-row gap-4 justify-between items-end bg-base-200/50 rounded-t-xl">
|
||||
<form class="flex-1 flex flex-wrap gap-4 items-end w-full" hx-get="/sources" hx-target="#content-area" hx-push-url="true">
|
||||
<div class="form-control w-full max-w-xs">
|
||||
<label class="label"><span class="label-text font-bold">Поиск</span></label>
|
||||
<input name="q" value="{{ q }}" placeholder="Название, ссылка, id" class="input input-bordered input-sm w-full">
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Статус</span></label>
|
||||
<select name="status_filter" class="select select-bordered select-sm">
|
||||
<option value="">Все</option>
|
||||
{% for st in ["new","ok","paused","error"] %}
|
||||
<option value="{{ st }}" {% if status_filter == st %}selected{% endif %}>{{ st }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-sm" type="submit">Найти</button>
|
||||
<div class="w-full">
|
||||
{% include "table_tools.html" %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if source_preview %}
|
||||
<div class="preview-block">
|
||||
<h3>Предпросмотр</h3>
|
||||
<table>
|
||||
<tr><th>#</th><th>Название</th><th>Тэг</th><th>Ссылка</th><th>VK id</th><th>Проверка</th></tr>
|
||||
{% for item in source_preview %}
|
||||
<tr>
|
||||
<td>{{ item.line_no }}</td>
|
||||
<td>{{ item.name or "—" }}</td>
|
||||
<td class="mono">#{{ item.tag or "—" }}</td>
|
||||
<td><a href="{{ item.url }}" target="_blank">{{ item.url }}</a></td>
|
||||
<td class="mono">{{ item.external_id }}<br>{{ item.external_owner_id or "" }}</td>
|
||||
<td>{% if item.ok %}<span class="pill ok">готов</span>{% else %}<span class="pill bad">{{ item.error }}</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<form method="post" action="/sources/bulk" class="row">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<input type="hidden" name="bulk_payload" value='{{ source_preview|tojson }}'>
|
||||
<button class="btn primary" type="submit">Добавить прошедшие проверку</button>
|
||||
<span class="muted">Строки с ошибками и дубли будут пропущены.</span>
|
||||
</form>
|
||||
|
||||
<div class="card-body p-0" id="content-area">
|
||||
{% include "sources_content.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</details>
|
||||
|
||||
<div class="panel">
|
||||
<form class="filter-grid sources-filter" method="get" action="/sources">
|
||||
<label><span>Поиск</span><input name="q" value="{{ q }}" placeholder="название, ссылка, id"></label>
|
||||
<label><span>Статус</span>
|
||||
<select name="status_filter">
|
||||
<option value="">Все</option>
|
||||
{% for st in ["new","ok","paused","error"] %}
|
||||
<option value="{{ st }}" {% if status_filter == st %}selected{% endif %}>{{ st }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label><span>На странице</span>
|
||||
<select name="per_page">
|
||||
{% for n in [25,50,100,200] %}
|
||||
<option value="{{ n }}" {% if pagination.per_page == n %}selected{% endif %}>{{ n }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
{% include "table_tools.html" %}
|
||||
<button class="btn filter-submit" type="submit">Показать</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<table class="compact-table">
|
||||
<tr>
|
||||
<th>#</th><th>Площадка</th><th>Название</th><th>Тэг</th><th>Ссылка</th><th>Статус</th><th>Посты</th><th>Последний парсинг</th><th></th>
|
||||
</tr>
|
||||
{% for s in sources %}
|
||||
<tr>
|
||||
<td>{{ s.id }}</td>
|
||||
<td>{{ s.platform }}</td>
|
||||
<td>
|
||||
<b>{{ s.name }}</b>
|
||||
{% if s.active %}<span class="pill ok source-state">on</span>{% else %}<span class="pill bad source-state">off</span>{% endif %}
|
||||
<div class="muted mono">{{ s.external_id or "" }}</div>
|
||||
</td>
|
||||
<td class="mono">#{{ s.tag or "—" }}</td>
|
||||
<td class="source-url"><a href="{{ s.url }}" target="_blank">{{ s.url }}</a></td>
|
||||
<td><span class="pill {% if s.status == 'ok' %}ok{% elif s.status == 'error' %}bad{% endif %}">{{ s.status }}</span>{% if s.status_msg %}<div class="muted status-msg">{{ s.status_msg }}</div>{% endif %}</td>
|
||||
<td>{{ s.posts_count }} <span class="muted">+{{ s.posts_24h }}/24ч</span></td>
|
||||
<td>{{ s.last_parsed_at_fmt or "нет" }}</td>
|
||||
<td>
|
||||
<div class="icon-actions">
|
||||
<a class="icon-btn" href="/sources/{{ s.id }}/edit" title="Править" aria-label="Править">✎</a>
|
||||
<form method="post" action="/sources/{{ s.id }}/toggle">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<button class="icon-btn" type="submit" title="{% if s.active %}Выключить{% else %}Включить{% endif %}" aria-label="Вкл/выкл">⏻</button>
|
||||
</form>
|
||||
<form method="post" action="/sources/{{ s.id }}/delete" onsubmit="return confirm('Удалить источник {{ s.name }}?');">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<button class="icon-btn danger" type="submit" title="Удалить" aria-label="Удалить">×</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% include "pagination.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user