feat(evolution): Add real-time GitHub commits display

- 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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-08 07:15:05 +01:00
parent 8015d790e0
commit 7aab04d012
3 changed files with 374 additions and 10 deletions

View File

@ -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)

View File

@ -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

View File

@ -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; }
</style>
""", 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('<h1 class="main-header">🛡️ SecuBox Evolution</h1>', unsafe_allow_html=True)
st.markdown('<p class="sub-header">Real-time project tracking • History • WIP • TODO • Documentation</p>', unsafe_allow_html=True)
st.markdown('<p class="sub-header">Live GitHub commits • History • WIP • TODO • Documentation</p>', 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('<span class="live-indicator"><span class="live-dot"></span> Live</span>', 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("""
<div class="live-indicator">
<span class="live-dot"></span>
Live GitHub Activity Updates every minute
</div>
""", unsafe_allow_html=True)
st.markdown("<br>", unsafe_allow_html=True)
# Dev metrics row
dcol1, dcol2, dcol3, dcol4 = st.columns(4)
with dcol1:
st.markdown(f"""
<div class="metric-card">
<div class="metric-value" style="color: #00a0ff;">{commit_stats.get('today', 0)}</div>
<div class="metric-label">Commits Today</div>
</div>
""", unsafe_allow_html=True)
with dcol2:
st.markdown(f"""
<div class="metric-card">
<div class="metric-value" style="color: #a855f7;">{commit_stats.get('this_week', 0)}</div>
<div class="metric-label">This Week</div>
</div>
""", unsafe_allow_html=True)
with dcol3:
contributors = len(commit_stats.get('authors', {}))
st.markdown(f"""
<div class="metric-card">
<div class="metric-value" style="color: #ffa500;">{contributors}</div>
<div class="metric-label">Contributors</div>
</div>
""", unsafe_allow_html=True)
with dcol4:
stars = repo_info.get('stargazers_count', 0)
st.markdown(f"""
<div class="metric-card">
<div class="metric-value" style="color: #ffd700;"> {stars}</div>
<div class="metric-label">GitHub Stars</div>
</div>
""", unsafe_allow_html=True)
st.markdown("<br>", 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"""
<div class="commit-card {type_class}">
<a href="{url}" target="_blank" style="text-decoration: none;">
<span class="commit-hash">{sha}</span>
</a>
<div class="commit-message">{message}</div>
<div class="commit-meta">
<span class="commit-author">👤 {author}</span>
&nbsp;&nbsp;
<span class="commit-time">🕐 {time_ago}</span>
</div>
</div>
""", 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"""
<div class="commit-card">
<span class="commit-hash">{sha}</span>
<div class="commit-message">{message}</div>
<div class="commit-meta">
<span class="commit-author">👤 {author}</span> <span class="commit-time">🕐 {time_ago}</span>
</div>
</div>
""", 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():
<div style="text-align: center; color: #808090; padding: 1rem;">
<small>
SecuBox Evolution Dashboard Auto-synced with GitHub master branch<br>
Data refreshes every 5 minutes
Devel status: 1 min Docs: 5 min
<a href="https://github.com/gkerma/secubox-openwrt" style="color: #00d4aa;">View on GitHub</a>
</small>
</div>