2025-08-12 13:33:25 +03:00

681 lines
29 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}API Logs - {{ block.super }}{% endblock %}
{% block css %}
<style>
.logs-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 10px;
padding: 30px;
margin-bottom: 30px;
}
.log-entry {
background: white;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
border-left: 4px solid #007bff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.log-entry:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.log-success { border-left-color: #28a745; }
.log-error { border-left-color: #dc3545; }
.log-warning { border-left-color: #ffc107; }
.log-info { border-left-color: #17a2b8; }
.log-debug { border-left-color: #6c757d; }
.log-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.log-timestamp {
color: #6c757d;
font-size: 0.9rem;
}
.log-level {
padding: 2px 8px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
}
.level-success { background: #d4edda; color: #155724; }
.level-error { background: #f8d7da; color: #721c24; }
.level-warning { background: #fff3cd; color: #856404; }
.level-info { background: #d1ecf1; color: #0c5460; }
.level-debug { background: #e2e3e5; color: #383d41; }
.log-content {
font-family: 'Courier New', monospace;
font-size: 0.9rem;
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
white-space: pre-wrap;
word-break: break-all;
}
.log-details {
display: none;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #dee2e6;
}
.detail-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #f1f1f1;
}
.detail-item:last-child {
border-bottom: none;
}
.filter-panel {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin: 20px 0;
}
.stat-card {
background: white;
border-radius: 8px;
padding: 20px;
text-align: center;
border: 1px solid #dee2e6;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: #007bff;
}
.stat-label {
color: #6c757d;
font-size: 0.9rem;
margin-top: 5px;
}
.response-time {
font-weight: bold;
}
.response-fast { color: #28a745; }
.response-medium { color: #ffc107; }
.response-slow { color: #dc3545; }
.status-code {
padding: 2px 6px;
border-radius: 4px;
font-size: 0.8rem;
font-weight: bold;
}
.status-2xx { background: #d4edda; color: #155724; }
.status-3xx { background: #d1ecf1; color: #0c5460; }
.status-4xx { background: #fff3cd; color: #856404; }
.status-5xx { background: #f8d7da; color: #721c24; }
.search-highlight {
background: #fff3cd;
padding: 1px 3px;
border-radius: 2px;
}
.log-actions {
display: flex;
gap: 10px;
margin-top: 10px;
}
.btn-expand {
font-size: 0.8rem;
padding: 2px 8px;
}
.pagination-wrapper {
display: flex;
justify-content: between;
align-items: center;
margin: 20px 0;
}
.real-time-indicator {
display: flex;
align-items: center;
color: #28a745;
font-size: 0.9rem;
}
.pulse-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #28a745;
margin-right: 8px;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
</style>
{% endblock %}
{% block content %}
<div class="content">
<div class="container-fluid">
<!-- Page Header -->
<div class="row">
<div class="col-12">
<div class="page-header">
<div class="page-title">
<h4><i class="fas fa-list-alt"></i> API Logs</h4>
<h6>Monitor and analyze API request logs</h6>
</div>
<div class="page-btn">
<button class="btn btn-primary me-2" id="refreshLogs">
<i class="fas fa-sync-alt"></i> Refresh
</button>
<button class="btn btn-success me-2" id="exportLogs">
<i class="fas fa-download"></i> Export
</button>
<button class="btn btn-info" id="realTimeToggle">
<i class="fas fa-play"></i> Real-time
</button>
</div>
</div>
</div>
</div>
<!-- Logs Header -->
<div class="row">
<div class="col-12">
<div class="logs-header">
<div class="row">
<div class="col-md-8">
<h5><i class="fas fa-server"></i> API Request Monitoring</h5>
<p>Real-time monitoring of API requests, responses, and system integration logs</p>
<div class="real-time-indicator" id="realTimeStatus" style="display: none;">
<div class="pulse-dot"></div>
<span>Real-time monitoring active</span>
</div>
</div>
<div class="col-md-4">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">1,247</div>
<div class="stat-label">Total Requests</div>
</div>
<div class="stat-card">
<div class="stat-value">23</div>
<div class="stat-label">Errors</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Filter Panel -->
<div class="row">
<div class="col-12">
<div class="filter-panel">
<div class="row">
<div class="col-md-2">
<label>Log Level</label>
<select class="form-control" id="levelFilter">
<option value="">All Levels</option>
<option value="success">Success</option>
<option value="error">Error</option>
<option value="warning">Warning</option>
<option value="info">Info</option>
<option value="debug">Debug</option>
</select>
</div>
<div class="col-md-2">
<label>API Endpoint</label>
<select class="form-control" id="endpointFilter">
<option value="">All Endpoints</option>
<option value="/api/patients">/api/patients</option>
<option value="/api/appointments">/api/appointments</option>
<option value="/api/lab-results">/api/lab-results</option>
<option value="/api/medications">/api/medications</option>
</select>
</div>
<div class="col-md-2">
<label>Status Code</label>
<select class="form-control" id="statusFilter">
<option value="">All Status</option>
<option value="2xx">2xx Success</option>
<option value="3xx">3xx Redirect</option>
<option value="4xx">4xx Client Error</option>
<option value="5xx">5xx Server Error</option>
</select>
</div>
<div class="col-md-2">
<label>Time Range</label>
<select class="form-control" id="timeFilter">
<option value="1h">Last Hour</option>
<option value="6h">Last 6 Hours</option>
<option value="24h">Last 24 Hours</option>
<option value="7d">Last 7 Days</option>
<option value="30d">Last 30 Days</option>
</select>
</div>
<div class="col-md-3">
<label>Search</label>
<input type="text" class="form-control" id="searchFilter" placeholder="Search logs...">
</div>
<div class="col-md-1">
<label>&nbsp;</label>
<button class="btn btn-primary form-control" id="applyFilters">
<i class="fas fa-filter"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- API Logs -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-terminal"></i> Request Logs</h5>
<div class="card-tools">
<span class="badge bg-primary">Live</span>
</div>
</div>
<div class="card-body">
<div id="logsContainer">
<!-- Success Log Entry -->
<div class="log-entry log-success">
<div class="log-header">
<div>
<span class="log-level level-success">SUCCESS</span>
<strong>GET /api/patients/12345</strong>
<span class="status-code status-2xx">200</span>
<span class="response-time response-fast">145ms</span>
</div>
<div class="log-timestamp">2024-01-20 14:32:15</div>
</div>
<div class="log-content">Patient data retrieved successfully for ID: 12345</div>
<div class="log-actions">
<button class="btn btn-sm btn-outline-primary btn-expand" onclick="toggleDetails(this)">
<i class="fas fa-chevron-down"></i> Details
</button>
<button class="btn btn-sm btn-outline-secondary">
<i class="fas fa-copy"></i> Copy
</button>
</div>
<div class="log-details">
<div class="detail-item">
<span><strong>Request ID:</strong></span>
<span>req_1642684335_abc123</span>
</div>
<div class="detail-item">
<span><strong>User Agent:</strong></span>
<span>Hospital-Management-System/1.0</span>
</div>
<div class="detail-item">
<span><strong>IP Address:</strong></span>
<span>192.168.1.100</span>
</div>
<div class="detail-item">
<span><strong>Response Size:</strong></span>
<span>2.3 KB</span>
</div>
<div class="detail-item">
<span><strong>Headers:</strong></span>
<span>Content-Type: application/json, Authorization: Bearer ***</span>
</div>
</div>
</div>
<!-- Error Log Entry -->
<div class="log-entry log-error">
<div class="log-header">
<div>
<span class="log-level level-error">ERROR</span>
<strong>POST /api/appointments</strong>
<span class="status-code status-4xx">400</span>
<span class="response-time response-medium">892ms</span>
</div>
<div class="log-timestamp">2024-01-20 14:31:42</div>
</div>
<div class="log-content">Validation error: Missing required field 'patient_id'</div>
<div class="log-actions">
<button class="btn btn-sm btn-outline-primary btn-expand" onclick="toggleDetails(this)">
<i class="fas fa-chevron-down"></i> Details
</button>
<button class="btn btn-sm btn-outline-danger">
<i class="fas fa-bug"></i> Debug
</button>
</div>
<div class="log-details">
<div class="detail-item">
<span><strong>Error Code:</strong></span>
<span>VALIDATION_ERROR</span>
</div>
<div class="detail-item">
<span><strong>Request Body:</strong></span>
<span>{"appointment_date": "2024-01-25", "doctor_id": 123}</span>
</div>
<div class="detail-item">
<span><strong>Stack Trace:</strong></span>
<span>ValidationError at line 45 in appointments/views.py</span>
</div>
</div>
</div>
<!-- Warning Log Entry -->
<div class="log-entry log-warning">
<div class="log-header">
<div>
<span class="log-level level-warning">WARNING</span>
<strong>GET /api/lab-results</strong>
<span class="status-code status-2xx">200</span>
<span class="response-time response-slow">3.2s</span>
</div>
<div class="log-timestamp">2024-01-20 14:30:18</div>
</div>
<div class="log-content">Slow query detected: Lab results query took 3.2 seconds</div>
<div class="log-actions">
<button class="btn btn-sm btn-outline-primary btn-expand" onclick="toggleDetails(this)">
<i class="fas fa-chevron-down"></i> Details
</button>
<button class="btn btn-sm btn-outline-warning">
<i class="fas fa-tachometer-alt"></i> Optimize
</button>
</div>
<div class="log-details">
<div class="detail-item">
<span><strong>Query:</strong></span>
<span>SELECT * FROM lab_results WHERE patient_id = 12345</span>
</div>
<div class="detail-item">
<span><strong>Records Returned:</strong></span>
<span>1,247</span>
</div>
<div class="detail-item">
<span><strong>Suggestion:</strong></span>
<span>Add pagination or date range filter</span>
</div>
</div>
</div>
<!-- Info Log Entry -->
<div class="log-entry log-info">
<div class="log-header">
<div>
<span class="log-level level-info">INFO</span>
<strong>PUT /api/medications/789</strong>
<span class="status-code status-2xx">200</span>
<span class="response-time response-fast">234ms</span>
</div>
<div class="log-timestamp">2024-01-20 14:29:55</div>
</div>
<div class="log-content">Medication record updated successfully</div>
<div class="log-actions">
<button class="btn btn-sm btn-outline-primary btn-expand" onclick="toggleDetails(this)">
<i class="fas fa-chevron-down"></i> Details
</button>
</div>
<div class="log-details">
<div class="detail-item">
<span><strong>Updated Fields:</strong></span>
<span>dosage, frequency, end_date</span>
</div>
<div class="detail-item">
<span><strong>Updated By:</strong></span>
<span>Dr. Smith (user_id: 456)</span>
</div>
</div>
</div>
<!-- Debug Log Entry -->
<div class="log-entry log-debug">
<div class="log-header">
<div>
<span class="log-level level-debug">DEBUG</span>
<strong>GET /api/system/health</strong>
<span class="status-code status-2xx">200</span>
<span class="response-time response-fast">12ms</span>
</div>
<div class="log-timestamp">2024-01-20 14:29:30</div>
</div>
<div class="log-content">System health check completed - all services operational</div>
<div class="log-actions">
<button class="btn btn-sm btn-outline-primary btn-expand" onclick="toggleDetails(this)">
<i class="fas fa-chevron-down"></i> Details
</button>
</div>
<div class="log-details">
<div class="detail-item">
<span><strong>Database:</strong></span>
<span>Connected (5ms)</span>
</div>
<div class="detail-item">
<span><strong>Cache:</strong></span>
<span>Connected (2ms)</span>
</div>
<div class="detail-item">
<span><strong>External APIs:</strong></span>
<span>All responding</span>
</div>
</div>
</div>
</div>
<!-- Pagination -->
<div class="pagination-wrapper">
<div>
<span class="text-muted">Showing 1-10 of 1,247 entries</span>
</div>
<nav>
<ul class="pagination pagination-sm">
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1">Previous</a>
</li>
<li class="page-item active">
<a class="page-link" href="#">1</a>
</li>
<li class="page-item">
<a class="page-link" href="#">2</a>
</li>
<li class="page-item">
<a class="page-link" href="#">3</a>
</li>
<li class="page-item">
<a class="page-link" href="#">Next</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
$(document).ready(function() {
var realTimeActive = false;
var realTimeInterval;
// Toggle log details
window.toggleDetails = function(button) {
var details = $(button).closest('.log-entry').find('.log-details');
var icon = $(button).find('i');
if (details.is(':visible')) {
details.slideUp();
icon.removeClass('fa-chevron-up').addClass('fa-chevron-down');
} else {
details.slideDown();
icon.removeClass('fa-chevron-down').addClass('fa-chevron-up');
}
};
// Real-time toggle
$('#realTimeToggle').click(function() {
if (!realTimeActive) {
startRealTime();
} else {
stopRealTime();
}
});
function startRealTime() {
realTimeActive = true;
$('#realTimeToggle').html('<i class="fas fa-pause"></i> Stop Real-time');
$('#realTimeStatus').show();
// Simulate real-time log updates
realTimeInterval = setInterval(function() {
addNewLogEntry();
}, 5000);
}
function stopRealTime() {
realTimeActive = false;
clearInterval(realTimeInterval);
$('#realTimeToggle').html('<i class="fas fa-play"></i> Real-time');
$('#realTimeStatus').hide();
}
function addNewLogEntry() {
var logTypes = ['success', 'error', 'warning', 'info', 'debug'];
var endpoints = ['/api/patients', '/api/appointments', '/api/lab-results', '/api/medications'];
var methods = ['GET', 'POST', 'PUT', 'DELETE'];
var type = logTypes[Math.floor(Math.random() * logTypes.length)];
var endpoint = endpoints[Math.floor(Math.random() * endpoints.length)];
var method = methods[Math.floor(Math.random() * methods.length)];
var timestamp = new Date().toLocaleString();
var statusCode = type === 'error' ? '500' : '200';
var statusClass = type === 'error' ? 'status-5xx' : 'status-2xx';
var responseTime = Math.floor(Math.random() * 1000) + 50;
var responseClass = responseTime < 200 ? 'response-fast' : responseTime < 1000 ? 'response-medium' : 'response-slow';
var newEntry = `
<div class="log-entry log-${type}" style="display: none;">
<div class="log-header">
<div>
<span class="log-level level-${type}">${type.toUpperCase()}</span>
<strong>${method} ${endpoint}</strong>
<span class="status-code ${statusClass}">${statusCode}</span>
<span class="response-time ${responseClass}">${responseTime}ms</span>
</div>
<div class="log-timestamp">${timestamp}</div>
</div>
<div class="log-content">New ${type} log entry generated</div>
<div class="log-actions">
<button class="btn btn-sm btn-outline-primary btn-expand" onclick="toggleDetails(this)">
<i class="fas fa-chevron-down"></i> Details
</button>
</div>
<div class="log-details">
<div class="detail-item">
<span><strong>Request ID:</strong></span>
<span>req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}</span>
</div>
</div>
</div>
`;
$('#logsContainer').prepend(newEntry);
$('#logsContainer .log-entry:first').slideDown();
// Remove old entries to prevent memory issues
if ($('#logsContainer .log-entry').length > 20) {
$('#logsContainer .log-entry:last').remove();
}
}
// Filter functionality
$('#applyFilters').click(function() {
var level = $('#levelFilter').val();
var endpoint = $('#endpointFilter').val();
var status = $('#statusFilter').val();
var search = $('#searchFilter').val().toLowerCase();
$('.log-entry').each(function() {
var show = true;
if (level && !$(this).hasClass('log-' + level)) {
show = false;
}
if (endpoint && !$(this).text().includes(endpoint)) {
show = false;
}
if (status && !$(this).find('.status-code').hasClass('status-' + status)) {
show = false;
}
if (search && !$(this).text().toLowerCase().includes(search)) {
show = false;
}
$(this).toggle(show);
});
});
// Search highlighting
$('#searchFilter').on('input', function() {
var searchTerm = $(this).val();
if (searchTerm.length > 2) {
highlightSearchTerm(searchTerm);
} else {
removeHighlights();
}
});
function highlightSearchTerm(term) {
$('.log-content').each(function() {
var content = $(this).html();
var regex = new RegExp('(' + term + ')', 'gi');
var highlighted = content.replace(regex, '<span class="search-highlight">$1</span>');
$(this).html(highlighted);
});
}
function removeHighlights() {
$('.search-highlight').each(function() {
$(this).replaceWith($(this).text());
});
}
// Refresh logs
$('#refreshLogs').click(function() {
location.reload();
});
// Export logs
$('#exportLogs').click(function() {
alert('Exporting API logs...');
});
// Copy log content
$(document).on('click', '.btn-outline-secondary', function() {
var logContent = $(this).closest('.log-entry').find('.log-content').text();
navigator.clipboard.writeText(logContent).then(function() {
alert('Log content copied to clipboard');
});
});
});
</script>
{% endblock %}