From ad893b48a74d03aeb4728478edaeb0e05ac15ffe Mon Sep 17 00:00:00 2001 From: SEB Fotografie - soeren Date: Thu, 7 Aug 2025 14:34:00 +0200 Subject: [PATCH] ConnectionTest im Backend funktioniert jetzt --- app/Api/Plugins/ApiPluginInterface.php | 1 + app/Api/Plugins/ComfyUi.php | 12 + app/Api/Plugins/RunwareAi.php | 27 +++ .../Resources/ApiProviderResource.php | 225 ++++++++++++------ .../Pages/CreateApiProvider.php | 9 + .../Pages/EditApiProvider.php | 9 + app/Filament/Resources/SettingResource.php | 22 +- app/Filament/Resources/StyleResource.php | 92 ++++--- .../StyleResource/Pages/ListStyles.php | 2 + .../Admin/NavigationStateController.php | 15 ++ app/Http/Controllers/Api/StyleController.php | 11 +- app/Providers/Filament/AdminPanelProvider.php | 3 + composer.json | 3 +- composer.lock | 2 +- config/app.php | 1 + config/blade-icons.php | 183 ++++++++++++++ ..._114358_add_sort_order_to_styles_table.php | 28 +++ resources/js/Pages/Home.vue | 12 +- resources/lang/de/filament.php | 14 ++ resources/lang/en/filament.php | 16 +- routes/api.php | 2 + 21 files changed, 580 insertions(+), 109 deletions(-) create mode 100644 app/Http/Controllers/Admin/NavigationStateController.php create mode 100644 config/blade-icons.php create mode 100644 database/migrations/2025_08_06_114358_add_sort_order_to_styles_table.php diff --git a/app/Api/Plugins/ApiPluginInterface.php b/app/Api/Plugins/ApiPluginInterface.php index 97cb068..55f40b0 100644 --- a/app/Api/Plugins/ApiPluginInterface.php +++ b/app/Api/Plugins/ApiPluginInterface.php @@ -12,4 +12,5 @@ interface ApiPluginInterface public function getStatus(string $imageUUID): array; public function getProgress(string $imageUUID): array; public function processImageStyleChange(\App\Models\Image $image, \App\Models\Style $style): array; + public function testConnection(array $data): bool; } \ No newline at end of file diff --git a/app/Api/Plugins/ComfyUi.php b/app/Api/Plugins/ComfyUi.php index f20f08d..cbc754b 100644 --- a/app/Api/Plugins/ComfyUi.php +++ b/app/Api/Plugins/ComfyUi.php @@ -227,4 +227,16 @@ class ComfyUi implements ApiPluginInterface usleep(500000); // Wait for 0.5 seconds before polling again } } + + public function testConnection(array $data): bool + { + $apiUrl = rtrim($data['api_url'], '/'); + try { + $response = Http::timeout(5)->get($apiUrl . '/queue'); + return $response->successful(); + } catch (\Exception $e) { + $this->logError('ComfyUI connection test failed.', ['error' => $e->getMessage()]); + return false; + } + } } \ No newline at end of file diff --git a/app/Api/Plugins/RunwareAi.php b/app/Api/Plugins/RunwareAi.php index 67822be..4031ca0 100644 --- a/app/Api/Plugins/RunwareAi.php +++ b/app/Api/Plugins/RunwareAi.php @@ -91,6 +91,33 @@ class RunwareAi implements ApiPluginInterface return $result; } + public function testConnection(array $data): bool + { + $apiUrl = rtrim($data['api_url'], '/'); + $token = $data['token'] ?? null; + + if (!$apiUrl || !$token) { + $this->logError('RunwareAI connection test failed: API URL or Token missing.'); + return false; + } + + try { + $response = Http::withHeaders([ + 'Authorization' => 'Bearer ' . $token, + 'Accept' => 'application/json', + ])->timeout(5)->post($apiUrl, [ + [ + 'taskType' => 'ping', + ] + ]); + + return $response->successful(); + } catch (\Exception $e) { + $this->logError('RunwareAI connection test failed.', ['error' => $e->getMessage()]); + return false; + } + } + private function upload(string $imagePath): array { $this->logInfo('Attempting to upload image to RunwareAI.', ['image_path' => $imagePath]); diff --git a/app/Filament/Resources/ApiProviderResource.php b/app/Filament/Resources/ApiProviderResource.php index 312feb6..65774f1 100644 --- a/app/Filament/Resources/ApiProviderResource.php +++ b/app/Filament/Resources/ApiProviderResource.php @@ -17,9 +17,15 @@ use Filament\Tables\Columns\TextColumn; use Filament\Forms\Components\Select; use Illuminate\Support\Facades\File; use App\Api\Plugins\ApiPluginInterface; - use Filament\Forms\Components\Toggle; use Filament\Tables\Columns\IconColumn; +use Filament\Forms\Components\Actions; +use Filament\Forms\Components\Actions\Action; +use Filament\Forms\Components\ViewField; +use Filament\Notifications\Notification; +use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Blade; +use Illuminate\Support\Facades\Log; class ApiProviderResource extends Resource { @@ -30,41 +36,130 @@ class ApiProviderResource extends Resource public static function form(Form $form): Form { $plugins = self::getAvailablePlugins(); - + $livewire = $form->getLivewire(); return $form ->schema([ - TextInput::make('name') - ->label(__('filament.resource.api_provider.form.name')) - ->required() - ->maxLength(255), - Toggle::make('enabled') - ->label(__('filament.resource.api_provider.form.enabled')) - ->default(true), - TextInput::make('api_url') - ->label(__('filament.resource.api_provider.form.api_url')) - ->required() - ->url() - ->maxLength(255), - TextInput::make('username') - ->label(__('filament.resource.api_provider.form.username')) - ->nullable() - ->maxLength(255), - TextInput::make('password') - ->label(__('filament.resource.api_provider.form.password')) - ->password() - ->nullable() - ->maxLength(255), - TextInput::make('token') - ->label(__('filament.resource.api_provider.form.token')) - ->nullable() - ->maxLength(255), - Select::make('plugin') - ->options($plugins) - ->nullable() - ->label(__('filament.resource.api_provider.form.plugin')), + Forms\Components\Section::make() + ->schema([ + TextInput::make('name') + ->label(__('filament.resource.api_provider.form.name')) + ->required() + ->maxLength(255), + Toggle::make('enabled') + ->label(__('filament.resource.api_provider.form.enabled')) + ->default(true), + TextInput::make('api_url') + ->label(__('filament.resource.api_provider.form.api_url')) + ->required() + ->url() + ->maxLength(255), + TextInput::make('username') + ->label(__('filament.resource.api_provider.form.username')) + ->nullable() + ->maxLength(255), + TextInput::make('password') + ->label(__('filament.resource.api_provider.form.password')) + ->password() + ->nullable() + ->maxLength(255), + TextInput::make('token') + ->label(__('filament.resource.api_provider.form.token')) + ->nullable() + ->maxLength(255), + Select::make('plugin') + ->options($plugins) + ->nullable() + ->label(__('filament.resource.api_provider.form.plugin')), + Actions::make([ + Action::make('test_connection') + ->label(__('filament.resource.api_provider.action.test_connection')) + ->icon(function (\Livewire\Component $livewire) { + return match ($livewire->testResultState) { + 'success' => 'heroicon-o-check-circle', + 'failed' => 'heroicon-o-x-circle', + default => 'heroicon-o-link', + }; + }) + ->color(function (\Livewire\Component $livewire) { + return match ($livewire->testResultState) { + 'success' => 'success', + 'failed' => 'danger', + default => 'gray', + }; + }) + ->action(function (array $data, Forms\Components\Component $component, \Livewire\Component $livewire) { + $formData = $component->getLivewire()->form->getState(); + $apiUrl = str_replace('127.0.0.1', 'localhost', $formData['api_url'] ?? null); + $plugin = $formData['plugin'] ?? null; + $username = $formData['username'] ?? null; + $password = $formData['password'] ?? null; + $token = $formData['token'] ?? null; + + try { + $http = Http::timeout(25); + + if ($username && $password) { + $http->withBasicAuth($username, $password); + } elseif ($token) { + $http->withToken($token); + } + + $response = $http->get($apiUrl); + + if ($response->successful()) { + Log::info('External API connection successful.', [ + 'api_url' => $apiUrl, + 'plugin' => $plugin, + ]); + Notification::make() + ->title(__('filament.resource.api_provider.notification.connection_successful')) + ->success() + ->send(); + $component->getLivewire()->dispatch('testConnectionFinished', result: 'success'); + } else { + Log::warning('External API connection failed: Non-successful response.', [ + 'api_url' => $apiUrl, + 'plugin' => $plugin, + 'status' => $response->status(), + 'response_body' => $response->body(), + ]); + Notification::make() + ->title(__('filament.resource.api_provider.notification.connection_failed')) + ->body($response->json('message', 'An unknown error occurred.')) + ->danger() + ->send(); + $component->getLivewire()->dispatch('testConnectionFinished', result: 'failed'); + } + } catch (\Illuminate\Http\Client\RequestException $e) { + Log::error('External API connection failed: Timeout or network error.', [ + 'api_url' => $apiUrl, + 'plugin' => $plugin, + 'error_message' => $e->getMessage(), + ]); + Notification::make() + ->title(__('filament.resource.api_provider.notification.connection_failed')) + ->body('Timeout or network error: ' . $e->getMessage()) + ->danger() + ->send(); + $component->getLivewire()->dispatch('testConnectionFinished', result: 'failed'); + } catch (\Exception $e) { + Log::error('External API connection failed: An unexpected error occurred.', [ + 'api_url' => $apiUrl, + 'plugin' => $plugin, + 'error_message' => $e->getMessage(), + ]); + Notification::make() + ->title(__('filament.resource.api_provider.notification.connection_failed')) + ->body('An unexpected error occurred: ' . $e->getMessage()) + ->danger() + ->send(); + $component->getLivewire()->dispatch('testConnectionFinished', result: 'failed'); + } + }), + ]), + ]) ]); } - public static function table(Table $table): Table { return $table @@ -103,42 +198,38 @@ class ApiProviderResource extends Resource Tables\Actions\CreateAction::make(), ]); } - - public static function getRelations(): array - { - return [ - // - ]; + public static function getRelations(): array + { + return [ + // + ]; } - - public static function getPages(): array - { - return [ - 'index' => Pages\ListApiProviders::route('/'), - 'create' => Pages\CreateApiProvider::route('/create'), - 'edit' => Pages\EditApiProvider::route('/{record}/edit'), - ]; + public static function getPages(): array + { + return [ + 'index' => Pages\ListApiProviders::route('/'), + 'create' => Pages\CreateApiProvider::route('/create'), + 'edit' => Pages\EditApiProvider::route('/{record}/edit'), + ]; } - - protected static function getAvailablePlugins(): array - { - $plugins = []; - $path = app_path('Api/Plugins'); - $files = File::files($path); - - foreach ($files as $file) { - $filename = $file->getFilenameWithoutExtension(); - if (in_array($filename, ['ApiPluginInterface', 'PluginLoader'])) { - continue; + protected static function getAvailablePlugins(): array + { + $plugins = []; + $path = app_path('Api/Plugins'); + $files = File::files($path); + + foreach ($files as $file) { + $filename = $file->getFilenameWithoutExtension(); + if (in_array($filename, ['ApiPluginInterface', 'PluginLoader'])) { + continue; + } + $class = "App\Api\Plugins\\" . $filename; + if (class_exists($class) && in_array(ApiPluginInterface::class, class_implements($class))) { + // Do not instantiate here, just get identifier and name if possible statically or by convention + // For now, we'll use filename as identifier and name + $plugins[$filename] = $filename; } - - $class = 'App\\Api\\Plugins\\' . $filename; - if (class_exists($class) && in_array(ApiPluginInterface::class, class_implements($class))) { - // Do not instantiate here, just get identifier and name if possible statically or by convention - // For now, we'll use filename as identifier and name - $plugins[$filename] = $filename; - } - } - return $plugins; - } + } + return $plugins; + } } \ No newline at end of file diff --git a/app/Filament/Resources/ApiProviderResource/Pages/CreateApiProvider.php b/app/Filament/Resources/ApiProviderResource/Pages/CreateApiProvider.php index 219541f..4b2128e 100644 --- a/app/Filament/Resources/ApiProviderResource/Pages/CreateApiProvider.php +++ b/app/Filament/Resources/ApiProviderResource/Pages/CreateApiProvider.php @@ -5,11 +5,20 @@ namespace App\Filament\Resources\ApiProviderResource\Pages; use App\Filament\Resources\ApiProviderResource; use Filament\Actions; use Filament\Resources\Pages\CreateRecord; +use Livewire\Attributes\On; class CreateApiProvider extends CreateRecord { + public $testResultState = 'none'; + protected static string $resource = ApiProviderResource::class; + #[On('testConnectionFinished')] + public function onTestConnectionFinished($result) + { + $this->testResultState = $result; + } + protected function getRedirectUrl(): string { return $this->getResource()::getUrl('index'); diff --git a/app/Filament/Resources/ApiProviderResource/Pages/EditApiProvider.php b/app/Filament/Resources/ApiProviderResource/Pages/EditApiProvider.php index 249d07c..4e24e4b 100644 --- a/app/Filament/Resources/ApiProviderResource/Pages/EditApiProvider.php +++ b/app/Filament/Resources/ApiProviderResource/Pages/EditApiProvider.php @@ -5,11 +5,20 @@ namespace App\Filament\Resources\ApiProviderResource\Pages; use App\Filament\Resources\ApiProviderResource; use Filament\Actions; use Filament\Resources\Pages\EditRecord; +use Livewire\Attributes\On; class EditApiProvider extends EditRecord { + public $testResultState = 'none'; + protected static string $resource = ApiProviderResource::class; + #[On('testConnectionFinished')] + public function onTestConnectionFinished($result) + { + $this->testResultState = $result; + } + protected function getHeaderActions(): array { return [ diff --git a/app/Filament/Resources/SettingResource.php b/app/Filament/Resources/SettingResource.php index 688b3cb..ce0f88f 100644 --- a/app/Filament/Resources/SettingResource.php +++ b/app/Filament/Resources/SettingResource.php @@ -31,11 +31,27 @@ class SettingResource extends Resource ->maxLength(255) ->hiddenOn('edit'), Forms\Components\Fieldset::make() - ->label(fn (?Setting $record) => $record ? $record->key : __('New Setting')) + ->label(fn (?Setting $record) => $record ? __('filament.resource.setting.form.' . $record->key) : __('New Setting')) ->schema([ TextInput::make('value') - ->label(__('Value')) - ->disableLabel() + ->label(__('Bitte Wert eingeben')) + ->disableLabel(fn (?Setting $record) => $record && ($record->key !== 'image_refresh_interval' && $record->key !== 'new_image_timespan_minutes' && $record->key !== 'gallery_heading')) + ->formatStateUsing(function (?string $state, ?Setting $record) { + if ($record && $record->key === 'image_refresh_interval') { + return (int)$state / 1000; + } else if ($record && $record->key === 'new_image_timespan_minutes') { + return (int)$state; + } + return $state; + }) + ->dehydrateStateUsing(function (?string $state, ?Setting $record) { + if ($record && $record->key === 'image_refresh_interval') { + return (int)$state * 1000; + } else if ($record && $record->key === 'new_image_timespan_minutes') { + return (int)$state; + } + return $state; + }) ]) ]); } diff --git a/app/Filament/Resources/StyleResource.php b/app/Filament/Resources/StyleResource.php index 80af72b..dd58eff 100644 --- a/app/Filament/Resources/StyleResource.php +++ b/app/Filament/Resources/StyleResource.php @@ -27,47 +27,70 @@ class StyleResource extends Resource public static function form(Form $form): Form { return $form + ->columns('full') ->schema([ - TextInput::make('title') - ->label(__('filament.resource.style.form.title')) - ->required() - ->maxLength(255), - Toggle::make('enabled') - ->label(__('filament.resource.style.form.enabled')) - ->default(true), - Textarea::make('prompt') - ->label(__('filament.resource.style.form.prompt')) - ->required() - ->rows(5), - Textarea::make('description') - ->label(__('filament.resource.style.form.description')) - ->required() - ->rows(5), - FileUpload::make('preview_image') - ->label(__('filament.resource.style.form.preview_image')) - ->disk('public') - ->directory('style_previews') - ->image() - ->imageEditor() - ->required() - ->rules(['mimes:jpeg,png,bmp,gif,webp']), - Textarea::make('parameters') - ->label(__('filament.resource.style.form.parameters')) - ->nullable() - ->rows(15) - ->json() - ->helperText(__('filament.resource.style.form.parameters_help')) - ->formatStateUsing(fn (?array $state): ?string => $state ? json_encode($state, JSON_PRETTY_PRINT) : null), - Select::make('ai_model_id') - ->relationship('aiModel', 'name') - ->label(__('filament.resource.style.form.ai_model')) - ->required(), + Forms\Components\Tabs::make('Style Details') + ->tabs([ + Forms\Components\Tabs\Tab::make('General') + ->schema([ + Forms\Components\Grid::make(2) + ->schema([ + TextInput::make('title') + ->label(__('filament.resource.style.form.title')) + ->required() + ->maxLength(255), + Toggle::make('enabled') + ->label(__('filament.resource.style.form.enabled')) + ->default(true), + ]), + Forms\Components\Grid::make(2) + ->schema([ + Textarea::make('prompt') + ->label(__('filament.resource.style.form.prompt')) + ->required() + ->rows(5), + Textarea::make('description') + ->label(__('filament.resource.style.form.description')) + ->required() + ->rows(5), + ]), + Select::make('ai_model_id') + ->relationship('aiModel', 'name') + ->label(__('filament.resource.style.form.ai_model')) + ->required(), + FileUpload::make('preview_image') + ->label(__('filament.resource.style.form.preview_image')) + ->disk('public') + ->directory('style_previews') + ->image() + ->imageEditor() + ->required() + ->rules(['mimes:jpeg,png,bmp,gif,webp']), + ]), + Forms\Components\Tabs\Tab::make('Details') + ->schema([ + + Forms\Components\TextInput::make('sort_order') + ->numeric() + ->default(0) + ->label(__('filament.resource.style.form.sort_order')), + Textarea::make('parameters') + ->label(__('filament.resource.style.form.parameters')) + ->nullable() + ->rows(15) + ->json() + ->helperText(__('filament.resource.style.form.parameters_help')) + ->formatStateUsing(fn (?array $state): ?string => $state ? json_encode($state, JSON_PRETTY_PRINT) : null), + ]), + ]), ]); } public static function table(Table $table): Table { return $table + ->defaultSort('sort_order') + ->reorderable('sort_order') ->columns([ TextColumn::make('title')->label(__('filament.resource.style.table.title'))->searchable()->sortable(), IconColumn::make('enabled') @@ -75,6 +98,7 @@ class StyleResource extends Resource ->boolean(), TextColumn::make('aiModel.name')->label(__('filament.resource.style.table.ai_model'))->searchable()->sortable(), ImageColumn::make('preview_image')->label(__('filament.resource.style.table.preview_image'))->disk('public'), + TextColumn::make('sort_order')->label(__('filament.resource.style.table.sort_order'))->sortable(), ]) ->filters([ SelectFilter::make('ai_model') diff --git a/app/Filament/Resources/StyleResource/Pages/ListStyles.php b/app/Filament/Resources/StyleResource/Pages/ListStyles.php index 4413bb2..2bfff12 100644 --- a/app/Filament/Resources/StyleResource/Pages/ListStyles.php +++ b/app/Filament/Resources/StyleResource/Pages/ListStyles.php @@ -5,11 +5,13 @@ namespace App\Filament\Resources\StyleResource\Pages; use App\Filament\Resources\StyleResource; use Filament\Actions; use Filament\Resources\Pages\ListRecords; +use Filament\Tables\Concerns\CanReorderRecords; use Illuminate\Contracts\Pagination\Paginator; use Illuminate\Database\Eloquent\Builder; class ListStyles extends ListRecords { + use CanReorderRecords; protected static string $resource = StyleResource::class; protected function getHeaderActions(): array diff --git a/app/Http/Controllers/Admin/NavigationStateController.php b/app/Http/Controllers/Admin/NavigationStateController.php new file mode 100644 index 0000000..4b62b77 --- /dev/null +++ b/app/Http/Controllers/Admin/NavigationStateController.php @@ -0,0 +1,15 @@ +json(['message' => 'Navigation state stored.']); + } +} diff --git a/app/Http/Controllers/Api/StyleController.php b/app/Http/Controllers/Api/StyleController.php index 18a963d..c038e1f 100644 --- a/app/Http/Controllers/Api/StyleController.php +++ b/app/Http/Controllers/Api/StyleController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\Style; +use App\Models\Setting; use Illuminate\Http\Request; class StyleController extends Controller @@ -18,7 +19,8 @@ class StyleController extends Controller $query->where('enabled', true); }); }) - ->get(); + ->get() + ->sortBy('sort_order'); if ($styles->isEmpty()) { return response()->json(['message' => __('api.no_styles_available')], 404); @@ -26,4 +28,11 @@ class StyleController extends Controller return response()->json($styles); } + + public function getImageRefreshInterval() + { + $interval = Setting::where('key', 'image_refresh_interval')->first(); + + return response()->json(['interval' => $interval ? (int)$interval->value / 1000 : 5]); + } } \ No newline at end of file diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index f22776a..532fb93 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -34,6 +34,9 @@ class AdminPanelProvider extends PanelProvider 'primary' => Color::Amber, ]) ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') + ->resources([ + \App\Filament\Resources\ApiProviderResource::class, + ]) ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') ->pages([ Pages\Dashboard::class, diff --git a/composer.json b/composer.json index 1384c5e..caca307 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,8 @@ "require": { "php": "^8.3", "amphp/websocket-client": "^2.0", - "filament/filament": "^3.2", + "blade-ui-kit/blade-icons": "^1.8", + "filament/filament": "^3.3", "guzzlehttp/guzzle": "^7.2", "inertiajs/inertia-laravel": "^1.0", "laravel/breeze": "^2.0", diff --git a/composer.lock b/composer.lock index 90c32bc..b3e0e8c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0b1ebe92702b13e1e049bf2d7f192913", + "content-hash": "c6dbfef94da04e316c0664ef987fb127", "packages": [ { "name": "amphp/amp", diff --git a/config/app.php b/config/app.php index 2b92cb9..d5ae817 100644 --- a/config/app.php +++ b/config/app.php @@ -185,6 +185,7 @@ return [ /* * Package Service Providers... */ + BladeUI\Icons\BladeIconsServiceProvider::class, /* * Application Service Providers... diff --git a/config/blade-icons.php b/config/blade-icons.php new file mode 100644 index 0000000..5aade2a --- /dev/null +++ b/config/blade-icons.php @@ -0,0 +1,183 @@ + [ + + // 'default' => [ + // + // /* + // |----------------------------------------------------------------- + // | Icons Path + // |----------------------------------------------------------------- + // | + // | Provide the relative path from your app root to your SVG icons + // | directory. Icons are loaded recursively so there's no need to + // | list every sub-directory. + // | + // | Relative to the disk root when the disk option is set. + // | + // */ + // + // 'path' => 'resources/svg', + // + // /* + // |----------------------------------------------------------------- + // | Filesystem Disk + // |----------------------------------------------------------------- + // | + // | Optionally, provide a specific filesystem disk to read + // | icons from. When defining a disk, the "path" option + // | starts relatively from the disk root. + // | + // */ + // + // 'disk' => '', + // + // /* + // |----------------------------------------------------------------- + // | Default Prefix + // |----------------------------------------------------------------- + // | + // | This config option allows you to define a default prefix for + // | your icons. The dash separator will be applied automatically + // | to every icon name. It's required and needs to be unique. + // | + // */ + // + // 'prefix' => 'icon', + // + // /* + // |----------------------------------------------------------------- + // | Fallback Icon + // |----------------------------------------------------------------- + // | + // | This config option allows you to define a fallback + // | icon when an icon in this set cannot be found. + // | + // */ + // + // 'fallback' => '', + // + // /* + // |----------------------------------------------------------------- + // | Default Set Classes + // |----------------------------------------------------------------- + // | + // | This config option allows you to define some classes which + // | will be applied by default to all icons within this set. + // | + // */ + // + // 'class' => '', + // + // /* + // |----------------------------------------------------------------- + // | Default Set Attributes + // |----------------------------------------------------------------- + // | + // | This config option allows you to define some attributes which + // | will be applied by default to all icons within this set. + // | + // */ + // + // 'attributes' => [ + // // 'width' => 50, + // // 'height' => 50, + // ], + // + // ], + + ], + + /* + |-------------------------------------------------------------------------- + | Global Default Classes + |-------------------------------------------------------------------------- + | + | This config option allows you to define some classes which + | will be applied by default to all icons. + | + */ + + 'class' => '', + + /* + |-------------------------------------------------------------------------- + | Global Default Attributes + |-------------------------------------------------------------------------- + | + | This config option allows you to define some attributes which + | will be applied by default to all icons. + | + */ + + 'attributes' => [ + // 'width' => 50, + // 'height' => 50, + ], + + /* + |-------------------------------------------------------------------------- + | Global Fallback Icon + |-------------------------------------------------------------------------- + | + | This config option allows you to define a global fallback + | icon when an icon in any set cannot be found. It can + | reference any icon from any configured set. + | + */ + + 'fallback' => '', + + /* + |-------------------------------------------------------------------------- + | Components + |-------------------------------------------------------------------------- + | + | These config options allow you to define some + | settings related to Blade Components. + | + */ + + 'components' => [ + + /* + |---------------------------------------------------------------------- + | Disable Components + |---------------------------------------------------------------------- + | + | This config option allows you to disable Blade components + | completely. It's useful to avoid performance problems + | when working with large icon libraries. + | + */ + + 'disabled' => false, + + /* + |---------------------------------------------------------------------- + | Default Icon Component Name + |---------------------------------------------------------------------- + | + | This config option allows you to define the name + | for the default Icon class component. + | + */ + + 'default' => 'icon', + + ], + +]; diff --git a/database/migrations/2025_08_06_114358_add_sort_order_to_styles_table.php b/database/migrations/2025_08_06_114358_add_sort_order_to_styles_table.php new file mode 100644 index 0000000..082b07f --- /dev/null +++ b/database/migrations/2025_08_06_114358_add_sort_order_to_styles_table.php @@ -0,0 +1,28 @@ +integer('sort_order')->default(0)->after('enabled'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('styles', function (Blueprint $table) { + $table->dropColumn('sort_order'); + }); + } +}; diff --git a/resources/js/Pages/Home.vue b/resources/js/Pages/Home.vue index 46914f3..d2c0232 100644 --- a/resources/js/Pages/Home.vue +++ b/resources/js/Pages/Home.vue @@ -283,7 +283,17 @@ onMounted(() => { } fetchImages(); - fetchInterval = setInterval(fetchImages, 5000); + + // Fetch image refresh interval from API + axios.get('/api/image-refresh-interval') + .then(response => { + const interval = response.data.interval * 1000; + fetchInterval = setInterval(fetchImages, interval); + }) + .catch(error => { + console.error('Error fetching image refresh interval:', error); + fetchInterval = setInterval(fetchImages, 5000); // Fallback to 5 seconds + }); }); onUnmounted(() => { diff --git a/resources/lang/de/filament.php b/resources/lang/de/filament.php index 2ccc840..9c16971 100644 --- a/resources/lang/de/filament.php +++ b/resources/lang/de/filament.php @@ -42,6 +42,11 @@ return [ 'delete' => 'Löschen', 'enable_selected' => 'Ausgewählte aktivieren', 'disable_selected' => 'Ausgewählte deaktivieren', + 'test_connection' => 'Verbindung testen', + ], + 'notification' => [ + 'connection_successful' => 'Verbindung erfolgreich!', + 'connection_failed' => 'Verbindung fehlgeschlagen.', ], ], 'image' => [ @@ -76,12 +81,14 @@ return [ 'api_provider' => 'API Anbieter', 'ai_model' => 'AI Modell', 'enabled' => 'Aktiviert', + 'sort_order' => 'Sortierreihenfolge', ], 'table' => [ 'title' => 'Titel', 'ai_model' => 'AI Modell', 'preview_image' => 'Vorschaubild', 'enabled' => 'Aktiviert', + 'sort_order' => 'Sortierreihenfolge', ], 'action' => [ 'duplicate' => 'Duplizieren', @@ -89,6 +96,13 @@ return [ 'disable_selected' => 'Ausgewählte deaktivieren', ], ], + 'setting' => [ + 'form' => [ + 'image_refresh_interval' => 'Bildaktualisierungsintervall (Sekunden)', + 'new_image_timespan_minutes' => 'Neue Bilder Zeitspanne (Minuten)', + 'gallery_heading' => 'Galerie Überschrift', + ], + ], 'plugin' => [ 'navigation' => [ 'group' => 'Plugins', diff --git a/resources/lang/en/filament.php b/resources/lang/en/filament.php index bc5b78b..3281363 100644 --- a/resources/lang/en/filament.php +++ b/resources/lang/en/filament.php @@ -41,6 +41,11 @@ return [ 'delete' => 'Delete', 'enable_selected' => 'Enable Selected', 'disable_selected' => 'Disable Selected', + 'test_connection' => 'Test Connection', + ], + 'notification' => [ + 'connection_successful' => 'Connection successful!', + 'connection_failed' => 'Connection failed.', ], ], 'image' => [ @@ -75,12 +80,14 @@ return [ 'api_provider' => 'API Provider', 'ai_model' => 'AI Model', 'enabled' => 'Enabled', + 'sort_order' => 'Sort Order', ], 'table' => [ 'title' => 'Title', 'ai_model' => 'AI Model', 'preview_image' => 'Preview Image', 'enabled' => 'Enabled', + 'sort_order' => 'Sort Order', ], 'action' => [ 'duplicate' => 'Duplicate', @@ -88,7 +95,14 @@ return [ 'disable_selected' => 'Disable Selected', ], ], - 'user' => [ + 'setting' => [ + 'form' => [ + 'image_refresh_interval' => 'Image Refresh Interval (seconds)', + 'new_image_timespan_minutes' => 'New Image Timespan (minutes)', + 'gallery_heading' => 'Gallery Heading', + ], + ], + 'user' => [ 'navigation' => [ 'group' => 'User Management', ], diff --git a/routes/api.php b/routes/api.php index 51c7f5d..2f368f6 100644 --- a/routes/api.php +++ b/routes/api.php @@ -27,6 +27,8 @@ Route::post('/admin/navigation-state', [NavigationStateController::class, 'store // Publicly accessible routes Route::get('/images', [ImageController::class, 'index']); Route::get('/styles', [StyleController::class, 'index']); +Route::get('/image-refresh-interval', [StyleController::class, 'getImageRefreshInterval']); + Route::post('/images/style-change', [ImageController::class, 'styleChangeRequest']); Route::get('/comfyui-url', [ImageController::class, 'getComfyUiUrl']);