#!/usr/bin/env python3 """ 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, timedelta from collections import Counter import time # Page config st.set_page_config( page_title="SecuBox Evolution", page_icon="🛡️", layout="wide", initial_sidebar_state="expanded" ) # Custom CSS for dark cyberpunk theme st.markdown(""" """, 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", "TODO": f"{GITHUB_BASE}/.claude/TODO.md", "README": f"{GITHUB_BASE}/README.md" } @st.cache_data(ttl=60) # 1-minute cache for real-time sync def fetch_file(url): """Fetch file content from GitHub (real-time sync)""" try: response = requests.get(url, timeout=10) if response.status_code == 200: return response.text return None 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: return [] milestones = [] # Match patterns like "1. **Title (2026-02-06)**" or "14. **P2P MirrorBox..." pattern = r'(\d+)\.\s+\*\*([^*]+)\*\*' for match in re.finditer(pattern, content): num = match.group(1) title = match.group(2).strip() # Extract date if present date_match = re.search(r'\((\d{4}-\d{2}-\d{2})\)', title) date = date_match.group(1) if date_match else None title_clean = re.sub(r'\s*\(\d{4}-\d{2}-\d{2}\)\s*', '', title) milestones.append({ 'num': int(num), 'title': title_clean, 'date': date }) return milestones def parse_packages(content): """Extract package names from content""" if not content: return [] packages = set() # Match `package-name` or secubox-app-xxx patterns patterns = [ r'`(secubox-[a-z0-9-]+)`', r'`(luci-app-[a-z0-9-]+)`', r'\*\*([a-z0-9-]+)\*\*:', ] for pattern in patterns: for match in re.finditer(pattern, content): pkg = match.group(1) if len(pkg) > 3: packages.add(pkg) return list(packages) def count_features(content): """Count features mentioned in content""" if not content: return {} features = { 'AI/LocalAI': len(re.findall(r'LocalAI|AI-powered|LLM|agent', content, re.I)), 'Security': len(re.findall(r'CrowdSec|WAF|firewall|threat|CVE|security', content, re.I)), 'DNS': len(re.findall(r'DNS|Vortex|dnsctl|AdGuard', content, re.I)), 'Mesh/P2P': len(re.findall(r'mesh|P2P|gossip|mirror|peer', content, re.I)), 'Containers': len(re.findall(r'LXC|Docker|container', content, re.I)), 'UI/LuCI': len(re.findall(r'LuCI|dashboard|UI|interface', content, re.I)), } return features def main(): # Header st.markdown('

🛡️ SecuBox Evolution

', 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..."): history = fetch_file(FILES["HISTORY"]) wip = fetch_file(FILES["WIP"]) todo = fetch_file(FILES["TODO"]) readme = fetch_file(FILES["README"]) # Parse data milestones = parse_history(history) packages = parse_packages(history or "") features = count_features(history or "") # Metrics row col1, col2, col3, col4 = st.columns(4) with col1: st.markdown(f"""
{len(milestones)}
Milestones
""", unsafe_allow_html=True) with col2: st.markdown(f"""
{len(packages)}
Packages
""", unsafe_allow_html=True) with col3: # Count TODO items todo_count = len(re.findall(r'^- \[[ x]\]', todo or "", re.M)) st.markdown(f"""
{todo_count}
TODO Items
""", unsafe_allow_html=True) with col4: # Latest date latest_date = "N/A" for m in reversed(milestones): if m['date']: latest_date = m['date'] break st.markdown(f"""
{latest_date}
Last Update
""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # Sidebar with st.sidebar: st.markdown("### 🔍 Search") search_query = st.text_input("Search in all files", placeholder="e.g., CrowdSec, HAProxy...") st.markdown("### 📊 Feature Distribution") if features: for feat, count in sorted(features.items(), key=lambda x: -x[1]): if count > 0: st.progress(min(count / 50, 1.0), text=f"{feat}: {count}") st.markdown("### 🏷️ Recent Packages") for pkg in packages[-10:]: st.markdown(f'{pkg}', unsafe_allow_html=True) st.markdown("---") st.markdown("### ⚡ Quick Links") 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, 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: # Highlight search results highlighted = history.replace(search_query, f'**:green[{search_query}]**') st.markdown(highlighted) elif history: # Show milestones as cards for m in reversed(milestones[-20:]): date_str = f"📅 {m['date']}" if m['date'] else "" st.markdown(f"""
{date_str}
{m['num']}. {m['title']}
""", unsafe_allow_html=True) with st.expander("📄 View Full History"): st.markdown(history) else: st.error("Could not fetch HISTORY.md") with tab3: st.markdown("## 🔧 Work In Progress") if search_query and wip: highlighted = wip.replace(search_query, f'**:orange[{search_query}]**') st.markdown(highlighted) elif wip: st.markdown(wip) else: st.error("Could not fetch WIP.md") with tab4: st.markdown("## 📋 TODO List") if search_query and todo: highlighted = todo.replace(search_query, f'**:yellow[{search_query}]**') st.markdown(highlighted) elif todo: # Parse and display TODO items lines = todo.split('\n') completed = 0 pending = 0 for line in lines: if re.match(r'^- \[x\]', line): completed += 1 elif re.match(r'^- \[ \]', line): pending += 1 col1, col2 = st.columns(2) with col1: st.metric("✅ Completed", completed) with col2: st.metric("⏳ Pending", pending) if completed + pending > 0: progress = completed / (completed + pending) st.progress(progress, text=f"Progress: {progress*100:.1f}%") st.markdown("---") st.markdown(todo) else: st.error("Could not fetch TODO.md") with tab5: st.markdown("## 📖 README") if search_query and readme: highlighted = readme.replace(search_query, f'**:blue[{search_query}]**') st.markdown(highlighted) elif readme: st.markdown(readme) else: st.error("Could not fetch README.md") with tab6: st.markdown("## 📈 Evolution Timeline") if milestones: # Group by month months = {} for m in milestones: if m['date']: month = m['date'][:7] # YYYY-MM if month not in months: months[month] = [] months[month].append(m) # Display timeline for month in sorted(months.keys(), reverse=True): items = months[month] month_name = datetime.strptime(month, "%Y-%m").strftime("%B %Y") st.markdown(f"### 📅 {month_name}") for item in items: st.markdown(f"""
{item['title']}
Milestone #{item['num']}
""", unsafe_allow_html=True) # Chart st.markdown("### 📊 Milestones per Month") month_counts = {m: len(items) for m, items in months.items()} if month_counts: import pandas as pd df = pd.DataFrame(list(month_counts.items()), columns=['Month', 'Count']) df = df.sort_values('Month') st.bar_chart(df.set_index('Month')) else: st.info("No dated milestones found in history") # Footer st.markdown("---") st.markdown("""
SecuBox Evolution Dashboard • Auto-synced with GitHub master branch
All tabs refresh every 60 seconds • View on GitHub
""", unsafe_allow_html=True) if __name__ == "__main__": main()