Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/Contracts/Plugins/HasTheme.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Contracts\Plugins;

use Closure;
use Filament\Contracts\Plugin;

interface HasTheme extends Plugin
{
/**
* @return array<string, array<int|string, string|int>|string>
*/
public function getThemeColors(): array;

public function getThemeFont(): ?string;

/**
* @return string|array<string>|null
*/
public function getThemeViteEntry(): string|array|null;

/**
* @return array<string, Closure>
*/
public function getThemeRenderHooks(): array;
}
2 changes: 2 additions & 0 deletions app/Enums/CustomizationKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ enum CustomizationKey: string
case ConsoleGraphPeriod = 'console_graph_period';
case TopNavigation = 'top_navigation';
case DashboardLayout = 'dashboard_layout';
case Theme = 'theme';

case ButtonStyle = 'button_style';
case RedirectToAdmin = 'redirect_to_admin';
Expand All @@ -25,6 +26,7 @@ public function getDefaultValue(): string|int|bool
self::ConsoleGraphPeriod => 30,
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
self::DashboardLayout => 'grid',
self::Theme => config('panel.filament.default-theme'),
self::ButtonStyle => true,
self::RedirectToAdmin => false,
// 0 means "unset", the table falls back to its contextual default (see ListServers).
Expand Down
14 changes: 13 additions & 1 deletion app/Filament/Admin/Pages/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Extensions\Captcha\CaptchaService;
use App\Extensions\OAuth\OAuthService;
use App\Notifications\MailTested;
use App\Services\Helpers\ThemeService;
use App\Traits\EnvironmentWriterTrait;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
Expand Down Expand Up @@ -73,6 +74,8 @@ class Settings extends Page implements HasSchemas

protected IconFactory $iconFactory;

protected ThemeService $themeService;

/** @var array<mixed>|null */
public ?array $data = [];

Expand All @@ -81,12 +84,13 @@ public function mount(): void
$this->form->fill();
}

public function boot(OAuthService $oauthService, AvatarService $avatarService, CaptchaService $captchaService, IconFactory $iconFactory): void
public function boot(OAuthService $oauthService, AvatarService $avatarService, CaptchaService $captchaService, IconFactory $iconFactory, ThemeService $themeService): void
{
$this->oauthService = $oauthService;
$this->avatarService = $avatarService;
$this->captchaService = $captchaService;
$this->iconFactory = $iconFactory;
$this->themeService = $themeService;
}

public static function canAccess(): bool
Expand Down Expand Up @@ -227,6 +231,14 @@ private function generalSettings(): array
'mixed' => trans('admin/setting.general.mixed'),
])
->default(env('FILAMENT_DEFAULT_NAVIGATION', config('panel.filament.default-navigation'))),
Select::make('FILAMENT_DEFAULT_THEME')
->label(trans('admin/setting.general.default_theme'))
->hintIcon(TablerIcon::QuestionMark, trans('admin/setting.general.default_theme_help'))
->options($this->themeService->getThemeOptions())
->placeholder(trans('profile.default_theme'))
->selectablePlaceholder(false)
->visible(fn () => $this->themeService->getThemes() !== [])
->default(config('panel.filament.default-theme')),
ToggleButtons::make('APP_2FA_REQUIRED')
->label(trans('admin/setting.general.2fa_requirement'))
->inline()
Expand Down
18 changes: 14 additions & 4 deletions app/Filament/Admin/Resources/Plugins/PluginResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
use App\Jobs\Plugin\UpdatePlugin;
use App\Models\Plugin;
use App\Services\Helpers\PluginService;
use App\Services\Helpers\ThemeService;
use BackedEnum;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
Expand Down Expand Up @@ -170,12 +172,20 @@ public static function table(Table $table): Table
->icon(TablerIcon::Check)
->color('success')
->visible(fn (Plugin $plugin) => $plugin->canEnable())
->requiresConfirmation(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled())
->modalHeading(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled() ? trans('admin/plugin.enable_theme_modal.heading') : null)
->modalDescription(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled() ? trans('admin/plugin.enable_theme_modal.description') : null)
Comment on lines -173 to -175

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a modal asking if we should change the default theme to the one you just enabled.

->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
->modalHidden(fn (Plugin $plugin) => !$plugin->isTheme())
->modalHeading(fn () => trans('admin/plugin.enable_theme_modal.heading'))
->modalDescription(fn () => trans('admin/plugin.enable_theme_modal.description'))
->schema([
Toggle::make('set_as_default')
->label(trans('admin/plugin.enable_theme_modal.set_as_default')),
])
->action(function (Plugin $plugin, array $data, $livewire, PluginService $pluginService, ThemeService $themeService) {
$pluginService->enablePlugin($plugin);

if ($data['set_as_default'] ?? false) {
$themeService->setDefaultTheme($plugin->id);
}

redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));

