525 lines
16 KiB
PHP
525 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Admin Performance Controller für das Webshop-System
|
|
*
|
|
* @author Webshop System
|
|
* @license GPL v3
|
|
*/
|
|
|
|
namespace App\Controllers\Admin;
|
|
|
|
use App\Core\Performance;
|
|
use App\Core\Cache;
|
|
use App\Core\Session;
|
|
use Doctrine\DBAL\DriverManager;
|
|
use Doctrine\DBAL\Exception;
|
|
|
|
class PerformanceController
|
|
{
|
|
private $performance;
|
|
private $cache;
|
|
private $session;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->performance = new Performance();
|
|
$this->cache = new Cache();
|
|
$this->session = new Session();
|
|
|
|
// Session-Check für Admin
|
|
if (!$this->session->isLoggedIn() || !$this->session->isAdmin()) {
|
|
header('Location: /admin/login');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performance-Dashboard anzeigen
|
|
*/
|
|
public function index()
|
|
{
|
|
$title = 'Performance-Monitoring - Webshop Admin';
|
|
|
|
// Performance-Header setzen
|
|
$this->performance->setPerformanceHeaders();
|
|
|
|
// Template rendern
|
|
$this->renderTemplate('admin/performance/index.html.twig', [
|
|
'title' => $title
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Performance-Statistiken API
|
|
*/
|
|
public function getStats()
|
|
{
|
|
header('Content-Type: application/json');
|
|
|
|
try {
|
|
$stats = $this->performance->getStats();
|
|
$cacheStats = $this->cache->getStats();
|
|
|
|
// Cache-Hit-Rate berechnen
|
|
$totalRequests = ($cacheStats['hits'] ?? 0) + ($cacheStats['misses'] ?? 0);
|
|
$hitRate = $totalRequests > 0 ? round(($cacheStats['hits'] ?? 0) / $totalRequests * 100, 2) : 0;
|
|
|
|
$response = [
|
|
'success' => true,
|
|
'data' => [
|
|
'execution_time' => $stats['execution_time'],
|
|
'memory_usage' => $stats['memory_usage'],
|
|
'queries_count' => $stats['queries_count'],
|
|
'cache_hit_rate' => $hitRate,
|
|
'cache_hits' => $cacheStats['hits'] ?? 0,
|
|
'cache_misses' => $cacheStats['misses'] ?? 0,
|
|
'cache_size' => $this->formatBytes($this->getCacheSize()),
|
|
'execution_time_labels' => $this->getExecutionTimeLabels(),
|
|
'execution_time_data' => $this->getExecutionTimeData(),
|
|
'memory_labels' => $this->getMemoryLabels(),
|
|
'memory_data' => $this->getMemoryData()
|
|
]
|
|
];
|
|
|
|
echo json_encode($response);
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'Failed to get performance stats: ' . $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performance-Metriken API
|
|
*/
|
|
public function getMetrics()
|
|
{
|
|
header('Content-Type: application/json');
|
|
|
|
try {
|
|
$metrics = [
|
|
[
|
|
'name' => 'Datenbank-Verbindungen',
|
|
'description' => 'Aktive Datenbankverbindungen',
|
|
'value' => $this->getDatabaseConnections(),
|
|
'unit' => 'Verbindungen',
|
|
'percentage' => min(100, $this->getDatabaseConnections() / 10 * 100)
|
|
],
|
|
[
|
|
'name' => 'Cache-Effizienz',
|
|
'description' => 'Cache-Trefferrate',
|
|
'value' => $this->getCacheHitRate(),
|
|
'unit' => '%',
|
|
'percentage' => $this->getCacheHitRate()
|
|
],
|
|
[
|
|
'name' => 'Speicherverbrauch',
|
|
'description' => 'PHP Speicherverbrauch',
|
|
'value' => $this->formatBytes(memory_get_usage()),
|
|
'unit' => 'Bytes',
|
|
'percentage' => min(100, memory_get_usage() / (1024 * 1024 * 128) * 100)
|
|
],
|
|
[
|
|
'name' => 'Datenbank-Größe',
|
|
'description' => 'Gesamtgröße der Datenbank',
|
|
'value' => $this->getDatabaseSize(),
|
|
'unit' => 'MB',
|
|
'percentage' => min(100, $this->getDatabaseSize() / 1000 * 100)
|
|
],
|
|
[
|
|
'name' => 'Aktive Sessions',
|
|
'description' => 'Aktive Benutzer-Sessions',
|
|
'value' => $this->getActiveSessions(),
|
|
'unit' => 'Sessions',
|
|
'percentage' => min(100, $this->getActiveSessions() / 100 * 100)
|
|
],
|
|
[
|
|
'name' => 'Durchschnittliche Ladezeit',
|
|
'description' => 'Durchschnittliche Seitenladezeit',
|
|
'value' => $this->getAverageLoadTime(),
|
|
'unit' => 'ms',
|
|
'percentage' => min(100, $this->getAverageLoadTime() / 2000 * 100)
|
|
]
|
|
];
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'data' => $metrics
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'Failed to get metrics: ' . $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Datenbank optimieren
|
|
*/
|
|
public function optimizeDatabase()
|
|
{
|
|
header('Content-Type: application/json');
|
|
|
|
try {
|
|
$result = $this->performance->optimizeDatabase();
|
|
|
|
if ($result) {
|
|
echo json_encode([
|
|
'success' => true,
|
|
'message' => 'Database optimized successfully'
|
|
]);
|
|
} else {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'Failed to optimize database'
|
|
]);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'Database optimization failed: ' . $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache leeren
|
|
*/
|
|
public function clearCache()
|
|
{
|
|
header('Content-Type: application/json');
|
|
|
|
try {
|
|
$result = $this->cache->clear();
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'message' => 'Cache cleared successfully',
|
|
'cleared_items' => $result
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'Failed to clear cache: ' . $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bilder optimieren
|
|
*/
|
|
public function optimizeImages()
|
|
{
|
|
header('Content-Type: application/json');
|
|
|
|
try {
|
|
$imageDir = __DIR__ . '/../../public/img/';
|
|
$optimizedCount = 0;
|
|
|
|
if (is_dir($imageDir)) {
|
|
$images = glob($imageDir . '*.{jpg,jpeg,png,gif}', GLOB_BRACE);
|
|
|
|
foreach ($images as $image) {
|
|
$optimizedPath = str_replace(['.jpg', '.jpeg', '.png', '.gif'], '_optimized.jpg', $image);
|
|
|
|
if ($this->performance->optimizeImage($image, $optimizedPath, [
|
|
'quality' => 85,
|
|
'max_width' => 1200,
|
|
'max_height' => 1200,
|
|
'format' => 'jpeg'
|
|
])) {
|
|
$optimizedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'message' => 'Images optimized successfully',
|
|
'optimized_count' => $optimizedCount
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'Failed to optimize images: ' . $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sitemap generieren
|
|
*/
|
|
public function generateSitemap()
|
|
{
|
|
header('Content-Type: application/json');
|
|
|
|
try {
|
|
$sitemap = $this->generateSitemapContent();
|
|
$sitemapPath = __DIR__ . '/../../public/sitemap.xml';
|
|
|
|
if (file_put_contents($sitemapPath, $sitemap)) {
|
|
echo json_encode([
|
|
'success' => true,
|
|
'message' => 'Sitemap generated successfully'
|
|
]);
|
|
} else {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'Failed to write sitemap file'
|
|
]);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'Failed to generate sitemap: ' . $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache-Größe abrufen
|
|
*/
|
|
private function getCacheSize()
|
|
{
|
|
$cacheDir = __DIR__ . '/../../cache/';
|
|
|
|
if (!is_dir($cacheDir)) {
|
|
return 0;
|
|
}
|
|
|
|
$size = 0;
|
|
$files = glob($cacheDir . '*.cache');
|
|
|
|
foreach ($files as $file) {
|
|
$size += filesize($file);
|
|
}
|
|
|
|
return $size;
|
|
}
|
|
|
|
/**
|
|
* Ausführungszeit-Labels generieren
|
|
*/
|
|
private function getExecutionTimeLabels()
|
|
{
|
|
$labels = [];
|
|
for ($i = 23; $i >= 0; $i--) {
|
|
$labels[] = date('H:i', strtotime("-$i hours"));
|
|
}
|
|
return $labels;
|
|
}
|
|
|
|
/**
|
|
* Ausführungszeit-Daten generieren
|
|
*/
|
|
private function getExecutionTimeData()
|
|
{
|
|
// Simulierte Daten für Demo
|
|
$data = [];
|
|
for ($i = 0; $i < 24; $i++) {
|
|
$data[] = rand(50, 200);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Speicher-Labels generieren
|
|
*/
|
|
private function getMemoryLabels()
|
|
{
|
|
return ['Min', 'Max', 'Durchschnitt', 'Aktuell'];
|
|
}
|
|
|
|
/**
|
|
* Speicher-Daten generieren
|
|
*/
|
|
private function getMemoryData()
|
|
{
|
|
$current = memory_get_usage() / (1024 * 1024);
|
|
return [
|
|
round($current * 0.8, 2),
|
|
round($current * 1.2, 2),
|
|
round($current, 2),
|
|
round($current, 2)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Datenbankverbindungen abrufen
|
|
*/
|
|
private function getDatabaseConnections()
|
|
{
|
|
try {
|
|
$connectionParams = [
|
|
'dbname' => getenv('DB_DATABASE') ?: 'freeshop',
|
|
'user' => getenv('DB_USERNAME') ?: 'freeshop_user',
|
|
'password' => getenv('DB_PASSWORD') ?: 'freeshop_password',
|
|
'host' => getenv('DB_HOST') ?: 'db',
|
|
'driver' => 'pdo_mysql',
|
|
'port' => getenv('DB_PORT') ?: 3306,
|
|
'charset' => 'utf8mb4',
|
|
];
|
|
|
|
$conn = DriverManager::getConnection($connectionParams);
|
|
$stmt = $conn->prepare('SHOW STATUS LIKE "Threads_connected"');
|
|
$stmt->execute();
|
|
$result = $stmt->fetchAssociative();
|
|
|
|
return intval($result['Value'] ?? 0);
|
|
|
|
} catch (Exception $e) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache-Hit-Rate abrufen
|
|
*/
|
|
private function getCacheHitRate()
|
|
{
|
|
$stats = $this->cache->getStats();
|
|
$hits = $stats['hits'] ?? 0;
|
|
$misses = $stats['misses'] ?? 0;
|
|
$total = $hits + $misses;
|
|
|
|
return $total > 0 ? round($hits / $total * 100, 2) : 0;
|
|
}
|
|
|
|
/**
|
|
* Datenbank-Größe abrufen
|
|
*/
|
|
private function getDatabaseSize()
|
|
{
|
|
try {
|
|
$connectionParams = [
|
|
'dbname' => getenv('DB_DATABASE') ?: 'freeshop',
|
|
'user' => getenv('DB_USERNAME') ?: 'freeshop_user',
|
|
'password' => getenv('DB_PASSWORD') ?: 'freeshop_password',
|
|
'host' => getenv('DB_HOST') ?: 'db',
|
|
'driver' => 'pdo_mysql',
|
|
'port' => getenv('DB_PORT') ?: 3306,
|
|
'charset' => 'utf8mb4',
|
|
];
|
|
|
|
$conn = DriverManager::getConnection($connectionParams);
|
|
$stmt = $conn->prepare('
|
|
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb
|
|
FROM information_schema.tables
|
|
WHERE table_schema = ?
|
|
');
|
|
$stmt->execute([getenv('DB_DATABASE') ?: 'freeshop']);
|
|
$result = $stmt->fetchAssociative();
|
|
|
|
return floatval($result['size_mb'] ?? 0);
|
|
|
|
} catch (Exception $e) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Aktive Sessions abrufen
|
|
*/
|
|
private function getActiveSessions()
|
|
{
|
|
$sessionDir = session_save_path() ?: '/tmp';
|
|
$sessions = glob($sessionDir . '/sess_*');
|
|
|
|
return count($sessions);
|
|
}
|
|
|
|
/**
|
|
* Durchschnittliche Ladezeit abrufen
|
|
*/
|
|
private function getAverageLoadTime()
|
|
{
|
|
// Simulierte Daten für Demo
|
|
return rand(100, 500);
|
|
}
|
|
|
|
/**
|
|
* Sitemap-Inhalt generieren
|
|
*/
|
|
private function generateSitemapContent()
|
|
{
|
|
$baseUrl = getenv('BASE_URL') ?: 'https://webshop.local';
|
|
|
|
$xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
|
$xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
|
|
|
// Startseite
|
|
$xml .= ' <url>' . "\n";
|
|
$xml .= ' <loc>' . $baseUrl . '</loc>' . "\n";
|
|
$xml .= ' <lastmod>' . date('Y-m-d') . '</lastmod>' . "\n";
|
|
$xml .= ' <changefreq>daily</changefreq>' . "\n";
|
|
$xml .= ' <priority>1.0</priority>' . "\n";
|
|
$xml .= ' </url>' . "\n";
|
|
|
|
// Produkte
|
|
try {
|
|
$connectionParams = [
|
|
'dbname' => getenv('DB_DATABASE') ?: 'freeshop',
|
|
'user' => getenv('DB_USERNAME') ?: 'freeshop_user',
|
|
'password' => getenv('DB_PASSWORD') ?: 'freeshop_password',
|
|
'host' => getenv('DB_HOST') ?: 'db',
|
|
'driver' => 'pdo_mysql',
|
|
'port' => getenv('DB_PORT') ?: 3306,
|
|
'charset' => 'utf8mb4',
|
|
];
|
|
|
|
$conn = DriverManager::getConnection($connectionParams);
|
|
$stmt = $conn->prepare('SELECT id, updated_at FROM ws_product WHERE active = 1');
|
|
$stmt->execute();
|
|
$products = $stmt->fetchAllAssociative();
|
|
|
|
foreach ($products as $product) {
|
|
$xml .= ' <url>' . "\n";
|
|
$xml .= ' <loc>' . $baseUrl . '/product/' . $product['id'] . '</loc>' . "\n";
|
|
$xml .= ' <lastmod>' . date('Y-m-d', strtotime($product['updated_at'])) . '</lastmod>' . "\n";
|
|
$xml .= ' <changefreq>weekly</changefreq>' . "\n";
|
|
$xml .= ' <priority>0.8</priority>' . "\n";
|
|
$xml .= ' </url>' . "\n";
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
// Ignore database errors
|
|
}
|
|
|
|
$xml .= '</urlset>';
|
|
|
|
return $xml;
|
|
}
|
|
|
|
/**
|
|
* Bytes formatieren
|
|
*/
|
|
private function formatBytes($bytes, $precision = 2)
|
|
{
|
|
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
|
|
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
|
|
$bytes /= 1024;
|
|
}
|
|
|
|
return round($bytes, $precision) . ' ' . $units[$i];
|
|
}
|
|
|
|
/**
|
|
* Template rendern
|
|
*/
|
|
private function renderTemplate($template, $data = [])
|
|
{
|
|
extract($data);
|
|
include __DIR__ . '/../../templates/' . $template;
|
|
}
|
|
}
|