Bump version to v1.6.3
This commit is contained in:
parent
a02f825274
commit
0bb7d246c1
9 changed files with 1758 additions and 221 deletions
|
|
@ -2,8 +2,13 @@
|
|||
|
||||
import streamlit as st
|
||||
from main import load_feeds, save_feeds, load_articles
|
||||
from utils.css_loader import load_css, apply_dark_theme
|
||||
import logging
|
||||
|
||||
# === CSS & Theme laden ===
|
||||
load_css()
|
||||
apply_dark_theme()
|
||||
|
||||
# === Logging vorbereiten ===
|
||||
log_dir = "logs"
|
||||
log_file = f"{log_dir}/rss_tool.log"
|
||||
|
|
@ -15,17 +20,29 @@ logging.basicConfig(
|
|||
|
||||
st.set_page_config(page_title="📡 Feed-Verwaltung")
|
||||
|
||||
st.title("📡 RSS Feed-Verwaltung")
|
||||
# Header
|
||||
st.markdown("""
|
||||
<div class="main-header">
|
||||
<h1>📡 RSS Feed-Verwaltung</h1>
|
||||
<p>Verwalte deine RSS-Feeds zentral und effizient</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
feeds = load_feeds()
|
||||
articles = load_articles()
|
||||
|
||||
# === Neuen Feed hinzufügen ===
|
||||
st.markdown('<div class="filter-section">', unsafe_allow_html=True)
|
||||
st.subheader("➕ Neuen Feed hinzufügen")
|
||||
|
||||
with st.form("add_feed_form"):
|
||||
new_url = st.text_input("Feed URL", "")
|
||||
new_name = st.text_input("Feed Name", "")
|
||||
submitted = st.form_submit_button("Feed hinzufügen")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
new_url = st.text_input("Feed URL", "", placeholder="https://example.com/feed.xml")
|
||||
with col2:
|
||||
new_name = st.text_input("Feed Name", "", placeholder="Beispiel News")
|
||||
|
||||
submitted = st.form_submit_button("Feed hinzufügen", use_container_width=True)
|
||||
if submitted:
|
||||
if new_url and new_name:
|
||||
if not any(f.get("url") == new_url for f in feeds):
|
||||
|
|
@ -39,33 +56,185 @@ with st.form("add_feed_form"):
|
|||
else:
|
||||
st.error("❌ Bitte gib sowohl URL als auch Name ein.")
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# === Bestehende Feeds bearbeiten ===
|
||||
st.subheader("🛠️ Vorhandene Feeds bearbeiten oder löschen")
|
||||
st.subheader("🛠️ Vorhandene Feeds verwalten")
|
||||
|
||||
for idx, feed in enumerate(feeds):
|
||||
with st.expander(f"🔗 {feed.get('name')}"):
|
||||
url = st.text_input(f"Feed-URL {idx}", value=feed.get("url"), key=f"url_{idx}")
|
||||
name = st.text_input(f"Feed-Name {idx}", value=feed.get("name"), key=f"name_{idx}")
|
||||
count = sum(1 for a in articles if a.get("source") == feed.get("url"))
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
if not feeds:
|
||||
st.info("Noch keine Feeds konfiguriert. Füge oben deinen ersten Feed hinzu!")
|
||||
else:
|
||||
for idx, feed in enumerate(feeds):
|
||||
feed_url = feed.get("url", "")
|
||||
feed_name = feed.get("name", "Unbekannt")
|
||||
article_count = sum(1 for a in articles if a.get("source") == feed_url)
|
||||
|
||||
# Feed Card
|
||||
st.markdown(f"""
|
||||
<div class="article-card">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
||||
<div>
|
||||
<h3 class="article-title">{feed_name}</h3>
|
||||
<div class="article-meta">{feed_url}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="status-badge status-online">{article_count} Artikel</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="article-footer">
|
||||
📰 Verknüpfte Artikel: {article_count}
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Actions
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
if st.button("💾 Änderungen speichern", key=f"save_{idx}"):
|
||||
old_url, old_name = feed.get("url"), feed.get("name")
|
||||
feeds[idx]["url"] = url
|
||||
feeds[idx]["name"] = name
|
||||
save_feeds(feeds)
|
||||
logging.info(f"✏️ Feed geändert: '{old_name}' ({old_url}) → '{name}' ({url})")
|
||||
st.success("Änderungen gespeichert.")
|
||||
st.rerun()
|
||||
if st.button("💾 Bearbeiten", key=f"edit_{idx}", use_container_width=True):
|
||||
st.session_state[f"edit_mode_{idx}"] = not st.session_state.get(f"edit_mode_{idx}", False)
|
||||
|
||||
with col2:
|
||||
if st.button("🗑️ Feed löschen", key=f"delete_{idx}"):
|
||||
deleted_feed = feeds.pop(idx)
|
||||
save_feeds(feeds)
|
||||
logging.info(f"❌ Feed gelöscht: {deleted_feed.get('name')} ({deleted_feed.get('url')})")
|
||||
st.warning("Feed gelöscht.")
|
||||
st.rerun()
|
||||
if st.button("🔄 Aktualisieren", key=f"refresh_{idx}", use_container_width=True):
|
||||
with st.spinner(f"Aktualisiere Feed '{feed_name}'..."):
|
||||
# Hier könntest du eine einzelne Feed-Update-Funktion implementieren
|
||||
from main import process_articles
|
||||
existing_ids = [a["id"] for a in articles]
|
||||
process_articles(existing_ids)
|
||||
st.success(f"Feed '{feed_name}' aktualisiert!")
|
||||
st.rerun()
|
||||
|
||||
st.caption(f"📰 Verknüpfte Artikel: {count}")
|
||||
with col3:
|
||||
if st.button("🗑️ Löschen", key=f"delete_{idx}", use_container_width=True):
|
||||
# Bestätigung über Session State
|
||||
if not st.session_state.get(f"confirm_delete_{idx}", False):
|
||||
st.session_state[f"confirm_delete_{idx}"] = True
|
||||
st.warning(f"Klicke erneut um '{feed_name}' wirklich zu löschen!")
|
||||
else:
|
||||
deleted_feed = feeds.pop(idx)
|
||||
save_feeds(feeds)
|
||||
logging.info(f"❌ Feed gelöscht: {deleted_feed.get('name')} ({deleted_feed.get('url')})")
|
||||
st.success(f"Feed '{feed_name}' wurde gelöscht.")
|
||||
# Cleanup Session State
|
||||
if f"confirm_delete_{idx}" in st.session_state:
|
||||
del st.session_state[f"confirm_delete_{idx}"]
|
||||
st.rerun()
|
||||
|
||||
# Edit Form (wenn aktiviert)
|
||||
if st.session_state.get(f"edit_mode_{idx}", False):
|
||||
st.markdown('<div class="filter-section" style="margin-top: 1rem;">', unsafe_allow_html=True)
|
||||
st.write("**Feed bearbeiten:**")
|
||||
|
||||
with st.form(f"edit_form_{idx}"):
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
edited_url = st.text_input("Feed-URL", value=feed_url, key=f"edit_url_{idx}")
|
||||
with col2:
|
||||
edited_name = st.text_input("Feed-Name", value=feed_name, key=f"edit_name_{idx}")
|
||||
|
||||
form_col1, form_col2 = st.columns(2)
|
||||
with form_col1:
|
||||
if st.form_submit_button("💾 Änderungen speichern", use_container_width=True):
|
||||
old_url, old_name = feed.get("url"), feed.get("name")
|
||||
feeds[idx]["url"] = edited_url
|
||||
feeds[idx]["name"] = edited_name
|
||||
save_feeds(feeds)
|
||||
logging.info(f"✏️ Feed geändert: '{old_name}' ({old_url}) → '{edited_name}' ({edited_url})")
|
||||
st.success("Änderungen gespeichert!")
|
||||
st.session_state[f"edit_mode_{idx}"] = False
|
||||
st.rerun()
|
||||
|
||||
with form_col2:
|
||||
if st.form_submit_button("❌ Abbrechen", use_container_width=True):
|
||||
st.session_state[f"edit_mode_{idx}"] = False
|
||||
st.rerun()
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# === Feed-Statistiken ===
|
||||
if feeds:
|
||||
st.markdown("---")
|
||||
st.subheader("📊 Feed-Statistiken")
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{}</div>
|
||||
<div>Feeds Gesamt</div>
|
||||
</div>
|
||||
""".format(len(feeds)), unsafe_allow_html=True)
|
||||
|
||||
with col2:
|
||||
total_articles = len(articles)
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{}</div>
|
||||
<div>Artikel Gesamt</div>
|
||||
</div>
|
||||
""".format(total_articles), unsafe_allow_html=True)
|
||||
|
||||
with col3:
|
||||
avg_articles = total_articles // len(feeds) if feeds else 0
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{}</div>
|
||||
<div>Ø Artikel pro Feed</div>
|
||||
</div>
|
||||
""".format(avg_articles), unsafe_allow_html=True)
|
||||
|
||||
# === Bulk Actions ===
|
||||
if feeds:
|
||||
st.markdown("---")
|
||||
st.subheader("⚡ Bulk-Aktionen")
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
if st.button("🔄 Alle Feeds aktualisieren", use_container_width=True):
|
||||
with st.spinner("Aktualisiere alle Feeds..."):
|
||||
from main import process_articles
|
||||
existing_ids = [a["id"] for a in articles]
|
||||
process_articles(existing_ids)
|
||||
st.success(f"Alle {len(feeds)} Feeds wurden aktualisiert!")
|
||||
st.rerun()
|
||||
|
||||
with col2:
|
||||
if st.button("📊 Feed-Performance anzeigen", use_container_width=True):
|
||||
st.subheader("📈 Feed-Performance")
|
||||
|
||||
# Performance-Daten sammeln
|
||||
feed_performance = []
|
||||
for feed in feeds:
|
||||
feed_url = feed.get("url", "")
|
||||
feed_name = feed.get("name", "Unbekannt")
|
||||
feed_articles = [a for a in articles if a.get("source") == feed_url]
|
||||
|
||||
performance = {
|
||||
"name": feed_name,
|
||||
"url": feed_url,
|
||||
"total_articles": len(feed_articles),
|
||||
"new_articles": len([a for a in feed_articles if a.get("status") == "New"]),
|
||||
"processed_articles": len([a for a in feed_articles if a.get("status") in ["Process", "Online", "WordPress Pending"]])
|
||||
}
|
||||
feed_performance.append(performance)
|
||||
|
||||
# Sortiere nach Artikel-Anzahl
|
||||
feed_performance.sort(key=lambda x: x["total_articles"], reverse=True)
|
||||
|
||||
# Anzeige als Cards
|
||||
for perf in feed_performance:
|
||||
success_rate = (perf["processed_articles"] / perf["total_articles"] * 100) if perf["total_articles"] > 0 else 0
|
||||
|
||||
st.markdown(f"""
|
||||
<div class="article-card">
|
||||
<h3 class="article-title">{perf["name"]}</h3>
|
||||
<div class="article-footer">
|
||||
📰 {perf["total_articles"]} Artikel |
|
||||
🆕 {perf["new_articles"]} Neu |
|
||||
✅ {perf["processed_articles"]} Verarbeitet |
|
||||
📊 {success_rate:.1f}% Success Rate
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
|
@ -1,23 +1,297 @@
|
|||
# log_viewer.py
|
||||
# pages/log_viewer.py
|
||||
|
||||
import streamlit as st
|
||||
import os
|
||||
from utils.css_loader import load_css, apply_dark_theme
|
||||
from datetime import datetime
|
||||
|
||||
# === CSS & Theme laden ===
|
||||
load_css()
|
||||
apply_dark_theme()
|
||||
|
||||
st.set_page_config(page_title="🧾 Log Viewer", layout="wide")
|
||||
st.title("🧾 Letzte Logeinträge anzeigen")
|
||||
|
||||
# Header
|
||||
st.markdown("""
|
||||
<div class="main-header">
|
||||
<h1>🧾 Log Viewer</h1>
|
||||
<p>Überwache Systemaktivitäten und Debug-Informationen</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
LOG_FILE = "logs/rss_tool.log"
|
||||
MAX_LINES = 500
|
||||
|
||||
if not os.path.exists(LOG_FILE):
|
||||
st.warning("Keine Logdatei gefunden.")
|
||||
else:
|
||||
with open(LOG_FILE, "r") as f:
|
||||
lines = f.readlines()
|
||||
# === Log-Datei Kontrollen ===
|
||||
st.markdown('<div class="filter-section">', unsafe_allow_html=True)
|
||||
st.subheader("📁 Log-Datei Optionen")
|
||||
|
||||
st.write(f"Letzte {min(len(lines), MAX_LINES)} Zeilen aus `{LOG_FILE}`:")
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
|
||||
st.code("".join(lines[-MAX_LINES:]), language="text")
|
||||
with col1:
|
||||
lines_to_show = st.selectbox(
|
||||
"Anzahl Zeilen",
|
||||
[50, 100, 200, 500, 1000],
|
||||
index=3, # Default: 500
|
||||
key="lines_select"
|
||||
)
|
||||
|
||||
if st.button("🔄 Neu laden"):
|
||||
with col2:
|
||||
if st.button("🔄 Neu laden", use_container_width=True):
|
||||
st.rerun()
|
||||
|
||||
with col3:
|
||||
log_level_filter = st.selectbox(
|
||||
"Log Level Filter",
|
||||
["Alle", "INFO", "WARNING", "ERROR", "DEBUG"],
|
||||
key="level_filter"
|
||||
)
|
||||
|
||||
with col4:
|
||||
search_term = st.text_input(
|
||||
"Suche in Logs",
|
||||
placeholder="Suchbegriff...",
|
||||
key="log_search"
|
||||
)
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# === Log-Datei Status ===
|
||||
if not os.path.exists(LOG_FILE):
|
||||
st.markdown("""
|
||||
<div class="wp-status">
|
||||
<strong>⚠️ Keine Log-Datei gefunden</strong><br>
|
||||
<div class="text-secondary">
|
||||
Die Log-Datei wurde noch nicht erstellt oder befindet sich an einem anderen Ort.<br>
|
||||
Erwarteter Pfad: <code>{}</code>
|
||||
</div>
|
||||
</div>
|
||||
""".format(LOG_FILE), unsafe_allow_html=True)
|
||||
else:
|
||||
# Datei-Informationen
|
||||
file_size = os.path.getsize(LOG_FILE)
|
||||
file_mtime = datetime.fromtimestamp(os.path.getmtime(LOG_FILE))
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{:.1f} KB</div>
|
||||
<div>Dateigröße</div>
|
||||
</div>
|
||||
""".format(file_size / 1024), unsafe_allow_html=True)
|
||||
|
||||
with col2:
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number" style="font-size: 1.5rem;">{}</div>
|
||||
<div>Letzte Änderung</div>
|
||||
</div>
|
||||
""".format(file_mtime.strftime("%H:%M:%S")), unsafe_allow_html=True)
|
||||
|
||||
with col3:
|
||||
# Zeilen zählen
|
||||
try:
|
||||
with open(LOG_FILE, "r", encoding="utf-8") as f:
|
||||
total_lines = sum(1 for _ in f)
|
||||
except:
|
||||
total_lines = 0
|
||||
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{}</div>
|
||||
<div>Zeilen Gesamt</div>
|
||||
</div>
|
||||
""".format(total_lines), unsafe_allow_html=True)
|
||||
|
||||
# === Log-Inhalt anzeigen ===
|
||||
try:
|
||||
with open(LOG_FILE, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Filter anwenden
|
||||
filtered_lines = []
|
||||
|
||||
for line in lines:
|
||||
# Log Level Filter
|
||||
if log_level_filter != "Alle":
|
||||
if f" - {log_level_filter} - " not in line:
|
||||
continue
|
||||
|
||||
# Suchfilter
|
||||
if search_term and search_term.lower() not in line.lower():
|
||||
continue
|
||||
|
||||
filtered_lines.append(line)
|
||||
|
||||
# Anzahl begrenzen
|
||||
display_lines = filtered_lines[-lines_to_show:] if len(filtered_lines) > lines_to_show else filtered_lines
|
||||
|
||||
# Header für Log-Anzeige
|
||||
st.subheader(f"📋 Log-Einträge ({len(display_lines)} von {len(filtered_lines)} gefilterten Zeilen)")
|
||||
|
||||
if display_lines:
|
||||
# Log-Inhalt mit Syntax-Highlighting
|
||||
log_content = "".join(display_lines)
|
||||
|
||||
# Farbkodierung für verschiedene Log-Level
|
||||
colored_content = log_content
|
||||
colored_content = colored_content.replace(" - ERROR - ", " - 🔴 ERROR - ")
|
||||
colored_content = colored_content.replace(" - WARNING - ", " - 🟡 WARNING - ")
|
||||
colored_content = colored_content.replace(" - INFO - ", " - 🔵 INFO - ")
|
||||
colored_content = colored_content.replace(" - DEBUG - ", " - ⚪ DEBUG - ")
|
||||
|
||||
# Log in Card anzeigen
|
||||
st.markdown("""
|
||||
<div class="article-card">
|
||||
<h3 class="article-title">📄 Log-Ausgabe</h3>
|
||||
<div class="article-meta">
|
||||
Letzte {count} Einträge | Filter: {level} | Suche: "{search}"
|
||||
</div>
|
||||
</div>
|
||||
""".format(
|
||||
count=len(display_lines),
|
||||
level=log_level_filter,
|
||||
search=search_term or "Keine"
|
||||
), unsafe_allow_html=True)
|
||||
|
||||
# Code-Block mit Logs
|
||||
st.code(colored_content, language="text")
|
||||
|
||||
# Download-Button
|
||||
st.download_button(
|
||||
label="💾 Log-Datei herunterladen",
|
||||
data=log_content,
|
||||
file_name=f"rss_tool_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
|
||||
mime="text/plain",
|
||||
use_container_width=True
|
||||
)
|
||||
|
||||
else:
|
||||
st.markdown("""
|
||||
<div class="wp-status">
|
||||
<strong>🔍 Keine Log-Einträge gefunden</strong><br>
|
||||
<div class="text-secondary">
|
||||
Mit den aktuellen Filtern wurden keine Log-Einträge gefunden.<br>
|
||||
Versuche andere Filter-Einstellungen.
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
except Exception as e:
|
||||
st.markdown(f"""
|
||||
<div class="wp-status">
|
||||
<strong>❌ Fehler beim Lesen der Log-Datei</strong><br>
|
||||
<div class="text-secondary">
|
||||
{str(e)}
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# === Log-Level Erklärung ===
|
||||
with st.expander("ℹ️ Log-Level Erklärung", expanded=False):
|
||||
st.markdown("""
|
||||
<div class="article-card">
|
||||
<h3 class="article-title">📖 Log-Level Bedeutung</h3>
|
||||
<div class="article-summary">
|
||||
<strong>🔵 INFO:</strong> Normale Programmaktivitäten (Feed-Updates, Artikel verarbeitet)<br>
|
||||
<strong>🟡 WARNING:</strong> Potentielle Probleme (Duplikate, fehlende Daten)<br>
|
||||
<strong>🔴 ERROR:</strong> Fehler die das Programm beeinträchtigen<br>
|
||||
<strong>⚪ DEBUG:</strong> Detaillierte Entwickler-Informationen
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# === Log-Datei verwalten ===
|
||||
st.markdown("---")
|
||||
st.subheader("🛠️ Log-Datei Verwaltung")
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
if st.button("🗑️ Log-Datei leeren", use_container_width=True):
|
||||
if st.button("⚠️ Wirklich leeren?", key="confirm_clear"):
|
||||
try:
|
||||
with open(LOG_FILE, "w", encoding="utf-8") as f:
|
||||
f.write("")
|
||||
st.success("Log-Datei wurde geleert!")
|
||||
st.rerun()
|
||||
except Exception as e:
|
||||
st.error(f"Fehler beim Leeren der Log-Datei: {e}")
|
||||
|
||||
with col2:
|
||||
if st.button("📦 Log archivieren", use_container_width=True):
|
||||
try:
|
||||
archive_name = f"rss_tool_log_archive_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
||||
with open(LOG_FILE, "r", encoding="utf-8") as f:
|
||||
log_data = f.read()
|
||||
|
||||
st.download_button(
|
||||
label=f"💾 {archive_name}",
|
||||
data=log_data,
|
||||
file_name=archive_name,
|
||||
mime="text/plain",
|
||||
key="archive_download"
|
||||
)
|
||||
except Exception as e:
|
||||
st.error(f"Fehler beim Archivieren: {e}")
|
||||
|
||||
with col3:
|
||||
if st.button("📊 Log-Statistiken", use_container_width=True):
|
||||
if os.path.exists(LOG_FILE):
|
||||
try:
|
||||
with open(LOG_FILE, "r", encoding="utf-8") as f:
|
||||
all_lines = f.readlines()
|
||||
|
||||
# Statistiken berechnen
|
||||
total_lines = len(all_lines)
|
||||
info_count = sum(1 for line in all_lines if " - INFO - " in line)
|
||||
warning_count = sum(1 for line in all_lines if " - WARNING - " in line)
|
||||
error_count = sum(1 for line in all_lines if " - ERROR - " in line)
|
||||
debug_count = sum(1 for line in all_lines if " - DEBUG - " in line)
|
||||
|
||||
st.subheader("📈 Log-Statistiken")
|
||||
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
|
||||
with col1:
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{}</div>
|
||||
<div>🔵 INFO</div>
|
||||
</div>
|
||||
""".format(info_count), unsafe_allow_html=True)
|
||||
|
||||
with col2:
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{}</div>
|
||||
<div>🟡 WARNING</div>
|
||||
</div>
|
||||
""".format(warning_count), unsafe_allow_html=True)
|
||||
|
||||
with col3:
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{}</div>
|
||||
<div>🔴 ERROR</div>
|
||||
</div>
|
||||
""".format(error_count), unsafe_allow_html=True)
|
||||
|
||||
with col4:
|
||||
st.markdown("""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{}</div>
|
||||
<div>⚪ DEBUG</div>
|
||||
</div>
|
||||
""".format(debug_count), unsafe_allow_html=True)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Fehler beim Berechnen der Statistiken: {e}")
|
||||
|
||||
# === Auto-Refresh Option ===
|
||||
if st.checkbox("🔄 Auto-Refresh (30s)", key="auto_refresh"):
|
||||
import time
|
||||
time.sleep(30)
|
||||
st.rerun()
|
||||
Loading…
Add table
Add a link
Reference in a new issue