1484 lines
92 KiB
PHP
1484 lines
92 KiB
PHP
<x-app-layout>
|
||
<div class="container-fluid pb-4" id="monitoring-master">
|
||
|
||
<!-- HEADER STRIP: WITH ENTRANCE ANIMATION (SLOW) -->
|
||
<div class="card adminuiux-card bg-dark text-white mb-3 border-0 shadow-lg overflow-hidden"
|
||
>
|
||
<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">{{ __('Monitoring Center') }}</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 node {{ $stats['hostname'] }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<!-- Subtle Background Decorative Circles -->
|
||
<div class="bg-decoration"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MAIN GRID: STAGGERED ANIMATION -->
|
||
<div class="row g-3 mb-4">
|
||
|
||
<!-- INFRASTRUCTURE FEED -->
|
||
<div class="col-lg-4">
|
||
<div class="card adminuiux-card border-0 shadow-sm h-100 hover-lift" >
|
||
<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">{{ __('Infrastructure Feed') }}</h6>
|
||
<span class="pulse-badge success" id="global-health-pill">ONLINE</span>
|
||
</div>
|
||
<div class="card-body px-0 pt-2">
|
||
<div class="list-group list-group-flush">
|
||
<!-- REDIS -->
|
||
<div
|
||
class="list-group-item border-0 py-3 px-4 d-flex align-items-center transition-all hover-bg-light">
|
||
<div class="icon-box me-3"><i class="bi bi-lightning-charge fs-5"></i></div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">Redis Cache</div>
|
||
<div class="extra-small text-muted"><span
|
||
id="stat-redis-latency">{{ $stats['redis']['latency'] ?? '0ms' }}</span> |
|
||
<span id="stat-redis-memory">{{ $stats['redis']['memory_used'] ?? 'Unknown' }}</span>
|
||
</div>
|
||
</div>
|
||
<span class="fw-bold extra-small {{ ($stats['redis']['status'] ?? '') === 'connected' ? 'text-success status-pulse' : 'text-danger' }}"
|
||
id="stat-redis-status-text">{{ ($stats['redis']['status'] ?? '') === 'connected' ? 'CONNECTED' : 'DISCONNECTED' }}</span>
|
||
</div>
|
||
<!-- DB -->
|
||
<div
|
||
class="list-group-item border-0 py-3 px-4 d-flex align-items-center transition-all hover-bg-light">
|
||
<div class="icon-box me-3"><i class="bi bi-database-check fs-5"></i></div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">Database Engine</div>
|
||
<div class="extra-small text-muted" id="stat-db-meta">{{ $stats['db_stats']['tables'] }} Tables | {{ $stats['db_stats']['latency'] ?? '0ms' }}</div>
|
||
</div>
|
||
<span class="fw-bold extra-small status-pulse {{ ($stats['db_stats']['status'] ?? 'STABLE') === 'STABLE' ? 'text-success' : 'text-danger' }}" id="stat-db-status">
|
||
{{ $stats['db_stats']['status'] ?? 'STABLE' }}
|
||
</span>
|
||
</div>
|
||
<!-- REVERB -->
|
||
<div
|
||
class="list-group-item border-0 py-3 px-4 d-flex align-items-center transition-all hover-bg-light">
|
||
<div class="icon-box me-3 {{ $stats['has_reverb'] ? 'active' : '' }}" id="reverb-icon">
|
||
<i class="bi bi-broadcast fs-5"></i>
|
||
</div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">Real-time Echo</div>
|
||
<div class="extra-small text-muted">WebSocket (Reverb) active</div>
|
||
</div>
|
||
<span
|
||
class="fw-bold extra-small {{ $stats['has_reverb'] ? 'text-success status-pulse' : 'text-muted' }}"
|
||
id="reverb-status-text">
|
||
{{ $stats['has_reverb'] ? 'ACTIVE' : 'IDLE' }}
|
||
</span>
|
||
</div>
|
||
<!-- SAP -->
|
||
<div
|
||
class="list-group-item border-0 py-3 px-4 d-flex align-items-center transition-all hover-bg-light">
|
||
<div class="icon-box me-3 {{ $stats['sap']['active'] ? 'active' : '' }}">
|
||
<i class="bi bi-diagram-3 fs-5"></i>
|
||
</div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">SAP RFC Bridge</div>
|
||
<div class="extra-small text-muted">Trace: {{ $stats['sap']['size'] }}</div>
|
||
</div>
|
||
<span class="fw-bold extra-small {{ $stats['sap']['active'] ? 'text-success' : 'text-muted' }}">
|
||
{{ $stats['sap']['status'] }}
|
||
</span>
|
||
</div>
|
||
<!-- MOBILE -->
|
||
<div
|
||
class="list-group-item border-0 py-3 px-4 d-flex align-items-center transition-all hover-bg-light">
|
||
<div class="icon-box me-3 active">
|
||
<i class="bi bi-phone fs-5"></i>
|
||
</div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">Mobile Telemetry</div>
|
||
<div class="extra-small text-muted">{{ $stats['mobile']['total_logs'] }} Events recorded</div>
|
||
</div>
|
||
<span class="fw-bold extra-small text-primary">
|
||
{{ $stats['mobile']['last_activity'] }}
|
||
</span>
|
||
</div>
|
||
<!-- JOBS THROUGHPUT -->
|
||
<div
|
||
class="list-group-item border-0 py-3 px-4 d-flex align-items-center transition-all hover-bg-light">
|
||
<div class="icon-box me-3"><i class="bi bi-cpu fs-5"></i></div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">Job Throughput</div>
|
||
<div class="extra-small text-muted" id="stat-queues-load-text">{{ $stats['queues']['load_factor'] }} tasks/min</div>
|
||
</div>
|
||
<span class="fw-bold extra-small text-success status-pulse">RUNNING</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- VITAL NUMBERS (MIDDLE) -->
|
||
<div class="col-lg-5">
|
||
<div class="row g-3 h-100">
|
||
<div class="col-6">
|
||
<div class="card adminuiux-card border-0 shadow-sm h-100 hover-lift">
|
||
<div class="card-body p-4">
|
||
<div class="d-flex justify-content-between mb-2">
|
||
<h6 class="fw-bold text-dark small mb-0">CPU</h6>
|
||
<i class="bi bi-speedometer 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-2">
|
||
<div class="bar" id="cpu-bar" style="width:{{$stats['cpu']}}%"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6">
|
||
<div class="card adminuiux-card border-0 shadow-sm h-100 hover-lift">
|
||
<div class="card-body p-4">
|
||
<div class="d-flex justify-content-between mb-2">
|
||
<h6 class="fw-bold text-dark small mb-0">RAM</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-1">
|
||
<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>
|
||
</div>
|
||
<div class="col-6">
|
||
<div
|
||
class="card adminuiux-card border-0 shadow-sm h-100 hover-lift text-warning card-glow-warning">
|
||
<div class="card-body p-4">
|
||
<div class="d-flex justify-content-between mb-2">
|
||
<h6 class="fw-bold text-dark small mb-0">DISK</h6>
|
||
<i class="bi bi-hdd-network"></i>
|
||
</div>
|
||
<h1 class="display-3 fw-black mb-0 counter-value" id="stat-disk-percent">
|
||
{{ $stats['disk']['percentage'] }}%
|
||
</h1>
|
||
<p class="extra-small text-muted mb-0" id="stat-disk-total">
|
||
{{ $stats['disk']['total'] }} available
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6">
|
||
<div
|
||
class="card adminuiux-card border-0 shadow-sm h-100 hover-lift bg-theme-1 text-white card-glow-theme">
|
||
<div class="card-body p-4">
|
||
<div class="d-flex justify-content-between mb-2">
|
||
<h6 class="fw-bold text-white small mb-0">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">
|
||
<span id="stat-users-auth">{{ $stats['users']['authenticated'] }}</span> Authenticated
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AUTOMATION (RIGHT) -->
|
||
<div class="col-lg-3">
|
||
<div class="row g-3 h-100">
|
||
<div class="col-12">
|
||
<div
|
||
class="card adminuiux-card border-0 shadow-sm h-100 hover-lift position-relative overflow-hidden">
|
||
<div class="card-body p-4">
|
||
<h6 class="fw-bold text-dark small">PENDING</h6>
|
||
<h1 class="display-1 fw-black text-theme-1 counter-value" id="stat-queues-pending">
|
||
{{ $stats['queues']['pending'] }}
|
||
</h1>
|
||
<i
|
||
class="bi bi-hourglass-top position-absolute top-50 end-0 translate-middle-y opacity-10 fs-1 me-3"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<div
|
||
class="card adminuiux-card border-0 shadow-sm h-100 hover-lift position-relative overflow-hidden">
|
||
<div class="card-body p-4">
|
||
<h6 class="fw-bold text-dark small text-danger">INCIDENTS</h6>
|
||
<h1 class="display-1 fw-black text-danger counter-value" id="stat-queues-failed">
|
||
{{ $stats['queues']['failed'] }}
|
||
</h1>
|
||
<i
|
||
class="bi bi-exclamation-triangle position-absolute top-50 end-0 translate-middle-y opacity-10 fs-1 me-3"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TABS AREA: MODERIZED CONSOLE -->
|
||
<div class="row g-3">
|
||
<div class="col-12">
|
||
<div class="card adminuiux-card border-0 shadow-sm overflow-hidden" >
|
||
<div class="card-header p-0 bg-white border-bottom">
|
||
<ul class="nav nav-tabs px-4 border-0 custom-console-tabs" id="consoleTabs" role="tablist">
|
||
@cantab('health and logs', 'system-monitor')
|
||
<li class="nav-item">
|
||
<button class="nav-link active py-3 px-4 small fw-bold" data-bs-toggle="tab"
|
||
data-bs-target="#tab-logs"><i class="bi bi-terminal me-2"></i>Logs</button>
|
||
</li>
|
||
@endcantab
|
||
@cantab('health and logs', 'error-logs')
|
||
<li class="nav-item">
|
||
<button class="nav-link py-3 px-4 small fw-bold" data-bs-toggle="tab"
|
||
data-bs-target="#tab-mobile"><i class="bi bi-phone me-2"></i>Mobile</button>
|
||
</li>
|
||
<li class="nav-item">
|
||
<button class="nav-link py-3 px-4 small fw-bold" data-bs-toggle="tab"
|
||
data-bs-target="#tab-sap"><i class="bi bi-broadcast me-2"></i>SAP RFC</button>
|
||
</li>
|
||
<li class="nav-item">
|
||
<button class="nav-link py-3 px-4 small fw-bold" data-bs-toggle="tab"
|
||
data-bs-target="#tab-jobs"><i class="bi bi-cpu me-2"></i>Queues</button>
|
||
</li>
|
||
@endcantab
|
||
@cantab('health and logs', 'ai-log-analysis')
|
||
<li class="nav-item">
|
||
<button class="nav-link py-3 px-4 small fw-bold text-danger" data-bs-toggle="tab"
|
||
data-bs-target="#tab-security"><i class="bi bi-shield-lock me-2"></i>Security AI</button>
|
||
</li>
|
||
@endcantab
|
||
@cantab('health and logs', 'query-logs')
|
||
<li class="nav-item">
|
||
<button class="nav-link py-3 px-4 small fw-bold" data-bs-toggle="tab"
|
||
data-bs-target="#tab-db"><i class="bi bi-search me-2"></i>Forensics</button>
|
||
</li>
|
||
@endcantab
|
||
@cantab('health and logs', 'system-monitor')
|
||
<li class="nav-item ms-auto">
|
||
<button class="nav-link py-3 px-4 small fw-bold" data-bs-toggle="tab"
|
||
data-bs-target="#tab-docs"><i class="bi bi-book me-2"></i>Documentation</button>
|
||
</li>
|
||
@endcantab
|
||
</ul>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<div class="tab-content">
|
||
<!-- LOGS -->
|
||
@cantab('health and logs', 'system-monitor')
|
||
<div class="tab-pane fade show active" id="tab-logs">
|
||
<div
|
||
class="action-bar p-3 bg-light border-bottom d-flex justify-content-between align-items-center px-4">
|
||
<span class="small fw-bold text-secondary text-uppercase ls-1">Runtime
|
||
Activity</span>
|
||
<div class="d-flex gap-2">
|
||
@can('manage health and logs')
|
||
<a href="{{ route('system-monitoring.logs.download') }}"
|
||
class="btn btn-sm btn-outline-dark rounded-pill px-3"><i
|
||
class="bi bi-file-earmark-x me-1"></i>Export CSV</a>
|
||
<button id="clear-logs-btn"
|
||
class="btn btn-sm btn-outline-danger rounded-pill px-3"><i
|
||
class="bi bi-trash me-1"></i>Clear All Logs</button>
|
||
@endcan
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table id="logs-datatable"
|
||
class="table table-hover align-middle mb-0 w-100 small compact-table">
|
||
<thead>
|
||
<tr class="bg-white">
|
||
<th class="ps-4">INCIDENT TIME</th>
|
||
<th>LVL</th>
|
||
<th>MANIFEST</th>
|
||
<th class="text-end pe-4">INTEL</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
@endcantab
|
||
<!-- JOBS -->
|
||
@cantab('health and logs', 'error-logs')
|
||
<div class="tab-pane fade" id="tab-jobs">
|
||
<div
|
||
class="action-bar p-3 bg-light border-bottom d-flex justify-content-between align-items-center px-4">
|
||
<span class="small fw-bold text-secondary text-uppercase ls-1">Queue Pipeline</span>
|
||
<div class="d-flex gap-2">
|
||
@can('manage health and logs')
|
||
<button id="retry-all-jobs-btn"
|
||
class="btn btn-sm btn-outline-dark rounded-pill px-3"><i
|
||
class="bi bi-arrow-repeat me-1"></i>Retry All Jobs</button>
|
||
<button id="clear-failed-jobs-btn"
|
||
class="btn btn-sm btn-outline-danger rounded-pill px-3"><i
|
||
class="bi bi-trash me-1"></i>Flush All Jobs</button>
|
||
@endcan
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table id="background-jobs-datatable"
|
||
class="table table-hover align-middle mb-0 w-100 small compact-table">
|
||
<thead>
|
||
<tr class="bg-white">
|
||
<th class="ps-4">TIMESTAMP</th>
|
||
<th>DISPOSITION</th>
|
||
<th>CHANNEL</th>
|
||
<th>DATA</th>
|
||
<th>TASK DETAIL</th>
|
||
@can('manage health and logs')
|
||
<th class="text-end pe-4">ACTION</th>
|
||
@endcan
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<!-- SAP RFC -->
|
||
<div class="tab-pane fade" id="tab-sap">
|
||
<div class="action-bar p-3 bg-light border-bottom d-flex justify-content-between align-items-center px-4">
|
||
<span class="small fw-bold text-secondary text-uppercase ls-1">SAP RFC Trace (dev_rfc.trc)</span>
|
||
<div class="d-flex gap-2">
|
||
@can('manage health and logs')
|
||
<a href="{{ route('system-monitoring.sap-logs.download') }}" class="btn btn-sm btn-outline-dark rounded-pill px-3"><i class="bi bi-file-earmark-x me-1"></i>Export CSV</a>
|
||
<button id="clear-sap-logs-btn" class="btn btn-sm btn-outline-danger rounded-pill px-3"><i class="bi bi-trash me-1"></i>Clear All Logs</button>
|
||
@endcan
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table id="sap-logs-datatable" class="table table-hover align-middle mb-0 w-100 small compact-table">
|
||
<thead>
|
||
<tr class="bg-white">
|
||
<th class="ps-4">TIMESTAMP</th>
|
||
<th>TRACE BLOCK SUMMARY</th>
|
||
<th class="text-end pe-4">DETAIL</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<!-- MOBILE -->
|
||
<div class="tab-pane fade" id="tab-mobile">
|
||
<div class="action-bar p-3 bg-light border-bottom d-flex justify-content-between align-items-center px-4">
|
||
<span class="small fw-bold text-secondary text-uppercase ls-1">Mobile Device Telemetry</span>
|
||
<div class="d-flex gap-2">
|
||
@can('manage health and logs')
|
||
<a href="{{ route('system-monitoring.mobile-logs.download') }}" class="btn btn-sm btn-outline-dark rounded-pill px-3"><i class="bi bi-file-earmark-x me-1"></i>Export CSV</a>
|
||
<button id="clear-mobile-logs-btn" class="btn btn-sm btn-outline-danger rounded-pill px-3"><i class="bi bi-trash me-1"></i>Clear All Logs</button>
|
||
@endcan
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table id="mobile-logs-datatable" class="table table-hover align-middle mb-0 w-100 small compact-table">
|
||
<thead>
|
||
<tr class="bg-white">
|
||
<th class="ps-4">TIMESTAMP</th>
|
||
<th>LVL</th>
|
||
<th>USER</th>
|
||
<th>MESSAGE</th>
|
||
<th class="text-end pe-4">DETAIL</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
@endcantab
|
||
@cantab('health and logs', 'ai-log-analysis')
|
||
<!-- SECURITY AI -->
|
||
<div class="tab-pane fade" id="tab-security">
|
||
<div class="action-bar p-3 bg-light border-bottom d-flex justify-content-between align-items-center px-4">
|
||
<span class="small fw-bold text-danger text-uppercase ls-1">AI-Driven System Hardening</span>
|
||
<button id="refresh-security-audit" class="btn btn-sm btn-outline-danger rounded-pill px-3">
|
||
<i class="bi bi-shield-check me-1"></i>RE-AUDIT SYSTEM
|
||
</button>
|
||
</div>
|
||
<div class="p-4">
|
||
<div class="row">
|
||
<div class="col-lg-3 text-center mb-4 mb-lg-0 border-end">
|
||
<div class="security-score-gauge mx-auto mb-3">
|
||
<div class="score-inner">
|
||
<h1 class="display-3 fw-black mb-0" id="ai-security-score">--</h1>
|
||
<div class="extra-small fw-bold text-uppercase opacity-50">Score</div>
|
||
</div>
|
||
</div>
|
||
<div id="security-score-label" class="badge rounded-pill px-3 py-2">SCANNING...</div>
|
||
<p class="extra-small text-muted mt-3">Last audit: <span id="ai-security-time">Never</span></p>
|
||
</div>
|
||
<div class="col-lg-9 px-lg-4">
|
||
<h6 class="fw-bold mb-3 d-flex align-items-center">
|
||
<i class="bi bi-clipboard2-pulse text-danger me-2"></i>
|
||
Security Analysis & Hardening Recommendations
|
||
</h6>
|
||
<div id="ai-security-report" class="markdown-body p-3 bg-white border rounded-4 scroll-custom" style="max-height: 500px; overflow-y: auto;">
|
||
<div class="text-center py-5 opacity-50">
|
||
<i class="bi bi-shield-lock display-4 d-block mb-3"></i>
|
||
<p class="fw-bold mb-1">No Active Audit Found</p>
|
||
<p class="small">Click the "RE-AUDIT SYSTEM" button above to initiate an AI-driven security analysis.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endcantab
|
||
<!-- DB -->
|
||
@cantab('health and logs', 'query-logs')
|
||
<div class="tab-pane fade p-4" id="tab-db">
|
||
<div class="row g-4">
|
||
<div class="col-md-6 mb-3">
|
||
<h6 class="fw-bold mb-3 small text-uppercase ls-1">Storage Metric</h6>
|
||
<div class="metric-card mb-2 d-flex justify-content-between align-items-center">
|
||
<span>Volume Footprint</span>
|
||
<span class="h4 fw-black mb-0 text-theme-1"
|
||
id="stat-db-size">{{ $stats['db_stats']['size'] }}</span>
|
||
</div>
|
||
<div class="metric-card d-flex justify-content-between align-items-center">
|
||
<span>Active Ecosystem Tables</span>
|
||
<span
|
||
class="h4 fw-black mb-0 text-dark">{{ $stats['db_stats']['tables'] }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<h6 class="fw-bold mb-3 small text-uppercase ls-1">Forensic Usage Breakdown</h6>
|
||
<div id="top-tables-list" class="scroll-custom"
|
||
style="max-height: 200px; overflow-y: auto;">
|
||
@foreach($stats['db_stats']['top_tables'] ?? [] as $t)
|
||
<div
|
||
class="d-flex justify-content-between mb-2 extra-small border-bottom pb-2">
|
||
<span class="fw-bold text-dark">{{ $t->table }}</span>
|
||
<span class="fw-black text-theme-1">{{ $t->size_pretty }}</span>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endcantab
|
||
|
||
{{-- DOCUMENTATION --}}
|
||
@cantab('health and logs', 'system-monitor')
|
||
<div class="tab-pane fade" id="tab-docs">
|
||
<div class="action-bar p-3 bg-light border-bottom d-flex justify-content-between align-items-center px-4">
|
||
<span class="small fw-bold text-secondary text-uppercase ls-1">Monitoring Center · Reference</span>
|
||
<span class="extra-small text-muted">Auto-refresh every 5s · Host {{ $stats['hostname'] }}</span>
|
||
</div>
|
||
|
||
<div class="p-4 p-lg-5">
|
||
|
||
{{-- HERO INTRO --}}
|
||
<div class="row align-items-center mb-5 g-4">
|
||
<div class="col-lg-8">
|
||
<h3 class="fw-black tracking-tight mb-2">What the Monitoring Center shows you</h3>
|
||
<p class="text-muted mb-0" style="font-size:.92rem;line-height:1.7;">
|
||
A single pane of glass that aggregates <span class="fw-bold text-dark">five live telemetry streams</span> —
|
||
application logs, mobile clients, SAP RFC traces, queue workers, and an AI-driven security audit —
|
||
on top of host vitals (CPU / RAM / Disk). All data is polled every
|
||
<span class="fw-bold text-dark">5 seconds</span>; counters animate when they change.
|
||
</p>
|
||
</div>
|
||
<div class="col-lg-4">
|
||
<div class="p-4 rounded-4 text-white" style="background:#0f172a;">
|
||
<div class="extra-small opacity-75 fw-bold mb-1">DATA SOURCES</div>
|
||
<div class="fw-bold mb-3" style="font-size:.95rem;">Where the numbers come from</div>
|
||
<div class="d-flex flex-column gap-2 extra-small">
|
||
<div><i class="bi bi-cpu me-2"></i>OS sysinfo (CPU / RAM / Disk)</div>
|
||
<div><i class="bi bi-database me-2"></i>Postgres / Redis live ping</div>
|
||
<div><i class="bi bi-file-earmark-text me-2"></i><code>storage/logs/laravel.log</code></div>
|
||
<div><i class="bi bi-broadcast me-2"></i><code>dev_rfc.trc</code> (SAP)</div>
|
||
<div><i class="bi bi-phone me-2"></i><code>mobile_logs</code> table</div>
|
||
<div><i class="bi bi-list-task me-2"></i><code>jobs</code> + <code>failed_jobs</code></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- TOP GRID GUIDE --}}
|
||
<h6 class="fw-bold text-uppercase mb-3 ls-1">Top grid · Three columns explained</h6>
|
||
<div class="row g-3 mb-5">
|
||
<div class="col-lg-4">
|
||
<div class="p-3 rounded-4 h-100" style="background:#f8fafc;border:1px solid #e2e8f0;">
|
||
<div class="d-flex align-items-center gap-2 mb-2">
|
||
<div class="icon-box active" style="width:36px;height:36px;"><i class="bi bi-diagram-3"></i></div>
|
||
<div class="fw-bold text-dark">Infrastructure Feed (left)</div>
|
||
</div>
|
||
<div class="extra-small text-muted" style="line-height:1.6;">
|
||
Live health pings of every external service: Redis, Postgres, Reverb WebSocket, SAP RFC bridge, Mobile telemetry, Job throughput. Status text turns red as soon as a ping fails.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-4">
|
||
<div class="p-3 rounded-4 h-100" style="background:#f8fafc;border:1px solid #e2e8f0;">
|
||
<div class="d-flex align-items-center gap-2 mb-2">
|
||
<div class="icon-box" style="background:#dbeafe;color:#1d4ed8;width:36px;height:36px;"><i class="bi bi-speedometer2"></i></div>
|
||
<div class="fw-bold text-dark">Vital Numbers (middle)</div>
|
||
</div>
|
||
<div class="extra-small text-muted" style="line-height:1.6;">
|
||
Host metrics: CPU load, RAM usage with swap, Disk usage, Active Users. The percentages animate via <code>counter-value</code> and the mini-progress bar reflects load in real time.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-4">
|
||
<div class="p-3 rounded-4 h-100" style="background:#f8fafc;border:1px solid #e2e8f0;">
|
||
<div class="d-flex align-items-center gap-2 mb-2">
|
||
<div class="icon-box" style="background:#fef3c7;color:#a16207;width:36px;height:36px;"><i class="bi bi-list-task"></i></div>
|
||
<div class="fw-bold text-dark">Automation (right)</div>
|
||
</div>
|
||
<div class="extra-small text-muted" style="line-height:1.6;">
|
||
Two stacked critical counters: pending jobs and failed/incident count. These come from Laravel's <code>jobs</code> and <code>failed_jobs</code> tables and update every cycle.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- TAB-BY-TAB GUIDE --}}
|
||
<h6 class="fw-bold text-uppercase mb-3 ls-1">Console tabs · What each one does</h6>
|
||
@php
|
||
$tabs = [
|
||
['icon'=>'bi-terminal', 'name'=>'Logs', 'color'=>'#0f172a', 'desc'=>'Tails <code>storage/logs/laravel.log</code>. Each row classifies INFO / WARN / ERROR / DEBUG. Click the row to open the terminal modal with the full message + stack trace. Admins can clear or export CSV.'],
|
||
['icon'=>'bi-phone', 'name'=>'Mobile', 'color'=>'#3b82f6', 'desc'=>'Telemetry from mobile clients hitting the API. Shows level, user, payload. Useful to verify a mobile deploy is reaching the right user pool.'],
|
||
['icon'=>'bi-broadcast', 'name'=>'SAP RFC', 'color'=>'#8b5cf6', 'desc'=>'Parsed blocks from <code>dev_rfc.trc</code>. Each block represents a roundtrip to SAP. Critical for diagnosing integration failures between Laravel and the SAP backend.'],
|
||
['icon'=>'bi-cpu', 'name'=>'Queues', 'color'=>'#f59e0b', 'desc'=>'Lists pending and failed background jobs. From here you can retry any failed job individually, retry all at once, or flush the queue entirely.'],
|
||
['icon'=>'bi-shield-lock', 'name'=>'Security AI', 'color'=>'#ef4444', 'desc'=>'Runs an AI-driven security audit of the running app: env file exposure, weak headers, missing CSRF / rate-limits, debug mode in prod, default credentials. Returns a numerical score + markdown hardening report.'],
|
||
['icon'=>'bi-search', 'name'=>'Forensics', 'color'=>'#22c55e', 'desc'=>'Database volume footprint, total table count, and a ranked list of the heaviest tables. Use this when storage growth becomes a concern.'],
|
||
];
|
||
@endphp
|
||
<div class="row g-3 mb-5">
|
||
@foreach($tabs as $t)
|
||
<div class="col-md-6 col-lg-4">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fff;border:1px solid #e2e8f0;">
|
||
<div class="d-flex align-items-center gap-2 mb-2">
|
||
<div class="icon-box" style="background:{{ $t['color'] }}1a;color:{{ $t['color'] }};width:36px;height:36px;">
|
||
<i class="{{ $t['icon'] }}"></i>
|
||
</div>
|
||
<div class="fw-bold text-dark">{{ $t['name'] }}</div>
|
||
</div>
|
||
<div class="extra-small text-muted" style="line-height:1.6;">{!! $t['desc'] !!}</div>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
|
||
{{-- SIGNAL DICTIONARY --}}
|
||
<h6 class="fw-bold text-uppercase mb-3 ls-1">
|
||
<i class="bi bi-flag-fill text-warning me-1"></i>How to read the indicators
|
||
</h6>
|
||
<div class="row g-2 mb-5">
|
||
<div class="col-md-6">
|
||
<div class="p-3 rounded-4 h-100" style="background:#f0fdf4;border:1px solid #bbf7d0;">
|
||
<div class="d-flex align-items-center gap-2 mb-1">
|
||
<span class="pulse-badge success">ONLINE / CONNECTED / RUNNING</span>
|
||
</div>
|
||
<div class="extra-small text-muted">All systems responding within latency budget. The pulsing dot animation indicates a recent successful ping.</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fef2f2;border:1px solid #fecaca;">
|
||
<div class="d-flex align-items-center gap-2 mb-1">
|
||
<span class="extra-small fw-bold text-danger">DISCONNECTED / OFFLINE</span>
|
||
</div>
|
||
<div class="extra-small text-muted">Service is unreachable. Check the corresponding env config (Redis host, DB credentials, SAP destination) and that the underlying daemon is running.</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fffbeb;border:1px solid #fde68a;">
|
||
<div class="d-flex align-items-center gap-2 mb-1">
|
||
<span class="extra-small fw-bold" style="color:#a16207;">IDLE</span>
|
||
</div>
|
||
<div class="extra-small text-muted">Service is configured but not currently emitting traffic — not necessarily broken (e.g. Reverb with no active subscribers).</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="p-3 rounded-4 h-100" style="background:#eff6ff;border:1px solid #bfdbfe;">
|
||
<div class="d-flex align-items-center gap-2 mb-1">
|
||
<span class="extra-small fw-bold text-primary">STABLE</span>
|
||
</div>
|
||
<div class="extra-small text-muted">Database engine status — Postgres responding, latency under threshold.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- THRESHOLDS --}}
|
||
<h6 class="fw-bold text-uppercase mb-3 ls-1">
|
||
<i class="bi bi-thermometer-half text-info me-1"></i>Recommended thresholds
|
||
</h6>
|
||
<div class="table-responsive mb-5">
|
||
<table class="table table-hover align-middle small mb-0" style="background:#f8fafc;border-radius:12px;overflow:hidden;">
|
||
<thead style="background:#e2e8f0;">
|
||
<tr>
|
||
<th class="ps-3">METRIC</th>
|
||
<th>HEALTHY</th>
|
||
<th>WARN</th>
|
||
<th>CRITICAL</th>
|
||
<th>WHAT TO DO</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">CPU Load</td>
|
||
<td><span class="pulse-badge success">< 60%</span></td>
|
||
<td><span class="extra-small text-warning fw-bold">60–80%</span></td>
|
||
<td><span class="extra-small text-danger fw-bold">> 80%</span></td>
|
||
<td class="text-muted">Check long-running queries / N+1 patterns / runaway workers.</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">RAM Usage</td>
|
||
<td><span class="pulse-badge success">< 70%</span></td>
|
||
<td><span class="extra-small text-warning fw-bold">70–85%</span></td>
|
||
<td><span class="extra-small text-danger fw-bold">> 85% + swap</span></td>
|
||
<td class="text-muted">Restart PHP-FPM / inspect cache size / scale instance.</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">Disk Usage</td>
|
||
<td><span class="pulse-badge success">< 75%</span></td>
|
||
<td><span class="extra-small text-warning fw-bold">75–90%</span></td>
|
||
<td><span class="extra-small text-danger fw-bold">> 90%</span></td>
|
||
<td class="text-muted">Rotate logs, prune old backups, run Forensics tab to find heavy tables.</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">Failed Jobs</td>
|
||
<td><span class="pulse-badge success">0</span></td>
|
||
<td><span class="extra-small text-warning fw-bold">1–10</span></td>
|
||
<td><span class="extra-small text-danger fw-bold">> 10 or rising</span></td>
|
||
<td class="text-muted">Open Queues tab, inspect payload, retry or flush.</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">Security Score</td>
|
||
<td><span class="pulse-badge success">≥ 85</span></td>
|
||
<td><span class="extra-small text-warning fw-bold">60–84</span></td>
|
||
<td><span class="extra-small text-danger fw-bold">< 60</span></td>
|
||
<td class="text-muted">Re-audit in Security AI tab, apply recommendations.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{{-- SECURITY AI HOW IT WORKS --}}
|
||
<h6 class="fw-bold text-uppercase mb-3 ls-1">
|
||
<i class="bi bi-shield-fill-check text-danger me-1"></i>Security AI · How the audit works
|
||
</h6>
|
||
<div class="row g-3 mb-5">
|
||
<div class="col-md-6">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fff;border:1px solid #e2e8f0;">
|
||
<div class="fw-bold text-dark mb-2">What it inspects</div>
|
||
<ul class="extra-small text-muted mb-0" style="line-height:1.8;padding-left:1.2rem;">
|
||
<li>Debug mode flag in production</li>
|
||
<li>HTTPS / HSTS / security headers</li>
|
||
<li>Default / weak credentials in seeders</li>
|
||
<li>Rate-limiting on auth endpoints</li>
|
||
<li>CSRF token enforcement</li>
|
||
<li>Session driver & cookie flags</li>
|
||
<li>File permissions on <code>.env</code>, <code>storage/</code></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fff;border:1px solid #e2e8f0;">
|
||
<div class="fw-bold text-dark mb-2">How to use</div>
|
||
<ol class="extra-small text-muted mb-0" style="line-height:1.8;padding-left:1.2rem;">
|
||
<li>Click <span class="fw-bold text-danger">RE-AUDIT SYSTEM</span></li>
|
||
<li>AI scans config + runtime state</li>
|
||
<li>Returns a score 0–100 and a Markdown report</li>
|
||
<li>Apply each "Recommendation" row by row — typically copy/paste config changes</li>
|
||
<li>Re-audit to confirm the score improved</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- PERMISSIONS --}}
|
||
<h6 class="fw-bold text-uppercase mb-3 ls-1">
|
||
<i class="bi bi-person-badge text-secondary me-1"></i>Permissions
|
||
</h6>
|
||
<div class="row g-2 mb-5">
|
||
<div class="col-md-4">
|
||
<div class="p-3 rounded-4 h-100" style="background:#f0fdf4;border:1px solid #bbf7d0;">
|
||
<div class="fw-bold text-success mb-1"><code class="text-success">view health and logs</code></div>
|
||
<div class="extra-small text-muted">Required to <span class="fw-bold">open</span> the Monitoring Center and view standard telemetry (Logs, Mobile, SAP, Queues).</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fef2f2;border:1px solid #fecaca;">
|
||
<div class="fw-bold text-danger mb-1"><code class="text-danger">manage health and logs</code></div>
|
||
<div class="extra-small text-muted">Required to <span class="fw-bold">clear logs</span>, <span class="fw-bold">retry / flush jobs</span>, and <span class="fw-bold">toggle maintenance</span>.</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fff7ed;border:1px solid #ffedd5;">
|
||
<div class="fw-bold text-warning mb-1"><code class="text-warning">view ai log analysis</code></div>
|
||
<div class="extra-small text-muted">Exclusive to <span class="fw-bold text-dark">Developer</span> role. Required to access the <span class="fw-bold">Security AI Audit</span> tab and run AI-driven scans.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- HEALTH CHECKLIST (LIVE) --}}
|
||
<h6 class="fw-bold text-uppercase mb-3 ls-1">
|
||
<i class="bi bi-activity text-success me-1"></i>Live health snapshot
|
||
</h6>
|
||
<div class="p-3 rounded-4 mb-5" style="background:#0c121e;color:#e2e8f0;border:1px solid #1e293b;">
|
||
<div class="extra-small fw-bold text-success mb-2" style="letter-spacing:1px;font-family:'Fira Code',monospace;">$ monitor --self-check</div>
|
||
<pre class="mb-0" style="font-family:'Fira Code',monospace;font-size:.78rem;line-height:1.8;color:#cbd5e1;white-space:pre-wrap;">
|
||
<span style="color:#fbbf24;">[01]</span> Host: <span style="color:#22c55e;">{{ $stats['hostname'] }}</span>
|
||
<span style="color:#fbbf24;">[02]</span> Uptime: <span style="color:#22c55e;">{{ $stats['uptime'] }}</span>
|
||
<span style="color:#fbbf24;">[03]</span> CPU load: <span style="color:{{ $stats['cpu'] > 80 ? '#ef4444' : ($stats['cpu'] > 60 ? '#f59e0b' : '#22c55e') }};">{{ $stats['cpu'] }}%</span>
|
||
<span style="color:#fbbf24;">[04]</span> RAM usage: <span style="color:{{ $stats['ram']['percentage'] > 85 ? '#ef4444' : ($stats['ram']['percentage'] > 70 ? '#f59e0b' : '#22c55e') }};">{{ $stats['ram']['percentage'] }}%</span> ({{ $stats['ram']['used'] }})
|
||
<span style="color:#fbbf24;">[05]</span> Disk usage: <span style="color:{{ $stats['disk']['percentage'] > 90 ? '#ef4444' : ($stats['disk']['percentage'] > 75 ? '#f59e0b' : '#22c55e') }};">{{ $stats['disk']['percentage'] }}%</span> ({{ $stats['disk']['total'] }} available)
|
||
<span style="color:#fbbf24;">[06]</span> Redis: <span style="color:{{ ($stats['redis']['status'] ?? '') === 'connected' ? '#22c55e' : '#ef4444' }};">{{ ($stats['redis']['status'] ?? 'unknown') }}</span>
|
||
<span style="color:#fbbf24;">[07]</span> Reverb WS: <span style="color:{{ $stats['has_reverb'] ? '#22c55e' : '#94a3b8' }};">{{ $stats['has_reverb'] ? 'active' : 'idle' }}</span>
|
||
<span style="color:#fbbf24;">[08]</span> SAP RFC: <span style="color:{{ $stats['sap']['active'] ? '#22c55e' : '#94a3b8' }};">{{ $stats['sap']['status'] }}</span>
|
||
<span style="color:#fbbf24;">[09]</span> DB tables: <span style="color:#22c55e;">{{ $stats['db_stats']['tables'] }}</span>
|
||
<span style="color:#fbbf24;">[10]</span> Pending: <span style="color:{{ ($stats['queues']['pending'] ?? 0) > 0 ? '#f59e0b' : '#22c55e' }};">{{ $stats['queues']['pending'] ?? 0 }}</span> jobs
|
||
<span style="color:#fbbf24;">[11]</span> Failed: <span style="color:{{ ($stats['queues']['failed'] ?? 0) > 0 ? '#ef4444' : '#22c55e' }};">{{ $stats['queues']['failed'] ?? 0 }}</span> jobs
|
||
<span style="color:#94a3b8;"># This snapshot was rendered at page load. The dashboard above
|
||
# refreshes every 5 seconds via /api/system-stats.</span></pre>
|
||
</div>
|
||
|
||
{{-- TROUBLESHOOTING --}}
|
||
<h6 class="fw-bold text-uppercase mb-3 ls-1">
|
||
<i class="bi bi-wrench-adjustable-circle text-warning me-1"></i>Common scenarios & remedies
|
||
</h6>
|
||
<div class="table-responsive mb-5">
|
||
<table class="table table-hover align-middle small mb-0" style="background:#f8fafc;border-radius:12px;overflow:hidden;">
|
||
<thead style="background:#e2e8f0;">
|
||
<tr>
|
||
<th class="ps-3">SYMPTOM</th>
|
||
<th>FIRST CHECK</th>
|
||
<th>LIKELY FIX</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">Logs tab is empty</td>
|
||
<td class="text-muted">File permissions on <code>storage/logs/laravel.log</code></td>
|
||
<td class="text-muted">Run <code>chmod -R 775 storage/</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">Redis shows DISCONNECTED</td>
|
||
<td class="text-muted"><code>REDIS_HOST</code> / port in <code>.env</code></td>
|
||
<td class="text-muted">Restart redis container / verify <code>redis-cli ping</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">Many failed jobs</td>
|
||
<td class="text-muted">Open Queues tab, inspect any row</td>
|
||
<td class="text-muted">Retry one to surface the error, fix root cause, then Retry All</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">SAP RFC blank</td>
|
||
<td class="text-muted">Trace file <code>dev_rfc.trc</code> exists & readable</td>
|
||
<td class="text-muted">Restart SAP RFC bridge / enable trace level in SAP config</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">Security score dropped</td>
|
||
<td class="text-muted">Read the latest report carefully</td>
|
||
<td class="text-muted">Apply each recommendation, re-audit until back to green</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="ps-3 fw-bold text-dark">Disk > 90%</td>
|
||
<td class="text-muted">Forensics tab — heaviest tables</td>
|
||
<td class="text-muted">Truncate logs / archive old data / increase volume</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{{-- FOOTER NOTE --}}
|
||
<div class="d-flex align-items-center gap-3 p-3 rounded-4" style="background:#eff6ff;border:1px solid #bfdbfe;">
|
||
<i class="bi bi-info-circle-fill text-primary fs-3"></i>
|
||
<div>
|
||
<div class="fw-bold text-dark mb-1" style="font-size:.85rem;">Performance notes</div>
|
||
<div class="extra-small text-muted">
|
||
Polling runs at <code>5000ms</code>. To reduce server load on slower environments, increase <code>REFRESH_RATE</code> in this view's script.
|
||
The Security AI audit is on-demand only (never polled) because each run consumes AI tokens. Active tab is persisted in <code>localStorage</code> as <code>systemMonitoringActiveTab</code>.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
@endcantab
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TERMINAL MODAL: ANIMATED -->
|
||
<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')
|
||
<!-- ADD ANIMATE.CSS CDN -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
|
||
<script>
|
||
$(document).ready(function () {
|
||
// High-Performance Polling
|
||
const REFRESH_RATE = 5000;
|
||
|
||
// ===== TAB PERSISTENCE =====
|
||
(function () {
|
||
const STORAGE_KEY = 'systemMonitoringActiveTab';
|
||
|
||
// Save tab whenever user switches
|
||
document.querySelectorAll('[data-bs-toggle="tab"]').forEach(function (tabEl) {
|
||
tabEl.addEventListener('shown.bs.tab', function (e) {
|
||
localStorage.setItem(STORAGE_KEY, e.target.getAttribute('data-bs-target'));
|
||
});
|
||
});
|
||
|
||
// Restore saved tab on page load
|
||
const savedTab = localStorage.getItem(STORAGE_KEY);
|
||
if (savedTab) {
|
||
const tabEl = document.querySelector(`[data-bs-target="${savedTab}"]`);
|
||
if (tabEl) {
|
||
setTimeout(() => {
|
||
bootstrap.Tab.getOrCreateInstance(tabEl).show();
|
||
}, 50);
|
||
}
|
||
}
|
||
})();
|
||
|
||
// Logs Init
|
||
const logsTable = $('#logs-datatable').DataTable({
|
||
processing: true, serverSide: true, ajax: '{{ route("system-monitoring.logs.datatable") }}',
|
||
order: [[0, 'desc']], pageLength: 10, autoWidth: false, dom: 'tr<"d-flex justify-content-between p-3 border-top"ip>',
|
||
columns: [{ data: 0, className: 'ps-4 datetime-col' }, { 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();
|
||
});
|
||
}
|
||
});
|
||
|
||
// Jobs Init
|
||
const jobsTable = $('#background-jobs-datatable').DataTable({
|
||
processing: true, serverSide: true, ajax: '{{ route("system-monitoring.background-jobs.datatable") }}',
|
||
order: [[0, 'desc']], pageLength: 10, autoWidth: false, dom: 'tr<"d-flex justify-content-between p-3 border-top"ip>',
|
||
columns: [
|
||
{ data: 0, className: 'ps-4' },
|
||
{ data: 1 },
|
||
{ data: 2 },
|
||
{ data: 3 },
|
||
{ data: 4 }
|
||
@can('manage health and logs')
|
||
, { data: 5, className: 'pe-4 text-end', orderable: false }
|
||
@endcan
|
||
],
|
||
drawCallback: function () {
|
||
$('.retry-job-btn, .delete-failed-job-btn, .view-job-detail').off('click').on('click', function () {
|
||
const id = $(this).data('id');
|
||
if ($(this).hasClass('view-job-detail')) {
|
||
const j = $(this).data('job'); $('#detail-message').text(j.exception + "\n\n" + JSON.stringify(JSON.parse(j.payload), null, 2));
|
||
new bootstrap.Modal('#logDetailModal').show();
|
||
} else {
|
||
const url = $(this).hasClass('retry-job-btn') ? '{{ route("system-monitoring.background-jobs.retry", ["id" => ":id"]) }}' : '{{ route("system-monitoring.background-jobs.delete-failed", ["id" => ":id"]) }}';
|
||
$.post(url.replace(':id', id), { _token: '{{ csrf_token() }}' }, () => { jobsTable.ajax.reload(null, false); refreshStats(); });
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
function refreshStats() {
|
||
const btn = $('#refresh-all-stats');
|
||
btn.find('i').addClass('bi-spin');
|
||
|
||
$.get('{{ route("system-monitoring.stats") }}', function (d) {
|
||
// Update and Animate numbers if changed
|
||
// RAM & Swap
|
||
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 + '%');
|
||
|
||
// Refined Users
|
||
updateVal('#stat-users-count', d.users.total);
|
||
$('#stat-users-auth').text(d.users.authenticated);
|
||
|
||
// Dynamic DB
|
||
$('#stat-db-meta').text(d.db_stats.tables + ' Tables | ' + d.db_stats.latency);
|
||
$('#stat-db-status').text(d.db_stats.status)
|
||
.removeClass('text-success text-danger text-warning')
|
||
.addClass(d.db_stats.status === 'STABLE' ? 'text-success' : (d.db_stats.status === 'OFFLINE' ? 'text-danger' : 'text-warning'));
|
||
|
||
updateVal('#stat-queues-pending', d.queues.pending);
|
||
updateVal('#stat-queues-failed', d.queues.failed);
|
||
$('#stat-queues-load-text').text(d.queues.load_factor + ' tasks/min');
|
||
|
||
$('#stat-uptime-badge').text(d.uptime);
|
||
$('#cpu-bar').css('width', d.cpu + '%');
|
||
|
||
// SAP & Mobile
|
||
$('#sap-icon').toggleClass('active', d.sap.active);
|
||
$('#stat-sap-detail').text('Trace: ' + d.sap.size);
|
||
$('#stat-sap-status').text(d.sap.status).toggleClass('text-success', d.sap.active).toggleClass('text-muted', !d.sap.active);
|
||
$('#stat-mobile-detail').text(d.mobile.total_logs + ' Events recorded');
|
||
$('#stat-mobile-activity').text(d.mobile.last_activity);
|
||
|
||
// Forensics Update
|
||
$('#stat-db-size').text(d.db_stats.size);
|
||
let tableHtml = '';
|
||
d.db_stats.top_tables.forEach(t => {
|
||
tableHtml += `<div class="d-flex justify-content-between mb-2 small border-bottom pb-1">
|
||
<span class="text-truncate me-2">${t.table}</span>
|
||
<span class="fw-bold text-nowrap">${t.size_pretty}</span>
|
||
</div>`;
|
||
});
|
||
$('#top-tables-list').html(tableHtml);
|
||
|
||
// Socket Feedback
|
||
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');
|
||
}
|
||
|
||
// Silent Table Update
|
||
logsTable.ajax.reload(null, false);
|
||
jobsTable.ajax.reload(null, false);
|
||
sapTable.ajax.reload(null, false);
|
||
mobileTable.ajax.reload(null, false);
|
||
|
||
setTimeout(() => btn.find('i').removeClass('bi-spin'), 1000);
|
||
});
|
||
}
|
||
|
||
// SAP Init
|
||
const sapTable = $('#sap-logs-datatable').DataTable({
|
||
processing: true, serverSide: true, ajax: '{{ route("system-monitoring.sap-logs.datatable") }}',
|
||
order: [[0, 'desc']], pageLength: 10, autoWidth: false, dom: 'tr<"d-flex justify-content-between p-3 border-top"ip>',
|
||
columns: [{ data: 0, className: 'ps-4 datetime-col' }, { data: 1 }, { data: 2, className: 'pe-4 text-end', orderable: false }],
|
||
drawCallback: function () {
|
||
$('.view-sap-log-detail').off('click').on('click', function () {
|
||
$('#detail-message').text($(this).data('message'));
|
||
new bootstrap.Modal('#logDetailModal').show();
|
||
});
|
||
}
|
||
});
|
||
|
||
// Mobile Init
|
||
const mobileTable = $('#mobile-logs-datatable').DataTable({
|
||
processing: true, serverSide: true, ajax: '{{ route("system-monitoring.mobile-logs.datatable") }}',
|
||
order: [[0, 'desc']], pageLength: 10, autoWidth: false, dom: 'tr<"d-flex justify-content-between p-3 border-top"ip>',
|
||
columns: [{ data: 0, className: 'ps-4 datetime-col' }, { data: 1 }, { data: 2 }, { data: 3 }, { data: 4, 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 + (log.context ? "\n\nContext: " + JSON.stringify(log.context, null, 2) : ""));
|
||
new bootstrap.Modal('#logDetailModal').show();
|
||
});
|
||
}
|
||
});
|
||
|
||
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);
|
||
$('#retry-all-jobs-btn, #clear-failed-jobs-btn, #clear-logs-btn, #clear-sap-logs-btn, #clear-mobile-logs-btn').on('click', function () {
|
||
let url;
|
||
switch(this.id) {
|
||
case 'clear-logs-btn': url = '{{ route("system-monitoring.logs.clear") }}'; break;
|
||
case 'clear-sap-logs-btn': url = '{{ route("system-monitoring.sap-logs.clear") }}'; break;
|
||
case 'clear-mobile-logs-btn': url = '{{ route("system-monitoring.mobile-logs.clear") }}'; break;
|
||
case 'clear-failed-jobs-btn': url = '{{ route("system-monitoring.background-jobs.clear") }}'; break;
|
||
case 'retry-all-jobs-btn': url = '{{ route("system-monitoring.background-jobs.retry") }}'; break;
|
||
}
|
||
StandardSwal.fire({ title: 'Confirmation', text: 'Apply operation?', icon: 'info', showCancelButton: true }).then(r => { if (r.isConfirmed) $.post(url, { _token: '{{ csrf_token() }}' }, () => { refreshStats(); }); });
|
||
});
|
||
|
||
function runSecurityAudit(refresh = false) {
|
||
const report = $('#ai-security-report');
|
||
const score = $('#ai-security-score');
|
||
const label = $('#security-score-label');
|
||
const time = $('#ai-security-time');
|
||
|
||
if (refresh) {
|
||
report.html('<div class="placeholder-glow"><span class="placeholder col-7"></span><span class="placeholder col-4"></span><span class="placeholder col-4"></span><span class="placeholder col-6"></span></div>');
|
||
score.text('--');
|
||
}
|
||
|
||
$.get('{{ route("ai.security-audit") }}', { refresh: refresh ? 1 : 0 }, function(d) {
|
||
if (d.error) {
|
||
report.html('<div class="alert alert-danger">'+d.error+'</div>');
|
||
return;
|
||
}
|
||
|
||
// Parse Score
|
||
const s = d.score;
|
||
score.text(s);
|
||
|
||
if (s >= 90) { label.text('EXCELLENT').addClass('text-bg-success').removeClass('text-bg-warning text-bg-danger'); }
|
||
else if (s >= 70) { label.text('MODERATE').addClass('text-bg-warning').removeClass('text-bg-success text-bg-danger'); }
|
||
else { label.text('CRITICAL').addClass('text-bg-danger').removeClass('text-bg-success text-bg-warning'); }
|
||
|
||
time.text(d.timestamp);
|
||
|
||
// Render Markdown (using marked if available, else simple replace)
|
||
if (window.marked) {
|
||
report.html(marked.parse(d.analysis));
|
||
} else {
|
||
report.html(d.analysis.replace(/\n/g, '<br>'));
|
||
}
|
||
|
||
// Transform Risk Levels into Badges
|
||
report.find('td').each(function() {
|
||
const text = $(this).text().trim().toUpperCase();
|
||
if (text === 'CRITICAL') $(this).html('<span class="badge rounded-pill text-bg-danger px-3 py-1">CRITICAL</span>');
|
||
else if (text === 'HIGH') $(this).html('<span class="badge rounded-pill text-bg-warning px-3 py-1 text-dark">HIGH</span>');
|
||
else if (text === 'MEDIUM') $(this).html('<span class="badge rounded-pill text-bg-info px-3 py-1 text-dark">MEDIUM</span>');
|
||
else if (text === 'LOW') $(this).html('<span class="badge rounded-pill text-bg-success px-3 py-1">LOW</span>');
|
||
});
|
||
});
|
||
}
|
||
|
||
// Manual trigger only (removed auto-trigger on tab show)
|
||
|
||
$('#refresh-security-audit').on('click', function() {
|
||
$(this).find('i').addClass('bi-spin');
|
||
runSecurityAudit(true);
|
||
setTimeout(() => $(this).find('i').removeClass('bi-spin'), 1500);
|
||
});
|
||
|
||
setInterval(refreshStats, REFRESH_RATE);
|
||
});
|
||
|
||
function copyLogDetail() {
|
||
const el = document.getElementById('detail-message');
|
||
const range = document.createRange(); range.selectNode(el);
|
||
window.getSelection().removeAllRanges(); window.getSelection().addRange(range);
|
||
document.execCommand('copy'); window.getSelection().removeAllRanges();
|
||
toastr?.success('Content copied to clipboard');
|
||
}
|
||
</script>
|
||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" crossorigin="anonymous"></script>
|
||
<style>
|
||
/* THE COOL FACTOR CSS */
|
||
.security-score-gauge {
|
||
width: 150px;
|
||
height: 150px;
|
||
border-radius: 50%;
|
||
background: conic-gradient(var(--adminuiux-theme-1) 0%, #f1f5f9 0%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
border: 10px solid #f8fafc;
|
||
box-shadow: inset 0 0 15px rgba(0,0,0,0.05);
|
||
}
|
||
.score-inner {
|
||
width: 110px;
|
||
height: 110px;
|
||
background: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 10px 20px rgba(0,0,0,0.05);
|
||
}
|
||
.fw-black {
|
||
font-weight: 900;
|
||
}
|
||
|
||
.ls-1 {
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.tracking-tight {
|
||
letter-spacing: -2px;
|
||
}
|
||
|
||
/* Typography Scale */
|
||
.display-3 {
|
||
font-size: 3.5rem;
|
||
font-weight: 900;
|
||
letter-spacing: -2px;
|
||
}
|
||
|
||
.display-1 {
|
||
font-size: 4.5rem;
|
||
fw-black;
|
||
letter-spacing: -3px;
|
||
line-height: 0.8;
|
||
}
|
||
|
||
.extra-small {
|
||
font-size: 0.85rem;
|
||
letter-spacing: 0.2px;
|
||
}
|
||
|
||
.hover-lift {
|
||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||
}
|
||
|
||
.hover-lift:hover {
|
||
transform: translateY(-10px);
|
||
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1) !important;
|
||
}
|
||
|
||
.bg-dark {
|
||
background: #1a1a1a !important;
|
||
}
|
||
|
||
.bg-decoration {
|
||
position: absolute;
|
||
right: -50px;
|
||
bottom: -50px;
|
||
width: 200px;
|
||
height: 200px;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border-radius: 50%;
|
||
z-index: 0;
|
||
}
|
||
|
||
.pulse-badge {
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
font-size: 0.7rem;
|
||
font-weight: 800;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.pulse-badge::before {
|
||
content: '';
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
display: inline-block;
|
||
animation: pulse-glow 1.5s infinite;
|
||
}
|
||
|
||
.pulse-badge.success {
|
||
background: #dcfce7;
|
||
color: #15803d;
|
||
}
|
||
|
||
.pulse-badge.success::before {
|
||
background: #22c55e;
|
||
box-shadow: 0 0 8px #22c55e;
|
||
}
|
||
|
||
@keyframes pulse-glow {
|
||
0% {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
}
|
||
|
||
50% {
|
||
transform: scale(1.5);
|
||
opacity: 0.5;
|
||
}
|
||
|
||
100% {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.icon-box {
|
||
width: 40px;
|
||
height: 40px;
|
||
background: #f1f5f9;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #64748b;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.icon-box.active {
|
||
background: #dcfce7;
|
||
color: #22c55e;
|
||
animation: float 2s infinite ease-in-out;
|
||
}
|
||
|
||
@keyframes float {
|
||
|
||
0%,
|
||
100% {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
50% {
|
||
transform: translateY(-5px);
|
||
}
|
||
}
|
||
|
||
.mini-progress {
|
||
height: 4px;
|
||
background: #f1f5f9;
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.mini-progress .bar {
|
||
height: 100%;
|
||
background: var(--adminuiux-theme-1);
|
||
transition: width 1s;
|
||
}
|
||
|
||
.card-glow-warning:hover {
|
||
box-shadow: 0 0 20px rgba(245, 158, 11, 0.2) !important;
|
||
}
|
||
|
||
.card-glow-theme:hover {
|
||
box-shadow: 0 0 20px rgba(var(--adminuiux-theme-1-rgb), 0.3) !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;
|
||
}
|
||
|
||
.bi-spin {
|
||
animation: spin 1s infinite linear;
|
||
display: inline-block;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% {
|
||
transform: rotate(0deg);
|
||
}
|
||
|
||
100% {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.custom-console-tabs .nav-link {
|
||
color: #94a3b8;
|
||
border-bottom-width: 3px !important;
|
||
}
|
||
|
||
.custom-console-tabs .nav-link.active {
|
||
color: var(--adminuiux-theme-1);
|
||
}
|
||
|
||
.metric-card {
|
||
padding: 15px;
|
||
background: #f8fafc;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
/* Custom Scrollbar */
|
||
.scroll-custom::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
.scroll-custom::-webkit-scrollbar-track {
|
||
background: #f1f5f9;
|
||
}
|
||
.scroll-custom::-webkit-scrollbar-thumb {
|
||
background: #cbd5e1;
|
||
border-radius: 10px;
|
||
}
|
||
.scroll-custom::-webkit-scrollbar-thumb:hover {
|
||
background: #94a3b8;
|
||
}
|
||
|
||
/* AI Security Report Typography */
|
||
#ai-security-report {
|
||
color: #1e293b !important;
|
||
line-height: 1.7;
|
||
}
|
||
#ai-security-report h1, #ai-security-report h2, #ai-security-report h3,
|
||
#ai-security-report h4, #ai-security-report h5, #ai-security-report h6 {
|
||
color: #0f172a !important;
|
||
font-weight: 800;
|
||
margin-top: 1.5rem;
|
||
margin-bottom: 1rem;
|
||
border-bottom: none;
|
||
}
|
||
#ai-security-report p, #ai-security-report li {
|
||
color: #334155 !important;
|
||
font-size: 0.95rem;
|
||
margin-bottom: 0.8rem;
|
||
}
|
||
#ai-security-report strong {
|
||
color: #0f172a !important;
|
||
font-weight: 700;
|
||
}
|
||
#ai-security-report code {
|
||
background: #f1f5f9;
|
||
color: #e11d48;
|
||
padding: 0.2rem 0.4rem;
|
||
border-radius: 4px;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
/* AI Security Report Table Overrides */
|
||
#ai-security-report table {
|
||
width: 100% !important;
|
||
display: table !important;
|
||
border-collapse: separate !important;
|
||
border-spacing: 0 !important;
|
||
margin-top: 1.5rem !important;
|
||
margin-bottom: 2rem !important;
|
||
border: 1px solid #e2e8f0 !important;
|
||
border-radius: 12px !important;
|
||
overflow: hidden !important;
|
||
background-color: #ffffff !important;
|
||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05) !important;
|
||
}
|
||
|
||
#ai-security-report th {
|
||
background-color: #f8fafc !important;
|
||
color: #0f172a !important;
|
||
font-weight: 800 !important;
|
||
text-transform: uppercase !important;
|
||
font-size: 0.75rem !important;
|
||
letter-spacing: 0.1em !important;
|
||
padding: 14px 18px !important;
|
||
border-bottom: 2px solid #e2e8f0 !important;
|
||
text-align: left !important;
|
||
}
|
||
|
||
#ai-security-report td {
|
||
padding: 14px 18px !important;
|
||
color: #1e293b !important;
|
||
font-size: 0.9rem !important;
|
||
border-bottom: 1px solid #f1f5f9 !important;
|
||
background-color: #ffffff !important;
|
||
line-height: 1.5 !important;
|
||
vertical-align: top !important;
|
||
}
|
||
|
||
#ai-security-report tr:hover td {
|
||
background-color: #fcfdfe !important;
|
||
}
|
||
|
||
#ai-security-report tr:last-child td {
|
||
border-bottom: none !important;
|
||
}
|
||
|
||
/* Fix for potential dark background inheritance */
|
||
.markdown-body table, .markdown-body tr, .markdown-body td, .markdown-body th {
|
||
background-color: transparent !important;
|
||
border-color: #e2e8f0 !important;
|
||
color: inherit !important;
|
||
}
|
||
|
||
/* AI Security Report Code Block Styling */
|
||
#ai-security-report pre {
|
||
background-color: #0f172a !important;
|
||
color: #f8fafc !important;
|
||
padding: 1.25rem !important;
|
||
border-radius: 12px !important;
|
||
margin-top: 1rem !important;
|
||
margin-bottom: 1.5rem !important;
|
||
border: 1px solid #1e293b !important;
|
||
font-family: 'Fira Code', 'JetBrains Mono', 'Courier New', monospace !important;
|
||
font-size: 0.85rem !important;
|
||
line-height: 1.6 !important;
|
||
overflow-x: auto !important;
|
||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important;
|
||
position: relative !important;
|
||
}
|
||
|
||
#ai-security-report pre::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 4px;
|
||
background: linear-gradient(90deg, #3b82f6, #8b5cf6, #ec4899);
|
||
border-top-left-radius: 12px;
|
||
border-top-right-radius: 12px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
#ai-security-report pre code {
|
||
background-color: transparent !important;
|
||
color: #e2e8f0 !important;
|
||
padding: 0 !important;
|
||
border-radius: 0 !important;
|
||
font-size: inherit !important;
|
||
}
|
||
|
||
/* Simple Syntax Highlighting (pseudo-classes for AI content) */
|
||
#ai-security-report .comment { color: #64748b !important; font-style: italic; }
|
||
#ai-security-report .keyword { color: #f472b6 !important; }
|
||
#ai-security-report .string { color: #34d399 !important; }
|
||
</style>
|
||
@endpush
|
||
</x-app-layout> |