From acbf5cc56df9158e2fc2d46332b4b42b11780c66 Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Mon, 24 Aug 2026 18:01:19 -0400 Subject: [PATCH] Add news feed panel to dashboard: exec summary pinned + scrolling headlines + 15min auto-refresh - New News tab consuming GET /api/news (headlines: source domain badge, relative timestamp, link-out target=_blank) and GET /api/news/summaries (latest executive summary pinned in a glowing briefing card). - Four states handled: loading, empty (summarizer idle w/o GEMINI key), error, success; HTML-escaped titles/domains (newsEsc). - Lightweight markdown renderer for summary bodies (bold/headers/lists). - Auto-refresh every 15min (NEWS_REFRESH_MS) + lazy load on tab open + manual refresh button. - Stretch hook: if articles ever carry lat/lon, headline pins plot on the GIBS map (renderNewsPins) + geotag indicator in the feed. - Dark neon styling matching existing dashboard theme. --- app/static/index.html | 201 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 2 deletions(-) diff --git a/app/static/index.html b/app/static/index.html index 88589d5..442fe14 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -138,6 +138,53 @@ .cam-pop td { padding: 0.12rem 0.2rem; vertical-align: top; } .cam-pop td.k { color: var(--muted); text-transform: uppercase; font-size: 0.62rem; letter-spacing: 0.04em; white-space: nowrap; width: 34%; } .cam-pop a { color: var(--accent); word-break: break-all; } + + /* ── News panel (scraper feed + exec summaries) ─────────────── */ + .news-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem; margin-bottom: 0.9rem; flex-wrap: wrap; } + .news-refresh { display: flex; align-items: center; gap: 0.6rem; } + .news-updated { font-size: 0.72rem; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } + .news-summary { + position: relative; background: linear-gradient(160deg, rgba(56,189,248,0.10), rgba(30,41,59,0.6) 45%); + border: 1px solid rgba(56,189,248,0.35); border-radius: 12px; padding: 1rem 1.25rem 1.1rem; + box-shadow: 0 0 24px rgba(56,189,248,0.14), inset 0 0 32px rgba(56,189,248,0.05); + margin-bottom: 1.2rem; overflow: hidden; + } + .news-summary::after { + content: ''; position: absolute; inset: 0; pointer-events: none; + background: radial-gradient(120px 60px at 85% -10%, rgba(56,189,248,0.22), transparent 70%); + } + .ns-kicker { font-size: 0.72rem; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); text-shadow: 0 0 12px rgba(56,189,248,0.6); display: flex; align-items: center; gap: 0.5rem; } + .ns-badge { font-size: 0.6rem; font-weight: 700; background: rgba(56,189,248,0.16); border: 1px solid rgba(56,189,248,0.4); border-radius: 9999px; padding: 0.12rem 0.5rem; color: var(--accent); letter-spacing: 0.08em; } + .ns-time { font-size: 0.7rem; color: var(--muted); font-family: ui-monospace, monospace; margin: 0.35rem 0 0.6rem; } + .ns-body { font-size: 0.92rem; line-height: 1.62; color: var(--text); white-space: pre-wrap; position: relative; z-index: 1; } + .ns-body h1, .ns-body h2, .ns-body h3, .ns-body h4 { font-size: 1rem; margin: 0.5rem 0 0.25rem; color: var(--accent); } + .ns-body ul, .ns-body ol { margin: 0.35rem 0 0.35rem 1.2rem; } + .ns-body li { margin-bottom: 0.2rem; } + .ns-body code { background: rgba(15,23,42,0.6); border: 1px solid var(--border); border-radius: 4px; padding: 0.05rem 0.3rem; font-size: 0.85em; } + .ns-empty { color: var(--muted); font-size: 0.85rem; font-style: italic; } + .ns-loading { color: var(--muted); font-size: 0.85rem; } + .ns-error { color: var(--red); font-size: 0.85rem; } + .news-list-head { display: flex; justify-content: space-between; align-items: center; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 0.4rem; } + .news-count { font-size: 0.7rem; color: var(--accent); font-family: ui-monospace, monospace; } + .news-list { max-height: 46vh; min-height: 120px; overflow-y: auto; border: 1px solid var(--border); border-radius: 10px; background: rgba(15,23,42,0.35); } + .news-list::-webkit-scrollbar { width: 8px; } + .news-list::-webkit-scrollbar-track { background: rgba(30,41,59,0.4); border-radius: 8px; } + .news-list::-webkit-scrollbar-thumb { background: rgba(56,189,248,0.35); border-radius: 8px; } + .news-list::-webkit-scrollbar-thumb:hover { background: rgba(56,189,248,0.55); } + .news-item { + display: flex; align-items: center; gap: 0.75rem; padding: 0.55rem 0.9rem; + border-bottom: 1px solid rgba(51,65,85,0.5); text-decoration: none; color: var(--text); transition: background .15s, box-shadow .15s; + } + .news-item:last-child { border-bottom: none; } + .news-item:hover { background: rgba(56,189,248,0.07); box-shadow: inset 2px 0 0 var(--accent); } + .news-dom { font-size: 0.66rem; font-family: ui-monospace, monospace; color: var(--accent); background: rgba(56,189,248,0.1); border: 1px solid rgba(56,189,248,0.25); border-radius: 9999px; padding: 0.12rem 0.5rem; white-space: nowrap; flex-shrink: 0; max-width: 30%; overflow: hidden; text-overflow: ellipsis; } + .news-title { flex: 1; font-size: 0.87rem; line-height: 1.35; color: var(--text); word-break: break-word; } + .news-item:hover .news-title { color: var(--accent); } + .news-time { font-size: 0.7rem; color: var(--muted); font-family: ui-monospace, monospace; white-space: nowrap; flex-shrink: 0; } + .news-geo { color: var(--green); font-size: 0.8rem; flex-shrink: 0; text-shadow: 0 0 8px rgba(74,222,128,0.6); } + .news-loading, .news-error { padding: 1rem 0.9rem; color: var(--muted); font-size: 0.85rem; } + .news-error { color: var(--red); } + .news-empty { padding: 1rem 0.9rem; color: var(--muted); font-size: 0.85rem; font-style: italic; } @@ -185,6 +232,7 @@ + @@ -323,6 +371,38 @@ + + +