Files

1610 lines
111 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &rarr; apply &rarr; 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.500.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 &lt; 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.001.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 &lt;user&gt;]</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 &amp; 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 &amp; 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 &rarr; click <span class="fw-bold">↻ ROLLBACK</span>. File is restored from <code>.bak</code>, caches cleared, log tagged <code>[rolled back by &lt;user&gt;]</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>?-&gt;</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-&gt;nama</code> → <code>$user?-&gt;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 &amp; 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 &middot; <span id="inspectIncidentId" class="text-success">#?</span> &middot; <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>