Spaces:
Build error
Build error
Upload 2 files
Browse files- app.py +985 -0
- requirements.txt +4 -0
app.py
ADDED
|
@@ -0,0 +1,985 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Cybersecurity Threat Monitoring Dashboard
|
| 3 |
+
A comprehensive multi-page dashboard built with Streamlit
|
| 4 |
+
Deployed on Hugging Face Spaces
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import streamlit as st
|
| 8 |
+
import pandas as pd
|
| 9 |
+
import numpy as np
|
| 10 |
+
import plotly.express as px
|
| 11 |
+
import plotly.graph_objects as go
|
| 12 |
+
from datetime import datetime, timedelta
|
| 13 |
+
import random
|
| 14 |
+
import time
|
| 15 |
+
|
| 16 |
+
# Page configuration
|
| 17 |
+
st.set_page_config(
|
| 18 |
+
page_title="CyberShield - Threat Monitor",
|
| 19 |
+
page_icon="π‘οΈ",
|
| 20 |
+
layout="wide",
|
| 21 |
+
initial_sidebar_state="expanded"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
# Load custom CSS for dark theme
|
| 25 |
+
def load_css():
|
| 26 |
+
st.markdown("""
|
| 27 |
+
<style>
|
| 28 |
+
/* Main dark theme */
|
| 29 |
+
.stApp {
|
| 30 |
+
background-color: #0a0a0f;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
/* Sidebar styling */
|
| 34 |
+
[data-testid="stSidebar"] {
|
| 35 |
+
background-color: #12121a;
|
| 36 |
+
border-right: 1px solid #1e1e2e;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
/* Metric cards */
|
| 40 |
+
.metric-card {
|
| 41 |
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
| 42 |
+
border: 1px solid #0f3460;
|
| 43 |
+
border-radius: 10px;
|
| 44 |
+
padding: 20px;
|
| 45 |
+
text-align: center;
|
| 46 |
+
box-shadow: 0 4px 15px rgba(0, 255, 255, 0.1);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.metric-value {
|
| 50 |
+
font-size: 2.5rem;
|
| 51 |
+
font-weight: bold;
|
| 52 |
+
color: #00ffff;
|
| 53 |
+
text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.metric-label {
|
| 57 |
+
font-size: 0.9rem;
|
| 58 |
+
color: #888;
|
| 59 |
+
text-transform: uppercase;
|
| 60 |
+
letter-spacing: 1px;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
.metric-critical { color: #ff4757; text-shadow: 0 0 10px rgba(255, 71, 87, 0.5); }
|
| 64 |
+
.metric-high { color: #ff7f50; text-shadow: 0 0 10px rgba(255, 127, 80, 0.5); }
|
| 65 |
+
.metric-medium { color: #ffa502; text-shadow: 0 0 10px rgba(255, 165, 2, 0.5); }
|
| 66 |
+
.metric-low { color: #2ed573; text-shadow: 0 0 10px rgba(46, 213, 115, 0.5); }
|
| 67 |
+
|
| 68 |
+
/* Alert box */
|
| 69 |
+
.alert-critical {
|
| 70 |
+
background: linear-gradient(90deg, rgba(255,71,87,0.2) 0%, rgba(255,71,87,0.05) 100%);
|
| 71 |
+
border-left: 4px solid #ff4757;
|
| 72 |
+
padding: 15px;
|
| 73 |
+
margin: 10px 0;
|
| 74 |
+
border-radius: 5px;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.alert-high {
|
| 78 |
+
background: linear-gradient(90deg, rgba(255,127,80,0.2) 0%, rgba(255,127,80,0.05) 100%);
|
| 79 |
+
border-left: 4px solid #ff7f50;
|
| 80 |
+
padding: 15px;
|
| 81 |
+
margin: 10px 0;
|
| 82 |
+
border-radius: 5px;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/* Header styling */
|
| 86 |
+
.main-header {
|
| 87 |
+
background: linear-gradient(90deg, #0f3460 0%, #1a1a2e 50%, #0f3460 100%);
|
| 88 |
+
padding: 20px;
|
| 89 |
+
border-radius: 10px;
|
| 90 |
+
margin-bottom: 20px;
|
| 91 |
+
border: 1px solid #00ffff33;
|
| 92 |
+
text-align: center;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
.main-header h1 {
|
| 96 |
+
color: #00ffff;
|
| 97 |
+
text-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
|
| 98 |
+
margin: 0;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
/* Table styling */
|
| 102 |
+
.dataframe {
|
| 103 |
+
background-color: #1a1a2e !important;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
/* Status indicators */
|
| 107 |
+
.status-dot {
|
| 108 |
+
height: 10px;
|
| 109 |
+
width: 10px;
|
| 110 |
+
border-radius: 50%;
|
| 111 |
+
display: inline-block;
|
| 112 |
+
margin-right: 5px;
|
| 113 |
+
animation: pulse 2s infinite;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.status-active { background-color: #2ed573; }
|
| 117 |
+
.status-warning { background-color: #ffa502; }
|
| 118 |
+
.status-danger { background-color: #ff4757; }
|
| 119 |
+
|
| 120 |
+
@keyframes pulse {
|
| 121 |
+
0% { opacity: 1; }
|
| 122 |
+
50% { opacity: 0.5; }
|
| 123 |
+
100% { opacity: 1; }
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
/* Scrollbar styling */
|
| 127 |
+
::-webkit-scrollbar {
|
| 128 |
+
width: 8px;
|
| 129 |
+
height: 8px;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
::-webkit-scrollbar-track {
|
| 133 |
+
background: #1a1a2e;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
::-webkit-scrollbar-thumb {
|
| 137 |
+
background: #0f3460;
|
| 138 |
+
border-radius: 4px;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
::-webkit-scrollbar-thumb:hover {
|
| 142 |
+
background: #00ffff;
|
| 143 |
+
}
|
| 144 |
+
</style>
|
| 145 |
+
""", unsafe_allow_html=True)
|
| 146 |
+
|
| 147 |
+
load_css()
|
| 148 |
+
|
| 149 |
+
# ============== DATA GENERATION ==============
|
| 150 |
+
|
| 151 |
+
# Cache the data generation to improve performance
|
| 152 |
+
@st.cache_data(ttl=60)
|
| 153 |
+
def generate_threat_data(num_records=500):
|
| 154 |
+
"""Generate realistic simulated threat data"""
|
| 155 |
+
|
| 156 |
+
attack_types = [
|
| 157 |
+
'DDoS Attack', 'SQL Injection', 'XSS Attack', 'Brute Force',
|
| 158 |
+
'Malware', 'Phishing', 'Ransomware', 'Man-in-the-Middle',
|
| 159 |
+
'Zero-Day Exploit', 'Port Scanning', 'DNS Tunneling', 'Credential Stuffing'
|
| 160 |
+
]
|
| 161 |
+
|
| 162 |
+
severities = ['Critical', 'High', 'Medium', 'Low']
|
| 163 |
+
severity_weights = [0.1, 0.25, 0.4, 0.25]
|
| 164 |
+
|
| 165 |
+
statuses = ['Active', 'Mitigated', 'Investigating', 'Blocked']
|
| 166 |
+
status_weights = [0.3, 0.25, 0.25, 0.2]
|
| 167 |
+
|
| 168 |
+
countries = [
|
| 169 |
+
('Russia', 'RUS', 55.7558, 37.6173),
|
| 170 |
+
('China', 'CHN', 39.9042, 116.4074),
|
| 171 |
+
('United States', 'USA', 37.0902, -95.7129),
|
| 172 |
+
('North Korea', 'PRK', 39.0392, 125.7625),
|
| 173 |
+
('Iran', 'IRN', 35.6892, 51.3890),
|
| 174 |
+
('Brazil', 'BRA', -14.2350, -51.9253),
|
| 175 |
+
('India', 'IND', 20.5937, 78.9629),
|
| 176 |
+
('Nigeria', 'NGA', 9.0820, 8.6753),
|
| 177 |
+
('Vietnam', 'VNM', 14.0583, 108.2772),
|
| 178 |
+
('Indonesia', 'IDN', -0.7893, 113.9213),
|
| 179 |
+
('Ukraine', 'UKR', 48.3794, 31.1656),
|
| 180 |
+
('Romania', 'ROU', 45.9432, 24.9668),
|
| 181 |
+
('Germany', 'DEU', 51.1657, 10.4515),
|
| 182 |
+
('Netherlands', 'NLD', 52.1326, 5.2913),
|
| 183 |
+
('Turkey', 'TUR', 38.9637, 35.2433)
|
| 184 |
+
]
|
| 185 |
+
|
| 186 |
+
target_countries = ['United States', 'United Kingdom', 'Germany', 'France', 'Japan', 'Australia', 'Canada']
|
| 187 |
+
|
| 188 |
+
common_ports = [22, 23, 25, 53, 80, 443, 445, 3306, 3389, 5432, 8080, 8443, 27017]
|
| 189 |
+
|
| 190 |
+
protocols = ['TCP', 'UDP', 'ICMP', 'HTTP', 'HTTPS', 'SSH', 'FTP', 'SMTP', 'DNS']
|
| 191 |
+
|
| 192 |
+
# Generate timestamps over the last 7 days
|
| 193 |
+
end_time = datetime.now()
|
| 194 |
+
start_time = end_time - timedelta(days=7)
|
| 195 |
+
|
| 196 |
+
data = []
|
| 197 |
+
for i in range(num_records):
|
| 198 |
+
country_data = random.choice(countries)
|
| 199 |
+
timestamp = start_time + timedelta(
|
| 200 |
+
seconds=random.randint(0, int((end_time - start_time).total_seconds()))
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
# Generate realistic IP
|
| 204 |
+
source_ip = f"{random.randint(1,223)}.{random.randint(0,255)}.{random.randint(0,255)}.{random.randint(1,254)}"
|
| 205 |
+
dest_ip = f"192.168.{random.randint(1,10)}.{random.randint(1,254)}"
|
| 206 |
+
|
| 207 |
+
attack_type = random.choice(attack_types)
|
| 208 |
+
severity = random.choices(severities, weights=severity_weights)[0]
|
| 209 |
+
|
| 210 |
+
data.append({
|
| 211 |
+
'id': f'THR-{str(i+1).zfill(6)}',
|
| 212 |
+
'timestamp': timestamp,
|
| 213 |
+
'attack_type': attack_type,
|
| 214 |
+
'severity': severity,
|
| 215 |
+
'source_ip': source_ip,
|
| 216 |
+
'source_country': country_data[0],
|
| 217 |
+
'country_code': country_data[1],
|
| 218 |
+
'latitude': country_data[2] + random.uniform(-5, 5),
|
| 219 |
+
'longitude': country_data[3] + random.uniform(-5, 5),
|
| 220 |
+
'destination_ip': dest_ip,
|
| 221 |
+
'target_country': random.choice(target_countries),
|
| 222 |
+
'port': random.choice(common_ports),
|
| 223 |
+
'protocol': random.choice(protocols),
|
| 224 |
+
'status': random.choices(statuses, weights=status_weights)[0],
|
| 225 |
+
'packets': random.randint(100, 100000),
|
| 226 |
+
'bytes': random.randint(10000, 10000000),
|
| 227 |
+
'confidence': random.randint(60, 99)
|
| 228 |
+
})
|
| 229 |
+
|
| 230 |
+
return pd.DataFrame(data)
|
| 231 |
+
|
| 232 |
+
@st.cache_data(ttl=60)
|
| 233 |
+
def generate_network_metrics():
|
| 234 |
+
"""Generate network traffic metrics"""
|
| 235 |
+
return {
|
| 236 |
+
'total_bandwidth': random.uniform(500, 1000),
|
| 237 |
+
'incoming_traffic': random.uniform(200, 500),
|
| 238 |
+
'outgoing_traffic': random.uniform(150, 400),
|
| 239 |
+
'blocked_requests': random.randint(1000, 5000),
|
| 240 |
+
'active_connections': random.randint(500, 2000),
|
| 241 |
+
'avg_response_time': random.uniform(10, 100),
|
| 242 |
+
'uptime': 99.97,
|
| 243 |
+
'threats_blocked_today': random.randint(100, 500)
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
def generate_alerts(df):
|
| 247 |
+
"""Generate recent critical alerts"""
|
| 248 |
+
critical_threats = df[df['severity'].isin(['Critical', 'High'])].sort_values('timestamp', ascending=False).head(10)
|
| 249 |
+
return critical_threats
|
| 250 |
+
|
| 251 |
+
# ============== SIDEBAR ==============
|
| 252 |
+
|
| 253 |
+
def render_sidebar():
|
| 254 |
+
with st.sidebar:
|
| 255 |
+
st.markdown("""
|
| 256 |
+
<div style='text-align: center; padding: 20px 0;'>
|
| 257 |
+
<h1 style='color: #00ffff; font-size: 1.8rem;'>π‘οΈ CyberShield</h1>
|
| 258 |
+
<p style='color: #888; font-size: 0.8rem;'>Threat Monitoring System v2.0</p>
|
| 259 |
+
</div>
|
| 260 |
+
""", unsafe_allow_html=True)
|
| 261 |
+
|
| 262 |
+
st.markdown("---")
|
| 263 |
+
|
| 264 |
+
# Navigation
|
| 265 |
+
st.markdown("### π Navigation")
|
| 266 |
+
page = st.radio(
|
| 267 |
+
"Select Page",
|
| 268 |
+
["π Overview", "π Threat Feed", "π Geographic Analysis", "π Network Analysis", "π¨ Alerts"],
|
| 269 |
+
label_visibility="collapsed"
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
st.markdown("---")
|
| 273 |
+
|
| 274 |
+
# Filters
|
| 275 |
+
st.markdown("### π Filters")
|
| 276 |
+
|
| 277 |
+
# Date range filter
|
| 278 |
+
date_range = st.date_input(
|
| 279 |
+
"Date Range",
|
| 280 |
+
value=(datetime.now() - timedelta(days=7), datetime.now()),
|
| 281 |
+
max_value=datetime.now()
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
# Severity filter
|
| 285 |
+
severity_filter = st.multiselect(
|
| 286 |
+
"Severity",
|
| 287 |
+
["Critical", "High", "Medium", "Low"],
|
| 288 |
+
default=["Critical", "High", "Medium", "Low"]
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
# Attack type filter
|
| 292 |
+
attack_types = [
|
| 293 |
+
'DDoS Attack', 'SQL Injection', 'XSS Attack', 'Brute Force',
|
| 294 |
+
'Malware', 'Phishing', 'Ransomware', 'Man-in-the-Middle',
|
| 295 |
+
'Zero-Day Exploit', 'Port Scanning', 'DNS Tunneling', 'Credential Stuffing'
|
| 296 |
+
]
|
| 297 |
+
attack_filter = st.multiselect(
|
| 298 |
+
"Attack Types",
|
| 299 |
+
attack_types,
|
| 300 |
+
default=attack_types
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
st.markdown("---")
|
| 304 |
+
|
| 305 |
+
# System status
|
| 306 |
+
st.markdown("### π» System Status")
|
| 307 |
+
st.markdown("""
|
| 308 |
+
<div style='padding: 10px; background: #1a1a2e; border-radius: 5px;'>
|
| 309 |
+
<p><span class='status-dot status-active'></span> Firewall: Active</p>
|
| 310 |
+
<p><span class='status-dot status-active'></span> IDS/IPS: Active</p>
|
| 311 |
+
<p><span class='status-dot status-active'></span> SIEM: Connected</p>
|
| 312 |
+
<p><span class='status-dot status-warning'></span> Threat Intel: Updating</p>
|
| 313 |
+
</div>
|
| 314 |
+
""", unsafe_allow_html=True)
|
| 315 |
+
|
| 316 |
+
st.markdown("---")
|
| 317 |
+
st.markdown("""
|
| 318 |
+
<div style='text-align: center; color: #666; font-size: 0.7rem;'>
|
| 319 |
+
<p>Last Updated: """ + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + """</p>
|
| 320 |
+
<p>Β© 2024 CyberShield</p>
|
| 321 |
+
</div>
|
| 322 |
+
""", unsafe_allow_html=True)
|
| 323 |
+
|
| 324 |
+
return page, date_range, severity_filter, attack_filter
|
| 325 |
+
|
| 326 |
+
# ============== PAGE FUNCTIONS ==============
|
| 327 |
+
|
| 328 |
+
def overview_page(df, metrics):
|
| 329 |
+
"""Main overview dashboard page"""
|
| 330 |
+
|
| 331 |
+
# Header
|
| 332 |
+
st.markdown("""
|
| 333 |
+
<div class='main-header'>
|
| 334 |
+
<h1>π‘οΈ CYBERSHIELD THREAT MONITOR</h1>
|
| 335 |
+
<p style='color: #888;'>Real-time Security Operations Dashboard</p>
|
| 336 |
+
</div>
|
| 337 |
+
""", unsafe_allow_html=True)
|
| 338 |
+
|
| 339 |
+
# Key metrics row
|
| 340 |
+
col1, col2, col3, col4, col5 = st.columns(5)
|
| 341 |
+
|
| 342 |
+
total_threats = len(df)
|
| 343 |
+
critical_count = len(df[df['severity'] == 'Critical'])
|
| 344 |
+
high_count = len(df[df['severity'] == 'High'])
|
| 345 |
+
blocked_count = len(df[df['status'] == 'Blocked'])
|
| 346 |
+
active_count = len(df[df['status'] == 'Active'])
|
| 347 |
+
|
| 348 |
+
with col1:
|
| 349 |
+
st.markdown(f"""
|
| 350 |
+
<div class='metric-card'>
|
| 351 |
+
<div class='metric-value'>{total_threats:,}</div>
|
| 352 |
+
<div class='metric-label'>Total Threats</div>
|
| 353 |
+
</div>
|
| 354 |
+
""", unsafe_allow_html=True)
|
| 355 |
+
|
| 356 |
+
with col2:
|
| 357 |
+
st.markdown(f"""
|
| 358 |
+
<div class='metric-card'>
|
| 359 |
+
<div class='metric-value metric-critical'>{critical_count}</div>
|
| 360 |
+
<div class='metric-label'>Critical</div>
|
| 361 |
+
</div>
|
| 362 |
+
""", unsafe_allow_html=True)
|
| 363 |
+
|
| 364 |
+
with col3:
|
| 365 |
+
st.markdown(f"""
|
| 366 |
+
<div class='metric-card'>
|
| 367 |
+
<div class='metric-value metric-high'>{high_count}</div>
|
| 368 |
+
<div class='metric-label'>High Severity</div>
|
| 369 |
+
</div>
|
| 370 |
+
""", unsafe_allow_html=True)
|
| 371 |
+
|
| 372 |
+
with col4:
|
| 373 |
+
st.markdown(f"""
|
| 374 |
+
<div class='metric-card'>
|
| 375 |
+
<div class='metric-value metric-low'>{blocked_count}</div>
|
| 376 |
+
<div class='metric-label'>Blocked</div>
|
| 377 |
+
</div>
|
| 378 |
+
""", unsafe_allow_html=True)
|
| 379 |
+
|
| 380 |
+
with col5:
|
| 381 |
+
st.markdown(f"""
|
| 382 |
+
<div class='metric-card'>
|
| 383 |
+
<div class='metric-value' style='color: #ff6b6b;'>{active_count}</div>
|
| 384 |
+
<div class='metric-label'>Active Threats</div>
|
| 385 |
+
</div>
|
| 386 |
+
""", unsafe_allow_html=True)
|
| 387 |
+
|
| 388 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 389 |
+
|
| 390 |
+
# Charts row 1
|
| 391 |
+
col1, col2 = st.columns(2)
|
| 392 |
+
|
| 393 |
+
with col1:
|
| 394 |
+
st.markdown("### π Threat Timeline (7 Days)")
|
| 395 |
+
df_timeline = df.copy()
|
| 396 |
+
df_timeline['date'] = df_timeline['timestamp'].dt.date
|
| 397 |
+
timeline_data = df_timeline.groupby(['date', 'severity']).size().reset_index(name='count')
|
| 398 |
+
|
| 399 |
+
fig_timeline = px.area(
|
| 400 |
+
timeline_data,
|
| 401 |
+
x='date',
|
| 402 |
+
y='count',
|
| 403 |
+
color='severity',
|
| 404 |
+
color_discrete_map={
|
| 405 |
+
'Critical': '#ff4757',
|
| 406 |
+
'High': '#ff7f50',
|
| 407 |
+
'Medium': '#ffa502',
|
| 408 |
+
'Low': '#2ed573'
|
| 409 |
+
}
|
| 410 |
+
)
|
| 411 |
+
fig_timeline.update_layout(
|
| 412 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 413 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 414 |
+
font_color='#888',
|
| 415 |
+
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
| 416 |
+
margin=dict(l=0, r=0, t=30, b=0),
|
| 417 |
+
xaxis=dict(gridcolor='#1a1a2e'),
|
| 418 |
+
yaxis=dict(gridcolor='#1a1a2e')
|
| 419 |
+
)
|
| 420 |
+
st.plotly_chart(fig_timeline, use_container_width=True)
|
| 421 |
+
|
| 422 |
+
with col2:
|
| 423 |
+
st.markdown("### π― Threat Severity Distribution")
|
| 424 |
+
severity_counts = df['severity'].value_counts()
|
| 425 |
+
|
| 426 |
+
fig_severity = go.Figure(data=[go.Pie(
|
| 427 |
+
labels=severity_counts.index,
|
| 428 |
+
values=severity_counts.values,
|
| 429 |
+
hole=0.6,
|
| 430 |
+
marker_colors=['#ff4757', '#ff7f50', '#ffa502', '#2ed573']
|
| 431 |
+
)])
|
| 432 |
+
fig_severity.update_layout(
|
| 433 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 434 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 435 |
+
font_color='#888',
|
| 436 |
+
showlegend=True,
|
| 437 |
+
legend=dict(orientation="h", yanchor="bottom", y=-0.1, xanchor="center", x=0.5),
|
| 438 |
+
margin=dict(l=0, r=0, t=30, b=0),
|
| 439 |
+
annotations=[dict(text='Threats', x=0.5, y=0.5, font_size=16, showarrow=False, font_color='#00ffff')]
|
| 440 |
+
)
|
| 441 |
+
st.plotly_chart(fig_severity, use_container_width=True)
|
| 442 |
+
|
| 443 |
+
# Charts row 2
|
| 444 |
+
col1, col2 = st.columns(2)
|
| 445 |
+
|
| 446 |
+
with col1:
|
| 447 |
+
st.markdown("### βοΈ Attack Type Distribution")
|
| 448 |
+
attack_counts = df['attack_type'].value_counts().head(10)
|
| 449 |
+
|
| 450 |
+
fig_attacks = px.bar(
|
| 451 |
+
x=attack_counts.values,
|
| 452 |
+
y=attack_counts.index,
|
| 453 |
+
orientation='h',
|
| 454 |
+
color=attack_counts.values,
|
| 455 |
+
color_continuous_scale='Tealgrn'
|
| 456 |
+
)
|
| 457 |
+
fig_attacks.update_layout(
|
| 458 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 459 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 460 |
+
font_color='#888',
|
| 461 |
+
showlegend=False,
|
| 462 |
+
margin=dict(l=0, r=0, t=10, b=0),
|
| 463 |
+
xaxis=dict(gridcolor='#1a1a2e', title='Count'),
|
| 464 |
+
yaxis=dict(gridcolor='#1a1a2e', title=''),
|
| 465 |
+
coloraxis_showscale=False
|
| 466 |
+
)
|
| 467 |
+
st.plotly_chart(fig_attacks, use_container_width=True)
|
| 468 |
+
|
| 469 |
+
with col2:
|
| 470 |
+
st.markdown("### π Top Targeted Ports")
|
| 471 |
+
port_counts = df['port'].value_counts().head(10)
|
| 472 |
+
|
| 473 |
+
fig_ports = px.bar(
|
| 474 |
+
x=port_counts.index.astype(str),
|
| 475 |
+
y=port_counts.values,
|
| 476 |
+
color=port_counts.values,
|
| 477 |
+
color_continuous_scale='Reds'
|
| 478 |
+
)
|
| 479 |
+
fig_ports.update_layout(
|
| 480 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 481 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 482 |
+
font_color='#888',
|
| 483 |
+
showlegend=False,
|
| 484 |
+
margin=dict(l=0, r=0, t=10, b=0),
|
| 485 |
+
xaxis=dict(gridcolor='#1a1a2e', title='Port'),
|
| 486 |
+
yaxis=dict(gridcolor='#1a1a2e', title='Attacks'),
|
| 487 |
+
coloraxis_showscale=False
|
| 488 |
+
)
|
| 489 |
+
st.plotly_chart(fig_ports, use_container_width=True)
|
| 490 |
+
|
| 491 |
+
# Recent alerts section
|
| 492 |
+
st.markdown("### π¨ Recent Critical Alerts")
|
| 493 |
+
recent_critical = df[df['severity'].isin(['Critical', 'High'])].sort_values('timestamp', ascending=False).head(5)
|
| 494 |
+
|
| 495 |
+
for _, alert in recent_critical.iterrows():
|
| 496 |
+
alert_class = 'alert-critical' if alert['severity'] == 'Critical' else 'alert-high'
|
| 497 |
+
st.markdown(f"""
|
| 498 |
+
<div class='{alert_class}'>
|
| 499 |
+
<strong>{alert['severity'].upper()}</strong> | {alert['attack_type']} |
|
| 500 |
+
Source: {alert['source_ip']} ({alert['source_country']}) |
|
| 501 |
+
Target Port: {alert['port']} |
|
| 502 |
+
{alert['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}
|
| 503 |
+
</div>
|
| 504 |
+
""", unsafe_allow_html=True)
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
def threat_feed_page(df):
|
| 508 |
+
"""Threat feed page with detailed table"""
|
| 509 |
+
|
| 510 |
+
st.markdown("## π Live Threat Feed")
|
| 511 |
+
st.markdown("Real-time view of all detected threats with detailed information")
|
| 512 |
+
|
| 513 |
+
# Search functionality
|
| 514 |
+
search = st.text_input("π Search (IP, Attack Type, Country)", "")
|
| 515 |
+
|
| 516 |
+
# Apply search filter
|
| 517 |
+
if search:
|
| 518 |
+
mask = (
|
| 519 |
+
df['source_ip'].str.contains(search, case=False, na=False) |
|
| 520 |
+
df['attack_type'].str.contains(search, case=False, na=False) |
|
| 521 |
+
df['source_country'].str.contains(search, case=False, na=False)
|
| 522 |
+
)
|
| 523 |
+
filtered_df = df[mask]
|
| 524 |
+
else:
|
| 525 |
+
filtered_df = df
|
| 526 |
+
|
| 527 |
+
# Sort by timestamp
|
| 528 |
+
filtered_df = filtered_df.sort_values('timestamp', ascending=False)
|
| 529 |
+
|
| 530 |
+
# Display metrics
|
| 531 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 532 |
+
col1.metric("Total Records", len(filtered_df))
|
| 533 |
+
col2.metric("Critical", len(filtered_df[filtered_df['severity'] == 'Critical']))
|
| 534 |
+
col3.metric("Active", len(filtered_df[filtered_df['status'] == 'Active']))
|
| 535 |
+
col4.metric("Blocked", len(filtered_df[filtered_df['status'] == 'Blocked']))
|
| 536 |
+
|
| 537 |
+
# Format dataframe for display
|
| 538 |
+
display_df = filtered_df[[
|
| 539 |
+
'id', 'timestamp', 'attack_type', 'severity', 'source_ip',
|
| 540 |
+
'source_country', 'port', 'protocol', 'status', 'confidence'
|
| 541 |
+
]].copy()
|
| 542 |
+
display_df['timestamp'] = display_df['timestamp'].dt.strftime('%Y-%m-%d %H:%M:%S')
|
| 543 |
+
display_df.columns = ['ID', 'Timestamp', 'Attack Type', 'Severity', 'Source IP',
|
| 544 |
+
'Country', 'Port', 'Protocol', 'Status', 'Confidence %']
|
| 545 |
+
|
| 546 |
+
# Color code severity
|
| 547 |
+
def highlight_severity(row):
|
| 548 |
+
colors = {
|
| 549 |
+
'Critical': 'background-color: rgba(255,71,87,0.3)',
|
| 550 |
+
'High': 'background-color: rgba(255,127,80,0.3)',
|
| 551 |
+
'Medium': 'background-color: rgba(255,165,2,0.3)',
|
| 552 |
+
'Low': 'background-color: rgba(46,213,115,0.3)'
|
| 553 |
+
}
|
| 554 |
+
return [colors.get(row['Severity'], '')] * len(row)
|
| 555 |
+
|
| 556 |
+
st.dataframe(
|
| 557 |
+
display_df.head(100).style.apply(highlight_severity, axis=1),
|
| 558 |
+
use_container_width=True,
|
| 559 |
+
height=600
|
| 560 |
+
)
|
| 561 |
+
|
| 562 |
+
st.info(f"Showing {min(100, len(display_df))} of {len(display_df)} records")
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
def geographic_page(df):
|
| 566 |
+
"""Geographic analysis page with world map"""
|
| 567 |
+
|
| 568 |
+
st.markdown("## π Geographic Threat Analysis")
|
| 569 |
+
st.markdown("Visualize attack origins and target locations worldwide")
|
| 570 |
+
|
| 571 |
+
col1, col2 = st.columns([2, 1])
|
| 572 |
+
|
| 573 |
+
with col1:
|
| 574 |
+
st.markdown("### πΊοΈ Global Attack Origins")
|
| 575 |
+
|
| 576 |
+
# Aggregate by country for bubble size
|
| 577 |
+
country_stats = df.groupby(['source_country', 'country_code', 'latitude', 'longitude']).agg({
|
| 578 |
+
'id': 'count',
|
| 579 |
+
'severity': lambda x: (x == 'Critical').sum()
|
| 580 |
+
}).reset_index()
|
| 581 |
+
country_stats.columns = ['country', 'code', 'lat', 'lon', 'total_attacks', 'critical_attacks']
|
| 582 |
+
|
| 583 |
+
# Create map
|
| 584 |
+
fig_map = px.scatter_geo(
|
| 585 |
+
country_stats,
|
| 586 |
+
lat='lat',
|
| 587 |
+
lon='lon',
|
| 588 |
+
size='total_attacks',
|
| 589 |
+
color='critical_attacks',
|
| 590 |
+
hover_name='country',
|
| 591 |
+
hover_data={'total_attacks': True, 'critical_attacks': True, 'lat': False, 'lon': False},
|
| 592 |
+
color_continuous_scale='Reds',
|
| 593 |
+
size_max=50,
|
| 594 |
+
projection='natural earth'
|
| 595 |
+
)
|
| 596 |
+
|
| 597 |
+
fig_map.update_layout(
|
| 598 |
+
geo=dict(
|
| 599 |
+
bgcolor='rgba(0,0,0,0)',
|
| 600 |
+
landcolor='#1a1a2e',
|
| 601 |
+
oceancolor='#0a0a0f',
|
| 602 |
+
countrycolor='#333',
|
| 603 |
+
coastlinecolor='#333',
|
| 604 |
+
showocean=True,
|
| 605 |
+
showland=True,
|
| 606 |
+
showcountries=True
|
| 607 |
+
),
|
| 608 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 609 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 610 |
+
font_color='#888',
|
| 611 |
+
margin=dict(l=0, r=0, t=0, b=0),
|
| 612 |
+
coloraxis_colorbar=dict(title='Critical')
|
| 613 |
+
)
|
| 614 |
+
|
| 615 |
+
st.plotly_chart(fig_map, use_container_width=True)
|
| 616 |
+
|
| 617 |
+
with col2:
|
| 618 |
+
st.markdown("### π΄ Top Attack Sources")
|
| 619 |
+
top_countries = df['source_country'].value_counts().head(10)
|
| 620 |
+
|
| 621 |
+
fig_countries = px.bar(
|
| 622 |
+
x=top_countries.values,
|
| 623 |
+
y=top_countries.index,
|
| 624 |
+
orientation='h',
|
| 625 |
+
color=top_countries.values,
|
| 626 |
+
color_continuous_scale='Reds'
|
| 627 |
+
)
|
| 628 |
+
fig_countries.update_layout(
|
| 629 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 630 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 631 |
+
font_color='#888',
|
| 632 |
+
showlegend=False,
|
| 633 |
+
margin=dict(l=0, r=0, t=10, b=0),
|
| 634 |
+
xaxis=dict(gridcolor='#1a1a2e', title='Attacks'),
|
| 635 |
+
yaxis=dict(gridcolor='#1a1a2e', title=''),
|
| 636 |
+
coloraxis_showscale=False,
|
| 637 |
+
height=400
|
| 638 |
+
)
|
| 639 |
+
st.plotly_chart(fig_countries, use_container_width=True)
|
| 640 |
+
|
| 641 |
+
# Target countries
|
| 642 |
+
col1, col2 = st.columns(2)
|
| 643 |
+
|
| 644 |
+
with col1:
|
| 645 |
+
st.markdown("### π― Top Targeted Countries")
|
| 646 |
+
target_counts = df['target_country'].value_counts()
|
| 647 |
+
|
| 648 |
+
fig_targets = px.pie(
|
| 649 |
+
values=target_counts.values,
|
| 650 |
+
names=target_counts.index,
|
| 651 |
+
hole=0.4,
|
| 652 |
+
color_discrete_sequence=px.colors.sequential.Tealgrn
|
| 653 |
+
)
|
| 654 |
+
fig_targets.update_layout(
|
| 655 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 656 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 657 |
+
font_color='#888',
|
| 658 |
+
margin=dict(l=0, r=0, t=10, b=0)
|
| 659 |
+
)
|
| 660 |
+
st.plotly_chart(fig_targets, use_container_width=True)
|
| 661 |
+
|
| 662 |
+
with col2:
|
| 663 |
+
st.markdown("### π Attack Flow by Country")
|
| 664 |
+
# Country-wise severity breakdown
|
| 665 |
+
country_severity = df.groupby(['source_country', 'severity']).size().reset_index(name='count')
|
| 666 |
+
country_severity = country_severity[country_severity['source_country'].isin(top_countries.index[:8])]
|
| 667 |
+
|
| 668 |
+
fig_flow = px.bar(
|
| 669 |
+
country_severity,
|
| 670 |
+
x='source_country',
|
| 671 |
+
y='count',
|
| 672 |
+
color='severity',
|
| 673 |
+
barmode='stack',
|
| 674 |
+
color_discrete_map={
|
| 675 |
+
'Critical': '#ff4757',
|
| 676 |
+
'High': '#ff7f50',
|
| 677 |
+
'Medium': '#ffa502',
|
| 678 |
+
'Low': '#2ed573'
|
| 679 |
+
}
|
| 680 |
+
)
|
| 681 |
+
fig_flow.update_layout(
|
| 682 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 683 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 684 |
+
font_color='#888',
|
| 685 |
+
margin=dict(l=0, r=0, t=10, b=0),
|
| 686 |
+
xaxis=dict(gridcolor='#1a1a2e', title=''),
|
| 687 |
+
yaxis=dict(gridcolor='#1a1a2e', title='Attacks'),
|
| 688 |
+
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
|
| 689 |
+
)
|
| 690 |
+
st.plotly_chart(fig_flow, use_container_width=True)
|
| 691 |
+
|
| 692 |
+
|
| 693 |
+
def network_page(df, metrics):
|
| 694 |
+
"""Network analysis page"""
|
| 695 |
+
|
| 696 |
+
st.markdown("## π Network Analysis")
|
| 697 |
+
st.markdown("Monitor network traffic and identify suspicious patterns")
|
| 698 |
+
|
| 699 |
+
# Network metrics
|
| 700 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 701 |
+
|
| 702 |
+
with col1:
|
| 703 |
+
st.markdown(f"""
|
| 704 |
+
<div class='metric-card'>
|
| 705 |
+
<div class='metric-value'>{metrics['total_bandwidth']:.1f}</div>
|
| 706 |
+
<div class='metric-label'>Bandwidth (Gbps)</div>
|
| 707 |
+
</div>
|
| 708 |
+
""", unsafe_allow_html=True)
|
| 709 |
+
|
| 710 |
+
with col2:
|
| 711 |
+
st.markdown(f"""
|
| 712 |
+
<div class='metric-card'>
|
| 713 |
+
<div class='metric-value'>{metrics['active_connections']:,}</div>
|
| 714 |
+
<div class='metric-label'>Active Connections</div>
|
| 715 |
+
</div>
|
| 716 |
+
""", unsafe_allow_html=True)
|
| 717 |
+
|
| 718 |
+
with col3:
|
| 719 |
+
st.markdown(f"""
|
| 720 |
+
<div class='metric-card'>
|
| 721 |
+
<div class='metric-value metric-low'>{metrics['uptime']}%</div>
|
| 722 |
+
<div class='metric-label'>System Uptime</div>
|
| 723 |
+
</div>
|
| 724 |
+
""", unsafe_allow_html=True)
|
| 725 |
+
|
| 726 |
+
with col4:
|
| 727 |
+
st.markdown(f"""
|
| 728 |
+
<div class='metric-card'>
|
| 729 |
+
<div class='metric-value'>{metrics['avg_response_time']:.1f}ms</div>
|
| 730 |
+
<div class='metric-label'>Avg Response Time</div>
|
| 731 |
+
</div>
|
| 732 |
+
""", unsafe_allow_html=True)
|
| 733 |
+
|
| 734 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 735 |
+
|
| 736 |
+
# Charts
|
| 737 |
+
col1, col2 = st.columns(2)
|
| 738 |
+
|
| 739 |
+
with col1:
|
| 740 |
+
st.markdown("### π Top Attacker IPs")
|
| 741 |
+
top_ips = df.groupby('source_ip').agg({
|
| 742 |
+
'id': 'count',
|
| 743 |
+
'severity': lambda x: (x == 'Critical').sum(),
|
| 744 |
+
'source_country': 'first'
|
| 745 |
+
}).reset_index()
|
| 746 |
+
top_ips.columns = ['IP Address', 'Total Attacks', 'Critical', 'Country']
|
| 747 |
+
top_ips = top_ips.sort_values('Total Attacks', ascending=False).head(10)
|
| 748 |
+
|
| 749 |
+
st.dataframe(
|
| 750 |
+
top_ips,
|
| 751 |
+
use_container_width=True,
|
| 752 |
+
hide_index=True
|
| 753 |
+
)
|
| 754 |
+
|
| 755 |
+
with col2:
|
| 756 |
+
st.markdown("### π‘ Protocol Distribution")
|
| 757 |
+
protocol_counts = df['protocol'].value_counts()
|
| 758 |
+
|
| 759 |
+
fig_protocol = px.pie(
|
| 760 |
+
values=protocol_counts.values,
|
| 761 |
+
names=protocol_counts.index,
|
| 762 |
+
color_discrete_sequence=px.colors.sequential.Plasma
|
| 763 |
+
)
|
| 764 |
+
fig_protocol.update_layout(
|
| 765 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 766 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 767 |
+
font_color='#888',
|
| 768 |
+
margin=dict(l=0, r=0, t=10, b=0)
|
| 769 |
+
)
|
| 770 |
+
st.plotly_chart(fig_protocol, use_container_width=True)
|
| 771 |
+
|
| 772 |
+
# Traffic gauge
|
| 773 |
+
col1, col2 = st.columns(2)
|
| 774 |
+
|
| 775 |
+
with col1:
|
| 776 |
+
st.markdown("### π₯ Incoming Traffic")
|
| 777 |
+
fig_in = go.Figure(go.Indicator(
|
| 778 |
+
mode="gauge+number",
|
| 779 |
+
value=metrics['incoming_traffic'],
|
| 780 |
+
title={'text': "Gbps", 'font': {'color': '#888'}},
|
| 781 |
+
gauge={
|
| 782 |
+
'axis': {'range': [0, 600], 'tickcolor': '#888'},
|
| 783 |
+
'bar': {'color': '#00ffff'},
|
| 784 |
+
'bgcolor': '#1a1a2e',
|
| 785 |
+
'bordercolor': '#333',
|
| 786 |
+
'steps': [
|
| 787 |
+
{'range': [0, 200], 'color': '#1a3a1a'},
|
| 788 |
+
{'range': [200, 400], 'color': '#3a3a1a'},
|
| 789 |
+
{'range': [400, 600], 'color': '#3a1a1a'}
|
| 790 |
+
]
|
| 791 |
+
}
|
| 792 |
+
))
|
| 793 |
+
fig_in.update_layout(
|
| 794 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 795 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 796 |
+
font_color='#888',
|
| 797 |
+
height=250,
|
| 798 |
+
margin=dict(l=20, r=20, t=40, b=20)
|
| 799 |
+
)
|
| 800 |
+
st.plotly_chart(fig_in, use_container_width=True)
|
| 801 |
+
|
| 802 |
+
with col2:
|
| 803 |
+
st.markdown("### π€ Outgoing Traffic")
|
| 804 |
+
fig_out = go.Figure(go.Indicator(
|
| 805 |
+
mode="gauge+number",
|
| 806 |
+
value=metrics['outgoing_traffic'],
|
| 807 |
+
title={'text': "Gbps", 'font': {'color': '#888'}},
|
| 808 |
+
gauge={
|
| 809 |
+
'axis': {'range': [0, 600], 'tickcolor': '#888'},
|
| 810 |
+
'bar': {'color': '#2ed573'},
|
| 811 |
+
'bgcolor': '#1a1a2e',
|
| 812 |
+
'bordercolor': '#333',
|
| 813 |
+
'steps': [
|
| 814 |
+
{'range': [0, 200], 'color': '#1a3a1a'},
|
| 815 |
+
{'range': [200, 400], 'color': '#3a3a1a'},
|
| 816 |
+
{'range': [400, 600], 'color': '#3a1a1a'}
|
| 817 |
+
]
|
| 818 |
+
}
|
| 819 |
+
))
|
| 820 |
+
fig_out.update_layout(
|
| 821 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 822 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 823 |
+
font_color='#888',
|
| 824 |
+
height=250,
|
| 825 |
+
margin=dict(l=20, r=20, t=40, b=20)
|
| 826 |
+
)
|
| 827 |
+
st.plotly_chart(fig_out, use_container_width=True)
|
| 828 |
+
|
| 829 |
+
# Hourly traffic pattern
|
| 830 |
+
st.markdown("### π Hourly Traffic Pattern")
|
| 831 |
+
df_hourly = df.copy()
|
| 832 |
+
df_hourly['hour'] = df_hourly['timestamp'].dt.hour
|
| 833 |
+
hourly_counts = df_hourly.groupby('hour').size().reset_index(name='attacks')
|
| 834 |
+
|
| 835 |
+
fig_hourly = px.line(
|
| 836 |
+
hourly_counts,
|
| 837 |
+
x='hour',
|
| 838 |
+
y='attacks',
|
| 839 |
+
markers=True
|
| 840 |
+
)
|
| 841 |
+
fig_hourly.update_traces(line_color='#00ffff', marker_color='#00ffff')
|
| 842 |
+
fig_hourly.update_layout(
|
| 843 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 844 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 845 |
+
font_color='#888',
|
| 846 |
+
margin=dict(l=0, r=0, t=10, b=0),
|
| 847 |
+
xaxis=dict(gridcolor='#1a1a2e', title='Hour of Day', dtick=2),
|
| 848 |
+
yaxis=dict(gridcolor='#1a1a2e', title='Number of Attacks')
|
| 849 |
+
)
|
| 850 |
+
st.plotly_chart(fig_hourly, use_container_width=True)
|
| 851 |
+
|
| 852 |
+
|
| 853 |
+
def alerts_page(df):
|
| 854 |
+
"""Alerts and notifications page"""
|
| 855 |
+
|
| 856 |
+
st.markdown("## π¨ Security Alerts")
|
| 857 |
+
st.markdown("Critical and high-severity alerts requiring immediate attention")
|
| 858 |
+
|
| 859 |
+
# Alert summary
|
| 860 |
+
critical_alerts = df[df['severity'] == 'Critical']
|
| 861 |
+
high_alerts = df[df['severity'] == 'High']
|
| 862 |
+
active_alerts = df[df['status'] == 'Active']
|
| 863 |
+
|
| 864 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 865 |
+
|
| 866 |
+
with col1:
|
| 867 |
+
st.markdown(f"""
|
| 868 |
+
<div class='metric-card'>
|
| 869 |
+
<div class='metric-value metric-critical'>{len(critical_alerts)}</div>
|
| 870 |
+
<div class='metric-label'>Critical Alerts</div>
|
| 871 |
+
</div>
|
| 872 |
+
""", unsafe_allow_html=True)
|
| 873 |
+
|
| 874 |
+
with col2:
|
| 875 |
+
st.markdown(f"""
|
| 876 |
+
<div class='metric-card'>
|
| 877 |
+
<div class='metric-value metric-high'>{len(high_alerts)}</div>
|
| 878 |
+
<div class='metric-label'>High Alerts</div>
|
| 879 |
+
</div>
|
| 880 |
+
""", unsafe_allow_html=True)
|
| 881 |
+
|
| 882 |
+
with col3:
|
| 883 |
+
st.markdown(f"""
|
| 884 |
+
<div class='metric-card'>
|
| 885 |
+
<div class='metric-value' style='color: #ff6b6b;'>{len(active_alerts)}</div>
|
| 886 |
+
<div class='metric-label'>Active Threats</div>
|
| 887 |
+
</div>
|
| 888 |
+
""", unsafe_allow_html=True)
|
| 889 |
+
|
| 890 |
+
with col4:
|
| 891 |
+
mitigated = len(df[df['status'] == 'Mitigated'])
|
| 892 |
+
st.markdown(f"""
|
| 893 |
+
<div class='metric-card'>
|
| 894 |
+
<div class='metric-value metric-low'>{mitigated}</div>
|
| 895 |
+
<div class='metric-label'>Mitigated</div>
|
| 896 |
+
</div>
|
| 897 |
+
""", unsafe_allow_html=True)
|
| 898 |
+
|
| 899 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 900 |
+
|
| 901 |
+
# Critical alerts list
|
| 902 |
+
st.markdown("### π΄ Critical Alerts")
|
| 903 |
+
critical_list = critical_alerts.sort_values('timestamp', ascending=False).head(15)
|
| 904 |
+
|
| 905 |
+
for _, alert in critical_list.iterrows():
|
| 906 |
+
with st.expander(f"β οΈ {alert['id']} - {alert['attack_type']} | {alert['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}"):
|
| 907 |
+
col1, col2, col3 = st.columns(3)
|
| 908 |
+
with col1:
|
| 909 |
+
st.markdown(f"""
|
| 910 |
+
**Source IP:** {alert['source_ip']}
|
| 911 |
+
**Country:** {alert['source_country']}
|
| 912 |
+
**Protocol:** {alert['protocol']}
|
| 913 |
+
""")
|
| 914 |
+
with col2:
|
| 915 |
+
st.markdown(f"""
|
| 916 |
+
**Target Port:** {alert['port']}
|
| 917 |
+
**Target Country:** {alert['target_country']}
|
| 918 |
+
**Destination IP:** {alert['destination_ip']}
|
| 919 |
+
""")
|
| 920 |
+
with col3:
|
| 921 |
+
st.markdown(f"""
|
| 922 |
+
**Status:** {alert['status']}
|
| 923 |
+
**Confidence:** {alert['confidence']}%
|
| 924 |
+
**Packets:** {alert['packets']:,}
|
| 925 |
+
""")
|
| 926 |
+
|
| 927 |
+
st.progress(alert['confidence'] / 100)
|
| 928 |
+
|
| 929 |
+
col1, col2, col3 = st.columns(3)
|
| 930 |
+
with col1:
|
| 931 |
+
st.button("π‘οΈ Block IP", key=f"block_{alert['id']}")
|
| 932 |
+
with col2:
|
| 933 |
+
st.button("π Investigate", key=f"inv_{alert['id']}")
|
| 934 |
+
with col3:
|
| 935 |
+
st.button("β
Mark Resolved", key=f"resolve_{alert['id']}")
|
| 936 |
+
|
| 937 |
+
st.markdown("---")
|
| 938 |
+
|
| 939 |
+
# High severity alerts
|
| 940 |
+
st.markdown("### π High Severity Alerts")
|
| 941 |
+
high_list = high_alerts.sort_values('timestamp', ascending=False).head(10)
|
| 942 |
+
|
| 943 |
+
for _, alert in high_list.iterrows():
|
| 944 |
+
st.markdown(f"""
|
| 945 |
+
<div class='alert-high'>
|
| 946 |
+
<strong>{alert['id']}</strong> | {alert['attack_type']} |
|
| 947 |
+
Source: {alert['source_ip']} ({alert['source_country']}) β
|
| 948 |
+
Port {alert['port']} | Status: {alert['status']} |
|
| 949 |
+
{alert['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}
|
| 950 |
+
</div>
|
| 951 |
+
""", unsafe_allow_html=True)
|
| 952 |
+
|
| 953 |
+
|
| 954 |
+
# ============== MAIN APP ==============
|
| 955 |
+
|
| 956 |
+
def main():
|
| 957 |
+
# Get sidebar selections
|
| 958 |
+
page, date_range, severity_filter, attack_filter = render_sidebar()
|
| 959 |
+
|
| 960 |
+
# Generate data
|
| 961 |
+
df = generate_threat_data(500)
|
| 962 |
+
metrics = generate_network_metrics()
|
| 963 |
+
|
| 964 |
+
# Apply filters
|
| 965 |
+
if len(date_range) == 2:
|
| 966 |
+
start_date, end_date = date_range
|
| 967 |
+
df = df[(df['timestamp'].dt.date >= start_date) & (df['timestamp'].dt.date <= end_date)]
|
| 968 |
+
|
| 969 |
+
df = df[df['severity'].isin(severity_filter)]
|
| 970 |
+
df = df[df['attack_type'].isin(attack_filter)]
|
| 971 |
+
|
| 972 |
+
# Render selected page
|
| 973 |
+
if page == "π Overview":
|
| 974 |
+
overview_page(df, metrics)
|
| 975 |
+
elif page == "π Threat Feed":
|
| 976 |
+
threat_feed_page(df)
|
| 977 |
+
elif page == "π Geographic Analysis":
|
| 978 |
+
geographic_page(df)
|
| 979 |
+
elif page == "π Network Analysis":
|
| 980 |
+
network_page(df, metrics)
|
| 981 |
+
elif page == "π¨ Alerts":
|
| 982 |
+
alerts_page(df)
|
| 983 |
+
|
| 984 |
+
if __name__ == "__main__":
|
| 985 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit==1.31.0
|
| 2 |
+
pandas==2.2.0
|
| 3 |
+
numpy==1.26.4
|
| 4 |
+
plotly==5.18.0
|