Notification::make()
Expand Down
10 changes: 10 additions & 0 deletions app/Filament/Pages/Auth/EditProfile.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Models\User;
use App\Models\UserSSHKey;
use App\Services\Helpers\LanguageService;
use App\Services\Helpers\ThemeService;
use App\Services\Ssh\KeyCreationService;
use App\Services\Users\UserUpdateService;
use App\Traits\Filament\CanCustomizeHeaderActions;
Expand Down Expand Up @@ -488,6 +489,12 @@ protected function getDefaultTabs(): array
true => trans('profile.icon'),
false => trans('profile.icon_button'),
]),
Select::make('theme')
->label(trans('profile.theme'))
->options(fn (ThemeService $themeService) => $themeService->getThemeOptions())
->placeholder(trans('profile.default_theme'))
->selectablePlaceholder(false)
->visible(fn (ThemeService $themeService) => $themeService->getThemes() !== []),
]),
Section::make(trans('profile.admin'))
->collapsible()
Expand Down Expand Up @@ -623,6 +630,7 @@ protected function mutateFormDataBeforeSave(array $data): array
'console_rows' => $data['console_rows'],
'console_graph_period' => $data['console_graph_period'],
'dashboard_layout' => $data['dashboard_layout'],
'theme' => $data['theme'] ?? $this->getUser()->getCustomization(CustomizationKey::Theme),
'top_navigation' => $data['top_navigation'],
'button_style' => $data['button_style'],
'redirect_to_admin' => $data['redirect_to_admin'] ?? $this->getUser()->getCustomization(CustomizationKey::RedirectToAdmin),
Expand All @@ -633,6 +641,7 @@ protected function mutateFormDataBeforeSave(array $data): array
$data['console_font_size'],
$data['console_rows'],
$data['dashboard_layout'],
$data['theme'],
$data['top_navigation'],
$data['button_style'],
$data['redirect_to_admin'],
Expand All @@ -650,6 +659,7 @@ protected function mutateFormDataBeforeFill(array $data): array
$data['console_rows'] = (int) $this->getUser()->getCustomization(CustomizationKey::ConsoleRows);
$data['console_graph_period'] = (int) $this->getUser()->getCustomization(CustomizationKey::ConsoleGraphPeriod);
$data['dashboard_layout'] = $this->getUser()->getCustomization(CustomizationKey::DashboardLayout);
$data['theme'] = $this->getUser()->getCustomization(CustomizationKey::Theme);
$data['button_style'] = $this->getUser()->getCustomization(CustomizationKey::ButtonStyle);
$data['redirect_to_admin'] = $this->getUser()->getCustomization(CustomizationKey::RedirectToAdmin);

Expand Down
23 changes: 23 additions & 0 deletions app/Http/Controllers/UpdateThemeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Http\Controllers;

use App\Enums\CustomizationKey;
use App\Services\Helpers\ThemeService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

class UpdateThemeController extends Controller
{
public function __invoke(Request $request, ThemeService $themeService): RedirectResponse
{
$data = $request->validate([
'theme' => ['required', 'string', Rule::in(array_keys($themeService->getThemeOptions()))],
]);

user()?->setCustomization(CustomizationKey::Theme, $data['theme']);

return redirect()->back();
}
}
2 changes: 1 addition & 1 deletion app/Jobs/Plugin/InstallPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function handle(PluginService $pluginService): void
Plugin::refreshRows();
$plugin = Plugin::findOrFail($this->pluginId);

$pluginService->installPlugin($plugin, !$plugin->isTheme() || !$pluginService->hasThemePluginEnabled());
$pluginService->installPlugin($plugin);

Notification::make()
->success()
Expand Down
12 changes: 12 additions & 0 deletions app/Models/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use App\Services\Helpers\SoftwareVersionService;
use Exception;
use Filament\Schemas\Components\Component;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
Expand Down Expand Up @@ -284,6 +285,17 @@ public function isPanelVersionStrict(): bool
return !str($this->panel_version)->startsWith('^');
}

/**
* @param Builder<self> $builder
* @return Builder<self>
*/
public function scopeThemes(Builder $builder): Builder
{
return $builder
->where('category', PluginCategory::Theme->value)
->where('status', PluginStatus::Enabled->value);
}

public function isTheme(): bool
{
return $this->category === PluginCategory::Theme;
Expand Down
1 change: 1 addition & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
'customization.console_graph_period' => ['integer', 'min:1'],
'customization.top_navigation' => ['boolean'],
'customization.dashboard_layout' => ['string', 'in:grid,table'],
'customization.theme' => ['string'],
'customization.servers_per_page' => ['integer', 'min:0'],
];

