104 lines
3.8 KiB
HTML
104 lines
3.8 KiB
HTML
{% extends "layout.html" %}
|
|
{% block content %}
|
|
<section class="mx-auto max-w-3xl p-6">
|
|
<h1 class="text-3xl font-bold mb-2">RSS Link Audit</h1>
|
|
<p class="mb-6 opacity-90">Paste a feed URL. This version uses <strong>SQLite/SQLModel caching</strong> and streams progress over <strong>SSE</strong>.</p>
|
|
|
|
<form id="feed-form" class="space-y-4 bg-[var(--ra-panel)] p-5 rounded-2xl shadow">
|
|
<label class="block">
|
|
<span class="block mb-2 font-semibold">Feed URL</span>
|
|
<input id="feed-input" type="url" name="feed_url" placeholder="https://example.com/feed.xml"
|
|
required
|
|
class="w-full p-3 rounded-xl bg-[var(--ra-ink)] text-[var(--ra-cream)] border border-[var(--ra-copper)] focus:outline-none focus:ring-2 focus:ring-[var(--ra-amber)]" />
|
|
</label>
|
|
<button class="px-4 py-2 rounded-xl font-semibold bg-[var(--ra-ruby)] hover:bg-[var(--ra-ruby-dark)]">
|
|
Analyze
|
|
</button>
|
|
</form>
|
|
|
|
<div id="status" class="mt-6 text-sm opacity-80"></div>
|
|
|
|
<section id="summary" class="mt-6"></section>
|
|
<section id="hosts" class="mt-4 space-y-6"></section>
|
|
</section>
|
|
|
|
<script>
|
|
const statusEl = document.getElementById('status');
|
|
const hostsEl = document.getElementById('hosts');
|
|
const summaryEl = document.getElementById('summary');
|
|
const form = document.getElementById('feed-form');
|
|
|
|
function setStatus(html) { statusEl.innerHTML = html; }
|
|
function appendHostCard(html) {
|
|
const div = document.createElement('div');
|
|
div.innerHTML = html;
|
|
hostsEl.appendChild(div.firstElementChild);
|
|
}
|
|
function setSummary(feed_url, post_count, host_count) {
|
|
summaryEl.innerHTML = `
|
|
<div class="rounded-2xl bg-[var(--ra-panel)] border border-[var(--ra-copper)] p-4">
|
|
<div class="font-semibold mb-1">Summary</div>
|
|
<div>Feed: <a class="underline" href="${feed_url}" target="_blank" rel="noopener">${feed_url}</a></div>
|
|
<div>Posts parsed: <strong>${post_count}</strong></div>
|
|
<div>Hosts found: <strong>${host_count}</strong></div>
|
|
</div>`;
|
|
}
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
hostsEl.innerHTML = '';
|
|
summaryEl.innerHTML = '';
|
|
setStatus('Starting…');
|
|
|
|
const fd = new FormData(form);
|
|
const resp = await fetch('/start', { method: 'POST', body: fd });
|
|
if (!resp.ok) {
|
|
setStatus('Failed to start.');
|
|
return;
|
|
}
|
|
const { job_id } = await resp.json();
|
|
setStatus('Job started. Connecting…');
|
|
|
|
const es = new EventSource(`/events/${job_id}`);
|
|
let postCount = 0, hostsCount = 0, seenCards = 0;
|
|
|
|
es.addEventListener('hello', () => setStatus('Connected. Parsing feed…'));
|
|
es.addEventListener('status', (ev) => {
|
|
const d = JSON.parse(ev.data).data;
|
|
setStatus(`${d.message}`);
|
|
});
|
|
es.addEventListener('posts', (ev) => {
|
|
const data = JSON.parse(ev.data).data;
|
|
postCount = data.count || 0;
|
|
setStatus(`Posts: ${postCount}. Fetching pages…`);
|
|
});
|
|
es.addEventListener('post_progress', (ev) => {
|
|
const d = JSON.parse(ev.data).data;
|
|
setStatus(`Fetching posts ${d.current}/${d.total}…`);
|
|
});
|
|
es.addEventListener('hosts', (ev) => {
|
|
const data = JSON.parse(ev.data).data;
|
|
hostsCount = data.count || 0;
|
|
setStatus(`Found ${hostsCount} hosts. Discovering their feeds…`);
|
|
});
|
|
es.addEventListener('host_card', (ev) => {
|
|
const data = JSON.parse(ev.data).data;
|
|
appendHostCard(data.html);
|
|
seenCards = data.index;
|
|
setStatus(`Rendered ${seenCards}/${data.total} hosts… Still discovering feeds…`);
|
|
});
|
|
es.addEventListener('summary', (ev) => {
|
|
const data = JSON.parse(ev.data).data;
|
|
setSummary(data.feed_url, postCount, hostsCount);
|
|
});
|
|
es.addEventListener('error', (ev) => {
|
|
const data = JSON.parse(ev.data).data;
|
|
setStatus('Error: ' + (data.message || 'Unknown'));
|
|
});
|
|
es.addEventListener('done', () => {
|
|
setStatus('Done.');
|
|
es.close();
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|