fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
@@ -4,6 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\View\Components;
|
||||
|
||||
use App\Application\Admin\ValueObjects\NavigationMenu;
|
||||
use App\Application\Admin\ValueObjects\NavigationSection;
|
||||
use App\Application\Admin\ValueObjects\NavigationItem;
|
||||
use App\Framework\View\Attributes\ComponentName;
|
||||
use App\Framework\View\Contracts\StaticComponent;
|
||||
use App\Framework\View\Dom\ElementNode;
|
||||
@@ -16,6 +19,7 @@ final readonly class AdminSidebar implements StaticComponent
|
||||
private string $title;
|
||||
private string $logoUrl;
|
||||
private string $currentPath;
|
||||
private ?NavigationMenu $navigationMenu;
|
||||
|
||||
public function __construct(
|
||||
string $content = '',
|
||||
@@ -24,7 +28,10 @@ final readonly class AdminSidebar implements StaticComponent
|
||||
// Extract attributes with defaults
|
||||
$this->title = $attributes['title'] ?? 'Admin Panel';
|
||||
$this->logoUrl = $attributes['logo-url'] ?? '/admin';
|
||||
$this->currentPath = $attributes['current-path'] ?? '/admin';
|
||||
$this->currentPath = $attributes['current-path'] ?? $attributes['currentPath'] ?? '/admin';
|
||||
|
||||
// Parse navigation menu from attributes
|
||||
$this->navigationMenu = $this->parseNavigationMenu($attributes);
|
||||
}
|
||||
|
||||
public function getRootNode(): Node
|
||||
@@ -42,6 +49,15 @@ final readonly class AdminSidebar implements StaticComponent
|
||||
}
|
||||
|
||||
private function buildSidebarContent(): string
|
||||
{
|
||||
$header = $this->buildHeader();
|
||||
$navigation = $this->buildNavigation();
|
||||
$footer = $this->buildFooter();
|
||||
|
||||
return $header . $navigation . $footer;
|
||||
}
|
||||
|
||||
private function buildHeader(): string
|
||||
{
|
||||
return <<<HTML
|
||||
<div class="admin-sidebar__header">
|
||||
@@ -49,10 +65,128 @@ final readonly class AdminSidebar implements StaticComponent
|
||||
<span class="admin-sidebar__title">{$this->title}</span>
|
||||
</a>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
|
||||
private function buildNavigation(): string
|
||||
{
|
||||
if ($this->navigationMenu !== null) {
|
||||
return $this->buildDynamicNavigation();
|
||||
}
|
||||
|
||||
return $this->buildFallbackNavigation();
|
||||
}
|
||||
|
||||
private function buildDynamicNavigation(): string
|
||||
{
|
||||
$html = '<nav class="admin-nav" aria-label="Primary">';
|
||||
|
||||
foreach ($this->navigationMenu->sections as $section) {
|
||||
$html .= $this->renderSection($section);
|
||||
}
|
||||
|
||||
$html .= '</nav>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function renderSection(NavigationSection $section): string
|
||||
{
|
||||
$html = '<div class="admin-nav__section">';
|
||||
|
||||
if ($section->name !== '') {
|
||||
$html .= '<h2 class="admin-nav__section-title">' . htmlspecialchars($section->name) . '</h2>';
|
||||
}
|
||||
|
||||
$html .= '<ul class="admin-nav__list" role="list">';
|
||||
|
||||
foreach ($section->items as $item) {
|
||||
$html .= $this->renderItem($item);
|
||||
}
|
||||
|
||||
$html .= '</ul></div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function renderItem(NavigationItem $item): string
|
||||
{
|
||||
$activeState = $item->isActive($this->currentPath)
|
||||
? 'aria-current="page"'
|
||||
: '';
|
||||
|
||||
$iconHtml = $this->renderIcon($item->icon);
|
||||
|
||||
return <<<HTML
|
||||
<li class="admin-nav__item">
|
||||
<a href="{$this->escape($item->url)}" class="admin-nav__link" {$activeState}>
|
||||
{$iconHtml}
|
||||
<span>{$this->escape($item->name)}</span>
|
||||
</a>
|
||||
</li>
|
||||
HTML;
|
||||
}
|
||||
|
||||
private function renderIcon(?string $icon): string
|
||||
{
|
||||
if ($icon === null || $icon === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Try to match icon name to SVG or use emoji fallback
|
||||
$svgIcon = $this->getSvgIcon($icon);
|
||||
|
||||
if ($svgIcon !== null) {
|
||||
return '<svg class="admin-nav__icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">' . $svgIcon . '</svg>';
|
||||
}
|
||||
|
||||
// Fallback to emoji or text
|
||||
$emojiIcon = $this->getEmojiIcon($icon);
|
||||
return '<span class="admin-nav__icon">' . $emojiIcon . '</span>';
|
||||
}
|
||||
|
||||
private function getSvgIcon(string $iconName): ?string
|
||||
{
|
||||
// Common icon mappings
|
||||
$icons = [
|
||||
'dashboard' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>',
|
||||
'server' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"/>',
|
||||
'database' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/>',
|
||||
'photo' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>',
|
||||
'chart-bar' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>',
|
||||
'code' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>',
|
||||
'bell' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>',
|
||||
'brain' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>',
|
||||
'file' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>',
|
||||
];
|
||||
|
||||
return $icons[strtolower($iconName)] ?? null;
|
||||
}
|
||||
|
||||
private function getEmojiIcon(string $iconName): string
|
||||
{
|
||||
$emoji = [
|
||||
'dashboard' => '📊',
|
||||
'server' => '🖥️',
|
||||
'database' => '💾',
|
||||
'photo' => '🖼️',
|
||||
'chart-bar' => '📈',
|
||||
'code' => '💻',
|
||||
'bell' => '🔔',
|
||||
'brain' => '🧠',
|
||||
'file' => '📄',
|
||||
];
|
||||
|
||||
return $emoji[strtolower($iconName)] ?? '📄';
|
||||
}
|
||||
|
||||
private function buildFallbackNavigation(): string
|
||||
{
|
||||
// Fallback to old hardcoded navigation for backward compatibility
|
||||
return <<<HTML
|
||||
<nav class="admin-nav">
|
||||
<div class="admin-nav__section">
|
||||
<h3 class="admin-nav__section-title">Dashboard</h3>
|
||||
<h2 class="admin-nav__section-title">Dashboard</h2>
|
||||
<ul class="admin-nav__list" role="list">
|
||||
<li class="admin-nav__item">
|
||||
<a href="/admin" class="admin-nav__link" {$this->getActiveState('/admin')}>
|
||||
@@ -62,50 +196,13 @@ final readonly class AdminSidebar implements StaticComponent
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="admin-nav__section">
|
||||
<h3 class="admin-nav__section-title">System</h3>
|
||||
<ul class="admin-nav__list" role="list">
|
||||
<li class="admin-nav__item">
|
||||
<a href="/admin/infrastructure/cache" class="admin-nav__link" {$this->getActiveState('/admin/infrastructure/cache')}>
|
||||
<span class="admin-nav__icon">💾</span>
|
||||
<span>Cache</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="admin-nav__item">
|
||||
<a href="/admin/infrastructure/logs" class="admin-nav__link" {$this->getActiveState('/admin/infrastructure/logs')}>
|
||||
<span class="admin-nav__icon">📝</span>
|
||||
<span>Logs</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="admin-nav__item">
|
||||
<a href="/admin/infrastructure/migrations" class="admin-nav__link" {$this->getActiveState('/admin/infrastructure/migrations')}>
|
||||
<span class="admin-nav__icon">🔄</span>
|
||||
<span>Migrations</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="admin-nav__section">
|
||||
<h3 class="admin-nav__section-title">Content</h3>
|
||||
<ul class="admin-nav__list" role="list">
|
||||
<li class="admin-nav__item">
|
||||
<a href="/admin/images" class="admin-nav__link" {$this->getActiveState('/admin/images')}>
|
||||
<span class="admin-nav__icon">🖼️</span>
|
||||
<span>Images</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="admin-nav__item">
|
||||
<a href="/admin/users" class="admin-nav__link" {$this->getActiveState('/admin/users')}>
|
||||
<span class="admin-nav__icon">👥</span>
|
||||
<span>Users</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
HTML;
|
||||
}
|
||||
|
||||
private function buildFooter(): string
|
||||
{
|
||||
return <<<HTML
|
||||
<div class="admin-sidebar__footer">
|
||||
<div class="admin-sidebar__user">
|
||||
<div class="admin-sidebar__avatar" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 0.875rem;">A</div>
|
||||
@@ -122,4 +219,51 @@ final readonly class AdminSidebar implements StaticComponent
|
||||
{
|
||||
return $path === $this->currentPath ? 'aria-current="page"' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse navigation menu from attributes
|
||||
*/
|
||||
private function parseNavigationMenu(array $attributes): ?NavigationMenu
|
||||
{
|
||||
// Try different attribute names
|
||||
$menuData = $attributes['navigation-menu']
|
||||
?? $attributes['navigation_menu']
|
||||
?? $attributes['navigationMenu']
|
||||
?? null;
|
||||
|
||||
if ($menuData === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle array directly
|
||||
if (is_array($menuData)) {
|
||||
try {
|
||||
return NavigationMenu::fromArray($menuData);
|
||||
} catch (\Exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle JSON string
|
||||
if (is_string($menuData)) {
|
||||
$decoded = json_decode($menuData, true);
|
||||
if (is_array($decoded)) {
|
||||
try {
|
||||
return NavigationMenu::fromArray($decoded);
|
||||
} catch (\Exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML special characters
|
||||
*/
|
||||
private function escape(string $string): string
|
||||
{
|
||||
return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user