From 7aab04d0122614ec9e4cae50c7971ae946d838a7 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sun, 8 Feb 2026 07:15:05 +0100 Subject: [PATCH] feat(evolution): Add real-time GitHub commits display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New "🚀 Devel" tab with live GitHub commit activity (1-min cache) - Metrics: Commits Today, This Week, Contributors, Stars - Commit type distribution (feat/fix/docs/refactor/chore) - Recent commits list with hash, message, author, relative time - Repository stats: forks, watchers, open issues - Cyberpunk-themed commit cards with color-coding - Pulsing live indicator animation Co-Authored-By: Claude Opus 4.5 --- .claude/HISTORY.md | 23 +- .claude/WIP.md | 10 +- .../files/secubox_evolution.py | 351 +++++++++++++++++- 3 files changed, 374 insertions(+), 10 deletions(-) diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 30a89475..95a99372 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -1,6 +1,6 @@ # SecuBox UI & Theme History -_Last updated: 2026-02-07_ +_Last updated: 2026-02-08_ 1. **Unified Dashboard Refresh (2025-12-20)** - Dashboard received the "sh-page-header" layout, hero stats, and SecuNav top tabs. @@ -990,3 +990,24 @@ _Last updated: 2026-02-07_ 2. Host: `./secubox-clone-station.sh clone` (detects, pulls, flashes target) 3. Target boots, resizes root, auto-joins mesh with pre-approved token - Part of v0.19 mesh deployment automation. + +29. **Evolution Dashboard Real-Time Commits (2026-02-08)** + - Enhanced `secubox-app-streamlit-evolution` with live GitHub commits display. + - New "🚀 Devel" tab (first tab) showing real-time development activity: + - Commits Today / This Week / Contributors / Stars metrics + - Commit type distribution (feat/fix/docs/refactor/chore) + - Recent commits list with: + - Short hash (7 chars) with link to GitHub + - Commit message (80 char truncated) + - Author name + - Relative time (e.g., "2h ago", "just now") + - Commit type color-coding (green=feat, red=fix, orange=docs, purple=refactor) + - Repository stats (forks, watchers, open issues) + - GitHub API integration: + - `fetch_commits(limit=30)` with 1-minute cache TTL for near real-time updates + - `fetch_repo_info()` for repository statistics + - `parse_commit_type()` for conventional commit parsing + - `format_time_ago()` for human-readable timestamps + - `get_commit_stats()` for daily/weekly aggregation + - Cyberpunk theme styling for commits (matching existing dashboard theme) + - Live indicator animation (pulsing green dot) diff --git a/.claude/WIP.md b/.claude/WIP.md index a214175f..4dd42799 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -1,6 +1,6 @@ # Work In Progress (Claude) -_Last updated: 2026-02-07_ +_Last updated: 2026-02-08_ > **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches @@ -53,6 +53,14 @@ _Last updated: 2026-02-07_ ### Just Completed (2026-02-06/08) +- **Evolution Dashboard Real-Time Commits** — DONE (2026-02-08) + - New "🚀 Devel" tab with live GitHub commits (1-min cache) + - Commits Today / This Week / Contributors / Stars metrics + - Commit type distribution with color-coding (feat/fix/docs/refactor) + - Recent commits with hash, message, author, relative time + - Repository stats (forks, watchers, open issues) + - Cyberpunk-themed commit cards with pulsing live indicator + - **Station Cloner/Deployer** — DONE (2026-02-08) - Host-side `secubox-clone-station.sh` with MOKATOOL integration for dual USB serial control - On-device `secubox-cloner` CLI for build/serve/token/export diff --git a/package/secubox/secubox-app-streamlit-evolution/files/secubox_evolution.py b/package/secubox/secubox-app-streamlit-evolution/files/secubox_evolution.py index 2f28a0f6..e659b2dc 100644 --- a/package/secubox/secubox-app-streamlit-evolution/files/secubox_evolution.py +++ b/package/secubox/secubox-app-streamlit-evolution/files/secubox_evolution.py @@ -2,13 +2,15 @@ """ SecuBox Evolution Dashboard Interactive Streamlit landing page showing project evolution, history, WIP, TODO, and README +Real-time GitHub commits integration for development status tracking """ import streamlit as st import requests import re -from datetime import datetime +from datetime import datetime, timedelta from collections import Counter +import time # Page config st.set_page_config( @@ -161,11 +163,98 @@ st.markdown(""" background: #00d4aa; border-radius: 50%; } + + .commit-card { + background: #12121a; + border: 1px solid #2a2a3a; + border-radius: 8px; + padding: 1rem; + margin: 0.5rem 0; + transition: all 0.3s ease; + } + + .commit-card:hover { + border-color: #00a0ff; + transform: translateX(4px); + } + + .commit-hash { + font-family: 'JetBrains Mono', monospace; + color: #00a0ff; + font-size: 0.85rem; + background: #00a0ff22; + padding: 0.2rem 0.5rem; + border-radius: 4px; + display: inline-block; + } + + .commit-message { + color: #e0e0e0; + font-weight: 500; + margin: 0.5rem 0; + word-break: break-word; + } + + .commit-meta { + color: #808090; + font-size: 0.8rem; + } + + .commit-author { + color: #00d4aa; + } + + .commit-time { + color: #ffa500; + } + + .live-indicator { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: #00d4aa; + font-size: 0.85rem; + } + + .live-dot { + width: 8px; + height: 8px; + background: #00d4aa; + border-radius: 50%; + animation: pulse 2s infinite; + } + + @keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.2); } + } + + .devel-status { + background: linear-gradient(135deg, #12121a 0%, #1a1a2e 100%); + border: 1px solid #2a2a3a; + border-radius: 12px; + padding: 1.5rem; + margin-bottom: 1rem; + } + + .devel-title { + color: #00a0ff; + font-size: 1.2rem; + font-weight: 600; + margin-bottom: 0.5rem; + } + + .commit-type-feat { border-left: 3px solid #00d4aa; } + .commit-type-fix { border-left: 3px solid #ff6b6b; } + .commit-type-docs { border-left: 3px solid #ffa500; } + .commit-type-refactor { border-left: 3px solid #a855f7; } + .commit-type-chore { border-left: 3px solid #808090; } """, unsafe_allow_html=True) # GitHub raw URLs GITHUB_BASE = "https://raw.githubusercontent.com/gkerma/secubox-openwrt/master" +GITHUB_API = "https://api.github.com/repos/gkerma/secubox-openwrt" FILES = { "HISTORY": f"{GITHUB_BASE}/.claude/HISTORY.md", "WIP": f"{GITHUB_BASE}/.claude/WIP.md", @@ -184,6 +273,110 @@ def fetch_file(url): except: return None +@st.cache_data(ttl=60) # 1-minute cache for near real-time updates +def fetch_commits(limit=30): + """Fetch recent commits from GitHub API""" + try: + response = requests.get( + f"{GITHUB_API}/commits", + params={"per_page": limit}, + headers={"Accept": "application/vnd.github.v3+json"}, + timeout=10 + ) + if response.status_code == 200: + return response.json() + return [] + except: + return [] + +@st.cache_data(ttl=60) +def fetch_repo_info(): + """Fetch repository information""" + try: + response = requests.get( + GITHUB_API, + headers={"Accept": "application/vnd.github.v3+json"}, + timeout=10 + ) + if response.status_code == 200: + return response.json() + return {} + except: + return {} + +def parse_commit_type(message): + """Extract commit type from conventional commit message""" + patterns = { + 'feat': r'^feat(\([^)]+\))?:', + 'fix': r'^fix(\([^)]+\))?:', + 'docs': r'^docs(\([^)]+\))?:', + 'refactor': r'^refactor(\([^)]+\))?:', + 'chore': r'^chore(\([^)]+\))?:', + 'test': r'^test(\([^)]+\))?:', + 'style': r'^style(\([^)]+\))?:', + 'perf': r'^perf(\([^)]+\))?:', + } + for ctype, pattern in patterns.items(): + if re.match(pattern, message, re.I): + return ctype + return 'other' + +def format_time_ago(iso_date): + """Convert ISO date to human-readable 'time ago' format""" + try: + dt = datetime.fromisoformat(iso_date.replace('Z', '+00:00')) + now = datetime.now(dt.tzinfo) + diff = now - dt + + if diff.days > 30: + return dt.strftime("%b %d, %Y") + elif diff.days > 0: + return f"{diff.days}d ago" + elif diff.seconds > 3600: + return f"{diff.seconds // 3600}h ago" + elif diff.seconds > 60: + return f"{diff.seconds // 60}m ago" + else: + return "just now" + except: + return iso_date[:10] + +def get_commit_stats(commits): + """Calculate commit statistics""" + if not commits: + return {} + + stats = { + 'total': len(commits), + 'types': Counter(), + 'authors': Counter(), + 'today': 0, + 'this_week': 0, + } + + now = datetime.now() + for c in commits: + commit = c.get('commit', {}) + message = commit.get('message', '').split('\n')[0] + stats['types'][parse_commit_type(message)] += 1 + + author = commit.get('author', {}).get('name', 'Unknown') + stats['authors'][author] += 1 + + try: + date_str = commit.get('author', {}).get('date', '') + if date_str: + dt = datetime.fromisoformat(date_str.replace('Z', '+00:00')) + days_ago = (now - dt.replace(tzinfo=None)).days + if days_ago == 0: + stats['today'] += 1 + if days_ago < 7: + stats['this_week'] += 1 + except: + pass + + return stats + def parse_history(content): """Parse HISTORY.md to extract milestones""" if not content: @@ -249,7 +442,7 @@ def count_features(content): def main(): # Header st.markdown('

