1610 lines
111 KiB
PHP
1610 lines
111 KiB
PHP
<x-app-layout>
|
||
@push('styles')
|
||
<script src="https://cdn.jsdelivr.net/npm/apexcharts" crossorigin="anonymous"></script>
|
||
<style>
|
||
/* Typography scale — matches system-monitoring */
|
||
.fw-black { font-weight: 900; }
|
||
.display-3 {
|
||
font-size: 3.5rem;
|
||
font-weight: 900;
|
||
letter-spacing: -2px;
|
||
line-height: 1;
|
||
}
|
||
.display-1 {
|
||
font-size: 4.5rem;
|
||
font-weight: 900;
|
||
letter-spacing: -3px;
|
||
line-height: 0.8;
|
||
}
|
||
.tracking-tight { letter-spacing: -2px; }
|
||
.extra-small { font-size: 0.85rem; letter-spacing: 0.2px; }
|
||
.ls-1 { letter-spacing: 1px; }
|
||
.hover-lift { transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
|
||
.hover-lift:hover { transform: translateY(-3px); box-shadow: 0 12px 24px rgba(0,0,0,0.06) !important; }
|
||
|
||
/* Decorative orbs on dark header */
|
||
.bg-decoration::before, .bg-decoration::after {
|
||
content: '';
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
background: rgba(255,255,255,0.03);
|
||
z-index: 0;
|
||
}
|
||
.bg-decoration::before { top: -60px; right: -40px; width: 220px; height: 220px; }
|
||
.bg-decoration::after { bottom: -80px; left: 30%; width: 160px; height: 160px; background: rgba(255,255,255,0.02); }
|
||
|
||
/* Pulse Badge */
|
||
.pulse-badge {
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
font-size: 0.7rem;
|
||
font-weight: 800;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.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; }
|
||
.pulse-badge.danger { background:#fee2e2; color:#b91c1c; }
|
||
.pulse-badge.danger::before { background:#ef4444; box-shadow:0 0 8px #ef4444; }
|
||
.pulse-badge.warning { background:#fef3c7; color:#a16207; }
|
||
.pulse-badge.warning::before { background:#f59e0b; box-shadow:0 0 8px #f59e0b; }
|
||
@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 (capability list) */
|
||
.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;
|
||
flex-shrink: 0;
|
||
}
|
||
.icon-box.active { background:#dcfce7; color:#22c55e; }
|
||
.icon-box.disabled { background:#fee2e2; color:#b91c1c; }
|
||
.icon-box.warn { background:#fef3c7; color:#a16207; }
|
||
|
||
/* Mini progress (matches monitoring) */
|
||
.mini-progress { height:4px; background:#f1f5f9; border-radius:2px; overflow:hidden; }
|
||
.mini-progress .bar { height:100%; background: var(--adminuiux-theme-1, #4338ca); transition: width 1s; }
|
||
.mini-progress .bar.success { background:#22c55e; }
|
||
.mini-progress .bar.danger { background:#ef4444; }
|
||
.mini-progress .bar.warn { background:#f59e0b; }
|
||
|
||
/* Console Tabs */
|
||
.custom-console-tabs .nav-link {
|
||
color:#94a3b8;
|
||
border:none;
|
||
border-bottom: 3px solid transparent !important;
|
||
}
|
||
.custom-console-tabs .nav-link.active {
|
||
color: var(--adminuiux-theme-1, #4338ca);
|
||
background: transparent;
|
||
border-bottom-color: var(--adminuiux-theme-1, #4338ca) !important;
|
||
}
|
||
.action-bar { letter-spacing: 0.8px; }
|
||
|
||
/* Counter animation */
|
||
.counter-value.bump { animation: bump 0.5s ease; }
|
||
@keyframes bump {
|
||
0% { transform: scale(1); }
|
||
30% { transform: scale(1.12); color:#0d6efd; }
|
||
100% { transform: scale(1); }
|
||
}
|
||
|
||
/* Terminal Modal */
|
||
.terminal-box {
|
||
background:#0c121e !important;
|
||
border:1px solid #1e293b !important;
|
||
color:#10b981 !important;
|
||
}
|
||
.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; }
|
||
.terminal-box pre, .terminal-box code { font-family:'Fira Code','JetBrains Mono',monospace; }
|
||
|
||
/* Top exceptions list */
|
||
.exception-row {
|
||
display:flex; align-items:center; justify-content:space-between;
|
||
padding: 0.85rem 1rem;
|
||
border-radius: 12px;
|
||
background:#f8fafc; border:1px solid #e2e8f0;
|
||
margin-bottom: 0.5rem;
|
||
transition: all 0.2s;
|
||
}
|
||
.exception-row:hover { background:#f1f5f9; transform: translateX(2px); }
|
||
.exception-rank {
|
||
width: 28px; height: 28px;
|
||
border-radius: 50%;
|
||
background: #fff;
|
||
border: 2px solid #e2e8f0;
|
||
display:flex; align-items:center; justify-content:center;
|
||
font-size: 0.72rem; font-weight: 800;
|
||
color: #64748b;
|
||
flex-shrink: 0;
|
||
}
|
||
.exception-row:nth-child(1) .exception-rank { border-color:#ef4444; color:#ef4444; }
|
||
.exception-row:nth-child(2) .exception-rank { border-color:#f59e0b; color:#a16207; }
|
||
.exception-row:nth-child(3) .exception-rank { border-color:#3b82f6; color:#1d4ed8; }
|
||
|
||
/* Inline switch */
|
||
.config-toggle-row {
|
||
display:flex; align-items:center; justify-content:space-between;
|
||
padding: 1rem 1.25rem;
|
||
border-bottom: 1px solid #f1f5f9;
|
||
}
|
||
.config-toggle-row:last-child { border-bottom: none; }
|
||
.config-toggle-row:hover { background:#f8fafc; }
|
||
|
||
/* Risk pill */
|
||
.risk-pill {
|
||
font-size:.6rem; letter-spacing:.5px; font-weight:800;
|
||
padding:2px 8px; border-radius: 20px;
|
||
}
|
||
.risk-pill.low { background:#dcfce7; color:#15803d; }
|
||
.risk-pill.med { background:#fef3c7; color:#a16207; }
|
||
.risk-pill.high { background:#fee2e2; color:#b91c1c; }
|
||
|
||
/* Sparkline container */
|
||
.sparkline-container {
|
||
position:absolute; bottom:0; left:0; right:0;
|
||
height: 50px; overflow:hidden;
|
||
border-bottom-left-radius: 16px;
|
||
border-bottom-right-radius: 16px;
|
||
opacity: 0.55;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.bi-spin { animation: spin 1s infinite linear; display: inline-block; }
|
||
@keyframes spin { from { transform: rotate(0deg);} to { transform: rotate(360deg);} }
|
||
|
||
.scroll-custom::-webkit-scrollbar { width:6px; }
|
||
.scroll-custom::-webkit-scrollbar-track { background:#f1f5f9; }
|
||
.scroll-custom::-webkit-scrollbar-thumb { background:#cbd5e1; border-radius:10px; }
|
||
|
||
/* Diff Viewer */
|
||
.diff-container {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1px;
|
||
background: #1e293b;
|
||
border: 1px solid #1e293b;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
.diff-panel {
|
||
background: #0f172a;
|
||
padding: 1rem;
|
||
overflow-x: auto;
|
||
}
|
||
.diff-panel-header {
|
||
font-size: 0.65rem;
|
||
font-weight: 900;
|
||
color: #64748b;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
margin-bottom: 0.5rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.diff-content {
|
||
font-family: 'Fira Code', monospace;
|
||
font-size: 0.72rem;
|
||
line-height: 1.5;
|
||
white-space: pre;
|
||
color: #94a3b8;
|
||
}
|
||
.diff-added { background: rgba(16, 185, 129, 0.15); color: #10b981 !important; display: block; width: 100%; }
|
||
.diff-removed { background: rgba(239, 68, 68, 0.15); color: #ef4444 !important; display: block; width: 100%; }
|
||
</style>
|
||
@endpush
|
||
|
||
<div class="container-fluid pb-4" id="ai-healing-master">
|
||
|
||
{{-- HEADER STRIP --}}
|
||
<div class="card adminuiux-card bg-dark text-white mb-3 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 g-3">
|
||
<div class="col">
|
||
<h1 class="display-5 fw-bold text-white mb-1 tracking-tight">
|
||
<i class="bi bi-robot text-theme-1 me-2"></i>{{ __('AI Self-Healing Engine') }}
|
||
</h1>
|
||
<p class="small text-white-50 mb-0 d-flex align-items-center gap-2 flex-wrap">
|
||
@php $engineEnabled = (bool) ($settings['ai_healing_enabled'] ?? false)
|
||
&& (bool) ($providerInfo['enabled'] ?? false)
|
||
&& (bool) ($providerInfo['has_key'] ?? false); @endphp
|
||
<span id="engine-pulse" class="pulse-badge {{ $engineEnabled ? 'success' : 'danger' }}">
|
||
{{ $engineEnabled ? 'ENGINE ACTIVE' : 'ENGINE DISABLED' }}
|
||
</span>
|
||
<span class="opacity-75">·</span>
|
||
<span>Provider: <span class="badge bg-white bg-opacity-10 text-white rounded-pill px-2" id="provider-pill">{{ $providerInfo['provider'] }}</span></span>
|
||
<span class="opacity-75">·</span>
|
||
<span>Last incident: <span class="text-warning fw-semibold" id="last-incident-relative">{{ $lastIncident ? $lastIncident->diffForHumans() : '—' }}</span></span>
|
||
</p>
|
||
</div>
|
||
<div class="col-auto d-flex align-items-center gap-2">
|
||
<button class="btn btn-theme-1 btn-square rounded-circle shadow-sm" id="btn-refresh-all" title="Refresh live data">
|
||
<i class="bi bi-arrow-clockwise" id="refresh-icon"></i>
|
||
</button>
|
||
@can('manage ai self-healing')
|
||
<button class="btn btn-outline-light btn-sm rounded-pill px-3 fw-bold" id="btn-simulate" style="font-size:.72rem;letter-spacing:.5px;">
|
||
<i class="bi bi-bug me-1"></i>SIMULATE
|
||
</button>
|
||
@endcan
|
||
</div>
|
||
</div>
|
||
<div class="bg-decoration"></div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ROW 1: CAPABILITIES (LEFT) | VITAL NUMBERS (MIDDLE) | CRITICAL (RIGHT) --}}
|
||
<div class="row g-3 mb-3">
|
||
|
||
{{-- LEFT: ENGINE CAPABILITIES --}}
|
||
<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">{{ __('Engine Capabilities') }}</h6>
|
||
<span class="pulse-badge {{ $engineEnabled ? 'success' : 'danger' }}" id="capabilities-pill">
|
||
{{ $engineEnabled ? 'OPERATIONAL' : 'STANDBY' }}
|
||
</span>
|
||
</div>
|
||
<div class="card-body px-0 pt-2">
|
||
<div class="list-group list-group-flush">
|
||
|
||
{{-- AI Provider Link --}}
|
||
<div class="list-group-item border-0 py-3 px-4 d-flex align-items-center">
|
||
<div class="icon-box me-3 {{ $providerInfo['enabled'] && $providerInfo['has_key'] ? 'active' : 'disabled' }}">
|
||
<i class="bi bi-cpu fs-5"></i>
|
||
</div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">AI Provider Link</div>
|
||
<div class="extra-small text-muted">
|
||
{{ $providerInfo['provider'] }}
|
||
@if(!$providerInfo['has_key'])
|
||
<span class="text-danger fw-bold">· KEY MISSING</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
<span class="fw-bold extra-small {{ $providerInfo['enabled'] && $providerInfo['has_key'] ? 'text-success' : 'text-danger' }}">
|
||
{{ $providerInfo['enabled'] && $providerInfo['has_key'] ? 'LINKED' : 'OFFLINE' }}
|
||
</span>
|
||
</div>
|
||
|
||
{{-- Exception Interceptor --}}
|
||
<div class="list-group-item border-0 py-3 px-4 d-flex align-items-center">
|
||
<div class="icon-box me-3 {{ $engineEnabled ? 'active' : 'disabled' }}" id="cap-interceptor-icon">
|
||
<i class="bi bi-shield-fill-check fs-5"></i>
|
||
</div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">Exception Interceptor</div>
|
||
<div class="extra-small text-muted">Captures runtime exceptions for AI analysis</div>
|
||
</div>
|
||
<span class="fw-bold extra-small {{ $engineEnabled ? 'text-success' : 'text-danger' }}" id="cap-interceptor-text">
|
||
{{ $engineEnabled ? 'LISTENING' : 'PAUSED' }}
|
||
</span>
|
||
</div>
|
||
|
||
{{-- Cache Clearing --}}
|
||
<div class="list-group-item border-0 py-3 px-4 d-flex align-items-center">
|
||
<div class="icon-box me-3 {{ ($settings['ai_healing_allow_cache'] ?? false) ? 'active' : '' }}" id="cap-cache-icon">
|
||
<i class="bi bi-layers fs-5"></i>
|
||
</div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">Cache Clearing</div>
|
||
<div class="extra-small text-muted">Flush config / route / view cache</div>
|
||
</div>
|
||
<span class="fw-bold extra-small {{ ($settings['ai_healing_allow_cache'] ?? false) ? 'text-success' : 'text-muted' }}" id="cap-cache-text">
|
||
{{ ($settings['ai_healing_allow_cache'] ?? false) ? 'ALLOWED' : 'BLOCKED' }}
|
||
</span>
|
||
</div>
|
||
|
||
{{-- Queue Restart --}}
|
||
<div class="list-group-item border-0 py-3 px-4 d-flex align-items-center">
|
||
<div class="icon-box me-3 {{ ($settings['ai_healing_allow_queue'] ?? false) ? 'active' : '' }}" id="cap-queue-icon">
|
||
<i class="bi bi-arrow-repeat fs-5"></i>
|
||
</div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">Queue Restart</div>
|
||
<div class="extra-small text-muted">Restart failed Horizon / queue workers</div>
|
||
</div>
|
||
<span class="fw-bold extra-small {{ ($settings['ai_healing_allow_queue'] ?? false) ? 'text-success' : 'text-muted' }}" id="cap-queue-text">
|
||
{{ ($settings['ai_healing_allow_queue'] ?? false) ? 'ALLOWED' : 'BLOCKED' }}
|
||
</span>
|
||
</div>
|
||
|
||
{{-- Maintenance --}}
|
||
<div class="list-group-item border-0 py-3 px-4 d-flex align-items-center">
|
||
<div class="icon-box me-3 {{ ($settings['ai_healing_allow_maintenance'] ?? false) ? 'warn' : '' }}" id="cap-maintenance-icon">
|
||
<i class="bi bi-cone-striped fs-5"></i>
|
||
</div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark">Maintenance Toggle</div>
|
||
<div class="extra-small text-muted">Auto-enable maintenance during critical fixes</div>
|
||
</div>
|
||
<span class="fw-bold extra-small {{ ($settings['ai_healing_allow_maintenance'] ?? false) ? 'text-warning' : 'text-muted' }}" id="cap-maintenance-text">
|
||
{{ ($settings['ai_healing_allow_maintenance'] ?? false) ? 'ALLOWED' : 'BLOCKED' }}
|
||
</span>
|
||
</div>
|
||
|
||
{{-- DB Migration --}}
|
||
<div class="list-group-item border-0 py-3 px-4 d-flex align-items-center">
|
||
<div class="icon-box me-3 {{ ($settings['ai_healing_allow_db'] ?? false) ? 'disabled' : '' }}" id="cap-db-icon">
|
||
<i class="bi bi-database-gear fs-5"></i>
|
||
</div>
|
||
<div class="flex-grow-1 text-truncate">
|
||
<div class="small fw-bold text-dark d-flex align-items-center gap-2">
|
||
Database Migration
|
||
<span class="risk-pill high">HIGH RISK</span>
|
||
</div>
|
||
<div class="extra-small text-muted">Allow AI to run schema migrations</div>
|
||
</div>
|
||
<span class="fw-bold extra-small {{ ($settings['ai_healing_allow_db'] ?? false) ? 'text-danger' : 'text-muted' }}" id="cap-db-text">
|
||
{{ ($settings['ai_healing_allow_db'] ?? false) ? 'ALLOWED' : 'BLOCKED' }}
|
||
</span>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- MIDDLE: VITAL NUMBERS --}}
|
||
<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 position-relative overflow-hidden">
|
||
<div class="card-body p-4">
|
||
<div class="d-flex justify-content-between mb-2">
|
||
<h6 class="fw-bold text-dark small mb-0">TOTAL INTERCEPTS</h6>
|
||
<i class="bi bi-shield-check text-theme-1"></i>
|
||
</div>
|
||
<h1 class="display-3 fw-black text-theme-1 mb-0 counter-value" id="stat-total">{{ $stats['total'] }}</h1>
|
||
<p class="extra-small text-muted mb-0">
|
||
<span class="fw-bold text-dark" id="stat-24h">{{ $stats['last_24h'] }}</span> in last 24h
|
||
</p>
|
||
</div>
|
||
<div id="spark-total" class="sparkline-container"></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">AUTO-RESOLVED</h6>
|
||
<i class="bi bi-check-circle text-success"></i>
|
||
</div>
|
||
<h1 class="display-3 fw-black text-success mb-0 counter-value" id="stat-resolved">{{ $stats['resolved'] }}</h1>
|
||
<div class="mini-progress mt-3">
|
||
<div class="bar success" id="bar-resolved" style="width: {{ $stats['rate'] }}%"></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">FAILED</h6>
|
||
<i class="bi bi-x-octagon text-danger"></i>
|
||
</div>
|
||
<h1 class="display-3 fw-black text-danger mb-0 counter-value" id="stat-failed">{{ $stats['failed'] }}</h1>
|
||
<p class="extra-small text-muted mb-0">Requires manual review</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">
|
||
<div class="card-body p-4">
|
||
<div class="d-flex justify-content-between mb-2">
|
||
<h6 class="fw-bold text-white small mb-0">RESOLUTION RATE</h6>
|
||
<i class="bi bi-graph-up-arrow text-white-50"></i>
|
||
</div>
|
||
<h1 class="display-3 fw-black text-white mb-0 counter-value" id="stat-rate">{{ $stats['rate'] }}<small style="font-size:.5em;">%</small></h1>
|
||
<p class="extra-small text-white-50 mb-0">Healing success ratio</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
{{-- RIGHT: CRITICAL INDICATORS --}}
|
||
<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 ANALYSIS</h6>
|
||
<h1 class="display-1 fw-black text-theme-1 counter-value" id="stat-pending">{{ $stats['pending'] }}</h1>
|
||
<i class="bi bi-hourglass-split 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 small text-danger">CRITICAL</h6>
|
||
<h1 class="display-1 fw-black text-danger counter-value" id="stat-critical">{{ $stats['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 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="aiConsoleTabs" role="tablist">
|
||
<li class="nav-item">
|
||
<button class="nav-link active py-3 px-4 small fw-bold" data-bs-toggle="tab" data-bs-target="#tab-feed">
|
||
<i class="bi bi-terminal me-2"></i>Activity Feed
|
||
</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-analytics">
|
||
<i class="bi bi-graph-up me-2"></i>Analytics
|
||
</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-config">
|
||
<i class="bi bi-sliders me-2"></i>Configuration
|
||
</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-forensics">
|
||
<i class="bi bi-search me-2"></i>Forensics
|
||
</button>
|
||
</li>
|
||
<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>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="card-body p-0">
|
||
<div class="tab-content">
|
||
|
||
{{-- TAB: ACTIVITY FEED --}}
|
||
<div class="tab-pane fade show active" id="tab-feed">
|
||
<div class="action-bar p-3 bg-light border-bottom d-flex justify-content-between align-items-center px-4 flex-wrap gap-2">
|
||
<span class="small fw-bold text-secondary text-uppercase">Runtime Activity</span>
|
||
<div class="d-flex gap-2 flex-wrap">
|
||
<span class="badge bg-success-subtle text-success rounded-pill fw-bold filter-chip" data-filter="all" style="cursor:pointer;font-size:.7rem;padding:.4rem .9rem;">ALL · <span id="chip-all">{{ $stats['total'] }}</span></span>
|
||
<span class="badge bg-light text-dark rounded-pill fw-bold filter-chip" data-filter="resolved" style="cursor:pointer;font-size:.7rem;padding:.4rem .9rem;">RESOLVED · <span id="chip-resolved">{{ $stats['resolved'] }}</span></span>
|
||
<span class="badge bg-light text-dark rounded-pill fw-bold filter-chip" data-filter="failed" style="cursor:pointer;font-size:.7rem;padding:.4rem .9rem;">FAILED · <span id="chip-failed">{{ $stats['failed'] }}</span></span>
|
||
<span class="badge bg-light text-dark rounded-pill fw-bold filter-chip" data-filter="pending" style="cursor:pointer;font-size:.7rem;padding:.4rem .9rem;">PENDING · <span id="chip-pending">{{ $stats['pending'] }}</span></span>
|
||
@can('manage ai self-healing')
|
||
<button id="btn-clear-logs" class="btn btn-sm btn-outline-danger rounded-pill px-3">
|
||
<i class="bi bi-trash me-1"></i>Purge
|
||
</button>
|
||
@endcan
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table id="ai-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>STATUS</th>
|
||
<th>EXCEPTION MANIFEST</th>
|
||
<th>AI DIAGNOSIS</th>
|
||
<th class="text-end pe-4">INTEL</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@if(count($logs) > 0)
|
||
@foreach($logs as $log)
|
||
<tr data-status="{{ $log->status }}">
|
||
<td class="ps-4 fw-semibold text-muted" data-sort="{{ $log->created_at->timestamp }}" style="white-space:nowrap;">
|
||
{{ $log->created_at->format('d M, H:i:s') }}
|
||
</td>
|
||
<td>
|
||
@if($log->status === 'resolved')
|
||
<span class="pulse-badge success">RESOLVED</span>
|
||
@elseif($log->status === 'failed')
|
||
<span class="pulse-badge danger">FAILED</span>
|
||
@else
|
||
<span class="pulse-badge warning">{{ strtoupper($log->status) }}</span>
|
||
@endif
|
||
</td>
|
||
<td>
|
||
<div class="fw-bold text-danger font-monospace" style="font-size:.75rem;">{{ Str::limit(class_basename($log->error_type), 38) }}</div>
|
||
<div class="extra-small text-muted">{{ Str::limit($log->error_message, 56) }}</div>
|
||
</td>
|
||
<td class="font-monospace extra-small text-dark">{{ Str::limit($log->ai_diagnosis ?? '-', 60) }}</td>
|
||
<td class="text-end pe-4">
|
||
<div class="btn-group" role="group">
|
||
<button class="btn btn-sm btn-light rounded-circle btn-inspect me-1" data-id="{{ $log->id }}" title="Inspect" style="width:32px;height:32px;padding:0;">
|
||
<i class="bi bi-terminal" style="font-size:.78rem;"></i>
|
||
</button>
|
||
@can('manage ai self-healing')
|
||
@if(in_array($log->status, ['failed', 'diagnosing', 'pending']))
|
||
<button class="btn btn-sm btn-light rounded-circle btn-retry" data-id="{{ $log->id }}" title="Re-run AI healer" style="width:32px;height:32px;padding:0;">
|
||
<i class="bi bi-arrow-repeat text-primary" style="font-size:.85rem;"></i>
|
||
</button>
|
||
@endif
|
||
@endcan
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
@endif
|
||
|
||
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- TAB: ANALYTICS --}}
|
||
<div class="tab-pane fade" id="tab-analytics">
|
||
<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">Healing Analytics · Last 24 Hours</span>
|
||
<span class="extra-small text-muted">Hourly bucket</span>
|
||
</div>
|
||
<div class="p-4">
|
||
<div class="row g-4">
|
||
<div class="col-lg-8">
|
||
<h6 class="fw-bold mb-3 small text-uppercase" style="letter-spacing:.5px;">Intercept Volume</h6>
|
||
<div id="chart-timeline" style="min-height: 280px;"></div>
|
||
</div>
|
||
<div class="col-lg-4">
|
||
<h6 class="fw-bold mb-3 small text-uppercase" style="letter-spacing:.5px;">Top Exception Types</h6>
|
||
<div id="top-exceptions-list" class="scroll-custom" style="max-height: 340px; overflow-y: auto;">
|
||
@forelse($topExceptions as $i => $exc)
|
||
<div class="exception-row" title="{{ $exc['fqcn'] }}">
|
||
<div class="d-flex align-items-center gap-2 text-truncate">
|
||
<div class="exception-rank">#{{ $i + 1 }}</div>
|
||
<div class="text-truncate">
|
||
<div class="fw-bold text-dark text-truncate font-monospace" style="font-size:.78rem;">{{ $exc['type'] }}</div>
|
||
<div class="extra-small text-muted">{{ $exc['count'] }} occurrence(s)</div>
|
||
</div>
|
||
</div>
|
||
<span class="badge bg-danger-subtle text-danger rounded-pill fw-bold ms-2">{{ $exc['count'] }}</span>
|
||
</div>
|
||
@empty
|
||
<div class="text-center text-muted py-5 opacity-50">
|
||
<i class="bi bi-bar-chart display-4 d-block mb-3"></i>
|
||
<p class="fw-bold mb-1">No data yet</p>
|
||
<p class="extra-small">Top exceptions will appear here.</p>
|
||
</div>
|
||
@endforelse
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- TAB: CONFIGURATION --}}
|
||
<div class="tab-pane fade" id="tab-config">
|
||
<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">Engine Configuration</span>
|
||
<a href="{{ route('system-config') }}" class="extra-small text-decoration-none">
|
||
<i class="bi bi-box-arrow-up-right me-1"></i>Open Global Settings
|
||
</a>
|
||
</div>
|
||
<form id="healing-config-form" class="p-0">
|
||
@csrf
|
||
|
||
<div class="config-toggle-row">
|
||
<div class="d-flex align-items-start gap-3">
|
||
<div class="icon-box {{ $engineEnabled ? 'active' : 'disabled' }}"><i class="bi bi-robot fs-5"></i></div>
|
||
<div>
|
||
<div class="fw-bold text-dark">AI Healing Engine <span class="risk-pill {{ $engineEnabled ? 'low' : 'med' }} ms-1">{{ $engineEnabled ? 'ACTIVE' : 'INACTIVE' }}</span></div>
|
||
<div class="extra-small text-muted">Master switch · controls whether the interceptor pipeline is active at all.</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="cfg_healing_enabled" name="ai_healing_enabled" value="1" @checked($engineEnabled) @disabled(auth()->user()->cannot('manage ai self-healing'))>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-toggle-row">
|
||
<div class="d-flex align-items-start gap-3">
|
||
<div class="icon-box {{ ($settings['ai_healing_allow_cache'] ?? false) ? 'active' : '' }}"><i class="bi bi-layers fs-5"></i></div>
|
||
<div>
|
||
<div class="fw-bold text-dark">Cache Clearing <span class="risk-pill low ms-1">SAFE</span></div>
|
||
<div class="extra-small text-muted">Permits the AI to flush application caches (config / route / view / app).</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="cfg_allow_cache" name="ai_healing_allow_cache" value="1" @checked($settings['ai_healing_allow_cache'] ?? false) @disabled(auth()->user()->cannot('manage ai self-healing'))>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-toggle-row">
|
||
<div class="d-flex align-items-start gap-3">
|
||
<div class="icon-box {{ ($settings['ai_healing_allow_queue'] ?? false) ? 'active' : '' }}"><i class="bi bi-arrow-repeat fs-5"></i></div>
|
||
<div>
|
||
<div class="fw-bold text-dark">Queue Restart <span class="risk-pill low ms-1">SAFE</span></div>
|
||
<div class="extra-small text-muted">Permits the AI to restart Horizon / queue workers when stalled.</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="cfg_allow_queue" name="ai_healing_allow_queue" value="1" @checked($settings['ai_healing_allow_queue'] ?? false) @disabled(auth()->user()->cannot('manage ai self-healing'))>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-toggle-row">
|
||
<div class="d-flex align-items-start gap-3">
|
||
<div class="icon-box {{ ($settings['ai_healing_allow_maintenance'] ?? false) ? 'warn' : '' }}"><i class="bi bi-cone-striped fs-5"></i></div>
|
||
<div>
|
||
<div class="fw-bold text-dark">Maintenance Toggle <span class="risk-pill med ms-1">MEDIUM</span></div>
|
||
<div class="extra-small text-muted">Permits the AI to enable maintenance mode during critical incidents.</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="cfg_allow_maintenance" name="ai_healing_allow_maintenance" value="1" @checked($settings['ai_healing_allow_maintenance'] ?? false) @disabled(auth()->user()->cannot('manage ai self-healing'))>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-toggle-row">
|
||
<div class="d-flex align-items-start gap-3">
|
||
<div class="icon-box {{ ($settings['ai_healing_allow_db'] ?? false) ? 'disabled' : '' }}"><i class="bi bi-database-gear fs-5"></i></div>
|
||
<div>
|
||
<div class="fw-bold text-dark">Database Migration <span class="risk-pill high ms-1">HIGH RISK</span></div>
|
||
<div class="extra-small text-muted">Permits the AI to run schema migrations. Recommended OFF in production.</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="cfg_allow_db" name="ai_healing_allow_db" value="1" @checked($settings['ai_healing_allow_db'] ?? false) @disabled(auth()->user()->cannot('manage ai self-healing'))>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="config-toggle-row px-4 py-4 bg-light bg-opacity-50">
|
||
<div class="row align-items-center w-100">
|
||
<div class="col">
|
||
<div class="fw-bold text-dark">Circuit Breaker Threshold</div>
|
||
<div class="extra-small text-muted">Maximum autonomous repair attempts allowed per hour to prevent infinite loops.</div>
|
||
</div>
|
||
<div class="col-auto">
|
||
<div class="input-group input-group-sm" style="width: 140px;">
|
||
<span class="input-group-text bg-white border-end-0"><i class="bi bi-clock-history"></i></span>
|
||
<input type="number" class="form-control border-start-0 fw-bold" name="ai_healing_max_attempts_per_hour" value="{{ $settings['ai_healing_max_attempts_per_hour'] ?? 5 }}" min="1" max="100" @disabled(auth()->user()->cannot('manage ai self-healing'))>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
@can('manage ai self-healing')
|
||
<div class="p-4 bg-light border-top d-flex justify-content-end gap-2">
|
||
<button type="submit" class="btn btn-theme-1 rounded-pill px-4 fw-bold" id="save-config-btn">
|
||
<i class="bi bi-cloud-arrow-up me-1"></i> Save Configuration
|
||
</button>
|
||
</div>
|
||
@endcan
|
||
</form>
|
||
</div>
|
||
|
||
{{-- TAB: FORENSICS --}}
|
||
<div class="tab-pane fade p-4" id="tab-forensics">
|
||
<div class="row g-4">
|
||
<div class="col-md-6">
|
||
<h6 class="fw-bold mb-3 small text-uppercase" style="letter-spacing:.5px;">Incident Distribution</h6>
|
||
<div id="chart-distribution" style="min-height: 260px;"></div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<h6 class="fw-bold mb-3 small text-uppercase" style="letter-spacing:.5px;">Engine Telemetry</h6>
|
||
<div class="row g-2">
|
||
<div class="col-6">
|
||
<div class="p-3 rounded-3" style="background:#f8fafc;border:1px solid #e2e8f0;">
|
||
<div class="extra-small text-muted fw-bold">Last Incident</div>
|
||
<div class="fw-black text-dark" id="forensic-last">{{ $lastIncident ? $lastIncident->diffForHumans() : 'No incidents' }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6">
|
||
<div class="p-3 rounded-3" style="background:#f8fafc;border:1px solid #e2e8f0;">
|
||
<div class="extra-small text-muted fw-bold">Records (24h)</div>
|
||
<div class="fw-black text-theme-1" id="forensic-24h">{{ $stats['last_24h'] }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6">
|
||
<div class="p-3 rounded-3" style="background:#f8fafc;border:1px solid #e2e8f0;">
|
||
<div class="extra-small text-muted fw-bold">All-Time Total</div>
|
||
<div class="fw-black text-dark" id="forensic-total">{{ $stats['total'] }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6">
|
||
<div class="p-3 rounded-3" style="background:#f8fafc;border:1px solid #e2e8f0;">
|
||
<div class="extra-small text-muted fw-bold">Success Rate</div>
|
||
<div class="fw-black text-success" id="forensic-rate">{{ $stats['rate'] }}%</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-12 mt-2">
|
||
<div class="p-3 rounded-3" style="background:#0c121e;color:#10b981;border:1px solid #1e293b;">
|
||
<div class="extra-small fw-bold opacity-50 mb-1">$ engine.status</div>
|
||
<code class="d-block" style="font-family:'Fira Code',monospace;font-size:.78rem;">
|
||
provider={{ strtolower($providerInfo['provider']) }} · enabled={{ $engineEnabled ? 'true' : 'false' }} · keyed={{ $providerInfo['has_key'] ? 'true' : 'false' }}
|
||
</code>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- TAB: DOCUMENTATION --}}
|
||
<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">Engine Documentation & Reference</span>
|
||
<span class="extra-small text-muted">v1.1 · Autonomous mode · Tier-based decisions</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">How the AI Self-Healing Engine works</h3>
|
||
<p class="text-muted mb-0" style="font-size:.92rem;line-height:1.7;">
|
||
When an exception is thrown anywhere in the application, the engine intercepts it, reads
|
||
<span class="fw-bold text-dark">25 lines of code</span> around the fault, and asks the AI to
|
||
<span class="fw-bold text-dark">classify the bug into one of three tiers</span> with a confidence score.
|
||
Tier 1 & 2 errors are <span class="fw-bold text-success">auto-fixed immediately — no confirmation required</span>.
|
||
Tier 3 errors (truly ambiguous) are logged for human review. Every code edit writes a
|
||
<code class="text-danger">.bak.YYYYMMDDhhmmss</code> backup, and one click rolls it back.
|
||
</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">PIPELINE</div>
|
||
<div class="fw-bold mb-3" style="font-size:.95rem;">Exception → Tier → Auto-Fix</div>
|
||
<div class="d-flex flex-column gap-2 extra-small">
|
||
<div><i class="bi bi-1-circle me-2"></i>Capture & classify exception</div>
|
||
<div><i class="bi bi-2-circle me-2"></i>Resolve fault file from trace</div>
|
||
<div><i class="bi bi-3-circle me-2"></i>AI tier & confidence scoring</div>
|
||
<div><i class="bi bi-4-circle me-2"></i>Gate against toggles</div>
|
||
<div><i class="bi bi-5-circle me-2"></i>Backup → apply → rollback option</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- 3-TIER POLICY (NEW) --}}
|
||
<h6 class="fw-bold text-uppercase mb-3" style="letter-spacing:.5px;">
|
||
<i class="bi bi-bullseye text-danger me-1"></i>Three-tier auto-fix policy
|
||
</h6>
|
||
<div class="row g-3 mb-5">
|
||
<div class="col-md-4">
|
||
<div class="p-4 rounded-4 h-100" style="background:#f0fdf4;border:2px solid #86efac;">
|
||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||
<span class="badge rounded-pill px-3 py-2 fw-bold" style="background:#22c55e;color:#fff;font-size:.7rem;letter-spacing:.5px;">TIER 1</span>
|
||
<span class="extra-small fw-bold text-success">conf ≥ 0.80</span>
|
||
</div>
|
||
<div class="fw-bold text-dark mb-1">AUTO-FIX MANDATORY</div>
|
||
<p class="extra-small text-muted mb-2" style="line-height:1.6;">AI <span class="fw-bold">must</span> return a remedy. Applied immediately without human confirmation.</p>
|
||
<div class="extra-small">
|
||
<span class="fw-bold text-dark">Examples:</span>
|
||
<ul class="mb-0 mt-1 ps-3 text-muted" style="line-height:1.7;">
|
||
<li>Typo (variable, method, route, class)</li>
|
||
<li>Missing <code>use</code> import</li>
|
||
<li>Unescaped <code>@@can</code> / <code>@@if</code> in Blade text</li>
|
||
<li>Unmatched directive pairs</li>
|
||
<li>Stale cache / view / route</li>
|
||
<li>Failed worker after deploy</li>
|
||
<li>Wrong helper API (<code>::set</code> vs <code>::put</code>)</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="p-4 rounded-4 h-100" style="background:#fffbeb;border:2px solid #fcd34d;">
|
||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||
<span class="badge rounded-pill px-3 py-2 fw-bold" style="background:#f59e0b;color:#fff;font-size:.7rem;letter-spacing:.5px;">TIER 2</span>
|
||
<span class="extra-small fw-bold" style="color:#a16207;">conf 0.50–0.79</span>
|
||
</div>
|
||
<div class="fw-bold text-dark mb-1">BEST-EFFORT FIX</div>
|
||
<p class="extra-small text-muted mb-2" style="line-height:1.6;">AI attempts the most likely fix when it can identify a concrete change.</p>
|
||
<div class="extra-small">
|
||
<span class="fw-bold text-dark">Examples:</span>
|
||
<ul class="mb-0 mt-1 ps-3 text-muted" style="line-height:1.7;">
|
||
<li>Type mismatch → cast (<code>(int) $id</code>)</li>
|
||
<li>SQL not portable across drivers (e.g. Postgres lacks <code>DATE_FORMAT</code>) → use Carbon in PHP</li>
|
||
<li>Missing model attribute with safe fallback</li>
|
||
<li>Argument count mismatch with obvious signature</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="p-4 rounded-4 h-100" style="background:#fef2f2;border:2px solid #fca5a5;">
|
||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||
<span class="badge rounded-pill px-3 py-2 fw-bold" style="background:#ef4444;color:#fff;font-size:.7rem;letter-spacing:.5px;">TIER 3</span>
|
||
<span class="extra-small fw-bold text-danger">conf < 0.50</span>
|
||
</div>
|
||
<div class="fw-bold text-dark mb-1">DO NOT AUTO-FIX</div>
|
||
<p class="extra-small text-muted mb-2" style="line-height:1.6;">AI returns <code>null</code> with a precise diagnosis. A human must review.</p>
|
||
<div class="extra-small">
|
||
<span class="fw-bold text-dark">Examples:</span>
|
||
<ul class="mb-0 mt-1 ps-3 text-muted" style="line-height:1.7;">
|
||
<li>Business logic / data semantics</li>
|
||
<li>Multi-file refactor</li>
|
||
<li>Race conditions / concurrency</li>
|
||
<li>Anything in <code>vendor/</code></li>
|
||
<li>Major block re-creation</li>
|
||
<li>Healer's own pipeline errors</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- CONFIDENCE TAG EXPLAINER --}}
|
||
<h6 class="fw-bold text-uppercase mb-3" style="letter-spacing:.5px;">
|
||
<i class="bi bi-graph-up text-info me-1"></i>Reading the confidence tag
|
||
</h6>
|
||
<div class="row g-3 mb-5">
|
||
<div class="col-md-7">
|
||
<div class="p-4 rounded-4 h-100" style="background:#0f172a;color:#cbd5e1;">
|
||
<div class="extra-small fw-bold text-success mb-2" style="letter-spacing:1px;font-family:'Fira Code',monospace;">AI_DIAGNOSIS</div>
|
||
<pre class="mb-0" style="font-family:'Fira Code',monospace;font-size:.78rem;line-height:1.7;white-space:pre-wrap;">
|
||
<span style="color:#22c55e;">[conf=0.92]</span><span style="color:#f59e0b;">[tier=1]</span> Found unescaped @@can directive in
|
||
documentation text at line 1140. Replaced with @@can to make
|
||
Blade output it as literal HTML.</pre>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-5">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fff;border:1px solid #e2e8f0;">
|
||
<div class="fw-bold text-dark mb-2" style="font-size:.88rem;">Every diagnosis is prefixed with:</div>
|
||
<ul class="extra-small text-muted mb-0 ps-3" style="line-height:1.8;">
|
||
<li><code class="text-success">[conf=0.00–1.00]</code> — how confident the AI was in this fix</li>
|
||
<li><code class="text-warning">[tier=1|2|3]</code> — which decision tier was applied</li>
|
||
<li>Then a ≤ 2-sentence plain-language explanation of <span class="fw-bold">what was wrong AND what was changed</span></li>
|
||
</ul>
|
||
<p class="extra-small text-muted mt-2 mb-0">
|
||
Visible in the terminal <span class="badge bg-dark text-white">INCIDENT_DUMP</span> modal under the <code>AI_DIAGNOSIS</code> section.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ROLLBACK FEATURE (NEW) --}}
|
||
<h6 class="fw-bold text-uppercase mb-3" style="letter-spacing:.5px;">
|
||
<i class="bi bi-arrow-counterclockwise text-warning me-1"></i>One-click rollback for code edits
|
||
</h6>
|
||
<div class="p-4 rounded-4 mb-5" style="background:#fffbeb;border:1px solid #fde68a;">
|
||
<div class="row align-items-center g-3">
|
||
<div class="col-md-1 text-center">
|
||
<i class="bi bi-arrow-counterclockwise display-4 text-warning"></i>
|
||
</div>
|
||
<div class="col-md-11">
|
||
<div class="fw-bold text-dark mb-2">Every code edit is reversible.</div>
|
||
<p class="extra-small text-muted mb-2" style="line-height:1.7;">
|
||
Before the AI overwrites a file, the original is copied to <code>filename.bak.YYYYMMDDhhmmss</code> in the same directory.
|
||
Open the <span class="fw-bold">INCIDENT_DUMP</span> modal of any resolved code-edit incident and you'll see a
|
||
<span class="badge bg-warning text-dark fw-bold">↻ ROLLBACK</span> button in the modal header.
|
||
</p>
|
||
<p class="extra-small text-muted mb-0" style="line-height:1.7;">
|
||
Clicking it restores the file from the backup, runs <code>optimize:clear</code>, and tags the log with
|
||
<code>[rolled back by <user>]</code> in the diagnosis. The rollback button only appears when the backup file
|
||
still exists on disk — if you delete <code>.bak</code> files manually, rollback becomes unavailable.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- WORKFLOW STEPS --}}
|
||
<h6 class="fw-bold text-uppercase mb-3" style="letter-spacing:.5px;">Lifecycle of an incident</h6>
|
||
<div class="row g-3 mb-5">
|
||
@php
|
||
$steps = [
|
||
['icon'=>'bi-bug', 'color'=>'#ef4444', 'title'=>'1. Intercept', 'desc'=>'Any Throwable (except HttpException & healer-internal errors) is caught at the framework level. A 5-min stuck-state reset frees any orphaned <em>diagnosing</em> logs.'],
|
||
['icon'=>'bi-clipboard-data', 'color'=>'#f59e0b', 'title'=>'2. Pending', 'desc'=>'A row is written to <code>ai_healing_logs</code> with status <span class="fw-bold">pending</span>. Duplicate exceptions within 2 minutes are skipped.'],
|
||
['icon'=>'bi-cpu', 'color'=>'#3b82f6', 'title'=>'3. Diagnosing', 'desc'=>'The healer job runs <span class="fw-bold">synchronously</span>, reads ±25 lines of context, builds a tiered prompt with your enabled commands, and calls the AI.'],
|
||
['icon'=>'bi-bullseye', 'color'=>'#8b5cf6', 'title'=>'4. Tier & gate', 'desc'=>'AI returns <code>{confidence, tier, code_edit?, action_command?}</code>. Tier 1/2 → proceed. Tier 3 → bail with diagnosis. Commands must pass capability-toggle gate.'],
|
||
['icon'=>'bi-floppy', 'color'=>'#06b6d4', 'title'=>'5. Backup', 'desc'=>'Before any code edit, the original file is copied to <code>.bak.YYYYMMDDhhmmss</code> in the same directory. This is what makes rollback possible.'],
|
||
['icon'=>'bi-check2-circle', 'color'=>'#22c55e', 'title'=>'6. Resolved', 'desc'=>'Edit applied or Artisan command executed. <code>optimize:clear</code> runs automatically after code edits. Diagnosis is tagged with <code>[conf=][tier=]</code>.'],
|
||
['icon'=>'bi-arrow-counterclockwise','color'=>'#f59e0b','title'=>'7. Rollback (optional)','desc'=>'Anytime later, open the incident modal → click <span class="fw-bold">↻ ROLLBACK</span>. File is restored from <code>.bak</code>, caches cleared, log tagged <code>[rolled back by <user>]</code>.'],
|
||
['icon'=>'bi-x-octagon', 'color'=>'#dc2626', 'title'=>'Failed (alt)', 'desc'=>'If invalid JSON, target not found, command refused, or AI marked it Tier 3 — status becomes <span class="fw-bold">failed</span> with a precise reason. Click <i class="bi bi-arrow-repeat"></i> to retry.'],
|
||
];
|
||
@endphp
|
||
@foreach($steps as $s)
|
||
<div class="col-md-6 col-lg-4">
|
||
<div class="p-3 rounded-4 h-100" style="background:#f8fafc;border:1px solid #e2e8f0;transition:all .2s;">
|
||
<div class="d-flex align-items-center gap-2 mb-2">
|
||
<div class="icon-box" style="background:{{ $s['color'] }}1a;color:{{ $s['color'] }};width:36px;height:36px;">
|
||
<i class="{{ $s['icon'] }}"></i>
|
||
</div>
|
||
<div class="fw-bold text-dark">{{ $s['title'] }}</div>
|
||
</div>
|
||
<div class="extra-small text-muted" style="line-height:1.6;">{!! $s['desc'] !!}</div>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
|
||
</div>
|
||
|
||
{{-- SOLVABLE GRID --}}
|
||
<h6 class="fw-bold text-uppercase mb-3" style="letter-spacing:.5px;">
|
||
<i class="bi bi-check-circle-fill text-success me-1"></i>What the engine can resolve
|
||
</h6>
|
||
<div class="row g-3 mb-5">
|
||
<div class="col-lg-6">
|
||
<div class="p-3 rounded-4 h-100" style="background:#f0fdf4;border:1px solid #bbf7d0;">
|
||
<div class="fw-bold text-success mb-2 d-flex align-items-center gap-2">
|
||
<i class="bi bi-terminal-fill"></i> Artisan commands
|
||
<span class="risk-pill low ms-1">GATED BY TOGGLES</span>
|
||
</div>
|
||
<div class="extra-small text-muted mb-3">Each command requires the matching capability toggle to be ON.</div>
|
||
<div class="d-flex flex-column gap-1 small">
|
||
<div><code class="text-dark">cache:clear</code> <span class="text-muted">— stale class / model cache</span></div>
|
||
<div><code class="text-dark">view:clear</code> <span class="text-muted">— deleted / renamed blade</span></div>
|
||
<div><code class="text-dark">config:clear</code> <span class="text-muted">— .env not reflected</span></div>
|
||
<div><code class="text-dark">route:clear</code> <span class="text-muted">— route not found after edit</span></div>
|
||
<div><code class="text-dark">optimize:clear</code> <span class="text-muted">— catch-all (most common)</span></div>
|
||
<div><code class="text-dark">queue:restart</code> <span class="text-muted">— Horizon workers stale</span></div>
|
||
<div><code class="text-dark">down</code> / <code class="text-dark">up</code> <span class="text-muted">— maintenance gate</span></div>
|
||
<div><code class="text-dark">migrate</code> <span class="text-muted">— pending schema change (HIGH RISK)</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-lg-6">
|
||
<div class="p-3 rounded-4 h-100" style="background:#eff6ff;border:1px solid #bfdbfe;">
|
||
<div class="fw-bold text-primary mb-2 d-flex align-items-center gap-2">
|
||
<i class="bi bi-code-square"></i> Code edits
|
||
|
||
<span class="risk-pill med ms-1">.BAK BACKUP WRITTEN</span>
|
||
</div>
|
||
<div class="extra-small text-muted mb-3">AI must return an EXACT string match that exists in the file.</div>
|
||
<div class="d-flex flex-column gap-1 small">
|
||
<div><i class="bi bi-dot text-primary"></i>Typos in variables, methods, class names</div>
|
||
<div><i class="bi bi-dot text-primary"></i>Missing or wrong <code>use</code> imports</div>
|
||
<div><i class="bi bi-dot text-primary"></i>Wrong route name in helpers</div>
|
||
<div><i class="bi bi-dot text-primary"></i>Undefined array keys → safe defaults</div>
|
||
<div><i class="bi bi-dot text-primary"></i>Null dereference → null-safe (<code>?-></code>)</div>
|
||
<div><i class="bi bi-dot text-primary"></i>Wrong helper API (e.g. <code>Cache::set</code> → <code>::put</code>)</div>
|
||
<div><i class="bi bi-dot text-primary"></i>Mistyped argument types / casts</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- NOT SOLVABLE --}}
|
||
<h6 class="fw-bold text-uppercase mb-3" style="letter-spacing:.5px;">
|
||
<i class="bi bi-x-circle-fill text-danger me-1"></i>What the engine WON'T solve
|
||
</h6>
|
||
<div class="row g-2 mb-5">
|
||
@php
|
||
$blockers = [
|
||
['Business logic bugs', 'AI cannot infer intent (e.g. 10% VAT vs 11%).'],
|
||
['Race conditions / concurrency', 'Not deducible from a single stack trace.'],
|
||
['Performance issues (N+1, slow SQL)', 'Not thrown as exceptions — skipped by the interceptor.'],
|
||
['HTTP 4xx / 5xx responses', 'HttpException is intentionally ignored.'],
|
||
['Errors inside <code>vendor/</code>', 'Healer refuses to touch third-party code.'],
|
||
['Multi-file refactors', 'Only one file / one string replacement per incident.'],
|
||
['<code>migrate:fresh</code>', 'Always refused, even when DB toggle is ON (data loss).'],
|
||
['Errors from the healer itself', 'Trace-based filter prevents infinite loops.'],
|
||
];
|
||
@endphp
|
||
@foreach($blockers as $b)
|
||
<div class="col-md-6">
|
||
<div class="d-flex align-items-start gap-2 p-2 rounded-3" style="background:#fef2f2;border:1px solid #fecaca;">
|
||
<i class="bi bi-slash-circle text-danger mt-1"></i>
|
||
<div class="extra-small">
|
||
<div class="fw-bold text-dark">{!! $b[0] !!}</div>
|
||
<div class="text-muted">{!! $b[1] !!}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
|
||
</div>
|
||
|
||
{{-- REAL-WORLD EXAMPLES --}}
|
||
<h6 class="fw-bold text-uppercase mb-3" style="letter-spacing:.5px;">
|
||
<i class="bi bi-lightbulb-fill text-warning me-1"></i>Real-world examples
|
||
</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">EXCEPTION</th>
|
||
<th style="width:120px;">RESOLVABLE</th>
|
||
<th>HOW</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@php
|
||
$examples = [
|
||
['View [dashboard] not found', 'yes', '<code>view:clear</code> if Cache toggle ON'],
|
||
['Class "App\\Foo\\Bar" not found', 'yes', '<code>optimize:clear</code> (stale autoload)'],
|
||
['Undefined property: stdClass::$nama in blade', 'yes', 'Code edit: <code>$user->nama</code> → <code>$user?->nama ?? \'—\'</code>'],
|
||
['Route [admin.users.editt] not defined', 'yes', 'Code edit: typo <code>editt</code> → <code>edit</code>'],
|
||
['QueryException: function date_format() does not exist (Postgres)', 'yes', 'Code edit: bucket in PHP with <code>Carbon</code> instead of SQL'],
|
||
['Horizon worker not picking up new code', 'yes', '<code>queue:restart</code> if Queue toggle ON'],
|
||
['TypeError: Argument #1 ($id) must be int, string given', 'yes', 'Code edit: <code>(int) $id</code> cast'],
|
||
['User balance goes negative due to race condition', 'no', 'Needs <code>DB::transaction + lockForUpdate</code> — out of scope'],
|
||
['Endpoint returns 401 but should return 200', 'no', 'HTTP responses are intentionally not intercepted'],
|
||
['Slow query causing timeout', 'no', 'Not thrown as exception'],
|
||
];
|
||
@endphp
|
||
@foreach($examples as $ex)
|
||
<tr>
|
||
<td class="ps-3 font-monospace text-danger" style="font-size:.78rem;">{{ $ex[0] }}</td>
|
||
<td>
|
||
@if($ex[1] === 'yes')
|
||
<span class="pulse-badge success">YES</span>
|
||
@else
|
||
<span class="pulse-badge danger">NO</span>
|
||
@endif
|
||
</td>
|
||
<td class="text-muted">{!! $ex[2] !!}</td>
|
||
</tr>
|
||
@endforeach
|
||
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{{-- STATUS DICTIONARY --}}
|
||
<h6 class="fw-bold text-uppercase mb-3" style="letter-spacing:.5px;">
|
||
<i class="bi bi-bookmark-fill text-info me-1"></i>Status dictionary
|
||
</h6>
|
||
<div class="row g-3 mb-5">
|
||
<div class="col-md-3">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fffbeb;border:1px solid #fde68a;">
|
||
<span class="pulse-badge warning mb-2">PENDING</span>
|
||
<div class="extra-small text-muted mt-2">Queued for the healer. Has not been picked up yet.</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="p-3 rounded-4 h-100" style="background:#eff6ff;border:1px solid #bfdbfe;">
|
||
<span class="pulse-badge" style="background:#dbeafe;color:#1d4ed8;">DIAGNOSING</span>
|
||
<div class="extra-small text-muted mt-2">AI is analyzing. Auto-resets to pending after 5 minutes if stuck.</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="p-3 rounded-4 h-100" style="background:#f0fdf4;border:1px solid #bbf7d0;">
|
||
<span class="pulse-badge success mb-2">RESOLVED</span>
|
||
<div class="extra-small text-muted mt-2">Action executed successfully. Reason is stored in <code>action_taken</code>.</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="p-3 rounded-4 h-100" style="background:#fef2f2;border:1px solid #fecaca;">
|
||
<span class="pulse-badge danger mb-2">FAILED</span>
|
||
<div class="extra-small text-muted mt-2">Refused, invalid, or AI couldn't help. Click <i class="bi bi-arrow-repeat"></i> to retry.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- TROUBLESHOOTING --}}
|
||
<h6 class="fw-bold text-uppercase mb-3" style="letter-spacing:.5px;">
|
||
<i class="bi bi-wrench-adjustable-circle text-secondary me-1"></i>Troubleshooting checklist
|
||
</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;">$ healer --diagnose-self</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;">[1]</span> Provider link is <span class="fw-bold {{ $providerInfo['enabled'] && $providerInfo['has_key'] ? '' : '' }}" style="color:{{ $providerInfo['enabled'] && $providerInfo['has_key'] ? '#22c55e' : '#ef4444' }};">{{ $providerInfo['enabled'] && $providerInfo['has_key'] ? 'LINKED' : 'OFFLINE — set API key in Global Settings → AI Config' }}</span>
|
||
<span style="color:#fbbf24;">[2]</span> Healing engine is <span style="color:{{ $engineEnabled ? '#22c55e' : '#ef4444' }};">{{ $engineEnabled ? 'ENABLED' : 'DISABLED — toggle on Configuration tab' }}</span>
|
||
<span style="color:#fbbf24;">[3]</span> Cache toggle: <span style="color:{{ ($settings['ai_healing_allow_cache'] ?? false) ? '#22c55e' : '#94a3b8' }};">{{ ($settings['ai_healing_allow_cache'] ?? false) ? 'on' : 'off' }}</span>
|
||
<span style="color:#fbbf24;">[4]</span> Queue toggle: <span style="color:{{ ($settings['ai_healing_allow_queue'] ?? false) ? '#22c55e' : '#94a3b8' }};">{{ ($settings['ai_healing_allow_queue'] ?? false) ? 'on' : 'off' }}</span>
|
||
<span style="color:#fbbf24;">[5]</span> Maintenance toggle: <span style="color:{{ ($settings['ai_healing_allow_maintenance'] ?? false) ? '#f59e0b' : '#94a3b8' }};">{{ ($settings['ai_healing_allow_maintenance'] ?? false) ? 'on' : 'off' }}</span>
|
||
<span style="color:#fbbf24;">[6]</span> DB migration toggle: <span style="color:{{ ($settings['ai_healing_allow_db'] ?? false) ? '#ef4444' : '#94a3b8' }};">{{ ($settings['ai_healing_allow_db'] ?? false) ? 'on (HIGH RISK)' : 'off' }}</span>
|
||
<span style="color:#94a3b8;"># DEBUGGING A SPECIFIC INCIDENT:
|
||
# 1. Click row in Activity Feed → opens terminal modal
|
||
# 2. Read [conf=][tier=] tag — tells you why AI chose this path
|
||
# 3. If FAILED, action_taken explains the exact refusal reason
|
||
# 4. If RESOLVED but fix is wrong, click ↻ ROLLBACK in modal header
|
||
# 5. To re-run AI on a failed/stuck log, click ↻ RE-RUN AI</span></pre>
|
||
</div>
|
||
|
||
{{-- FOOTER NOTE --}}
|
||
<div class="d-flex align-items-center gap-3 p-3 rounded-4" style="background:#fef3c7;border:1px solid #fde68a;">
|
||
<i class="bi bi-shield-exclamation text-warning fs-3"></i>
|
||
<div>
|
||
<div class="fw-bold text-dark mb-1" style="font-size:.85rem;">Security posture under autonomous mode</div>
|
||
<div class="extra-small text-muted">
|
||
Even though Tier 1 & Tier 2 fixes apply without confirmation, the safety perimeter is unchanged:
|
||
the healer <span class="fw-bold">never</span> executes arbitrary shell commands,
|
||
<span class="fw-bold">never</span> touches <code>vendor/</code>,
|
||
<span class="fw-bold">never</span> runs <code>migrate:fresh</code>, and
|
||
<span class="fw-bold">never</span> applies a command that your capability toggles have disabled.
|
||
Every code edit produces a timestamped <code>.bak</code> next to the original and is reversible via the
|
||
<span class="badge bg-warning text-dark fw-bold">↻ ROLLBACK</span> button.
|
||
A re-entrancy guard (<code>AI_HEALER_RUNNING</code> constant + trace-based filter) prevents the healer from
|
||
healing its own exceptions.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- TERMINAL INSPECT MODAL --}}
|
||
<div class="modal fade" id="inspectModal" tabindex="-1">
|
||
<div class="modal-dialog modal-xl">
|
||
<div class="modal-content adminuiux-card border-0 shadow-2xl terminal-box">
|
||
<div class="modal-header terminal-header">
|
||
<div class="d-flex align-items-center">
|
||
<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" style="letter-spacing:1px;">
|
||
INCIDENT_DUMP · <span id="inspectIncidentId" class="text-success">#?</span> · <span id="inspectIncidentTime" class="text-warning"></span>
|
||
</h6>
|
||
</div>
|
||
<div class="d-flex align-items-center gap-2">
|
||
<button type="button" id="inspectRollbackBtn" class="btn btn-sm btn-outline-warning rounded-pill px-3 fw-bold d-none" style="font-size:.7rem;letter-spacing:.5px;" title="Restore the file from the .bak backup">
|
||
<i class="bi bi-arrow-counterclockwise me-1"></i>ROLLBACK
|
||
</button>
|
||
<button type="button" id="inspectRetryBtn" class="btn btn-sm btn-outline-light rounded-pill px-3 fw-bold d-none" style="font-size:.7rem;letter-spacing:.5px;">
|
||
<i class="bi bi-arrow-repeat me-1"></i>RE-RUN AI
|
||
</button>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
</div>
|
||
<div class="modal-body p-0">
|
||
<div class="p-4" style="max-height: 75vh; overflow-y: auto;">
|
||
<div class="mb-4">
|
||
<div class="extra-small text-white-50 fw-bold mb-1" style="letter-spacing:1px;">// EXCEPTION</div>
|
||
<h6 class="font-monospace text-danger mb-1" id="inspectErrorType"></h6>
|
||
<pre class="mb-0 small text-warning font-monospace" id="inspectErrorMessage" style="white-space:pre-wrap;"></pre>
|
||
</div>
|
||
<div class="mb-4">
|
||
<div class="extra-small text-white-50 fw-bold mb-1" style="letter-spacing:1px;">// AI_DIAGNOSIS</div>
|
||
<pre class="mb-0 small text-success font-monospace" id="inspectAiDiagnosis" style="white-space:pre-wrap;"></pre>
|
||
</div>
|
||
<div class="mb-4">
|
||
<div class="extra-small text-white-50 fw-bold mb-1" style="letter-spacing:1px;">// REMEDIATION_ACTION</div>
|
||
<pre class="mb-0 small text-info font-monospace" id="inspectActionTaken" style="white-space:pre-wrap;"></pre>
|
||
</div>
|
||
<div class="mb-4 d-none" id="inspectDiffContainer">
|
||
<div class="extra-small text-white-50 fw-bold mb-1" style="letter-spacing:1px;">// CODE_AUDIT_TRAIL (SIDE-BY-SIDE)</div>
|
||
<div class="diff-container mt-2">
|
||
<div class="diff-panel border-end border-secondary">
|
||
<div class="diff-panel-header"><i class="bi bi-file-earmark-minus text-danger"></i> Original Code</div>
|
||
<div class="diff-content scroll-custom" id="inspectDiffOriginal" style="max-height: 400px;"></div>
|
||
</div>
|
||
<div class="diff-panel">
|
||
<div class="diff-panel-header"><i class="bi bi-file-earmark-plus text-success"></i> Fixed Code</div>
|
||
<div class="diff-content scroll-custom" id="inspectDiffFixed" style="max-height: 400px;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div class="extra-small text-white-50 fw-bold mb-1" style="letter-spacing:1px;">// STACK_TRACE</div>
|
||
<pre class="mb-0 small text-light font-monospace scroll-custom" id="inspectStackTrace" style="white-space:pre-wrap;max-height:320px;overflow-y:auto;font-size:.72rem;line-height:1.6;"></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
@push('scripts')
|
||
<script>
|
||
(function () {
|
||
const STATS_URL = '{{ route("ai-self-healing.stats") }}';
|
||
const SHOW_URL = '/ai-self-healing/log/';
|
||
const REFRESH_INTERVAL = 30000;
|
||
|
||
// --- DataTable ---------------------------------------------------------
|
||
const dtOptions = {
|
||
order: [[0, 'desc']],
|
||
pageLength: 15,
|
||
autoWidth: false,
|
||
dom: 'tr<"d-flex justify-content-between align-items-center px-4 py-3 border-top extra-small text-muted"ip>',
|
||
language: {
|
||
emptyTable: 'No incidents recorded. System is clean.',
|
||
info: '_START_–_END_ of _TOTAL_ incidents',
|
||
infoEmpty: 'No incidents',
|
||
paginate: { previous: '‹', next: '›' }
|
||
},
|
||
columns: [
|
||
{ orderable: true },
|
||
{ orderable: true },
|
||
{ orderable: true },
|
||
{ orderable: true },
|
||
{ orderable: false }
|
||
]
|
||
};
|
||
|
||
let dt = $('#ai-logs-datatable').DataTable(dtOptions);
|
||
let activeFilter = 'all';
|
||
|
||
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
|
||
if (settings.nTable.id !== 'ai-logs-datatable') return true;
|
||
if (activeFilter === 'all') return true;
|
||
const status = $(dt.row(dataIndex).node()).data('status') || '';
|
||
if (activeFilter === 'pending') return status === 'pending' || status === 'diagnosing';
|
||
return status === activeFilter;
|
||
});
|
||
|
||
$('.filter-chip').on('click', function () {
|
||
$('.filter-chip').removeClass('bg-success-subtle text-success').addClass('bg-light text-dark');
|
||
$(this).removeClass('bg-light text-dark').addClass('bg-success-subtle text-success');
|
||
activeFilter = $(this).data('filter');
|
||
dt.draw();
|
||
});
|
||
|
||
// --- Inspect Modal -----------------------------------------------------
|
||
$('#ai-logs-datatable tbody').on('click', '.btn-inspect', function () {
|
||
const id = $(this).data('id');
|
||
const btn = $(this);
|
||
const orig = btn.html();
|
||
btn.html('<span class="spinner-border spinner-border-sm" role="status"></span>').prop('disabled', true);
|
||
|
||
$.get(SHOW_URL + id, function (res) {
|
||
if (res.success) {
|
||
const d = res.data;
|
||
$('#inspectIncidentId').text('#' + d.id);
|
||
$('#inspectIncidentTime').text(d.created_at + ' (' + d.relative_time + ')');
|
||
$('#inspectErrorType').text(d.error_type);
|
||
$('#inspectErrorMessage').text(d.error_message || '(no message)');
|
||
$('#inspectAiDiagnosis').text(d.ai_diagnosis || '(no diagnosis available)');
|
||
$('#inspectActionTaken').text(d.action_taken || '(no action taken)');
|
||
$('#inspectStackTrace').text(d.stack_trace || '(no stack trace)');
|
||
|
||
if (d.original_code || d.fixed_code) {
|
||
$('#inspectDiffContainer').removeClass('d-none');
|
||
$('#inspectDiffOriginal').text(d.original_code || '');
|
||
$('#inspectDiffFixed').text(d.fixed_code || '');
|
||
} else {
|
||
$('#inspectDiffContainer').addClass('d-none');
|
||
}
|
||
|
||
// Show the modal's RE-RUN button only when this log is retriable
|
||
const retriable = ['failed', 'diagnosing', 'pending'].includes(d.status);
|
||
const $retry = $('#inspectRetryBtn');
|
||
if (retriable) {
|
||
$retry.removeClass('d-none').data('id', d.id);
|
||
} else {
|
||
$retry.addClass('d-none').removeData('id');
|
||
}
|
||
|
||
// Show ROLLBACK button only when a code-edit backup exists
|
||
const $rb = $('#inspectRollbackBtn');
|
||
if (d.rollback && d.rollback.available) {
|
||
$rb.removeClass('d-none').data('id', d.id);
|
||
$rb.attr('title', 'Restore ' + d.rollback.file + ' from ' + d.rollback.backup_file);
|
||
} else {
|
||
$rb.addClass('d-none').removeData('id');
|
||
}
|
||
|
||
new bootstrap.Modal(document.getElementById('inspectModal')).show();
|
||
}
|
||
}).fail(function () {
|
||
if (typeof toastr !== 'undefined') toastr.error('Failed to load incident.');
|
||
}).always(function () {
|
||
btn.html(orig).prop('disabled', false);
|
||
});
|
||
});
|
||
|
||
// --- Retry handler (table row + modal button) ------------------------
|
||
function runRetry(id, $sourceBtn) {
|
||
const origHtml = $sourceBtn ? $sourceBtn.html() : null;
|
||
if ($sourceBtn) {
|
||
$sourceBtn.html('<span class="spinner-border spinner-border-sm" role="status"></span>').prop('disabled', true);
|
||
}
|
||
return $.ajax({
|
||
url: '/ai-self-healing/log/' + id + '/retry',
|
||
method: 'POST',
|
||
data: { _token: '{{ csrf_token() }}' }
|
||
}).done(function (res) {
|
||
if (res.success) {
|
||
if (typeof toastr !== 'undefined') {
|
||
const t = (res.status === 'resolved') ? 'success' : (res.status === 'failed' ? 'warning' : 'info');
|
||
toastr[t]('Healer re-ran. New status: ' + res.status.toUpperCase());
|
||
}
|
||
refreshFeed();
|
||
bootstrap.Modal.getInstance(document.getElementById('inspectModal'))?.hide();
|
||
} else {
|
||
if (typeof toastr !== 'undefined') toastr.error(res.message || 'Retry failed.');
|
||
}
|
||
}).fail(function (xhr) {
|
||
const msg = xhr.responseJSON?.message || 'Retry failed.';
|
||
if (typeof toastr !== 'undefined') toastr.error(msg);
|
||
}).always(function () {
|
||
if ($sourceBtn && origHtml) $sourceBtn.html(origHtml).prop('disabled', false);
|
||
});
|
||
}
|
||
|
||
$('#ai-logs-datatable tbody').on('click', '.btn-retry', function (e) {
|
||
e.stopPropagation();
|
||
runRetry($(this).data('id'), $(this));
|
||
});
|
||
|
||
$('#inspectRetryBtn').on('click', function () {
|
||
const id = $(this).data('id');
|
||
if (!id) return;
|
||
runRetry(id, $(this));
|
||
});
|
||
|
||
// --- Rollback handler -------------------------------------------------
|
||
$('#inspectRollbackBtn').on('click', function () {
|
||
const id = $(this).data('id');
|
||
if (!id) return;
|
||
const $btn = $(this);
|
||
StandardSwal.fire({
|
||
title: 'Roll back this fix?',
|
||
text: 'Restores the file from the .bak backup written when the AI applied the edit. The healing log will be marked as rolled back.',
|
||
icon: 'warning',
|
||
showCancelButton: true,
|
||
confirmButtonColor: '#f59e0b',
|
||
confirmButtonText: 'Yes, roll back'
|
||
}).then(r => {
|
||
if (!r.isConfirmed) return;
|
||
const orig = $btn.html();
|
||
$btn.html('<span class="spinner-border spinner-border-sm" role="status"></span>').prop('disabled', true);
|
||
$.ajax({
|
||
url: '/ai-self-healing/log/' + id + '/rollback',
|
||
method: 'POST',
|
||
data: { _token: '{{ csrf_token() }}' }
|
||
}).done(function (res) {
|
||
if (res.success) {
|
||
if (typeof toastr !== 'undefined') toastr.success(res.message);
|
||
refreshFeed();
|
||
bootstrap.Modal.getInstance(document.getElementById('inspectModal'))?.hide();
|
||
} else {
|
||
if (typeof toastr !== 'undefined') toastr.error(res.message || 'Rollback failed.');
|
||
}
|
||
}).fail(function (xhr) {
|
||
const msg = xhr.responseJSON?.message || 'Rollback failed.';
|
||
if (typeof toastr !== 'undefined') toastr.error(msg);
|
||
}).always(function () {
|
||
$btn.html(orig).prop('disabled', false);
|
||
});
|
||
});
|
||
});
|
||
|
||
// --- Apex Charts -------------------------------------------------------
|
||
const timelineData = @json($timeline);
|
||
const initialStats = @json($stats);
|
||
|
||
const sparkOpts = {
|
||
chart: { type:'area', height: 50, sparkline: { enabled: true }, animations: { enabled: false } },
|
||
stroke: { curve:'smooth', width: 2 },
|
||
fill: { type:'gradient', gradient: { opacityFrom: 0.5, opacityTo: 0 } },
|
||
colors: ['#4338ca'],
|
||
tooltip: { enabled: false },
|
||
series: [{ name:'Intercepts', data: timelineData.map(p => p.count) }]
|
||
};
|
||
const sparkTotal = new ApexCharts(document.querySelector('#spark-total'), sparkOpts);
|
||
sparkTotal.render();
|
||
|
||
// Main analytics chart
|
||
const timelineChart = new ApexCharts(document.querySelector('#chart-timeline'), {
|
||
chart: { type:'area', height: 280, toolbar: { show: false }, animations: { enabled: true } },
|
||
stroke: { curve:'smooth', width: 2 },
|
||
fill: { type:'gradient', gradient: { opacityFrom: 0.4, opacityTo: 0.05 } },
|
||
colors: ['#4338ca'],
|
||
dataLabels: { enabled: false },
|
||
xaxis: {
|
||
categories: timelineData.map(p => p.label),
|
||
labels: { style: { fontSize: '11px', colors: '#94a3b8' } },
|
||
axisBorder: { show: false }, axisTicks: { show: false }
|
||
},
|
||
yaxis: { labels: { style: { fontSize:'11px', colors:'#94a3b8' } }, min: 0, forceNiceScale: true },
|
||
grid: { borderColor:'#f1f5f9', strokeDashArray: 3 },
|
||
tooltip: { theme: 'dark' },
|
||
series: [{ name:'Incidents', data: timelineData.map(p => p.count) }]
|
||
});
|
||
timelineChart.render();
|
||
|
||
// Forensics donut
|
||
const distChart = new ApexCharts(document.querySelector('#chart-distribution'), {
|
||
chart: { type:'donut', height: 260 },
|
||
labels: ['Resolved','Failed','Pending'],
|
||
colors: ['#22c55e','#ef4444','#f59e0b'],
|
||
series: [initialStats.resolved, initialStats.failed, initialStats.pending],
|
||
legend: { position:'bottom', fontSize:'12px' },
|
||
plotOptions: { pie: { donut: { size:'68%', labels: { show: true, total: { show: true, label:'Total', fontWeight: 700, color:'#0f172a' } } } } },
|
||
dataLabels: { enabled: false }
|
||
});
|
||
distChart.render();
|
||
|
||
// --- Refresh stats + charts -------------------------------------------
|
||
function bump(id) {
|
||
const el = $('#' + id);
|
||
el.removeClass('bump'); void el[0].offsetWidth; el.addClass('bump');
|
||
}
|
||
function setStat(id, newVal, suffix) {
|
||
const el = $('#' + id);
|
||
const display = suffix ? newVal + suffix : newVal;
|
||
if (el.text().trim() !== String(display)) {
|
||
el.text(display);
|
||
bump(id);
|
||
}
|
||
}
|
||
|
||
function refreshStats() {
|
||
$.get(STATS_URL, function (d) {
|
||
setStat('stat-total', d.total);
|
||
setStat('stat-resolved', d.resolved);
|
||
setStat('stat-failed', d.failed);
|
||
setStat('stat-pending', d.pending);
|
||
setStat('stat-critical', d.failed);
|
||
setStat('stat-24h', d.last_24h);
|
||
$('#stat-rate').html(d.rate + '<small style="font-size:.5em;">%</small>');
|
||
$('#bar-resolved').css('width', d.rate + '%');
|
||
|
||
$('#chip-all').text(d.total);
|
||
$('#chip-resolved').text(d.resolved);
|
||
$('#chip-failed').text(d.failed);
|
||
$('#chip-pending').text(d.pending);
|
||
|
||
$('#forensic-total').text(d.total);
|
||
$('#forensic-24h').text(d.last_24h);
|
||
$('#forensic-rate').text(d.rate + '%');
|
||
$('#forensic-last').text(d.last_incident);
|
||
$('#last-incident-relative').text(d.last_incident);
|
||
|
||
if (d.timeline) {
|
||
sparkTotal.updateSeries([{ data: d.timeline.map(p => p.count) }]);
|
||
timelineChart.updateOptions({
|
||
xaxis: { categories: d.timeline.map(p => p.label) }
|
||
});
|
||
timelineChart.updateSeries([{ name:'Incidents', data: d.timeline.map(p => p.count) }]);
|
||
}
|
||
distChart.updateSeries([d.resolved, d.failed, d.pending]);
|
||
});
|
||
}
|
||
|
||
function refreshFeed() {
|
||
$('#refresh-icon').addClass('bi-spin');
|
||
$.get(window.location.href, function (html) {
|
||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||
dt.destroy();
|
||
$('#ai-logs-datatable tbody').html($(doc).find('#ai-logs-datatable tbody').html());
|
||
dt = $('#ai-logs-datatable').DataTable(dtOptions);
|
||
if (activeFilter !== 'all') dt.draw();
|
||
refreshStats();
|
||
setTimeout(() => $('#refresh-icon').removeClass('bi-spin'), 400);
|
||
});
|
||
}
|
||
|
||
// --- Buttons ----------------------------------------------------------
|
||
$('#btn-refresh-all').on('click', refreshFeed);
|
||
|
||
$('#btn-simulate').on('click', function () {
|
||
StandardSwal.fire({
|
||
title: 'Simulate Exception?',
|
||
text: 'Throws an intentional exception to test the AI interceptor pipeline.',
|
||
icon: 'info',
|
||
showCancelButton: true,
|
||
confirmButtonText: 'Yes, simulate'
|
||
}).then(r => {
|
||
if (!r.isConfirmed) return;
|
||
$('#btn-simulate i').addClass('bi-spin');
|
||
$.post('{{ route("ai-self-healing.simulate") }}', { _token: '{{ csrf_token() }}' })
|
||
.always(() => {
|
||
if (typeof toastr !== 'undefined') toastr.info('Simulated exception dispatched to AI engine.');
|
||
setTimeout(() => {
|
||
refreshFeed();
|
||
$('#btn-simulate i').removeClass('bi-spin');
|
||
}, 1500);
|
||
});
|
||
});
|
||
});
|
||
|
||
$('#btn-clear-logs').on('click', function () {
|
||
StandardSwal.fire({
|
||
title: 'Purge All Logs?',
|
||
text: 'This will permanently delete every AI diagnostic log. Cannot be undone.',
|
||
icon: 'warning',
|
||
showCancelButton: true,
|
||
confirmButtonColor: '#ef4444',
|
||
confirmButtonText: 'Yes, purge'
|
||
}).then(r => {
|
||
if (!r.isConfirmed) return;
|
||
$.post('{{ route("ai-self-healing.clear") }}', { _token: '{{ csrf_token() }}' }, function (res) {
|
||
if (res.success) {
|
||
refreshFeed();
|
||
if (typeof toastr !== 'undefined') toastr.success('Logs purged successfully.');
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
// --- Configuration form -----------------------------------------------
|
||
function applyCapabilityVisuals() {
|
||
const map = [
|
||
['cfg_healing_enabled', 'cap-interceptor', 'LISTENING', 'PAUSED'],
|
||
['cfg_allow_cache', 'cap-cache', 'ALLOWED', 'BLOCKED'],
|
||
['cfg_allow_queue', 'cap-queue', 'ALLOWED', 'BLOCKED'],
|
||
['cfg_allow_maintenance', 'cap-maintenance', 'ALLOWED', 'BLOCKED'],
|
||
['cfg_allow_db', 'cap-db', 'ALLOWED', 'BLOCKED'],
|
||
];
|
||
map.forEach(([fieldId, capId, onTxt, offTxt]) => {
|
||
const on = $('#' + fieldId).is(':checked');
|
||
const $icon = $('#' + capId + '-icon');
|
||
const $text = $('#' + capId + '-text');
|
||
$icon.removeClass('active disabled warn');
|
||
$text.removeClass('text-success text-danger text-warning text-muted');
|
||
if (on) {
|
||
if (capId === 'cap-maintenance') { $icon.addClass('warn'); $text.addClass('text-warning'); }
|
||
else if (capId === 'cap-db') { $icon.addClass('disabled'); $text.addClass('text-danger'); }
|
||
else { $icon.addClass('active'); $text.addClass('text-success'); }
|
||
$text.text(onTxt);
|
||
} else {
|
||
if (capId === 'cap-interceptor') { $icon.addClass('disabled'); $text.addClass('text-danger'); }
|
||
else { $text.addClass('text-muted'); }
|
||
$text.text(offTxt);
|
||
}
|
||
});
|
||
|
||
// Engine pulse & capabilities pill
|
||
const engineOn = $('#cfg_healing_enabled').is(':checked');
|
||
$('#engine-pulse').removeClass('success danger').addClass(engineOn ? 'success' : 'danger').text(engineOn ? 'ENGINE ACTIVE' : 'ENGINE DISABLED');
|
||
$('#capabilities-pill').removeClass('success danger').addClass(engineOn ? 'success' : 'danger').text(engineOn ? 'OPERATIONAL' : 'STANDBY');
|
||
}
|
||
|
||
// Live visual feedback as toggles change (before save)
|
||
$('#healing-config-form .form-check-input').on('change', applyCapabilityVisuals);
|
||
|
||
$('#healing-config-form').on('submit', function (e) {
|
||
e.preventDefault();
|
||
const btn = $('#save-config-btn');
|
||
const original = btn.html();
|
||
btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span> Saving…');
|
||
|
||
$.post('{{ route("ai-self-healing.update") }}', $(this).serialize(), function (res) {
|
||
if (res.success) {
|
||
btn.removeClass('btn-theme-1').addClass('btn-success').html('<i class="bi bi-check-lg me-1"></i> Saved');
|
||
if (typeof toastr !== 'undefined') toastr.success(res.message);
|
||
|
||
// Sync navbar visibility
|
||
const aiOn = $('#cfg_healing_enabled').is(':checked');
|
||
$('#menu-ai-diagnostics').toggleClass('d-none', !aiOn);
|
||
|
||
applyCapabilityVisuals();
|
||
|
||
setTimeout(() => {
|
||
btn.prop('disabled', false).removeClass('btn-success').addClass('btn-theme-1').html(original);
|
||
}, 1800);
|
||
}
|
||
}).fail(() => {
|
||
if (typeof toastr !== 'undefined') toastr.error('Failed to save configuration.');
|
||
btn.prop('disabled', false).html(original);
|
||
});
|
||
});
|
||
|
||
// --- Auto refresh -----------------------------------------------------
|
||
setInterval(refreshStats, REFRESH_INTERVAL);
|
||
})();
|
||
</script>
|
||
@endpush
|
||
</x-app-layout>
|