Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
After refactoring to blueprints, templates still used bare endpoint names
(e.g., url_for('admin_zopk')) instead of prefixed names (e.g.,
url_for('admin.admin_zopk')). While most worked via backward-compat aliases,
api_zopk_search_news was missing from the alias list causing 500 on /admin/zopk.
Fixed 19 template files and added missing alias for api_zopk_search_news.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1058 lines
45 KiB
HTML
1058 lines
45 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Status systemu - Admin{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.status-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: var(--spacing-xl);
|
|
}
|
|
|
|
.status-header h1 {
|
|
font-size: var(--font-size-2xl);
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--spacing-xs);
|
|
}
|
|
|
|
.status-header p {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.refresh-info {
|
|
text-align: right;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--spacing-md) var(--spacing-lg);
|
|
}
|
|
|
|
.refresh-info .timestamp {
|
|
font-size: var(--font-size-lg);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
font-family: monospace;
|
|
}
|
|
|
|
.refresh-info .label {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.refresh-info .next-refresh {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--primary);
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
.refresh-indicator {
|
|
display: inline-block;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: #22c55e;
|
|
margin-right: var(--spacing-xs);
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
.metrics-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: var(--spacing-xl);
|
|
margin-bottom: var(--spacing-xl);
|
|
}
|
|
|
|
.metric-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-xl);
|
|
padding: var(--spacing-xl);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.metric-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
}
|
|
|
|
.metric-card.system::before { background: linear-gradient(90deg, #3b82f6, #8b5cf6); }
|
|
.metric-card.database::before { background: linear-gradient(90deg, #10b981, #14b8a6); }
|
|
.metric-card.app::before { background: linear-gradient(90deg, #f59e0b, #f97316); }
|
|
.metric-card.process::before { background: linear-gradient(90deg, #ec4899, #f43f5e); }
|
|
|
|
.metric-card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-md);
|
|
margin-bottom: var(--spacing-lg);
|
|
}
|
|
|
|
.metric-card-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: var(--radius-lg);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.metric-card.system .metric-card-icon { background: linear-gradient(135deg, #dbeafe, #ede9fe); }
|
|
.metric-card.database .metric-card-icon { background: linear-gradient(135deg, #d1fae5, #ccfbf1); }
|
|
.metric-card.app .metric-card-icon { background: linear-gradient(135deg, #fef3c7, #ffedd5); }
|
|
.metric-card.process .metric-card-icon { background: linear-gradient(135deg, #fce7f3, #fee2e2); }
|
|
|
|
.metric-card-title {
|
|
font-size: var(--font-size-lg);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.metric-card-subtitle {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.gauge-container {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-bottom: var(--spacing-lg);
|
|
}
|
|
|
|
.gauge {
|
|
position: relative;
|
|
width: 140px;
|
|
height: 70px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.gauge-bg {
|
|
position: absolute;
|
|
width: 140px;
|
|
height: 140px;
|
|
border-radius: 50%;
|
|
border: 12px solid var(--border);
|
|
border-bottom-color: transparent;
|
|
border-left-color: transparent;
|
|
transform: rotate(225deg);
|
|
}
|
|
|
|
.gauge-fill {
|
|
position: absolute;
|
|
width: 140px;
|
|
height: 140px;
|
|
border-radius: 50%;
|
|
border: 12px solid;
|
|
border-bottom-color: transparent;
|
|
border-left-color: transparent;
|
|
transform: rotate(225deg);
|
|
transition: transform 0.8s ease-out;
|
|
}
|
|
|
|
.gauge-fill.low { border-top-color: #22c55e; border-right-color: #22c55e; }
|
|
.gauge-fill.medium { border-top-color: #f59e0b; border-right-color: #f59e0b; }
|
|
.gauge-fill.high { border-top-color: #ef4444; border-right-color: #ef4444; }
|
|
|
|
.gauge-value {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
font-size: var(--font-size-2xl);
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.gauge-label {
|
|
position: absolute;
|
|
bottom: -20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
font-size: var(--font-size-xs);
|
|
color: var(--text-secondary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.metric-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: var(--spacing-md);
|
|
}
|
|
|
|
.metric-stat {
|
|
background: var(--background);
|
|
padding: var(--spacing-md);
|
|
border-radius: var(--radius);
|
|
text-align: center;
|
|
}
|
|
|
|
.metric-stat-value {
|
|
font-size: var(--font-size-xl);
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.metric-stat-label {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--text-secondary);
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
.progress-bar-container {
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
|
|
.progress-bar-label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: var(--spacing-xs);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 8px;
|
|
background: var(--border);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-bar-fill {
|
|
height: 100%;
|
|
border-radius: 4px;
|
|
transition: width 0.5s ease;
|
|
}
|
|
|
|
.progress-bar-fill.low { background: linear-gradient(90deg, #22c55e, #34d399); }
|
|
.progress-bar-fill.medium { background: linear-gradient(90deg, #f59e0b, #fbbf24); }
|
|
.progress-bar-fill.high { background: linear-gradient(90deg, #ef4444, #f87171); }
|
|
|
|
.status-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
padding: var(--spacing-xs) var(--spacing-md);
|
|
border-radius: var(--radius);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-badge.ok {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.status-badge.warning {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.status-badge.error {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: var(--spacing-sm) 0;
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.info-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.info-label {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.info-value {
|
|
color: var(--text-primary);
|
|
font-weight: 500;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.quick-actions {
|
|
display: flex;
|
|
gap: var(--spacing-md);
|
|
margin-top: var(--spacing-lg);
|
|
}
|
|
|
|
.quick-action-btn {
|
|
flex: 1;
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
background: var(--background);
|
|
color: var(--text-primary);
|
|
font-size: var(--font-size-sm);
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
text-align: center;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.quick-action-btn:hover {
|
|
border-color: var(--primary);
|
|
color: var(--primary);
|
|
}
|
|
|
|
.back-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
font-size: var(--font-size-sm);
|
|
margin-bottom: var(--spacing-lg);
|
|
}
|
|
|
|
.back-link:hover {
|
|
color: var(--primary);
|
|
}
|
|
|
|
/* Health indicators grid */
|
|
.health-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: var(--spacing-md);
|
|
margin-top: var(--spacing-lg);
|
|
}
|
|
|
|
.health-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
background: var(--background);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.health-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.health-dot.ok { background: #22c55e; }
|
|
.health-dot.warning { background: #f59e0b; }
|
|
.health-dot.error { background: #ef4444; }
|
|
|
|
.health-name {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-primary);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<a href="{{ url_for('admin.admin_security') }}" class="back-link">← Powrót do panelu bezpieczeństwa</a>
|
|
|
|
<div class="status-header">
|
|
<div>
|
|
<h1>📊 Status systemu</h1>
|
|
<p>Monitoring wydajności i zdrowia portalu NordaBiznes</p>
|
|
</div>
|
|
<div class="refresh-info">
|
|
<div class="label">Ostatnia aktualizacja</div>
|
|
<div class="timestamp" id="last-update">{{ generated_at.strftime('%H:%M:%S') }}</div>
|
|
<div class="label" style="margin-top: var(--spacing-xs);">{{ generated_at.strftime('%d.%m.%Y') }}</div>
|
|
<div class="next-refresh">
|
|
<span class="refresh-indicator"></span>
|
|
Auto-refresh: <span id="countdown">5:00</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="metrics-grid">
|
|
<!-- System Metrics Card -->
|
|
<div class="metric-card system">
|
|
<div class="metric-card-header">
|
|
<div class="metric-card-icon">🖥️</div>
|
|
<div>
|
|
<div class="metric-card-title">System</div>
|
|
<div class="metric-card-subtitle">{{ system_metrics.hostname }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CPU Gauge -->
|
|
{% if system_metrics.cpu_percent is not none %}
|
|
<div class="gauge-container">
|
|
<div class="gauge">
|
|
<div class="gauge-bg"></div>
|
|
<div class="gauge-fill {{ 'low' if system_metrics.cpu_percent < 50 else ('medium' if system_metrics.cpu_percent < 80 else 'high') }}"
|
|
style="transform: rotate({{ 225 + (system_metrics.cpu_percent * 1.8) }}deg)"></div>
|
|
<div class="gauge-value" id="cpu-value">{{ system_metrics.cpu_percent }}%</div>
|
|
<div class="gauge-label">CPU</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- RAM Progress Bar -->
|
|
{% if system_metrics.ram_percent is not none %}
|
|
<div class="progress-bar-container">
|
|
<div class="progress-bar-label">
|
|
<span>RAM</span>
|
|
<span id="ram-text">{{ system_metrics.ram_used_gb }} GB / {{ system_metrics.ram_total_gb }} GB ({{ system_metrics.ram_percent }}%)</span>
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-bar-fill {{ 'low' if system_metrics.ram_percent < 50 else ('medium' if system_metrics.ram_percent < 80 else 'high') }}"
|
|
id="ram-bar" style="width: {{ system_metrics.ram_percent }}%"></div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Disk Progress Bar -->
|
|
{% if system_metrics.disk_percent is not none %}
|
|
<div class="progress-bar-container">
|
|
<div class="progress-bar-label">
|
|
<span>Dysk</span>
|
|
<span id="disk-text">{{ system_metrics.disk_used }} / {{ system_metrics.disk_total }} ({{ system_metrics.disk_percent }}%)</span>
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-bar-fill {{ 'low' if system_metrics.disk_percent < 50 else ('medium' if system_metrics.disk_percent < 80 else 'high') }}"
|
|
id="disk-bar" style="width: {{ system_metrics.disk_percent }}%"></div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="info-row">
|
|
<span class="info-label">System operacyjny</span>
|
|
<span class="info-value">{{ system_metrics.os }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Python</span>
|
|
<span class="info-value">{{ system_metrics.python }}</span>
|
|
</div>
|
|
{% if system_metrics.uptime %}
|
|
<div class="info-row">
|
|
<span class="info-label">Uptime</span>
|
|
<span class="info-value">{{ system_metrics.uptime }}</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if system_metrics.load_1 is defined %}
|
|
<div class="info-row">
|
|
<span class="info-label">Load Average</span>
|
|
<span class="info-value">{{ system_metrics.load_1 }} / {{ system_metrics.load_5 }} / {{ system_metrics.load_15 }}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Database Metrics Card -->
|
|
<div class="metric-card database">
|
|
<div class="metric-card-header">
|
|
<div class="metric-card-icon">🗄️</div>
|
|
<div>
|
|
<div class="metric-card-title">Baza danych</div>
|
|
<div class="metric-card-subtitle">PostgreSQL</div>
|
|
</div>
|
|
<span class="status-badge {{ 'ok' if db_metrics.status == 'ok' else 'error' }}">
|
|
{{ '✓ OK' if db_metrics.status == 'ok' else '✗ Błąd' }}
|
|
</span>
|
|
</div>
|
|
|
|
{% if db_metrics.status == 'ok' %}
|
|
<div class="metric-stats">
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value">{{ db_metrics.size_mb }} MB</div>
|
|
<div class="metric-stat-label">Rozmiar bazy</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value" id="db-connections">{{ db_metrics.active_connections }}/{{ db_metrics.total_connections }}</div>
|
|
<div class="metric-stat-label">Połączenia aktywne</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value">{{ db_metrics.companies }}</div>
|
|
<div class="metric-stat-label">Firm</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value">{{ db_metrics.users }}</div>
|
|
<div class="metric-stat-label">Użytkowników</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if db_metrics.chat_messages is defined %}
|
|
<div style="margin-top: var(--spacing-lg);">
|
|
<div class="info-row">
|
|
<span class="info-label">Wiadomości chat</span>
|
|
<span class="info-value">{{ db_metrics.chat_messages }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Sesje chat</span>
|
|
<span class="info-value">{{ db_metrics.chat_sessions }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Profile Social Media</span>
|
|
<span class="info-value">{{ db_metrics.social_media }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Audyty SEO</span>
|
|
<span class="info-value">{{ db_metrics.seo_audits }}</span>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if db_metrics.cache_hit_ratio is defined %}
|
|
<div style="margin-top: var(--spacing-md); padding-top: var(--spacing-md); border-top: 1px solid var(--border);">
|
|
<div class="info-row">
|
|
<span class="info-label">Cache Hit Ratio</span>
|
|
<span class="info-value" style="color: {{ '#22c55e' if db_metrics.cache_hit_ratio > 95 else '#f59e0b' if db_metrics.cache_hit_ratio > 80 else '#ef4444' }};">{{ db_metrics.cache_hit_ratio }}%</span>
|
|
</div>
|
|
{% if db_metrics.slow_queries is defined %}
|
|
<div class="info-row">
|
|
<span class="info-label">Slow Queries (>1s)</span>
|
|
<span class="info-value">{{ db_metrics.slow_queries }}</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if db_metrics.deadlocks is defined %}
|
|
<div class="info-row">
|
|
<span class="info-label">Deadlocks</span>
|
|
<span class="info-value" style="color: {{ '#22c55e' if db_metrics.deadlocks == 0 else '#ef4444' }};">{{ db_metrics.deadlocks }}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<div style="padding: var(--spacing-xl); text-align: center; color: #991b1b;">
|
|
<p>⚠️ Błąd połączenia z bazą danych</p>
|
|
<p style="font-size: var(--font-size-xs);">{{ db_metrics.error }}</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Application Metrics Card -->
|
|
<div class="metric-card app">
|
|
<div class="metric-card-header">
|
|
<div class="metric-card-icon">🌐</div>
|
|
<div>
|
|
<div class="metric-card-title">Aplikacja</div>
|
|
<div class="metric-card-subtitle">NordaBiznes Portal</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if app_metrics.endpoints_ok is not none %}
|
|
<div class="gauge-container">
|
|
<div class="gauge">
|
|
<div class="gauge-bg"></div>
|
|
<div class="gauge-fill {{ 'low' if app_metrics.endpoints_percent == 100 else ('medium' if app_metrics.endpoints_percent >= 80 else 'high') }}"
|
|
style="transform: rotate({{ 225 + (app_metrics.endpoints_percent * 1.8) }}deg)"></div>
|
|
<div class="gauge-value">{{ app_metrics.endpoints_percent|int }}%</div>
|
|
<div class="gauge-label">Endpointy OK</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="metric-stats">
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value">{{ app_metrics.logins_24h }}</div>
|
|
<div class="metric-stat-label">Logowań (24h)</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value" id="alerts-24h">{{ app_metrics.alerts_24h }}</div>
|
|
<div class="metric-stat-label">Alertów (24h)</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value">{{ app_metrics.admins }}</div>
|
|
<div class="metric-stat-label">Administratorów</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value">{{ app_metrics.users_with_2fa }}</div>
|
|
<div class="metric-stat-label">Z 2FA</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="health-grid">
|
|
<div class="health-item">
|
|
<div class="health-dot ok"></div>
|
|
<span class="health-name">Frontend</span>
|
|
</div>
|
|
<div class="health-item">
|
|
<div class="health-dot {{ 'ok' if db_metrics.status == 'ok' else 'error' }}"></div>
|
|
<span class="health-name">Database</span>
|
|
</div>
|
|
<div class="health-item">
|
|
<div class="health-dot ok"></div>
|
|
<span class="health-name">API</span>
|
|
</div>
|
|
<div class="health-item">
|
|
<div class="health-dot ok"></div>
|
|
<span class="health-name">Auth</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Process Metrics Card -->
|
|
<div class="metric-card process">
|
|
<div class="metric-card-header">
|
|
<div class="metric-card-icon">⚙️</div>
|
|
<div>
|
|
<div class="metric-card-title">Procesy</div>
|
|
<div class="metric-card-subtitle">Serwer aplikacji</div>
|
|
</div>
|
|
<span class="status-badge {{ 'ok' if process_metrics.gunicorn_status == 'running' else 'warning' }}">
|
|
{{ 'Gunicorn ✓' if process_metrics.gunicorn_status == 'running' else 'Dev mode' }}
|
|
</span>
|
|
</div>
|
|
|
|
{% if process_metrics.gunicorn_status == 'running' %}
|
|
<div class="metric-stats" style="grid-template-columns: 1fr;">
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value">{{ process_metrics.gunicorn_workers }}</div>
|
|
<div class="metric-stat-label">Gunicorn Workers</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div style="padding: var(--spacing-lg); text-align: center;">
|
|
<p style="color: var(--text-secondary); font-size: var(--font-size-sm);">
|
|
{% if process_metrics.gunicorn_status == 'not found' %}
|
|
Gunicorn nie wykryty - tryb deweloperski
|
|
{% else %}
|
|
Status Gunicorn nieznany
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="quick-actions">
|
|
<a href="{{ url_for('health_full') }}" class="quick-action-btn" target="_blank">🔍 Pełny health check</a>
|
|
<a href="{{ url_for('admin.admin_security') }}" class="quick-action-btn">🛡️ Bezpieczeństwo</a>
|
|
</div>
|
|
|
|
<div class="quick-actions">
|
|
<a href="{{ url_for('admin.debug_panel') }}" class="quick-action-btn">🐛 Debug panel</a>
|
|
<a href="{{ url_for('admin.admin_analytics') }}" class="quick-action-btn">📈 Analityka</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- New Metrics Row -->
|
|
<div class="metrics-grid" style="margin-bottom: var(--spacing-xl);">
|
|
<!-- SSL Certificate Card -->
|
|
{% if ssl_metrics is defined %}
|
|
<div class="metric-card" style="border-top: 4px solid {{ '#22c55e' if ssl_metrics.status == 'ok' else '#f59e0b' if ssl_metrics.status == 'warning' else '#ef4444' }};">
|
|
<div class="metric-card-header">
|
|
<div class="metric-card-icon" style="background: linear-gradient(135deg, #dcfce7, #d1fae5);">🔒</div>
|
|
<div>
|
|
<div class="metric-card-title">Certyfikat SSL</div>
|
|
<div class="metric-card-subtitle">{{ ssl_metrics.domain if ssl_metrics.domain else 'nordabiznes.pl' }}</div>
|
|
</div>
|
|
<span class="status-badge {{ ssl_metrics.status }}">
|
|
{% if ssl_metrics.status == 'ok' %}✓ Ważny
|
|
{% elif ssl_metrics.status == 'warning' %}⚠️ Wkrótce wygasa
|
|
{% else %}✗ Problem{% endif %}
|
|
</span>
|
|
</div>
|
|
{% if ssl_metrics.days_left is defined %}
|
|
<div class="metric-stats" style="grid-template-columns: 1fr 1fr;">
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value" style="color: {{ '#22c55e' if ssl_metrics.days_left > 30 else '#f59e0b' if ssl_metrics.days_left > 7 else '#ef4444' }};">{{ ssl_metrics.days_left }}</div>
|
|
<div class="metric-stat-label">Dni do wygaśnięcia</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value" style="font-size: var(--font-size-sm);">{{ ssl_metrics.expires }}</div>
|
|
<div class="metric-stat-label">Data wygaśnięcia</div>
|
|
</div>
|
|
</div>
|
|
<div class="metric-stat" style="margin-top: var(--spacing-md); text-align: center; padding: var(--spacing-sm); background: var(--bg-secondary); border-radius: var(--radius-md);">
|
|
<div class="metric-stat-value" style="font-size: var(--font-size-sm);">{{ ssl_metrics.issuer }}</div>
|
|
<div class="metric-stat-label">Wystawca</div>
|
|
</div>
|
|
{% else %}
|
|
<div style="padding: var(--spacing-lg); text-align: center; color: #991b1b;">
|
|
<p>⚠️ Nie można sprawdzić certyfikatu</p>
|
|
{% if ssl_metrics.error %}<p style="font-size: var(--font-size-xs);">{{ ssl_metrics.error }}</p>{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Deploy Info Card -->
|
|
{% if deploy_metrics is defined %}
|
|
<div class="metric-card" style="border-top: 4px solid #8b5cf6;">
|
|
<div class="metric-card-header">
|
|
<div class="metric-card-icon" style="background: linear-gradient(135deg, #ede9fe, #ddd6fe);">🚀</div>
|
|
<div>
|
|
<div class="metric-card-title">Deploy</div>
|
|
<div class="metric-card-subtitle">Git / Wersja</div>
|
|
</div>
|
|
</div>
|
|
{% if deploy_metrics.commit %}
|
|
<div class="metric-stats" style="grid-template-columns: 1fr;">
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value" style="font-family: monospace; font-size: var(--font-size-lg);">{{ deploy_metrics.commit }}</div>
|
|
<div class="metric-stat-label">Aktualny commit</div>
|
|
</div>
|
|
</div>
|
|
<div style="margin-top: var(--spacing-md);">
|
|
<div class="info-row">
|
|
<span class="info-label">Branch</span>
|
|
<span class="info-value">{{ deploy_metrics.branch }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Data commitu</span>
|
|
<span class="info-value">{{ deploy_metrics.commit_date }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Opis</span>
|
|
<span class="info-value" style="font-size: var(--font-size-xs); max-width: 200px; overflow: hidden; text-overflow: ellipsis;">{{ deploy_metrics.commit_message }}</span>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div style="padding: var(--spacing-lg); text-align: center; color: var(--text-secondary);">
|
|
<p>Brak danych Git</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Security Metrics Card -->
|
|
{% if security_metrics is defined %}
|
|
<div class="metric-card" style="border-top: 4px solid #ef4444;">
|
|
<div class="metric-card-header">
|
|
<div class="metric-card-icon" style="background: linear-gradient(135deg, #fee2e2, #fecaca);">🛡️</div>
|
|
<div>
|
|
<div class="metric-card-title">Bezpieczeństwo</div>
|
|
<div class="metric-card-subtitle">Ostatnie 24h</div>
|
|
</div>
|
|
</div>
|
|
<div class="metric-stats">
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value" style="color: {{ '#22c55e' if security_metrics.failed_logins_24h == 0 else '#f59e0b' if security_metrics.failed_logins_24h < 10 else '#ef4444' }};">{{ security_metrics.failed_logins_24h }}</div>
|
|
<div class="metric-stat-label">Nieudane logowania</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value">{{ security_metrics.geoip_blocked_24h }}</div>
|
|
<div class="metric-stat-label">Blokady GeoIP</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value">{{ security_metrics.rate_limit_24h }}</div>
|
|
<div class="metric-stat-label">Rate limit hits</div>
|
|
</div>
|
|
<div class="metric-stat">
|
|
<div class="metric-stat-value" style="color: {{ '#22c55e' if security_metrics.locked_accounts == 0 else '#ef4444' }};">{{ security_metrics.locked_accounts }}</div>
|
|
<div class="metric-stat-label">Zablokowane konta</div>
|
|
</div>
|
|
</div>
|
|
<div class="info-row" style="margin-top: var(--spacing-md);">
|
|
<span class="info-label">Unikalne IP zablokowane</span>
|
|
<span class="info-value">{{ security_metrics.unique_ips_blocked }}</span>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- External APIs Status Card -->
|
|
{% if external_apis is defined %}
|
|
<div class="metric-card" style="border-top: 4px solid #06b6d4;">
|
|
<div class="metric-card-header">
|
|
<div class="metric-card-icon" style="background: linear-gradient(135deg, #cffafe, #a5f3fc);">🔌</div>
|
|
<div>
|
|
<div class="metric-card-title">External APIs</div>
|
|
<div class="metric-card-subtitle">Status połączeń</div>
|
|
</div>
|
|
</div>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
|
{% for api in external_apis %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm); background: var(--background); border-radius: var(--radius);">
|
|
<div class="health-dot {{ api.status }}"></div>
|
|
<span style="flex: 1; font-weight: 500;">{{ api.name }}</span>
|
|
{% if api.latency %}
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary);">{{ api.latency }}ms</span>
|
|
{% else %}
|
|
<span style="font-size: var(--font-size-xs); color: #ef4444;">offline</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Servers Status Row -->
|
|
{% if servers_status is defined %}
|
|
<div class="metric-card" style="margin-bottom: var(--spacing-xl); border-top: 4px solid #6366f1;">
|
|
<div class="metric-card-header">
|
|
<div class="metric-card-icon" style="background: linear-gradient(135deg, #e0e7ff, #c7d2fe);">🖥️</div>
|
|
<div>
|
|
<div class="metric-card-title">Status serwerów</div>
|
|
<div class="metric-card-subtitle">Ping w sieci INPI</div>
|
|
</div>
|
|
</div>
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--spacing-md);">
|
|
{% for server in servers_status %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius); border-left: 3px solid {{ '#22c55e' if server.status == 'online' else '#ef4444' }};">
|
|
<div>
|
|
<div class="health-dot {{ 'ok' if server.status == 'online' else 'error' }}" style="width: 12px; height: 12px;"></div>
|
|
</div>
|
|
<div style="flex: 1;">
|
|
<div style="font-weight: 600;">{{ server.name }}</div>
|
|
<div style="font-size: var(--font-size-xs); color: var(--text-secondary); font-family: monospace;">{{ server.ip }}</div>
|
|
</div>
|
|
<div style="text-align: right;">
|
|
{% if server.status == 'online' %}
|
|
<div style="font-size: var(--font-size-sm); color: #22c55e;">Online</div>
|
|
<div style="font-size: var(--font-size-xs); color: var(--text-secondary);">{{ server.latency }}ms</div>
|
|
{% else %}
|
|
<div style="font-size: var(--font-size-sm); color: #ef4444;">Offline</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Technology Stack Section -->
|
|
<div class="tech-stack-section" style="margin-top: var(--spacing-2xl);">
|
|
<h2 style="font-size: var(--font-size-xl); color: var(--text-primary); margin-bottom: var(--spacing-lg);">
|
|
🛠️ Stos technologiczny
|
|
</h2>
|
|
|
|
<div class="tech-categories" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: var(--spacing-lg);">
|
|
|
|
<!-- Programming -->
|
|
<div class="tech-category" style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--spacing-lg);">
|
|
<h3 style="display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-base); color: var(--text-primary); margin-bottom: var(--spacing-md); padding-bottom: var(--spacing-sm); border-bottom: 2px solid #3b82f6;">
|
|
💻 Programowanie
|
|
</h3>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
|
{% for tech in technology_stack.programming %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm); background: var(--background); border-radius: var(--radius);">
|
|
<span style="font-size: 1.3em;">{{ tech.icon }}</span>
|
|
<span style="flex: 1; font-weight: 500;">{{ tech.name }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary); background: var(--surface); padding: 2px 8px; border-radius: var(--radius);">{{ tech.version }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Databases -->
|
|
<div class="tech-category" style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--spacing-lg);">
|
|
<h3 style="display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-base); color: var(--text-primary); margin-bottom: var(--spacing-md); padding-bottom: var(--spacing-sm); border-bottom: 2px solid #10b981;">
|
|
🗄️ Bazy danych
|
|
</h3>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
|
{% for tech in technology_stack.databases %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm); background: var(--background); border-radius: var(--radius);">
|
|
<span style="font-size: 1.3em;">{{ tech.icon }}</span>
|
|
<span style="flex: 1; font-weight: 500;">{{ tech.name }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary);">{{ tech.category }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary); background: var(--surface); padding: 2px 8px; border-radius: var(--radius);">{{ tech.version }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AI -->
|
|
<div class="tech-category" style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--spacing-lg);">
|
|
<h3 style="display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-base); color: var(--text-primary); margin-bottom: var(--spacing-md); padding-bottom: var(--spacing-sm); border-bottom: 2px solid #8b5cf6;">
|
|
🤖 Sztuczna inteligencja
|
|
</h3>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
|
{% for tech in technology_stack.ai %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm); background: var(--background); border-radius: var(--radius);">
|
|
<span style="font-size: 1.3em;">{{ tech.icon }}</span>
|
|
<span style="flex: 1; font-weight: 500;">{{ tech.name }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary);">{{ tech.category }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary); background: var(--surface); padding: 2px 8px; border-radius: var(--radius);">{{ tech.version }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Infrastructure -->
|
|
<div class="tech-category" style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--spacing-lg);">
|
|
<h3 style="display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-base); color: var(--text-primary); margin-bottom: var(--spacing-md); padding-bottom: var(--spacing-sm); border-bottom: 2px solid #f59e0b;">
|
|
🖥️ Infrastruktura
|
|
</h3>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
|
{% for tech in technology_stack.infrastructure %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm); background: var(--background); border-radius: var(--radius);">
|
|
<span style="font-size: 1.3em;">{{ tech.icon }}</span>
|
|
<span style="flex: 1; font-weight: 500;">{{ tech.name }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary);">{{ tech.category }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary); background: var(--surface); padding: 2px 8px; border-radius: var(--radius);">{{ tech.version }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Network -->
|
|
<div class="tech-category" style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--spacing-lg);">
|
|
<h3 style="display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-base); color: var(--text-primary); margin-bottom: var(--spacing-md); padding-bottom: var(--spacing-sm); border-bottom: 2px solid #ef4444;">
|
|
🌐 Sieć
|
|
</h3>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
|
{% for tech in technology_stack.network %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm); background: var(--background); border-radius: var(--radius);">
|
|
<span style="font-size: 1.3em;">{{ tech.icon }}</span>
|
|
<span style="flex: 1; font-weight: 500;">{{ tech.name }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary);">{{ tech.category }}</span>
|
|
{% if tech.version %}
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary); background: var(--surface); padding: 2px 8px; border-radius: var(--radius);">{{ tech.version }}</span>
|
|
{% else %}
|
|
<span style="font-size: var(--font-size-xs); color: var(--warning); background: #fef3c7; padding: 2px 8px; border-radius: var(--radius);">nieznana</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Security -->
|
|
<div class="tech-category" style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--spacing-lg);">
|
|
<h3 style="display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-base); color: var(--text-primary); margin-bottom: var(--spacing-md); padding-bottom: var(--spacing-sm); border-bottom: 2px solid #14b8a6;">
|
|
🛡️ Bezpieczeństwo
|
|
</h3>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
|
{% for tech in technology_stack.security %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm); background: var(--background); border-radius: var(--radius);">
|
|
<span style="font-size: 1.3em;">{{ tech.icon }}</span>
|
|
<span style="flex: 1; font-weight: 500;">{{ tech.name }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary);">{{ tech.category }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary); background: var(--surface); padding: 2px 8px; border-radius: var(--radius);">{{ tech.version }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DevOps -->
|
|
<div class="tech-category" style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--spacing-lg);">
|
|
<h3 style="display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-base); color: var(--text-primary); margin-bottom: var(--spacing-md); padding-bottom: var(--spacing-sm); border-bottom: 2px solid #ec4899;">
|
|
⚙️ DevOps
|
|
</h3>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
|
{% for tech in technology_stack.devops %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm); background: var(--background); border-radius: var(--radius);">
|
|
<span style="font-size: 1.3em;">{{ tech.icon }}</span>
|
|
<span style="flex: 1; font-weight: 500;">{{ tech.name }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary);">{{ tech.category }}</span>
|
|
<span style="font-size: var(--font-size-xs); color: var(--text-secondary); background: var(--surface); padding: 2px 8px; border-radius: var(--radius);">{{ tech.version }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Servers -->
|
|
<div class="tech-category" style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--spacing-lg);">
|
|
<h3 style="display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-base); color: var(--text-primary); margin-bottom: var(--spacing-md); padding-bottom: var(--spacing-sm); border-bottom: 2px solid #6366f1;">
|
|
🖥️ Serwery
|
|
</h3>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
|
{% for server in technology_stack.servers %}
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm); background: var(--background); border-radius: var(--radius);">
|
|
<span style="font-size: 1.3em;">{{ server.icon }}</span>
|
|
<span style="flex: 1; font-weight: 500;">{{ server.name }}</span>
|
|
<span style="font-family: monospace; font-size: var(--font-size-xs); color: var(--text-secondary); background: var(--surface); padding: 2px 8px; border-radius: var(--radius);">{{ server.ip }}</span>
|
|
</div>
|
|
<div style="font-size: var(--font-size-xs); color: var(--text-secondary); padding-left: calc(1.3em + var(--spacing-md)); margin-top: -4px;">
|
|
{{ server.role }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
// Auto-refresh every 5 minutes
|
|
const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes in ms
|
|
let countdownSeconds = 300;
|
|
|
|
function updateCountdown() {
|
|
const minutes = Math.floor(countdownSeconds / 60);
|
|
const seconds = countdownSeconds % 60;
|
|
document.getElementById('countdown').textContent =
|
|
`${minutes}:${seconds.toString().padStart(2, '0')}`;
|
|
|
|
if (countdownSeconds <= 0) {
|
|
refreshData();
|
|
countdownSeconds = 300;
|
|
} else {
|
|
countdownSeconds--;
|
|
}
|
|
}
|
|
|
|
async function refreshData() {
|
|
try {
|
|
const response = await fetch('/api/admin/status');
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
|
|
// Update timestamp
|
|
const timestamp = new Date(data.timestamp);
|
|
document.getElementById('last-update').textContent =
|
|
timestamp.toLocaleTimeString('pl-PL');
|
|
|
|
// Update CPU if available
|
|
if (data.system && data.system.cpu_percent !== null) {
|
|
const cpuEl = document.getElementById('cpu-value');
|
|
if (cpuEl) cpuEl.textContent = data.system.cpu_percent + '%';
|
|
}
|
|
|
|
// Update RAM if available
|
|
if (data.system && data.system.ram_percent !== null) {
|
|
const ramBar = document.getElementById('ram-bar');
|
|
if (ramBar) {
|
|
ramBar.style.width = data.system.ram_percent + '%';
|
|
ramBar.className = 'progress-bar-fill ' +
|
|
(data.system.ram_percent < 50 ? 'low' :
|
|
data.system.ram_percent < 80 ? 'medium' : 'high');
|
|
}
|
|
}
|
|
|
|
// Update disk if available
|
|
if (data.system && data.system.disk_percent !== null) {
|
|
const diskBar = document.getElementById('disk-bar');
|
|
if (diskBar) {
|
|
diskBar.style.width = data.system.disk_percent + '%';
|
|
diskBar.className = 'progress-bar-fill ' +
|
|
(data.system.disk_percent < 50 ? 'low' :
|
|
data.system.disk_percent < 80 ? 'medium' : 'high');
|
|
}
|
|
}
|
|
|
|
// Update DB connections
|
|
if (data.database && data.database.active_connections !== undefined) {
|
|
const connEl = document.getElementById('db-connections');
|
|
if (connEl) connEl.textContent = data.database.active_connections + '/...';
|
|
}
|
|
|
|
// Update alerts count
|
|
if (data.app && data.app.alerts_24h !== undefined) {
|
|
const alertsEl = document.getElementById('alerts-24h');
|
|
if (alertsEl) alertsEl.textContent = data.app.alerts_24h;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to refresh status data:', error);
|
|
}
|
|
}
|
|
|
|
// Start countdown
|
|
setInterval(updateCountdown, 1000);
|
|
|
|
// Also refresh when page becomes visible again
|
|
document.addEventListener('visibilitychange', function() {
|
|
if (!document.hidden) {
|
|
refreshData();
|
|
countdownSeconds = 300;
|
|
}
|
|
});
|
|
{% endblock %}
|