new_functions #2
54
app.py
54
app.py
@@ -1,10 +1,8 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import ssl
|
import ssl
|
||||||
|
|
||||||
import configparser
|
import configparser
|
||||||
from flask import Flask, render_template, render_template_string
|
from flask import Flask, render_template, render_template_string, request, jsonify
|
||||||
from routes.main_routes import main_bp
|
from routes.main_routes import main_bp
|
||||||
from routes.edit_routes import edit_bp
|
from routes.edit_routes import edit_bp
|
||||||
from utils.stats_utils import fetch_haproxy_stats, parse_haproxy_stats
|
from utils.stats_utils import fetch_haproxy_stats, parse_haproxy_stats
|
||||||
@@ -59,7 +57,6 @@ except Exception as e:
|
|||||||
|
|
||||||
app.register_blueprint(main_bp)
|
app.register_blueprint(main_bp)
|
||||||
app.register_blueprint(edit_bp)
|
app.register_blueprint(edit_bp)
|
||||||
|
|
||||||
setup_auth(app)
|
setup_auth(app)
|
||||||
|
|
||||||
certificate_path = None
|
certificate_path = None
|
||||||
@@ -69,7 +66,6 @@ ssl_context = None
|
|||||||
try:
|
try:
|
||||||
config2 = configparser.ConfigParser()
|
config2 = configparser.ConfigParser()
|
||||||
config2.read(SSL_INI)
|
config2.read(SSL_INI)
|
||||||
|
|
||||||
if config2.has_section('ssl'):
|
if config2.has_section('ssl'):
|
||||||
certificate_path = config2.get('ssl', 'certificate_path')
|
certificate_path = config2.get('ssl', 'certificate_path')
|
||||||
private_key_path = config2.get('ssl', 'private_key_path')
|
private_key_path = config2.get('ssl', 'private_key_path')
|
||||||
@@ -88,41 +84,70 @@ try:
|
|||||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||||
ssl_context.load_cert_chain(certfile=certificate_path, keyfile=private_key_path)
|
ssl_context.load_cert_chain(certfile=certificate_path, keyfile=private_key_path)
|
||||||
print(f"[APP] SSL context loaded", flush=True)
|
print(f"[APP] SSL context loaded", flush=True)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[APP] SSL error: {e}", flush=True)
|
print(f"[APP] SSL error: {e}", flush=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/statistics')
|
@app.route('/statistics')
|
||||||
def display_haproxy_stats():
|
def display_haproxy_stats():
|
||||||
haproxy_stats = fetch_haproxy_stats()
|
haproxy_stats = fetch_haproxy_stats()
|
||||||
parsed_stats = parse_haproxy_stats(haproxy_stats)
|
parsed_stats = parse_haproxy_stats(haproxy_stats)
|
||||||
return render_template('statistics.html', stats=parsed_stats)
|
return render_template('statistics.html', stats=parsed_stats)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/logs', endpoint='display_logs')
|
@app.route('/logs', endpoint='display_logs')
|
||||||
#@requires_auth
|
|
||||||
def display_haproxy_logs():
|
def display_haproxy_logs():
|
||||||
log_file_path = '/var/log/haproxy.log'
|
log_file_path = '/var/log/haproxy.log'
|
||||||
|
|
||||||
if not os.path.exists(log_file_path):
|
if not os.path.exists(log_file_path):
|
||||||
return render_template('logs.html',
|
return render_template('logs.html',
|
||||||
logs=[],
|
logs=[],
|
||||||
|
total_logs=0,
|
||||||
error_message=f"Log file not found: {log_file_path}")
|
error_message=f"Log file not found: {log_file_path}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logs = parse_log_file(log_file_path)
|
logs = parse_log_file(log_file_path)
|
||||||
if not logs:
|
total_logs = len(logs)
|
||||||
|
# Załaduj ostatnie 200 logów
|
||||||
|
initial_logs = logs[-200:] if len(logs) > 200 else logs
|
||||||
|
|
||||||
return render_template('logs.html',
|
return render_template('logs.html',
|
||||||
logs=[],
|
logs=initial_logs,
|
||||||
error_message="Log file is empty or unreadable")
|
total_logs=total_logs,
|
||||||
return render_template('logs.html', logs=logs)
|
loaded_count=len(initial_logs))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return render_template('logs.html',
|
return render_template('logs.html',
|
||||||
logs=[],
|
logs=[],
|
||||||
|
total_logs=0,
|
||||||
error_message=f"Error parsing logs: {str(e)}")
|
error_message=f"Error parsing logs: {str(e)}")
|
||||||
|
|
||||||
|
@app.route('/api/logs', methods=['POST'])
|
||||||
|
def api_get_logs():
|
||||||
|
try:
|
||||||
|
log_file_path = '/var/log/haproxy.log'
|
||||||
|
|
||||||
|
if not os.path.exists(log_file_path):
|
||||||
|
return jsonify({'error': 'Log file not found'}), 404
|
||||||
|
|
||||||
|
page = request.json.get('page', 1)
|
||||||
|
per_page = request.json.get('per_page', 50)
|
||||||
|
offset = (page - 1) * per_page
|
||||||
|
|
||||||
|
logs = parse_log_file(log_file_path)
|
||||||
|
total_logs = len(logs)
|
||||||
|
|
||||||
|
reversed_logs = logs[::-1]
|
||||||
|
paginated_logs = reversed_logs[offset:offset + per_page]
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'logs': paginated_logs,
|
||||||
|
'page': page,
|
||||||
|
'per_page': per_page,
|
||||||
|
'total': total_logs,
|
||||||
|
'has_more': offset + per_page < total_logs
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[API] Error: {e}", flush=True)
|
||||||
|
return jsonify({'error': str(e), 'success': False}), 500
|
||||||
|
|
||||||
@app.route('/home')
|
@app.route('/home')
|
||||||
def home():
|
def home():
|
||||||
@@ -134,6 +159,5 @@ def home():
|
|||||||
layer7_count=layer7_count,
|
layer7_count=layer7_count,
|
||||||
layer4_count=layer4_count)
|
layer4_count=layer4_count)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='::', port=5000, ssl_context=ssl_context, debug=True)
|
app.run(host='::', port=5000, ssl_context=ssl_context, debug=True)
|
||||||
@@ -1,103 +1,107 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const filterIp = document.getElementById('filter_ip');
|
let currentPage = 1;
|
||||||
const filterStatus = document.getElementById('filter_status');
|
let perPage = 50;
|
||||||
const filterMethod = document.getElementById('filter_method');
|
let totalLogs = parseInt(document.getElementById('total_count').textContent);
|
||||||
const filterThreats = document.getElementById('filter_threats');
|
let allLogs = [];
|
||||||
const filterHideStats = document.getElementById('filter_hide_stats');
|
|
||||||
const resetBtn = document.getElementById('reset_filters');
|
|
||||||
|
|
||||||
const logsTable = document.getElementById('logs_table');
|
const logsContainer = document.getElementById('logs_container');
|
||||||
if (!logsTable) return; // Exit if no logs
|
const perPageSelect = document.getElementById('logs_per_page');
|
||||||
|
const refreshBtn = document.getElementById('refresh_logs_btn');
|
||||||
|
const prevBtn = document.getElementById('prev_btn');
|
||||||
|
const nextBtn = document.getElementById('next_btn');
|
||||||
|
const loadAllBtn = document.getElementById('load_all_btn');
|
||||||
|
const loadedSpan = document.getElementById('loaded_count');
|
||||||
|
const currentPageSpan = document.getElementById('current_page');
|
||||||
|
const totalPagesSpan = document.getElementById('total_pages');
|
||||||
|
|
||||||
const allRows = Array.from(document.querySelectorAll('.log-row'));
|
perPageSelect.addEventListener('change', function(e) {
|
||||||
|
perPage = parseInt(e.target.value);
|
||||||
|
currentPage = 1;
|
||||||
|
loadLogs();
|
||||||
|
});
|
||||||
|
|
||||||
// Filter function
|
refreshBtn.addEventListener('click', function() {
|
||||||
function applyFilters() {
|
currentPage = 1;
|
||||||
const ipValue = filterIp.value.toLowerCase();
|
loadLogs();
|
||||||
const statusValue = filterStatus.value;
|
});
|
||||||
const methodValue = filterMethod.value;
|
|
||||||
const showThreats = filterThreats.checked;
|
|
||||||
const hideStats = filterHideStats.checked;
|
|
||||||
|
|
||||||
let visibleCount = 0;
|
prevBtn.addEventListener('click', function() {
|
||||||
let threatCount = 0;
|
if (currentPage > 1) {
|
||||||
let count2xx = 0, count4xx = 0, count5xx = 0;
|
currentPage--;
|
||||||
const uniqueIps = new Set();
|
loadLogs();
|
||||||
|
|
||||||
allRows.forEach(row => {
|
|
||||||
const ip = row.dataset.ip;
|
|
||||||
const status = row.dataset.status;
|
|
||||||
const method = row.dataset.method;
|
|
||||||
const hasThreat = row.dataset.threats === '1';
|
|
||||||
const url = row.querySelector('td:nth-child(4)').textContent.trim();
|
|
||||||
|
|
||||||
let show = true;
|
|
||||||
|
|
||||||
// IP filter
|
|
||||||
if (ipValue && !ip.includes(ipValue)) {
|
|
||||||
show = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status filter
|
|
||||||
if (statusValue) {
|
|
||||||
const statusStart = statusValue;
|
|
||||||
if (!status.startsWith(statusStart)) {
|
|
||||||
show = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method filter
|
|
||||||
if (methodValue && method !== methodValue) {
|
|
||||||
show = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Threats filter
|
|
||||||
if (!showThreats && hasThreat) {
|
|
||||||
show = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide /stats filter
|
|
||||||
if (hideStats && url.includes('/stats')) {
|
|
||||||
show = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
row.style.display = show ? '' : 'none';
|
|
||||||
|
|
||||||
if (show) {
|
|
||||||
visibleCount++;
|
|
||||||
if (hasThreat) threatCount++;
|
|
||||||
if (status.startsWith('2')) count2xx++;
|
|
||||||
if (status.startsWith('4')) count4xx++;
|
|
||||||
if (status.startsWith('5')) count5xx++;
|
|
||||||
uniqueIps.add(ip);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update stats
|
nextBtn.addEventListener('click', function() {
|
||||||
document.getElementById('stat_total').textContent = visibleCount;
|
const totalPages = Math.ceil(totalLogs / perPage);
|
||||||
document.getElementById('stat_threats').textContent = threatCount;
|
if (currentPage < totalPages) {
|
||||||
document.getElementById('stat_2xx').textContent = count2xx;
|
currentPage++;
|
||||||
document.getElementById('stat_4xx').textContent = count4xx;
|
loadLogs();
|
||||||
document.getElementById('stat_5xx').textContent = count5xx;
|
}
|
||||||
document.getElementById('stat_ips').textContent = uniqueIps.size;
|
});
|
||||||
|
|
||||||
|
loadAllBtn.addEventListener('click', function() {
|
||||||
|
perPage = totalLogs;
|
||||||
|
currentPage = 1;
|
||||||
|
perPageSelect.value = totalLogs;
|
||||||
|
loadLogs();
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadLogs() {
|
||||||
|
fetch('/api/logs', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
page: currentPage,
|
||||||
|
per_page: perPage
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
renderLogs(data.logs);
|
||||||
|
updatePagination(data);
|
||||||
|
console.log(`[Logs] Załadowano page ${data.page}/${Math.ceil(data.total / data.per_page)}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading logs:', error);
|
||||||
|
logsContainer.innerHTML = `<tr><td class="alert alert-danger">Błąd załadowania logów</td></tr>`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event listeners
|
function renderLogs(logs) {
|
||||||
filterIp.addEventListener('input', applyFilters);
|
if (!logs || logs.length === 0) {
|
||||||
filterStatus.addEventListener('change', applyFilters);
|
logsContainer.innerHTML = '<tr><td class="text-center text-muted py-4"><i class="bi bi-inbox"></i> Brak logów</td></tr>';
|
||||||
filterMethod.addEventListener('change', applyFilters);
|
return;
|
||||||
filterThreats.addEventListener('change', applyFilters);
|
}
|
||||||
filterHideStats.addEventListener('change', applyFilters);
|
|
||||||
|
|
||||||
// Reset button
|
logsContainer.innerHTML = logs.map(entry => `
|
||||||
resetBtn.addEventListener('click', function() {
|
<tr>
|
||||||
filterIp.value = '';
|
<td>
|
||||||
filterStatus.value = '';
|
<small style="font-family: monospace; color: #666;">
|
||||||
filterMethod.value = '';
|
<i class="bi bi-clock text-muted me-1"></i>${entry.timestamp || 'N/A'}<br>
|
||||||
filterThreats.checked = true;
|
<span class="text-muted">${entry.source || 'N/A'}</span><br>
|
||||||
filterHideStats.checked = true;
|
<code style="color: #333; word-break: break-all; display: block; margin-top: 4px;">
|
||||||
applyFilters();
|
${entry.message || 'N/A'}
|
||||||
});
|
</code>
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
applyFilters();
|
function updatePagination(data) {
|
||||||
|
const totalPages = Math.ceil(data.total / data.per_page);
|
||||||
|
loadedSpan.textContent = data.logs.length;
|
||||||
|
currentPageSpan.textContent = data.page;
|
||||||
|
totalPagesSpan.textContent = totalPages;
|
||||||
|
|
||||||
|
prevBtn.disabled = data.page === 1;
|
||||||
|
nextBtn.disabled = !data.has_more;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLogs();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user