feat: Add security mechanisms list and GeoIP stats to admin dashboard
- New 'Mechanisms' tab listing all security features with star ratings (5★=critical) - New 'GeoIP' tab with blocking statistics (daily/monthly/yearly/total) - Country breakdown with flags for blocked connections - Status indicators for each security mechanism Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
341ce29aa9
commit
e9e37796c7
61
app.py
61
app.py
@ -10615,12 +10615,71 @@ def admin_security():
|
||||
'alert_breakdown': {a.alert_type: a.count for a in alert_breakdown}
|
||||
}
|
||||
|
||||
# GeoIP stats
|
||||
from security_service import _get_geoip_enabled
|
||||
geoip_enabled = _get_geoip_enabled()
|
||||
|
||||
geoip_stats = {'today': 0, 'this_month': 0, 'this_year': 0, 'total': 0, 'by_country': []}
|
||||
|
||||
if geoip_enabled:
|
||||
today = datetime.now().date()
|
||||
first_of_month = today.replace(day=1)
|
||||
first_of_year = today.replace(month=1, day=1)
|
||||
|
||||
# Count geo_blocked alerts
|
||||
geoip_stats['today'] = db.query(SecurityAlert).filter(
|
||||
SecurityAlert.alert_type == 'geo_blocked',
|
||||
func.date(SecurityAlert.created_at) == today
|
||||
).count()
|
||||
|
||||
geoip_stats['this_month'] = db.query(SecurityAlert).filter(
|
||||
SecurityAlert.alert_type == 'geo_blocked',
|
||||
func.date(SecurityAlert.created_at) >= first_of_month
|
||||
).count()
|
||||
|
||||
geoip_stats['this_year'] = db.query(SecurityAlert).filter(
|
||||
SecurityAlert.alert_type == 'geo_blocked',
|
||||
func.date(SecurityAlert.created_at) >= first_of_year
|
||||
).count()
|
||||
|
||||
geoip_stats['total'] = db.query(SecurityAlert).filter(
|
||||
SecurityAlert.alert_type == 'geo_blocked'
|
||||
).count()
|
||||
|
||||
# Country breakdown (from details JSON)
|
||||
country_flags = {
|
||||
'RU': ('🇷🇺', 'Rosja'), 'CN': ('🇨🇳', 'Chiny'), 'KP': ('🇰🇵', 'Korea Płn.'),
|
||||
'IR': ('🇮🇷', 'Iran'), 'BY': ('🇧🇾', 'Białoruś'), 'SY': ('🇸🇾', 'Syria'),
|
||||
'VE': ('🇻🇪', 'Wenezuela'), 'CU': ('🇨🇺', 'Kuba')
|
||||
}
|
||||
|
||||
geo_alerts = db.query(SecurityAlert).filter(
|
||||
SecurityAlert.alert_type == 'geo_blocked'
|
||||
).all()
|
||||
|
||||
country_counts = {}
|
||||
for alert in geo_alerts:
|
||||
if alert.details and 'country' in alert.details:
|
||||
country = alert.details['country']
|
||||
if country:
|
||||
country_counts[country] = country_counts.get(country, 0) + 1
|
||||
|
||||
# Sort by count descending
|
||||
sorted_countries = sorted(country_counts.items(), key=lambda x: x[1], reverse=True)
|
||||
for code, count in sorted_countries:
|
||||
flag, name = country_flags.get(code, ('🏴', code))
|
||||
geoip_stats['by_country'].append({
|
||||
'code': code, 'flag': flag, 'name': name, 'count': count
|
||||
})
|
||||
|
||||
return render_template(
|
||||
'admin/security_dashboard.html',
|
||||
audit_logs=audit_logs,
|
||||
alerts=alerts,
|
||||
locked_accounts=locked_accounts,
|
||||
stats=stats
|
||||
stats=stats,
|
||||
geoip_enabled=geoip_enabled,
|
||||
geoip_stats=geoip_stats
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@ -236,6 +236,116 @@
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Security mechanisms styles */
|
||||
.mechanisms-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.mechanism-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-lg);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
background: var(--background);
|
||||
border-radius: var(--radius);
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.mechanism-item.stars-5 { border-left-color: #22c55e; }
|
||||
.mechanism-item.stars-4 { border-left-color: #3b82f6; }
|
||||
.mechanism-item.stars-3 { border-left-color: #f59e0b; }
|
||||
|
||||
.mechanism-stars {
|
||||
font-size: 1rem;
|
||||
color: #fbbf24;
|
||||
min-width: 100px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.mechanism-name {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mechanism-desc {
|
||||
flex: 2;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.mechanism-status {
|
||||
font-size: var(--font-size-xs);
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mechanism-status.active {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.mechanism-status.inactive {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
/* GeoIP stats */
|
||||
.geoip-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.geoip-stat {
|
||||
background: var(--background);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.geoip-stat-value {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.geoip-stat-label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-secondary);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.country-breakdown {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.country-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
padding: var(--spacing-xs) var(--spacing-md);
|
||||
background: var(--background);
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.country-flag {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.country-count {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@ -267,13 +377,175 @@
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="tabs">
|
||||
<button class="tab active" onclick="showTab('alerts')">Alerty bezpieczeństwa</button>
|
||||
<button class="tab active" onclick="showTab('mechanisms')">Mechanizmy</button>
|
||||
<button class="tab" onclick="showTab('geoip')">GeoIP</button>
|
||||
<button class="tab" onclick="showTab('alerts')">Alerty</button>
|
||||
<button class="tab" onclick="showTab('audit')">Audit log</button>
|
||||
<button class="tab" onclick="showTab('locked')">Zablokowane konta</button>
|
||||
</div>
|
||||
|
||||
<!-- Mechanisms Tab -->
|
||||
<div id="tab-mechanisms" class="tab-content active">
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2>🔐 Mechanizmy bezpieczeństwa</h2>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin-bottom: var(--spacing-lg);">
|
||||
Lista wszystkich aktywnych mechanizmów ochrony systemu, posortowana według ważności (5★ = krytyczne).
|
||||
</p>
|
||||
|
||||
<div class="mechanisms-list">
|
||||
<!-- 5 stars - CRITICAL -->
|
||||
<div class="mechanism-item stars-5">
|
||||
<span class="mechanism-stars">★★★★★</span>
|
||||
<span class="mechanism-name">Uwierzytelnianie dwuskładnikowe (2FA)</span>
|
||||
<span class="mechanism-desc">TOTP przez aplikacje mobilne (Google/Microsoft Authenticator)</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-5">
|
||||
<span class="mechanism-stars">★★★★★</span>
|
||||
<span class="mechanism-name">Ochrona przed CSRF</span>
|
||||
<span class="mechanism-desc">Tokeny CSRF w formularzach (Flask-WTF)</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-5">
|
||||
<span class="mechanism-stars">★★★★★</span>
|
||||
<span class="mechanism-name">Szyfrowanie połączeń (HTTPS/TLS)</span>
|
||||
<span class="mechanism-desc">Let's Encrypt SSL z automatycznym odnowieniem</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-5">
|
||||
<span class="mechanism-stars">★★★★★</span>
|
||||
<span class="mechanism-name">Hashowanie haseł</span>
|
||||
<span class="mechanism-desc">Bezpieczne przechowywanie z Werkzeug (bcrypt)</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-5">
|
||||
<span class="mechanism-stars">★★★★★</span>
|
||||
<span class="mechanism-name">Ochrona przed SQL Injection</span>
|
||||
<span class="mechanism-desc">Parametryzowane zapytania przez SQLAlchemy ORM</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-5">
|
||||
<span class="mechanism-stars">★★★★★</span>
|
||||
<span class="mechanism-name">Ochrona przed XSS</span>
|
||||
<span class="mechanism-desc">Automatyczne escapowanie w szablonach Jinja2</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
|
||||
<!-- 4 stars - IMPORTANT -->
|
||||
<div class="mechanism-item stars-4">
|
||||
<span class="mechanism-stars">★★★★☆</span>
|
||||
<span class="mechanism-name">Blokowanie geograficzne (GeoIP)</span>
|
||||
<span class="mechanism-desc">Blokada krajów wysokiego ryzyka: RU, CN, KP, IR, BY, SY, VE, CU</span>
|
||||
<span class="mechanism-status {{ 'active' if geoip_enabled else 'inactive' }}">{{ 'Aktywne' if geoip_enabled else 'Nieaktywne' }}</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-4">
|
||||
<span class="mechanism-stars">★★★★☆</span>
|
||||
<span class="mechanism-name">Rate Limiting</span>
|
||||
<span class="mechanism-desc">Ograniczenie żądań przez Flask-Limiter z Redis</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-4">
|
||||
<span class="mechanism-stars">★★★★☆</span>
|
||||
<span class="mechanism-name">Blokada konta</span>
|
||||
<span class="mechanism-desc">Automatyczna blokada po 5 nieudanych logowaniach</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-4">
|
||||
<span class="mechanism-stars">★★★★☆</span>
|
||||
<span class="mechanism-name">Audit Log</span>
|
||||
<span class="mechanism-desc">Śledzenie wszystkich działań administracyjnych</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-4">
|
||||
<span class="mechanism-stars">★★★★☆</span>
|
||||
<span class="mechanism-name">Bezpieczne sesje</span>
|
||||
<span class="mechanism-desc">Zarządzanie sesjami przez Flask-Login</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
|
||||
<!-- 3 stars - ADDITIONAL -->
|
||||
<div class="mechanism-item stars-3">
|
||||
<span class="mechanism-stars">★★★☆☆</span>
|
||||
<span class="mechanism-name">Honeypot endpoints</span>
|
||||
<span class="mechanism-desc">Wykrywanie skanerów i botów (/.env, /wp-admin, /phpmyadmin)</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-3">
|
||||
<span class="mechanism-stars">★★★☆☆</span>
|
||||
<span class="mechanism-name">Security Alerting</span>
|
||||
<span class="mechanism-desc">Powiadomienia email o krytycznych zdarzeniach</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
<div class="mechanism-item stars-3">
|
||||
<span class="mechanism-stars">★★★☆☆</span>
|
||||
<span class="mechanism-name">Security Logging</span>
|
||||
<span class="mechanism-desc">Centralne logowanie zdarzeń bezpieczeństwa</span>
|
||||
<span class="mechanism-status active">Aktywne</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GeoIP Tab -->
|
||||
<div id="tab-geoip" class="tab-content">
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2>🌍 Statystyki GeoIP Blocking</h2>
|
||||
</div>
|
||||
|
||||
{% if geoip_enabled %}
|
||||
<div class="geoip-stats">
|
||||
<div class="geoip-stat">
|
||||
<div class="geoip-stat-value">{{ geoip_stats.today }}</div>
|
||||
<div class="geoip-stat-label">Zablokowanych dziś</div>
|
||||
</div>
|
||||
<div class="geoip-stat">
|
||||
<div class="geoip-stat-value">{{ geoip_stats.this_month }}</div>
|
||||
<div class="geoip-stat-label">W tym miesiącu</div>
|
||||
</div>
|
||||
<div class="geoip-stat">
|
||||
<div class="geoip-stat-value">{{ geoip_stats.this_year }}</div>
|
||||
<div class="geoip-stat-label">W tym roku</div>
|
||||
</div>
|
||||
<div class="geoip-stat">
|
||||
<div class="geoip-stat-value">{{ geoip_stats.total }}</div>
|
||||
<div class="geoip-stat-label">Od początku</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-bottom: var(--spacing-md);">Zablokowane kraje</h3>
|
||||
<p style="color: var(--text-secondary); font-size: var(--font-size-sm); margin-bottom: var(--spacing-md);">
|
||||
Blokowane: 🇷🇺 Rosja, 🇨🇳 Chiny, 🇰🇵 Korea Północna, 🇮🇷 Iran, 🇧🇾 Białoruś, 🇸🇾 Syria, 🇻🇪 Wenezuela, 🇨🇺 Kuba
|
||||
</p>
|
||||
|
||||
{% if geoip_stats.by_country %}
|
||||
<div class="country-breakdown">
|
||||
{% for country in geoip_stats.by_country %}
|
||||
<div class="country-badge">
|
||||
<span class="country-flag">{{ country.flag }}</span>
|
||||
<span>{{ country.name }}</span>
|
||||
<span class="country-count">{{ country.count }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<p>Brak zablokowanych połączeń</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="empty-state" style="background: #fef3c7; border-radius: var(--radius-lg); padding: var(--spacing-xl);">
|
||||
<p style="color: #92400e;">⚠️ GeoIP Blocking jest wyłączone</p>
|
||||
<p style="color: #78716c; font-size: var(--font-size-sm);">Ustaw GEOIP_ENABLED=true w .env aby włączyć</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alerts Tab -->
|
||||
<div id="tab-alerts" class="tab-content active">
|
||||
<div id="tab-alerts" class="tab-content">
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2>🚨 Alerty bezpieczeństwa</h2>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user