- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
294 lines
10 KiB
PHP
294 lines
10 KiB
PHP
@extends('admin.layout')
|
|
|
|
@section('title', 'Database Health Dashboard')
|
|
|
|
@section('content')
|
|
<div class="container-fluid">
|
|
<h1 class="h3 mb-4 text-gray-800">Database Health Dashboard</h1>
|
|
|
|
<div class="row">
|
|
<!-- Connection Selector -->
|
|
<div class="col-md-12 mb-4">
|
|
<div class="card shadow">
|
|
<div class="card-header py-3">
|
|
<h6 class="m-0 font-weight-bold text-primary">Database Connection</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<select id="connection-selector" class="form-control">
|
|
<option value="all">All Connections</option>
|
|
@foreach($connections as $conn)
|
|
<option value="{{ $conn }}">{{ $conn }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<button id="refresh-button" class="btn btn-primary">
|
|
<i class="fas fa-sync-alt"></i> Refresh
|
|
</button>
|
|
<div class="float-right">
|
|
<div class="custom-control custom-switch">
|
|
<input type="checkbox" class="custom-control-input" id="auto-refresh">
|
|
<label class="custom-control-label" for="auto-refresh">Auto-refresh (30s)</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="health-results">
|
|
<!-- Health check results will be loaded here -->
|
|
<div class="text-center py-5">
|
|
<div class="spinner-border" role="status">
|
|
<span class="sr-only">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Health Check Details Modal -->
|
|
<div class="modal fade" id="health-details-modal" tabindex="-1" role="dialog" aria-labelledby="health-details-modal-label" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="health-details-modal-label">Health Check Details</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body" id="health-details-content">
|
|
<!-- Health check details will be loaded here -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@section('scripts')
|
|
<script>
|
|
// Global variables
|
|
let autoRefreshInterval = null;
|
|
let currentConnection = 'all';
|
|
|
|
$(document).ready(function() {
|
|
// Load initial data
|
|
loadHealthData();
|
|
|
|
// Set up connection selector
|
|
$('#connection-selector').change(function() {
|
|
currentConnection = $(this).val();
|
|
loadHealthData();
|
|
});
|
|
|
|
// Set up refresh button
|
|
$('#refresh-button').click(function() {
|
|
loadHealthData();
|
|
});
|
|
|
|
// Set up auto-refresh
|
|
$('#auto-refresh').change(function() {
|
|
if ($(this).is(':checked')) {
|
|
autoRefreshInterval = setInterval(loadHealthData, 30000);
|
|
} else {
|
|
clearInterval(autoRefreshInterval);
|
|
}
|
|
});
|
|
});
|
|
|
|
function loadHealthData() {
|
|
const url = currentConnection === 'all'
|
|
? '/admin/database/health/all'
|
|
: `/admin/database/health/${currentConnection}`;
|
|
|
|
$.ajax({
|
|
url: url,
|
|
method: 'GET',
|
|
success: function(data) {
|
|
renderHealthData(data);
|
|
},
|
|
error: function() {
|
|
$('#health-results').html('<div class="alert alert-danger">Failed to load health data</div>');
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderHealthData(data) {
|
|
let html = '';
|
|
|
|
if (currentConnection === 'all') {
|
|
// Render all connections
|
|
let allHealthy = true;
|
|
|
|
for (const [connection, result] of Object.entries(data)) {
|
|
if (!result.is_healthy) {
|
|
allHealthy = false;
|
|
}
|
|
|
|
html += renderConnectionHealth(connection, result);
|
|
}
|
|
|
|
// Add overall status card
|
|
const overallStatusHtml = `
|
|
<div class="row mb-4">
|
|
<div class="col-md-12">
|
|
<div class="card ${allHealthy ? 'bg-success' : 'bg-danger'} text-white shadow">
|
|
<div class="card-body">
|
|
<div class="row align-items-center">
|
|
<div class="col-md-1 text-center">
|
|
<i class="fas ${allHealthy ? 'fa-check-circle' : 'fa-exclamation-circle'} fa-3x"></i>
|
|
</div>
|
|
<div class="col-md-11">
|
|
<h5>Overall Status: ${allHealthy ? 'Healthy' : 'Unhealthy'}</h5>
|
|
<p class="mb-0">${allHealthy ? 'All database connections are healthy' : 'One or more database connections have issues'}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
html = overallStatusHtml + html;
|
|
} else {
|
|
// Render single connection
|
|
html = renderConnectionHealth(currentConnection, data);
|
|
}
|
|
|
|
$('#health-results').html(html);
|
|
|
|
// Set up view details buttons
|
|
$('.view-details').click(function() {
|
|
const checkId = $(this).data('check-id');
|
|
const connection = $(this).data('connection');
|
|
const checkData = $(this).data('check');
|
|
|
|
$('#health-details-modal-label').text(`${checkData.name} Details`);
|
|
|
|
let detailsHtml = `
|
|
<div class="mb-3">
|
|
<h6>Status</h6>
|
|
<p>${formatStatus(checkData.status)}</p>
|
|
</div>
|
|
<div class="mb-3">
|
|
<h6>Message</h6>
|
|
<p>${checkData.message}</p>
|
|
</div>
|
|
<div class="mb-3">
|
|
<h6>Description</h6>
|
|
<p>${checkData.description}</p>
|
|
</div>
|
|
`;
|
|
|
|
$('#health-details-content').html(detailsHtml);
|
|
$('#health-details-modal').modal('show');
|
|
});
|
|
}
|
|
|
|
function renderConnectionHealth(connection, result) {
|
|
const timestamp = result.timestamp;
|
|
const overallStatus = result.overall_status;
|
|
const isHealthy = result.is_healthy;
|
|
|
|
let html = `
|
|
<div class="row mb-4">
|
|
<div class="col-md-12">
|
|
<div class="card shadow">
|
|
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
|
<h6 class="m-0 font-weight-bold text-primary">Connection: ${connection}</h6>
|
|
<div>
|
|
<span class="mr-2">Status: ${formatStatus(overallStatus)}</span>
|
|
<span>Last checked: ${timestamp}</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
`;
|
|
|
|
// Display errors
|
|
if (result.errors && Object.keys(result.errors).length > 0) {
|
|
html += `
|
|
<div class="alert alert-danger">
|
|
<h5>Errors</h5>
|
|
<ul>
|
|
`;
|
|
|
|
for (const [errorId, error] of Object.entries(result.errors)) {
|
|
html += `<li><strong>${error.name}:</strong> ${error.message}</li>`;
|
|
}
|
|
|
|
html += `
|
|
</ul>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Display checks
|
|
html += `
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>Check</th>
|
|
<th>Status</th>
|
|
<th>Message</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
`;
|
|
|
|
for (const [checkId, check] of Object.entries(result.checks)) {
|
|
html += `
|
|
<tr>
|
|
<td>${check.name}</td>
|
|
<td>${formatStatus(check.status)}</td>
|
|
<td>${check.message}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-info view-details"
|
|
data-check-id="${checkId}"
|
|
data-connection="${connection}"
|
|
data-check='${JSON.stringify(check)}'>
|
|
View Details
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
|
|
html += `
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
|
|
html += `
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
return html;
|
|
}
|
|
|
|
function formatStatus(status) {
|
|
switch (status) {
|
|
case 'ok':
|
|
return '<span class="badge badge-success">OK</span>';
|
|
case 'warning':
|
|
return '<span class="badge badge-warning">WARNING</span>';
|
|
case 'error':
|
|
return '<span class="badge badge-danger">ERROR</span>';
|
|
default:
|
|
return `<span class="badge badge-secondary">${status}</span>`;
|
|
}
|
|
}
|
|
</script>
|
|
@endsection
|