diff --git a/CHANGELOG.md b/CHANGELOG.md index 48943f9..dd6adc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,22 @@ Dieses Projekt verwendet [Semantic Versioning](https://semver.org/lang/de/). --- +## [0.4.0] - 2026-06-29 + +### Added +- Einstellungen → Layout: Seitenname, Logo-Upload, Button-Farbe (Colorpicker), Dark/Light-Mode +- Settings-Tabelle als Key-Value-Store in der Datenbank +- SettingsService mit Cache-Layer (automatische Invalidierung bei Änderung) +- SettingsServiceProvider: Einstellungen werden global in alle Views injiziert +- Dark-Mode via `dark`-Klasse auf HTML-Element (Tailwind CSS) +- CSS-Variable `--color-primary` für dynamische Button-Farbe +- Hilfe-Menü auf Ebene 0 (Dropdown) für alle eingeloggten Benutzer +- Hilfe → Handbuch: Übersicht über Rollen, Funktionen, Bedienung +- Hilfe → Changelog: Changelog direkt im Browser lesbar +- Navigation: Einstellungen-Dropdown um Layout erweitert + +--- + ## [0.3.0] - 2026-06-27 ### Added @@ -46,7 +62,8 @@ Dieses Projekt verwendet [Semantic Versioning](https://semver.org/lang/de/). - Grundlegende PHP-Projektstruktur (public/, src/, config/) - composer.json, .gitignore, README.md -[Unreleased]: http://localhost:3000/admin/Network-MGMT/compare/v0.3.0...HEAD +[Unreleased]: http://localhost:3000/admin/Network-MGMT/compare/v0.4.0...HEAD +[0.4.0]: http://localhost:3000/admin/Network-MGMT/compare/v0.3.0...v0.4.0 [0.3.0]: http://localhost:3000/admin/Network-MGMT/compare/v0.2.0...v0.3.0 [0.2.0]: http://localhost:3000/admin/Network-MGMT/compare/v0.1.0...v0.2.0 [0.1.0]: http://localhost:3000/admin/Network-MGMT/releases/tag/v0.1.0 diff --git a/app/Http/Controllers/Admin/LayoutController.php b/app/Http/Controllers/Admin/LayoutController.php new file mode 100644 index 0000000..13e524c --- /dev/null +++ b/app/Http/Controllers/Admin/LayoutController.php @@ -0,0 +1,56 @@ + $this->settings->all(), + ]); + } + + public function update(Request $request): RedirectResponse + { + $validated = $request->validate([ + 'site_name' => ['required', 'string', 'max:100'], + 'button_color' => ['required', 'string', 'regex:/^#[0-9a-fA-F]{6}$/'], + 'theme_mode' => ['required', 'in:light,dark'], + 'site_logo' => ['nullable', 'image', 'max:2048'], + ]); + + // Logo-Upload verarbeiten + if ($request->hasFile('site_logo')) { + $path = $request->file('site_logo')->store('logos', 'public'); + $this->settings->set('site_logo', $path); + } + + $this->settings->setMany([ + 'site_name' => $validated['site_name'], + 'button_color' => $validated['button_color'], + 'theme_mode' => $validated['theme_mode'], + ]); + + return redirect() + ->route('admin.layout.index') + ->with('success', 'Layout-Einstellungen gespeichert.'); + } + + public function removeLogo(): RedirectResponse + { + $this->settings->set('site_logo', ''); + + return redirect() + ->route('admin.layout.index') + ->with('success', 'Logo entfernt.'); + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 9cfcc13..b27bbb6 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -47,7 +47,7 @@ class UserController extends Controller return redirect() ->route('admin.users.index') - ->with('success', "Benutzer „{$user->name}" wurde angelegt."); + ->with('success', "Benutzer \"{$user->name}\" wurde angelegt."); } public function edit(User $user): View @@ -79,7 +79,7 @@ class UserController extends Controller return redirect() ->route('admin.users.index') - ->with('success', "Benutzer „{$user->name}" wurde aktualisiert."); + ->with('success', "Benutzer \"{$user->name}\" wurde aktualisiert."); } public function destroy(User $user): RedirectResponse @@ -95,6 +95,6 @@ class UserController extends Controller return redirect() ->route('admin.users.index') - ->with('success', "Benutzer „{$name}" wurde gelöscht."); + ->with('success', "Benutzer \"{$name}\" wurde gelöscht."); } } diff --git a/app/Http/Controllers/HelpController.php b/app/Http/Controllers/HelpController.php new file mode 100644 index 0000000..32cf491 --- /dev/null +++ b/app/Http/Controllers/HelpController.php @@ -0,0 +1,23 @@ +value('value') ?? $default; + } + + public static function set(string $key, mixed $value): void + { + static::updateOrCreate(['key' => $key], ['value' => $value]); + } + + public static function allAsArray(): array + { + return static::pluck('value', 'key')->toArray(); + } +} diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php new file mode 100644 index 0000000..a7b57aa --- /dev/null +++ b/app/Providers/SettingsServiceProvider.php @@ -0,0 +1,33 @@ +app->singleton(SettingsService::class); + } + + public function boot(): void + { + // Settings nur laden wenn die Tabelle existiert (z.B. vor Migration schützen) + if (!$this->app->runningInConsole() && Schema::hasTable('settings')) { + $settings = $this->app->make(SettingsService::class)->all(); + + View::share('appSettings', $settings); + } else { + View::share('appSettings', [ + 'site_name' => config('app.name'), + 'site_logo' => '', + 'button_color' => '#4f46e5', + 'theme_mode' => 'light', + ]); + } + } +} diff --git a/app/Services/SettingsService.php b/app/Services/SettingsService.php new file mode 100644 index 0000000..bdd644c --- /dev/null +++ b/app/Services/SettingsService.php @@ -0,0 +1,36 @@ + Setting::allAsArray()); + } + + public function get(string $key, mixed $default = null): mixed + { + return $this->all()[$key] ?? $default; + } + + public function set(string $key, mixed $value): void + { + Setting::set($key, $value); + Cache::forget(self::CACHE_KEY); + } + + public function setMany(array $data): void + { + foreach ($data as $key => $value) { + Setting::set($key, $value); + } + Cache::forget(self::CACHE_KEY); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 7a2848f..c936581 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -6,13 +6,20 @@ use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Http\Request; return Application::configure(basePath: dirname(__DIR__)) + ->withProviders([ + App\Providers\SettingsServiceProvider::class, + ]) ->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - // + $middleware->alias([ + 'role' => \Spatie\Permission\Middleware\RoleMiddleware::class, + 'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class, + 'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class, + ]); }) ->withExceptions(function (Exceptions $exceptions): void { $exceptions->shouldRenderJsonWhen( diff --git a/database/migrations/2026_06_29_000001_create_settings_table.php b/database/migrations/2026_06_29_000001_create_settings_table.php new file mode 100644 index 0000000..5cc2756 --- /dev/null +++ b/database/migrations/2026_06_29_000001_create_settings_table.php @@ -0,0 +1,23 @@ +id(); + $table->string('key')->unique(); + $table->text('value')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('settings'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 6ccb4cb..d754eb8 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -10,6 +10,7 @@ class DatabaseSeeder extends Seeder { $this->call([ RolesAndPermissionsSeeder::class, + SettingsSeeder::class, ]); } } diff --git a/database/seeders/SettingsSeeder.php b/database/seeders/SettingsSeeder.php new file mode 100644 index 0000000..3efad48 --- /dev/null +++ b/database/seeders/SettingsSeeder.php @@ -0,0 +1,25 @@ + 'Network-MGMT', + 'site_logo' => '', + 'button_color' => '#4f46e5', // Indigo-600 + 'theme_mode' => 'light', // light | dark + ]; + + foreach ($defaults as $key => $value) { + Setting::firstOrCreate(['key' => $key], ['value' => $value]); + } + + $this->command->info('Default settings seeded.'); + } +} diff --git a/resources/views/admin/layout/index.blade.php b/resources/views/admin/layout/index.blade.php new file mode 100644 index 0000000..6addc4f --- /dev/null +++ b/resources/views/admin/layout/index.blade.php @@ -0,0 +1,137 @@ + + +

+ Layout-Einstellungen +

+
+ +
+
+ + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + +
+ @csrf + @method('PUT') + + {{-- Site-Name --}} +
+

+ Allgemein +

+
+ + +

Wird im Browser-Tab und in der Navigation angezeigt.

+ +
+
+ + {{-- Logo --}} +
+

+ Logo +

+ + @if(!empty($settings['site_logo'])) + + @endif + +
+ + + +
+
+ + {{-- Button-Farbe --}} +
+

+ Button-Farbe +

+
+ +
+

Primärfarbe für Buttons und Akzente

+

+ {{ old('button_color', $settings['button_color'] ?? '#4f46e5') }} +

+
+ +
+ +
+ + {{-- Dark / Light Mode --}} +
+

+ Erscheinungsbild +

+
+ + +
+
+ + {{-- Speichern --}} +
+ +
+
+
+
+ + +
diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php index 03d58ea..73da39f 100644 --- a/resources/views/admin/users/index.blade.php +++ b/resources/views/admin/users/index.blade.php @@ -1,19 +1,21 @@ -
-

- Benutzerverwaltung -

- - + Neuer Benutzer - -
+

+ Benutzerverwaltung +

+ {{-- Toolbar --}} + + {{-- Flash-Meldungen --}} @if(session('success'))
diff --git a/resources/views/help/changelog.blade.php b/resources/views/help/changelog.blade.php new file mode 100644 index 0000000..69dc369 --- /dev/null +++ b/resources/views/help/changelog.blade.php @@ -0,0 +1,42 @@ + + +

+ Changelog +

+
+ +
+
+
+
+ @php + // Einfaches Markdown-to-HTML für den Changelog + $lines = explode("\n", $content); + $html = ''; + foreach ($lines as $line) { + if (str_starts_with($line, '## ')) { + $html .= '

' + . e(substr($line, 3)) . '

'; + } elseif (str_starts_with($line, '### ')) { + $html .= '

' + . e(substr($line, 4)) . '

'; + } elseif (str_starts_with($line, '# ')) { + $html .= '

' + . e(substr($line, 2)) . '

'; + } elseif (str_starts_with($line, '- ')) { + $html .= '
  • ' + . e(substr($line, 2)) . '
  • '; + } elseif (trim($line) === '---') { + $html .= '
    '; + } elseif (trim($line) !== '') { + $html .= '

    ' + . e($line) . '

    '; + } + } + @endphp + {!! $html !!} +
    +
    +
    +
    +
    diff --git a/resources/views/help/manual.blade.php b/resources/views/help/manual.blade.php new file mode 100644 index 0000000..a4edca1 --- /dev/null +++ b/resources/views/help/manual.blade.php @@ -0,0 +1,77 @@ + + +

    + Handbuch +

    +
    + +
    +
    +
    + +
    +

    Erste Schritte

    +

    + Network-MGMT ist eine webbasierte Verwaltungsplattform für Netzwerk-Ressourcen + mit rollenbasierter Zugriffskontrolle. +

    +
    + +
    +

    Rollen & Rechte

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    RolleBeschreibungBerechtigungen
    adminAdministratorVollzugriff auf alle Funktionen
    managerManagerNetzwerk lesen, anlegen, bearbeiten; Benutzer lesen
    userBenutzerNetzwerk lesen
    +
    +
    + +
    +

    Benutzerverwaltung

    +

    + Unter Einstellungen → Benutzerverwaltung können Administratoren + neue Benutzer anlegen, bestehende bearbeiten und Rollen zuweisen. + Der eigene Account kann nicht gelöscht werden. +

    +
    + +
    +

    Layout-Einstellungen

    +

    + Unter Einstellungen → Layout kann der Seitenname, das Logo, + die Button-Farbe sowie der Dark/Light-Mode konfiguriert werden. + Änderungen werden sofort für alle Benutzer wirksam. +

    +
    + +
    + Network-MGMT · Version {{ config('app.version', '0.4.0') }} +
    + +
    +
    +
    +
    diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index c5ff315..bb867b1 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -1,11 +1,12 @@ - + - {{ config('app.name', 'Laravel') }} + {{ $appSettings['site_name'] ?? config('app.name', 'Network-MGMT') }} @@ -13,14 +14,28 @@ @vite(['resources/css/app.css', 'resources/js/app.js']) + + + -
    +
    @include('layouts.navigation') @isset($header) -
    +
    {{ $header }}
    diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index dbe7983..f09298c 100644 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -15,11 +15,50 @@ {{ __('Dashboard') }} + @role('admin') - - Benutzerverwaltung - + {{-- Einstellungen-Dropdown --}} + + + + + + + 👥 Benutzerverwaltung + + + 🎨 Layout + + + @endrole + + {{-- Hilfe-Dropdown --}} + + + + + + + 📖 Handbuch + + + 📋 Changelog + + +
    @@ -75,11 +114,28 @@ {{ __('Dashboard') }} + @role('admin') - - Benutzerverwaltung - +
    +
    Einstellungen
    + +   👥 Benutzerverwaltung + + +   🎨 Layout + +
    @endrole + +
    +
    Hilfe
    + +   📖 Handbuch + + +   📋 Changelog + +
    diff --git a/routes/web.php b/routes/web.php index 056fc97..3c861d6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,7 +1,9 @@ group(function () { Route::get('/', fn() => redirect()->route('admin.users.index')); Route::resource('users', AdminUserController::class); + Route::get('layout', [AdminLayoutController::class, 'index'])->name('layout.index'); + Route::put('layout', [AdminLayoutController::class, 'update'])->name('layout.update'); + Route::get('layout/remove-logo', [AdminLayoutController::class, 'removeLogo'])->name('layout.removeLogo'); + }); + +// Hilfe-Bereich – für alle eingeloggten Benutzer +Route::prefix('help') + ->name('help.') + ->middleware(['auth']) + ->group(function () { + Route::get('manual', [HelpController::class, 'manual'])->name('manual'); + Route::get('changelog', [HelpController::class, 'changelog'])->name('changelog'); }); require __DIR__.'/auth.php';