Files
biiproject-kit-v1/resources/views/pages/dashboard.blade.php.bak.20260515202041
T

677 lines
30 KiB
Plaintext

<x-app-layout>
@push('styles')
<script src="https://cdn.jsdelivr.net/npm/apexcharts" crossorigin="anonymous"></script>
<style>
.sparkline-container {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60px;
overflow: hidden;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
opacity: 0.6;
}
.card-body {
position: relative;
z-index: 1;
}
/* Skeleton Loading */
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e6e6e6 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
border-radius: 4px;
display: inline-block;
}
@keyframes skeleton-loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.skeleton-text {
height: 1rem;
margin-bottom: 0.5rem;
width: 100%;
}
.skeleton-title {
height: 2.5rem;
width: 60%;
margin: 10px auto;
}
</style>
@endpush
<div class="container-fluid mt-3 pb-5" id="monitoring-master">
{{-- Welcome Header --}}
<div
class="card adminuiux-card bg-dark text-white mb-4 border-0 shadow-lg overflow-hidden animate__animated animate__fadeIn">
<div class="card-body p-4 position-relative">
<div class="row align-items-center position-relative z-1">
<div class="col">
<h1 class="display-5 fw-bold text-white mb-1 tracking-tight">{{ __('Operational Dashboard') }}
</h1>
<p class="small text-white-50 mb-0 d-flex align-items-center gap-2">
<span class="status-indicator"></span>
System operational since <span class="badge text-bg-theme-1 rounded-pill px-3 shadow-sm"
id="stat-uptime-badge">{{ $stats['uptime'] }}</span>
at <span class="fw-bold">{{ $stats['hostname'] }}</span> ({{ $stats['ip'] }})
</p>
</div>
<div class="col-auto">
<button class="btn btn-theme-1 btn-square rounded-circle shadow-sm" id="refresh-all-stats"
title="Refresh Live Data">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div class="bg-decoration"></div>
</div>
</div>
{{-- Main Stats Row --}}
<div class="row g-3 g-lg-4 mb-4">
{{-- CPU --}}
<div class="col-6 col-md-6 col-lg-3">
<div class="card adminuiux-card border-0 shadow-sm h-100 hover-lift animate__animated animate__fadeIn"
style="animation-delay: 0.1s">
<div class="card-body p-4 text-center">
<div class="d-flex justify-content-between mb-2">
<h6 class="fw-bold text-dark small mb-0">CPU LOAD</h6>
<i class="bi bi-speedometer2 text-theme-1"></i>
</div>
<h1 class="display-3 fw-black text-theme-1 mb-0 counter-value" id="stat-cpu-percent">
{{ $stats['cpu'] }}%
</h1>
<div class="mini-progress mt-3">
<div class="bar" id="cpu-bar" style="width:{{$stats['cpu']}}%"></div>
</div>
</div>
<div id="chart-cpu-sparkline" class="sparkline-container"></div>
</div>
</div>
{{-- RAM --}}
<div class="col-6 col-md-6 col-lg-3">
<div class="card adminuiux-card border-0 shadow-sm h-100 hover-lift animate__animated animate__fadeIn"
style="animation-delay: 0.2s">
<div class="card-body p-4 text-center">
<div class="d-flex justify-content-between mb-2">
<h6 class="fw-bold text-dark small mb-0">MEMORY</h6>
<i class="bi bi-memory text-info"></i>
</div>
<h1 class="display-3 fw-black text-info mb-0 counter-value" id="stat-ram-percent">
{{ $stats['ram']['percentage'] }}%
</h1>
<div class="d-flex justify-content-between extra-small text-muted mt-2">
<span id="stat-ram-used">{{ $stats['ram']['used'] }} used</span>
<span id="stat-swap-info" class="text-warning">Swap:
{{ $stats['ram']['swap']['percentage'] ?? 0 }}%</span>
</div>
</div>
<div id="chart-ram-sparkline" class="sparkline-container"></div>
</div>
</div>
{{-- DISK --}}
<div class="col-6 col-md-6 col-lg-3">
<div class="card adminuiux-card border-0 shadow-sm h-100 hover-lift animate__animated animate__fadeIn"
style="animation-delay: 0.3s">
<div class="card-body p-4 text-center">
<div class="d-flex justify-content-between mb-2">
<h6 class="fw-bold text-dark small mb-0">STORAGE</h6>
<i class="bi bi-hdd-network text-warning"></i>
</div>
<h1 class="display-3 fw-black text-warning mb-0 counter-value" id="stat-disk-percent">
{{ $stats['disk']['percentage'] }}%
</h1>
<p class="extra-small text-muted mb-0 mt-2" id="stat-disk-total">
{{ $stats['disk']['free'] }} available
</p>
</div>
<div id="chart-disk-sparkline" class="sparkline-container"></div>
</div>
</div>
{{-- SESSIONS --}}
<div class="col-6 col-md-6 col-lg-3">
<div class="card adminuiux-card border-0 shadow-sm h-100 hover-lift bg-theme-1 text-white card-glow-theme animate__animated animate__fadeIn"
style="animation-delay: 0.4s">
<div class="card-body p-4 text-center">
<div class="d-flex justify-content-between mb-2">
<h6 class="fw-bold text-white small mb-0">LIVE USERS</h6>
<i class="bi bi-people text-white-50"></i>
</div>
<h1 class="display-3 fw-black text-white mb-0 counter-value" id="stat-users-count">
{{ $stats['users']['total'] }}
</h1>
<p class="extra-small text-white-50 mb-0 mt-2">
<span id="stat-users-auth">{{ $stats['users']['authenticated'] }}</span> Authenticated
</p>
</div>
</div>
</div>
</div>
<div class="row g-3 g-lg-6">
@if(auth()->user()->can('view health and logs'))
{{-- Right Column: Live Console (Logs) --}}
<div class="col-lg-7">
<div
class="card adminuiux-card border-0 shadow-sm h-100 mb-4 mb-lg-0 animate__animated animate__fadeIn">
<div
class="card-header bg-white border-bottom p-4 d-flex justify-content-between align-items-center">
<h6 class="fw-bold text-dark mb-0 d-flex align-items-center gap-2">
<i class="bi bi-terminal text-theme-1"></i>
{{ __('Runtime Activity Feed') }}
</h6>
<div class="d-flex gap-2">
<a href="{{ route('system-monitoring') }}"
class="btn btn-sm btn-light rounded-pill px-3 fw-bold small">
<i class="bi bi-gear me-1"></i> FULL MONITOR
</a>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<style>
#logs-datatable tbody td:nth-child(3) {
white-space: normal !important;
min-width: 250px;
max-width: 400px;
word-break: break-word;
}
#logs-datatable thead th {
white-space: nowrap;
}
</style>
<table id="logs-datatable"
class="table table-hover align-middle mb-0 w-100 small compact-table">
<thead>
<tr class="bg-white">
<th>INCIDENT TIME</th>
<th>LVL</th>
<th>MANIFEST</th>
<th class="text-end pe-4">INTEL</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
@can('view ai log analysis')
{{-- AI Analysis Card --}}
<div class="col-lg-5">
<div class="card adminuiux-card border-0 shadow-sm h-100 mb-4 mb-lg-0 animate__animated animate__fadeIn"
style="animation-delay: 0.5s">
<div
class="card-header bg-transparent border-0 pt-4 px-4 d-flex justify-content-between align-items-center">
<h6 class="fw-bold text-dark mb-0 d-flex align-items-center gap-2">
<i class="bi bi-robot text-theme-1"></i>
{{ __('AI Security Insight') }}
</h6>
<div class="d-flex align-items-center gap-2">
<button class="btn btn-sm btn-outline-danger rounded-pill px-3 extra-small"
id="btn-ai-clear">
<i class="bi bi-trash me-1"></i>Clear
</button>
<button class="btn btn-sm btn-outline-dark rounded-pill px-3 extra-small"
id="btn-ai-analyze">
<i class="bi bi-cpu me-1"></i>Analyze
</button>
</div>
</div>
<div class="card-body px-4">
<div id="ai-analysis-container" class="small text-muted"
style="min-height: 200px; line-height: 1.6;">
<div class="placeholder-glow" id="ai-placeholder" style="display:none;">
<div class="d-flex flex-column gap-3">
<span class="placeholder col-12 rounded-pill py-2"></span>
<span class="placeholder col-10 rounded-pill py-1"></span>
<span class="placeholder col-11 rounded-pill py-1"></span>
<span class="placeholder col-8 rounded-pill py-1"></span>
<span class="placeholder col-9 rounded-pill py-1"></span>
</div>
</div>
<div id="ai-content-display" class="animate__animated animate__fadeIn">
<div class="text-center py-5 opacity-50">
<i class="bi bi-robot display-4 d-block mb-3"></i>
<p class="fst-italic">Click analyze to get security insights from your recent
activity logs.</p>
</div>
</div>
</div>
</div>
</div>
</div>
@endcan
</div>
</div>
{{-- Log Detail Modal --}}
<div class="modal fade animate__animated animate__fadeIn" id="logDetailModal" tabindex="-1">
<div class="modal-dialog modal-xl animate__animated animate__fadeIn">
<div class="modal-content adminuiux-card border-0 shadow-2xl terminal-box">
<div class="modal-header terminal-header">
<div class="window-controls me-3">
<span class="dot red"></span><span class="dot yellow"></span><span class="dot green"></span>
</div>
<h6 class="modal-title fw-bold extra-small text-white-50 letter-spacing-1">TELEMETRY_DUMP @node_01
</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0">
<div class="code-container p-4">
<pre id="detail-message" class="mb-0 small text-success font-monospace scroll-custom"
style="max-height: 700px; overflow-y: auto; overflow-x: hidden; line-height: 1.6; white-space: pre-wrap; word-break: break-all;"></pre>
</div>
</div>
</div>
</div>
</div>
@push('scripts')
<script>
$(document).ready(function () {
@if(auth()->user()->can('view health and logs'))
const REFRESH_RATE = 10000; // 10s for dashboard to be less aggressive
// Logs Init
const logsTable = $('#logs-datatable').DataTable({
processing: true, serverSide: true, ajax: '{{ route("system-monitoring.logs.datatable") }}',
order: [[0, 'desc']], pageLength: 5, autoWidth: false, dom: 'tr<"p-3 border-top d-flex justify-content-end"p>',
columns: [{ data: 0, className: 'ps-4 datetime-col fw-bold' }, { data: 1 }, { data: 2 }, { data: 3, className: 'pe-4 text-end', orderable: false }],
drawCallback: function () {
$('.view-log-detail').off('click').on('click', function () {
const log = $(this).data('log');
$('#detail-message').text(log.message);
new bootstrap.Modal('#logDetailModal').show();
});
}
});
function refreshStats() {
const btn = $('#refresh-all-stats');
btn.find('i').addClass('bi-spin');
$.get('{{ route("system-monitoring.stats") }}', function (d) {
updateVal('#stat-cpu-percent', d.cpu + '%');
updateVal('#stat-ram-percent', d.ram.percentage + '%');
$('#stat-ram-used').text(d.ram.used + ' used');
if (d.ram.swap) {
$('#stat-swap-info').text('Swap: ' + d.ram.swap.percentage + '%');
}
updateVal('#stat-disk-percent', d.disk.percentage + '%');
$('#stat-disk-total').text(d.disk.free + ' available');
// Refined Users
updateVal('#stat-users-count', d.users.total);
$('#stat-users-auth').text(d.users.authenticated);
updateVal('#stat-queues-pending', d.queues.pending);
updateVal('#stat-queues-failed', d.queues.failed);
$('#stat-uptime-badge').text(d.uptime);
$('#cpu-bar').css('width', d.cpu + '%');
// Update Charts
updateSparkline(cpuChart, d.cpu);
updateSparkline(ramChart, d.ram.percentage);
updateSparkline(diskChart, d.disk.percentage);
if (d.has_reverb) {
$('#reverb-icon').addClass('active');
$('#reverb-status-text').text('ACTIVE').removeClass('text-muted').addClass('text-success');
} else {
$('#reverb-icon').removeClass('active');
$('#reverb-status-text').text('IDLE').addClass('text-muted').removeClass('text-success');
}
logsTable.ajax.reload(null, false);
setTimeout(() => btn.find('i').removeClass('bi-spin'), 1000);
});
}
// --- Sparkline Helpers ---
const sparklineOptions = (color) => ({
series: [{ data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }],
chart: { type: 'area', height: 60, sparkline: { enabled: true }, animations: { enabled: true, easing: 'linear', dynamicAnimation: { speed: 1000 } } },
stroke: { curve: 'smooth', width: 2 },
fill: { opacity: 0.3, type: 'gradient', gradient: { shadeIntensity: 1, opacityFrom: 0.4, opacityTo: 0.1 } },
colors: [color],
tooltip: { enabled: false }
});
const cpuChart = new ApexCharts(document.querySelector("#chart-cpu-sparkline"), sparklineOptions('var(--adminuiux-theme-1)'));
const ramChart = new ApexCharts(document.querySelector("#chart-ram-sparkline"), sparklineOptions('#0dcaf0'));
const diskChart = new ApexCharts(document.querySelector("#chart-disk-sparkline"), sparklineOptions('#ffc107'));
cpuChart.render();
ramChart.render();
diskChart.render();
function updateSparkline(chart, val) {
let newData = chart.w.globals.series[0].slice();
newData.push(val);
if (newData.length > 10) newData.shift();
chart.updateSeries([{ data: newData }]);
}
function updateVal(selector, newVal) {
const el = $(selector);
if (el.text() !== newVal.toString()) {
el.text(newVal).addClass('animate__animated animate__pulse');
setTimeout(() => el.removeClass('animate__animated animate__pulse'), 1000);
}
}
$('#refresh-all-stats').on('click', refreshStats);
setInterval(refreshStats, REFRESH_RATE);
// 📡 Real-time Activity Feed (Broadcasting)
if (window.Echo) {
window.Echo.private('admin.monitoring')
.listen('.activity.created', (e) => {
console.log('Real-time Log received:', e.log);
// Prepend to DataTable
const rowNode = logsTable.row.add([
e.log.datetime,
e.log.level,
e.log.manifest,
`<button class="btn btn-square btn-light btn-sm rounded-circle view-log-detail" data-log='${JSON.stringify({ message: e.log.description }).replace(/'/g, "&apos;")}'>
<i class="bi bi-info-circle"></i>
</button>`
]).draw(false).node();
$(rowNode).addClass('animate__animated animate__highlight').css('background-color', '#fff9c4');
setTimeout(() => $(rowNode).css('background-color', ''), 3000);
// Re-bind modal click
$(rowNode).find('.view-log-detail').on('click', function () {
const log = $(this).data('log');
$('#detail-message').text(log.message);
new bootstrap.Modal('#logDetailModal').show();
});
});
}
@endif
// 🤖 AI Log Analysis JS
function fetchAiAnalysis() {
$.get('{{ route("ai.log-analysis.index") }}', function (d) {
if (d.analysis && !d.analysis.includes('Analysis not generated yet')) {
renderAiContent(d.analysis);
}
});
}
function renderAiContent(text) {
// Simple markdown-ish bold headers conversion
let formatted = text.replace(/### (.*)/g, '<h6 class="fw-bold text-dark mt-3 mb-2">$1</h6>')
.replace(/## (.*)/g, '<h6 class="fw-bold text-dark mt-3 mb-2">$1</h6>')
.replace(/\*\*(.*)\*\*/g, '<strong>$1</strong>')
.replace(/\n/g, '<br>');
$('#ai-content-display').html(formatted);
}
$('#btn-ai-analyze').on('click', function () {
const btn = $(this);
btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span> Analyzing...');
$('#ai-content-display').fadeOut(200, function () {
$('#ai-placeholder').show();
$.post('{{ route("ai.log-analysis.analyze") }}', { _token: '{{ csrf_token() }}' }, function (d) {
renderAiContent(d.analysis);
$('#ai-placeholder').hide();
$('#ai-content-display').fadeIn();
btn.prop('disabled', false).html('<i class="bi bi-cpu me-1"></i>Analyze');
}).fail(function () {
$('#ai-content-display').html('<div class="alert alert-danger p-2 small"><i class="bi bi-exclamation-triangle me-2"></i> Error connecting to AI service.</div>').fadeIn();
$('#ai-placeholder').hide();
btn.prop('disabled', false).html('<i class="bi bi-cpu me-1"></i>Analyze');
});
});
});
$('#btn-ai-clear').on('click', function () {
const btn = $(this);
btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm"></span>');
$.post('{{ route("ai.log-analysis.clear") }}', { _token: '{{ csrf_token() }}' }, function (d) {
$('#ai-content-display').fadeOut(200, function () {
$(this).html(`
<div class="text-center py-5 opacity-50">
<i class="bi bi-robot display-4 d-block mb-3"></i>
<p class="fst-italic">Click analyze to get security insights from your recent activity logs.</p>
</div>
`).fadeIn();
});
btn.prop('disabled', false).html('<i class="bi bi-trash me-1"></i>Clear');
}).fail(function () {
btn.prop('disabled', false).html('<i class="bi bi-trash me-1"></i>Clear');
});
});
fetchAiAnalysis();
});
</script>
<style>
.fw-black {
font-weight: 900;
}
.ls-1 {
letter-spacing: 1px;
}
.ls-2 {
letter-spacing: 2px;
}
.tracking-tight {
letter-spacing: -2px;
}
.display-3 {
font-size: 3.5rem;
letter-spacing: -2px;
}
.extra-small {
font-size: 0.85rem !important;
letter-spacing: 0.2px;
}
.hover-lift {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.hover-lift:hover {
transform: translateY(-4px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08) !important;
}
.bg-decoration {
position: absolute;
right: -50px;
bottom: -50px;
width: 150px;
height: 150px;
background: rgba(255, 255, 255, 0.05);
border-radius: 50%;
z-index: 0;
}
.icon-shape {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
}
.bg-theme-1-subtle {
background-color: rgba(var(--adminuiux-theme-1-rgb), 0.1);
}
.bg-info-subtle {
background-color: rgba(13, 202, 240, 0.1);
}
.bg-warning-subtle {
background-color: rgba(255, 193, 7, 0.1);
}
.bg-success-subtle {
background-color: rgba(25, 135, 84, 0.1);
}
.bg-danger-subtle {
background-color: rgba(220, 53, 69, 0.1);
}
.icon-box-modern {
width: 32px;
height: 32px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.hover-bg-light:hover {
background-color: #f8fafc;
cursor: pointer;
}
.pulse-tiny {
animation: pulse-glow-tiny 2s infinite;
}
@keyframes pulse-glow-tiny {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.bi-spin {
animation: spin 1.5s infinite linear;
display: inline-block;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.compact-table thead th {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 1px;
color: #64748b;
border-top: 0;
}
.compact-table tbody td {
padding: 12px 0;
border-bottom: 1px solid #f1f5f9;
font-size: 0.85rem;
}
.scroll-custom::-webkit-scrollbar {
width: 4px;
}
.scroll-custom::-webkit-scrollbar-track {
background: transparent;
}
.scroll-custom::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 10px;
}
.markdown-body {
font-size: 0.7rem !important;
line-height: 1.4 !important;
}
.markdown-body p {
margin-bottom: 0.4rem !important;
}
/* Terminal Styling */
.terminal-box {
background: #0c121e !important;
border: 1px solid #1e293b !important;
color: #10b981 !important;
min-height: 500px;
display: flex;
flex-direction: column;
}
.terminal-header {
background: #1e293b !important;
border-bottom: 1px solid #334155 !important;
padding: 12px 20px !important;
display: flex;
align-items: center;
justify-content: space-between;
}
.window-controls .dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
margin-right: 6px;
}
.dot.red {
background: #ef4444;
}
.dot.yellow {
background: #f59e0b;
}
.dot.green {
background: #22c55e;
}
.code-container pre {
font-family: 'Fira Code', 'JetBrains Mono', monospace !important;
}
</style>
@endpush
</x-app-layout>