Add lightweight web control panel
This commit is contained in:
+177
@@ -0,0 +1,177 @@
|
||||
const state = {
|
||||
dashboard: null,
|
||||
};
|
||||
|
||||
async function api(path, options = {}) {
|
||||
const response = await fetch(path, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
...options,
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || "Request failed");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function formatDate(value) {
|
||||
if (!value) return "—";
|
||||
return value.replace("T", " ").replace("+00:00", " UTC");
|
||||
}
|
||||
|
||||
function setBusy(button, busy) {
|
||||
if (!button) return;
|
||||
button.disabled = busy;
|
||||
}
|
||||
|
||||
function renderAuth(auth) {
|
||||
document.getElementById("auth-status").textContent = auth.status;
|
||||
document.getElementById("auth-details").textContent = auth.details || "";
|
||||
}
|
||||
|
||||
function renderSummary(data) {
|
||||
document.getElementById("channel-count").textContent = String(data.state.channel_count);
|
||||
document.getElementById("forwarding-count").textContent = String(data.state.forwarding_rules.length);
|
||||
const toggle = document.getElementById("scrape-media-toggle");
|
||||
toggle.checked = Boolean(data.state.scrape_media);
|
||||
document.getElementById("scrape-media-label").textContent = toggle.checked ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
function renderChannels(channels) {
|
||||
const tbody = document.getElementById("channels-table");
|
||||
tbody.innerHTML = "";
|
||||
const template = document.getElementById("channel-row-template");
|
||||
|
||||
channels.forEach((channel) => {
|
||||
const node = template.content.firstElementChild.cloneNode(true);
|
||||
node.querySelector(".channel-name").textContent = channel.name;
|
||||
node.querySelector(".channel-id").textContent = channel.channel_id;
|
||||
node.querySelector(".message-count").textContent = String(channel.message_count);
|
||||
node.querySelector(".media-count").textContent = String(channel.media_count);
|
||||
node.querySelector(".last-date").textContent = channel.last_date || "—";
|
||||
|
||||
node.querySelector(".scrape-btn").addEventListener("click", async () => {
|
||||
await api("/api/jobs/scrape", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ channel_id: channel.channel_id }),
|
||||
});
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
node.querySelector(".export-view-btn").addEventListener("click", () => {
|
||||
window.location.href = `/viewer?channel=${encodeURIComponent(channel.channel_id)}`;
|
||||
});
|
||||
|
||||
node.querySelector(".media-btn").addEventListener("click", async () => {
|
||||
await api("/api/jobs/rescrape-media", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ channel_id: channel.channel_id }),
|
||||
});
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
node.querySelector(".remove-btn").addEventListener("click", async () => {
|
||||
await api("/api/channels/remove", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ channel_id: channel.channel_id }),
|
||||
});
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
tbody.appendChild(node);
|
||||
});
|
||||
}
|
||||
|
||||
function renderJobs(jobs) {
|
||||
const root = document.getElementById("jobs-list");
|
||||
root.innerHTML = "";
|
||||
const template = document.getElementById("job-template");
|
||||
|
||||
if (!jobs.length) {
|
||||
root.innerHTML = '<div class="muted">Пока задач нет.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
jobs.forEach((job) => {
|
||||
const node = template.content.firstElementChild.cloneNode(true);
|
||||
node.querySelector(".job-title").textContent = job.title;
|
||||
node.querySelector(".job-status").textContent = job.status;
|
||||
node.querySelector(".job-time").textContent = [job.started_at, job.finished_at].filter(Boolean).join(" -> ");
|
||||
node.querySelector(".job-logs").textContent = job.logs || job.error || "Без логов";
|
||||
root.appendChild(node);
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshDashboard() {
|
||||
const data = await api("/api/dashboard");
|
||||
state.dashboard = data;
|
||||
renderAuth(data.auth);
|
||||
renderSummary(data);
|
||||
renderChannels(data.channels);
|
||||
renderJobs(data.jobs);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
document.getElementById("scrape-all-btn").addEventListener("click", async (event) => {
|
||||
setBusy(event.currentTarget, true);
|
||||
try {
|
||||
await api("/api/jobs/scrape", { method: "POST", body: JSON.stringify({}) });
|
||||
await refreshDashboard();
|
||||
} finally {
|
||||
setBusy(event.currentTarget, false);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("export-all-btn").addEventListener("click", async (event) => {
|
||||
setBusy(event.currentTarget, true);
|
||||
try {
|
||||
await api("/api/jobs/export", { method: "POST", body: JSON.stringify({}) });
|
||||
await refreshDashboard();
|
||||
} finally {
|
||||
setBusy(event.currentTarget, false);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("refresh-dialogs-btn").addEventListener("click", async (event) => {
|
||||
setBusy(event.currentTarget, true);
|
||||
try {
|
||||
await api("/api/jobs/refresh-dialogs", { method: "POST", body: JSON.stringify({}) });
|
||||
await refreshDashboard();
|
||||
} finally {
|
||||
setBusy(event.currentTarget, false);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("refresh-jobs-btn").addEventListener("click", refreshDashboard);
|
||||
|
||||
document.getElementById("scrape-media-toggle").addEventListener("change", async (event) => {
|
||||
const checked = event.currentTarget.checked;
|
||||
document.getElementById("scrape-media-label").textContent = checked ? "ON" : "OFF";
|
||||
await api("/api/settings/media", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ value: checked }),
|
||||
});
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
document.getElementById("add-channel-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const channelId = document.getElementById("add-channel-id").value.trim();
|
||||
const name = document.getElementById("add-channel-name").value.trim();
|
||||
if (!channelId) return;
|
||||
await api("/api/channels/add", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ channel_id: channelId, name }),
|
||||
});
|
||||
event.currentTarget.reset();
|
||||
await refreshDashboard();
|
||||
});
|
||||
|
||||
await refreshDashboard();
|
||||
window.setInterval(refreshDashboard, 5000);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
alert(error.message);
|
||||
});
|
||||
Reference in New Issue
Block a user