new_functions_and_fixes #1
@@ -1,22 +1,92 @@
|
|||||||
document.getElementById('filter_status')?.addEventListener('change', filterLogs);
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.getElementById('filter_threat')?.addEventListener('change', filterLogs);
|
const filterIp = document.getElementById('filter_ip');
|
||||||
document.getElementById('filter_method')?.addEventListener('change', filterLogs);
|
const filterStatus = document.getElementById('filter_status');
|
||||||
document.getElementById('filter_threats_only')?.addEventListener('change', filterLogs);
|
const filterMethod = document.getElementById('filter_method');
|
||||||
|
const filterThreats = document.getElementById('filter_threats');
|
||||||
|
const resetBtn = document.getElementById('reset_filters');
|
||||||
|
|
||||||
function filterLogs() {
|
const logsTable = document.getElementById('logs_table');
|
||||||
const statusFilter = document.getElementById('filter_status')?.value;
|
const allRows = Array.from(document.querySelectorAll('.log-row'));
|
||||||
const threatFilter = document.getElementById('filter_threat')?.value;
|
|
||||||
const methodFilter = document.getElementById('filter_method')?.value;
|
// Filter function
|
||||||
const threatsOnly = document.getElementById('filter_threats_only')?.checked;
|
function applyFilters() {
|
||||||
|
const ipValue = filterIp.value.toLowerCase();
|
||||||
|
const statusValue = filterStatus.value;
|
||||||
|
const methodValue = filterMethod.value;
|
||||||
|
const showThreats = filterThreats.checked;
|
||||||
|
|
||||||
|
let visibleCount = 0;
|
||||||
|
let threatCount = 0;
|
||||||
|
let count2xx = 0, count4xx = 0, count5xx = 0;
|
||||||
|
const uniqueIps = new Set();
|
||||||
|
|
||||||
|
allRows.forEach(row => {
|
||||||
|
const ip = row.dataset.ip;
|
||||||
|
const status = row.dataset.status;
|
||||||
|
const method = row.dataset.method;
|
||||||
|
const hasThreat = row.dataset.threats === '1';
|
||||||
|
|
||||||
document.querySelectorAll('.log-row').forEach(row => {
|
|
||||||
let show = true;
|
let show = true;
|
||||||
|
|
||||||
if (statusFilter && row.dataset.status !== statusFilter) show = false;
|
// IP filter
|
||||||
if (threatFilter && row.dataset.threat !== threatFilter) show = false;
|
if (ipValue && !ip.includes(ipValue)) {
|
||||||
if (methodFilter && row.dataset.method !== methodFilter) show = false;
|
show = false;
|
||||||
if (threatsOnly && row.dataset.threatCount === '0') 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;
|
||||||
|
}
|
||||||
|
|
||||||
row.style.display = show ? '' : 'none';
|
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
|
||||||
|
document.getElementById('stat_total').textContent = visibleCount;
|
||||||
|
document.getElementById('stat_threats').textContent = threatCount;
|
||||||
|
document.getElementById('stat_2xx').textContent = count2xx;
|
||||||
|
document.getElementById('stat_4xx').textContent = count4xx;
|
||||||
|
document.getElementById('stat_5xx').textContent = count5xx;
|
||||||
|
document.getElementById('stat_ips').textContent = uniqueIps.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
filterIp.addEventListener('input', applyFilters);
|
||||||
|
filterStatus.addEventListener('change', applyFilters);
|
||||||
|
filterMethod.addEventListener('change', applyFilters);
|
||||||
|
filterThreats.addEventListener('change', applyFilters);
|
||||||
|
|
||||||
|
// Reset button
|
||||||
|
resetBtn.addEventListener('click', function() {
|
||||||
|
filterIp.value = '';
|
||||||
|
filterStatus.value = '';
|
||||||
|
filterMethod.value = '';
|
||||||
|
filterThreats.checked = true;
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial stats
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
@@ -2,144 +2,158 @@
|
|||||||
|
|
||||||
{% set active_page = "logs" %}
|
{% set active_page = "logs" %}
|
||||||
|
|
||||||
{% block title %}HAProxy • Access Logs{% endblock %}
|
{% block title %}HAProxy • Logs{% endblock %}
|
||||||
|
|
||||||
{% block breadcrumb %}Access Logs{% endblock %}
|
{% block breadcrumb %}Logs{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="card shadow-sm mb-4">
|
||||||
<div class="card-header bg-info text-white">
|
<div class="card-header bg-info text-white">
|
||||||
<h5 class="mb-0"><i class="bi bi-file-text me-2"></i>HAProxy Access Logs & Security Analysis</h5>
|
<h5 class="mb-0"><i class="bi bi-file-text me-2"></i>HAProxy Access Logs</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
|
<!-- Filter Section (kompaktnie, jak było) -->
|
||||||
{% if logs %}
|
{% if logs %}
|
||||||
<!-- Statistics Cards -->
|
<div class="row mb-3 g-2">
|
||||||
<div class="row mb-4">
|
<div class="col-auto">
|
||||||
<div class="col-md-3">
|
<input type="text" class="form-control form-control-sm" id="filter_ip" placeholder="Filter by IP">
|
||||||
<div class="card text-center">
|
|
||||||
<div class="card-body">
|
|
||||||
<h6 class="text-muted">Total Requests</h6>
|
|
||||||
<div class="fs-3 fw-bold text-primary">{{ logs|length }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-auto">
|
||||||
</div>
|
<select class="form-select form-select-sm" id="filter_status" style="width: auto;">
|
||||||
<div class="col-md-3">
|
<option value="">All Status</option>
|
||||||
<div class="card text-center">
|
<option value="2">2xx (Success)</option>
|
||||||
<div class="card-body">
|
<option value="3">3xx (Redirect)</option>
|
||||||
<h6 class="text-muted">Threats Detected</h6>
|
<option value="4">4xx (Client Error)</option>
|
||||||
<div class="fs-3 fw-bold text-danger">
|
<option value="5">5xx (Server Error)</option>
|
||||||
{{ logs|selectattr('is_threat')|list|length }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="card text-center">
|
|
||||||
<div class="card-body">
|
|
||||||
<h6 class="text-muted">Unique IPs</h6>
|
|
||||||
<div class="fs-3 fw-bold text-warning">
|
|
||||||
{{ logs|map(attribute='ip_address')|unique|list|length }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="card text-center">
|
|
||||||
<div class="card-body">
|
|
||||||
<h6 class="text-muted">Success Rate</h6>
|
|
||||||
<div class="fs-3 fw-bold text-success">
|
|
||||||
{% set success_count = logs|selectattr('status_code')|selectattr('status_code', 'ge', 200)|selectattr('status_code', 'lt', 300)|list|length %}
|
|
||||||
{{ ((success_count / logs|length * 100)|round(1)) if logs else 0 }}%
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Filters -->
|
|
||||||
<div class="card mb-4 bg-light">
|
|
||||||
<div class="card-body">
|
|
||||||
<h6 class="mb-3"><i class="bi bi-funnel me-2"></i>Filters</h6>
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-md-3">
|
|
||||||
<label class="form-label small">Status Code</label>
|
|
||||||
<select class="form-select form-select-sm" id="filter_status">
|
|
||||||
<option value="">All</option>
|
|
||||||
{% for log in logs %}
|
|
||||||
<option value="{{ log.status_code }}">{{ log.status_code }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-auto">
|
||||||
<label class="form-label small">Threat Level</label>
|
<select class="form-select form-select-sm" id="filter_method" style="width: auto;">
|
||||||
<select class="form-select form-select-sm" id="filter_threat">
|
<option value="">All Methods</option>
|
||||||
<option value="">All</option>
|
<option value="GET">GET</option>
|
||||||
<option value="danger">Danger</option>
|
<option value="POST">POST</option>
|
||||||
<option value="warning">Warning</option>
|
<option value="PUT">PUT</option>
|
||||||
<option value="info">Info</option>
|
<option value="DELETE">DELETE</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-auto">
|
||||||
<label class="form-label small">HTTP Method</label>
|
<div class="form-check">
|
||||||
<select class="form-select form-select-sm" id="filter_method">
|
<input class="form-check-input" type="checkbox" id="filter_threats" checked>
|
||||||
<option value="">All</option>
|
<label class="form-check-label" for="filter_threats" style="margin-top: 5px;">
|
||||||
{% set methods = logs|map(attribute='http_method')|unique %}
|
Show Threats
|
||||||
{% for method in methods %}
|
|
||||||
<option value="{{ method }}">{{ method }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<label class="form-label small">Show Threats Only</label>
|
|
||||||
<div class="form-check mt-2">
|
|
||||||
<input class="form-check-input" type="checkbox" id="filter_threats_only">
|
|
||||||
<label class="form-check-label" for="filter_threats_only">
|
|
||||||
Threats
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-auto ms-auto">
|
||||||
|
<button class="btn btn-sm btn-secondary" id="reset_filters">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Cards (kompaktnie) -->
|
||||||
|
<div class="row mb-3 g-2">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="card text-center" style="font-size: 0.9rem;">
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<div class="text-muted small">Total</div>
|
||||||
|
<strong id="stat_total">{{ logs|length }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="card text-center text-danger" style="font-size: 0.9rem;">
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<div class="text-muted small">Threats</div>
|
||||||
|
<strong id="stat_threats">0</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="card text-center text-success" style="font-size: 0.9rem;">
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<div class="text-muted small">2xx</div>
|
||||||
|
<strong id="stat_2xx">0</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="card text-center text-warning" style="font-size: 0.9rem;">
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<div class="text-muted small">4xx</div>
|
||||||
|
<strong id="stat_4xx">0</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="card text-center text-danger" style="font-size: 0.9rem;">
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<div class="text-muted small">5xx</div>
|
||||||
|
<strong id="stat_5xx">0</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="card text-center" style="font-size: 0.9rem;">
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<div class="text-muted small">Unique IPs</div>
|
||||||
|
<strong id="stat_ips">0</strong>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Logs Table -->
|
<hr>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-hover table-sm">
|
<table class="table table-striped table-hover">
|
||||||
<thead class="table-dark">
|
<thead class="table-dark">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Timestamp</th>
|
<th>Timestamp</th>
|
||||||
<th>IP Address</th>
|
<th>IP Address</th>
|
||||||
<th>Method</th>
|
<th>HTTP Method</th>
|
||||||
<th>URL</th>
|
<th>Requested URL</th>
|
||||||
<th>Status</th>
|
<th>Status Code</th>
|
||||||
<th>Threats</th>
|
<th>Alerts</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="logs_table_body">
|
<tbody id="logs_table">
|
||||||
{% for log in logs %}
|
{% for entry in logs %}
|
||||||
<tr class="log-row" data-status="{{ log.status_code }}" data-threat="{{ log.threat_level }}" data-method="{{ log.http_method }}" data-threat-count="{{ 1 if log.is_threat else 0 }}">
|
<tr class="log-row"
|
||||||
<td class="small">{{ log.timestamp }}</td>
|
data-ip="{{ entry['ip_address'] }}"
|
||||||
|
data-status="{{ entry['status_code'] }}"
|
||||||
|
data-method="{{ entry['http_method'] }}"
|
||||||
|
data-threats="{% if entry['xss_alert'] or entry['sql_alert'] or entry['put_method'] or entry['webshell_alert'] or entry['illegal_resource'] %}1{% else %}0{% endif %}">
|
||||||
|
<td>{{ entry['timestamp'] }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-secondary">{{ log.ip_address }}</span>
|
<span class="badge bg-secondary">{{ entry['ip_address'] }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-primary">{{ log.http_method }}</span>
|
<span class="badge bg-primary">{{ entry['http_method'] }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-truncate" style="max-width: 300px;" title="{{ log.requested_url }}">
|
<td class="text-truncate" style="max-width: 300px;" title="{{ entry['requested_url'] }}">
|
||||||
{{ log.requested_url }}
|
{{ entry['requested_url'] }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-{{ log.status_category }}">{{ log.status_code }}</span>
|
<span class="badge {% if entry['status_code']|int >= 200 and entry['status_code']|int < 300 %}bg-success{% elif entry['status_code']|int >= 300 and entry['status_code']|int < 400 %}bg-secondary{% elif entry['status_code']|int >= 400 and entry['status_code']|int < 500 %}bg-warning{% else %}bg-danger{% endif %}">
|
||||||
|
{{ entry['status_code'] }}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if log.is_threat %}
|
{% if entry['xss_alert'] %}
|
||||||
{% for threat in log.threats %}
|
<span class="badge bg-danger">XSS</span>
|
||||||
<span class="badge bg-{{ log.threat_level }} me-1">{{ threat }}</span>
|
{% endif %}
|
||||||
{% endfor %}
|
{% if entry['sql_alert'] %}
|
||||||
{% else %}
|
<span class="badge bg-danger">SQL</span>
|
||||||
<span class="text-muted small">—</span>
|
{% endif %}
|
||||||
|
{% if entry['put_method'] %}
|
||||||
|
<span class="badge bg-warning">PUT</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if entry['webshell_alert'] %}
|
||||||
|
<span class="badge bg-danger">Webshell</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if entry['illegal_resource'] %}
|
||||||
|
<span class="badge bg-warning">403</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -158,5 +172,4 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='js/logs.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/logs.js') }}"></script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user