🛡️ SecuBox Evolution

', unsafe_allow_html=True) - st.markdown('

Real-time project tracking • History • WIP • TODO • Documentation

', unsafe_allow_html=True) + st.markdown('

Live GitHub commits • History • WIP • TODO • Documentation

', unsafe_allow_html=True) # Fetch all files with st.spinner("Fetching latest data from GitHub..."): @@ -328,14 +521,156 @@ def main(): st.markdown("[GitHub Repository](https://github.com/gkerma/secubox-openwrt)") st.markdown("[SecuBox Portal](https://secubox.in)") + st.markdown("---") + st.markdown("### 🚀 Devel Status") + st.markdown(' Live', unsafe_allow_html=True) + if st.button("🔄 Refresh Data"): st.cache_data.clear() st.rerun() + # Fetch commits for devel status + commits = fetch_commits(30) + commit_stats = get_commit_stats(commits) + repo_info = fetch_repo_info() + # Main tabs - tab1, tab2, tab3, tab4, tab5 = st.tabs(["📜 History", "🔧 WIP", "📋 TODO", "📖 README", "📈 Timeline"]) + tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs(["🚀 Devel", "📜 History", "🔧 WIP", "📋 TODO", "📖 README", "📈 Timeline"]) with tab1: + st.markdown("## 🚀 Development Status") + + # Live indicator + st.markdown(""" +
+ + Live GitHub Activity • Updates every minute +
+ """, unsafe_allow_html=True) + + st.markdown("
", unsafe_allow_html=True) + + # Dev metrics row + dcol1, dcol2, dcol3, dcol4 = st.columns(4) + + with dcol1: + st.markdown(f""" +
+
{commit_stats.get('today', 0)}
+
Commits Today
+
+ """, unsafe_allow_html=True) + + with dcol2: + st.markdown(f""" +
+
{commit_stats.get('this_week', 0)}
+
This Week
+
+ """, unsafe_allow_html=True) + + with dcol3: + contributors = len(commit_stats.get('authors', {})) + st.markdown(f""" +
+
{contributors}
+
Contributors
+
+ """, unsafe_allow_html=True) + + with dcol4: + stars = repo_info.get('stargazers_count', 0) + st.markdown(f""" +
+
⭐ {stars}
+
GitHub Stars
+
+ """, unsafe_allow_html=True) + + st.markdown("
", unsafe_allow_html=True) + + # Commit type distribution + if commit_stats.get('types'): + st.markdown("### 📊 Commit Types") + type_colors = { + 'feat': '🟢', 'fix': '🔴', 'docs': '🟡', + 'refactor': '🟣', 'chore': '⚪', 'other': '⚫', + 'test': '🔵', 'style': '🟠', 'perf': '💜' + } + type_cols = st.columns(len(commit_stats['types'])) + for i, (ctype, count) in enumerate(sorted(commit_stats['types'].items(), key=lambda x: -x[1])): + with type_cols[i % len(type_cols)]: + emoji = type_colors.get(ctype, '⚫') + st.metric(f"{emoji} {ctype}", count) + + st.markdown("---") + + # Recent commits list + st.markdown("### 📝 Recent Commits") + + if commits: + for c in commits[:15]: + sha = c.get('sha', '')[:7] + commit = c.get('commit', {}) + message = commit.get('message', '').split('\n')[0][:80] + author = commit.get('author', {}).get('name', 'Unknown') + date_str = commit.get('author', {}).get('date', '') + time_ago = format_time_ago(date_str) + url = c.get('html_url', '#') + + # Determine commit type for styling + ctype = parse_commit_type(message) + type_class = f"commit-type-{ctype}" if ctype != 'other' else '' + + st.markdown(f""" +
+ + {sha} + +
{message}
+
+ 👤 {author} +  •  + 🕐 {time_ago} +
+
+ """, unsafe_allow_html=True) + + # Show more button + with st.expander("📜 View All Commits (30)"): + for c in commits[15:]: + sha = c.get('sha', '')[:7] + commit = c.get('commit', {}) + message = commit.get('message', '').split('\n')[0][:80] + author = commit.get('author', {}).get('name', 'Unknown') + date_str = commit.get('author', {}).get('date', '') + time_ago = format_time_ago(date_str) + + st.markdown(f""" +
+ {sha} +
{message}
+
+ 👤 {author}🕐 {time_ago} +
+
+ """, unsafe_allow_html=True) + else: + st.warning("Could not fetch commits from GitHub API") + + # Repo quick stats + if repo_info: + st.markdown("---") + st.markdown("### 📈 Repository Stats") + rcol1, rcol2, rcol3 = st.columns(3) + with rcol1: + st.metric("🍴 Forks", repo_info.get('forks_count', 0)) + with rcol2: + st.metric("👀 Watchers", repo_info.get('watchers_count', 0)) + with rcol3: + st.metric("❗ Open Issues", repo_info.get('open_issues_count', 0)) + + with tab2: st.markdown("## 📜 Project History") if search_query and history: @@ -358,7 +693,7 @@ def main(): else: st.error("Could not fetch HISTORY.md") - with tab2: + with tab3: st.markdown("## 🔧 Work In Progress") if search_query and wip: @@ -369,7 +704,7 @@ def main(): else: st.error("Could not fetch WIP.md") - with tab3: + with tab4: st.markdown("## 📋 TODO List") if search_query and todo: @@ -402,7 +737,7 @@ def main(): else: st.error("Could not fetch TODO.md") - with tab4: + with tab5: st.markdown("## 📖 README") if search_query and readme: @@ -413,7 +748,7 @@ def main(): else: st.error("Could not fetch README.md") - with tab5: + with tab6: st.markdown("## 📈 Evolution Timeline") if milestones: @@ -458,7 +793,7 @@ def main():
SecuBox Evolution Dashboard • Auto-synced with GitHub master branch
- Data refreshes every 5 minutes • + Devel status: 1 min • Docs: 5 min • View on GitHub