rss-link-app/templates/index.html
2025-09-03 20:22:39 -05:00

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 %}