Add web auth and continuous scraping logs
This commit is contained in:
1 parent
19e173212c
commit
db3641ddf9
7 files changed
+640
-10
No files matched your search
+121
-1
@@ -1,5 +1,7 @@
|
||||
const state = {
|
||||
dashboard: null,
|
||||
auth: null,
|
||||
continuous: null,
|
||||
};
|
||||
|
||||
async function api(path, options = {}) {
|
||||
@@ -29,6 +31,30 @@ function renderAuth(auth) {
|
||||
document.getElementById("auth-details").textContent = auth.details || "";
|
||||
}
|
||||
|
||||
function toggleHidden(id, hidden) {
|
||||
document.getElementById(id).classList.toggle("hidden", hidden);
|
||||
}
|
||||
|
||||
function renderAuthPanel(authData) {
|
||||
state.auth = authData;
|
||||
renderAuth(authData.auth_status);
|
||||
|
||||
const apiId = authData.saved_credentials?.api_id ?? "";
|
||||
document.getElementById("api-id-input").value = apiId || "";
|
||||
document.getElementById("api-hash-input").placeholder = authData.saved_credentials?.api_hash_present
|
||||
? "API Hash сохранён"
|
||||
: "API Hash";
|
||||
|
||||
const showQr = Boolean(authData.qr_image);
|
||||
toggleHidden("qr-wrap", !showQr);
|
||||
if (showQr) {
|
||||
document.getElementById("qr-image").src = authData.qr_image;
|
||||
}
|
||||
|
||||
toggleHidden("code-form", authData.phase !== "code_required");
|
||||
toggleHidden("password-form", authData.phase !== "password_required");
|
||||
}
|
||||
|
||||
function renderSummary(data) {
|
||||
document.getElementById("channel-count").textContent = String(data.state.channel_count);
|
||||
document.getElementById("forwarding-count").textContent = String(data.state.forwarding_rules.length);
|
||||
@@ -102,13 +128,32 @@ function renderJobs(jobs) {
|
||||
});
|
||||
}
|
||||
|
||||
function renderContinuous(data) {
|
||||
state.continuous = data;
|
||||
const config = data.config || {};
|
||||
const status = data.status || {};
|
||||
|
||||
document.getElementById("continuous-enabled").checked = Boolean(config.enabled);
|
||||
document.getElementById("continuous-interval").value = config.interval_minutes || 1;
|
||||
document.getElementById("continuous-all").checked = Boolean(config.run_all_tracked);
|
||||
document.getElementById("continuous-channels").value = (config.channels || []).join(", ");
|
||||
|
||||
document.getElementById("continuous-status").textContent = status.running ? "running" : "stopped";
|
||||
document.getElementById("continuous-last-iteration").textContent = status.last_iteration_at || "—";
|
||||
document.getElementById("continuous-last-error").textContent = status.last_error || "—";
|
||||
document.getElementById("continuous-logs").textContent = (status.logs || []).join("\n") || "Логов пока нет.";
|
||||
}
|
||||
|
||||
async function refreshDashboard() {
|
||||
const data = await api("/api/dashboard");
|
||||
const authData = await api("/api/auth");
|
||||
const continuousData = await api("/api/continuous");
|
||||
state.dashboard = data;
|
||||
renderAuth(data.auth);
|
||||
renderAuthPanel(authData);
|
||||
renderSummary(data);
|
||||
renderChannels(data.channels);
|
||||
renderJobs(data.jobs);
|
||||
renderContinuous(continuousData);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
@@ -167,6 +212,81 @@ async function main() {
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
document.getElementById("credentials-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const apiId = document.getElementById("api-id-input").value.trim();
|
||||
const apiHash = document.getElementById("api-hash-input").value.trim();
|
||||
await api("/api/auth/credentials", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ api_id: apiId, api_hash: apiHash }),
|
||||
});
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
document.getElementById("start-qr-btn").addEventListener("click", async (event) => {
|
||||
setBusy(event.currentTarget, true);
|
||||
try {
|
||||
await api("/api/auth/qr/start", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
await refreshDashboard();
|
||||
} finally {
|
||||
setBusy(event.currentTarget, false);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("phone-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const phone = document.getElementById("phone-input").value.trim();
|
||||
await api("/api/auth/phone/request", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ phone }),
|
||||
});
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
document.getElementById("code-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const code = document.getElementById("code-input").value.trim();
|
||||
await api("/api/auth/phone/submit", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ code }),
|
||||
});
|
||||
event.currentTarget.reset();
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
document.getElementById("password-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const password = document.getElementById("password-input").value.trim();
|
||||
await api("/api/auth/password", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ password }),
|
||||
});
|
||||
event.currentTarget.reset();
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
document.getElementById("continuous-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const enabled = document.getElementById("continuous-enabled").checked;
|
||||
const intervalMinutes = document.getElementById("continuous-interval").value;
|
||||
const runAllTracked = document.getElementById("continuous-all").checked;
|
||||
const channelsRaw = document.getElementById("continuous-channels").value.trim();
|
||||
const channels = channelsRaw ? channelsRaw.split(",").map((item) => item.trim()).filter(Boolean) : [];
|
||||
await api("/api/continuous", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
enabled,
|
||||
interval_minutes: intervalMinutes,
|
||||
run_all_tracked: runAllTracked,
|
||||
channels,
|
||||
}),
|
||||
});
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
await refreshDashboard();
|
||||
window.setInterval(refreshDashboard, 5000);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,39 @@
|
||||
<p id="auth-details" class="muted small"></p>
|
||||
</section>
|
||||
|
||||
<section class="status-card">
|
||||
<div class="section-title">Telegram Login</div>
|
||||
<form id="credentials-form" class="stack-form">
|
||||
<input id="api-id-input" name="api_id" placeholder="API ID" />
|
||||
<input id="api-hash-input" name="api_hash" placeholder="API Hash" />
|
||||
<button class="button" type="submit">Сохранить креды</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-actions">
|
||||
<button class="button primary" id="start-qr-btn">Логин по QR</button>
|
||||
</div>
|
||||
|
||||
<div id="qr-wrap" class="qr-wrap hidden">
|
||||
<img id="qr-image" alt="Telegram QR login" />
|
||||
<p class="muted small">Открой Telegram -> Settings -> Devices -> Scan QR</p>
|
||||
</div>
|
||||
|
||||
<form id="phone-form" class="stack-form">
|
||||
<input id="phone-input" name="phone" placeholder="+1234567890" />
|
||||
<button class="button" type="submit">Отправить код</button>
|
||||
</form>
|
||||
|
||||
<form id="code-form" class="stack-form hidden">
|
||||
<input id="code-input" name="code" placeholder="Код из Telegram" />
|
||||
<button class="button" type="submit">Подтвердить код</button>
|
||||
</form>
|
||||
|
||||
<form id="password-form" class="stack-form hidden">
|
||||
<input id="password-input" name="password" type="password" placeholder="2FA password" />
|
||||
<button class="button" type="submit">Подтвердить пароль</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="status-card">
|
||||
<div class="section-title">Быстрые действия</div>
|
||||
<button class="button primary" id="scrape-all-btn">Скрапить все каналы</button>
|
||||
@@ -92,6 +125,43 @@
|
||||
</div>
|
||||
<div id="jobs-list" class="jobs-list"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-header">
|
||||
<div>
|
||||
<h2>Continuous Scraping</h2>
|
||||
<p class="muted">Периодически перескрапливает каналы и пишет лог прямо в панель.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="continuous-form" class="continuous-form">
|
||||
<label class="toggle-row">
|
||||
<span>Включить</span>
|
||||
<input id="continuous-enabled" type="checkbox" />
|
||||
</label>
|
||||
<label class="stack-form">
|
||||
<span class="muted small">Интервал, минут</span>
|
||||
<input id="continuous-interval" type="number" min="1" value="1" />
|
||||
</label>
|
||||
<label class="toggle-row">
|
||||
<span>Все отслеживаемые каналы</span>
|
||||
<input id="continuous-all" type="checkbox" checked />
|
||||
</label>
|
||||
<label class="stack-form">
|
||||
<span class="muted small">Если выключить опцию выше, сюда можно вписать ID каналов через запятую</span>
|
||||
<input id="continuous-channels" placeholder="-100123, 5926277437" />
|
||||
</label>
|
||||
<button class="button primary" type="submit">Сохранить continuous scraping</button>
|
||||
</form>
|
||||
|
||||
<div class="continuous-meta">
|
||||
<div class="muted small">Статус: <span id="continuous-status">—</span></div>
|
||||
<div class="muted small">Последний цикл: <span id="continuous-last-iteration">—</span></div>
|
||||
<div class="muted small">Последняя ошибка: <span id="continuous-last-error">—</span></div>
|
||||
</div>
|
||||
|
||||
<pre id="continuous-logs" class="job-logs"></pre>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -186,6 +186,38 @@ button {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stack-form {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.auth-actions {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.qr-wrap {
|
||||
margin-top: 14px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #0d141c;
|
||||
}
|
||||
|
||||
.qr-wrap img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 240px;
|
||||
aspect-ratio: 1;
|
||||
margin: 0 auto 10px;
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
input {
|
||||
min-width: 160px;
|
||||
padding: 10px 12px;
|
||||
@@ -257,6 +289,17 @@ tbody tr:hover {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.continuous-form {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.continuous-meta {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.viewer-shell {
|
||||
grid-template-columns: 320px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user