2025-10-10 14:01:19 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Controllers;
|
|
|
|
|
|
|
|
|
|
use Core\Controller;
|
|
|
|
|
use Core\Auth;
|
|
|
|
|
use App\Models\ErrorLog;
|
|
|
|
|
|
|
|
|
|
class ErrorLogController extends Controller
|
|
|
|
|
{
|
|
|
|
|
private $errorLogModel;
|
|
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
Auth::requireAdmin();
|
|
|
|
|
$this->errorLogModel = new ErrorLog();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Display list of errors with filters
|
|
|
|
|
*/
|
|
|
|
|
public function index()
|
|
|
|
|
{
|
|
|
|
|
// Get filters from query params
|
|
|
|
|
$filters = [
|
|
|
|
|
'resolved' => $_GET['resolved'] ?? '',
|
|
|
|
|
'type' => $_GET['type'] ?? '',
|
|
|
|
|
'sort' => $_GET['sort'] ?? 'last_occurred_at',
|
|
|
|
|
'order' => $_GET['order'] ?? 'desc'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Pagination
|
|
|
|
|
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
|
|
|
|
$perPage = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 25;
|
|
|
|
|
$offset = ($page - 1) * $perPage;
|
|
|
|
|
|
|
|
|
|
// Get total count using model
|
|
|
|
|
$totalErrors = $this->errorLogModel->countUniqueErrors($filters);
|
|
|
|
|
|
|
|
|
|
// Get paginated errors using model
|
|
|
|
|
$errors = $this->errorLogModel->getPaginatedErrors($filters, $perPage, $offset);
|
|
|
|
|
|
|
|
|
|
// Get statistics using model
|
2025-10-20 19:53:28 +03:00
|
|
|
$errorStats = $this->errorLogModel->getAdminStats();
|
2025-10-10 14:01:19 +03:00
|
|
|
|
|
|
|
|
// Pagination data
|
|
|
|
|
$totalPages = ceil($totalErrors / $perPage);
|
|
|
|
|
$pagination = [
|
|
|
|
|
'current_page' => $page,
|
|
|
|
|
'total_pages' => $totalPages,
|
|
|
|
|
'per_page' => $perPage,
|
|
|
|
|
'total' => $totalErrors,
|
|
|
|
|
'showing_from' => $totalErrors > 0 ? $offset + 1 : 0,
|
|
|
|
|
'showing_to' => min($offset + $perPage, $totalErrors)
|
|
|
|
|
];
|
|
|
|
|
|
2025-10-20 19:53:28 +03:00
|
|
|
$this->view('errors/admin-index', compact('errors', 'errorStats', 'filters', 'pagination'));
|
2025-10-10 14:01:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Show error details
|
|
|
|
|
*/
|
|
|
|
|
public function show($params = [])
|
|
|
|
|
{
|
|
|
|
|
$errorId = $params['id'] ?? '';
|
|
|
|
|
|
|
|
|
|
// Get all occurrences using model
|
|
|
|
|
$errorOccurrences = $this->errorLogModel->getOccurrencesByErrorId($errorId);
|
|
|
|
|
|
|
|
|
|
if (empty($errorOccurrences)) {
|
|
|
|
|
$_SESSION['error'] = 'Error not found';
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the most recent occurrence for display
|
|
|
|
|
$error = $errorOccurrences[0];
|
|
|
|
|
|
|
|
|
|
// Parse JSON fields
|
|
|
|
|
$error['stack_trace_array'] = json_decode($error['stack_trace'], true) ?? [];
|
|
|
|
|
$error['request_data'] = json_decode($error['request_data'], true) ?? [];
|
|
|
|
|
$error['session_data'] = json_decode($error['session_data'], true) ?? [];
|
|
|
|
|
|
|
|
|
|
$this->view('errors/admin-detail', compact('error', 'errorOccurrences'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mark error as resolved
|
|
|
|
|
*/
|
|
|
|
|
public function markResolved($params = [])
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/errors');
|
|
|
|
|
|
|
|
|
|
$errorId = $params['id'] ?? '';
|
|
|
|
|
$notes = $_POST['notes'] ?? null;
|
|
|
|
|
|
|
|
|
|
// Mark error as resolved using model
|
2025-10-20 18:38:58 +03:00
|
|
|
$this->errorLogModel->markErrorResolved($errorId, \Core\Auth::id(), $notes);
|
2025-10-10 14:01:19 +03:00
|
|
|
|
|
|
|
|
$_SESSION['success'] = 'Error marked as resolved';
|
|
|
|
|
header('Location: /errors/' . urlencode($errorId));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mark error as unresolved
|
|
|
|
|
*/
|
|
|
|
|
public function markUnresolved($params = [])
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/errors');
|
|
|
|
|
|
|
|
|
|
$errorId = $params['id'] ?? '';
|
|
|
|
|
|
|
|
|
|
// Mark error as unresolved using model
|
|
|
|
|
$this->errorLogModel->markErrorUnresolved($errorId);
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = 'Error marked as unresolved';
|
|
|
|
|
header('Location: /errors/' . urlencode($errorId));
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Delete error and all its occurrences
|
|
|
|
|
*/
|
|
|
|
|
public function delete($params = [])
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/errors');
|
|
|
|
|
|
|
|
|
|
$errorId = $params['id'] ?? '';
|
|
|
|
|
|
|
|
|
|
// Delete error using model
|
|
|
|
|
$this->errorLogModel->deleteByErrorId($errorId);
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = 'Error deleted successfully';
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear old resolved errors
|
|
|
|
|
*/
|
|
|
|
|
public function clearResolved()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/errors');
|
|
|
|
|
|
|
|
|
|
$daysOld = isset($_POST['days']) ? (int)$_POST['days'] : 30;
|
|
|
|
|
|
|
|
|
|
// Clear old errors using model
|
|
|
|
|
$deletedCount = $this->errorLogModel->clearOldResolved($daysOld);
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = "Deleted $deletedCount resolved error(s) older than $daysOld days";
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Bulk delete errors
|
|
|
|
|
*/
|
|
|
|
|
public function bulkDelete()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/errors');
|
|
|
|
|
|
|
|
|
|
$errorIdsJson = $_POST['error_ids'] ?? '[]';
|
|
|
|
|
$errorIds = json_decode($errorIdsJson, true);
|
|
|
|
|
|
|
|
|
|
if (empty($errorIds) || !is_array($errorIds)) {
|
|
|
|
|
$_SESSION['error'] = 'No errors selected for deletion';
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$deletedCount = 0;
|
|
|
|
|
foreach ($errorIds as $errorId) {
|
|
|
|
|
if ($this->errorLogModel->deleteByErrorId($errorId)) {
|
|
|
|
|
$deletedCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = "Successfully deleted $deletedCount error(s)";
|
|
|
|
|
header('Location: /errors');
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|