nhance_partner_be/app/Controllers/LogViewerController.php
2026-08-05 10:30:55 +05:30

176 lines
5.1 KiB
PHP

<?php
namespace App\Controllers;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\ResponseInterface;
class LogViewerController extends BaseController
{
use ResponseTrait;
private string $logsPath;
public function __construct()
{
$this->logsPath = rtrim(WRITEPATH . 'logs', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}
public function index(): string
{
return view('logs/index');
}
public function list(): ResponseInterface
{
$search = trim((string) ($this->request->getGet('q') ?? ''));
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
$limit = max(1, min(50, (int) ($this->request->getGet('limit') ?? 10)));
$all = $this->listLogFiles($search !== '' ? $search : null);
$total = count($all);
$offset = ($page - 1) * $limit;
$files = array_slice($all, $offset, $limit);
$pages = $total > 0 ? (int) ceil($total / $limit) : 0;
return $this->respond([
'status' => 'success',
'files' => $files,
'page' => $page,
'limit' => $limit,
'total' => $total,
'total_pages'=> $pages,
'has_more' => $page < $pages,
]);
}
public function view(): string|ResponseInterface
{
$file = $this->resolveLogFile((string) ($this->request->getGet('file') ?? ''));
if ($file === null) {
return $this->response->setStatusCode(404)->setBody('Log file not found.');
}
$content = @file_get_contents($file['path']);
if ($content === false) {
return $this->response->setStatusCode(500)->setBody('Unable to read log file.');
}
$maxBytes = 2 * 1024 * 1024;
$truncated = false;
if (strlen($content) > $maxBytes) {
$content = substr($content, -$maxBytes);
$truncated = true;
}
return view('logs/view', [
'file' => $file,
'content' => $content,
'truncated' => $truncated,
'maxBytes' => $maxBytes,
]);
}
public function download(): DownloadResponse|ResponseInterface
{
$file = $this->resolveLogFile((string) ($this->request->getGet('file') ?? ''));
if ($file === null) {
return $this->response->setStatusCode(404)->setBody('Log file not found.');
}
return $this->response->download($file['path'], null)->setFileName($file['name']);
}
/**
* @return list<array{name: string, size: int, size_human: string, modified: string, modified_ts: int}>
*/
private function listLogFiles(?string $search = null): array
{
if (! is_dir($this->logsPath)) {
return [];
}
$files = glob($this->logsPath . 'log-*.log') ?: [];
$result = [];
$needle = $search !== null ? strtolower($search) : null;
foreach ($files as $path) {
if (! is_file($path)) {
continue;
}
$name = basename($path);
if ($needle !== null && ! str_contains(strtolower($name), $needle)) {
continue;
}
$size = (int) filesize($path);
$mtime = (int) filemtime($path);
$result[] = [
'name' => $name,
'size' => $size,
'size_human' => $this->formatBytes($size),
'modified' => date('Y-m-d H:i:s', $mtime),
'modified_ts' => $mtime,
];
}
usort($result, static fn (array $a, array $b): int => $b['modified_ts'] <=> $a['modified_ts']);
return $result;
}
/**
* @return array{name: string, path: string, size: int, size_human: string, modified: string}|null
*/
private function resolveLogFile(string $name): ?array
{
$name = basename(trim($name));
if ($name === '' || ! preg_match('/^log-\d{4}-\d{2}-\d{2}\.log$/', $name)) {
return null;
}
$path = $this->logsPath . $name;
$realPath = realpath($path);
$realLogs = realpath($this->logsPath);
if ($realPath === false || $realLogs === false || ! str_starts_with($realPath, $realLogs) || ! is_file($realPath)) {
return null;
}
$size = (int) filesize($realPath);
return [
'name' => $name,
'path' => $realPath,
'size' => $size,
'size_human' => $this->formatBytes($size),
'modified' => date('Y-m-d H:i:s', (int) filemtime($realPath)),
];
}
private function formatBytes(int $bytes): string
{
if ($bytes < 1024) {
return $bytes . ' B';
}
$units = ['KB', 'MB', 'GB'];
$value = (float) $bytes;
foreach ($units as $unit) {
$value /= 1024;
if ($value < 1024) {
return round($value, 2) . ' ' . $unit;
}
}
return round($value / 1024, 2) . ' TB';
}
}