Expand Down
6 changes: 0 additions & 6 deletions app/Providers/Filament/AdminPanelProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use App\Enums\TablerIcon;
use App\Filament\Admin\Pages\ListLogs;
use App\Filament\Admin\Pages\ViewLogs;
use App\Services\Helpers\PluginService;
use Boquizo\FilamentLogViewer\FilamentLogViewerPlugin;
use CharrafiMed\GlobalSearchModal\GlobalSearchModalPlugin;
use Filament\Actions\Action;
Expand Down Expand Up @@ -50,11 +49,6 @@ public function panel(Panel $panel): Panel
GlobalSearchModalPlugin::make(),
]);

/** @var PluginService $pluginService */
$pluginService = app(PluginService::class); // @phpstan-ignore myCustomRules.forbiddenGlobalFunctions

$pluginService->loadPanelPlugins($panel);

return $panel;
}
}
6 changes: 0 additions & 6 deletions app/Providers/Filament/AppPanelProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace App\Providers\Filament;

use App\Enums\TablerIcon;
use App\Services\Helpers\PluginService;
use Boquizo\FilamentLogViewer\FilamentLogViewerPlugin;
use Filament\Actions\Action;
use Filament\Facades\Filament;
Expand Down Expand Up @@ -32,11 +31,6 @@ public function panel(Panel $panel): Panel
->authorize(false),
]);

/** @var PluginService $pluginService */
$pluginService = app(PluginService::class); // @phpstan-ignore myCustomRules.forbiddenGlobalFunctions

$pluginService->loadPanelPlugins($panel);

return $panel;
}
}
22 changes: 22 additions & 0 deletions app/Providers/Filament/PanelProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,45 @@
use App\Enums\CustomizationKey;
use App\Filament\Pages\Auth\EditProfile;
use App\Filament\Pages\Auth\Login;
use App\Http\Controllers\UpdateThemeController;
use App\Http\Middleware\LanguageMiddleware;
use App\Http\Middleware\PreventRequestForgery;
use App\Http\Middleware\RedirectIfNotInstalled;
use App\Http\Middleware\RequireTwoFactorAuthentication;
use App\Http\Middleware\SetSecurityHeaders;
use App\Services\Helpers\PluginService;
use App\Services\Helpers\ThemeService;
use Filament\Actions\Action;
use Filament\Auth\MultiFactor\App\AppAuthentication;
use Filament\Auth\MultiFactor\Email\EmailAuthentication;
use Filament\Facades\Filament;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Panel;
use Filament\PanelProvider as BasePanelProvider;
use Filament\View\PanelsRenderHook;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Route;
use Illuminate\View\Middleware\ShareErrorsFromSession;

abstract class PanelProvider extends BasePanelProvider
{
public function register(): void
{
Filament::registerPanel(function (): Panel {
$panel = $this->panel(Panel::make());

$this->app->make(PluginService::class)->loadPanelPlugins($panel);

return $panel;
});
}

public function panel(Panel $panel): Panel
{
return $panel
Expand Down Expand Up @@ -55,6 +72,11 @@ public function panel(Panel $panel): Panel
'profile' => fn (Action $action) => $action
->url(fn () => EditProfile::getUrl(panel: 'app')),
])
->authenticatedRoutes(fn () => Route::post('theme', UpdateThemeController::class)->name('theme'))
->renderHook(PanelsRenderHook::USER_MENU_PROFILE_AFTER, fn (ThemeService $themeService) => $themeService->getThemes() === [] ? '' : view('filament.components.theme-select', [
'themes' => $themeService->getThemeOptions(),
'selected' => $themeService->getSelectedOption(),
]))
->login(Login::class)
->passwordReset()
->multiFactorAuthentication([
Expand Down
6 changes: 0 additions & 6 deletions app/Providers/Filament/ServerPanelProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use App\Filament\App\Resources\Servers\Pages\ListServers;
use App\Http\Middleware\Activity\ServerSubject;
use App\Models\Server;
use App\Services\Helpers\PluginService;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\Navigation\NavigationItem;
Expand Down Expand Up @@ -54,11 +53,6 @@ public function panel(Panel $panel): Panel
ServerSubject::class,
]);

/** @var PluginService $pluginService */
$pluginService = app(PluginService::class); // @phpstan-ignore myCustomRules.forbiddenGlobalFunctions

$pluginService->loadPanelPlugins($panel);

return $panel;
}
}
12 changes: 0 additions & 12 deletions app/Services/Helpers/PluginService.php
Original file line number Diff line number Diff line change
Expand Up @@ -549,18 +549,6 @@ public function updateLoadOrder(array $order): void
}
}

public function hasThemePluginEnabled(): bool
{
$plugins = Plugin::orderBy('load_order')->get();
foreach ($plugins as $plugin) {
if ($plugin->isTheme() && $plugin->status === PluginStatus::Enabled) {
return true;
}
}

return false;
}

/** @return string[] */
public function getPluginLanguages(): array
{
Expand Down
Loading
Loading