241 lines
7.3 KiB
HTML
Raw Normal View History

2025-10-02 21:17:36 +02:00
{% extends "base.html" %}
{% block title %}Sync Feeds - RSS Feed Manager{% endblock %}
{% block extra_css %}
<style>
.sync-container {
max-width: 900px;
margin: 0 auto;
}
.sync-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.stat-box {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
text-align: center;
}
.stat-value {
font-size: 32px;
font-weight: bold;
color: #1a73e8;
display: block;
margin-bottom: 5px;
}
.stat-label {
font-size: 14px;
color: #5f6368;
}
.log-container {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
height: 400px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
margin-bottom: 20px;
}
.log-entry {
padding: 5px 0;
border-bottom: 1px solid #e8eaed;
}
.log-entry:last-child {
border-bottom: none;
}
.log-time {
color: #5f6368;
margin-right: 10px;
}
.log-fetching {
color: #1a73e8;
}
.log-parsed {
color: #1e8e3e;
}
.log-error {
color: #d93025;
}
.log-complete {
color: #1e8e3e;
font-weight: bold;
}
.progress-bar {
background-color: #e8eaed;
border-radius: 4px;
height: 8px;
overflow: hidden;
margin-bottom: 30px;
}
.progress-fill {
background-color: #1a73e8;
height: 100%;
width: 0%;
transition: width 0.3s ease;
}
#syncButton:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
{% endblock %}
{% block content %}
<div class="sync-container">
<h1 style="margin-bottom: 20px; font-size: 32px; font-weight: normal;">Synchronize RSS Feeds</h1>
<div class="stats" style="background-color: #f8f9fa; border-radius: 8px; padding: 16px; margin-bottom: 20px; font-size: 14px; color: #5f6368;">
Total Feeds: <strong>{{ total_feeds }}</strong> | Total Articles in DB: <strong>{{ total_articles }}</strong>
</div>
<div style="margin-bottom: 30px;">
<button id="syncButton" class="btn btn-primary">Start Synchronization</button>
<a href="/manage" class="btn">Back to Manage</a>
</div>
<div class="progress-bar" id="progressBar" style="display: none;">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="sync-stats" id="statsContainer" style="display: none;">
<div class="stat-box">
<span class="stat-value" id="statCompleted">0</span>
<span class="stat-label">Completed</span>
</div>
<div class="stat-box">
<span class="stat-value" id="statTotal">0</span>
<span class="stat-label">Total Feeds</span>
</div>
<div class="stat-box">
<span class="stat-value" id="statArticles">0</span>
<span class="stat-label">Articles Synced</span>
</div>
<div class="stat-box">
<span class="stat-value" id="statReqPerSec">0</span>
<span class="stat-label">Req/s</span>
</div>
</div>
<div class="log-container" id="logContainer" style="display: none;"></div>
</div>
{% endblock %}
{% block extra_js %}
<script>
const syncButton = document.getElementById('syncButton');
const logContainer = document.getElementById('logContainer');
const statsContainer = document.getElementById('statsContainer');
const progressBar = document.getElementById('progressBar');
const progressFill = document.getElementById('progressFill');
const statCompleted = document.getElementById('statCompleted');
const statTotal = document.getElementById('statTotal');
const statArticles = document.getElementById('statArticles');
const statReqPerSec = document.getElementById('statReqPerSec');
function addLog(message, type = 'info') {
const entry = document.createElement('div');
entry.className = `log-entry log-${type}`;
const time = new Date().toLocaleTimeString();
entry.innerHTML = `<span class="log-time">[${time}]</span> ${message}`;
logContainer.appendChild(entry);
logContainer.scrollTop = logContainer.scrollHeight;
}
function updateStats(completed, total, articles, reqPerSec) {
statCompleted.textContent = completed;
statTotal.textContent = total;
statArticles.textContent = articles;
statReqPerSec.textContent = reqPerSec;
const percentage = (completed / total) * 100;
progressFill.style.width = percentage + '%';
}
syncButton.addEventListener('click', () => {
syncButton.disabled = true;
logContainer.style.display = 'block';
statsContainer.style.display = 'grid';
progressBar.style.display = 'block';
logContainer.innerHTML = '';
progressFill.style.width = '0%';
const ws = new WebSocket('ws://127.0.0.1:8592/ws/sync');
ws.onopen = () => {
addLog('WebSocket connection established', 'info');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch(data.type) {
case 'start':
addLog(data.message, 'info');
updateStats(0, data.total, 0, 0);
break;
case 'fetching':
addLog(`Fetching: ${data.feed} (${data.url})`, 'fetching');
updateStats(data.completed, data.total, statArticles.textContent, data.req_per_sec);
break;
case 'parsed':
addLog(`✓ Parsed: ${data.feed} - ${data.articles} articles added`, 'parsed');
updateStats(data.completed, data.total, data.total_articles, data.req_per_sec);
break;
case 'timeout':
addLog(data.message, 'info');
updateStats(data.completed, data.total, statArticles.textContent, statReqPerSec.textContent);
break;
case 'error':
addLog(`✗ Error: ${data.feed} - ${data.error}`, 'error');
if (data.completed) {
updateStats(data.completed, data.total, statArticles.textContent, data.req_per_sec);
}
break;
case 'complete':
addLog(`✓ Synchronization complete! ${data.total_feeds} feeds processed, ${data.total_articles} articles synced in ${data.elapsed}s (avg ${data.avg_req_per_sec} req/s)`, 'complete');
syncButton.disabled = false;
break;
}
};
ws.onerror = (error) => {
addLog('WebSocket error occurred', 'error');
syncButton.disabled = false;
};
ws.onclose = () => {
addLog('WebSocket connection closed', 'info');
syncButton.disabled = false;
};
});
</script>
{% endblock %}