Spaces:
Sleeping
Sleeping
import streamlit as st | |
from pymongo import MongoClient | |
from pymongo.errors import ConnectionFailure | |
import datetime | |
import os | |
# Page configuration | |
st.set_page_config( | |
page_title="GitHub Activity Feed", | |
page_icon="π", | |
layout="wide", | |
initial_sidebar_state="collapsed" | |
) | |
# Custom CSS for beautiful UI | |
st.markdown(""" | |
<style> | |
.main { | |
padding-top: 2rem; | |
} | |
.stTitle { | |
color: #1f2937; | |
font-size: 3rem !important; | |
font-weight: 700 !important; | |
text-align: center; | |
margin-bottom: 2rem; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
background-clip: text; | |
} | |
.activity-card { | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
padding: 1.5rem; | |
border-radius: 12px; | |
margin: 1rem 0; | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); | |
color: white; | |
border-left: 4px solid #f59e0b; | |
} | |
.activity-text { | |
font-size: 1.1rem; | |
font-weight: 500; | |
margin: 0; | |
} | |
.activity-time { | |
font-size: 0.9rem; | |
opacity: 0.8; | |
margin-top: 0.5rem; | |
} | |
.stats-container { | |
display: flex; | |
justify-content: space-around; | |
margin: 2rem 0; | |
} | |
.stat-card { | |
background: white; | |
padding: 1.5rem; | |
border-radius: 12px; | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); | |
text-align: center; | |
flex: 1; | |
margin: 0 0.5rem; | |
border-top: 4px solid #10b981; | |
} | |
.stat-number { | |
font-size: 2rem; | |
font-weight: 700; | |
color: #1f2937; | |
margin: 0; | |
} | |
.stat-label { | |
color: #6b7280; | |
font-size: 0.9rem; | |
margin-top: 0.5rem; | |
} | |
.no-events { | |
text-align: center; | |
padding: 3rem; | |
color: #6b7280; | |
font-size: 1.2rem; | |
background: #f9fafb; | |
border-radius: 12px; | |
margin: 2rem 0; | |
} | |
.header-subtitle { | |
text-align: center; | |
color: #6b7280; | |
font-size: 1.2rem; | |
margin-bottom: 3rem; | |
} | |
.activity-icon { | |
font-size: 1.5rem; | |
margin-right: 0.5rem; | |
} | |
.status-indicator { | |
display: inline-block; | |
width: 12px; | |
height: 12px; | |
border-radius: 50%; | |
margin-right: 8px; | |
} | |
.status-connected { | |
background-color: #10b981; | |
animation: pulse 2s infinite; | |
} | |
.status-disconnected { | |
background-color: #ef4444; | |
} | |
@keyframes pulse { | |
0% { opacity: 1; } | |
50% { opacity: 0.5; } | |
100% { opacity: 1; } | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# App header | |
st.markdown('<h1 class="stTitle">π GitHub Activity Feed</h1>', unsafe_allow_html=True) | |
st.markdown('<p class="header-subtitle">Real-time monitoring of your GitHub repository activities</p>', unsafe_allow_html=True) | |
# Database connection | |
def init_connection(): | |
try: | |
# Get MongoDB URI from environment variable | |
mongodb_uri = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/') | |
client = MongoClient( | |
mongodb_uri, | |
serverSelectionTimeoutMS=5000, | |
connectTimeoutMS=5000 | |
) | |
# Test the connection | |
client.admin.command('ping') | |
return client | |
except ConnectionFailure as e: | |
st.error(f"Database connection failed: {e}") | |
return None | |
except Exception as e: | |
st.error(f"Unexpected error: {e}") | |
return None | |
# Initialize connection | |
client = init_connection() | |
# Connection status indicator | |
if client: | |
st.markdown( | |
'<div style="text-align: center; margin-bottom: 2rem;">' | |
'<span class="status-indicator status-connected"></span>' | |
'<span style="color: #10b981; font-weight: 500;">Database Connected</span>' | |
'</div>', | |
unsafe_allow_html=True | |
) | |
else: | |
st.markdown( | |
'<div style="text-align: center; margin-bottom: 2rem;">' | |
'<span class="status-indicator status-disconnected"></span>' | |
'<span style="color: #ef4444; font-weight: 500;">Database Disconnected</span>' | |
'</div>', | |
unsafe_allow_html=True | |
) | |
if client: | |
db = client["github_webhooks"] | |
collection = db["events"] | |
# Manual refresh section | |
col1, col2, col3 = st.columns([1, 1, 1]) | |
with col2: | |
if st.button("π Refresh Data", key="refresh_btn", type="primary"): | |
# Clear cache to force refresh | |
st.cache_resource.clear() | |
st.rerun() | |
# Auto-refresh toggle | |
col1, col2, col3 = st.columns([1, 1, 1]) | |
with col2: | |
auto_refresh = st.checkbox("π Auto-refresh (30s)", value=False) | |
# Fetch events | |
try: | |
events = list(collection.find().sort("timestamp", -1).limit(20)) | |
# Statistics | |
if events: | |
total_events = collection.count_documents({}) | |
today_events = collection.count_documents({ | |
"timestamp": { | |
"$gte": datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) | |
} | |
}) | |
# Display stats | |
st.markdown(""" | |
<div class="stats-container"> | |
<div class="stat-card"> | |
<div class="stat-number">{}</div> | |
<div class="stat-label">Total Events</div> | |
</div> | |
<div class="stat-card"> | |
<div class="stat-number">{}</div> | |
<div class="stat-label">Today's Events</div> | |
</div> | |
<div class="stat-card"> | |
<div class="stat-number">{}</div> | |
<div class="stat-label">Recent Activities</div> | |
</div> | |
</div> | |
""".format(total_events, today_events, len(events)), unsafe_allow_html=True) | |
# Display events | |
if events: | |
st.markdown("### π Recent Activities") | |
for event in events: | |
# Determine icon based on event type | |
if "pushed" in event['message']: | |
icon = "π€" | |
elif "pull request" in event['message']: | |
icon = "π" | |
elif "merged" in event['message']: | |
icon = "β " | |
else: | |
icon = "π" | |
# Format timestamp | |
event_time = event.get('timestamp', datetime.datetime.now()) | |
if isinstance(event_time, datetime.datetime): | |
time_str = event_time.strftime("%B %d, %Y at %I:%M %p UTC") | |
else: | |
time_str = "Unknown time" | |
st.markdown(f""" | |
<div class="activity-card"> | |
<div class="activity-text"> | |
<span class="activity-icon">{icon}</span> | |
{event['message']} | |
</div> | |
<div class="activity-time">β° {time_str}</div> | |
</div> | |
""", unsafe_allow_html=True) | |
else: | |
st.markdown(""" | |
<div class="no-events"> | |
<h3>π No GitHub events yet!</h3> | |
<p>Your repository activities will appear here once you set up the webhook.</p> | |
<p>Push some code or create a pull request to see the magic happen! β¨</p> | |
</div> | |
""", unsafe_allow_html=True) | |
except Exception as e: | |
st.error(f"Error fetching events: {e}") | |
st.markdown(""" | |
<div class="no-events"> | |
<h3>β οΈ Connection Issue</h3> | |
<p>Unable to fetch events from the database.</p> | |
<p>Please check your connection and try again.</p> | |
</div> | |
""", unsafe_allow_html=True) | |
else: | |
st.markdown(""" | |
<div class="no-events"> | |
<h3>β Database Connection Failed</h3> | |
<p>Unable to connect to MongoDB. Please check your connection string.</p> | |
<p>Make sure your MONGODB_URI environment variable is set correctly.</p> | |
</div> | |
""", unsafe_allow_html=True) | |
# Footer | |
st.markdown("---") | |
st.markdown( | |
"<div style='text-align: center; color: #6b7280; padding: 1rem;'>" | |
"Built with β€οΈ using Streamlit and FastAPI | " | |
f"Last updated: {datetime.datetime.now().strftime('%H:%M:%S UTC')}" | |
"</div>", | |
unsafe_allow_html=True | |
) | |
# Auto-refresh functionality (optional) | |
if auto_refresh: | |
import time | |
time.sleep(30) | |
st.rerun() |