Add monitoring TG service

This commit is contained in:
Grendgi
2026-06-04 14:55:41 +03:00
commit f9e072774c
74 changed files with 7232 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>👥 HR — подразделы</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/api/monitoring-tg/static/css/app.css" />
</head>
<body>
<header>
<h1 id="page-title">parser-tg-bot · 👥 HR / Кадры</h1>
<nav id="nav-section"></nav>
</header>
<main>
<div class="row">
<h2>Подразделы HR</h2>
<div class="spacer"></div>
<button id="open-create">+ Новый подраздел</button>
</div>
<p class="muted">
Каждый подраздел — это собственный набор каналов, своя статистика и свой
LLM-промпт (с фоллбэком на промпт вертикали). Например: IT, продажи,
маркетинг, рабочие специальности.
</p>
<div id="sections-grid"></div>
</main>
<dialog id="create-dialog">
<h3 style="margin-top:0">Новый подраздел</h3>
<form id="create-form">
<label class="row" style="gap:8px; margin-bottom:8px">
<span style="min-width:120px" class="muted">Название</span>
<input type="text" id="new-title" required placeholder="IT" style="flex:1" />
</label>
<div class="row" style="gap:8px; margin-bottom:8px; font-size:12px">
<span style="min-width:120px" class="muted">URL-адрес</span>
<span class="muted mono">/hr/<span id="new-slug-preview">(введите название)</span>/</span>
<div class="spacer"></div>
<a href="#" id="new-slug-manual" class="muted">изменить вручную</a>
</div>
<label class="row slug-row" style="gap:8px; margin-bottom:8px" hidden>
<span style="min-width:120px" class="muted">Slug</span>
<input type="text" id="new-slug" pattern="[a-z0-9][a-z0-9_-]*[a-z0-9]?"
placeholder="it" style="flex:1" />
</label>
<label class="row" style="gap:8px; margin-bottom:8px">
<span style="min-width:120px" class="muted">Иконка</span>
<input type="text" id="new-emoji" maxlength="4" placeholder="💻" style="width:80px" />
</label>
<label class="row" style="gap:8px; margin-bottom:8px">
<span style="min-width:120px" class="muted">Код доступа</span>
<input type="text" id="new-access-code" required minlength="3"
autocomplete="new-password" style="flex:1" />
</label>
<label class="row" style="gap:8px; margin-bottom:8px; align-items:flex-start">
<span style="min-width:120px" class="muted">Описание</span>
<textarea id="new-description" rows="3" style="flex:1"></textarea>
</label>
<div class="row" style="justify-content:flex-end; gap:8px; margin-top:12px">
<button type="button" id="create-cancel" class="secondary">Отмена</button>
<button type="submit">Создать</button>
</div>
</form>
</dialog>
<dialog id="edit-dialog">
<h3 style="margin-top:0">Редактировать подраздел</h3>
<form id="edit-form">
<input type="hidden" id="edit-slug" />
<label class="row" style="gap:8px; margin-bottom:8px">
<span style="min-width:120px" class="muted">Название</span>
<input type="text" id="edit-title" required style="flex:1" />
</label>
<label class="row" style="gap:8px; margin-bottom:8px">
<span style="min-width:120px" class="muted">Иконка</span>
<input type="text" id="edit-emoji" maxlength="4" style="width:80px" />
</label>
<label class="row" style="gap:8px; margin-bottom:8px">
<span style="min-width:120px" class="muted">Код доступа</span>
<input type="text" id="edit-access-code" required minlength="3"
autocomplete="new-password" style="flex:1" />
</label>
<label class="row" style="gap:8px; margin-bottom:8px; align-items:flex-start">
<span style="min-width:120px" class="muted">Описание</span>
<textarea id="edit-description" rows="3" style="flex:1"></textarea>
</label>
<div class="row" style="justify-content:flex-end; gap:8px; margin-top:12px">
<button type="button" id="edit-cancel" class="secondary">Отмена</button>
<button type="submit">Сохранить</button>
</div>
</form>
</dialog>
<script type="module" src="/api/monitoring-tg/static/js/nav.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/nav-status.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/sections-list.js"></script>
</body>
</html>

View File

@@ -0,0 +1,48 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>👥 HR · Каналы — parser-tg-bot</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/api/monitoring-tg/static/css/app.css" />
</head>
<body>
<header>
<h1 id="page-title">parser-tg-bot</h1>
<nav id="nav-section"></nav>
</header>
<main>
<h2 id="page-heading">Каналы подраздела</h2>
<div class="card" style="margin-bottom:24px">
<form id="add-form" class="row">
<input type="text" id="identifier" placeholder="@channel или https://t.me/..." required style="flex:1; min-width:280px" />
<button type="submit">Добавить канал</button>
</form>
<div class="muted" style="margin-top:8px; font-size:12px">
Канал будет привязан к текущему подразделу.
</div>
</div>
<div class="card">
<table>
<thead>
<tr>
<th>ID</th>
<th>Канал</th>
<th>Telegram ID</th>
<th>Сообщ.</th>
<th>Последний опрос</th>
<th>Статус</th>
<th></th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
</main>
<script type="module" src="/api/monitoring-tg/static/js/nav.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/nav-status.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/channels.js"></script>
</body>
</html>

View File

