refactor: remove debug panel entirely
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
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
Remove in-memory log viewer (DebugLogHandler, 5 routes, template, menu links, endpoint aliases). Logs available via journalctl on server. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
954cd02975
commit
d8ee8fe7e4
28
app.py
28
app.py
@ -22,7 +22,6 @@ import secrets
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta, date
|
||||
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session, Response, send_file
|
||||
@ -64,37 +63,11 @@ STAGING_TEST_FEATURES = {
|
||||
},
|
||||
}
|
||||
|
||||
# Configure logging with in-memory buffer for debug panel
|
||||
class DebugLogHandler(logging.Handler):
|
||||
"""Custom handler that stores logs in memory for real-time viewing"""
|
||||
def __init__(self, max_logs=500):
|
||||
super().__init__()
|
||||
self.logs = deque(maxlen=max_logs)
|
||||
|
||||
def emit(self, record):
|
||||
log_entry = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'level': record.levelname,
|
||||
'logger': record.name,
|
||||
'message': self.format(record),
|
||||
'module': record.module,
|
||||
'funcName': record.funcName,
|
||||
'lineno': record.lineno
|
||||
}
|
||||
self.logs.append(log_entry)
|
||||
|
||||
# Create debug handler
|
||||
debug_handler = DebugLogHandler(max_logs=500)
|
||||
debug_handler.setFormatter(logging.Formatter('%(message)s'))
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# Add debug handler to root logger
|
||||
logging.getLogger().addHandler(debug_handler)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Security logger for fail2ban integration
|
||||
@ -1011,7 +984,6 @@ def health_full():
|
||||
('/admin/analytics', 'Admin: Analityka'),
|
||||
('/admin/status', 'Admin: Status systemu'),
|
||||
('/admin/health', 'Admin: Health dashboard'),
|
||||
('/admin/debug', 'Admin: Debug'),
|
||||
('/admin/ai-usage', 'Admin: AI Usage'),
|
||||
('/admin/chat-analytics', 'Admin: Chat analytics'),
|
||||
('/admin/users', 'Admin: Użytkownicy'),
|
||||
|
||||
@ -326,10 +326,9 @@ def register_blueprints(app):
|
||||
# SEO & Audits (Phase 6.2a)
|
||||
'admin_seo': 'admin.admin_seo',
|
||||
'admin_gbp_audit': 'admin.admin_gbp_audit',
|
||||
# Status, Health, Debug (Phase 6.2c)
|
||||
# Status, Health (Phase 6.2c)
|
||||
'admin_status': 'admin.admin_status',
|
||||
'admin_health': 'admin.admin_health',
|
||||
'debug_panel': 'admin.debug_panel',
|
||||
# Social Media (Phase 6.2e)
|
||||
'admin_social_media': 'admin.admin_social_media',
|
||||
'admin_social_audit': 'admin.admin_social_audit',
|
||||
|
||||
@ -786,94 +786,3 @@ def api_admin_health():
|
||||
'health_percent': round(100 * ok_count / len(results), 1)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DEBUG PANEL
|
||||
# ============================================================
|
||||
|
||||
@bp.route('/debug')
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
def debug_panel():
|
||||
"""Real-time debug panel for monitoring app activity"""
|
||||
return render_template('admin/debug.html')
|
||||
|
||||
|
||||
@bp.route('/api/logs')
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
def api_get_logs():
|
||||
"""API: Get recent logs"""
|
||||
# Import debug_handler from main app
|
||||
from app import debug_handler
|
||||
|
||||
# Get optional filters
|
||||
level = request.args.get('level', '') # DEBUG, INFO, WARNING, ERROR
|
||||
since = request.args.get('since', '') # ISO timestamp
|
||||
limit = min(int(request.args.get('limit', 100)), 500)
|
||||
|
||||
logs = list(debug_handler.logs)
|
||||
|
||||
# Filter by level
|
||||
if level:
|
||||
logs = [l for l in logs if l['level'] == level.upper()]
|
||||
|
||||
# Filter by timestamp
|
||||
if since:
|
||||
logs = [l for l in logs if l['timestamp'] > since]
|
||||
|
||||
# Return most recent
|
||||
logs = logs[-limit:]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logs': logs,
|
||||
'total': len(debug_handler.logs)
|
||||
})
|
||||
|
||||
|
||||
@bp.route('/api/logs/stream')
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
def api_logs_stream():
|
||||
"""SSE endpoint for real-time log streaming"""
|
||||
from app import debug_handler
|
||||
import time
|
||||
|
||||
def generate():
|
||||
last_count = 0
|
||||
while True:
|
||||
current_count = len(debug_handler.logs)
|
||||
if current_count > last_count:
|
||||
# Send new logs
|
||||
new_logs = list(debug_handler.logs)[last_count:]
|
||||
for log in new_logs:
|
||||
yield f"data: {json.dumps(log)}\n\n"
|
||||
last_count = current_count
|
||||
time.sleep(0.5)
|
||||
|
||||
return Response(generate(), mimetype='text/event-stream')
|
||||
|
||||
|
||||
@bp.route('/api/logs/clear', methods=['POST'])
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
def api_clear_logs():
|
||||
"""API: Clear log buffer"""
|
||||
from app import debug_handler
|
||||
debug_handler.logs.clear()
|
||||
logger.info("Log buffer cleared by admin")
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@bp.route('/api/test-log', methods=['POST'])
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
def api_test_log():
|
||||
"""API: Generate test log entries"""
|
||||
logger.debug("Test DEBUG message")
|
||||
logger.info("Test INFO message")
|
||||
logger.warning("Test WARNING message")
|
||||
logger.error("Test ERROR message")
|
||||
return jsonify({'success': True, 'message': 'Test logs generated'})
|
||||
|
||||
@ -1,448 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Debug Panel - Norda Biznes Partner{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.debug-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.debug-header h1 {
|
||||
font-size: var(--font-size-2xl);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: var(--error);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-indicator.connected {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.debug-controls {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.debug-controls select,
|
||||
.debug-controls button {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--font-size-sm);
|
||||
background: var(--surface);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.debug-controls button:hover {
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--error) !important;
|
||||
color: white !important;
|
||||
border-color: var(--error) !important;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.log-container {
|
||||
background: #1e1e1e;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-md);
|
||||
height: calc(100vh - 300px);
|
||||
min-height: 400px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 2px;
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.log-entry.DEBUG { color: #9cdcfe; }
|
||||
.log-entry.INFO { color: #4ec9b0; }
|
||||
.log-entry.WARNING { color: #dcdcaa; background: rgba(220, 220, 170, 0.1); }
|
||||
.log-entry.ERROR { color: #f48771; background: rgba(244, 135, 113, 0.1); }
|
||||
|
||||
.log-timestamp {
|
||||
color: #6a9955;
|
||||
flex-shrink: 0;
|
||||
width: 85px;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
flex-shrink: 0;
|
||||
width: 60px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.log-logger {
|
||||
color: #569cd6;
|
||||
flex-shrink: 0;
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.log-location {
|
||||
color: #808080;
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stats-bar {
|
||||
display: flex;
|
||||
gap: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
padding: var(--spacing-md);
|
||||
background: var(--surface);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.stat-item .count {
|
||||
font-weight: 700;
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.stat-item.info .count { color: var(--primary); }
|
||||
.stat-item.warning .count { color: var(--warning); }
|
||||
.stat-item.error .count { color: var(--error); }
|
||||
|
||||
.empty-logs {
|
||||
color: #6a9955;
|
||||
text-align: center;
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.auto-scroll-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="debug-header">
|
||||
<h1>
|
||||
<span class="status-indicator" id="statusIndicator"></span>
|
||||
Debug Panel
|
||||
</h1>
|
||||
<div class="debug-controls">
|
||||
<label class="auto-scroll-label">
|
||||
<input type="checkbox" id="autoScroll" checked>
|
||||
Auto-scroll
|
||||
</label>
|
||||
<select id="levelFilter">
|
||||
<option value="">Wszystkie poziomy</option>
|
||||
<option value="DEBUG">DEBUG</option>
|
||||
<option value="INFO">INFO</option>
|
||||
<option value="WARNING">WARNING</option>
|
||||
<option value="ERROR">ERROR</option>
|
||||
</select>
|
||||
<button onclick="testLogs()">Test Logs</button>
|
||||
<button onclick="clearLogs()" class="btn-danger">Wyczyść</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-bar">
|
||||
<div class="stat-item info">
|
||||
<span class="count" id="infoCount">0</span>
|
||||
<span>INFO</span>
|
||||
</div>
|
||||
<div class="stat-item warning">
|
||||
<span class="count" id="warningCount">0</span>
|
||||
<span>WARNING</span>
|
||||
</div>
|
||||
<div class="stat-item error">
|
||||
<span class="count" id="errorCount">0</span>
|
||||
<span>ERROR</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="count" id="totalCount">0</span>
|
||||
<span>Total</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="log-container" id="logContainer">
|
||||
<div class="empty-logs" id="emptyMessage">
|
||||
Oczekiwanie na logi... Wykonaj jakąś akcję na stronie.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Universal Confirm Modal -->
|
||||
<div class="modal-overlay" id="confirmModal">
|
||||
<div class="modal" style="max-width: 420px; background: var(--surface); border-radius: var(--radius-lg); padding: var(--spacing-xl);">
|
||||
<div style="text-align: center; margin-bottom: var(--spacing-lg);">
|
||||
<div class="modal-icon" id="confirmModalIcon" style="font-size: 3em; margin-bottom: var(--spacing-md);">❓</div>
|
||||
<h3 id="confirmModalTitle" style="margin-bottom: var(--spacing-sm);">Potwierdzenie</h3>
|
||||
<p class="modal-description" id="confirmModalMessage" style="color: var(--text-secondary);"></p>
|
||||
</div>
|
||||
<div class="modal-actions" style="display: flex; gap: var(--spacing-sm); justify-content: center;">
|
||||
<button type="button" class="btn btn-secondary" id="confirmModalCancel">Anuluj</button>
|
||||
<button type="button" class="btn btn-primary" id="confirmModalOk">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.modal-overlay#confirmModal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1050; align-items: center; justify-content: center; }
|
||||
.modal-overlay#confirmModal.active { display: flex; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
let confirmResolve = null;
|
||||
|
||||
function showConfirm(message, options = {}) {
|
||||
return new Promise(resolve => {
|
||||
confirmResolve = resolve;
|
||||
document.getElementById('confirmModalIcon').textContent = options.icon || '❓';
|
||||
document.getElementById('confirmModalTitle').textContent = options.title || 'Potwierdzenie';
|
||||
document.getElementById('confirmModalMessage').innerHTML = message;
|
||||
document.getElementById('confirmModalOk').textContent = options.okText || 'OK';
|
||||
document.getElementById('confirmModalOk').className = 'btn ' + (options.okClass || 'btn-primary');
|
||||
document.getElementById('confirmModal').classList.add('active');
|
||||
});
|
||||
}
|
||||
|
||||
function closeConfirm(result) {
|
||||
document.getElementById('confirmModal').classList.remove('active');
|
||||
if (confirmResolve) { confirmResolve(result); confirmResolve = null; }
|
||||
}
|
||||
|
||||
document.getElementById('confirmModalOk').addEventListener('click', () => closeConfirm(true));
|
||||
document.getElementById('confirmModalCancel').addEventListener('click', () => closeConfirm(false));
|
||||
document.getElementById('confirmModal').addEventListener('click', e => { if (e.target.id === 'confirmModal') closeConfirm(false); });
|
||||
const logContainer = document.getElementById('logContainer');
|
||||
const emptyMessage = document.getElementById('emptyMessage');
|
||||
const statusIndicator = document.getElementById('statusIndicator');
|
||||
const levelFilter = document.getElementById('levelFilter');
|
||||
const autoScrollCheckbox = document.getElementById('autoScroll');
|
||||
|
||||
let stats = { DEBUG: 0, INFO: 0, WARNING: 0, ERROR: 0 };
|
||||
let allLogs = [];
|
||||
let eventSource = null;
|
||||
|
||||
// Format timestamp
|
||||
function formatTime(isoString) {
|
||||
const date = new Date(isoString);
|
||||
return date.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
}
|
||||
|
||||
// Add log entry to UI
|
||||
function addLogEntry(log) {
|
||||
allLogs.push(log);
|
||||
stats[log.level] = (stats[log.level] || 0) + 1;
|
||||
updateStats();
|
||||
|
||||
// Check filter
|
||||
const filter = levelFilter.value;
|
||||
if (filter && log.level !== filter) return;
|
||||
|
||||
emptyMessage.style.display = 'none';
|
||||
|
||||
const entry = document.createElement('div');
|
||||
entry.className = `log-entry ${log.level}`;
|
||||
entry.innerHTML = `
|
||||
<span class="log-timestamp">${formatTime(log.timestamp)}</span>
|
||||
<span class="log-level">${log.level}</span>
|
||||
<span class="log-logger">${log.logger}</span>
|
||||
<span class="log-message">${escapeHtml(log.message)}</span>
|
||||
<span class="log-location">${log.module}:${log.lineno}</span>
|
||||
`;
|
||||
|
||||
logContainer.appendChild(entry);
|
||||
|
||||
if (autoScrollCheckbox.checked) {
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Escape HTML
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Update stats
|
||||
function updateStats() {
|
||||
document.getElementById('infoCount').textContent = stats.INFO || 0;
|
||||
document.getElementById('warningCount').textContent = stats.WARNING || 0;
|
||||
document.getElementById('errorCount').textContent = stats.ERROR || 0;
|
||||
document.getElementById('totalCount').textContent = allLogs.length;
|
||||
}
|
||||
|
||||
// Poll for new logs (SSE doesn't work well with session auth)
|
||||
let lastLogTimestamp = '';
|
||||
let pollInterval = null;
|
||||
|
||||
function startPolling() {
|
||||
statusIndicator.classList.add('connected');
|
||||
|
||||
pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const url = lastLogTimestamp
|
||||
? `/api/admin/logs?limit=50&since=${encodeURIComponent(lastLogTimestamp)}`
|
||||
: '/api/admin/logs?limit=50';
|
||||
|
||||
const response = await fetch(url, { credentials: 'include' });
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.logs.length > 0) {
|
||||
data.logs.forEach(log => {
|
||||
addLogEntry(log);
|
||||
lastLogTimestamp = log.timestamp;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Polling error:', error);
|
||||
statusIndicator.classList.remove('connected');
|
||||
}
|
||||
}, 1000); // Poll every second
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (pollInterval) {
|
||||
clearInterval(pollInterval);
|
||||
pollInterval = null;
|
||||
}
|
||||
statusIndicator.classList.remove('connected');
|
||||
}
|
||||
|
||||
// Load initial logs
|
||||
async function loadInitialLogs() {
|
||||
try {
|
||||
const response = await fetch('/api/admin/logs?limit=200', {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.logs.length > 0) {
|
||||
data.logs.forEach(log => addLogEntry(log));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load logs:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter logs
|
||||
levelFilter.addEventListener('change', function() {
|
||||
// Clear container
|
||||
logContainer.innerHTML = '';
|
||||
|
||||
const filter = this.value;
|
||||
const filtered = filter ? allLogs.filter(l => l.level === filter) : allLogs;
|
||||
|
||||
if (filtered.length === 0) {
|
||||
logContainer.innerHTML = '<div class="empty-logs">Brak logów dla wybranego filtru</div>';
|
||||
} else {
|
||||
filtered.forEach(log => {
|
||||
const entry = document.createElement('div');
|
||||
entry.className = `log-entry ${log.level}`;
|
||||
entry.innerHTML = `
|
||||
<span class="log-timestamp">${formatTime(log.timestamp)}</span>
|
||||
<span class="log-level">${log.level}</span>
|
||||
<span class="log-logger">${log.logger}</span>
|
||||
<span class="log-message">${escapeHtml(log.message)}</span>
|
||||
<span class="log-location">${log.module}:${log.lineno}</span>
|
||||
`;
|
||||
logContainer.appendChild(entry);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Clear logs
|
||||
async function clearLogs() {
|
||||
const confirmed = await showConfirm('Czy na pewno chcesz wyczyścić wszystkie logi?', {
|
||||
icon: '🗑️',
|
||||
title: 'Czyszczenie logów',
|
||||
okText: 'Wyczyść',
|
||||
okClass: 'btn-danger'
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/logs/clear', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
allLogs = [];
|
||||
stats = { DEBUG: 0, INFO: 0, WARNING: 0, ERROR: 0 };
|
||||
updateStats();
|
||||
logContainer.innerHTML = '<div class="empty-logs" id="emptyMessage">Logi wyczyszczone</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to clear logs:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Test logs
|
||||
async function testLogs() {
|
||||
try {
|
||||
await fetch('/api/admin/test-log', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to generate test logs:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadInitialLogs();
|
||||
startPolling();
|
||||
|
||||
// Cleanup on page unload
|
||||
window.addEventListener('beforeunload', stopPolling);
|
||||
{% endblock %}
|
||||
@ -634,7 +634,6 @@
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<a href="{{ url_for('admin.debug_panel') }}" class="quick-action-btn">🐛 Debug panel</a>
|
||||
<a href="{{ url_for('admin.user_insights') }}" class="quick-action-btn">📈 Analityka</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1819,14 +1819,6 @@
|
||||
</svg>
|
||||
Monitoring AI
|
||||
</a>
|
||||
{% if current_user.has_role(SystemRole.ADMIN) %}
|
||||
<a href="{{ url_for('debug_panel') }}">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
|
||||
</svg>
|
||||
Debug Panel
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -948,13 +948,6 @@
|
||||
<span class="btn btn-sm btn-success">Statystyki</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('debug_panel') }}" class="admin-function-card">
|
||||
<svg fill="none" stroke="#ef4444" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
|
||||
</svg>
|
||||
<h6>Debug Panel</h6>
|
||||
<span class="btn btn-sm btn-danger">Logi</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user