Files
EonaCat.Logger/EonaCat.Logger.LogServer/Pages/Index.cshtml
2026-01-08 15:18:34 +01:00

369 lines
18 KiB
Plaintext

@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LogCentral - Enterprise Logging Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.log-trace {
@@apply bg-gray-100 border-l-4 border-gray-400;
}
.log-debug {
@@apply bg-blue-50 border-l-4 border-blue-400;
}
.log-info {
@@apply bg-green-50 border-l-4 border-green-400;
}
.log-warning {
@@apply bg-yellow-50 border-l-4 border-yellow-400;
}
.log-error {
@@apply bg-red-50 border-l-4 border-red-400;
}
.log-critical {
@@apply bg-purple-50 border-l-4 border-purple-600;
}
.log-security {
@@apply bg-orange-50 border-l-4 border-orange-500;
}
.log-analytics {
@@apply bg-teal-50 border-l-4 border-teal-400;
}
</style>
</head>
<body class="bg-gray-50">
<div class="min-h-screen">
<!-- Header -->
<header class="bg-gradient-to-r from-blue-600 to-blue-800 text-white shadow-lg">
<div class="container mx-auto px-6 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<svg class="w-10 h-10" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z" />
<path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" />
</svg>
<div>
<h1 class="text-2xl font-bold">LogCentral</h1>
<p class="text-blue-200 text-sm">Enterprise Logging Platform</p>
</div>
</div>
<nav class="flex space-x-6">
<button onclick="showTab('dashboard')" class="tab-btn hover:text-blue-200 transition">Dashboard</button>
<button onclick="showTab('logs')" class="tab-btn hover:text-blue-200 transition">Logs</button>
<button onclick="showTab('analytics')" class="tab-btn hover:text-blue-200 transition">Analytics</button>
<button onclick="showTab('search')" class="tab-btn hover:text-blue-200 transition">Search</button>
</nav>
</div>
</div>
</header>
<div class="container mx-auto px-6 py-8">
<!-- Dashboard Tab -->
<div id="dashboard-tab" class="tab-content">
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium">Total Logs</p>
<p id="stat-total" class="text-3xl font-bold text-gray-800 mt-2">0</p>
</div>
<div class="bg-blue-100 rounded-full p-3">
<svg class="w-8 h-8 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z" />
<path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm9.707 5.707a1 1 0 00-1.414-1.414L9 12.586l-1.293-1.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" />
</svg>
</div>
</div>
<p class="text-green-600 text-sm mt-4">
<span id="stat-last24h">0</span> in last 24h
</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium">Errors</p>
<p id="stat-errors" class="text-3xl font-bold text-red-600 mt-2">0</p>
</div>
<div class="bg-red-100 rounded-full p-3">
<svg class="w-8 h-8 text-red-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" />
</svg>
</div>
</div>
<p class="text-gray-500 text-sm mt-4">Critical issues</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium">Warnings</p>
<p id="stat-warnings" class="text-3xl font-bold text-yellow-600 mt-2">0</p>
</div>
<div class="bg-yellow-100 rounded-full p-3">
<svg class="w-8 h-8 text-yellow-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" />
</svg>
</div>
</div>
<p class="text-gray-500 text-sm mt-4">Needs attention</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium">Applications</p>
<p id="stat-apps" class="text-3xl font-bold text-purple-600 mt-2">0</p>
</div>
<div class="bg-purple-100 rounded-full p-3">
<svg class="w-8 h-8 text-purple-600" fill="currentColor" viewBox="0 0 20 20">
<path d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z" />
</svg>
</div>
</div>
<p class="text-gray-500 text-sm mt-4">Active services</p>
</div>
</div>
<!-- Charts -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Logs by Level</h3>
<canvas id="levelChart"></canvas>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Log Trends (24h)</h3>
<canvas id="trendChart"></canvas>
</div>
</div>
<!-- Recent Errors -->
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Recent Critical Events</h3>
<div id="recent-errors" class="space-y-3"></div>
</div>
</div>
<!-- Logs Tab -->
<div id="logs-tab" class="tab-content hidden">
<!-- Filters -->
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Filters</h3>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<input type="text" id="filter-search" placeholder="Search logs..." class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<select id="filter-app" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">All Applications</option>
</select>
<select id="filter-level" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">All Levels</option>
<option value="0">Trace</option>
<option value="1">Debug</option>
<option value="2">Info</option>
<option value="3">Warning</option>
<option value="4">Error</option>
<option value="5">Critical</option>
<option value="6">Security</option>
<option value="7">Analytics</option>
</select>
<button onclick="applyFilters()" class="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition">Apply Filters</button>
</div>
</div>
<!-- Log List -->
<div class="bg-white rounded-lg shadow-md p-6">
<div id="logs-container" class="space-y-3"></div>
<div class="mt-6 flex justify-between items-center">
<button onclick="previousPage()" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">Previous</button>
<span id="page-info" class="text-gray-600">Page 1</span>
<button onclick="nextPage()" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">Next</button>
</div>
</div>
</div>
<!-- Analytics Tab -->
<div id="analytics-tab" class="tab-content hidden">
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Analytics Dashboard</h3>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="text-md font-medium text-gray-700 mb-3">Top Events</h4>
<canvas id="analyticsChart"></canvas>
</div>
<div id="analytics-details" class="space-y-3"></div>
</div>
</div>
</div>
<!-- Search Tab -->
<div id="search-tab" class="tab-content hidden">
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Advanced Search</h3>
<div class="flex gap-4 mb-6">
<input type="text" id="search-query" placeholder="Enter search query..." class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<button onclick="performSearch()" class="bg-blue-600 text-white px-8 py-2 rounded-lg hover:bg-blue-700 transition">Search</button>
</div>
<div id="search-results" class="space-y-3"></div>
</div>
</div>
</div>
</div>
<script>
let currentPage = 1;
let levelChart, trendChart, analyticsChart;
async function loadStats() {
try {
const response = await fetch('/api/logs/stats');
const stats = await response.json();
document.getElementById('stat-total').textContent = stats.totalLogs.toLocaleString();
document.getElementById('stat-last24h').textContent = stats.last24Hours.toLocaleString();
document.getElementById('stat-errors').textContent = stats.errorCount.toLocaleString();
document.getElementById('stat-warnings').textContent = stats.warningCount.toLocaleString();
document.getElementById('stat-apps').textContent = stats.applications.toLocaleString();
updateLevelChart(stats.byLevel);
} catch (error) {
console.error('Error loading stats:', error);
}
}
function updateLevelChart(byLevel) {
const ctx = document.getElementById('levelChart').getContext('2d');
const labels = ['Trace', 'Debug', 'Info', 'Warning', 'Error', 'Critical', 'Security', 'Analytics'];
const colors = ['#9CA3AF', '#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#9333EA', '#F97316', '#14B8A6'];
const data = Array(8).fill(0);
byLevel.forEach(item => {
data[item.level] = item.count;
});
if (levelChart) levelChart.destroy();
levelChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: colors
}]
},
options: {
responsive: true,
maintainAspectRatio: true
}
});
}
async function loadLogs() {
try {
const params = new URLSearchParams({
page: currentPage,
pageSize: 20,
search: document.getElementById('filter-search')?.value || '',
application: document.getElementById('filter-app')?.value || '',
level: document.getElementById('filter-level')?.value || ''
});
const response = await fetch(`/api/logs?${params}`);
const result = await response.json();
displayLogs(result.items);
document.getElementById('page-info').textContent = `Page ${result.page} of ${result.totalPages}`;
} catch (error) {
console.error('Error loading logs:', error);
}
}
function displayLogs(logs) {
const container = document.getElementById('logs-container');
const levels = ['trace', 'debug', 'info', 'warning', 'error', 'critical', 'security', 'analytics'];
const levelNames = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRIT', 'SEC', 'ANLYTC'];
container.innerHTML = logs.map(log => `
<div class="log-${levels[log.level]} p-4 rounded-lg">
<div class="flex justify-between items-start mb-2">
<div class="flex items-center space-x-3">
<span class="px-2 py-1 text-xs font-semibold rounded">${levelNames[log.level]}</span>
<span class="text-sm font-medium text-gray-700">${log.applicationName}</span>
<span class="text-xs text-gray-500">${log.category}</span>
</div>
<span class="text-xs text-gray-500">${new Date(log.timestamp).toLocaleString()}</span>
</div>
<p class="text-gray-800 font-medium mb-1">${log.message}</p>
${log.exception ? `<pre class="text-xs text-gray-600 mt-2 overflow-x-auto bg-gray-50 p-2 rounded">${log.exception}</pre>` : ''}
<div class="flex gap-2 mt-2 text-xs text-gray-500">
<span>Machine: ${log.machineName}</span>
${log.userId ? `<span>User: ${log.userId}</span>` : ''}
</div>
</div>
`).join('');
}
function showTab(tab) {
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
document.getElementById(`${tab}-tab`).classList.remove('hidden');
if (tab === 'dashboard') loadStats();
if (tab === 'logs') loadLogs();
if (tab === 'analytics') loadAnalytics();
}
function applyFilters() {
currentPage = 1;
loadLogs();
}
function previousPage() {
if (currentPage > 1) {
currentPage--;
loadLogs();
}
}
function nextPage() {
currentPage++;
loadLogs();
}
async function performSearch() {
const query = document.getElementById('search-query').value;
if (!query) return;
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const results = await response.json();
displayLogs(results);
} catch (error) {
console.error('Error searching:', error);
}
}
async function loadAnalytics() {
// Load analytics data
}
// Initialize
loadStats();
setInterval(loadStats, 30000); // Refresh every 30 seconds
</script>
</body>
</html>