@@ -0,0 +1,43 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>👥 HR · Дашборд — parser-tg-bot</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/api/monitoring-tg/static/css/app.css" />
</head>
<body>
<header>
<h1 id="page-title">parser-tg-bot</h1>
<nav id="nav-section"></nav>
</header>
<main>
<div class="row">
<h2 id="page-heading">Дашборд</h2>
<div class="spacer"></div>
<button id="poll-all">Опросить все каналы подраздела</button>
</div>
<div class="stats-grid" id="stats"></div>
<h3>Каналы подраздела</h3>
<div class="card">
<table>
<thead>
<tr>
<th>Канал</th>
<th>Сообщений</th>
<th>Последнее сообщение</th>
<th>Последний опрос</th>
<th>Статус</th>
</tr>
</thead>
<tbody id="channels-tbody"></tbody>
</table>
</div>
</main>
<script type="module" src="/api/monitoring-tg/static/js/nav.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/nav-status.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/dashboard.js"></script>
</body>
</html>

View File

@@ -0,0 +1,78 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>👥 HR · Сообщения — parser-tg-bot</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/api/monitoring-tg/static/css/app.css" />
</head>
<body>
<header>
<h1 id="page-title">parser-tg-bot</h1>
<nav id="nav-section"></nav>
</header>
<main>
<h2 id="page-heading">Сообщения подраздела</h2>
<div class="toolbar card">
<select id="channel-filter">
<option value="">Все каналы подраздела</option>
</select>
<input type="search" id="search" placeholder="Поиск по тексту..." />
<select id="hr-kind">
<option value="">Любой тип лида</option>
<option value="any">👥 HR (любой)</option>
<option value="vacancy">📢 Вакансия (наниматель)</option>
<option value="resume">📄 Резюме (соискатель)</option>
<option value="contact">📇 Лид-контакт</option>
</select>
<label class="row" style="gap:6px">
<input type="checkbox" id="leads-only" />
<span class="muted">🎯 Только лиды (ИИ)</span>
</label>
<select id="min-confidence" title="Минимальная уверенность ИИ">
<option value="0.3">0.3+</option>
<option value="0.5" selected>0.5+</option>
<option value="0.7">0.7+</option>
<option value="0.9">0.9+</option>
</select>
<label class="row" style="gap:6px">
<input type="checkbox" id="has-phone" />
<span class="muted">📞 С телефоном</span>
</label>
<select id="limit">
<option value="25">25</option>
<option value="50" selected>50</option>
<option value="100">100</option>
<option value="200">200</option>
</select>
<div class="spacer"></div>
<label class="row" style="gap:6px">
<input type="checkbox" id="autorefresh" />
<span class="muted">Автообновление</span>
</label>
<button id="refresh" class="secondary">Обновить</button>
</div>
<div class="card" id="list"></div>
<div class="pagination">
<button id="prev" class="secondary">← Назад</button>
<span class="muted" id="page-info" style="align-self:center"></span>
<button id="next" class="secondary">Вперёд →</button>
</div>
</main>
<dialog id="raw-dialog">
<h3 style="margin-top:0">Сообщение</h3>
<pre id="raw-content"></pre>
<div class="row" style="justify-content:flex-end; margin-top:12px">
<button class="secondary" id="raw-close">Закрыть</button>
</div>
</dialog>
<script type="module" src="/api/monitoring-tg/static/js/nav.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/nav-status.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/messages.js"></script>
</body>
</html>

View File

@@ -0,0 +1,66 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>👥 HR · Настройки — parser-tg-bot</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/api/monitoring-tg/static/css/app.css" />
</head>
<body>
<header>
<h1 id="page-title">parser-tg-bot</h1>
<nav id="nav-section"></nav>
</header>
<main>
<h2 id="page-heading">Настройки подраздела</h2>
<div class="card" style="margin-bottom:24px">
<h3 style="margin-top:0">Текущая конфигурация</h3>
<table>
<tbody id="config-tbody">
<tr><td colspan="2" class="empty">Загрузка...</td></tr>
</tbody>
</table>
<div class="muted" style="font-size:12px; margin-top:12px">
Параметры задаются через переменные окружения (<span class="mono">.env</span>).
Для изменения отредактируйте <span class="mono">.env</span> и перезапустите контейнер:
<span class="mono">docker compose restart app</span>.
</div>
</div>
<div class="card" style="margin-bottom:24px">
<h3 style="margin-top:0">Действия</h3>
<div class="row">
<button id="poll-all">Опросить все каналы подраздела сейчас</button>
<a href="/api/monitoring-tg/docs" target="_blank" class="badge">OpenAPI / Swagger</a>
<a href="/api/monitoring-tg/healthz" target="_blank" class="badge">Health check</a>
</div>
</div>
<div class="card" style="margin-bottom:24px">
<h3 style="margin-top:0">🤖 Промпт ИИ</h3>
<div class="row" style="margin-bottom:8px">
<span class="badge" id="prompt-status"></span>
<span class="muted" id="prompt-length"></span>
<div class="spacer"></div>
<select id="prompt-level" title="Уровень редактирования промпта">
<option value="section" selected>Промпт подраздела</option>
<option value="vertical">Промпт вертикали</option>
</select>
<button id="prompt-reset" class="secondary">Сбросить уровень</button>
<button id="prompt-save">Сохранить</button>
</div>
<textarea id="prompt-editor" rows="22"
style="width:100%; font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:12px"></textarea>
<div class="muted" style="font-size:12px; margin-top:8px">
Каскад: <strong>section → vertical → default</strong>. Если промпта на
уровне подраздела нет, используется промпт вертикали; если и его нет —
встроенный по умолчанию. Сохранение применится в течение ~5 сек.
</div>
</div>
</main>
<script type="module" src="/api/monitoring-tg/static/js/nav.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/nav-status.js"></script>
<script type="module" src="/api/monitoring-tg/static/js/settings.js"></script>
</body>
</html>