First Commit
This commit is contained in:
Executable
+302
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use Kernel\View;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Modules\Auth\Models\User;
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as DB;
|
||||
|
||||
class SystemController extends BaseAdminController
|
||||
{
|
||||
private $logPath;
|
||||
private $cachePath;
|
||||
|
||||
public function __construct(View $view)
|
||||
{
|
||||
parent::__construct($view);
|
||||
$this->logPath = dirname(__DIR__, 2) . '/logs';
|
||||
$this->cachePath = dirname(__DIR__, 2) . '/cache';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display system information.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function info(Request $request): Response
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
if (!$user || !$user->isSuperAdmin()) {
|
||||
return new Response($this->view->render('components/alerts.twig', [
|
||||
'type' => 'danger',
|
||||
'message' => 'Access denied.'
|
||||
]), 403);
|
||||
}
|
||||
|
||||
$dbVersion = 'Unknown';
|
||||
try {
|
||||
$pdo = DB::connection()->getPdo();
|
||||
$dbVersion = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
} catch (\Exception $e) {
|
||||
$dbVersion = 'Error: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
$info = [
|
||||
'app' => [
|
||||
'name' => 'SysCMS',
|
||||
'version' => '1.0.0', // Should be from config or const
|
||||
'env' => getenv('APP_ENV') ?: 'production',
|
||||
'debug' => getenv('APP_DEBUG') ?: false,
|
||||
],
|
||||
'system' => [
|
||||
'os' => php_uname('s') . ' ' . php_uname('r'),
|
||||
'hostname' => gethostname(),
|
||||
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown',
|
||||
'timezone' => date_default_timezone_get(),
|
||||
],
|
||||
'php' => [
|
||||
'version' => phpversion(),
|
||||
'sapi' => php_sapi_name(),
|
||||
'memory_limit' => ini_get('memory_limit'),
|
||||
'max_execution_time' => ini_get('max_execution_time') . 's',
|
||||
'upload_max_filesize' => ini_get('upload_max_filesize'),
|
||||
'post_max_size' => ini_get('post_max_size'),
|
||||
'extensions' => get_loaded_extensions(),
|
||||
],
|
||||
'database' => [
|
||||
'driver' => DB::connection()->getDriverName(),
|
||||
'version' => $dbVersion,
|
||||
'database' => DB::connection()->getDatabaseName(),
|
||||
],
|
||||
'disk' => [
|
||||
'total' => $this->formatBytes(disk_total_space('.')),
|
||||
'free' => $this->formatBytes(disk_free_space('.')),
|
||||
'used' => $this->formatBytes(disk_total_space('.') - disk_free_space('.')),
|
||||
]
|
||||
];
|
||||
|
||||
return $this->renderAdminView('system/info.twig', [
|
||||
'page_title' => 'System Information',
|
||||
'info' => $info
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display PHP Info.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function phpInfo(Request $request): Response
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
if (!$user || !$user->isSuperAdmin()) {
|
||||
return new Response('Access denied', 403);
|
||||
}
|
||||
|
||||
ob_start();
|
||||
phpinfo();
|
||||
$phpinfo = ob_get_clean();
|
||||
|
||||
// Extract body content to embed cleanly if desired, or just output raw.
|
||||
// For admin panel consistency, usually raw in a new tab is easiest,
|
||||
// but embedding provides better UX.
|
||||
// Let's strip body tags to embed.
|
||||
|
||||
$phpinfo = preg_replace('%^.*<body>(.*)</body>.*$%ms', '$1', $phpinfo);
|
||||
|
||||
return $this->renderAdminView('system/phpinfo.twig', [
|
||||
'page_title' => 'PHP Info',
|
||||
'phpinfo' => $phpinfo
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all log files.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function logsIndex(Request $request): Response
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
if (!$user || !$user->isSuperAdmin()) {
|
||||
return new Response($this->view->render('components/alerts.twig', [
|
||||
'type' => 'danger',
|
||||
'message' => 'Access denied.'
|
||||
]), 403);
|
||||
}
|
||||
|
||||
$files = [];
|
||||
if (is_dir($this->logPath)) {
|
||||
$files = glob($this->logPath . '/*.log');
|
||||
}
|
||||
|
||||
// Sort by modification time desc
|
||||
usort($files, function($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
|
||||
$logs = [];
|
||||
foreach ($files as $file) {
|
||||
$logs[] = [
|
||||
'name' => basename($file),
|
||||
'size' => $this->formatBytes(filesize($file)),
|
||||
'modified' => date('Y-m-d H:i:s', filemtime($file))
|
||||
];
|
||||
}
|
||||
|
||||
return $this->renderAdminView('logs/index.twig', [
|
||||
'page_title' => 'System Logs',
|
||||
'logs' => $logs
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show log file content.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function logsShow(Request $request): Response
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
if (!$user || !$user->isSuperAdmin()) {
|
||||
return new Response($this->view->render('components/alerts.twig', [
|
||||
'type' => 'danger',
|
||||
'message' => 'Access denied.'
|
||||
]), 403);
|
||||
}
|
||||
|
||||
$filename = $request->query->get('file');
|
||||
if (!$filename) {
|
||||
return new RedirectResponse(url('admin/logs'));
|
||||
}
|
||||
|
||||
// Security check: prevent directory traversal
|
||||
if (strpos($filename, '..') !== false || strpos($filename, '/') !== false || strpos($filename, '\\') !== false) {
|
||||
return new Response('Invalid filename', 400);
|
||||
}
|
||||
|
||||
$filePath = $this->logPath . '/' . $filename;
|
||||
if (!file_exists($filePath)) {
|
||||
return new Response('File not found', 404);
|
||||
}
|
||||
|
||||
// Limit file size read to avoid memory issues (e.g., 5MB)
|
||||
if (filesize($filePath) > 5 * 1024 * 1024) {
|
||||
// Read only last 500 lines if file is too big
|
||||
$content = $this->tailCustom($filePath, 500);
|
||||
$content = "File too large, showing last 500 lines:\n\n" . $content;
|
||||
} else {
|
||||
$content = file_get_contents($filePath);
|
||||
}
|
||||
|
||||
return $this->renderAdminView('logs/show.twig', [
|
||||
'page_title' => 'Log: ' . $filename,
|
||||
'filename' => $filename,
|
||||
'content' => $content
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear system cache.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function clearCache(Request $request): Response
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
if (!$user || !$user->isSuperAdmin()) {
|
||||
return new Response($this->view->render('components/alerts.twig', [
|
||||
'type' => 'danger',
|
||||
'message' => 'Access denied.'
|
||||
]), 403);
|
||||
}
|
||||
|
||||
$deletedCount = 0;
|
||||
|
||||
// Clear file cache
|
||||
if (is_dir($this->cachePath)) {
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($this->cachePath, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($files as $fileinfo) {
|
||||
// Skip .gitignore
|
||||
if ($fileinfo->getFilename() === '.gitignore') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
|
||||
if (@$todo($fileinfo->getRealPath())) {
|
||||
if ($todo === 'unlink') {
|
||||
$deletedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear OpCache
|
||||
$opcacheCleared = false;
|
||||
if (function_exists('opcache_reset')) {
|
||||
$opcacheCleared = opcache_reset();
|
||||
}
|
||||
|
||||
// Add a success flash message (if flash system exists, otherwise just redirect with query param)
|
||||
// Since we don't have a flash message system clearly defined in context, let's use query param
|
||||
|
||||
return new RedirectResponse(url('dashboard?cache_cleared=1&deleted=' . $deletedCount . '&opcache=' . ($opcacheCleared ? 1 : 0)));
|
||||
}
|
||||
|
||||
private function formatBytes($bytes, $precision = 2) {
|
||||
$units = array('B', 'KB', 'MB', 'GB', 'TB');
|
||||
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
|
||||
$bytes /= pow(1024, $pow);
|
||||
|
||||
return round($bytes, $precision) . ' ' . $units[$pow];
|
||||
}
|
||||
|
||||
/**
|
||||
* Slightly modified tail function
|
||||
*/
|
||||
private function tailCustom($filepath, $lines = 1) {
|
||||
$f = @fopen($filepath, "rb");
|
||||
if ($f === false) return false;
|
||||
|
||||
$buffer = 4096;
|
||||
fseek($f, -1, SEEK_END);
|
||||
|
||||
if (fread($f, 1) != "\n") $lines -= 1;
|
||||
|
||||
$output = '';
|
||||
$chunk = '';
|
||||
|
||||
while (ftell($f) > 0 && $lines >= 0) {
|
||||
$seek = min(ftell($f), $buffer);
|
||||
fseek($f, -$seek, SEEK_CUR);
|
||||
$chunk = fread($f, $seek);
|
||||
$output = $chunk . $output;
|
||||
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
|
||||
$lines -= substr_count($chunk, "\n");
|
||||
}
|
||||
|
||||
while ($lines++ < 0) {
|
||||
$output = substr($output, strpos($output, "\n") + 1);
|
||||
}
|
||||
|
||||
fclose($f);
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user