Add avatar providers (#1192)
* Add avatar providers * fix exists check for local avatar * Use avatar in user lists --------- Co-authored-by: Charles <charles@pelican.dev>
This commit is contained in:
parent
377b3f170d
commit
fa8ae0aea5
40
app/Extensions/Avatar/AvatarProvider.php
Normal file
40
app/Extensions/Avatar/AvatarProvider.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Avatar;
|
||||
|
||||
use Filament\AvatarProviders\Contracts\AvatarProvider as AvatarProviderContract;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class AvatarProvider implements AvatarProviderContract
|
||||
{
|
||||
/**
|
||||
* @var array<string, static>
|
||||
*/
|
||||
protected static array $providers = [];
|
||||
|
||||
public static function getProvider(string $id): ?self
|
||||
{
|
||||
return Arr::get(static::$providers, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, static>
|
||||
*/
|
||||
public static function getAll(): array
|
||||
{
|
||||
return static::$providers;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
static::$providers[$this->getId()] = $this;
|
||||
}
|
||||
|
||||
abstract public function getId(): string;
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return Str::title($this->getId());
|
||||
}
|
||||
}
|
27
app/Extensions/Avatar/Providers/GravatarProvider.php
Normal file
27
app/Extensions/Avatar/Providers/GravatarProvider.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Avatar\Providers;
|
||||
|
||||
use App\Extensions\Avatar\AvatarProvider;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class GravatarProvider extends AvatarProvider
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'gravatar';
|
||||
}
|
||||
|
||||
public function get(Model|Authenticatable $record): string
|
||||
{
|
||||
/** @var User $record */
|
||||
return 'https://gravatar.com/avatar/' . md5($record->email);
|
||||
}
|
||||
|
||||
public static function register(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
29
app/Extensions/Avatar/Providers/LocalAvatarProvider.php
Normal file
29
app/Extensions/Avatar/Providers/LocalAvatarProvider.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Avatar\Providers;
|
||||
|
||||
use App\Extensions\Avatar\AvatarProvider;
|
||||
use Filament\AvatarProviders\UiAvatarsProvider as FilamentUiAvatarsProvider;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class LocalAvatarProvider extends AvatarProvider
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'local';
|
||||
}
|
||||
|
||||
public function get(Model|Authenticatable $record): string
|
||||
{
|
||||
$path = 'avatars/' . $record->getKey() . '.png';
|
||||
|
||||
return Storage::disk('public')->exists($path) ? Storage::url($path) : (new FilamentUiAvatarsProvider())->get($record);
|
||||
}
|
||||
|
||||
public static function register(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
31
app/Extensions/Avatar/Providers/UiAvatarsProvider.php
Normal file
31
app/Extensions/Avatar/Providers/UiAvatarsProvider.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Avatar\Providers;
|
||||
|
||||
use App\Extensions\Avatar\AvatarProvider;
|
||||
use Filament\AvatarProviders\UiAvatarsProvider as FilamentUiAvatarsProvider;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UiAvatarsProvider extends AvatarProvider
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'uiavatars';
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'UI Avatars';
|
||||
}
|
||||
|
||||
public function get(Model|Authenticatable $record): string
|
||||
{
|
||||
return (new FilamentUiAvatarsProvider())->get($record);
|
||||
}
|
||||
|
||||
public static function register(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Extensions\Avatar\AvatarProvider;
|
||||
use App\Extensions\Captcha\Providers\CaptchaProvider;
|
||||
use App\Extensions\OAuth\Providers\OAuthProvider;
|
||||
use App\Models\Backup;
|
||||
@ -134,26 +135,38 @@ class Settings extends Page implements HasForms
|
||||
->default(env('APP_FAVICON', '/pelican.ico'))
|
||||
->placeholder('/pelican.ico'),
|
||||
]),
|
||||
Toggle::make('APP_DEBUG')
|
||||
->label(trans('admin/setting.general.debug_mode'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_DEBUG', (bool) $state))
|
||||
->default(env('APP_DEBUG', config('app.debug'))),
|
||||
ToggleButtons::make('FILAMENT_TOP_NAVIGATION')
|
||||
->label(trans('admin/setting.general.navigation'))
|
||||
->inline()
|
||||
->options([
|
||||
false => trans('admin/setting.general.sidebar'),
|
||||
true => trans('admin/setting.general.topbar'),
|
||||
])
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_TOP_NAVIGATION', (bool) $state))
|
||||
->default(env('FILAMENT_TOP_NAVIGATION', config('panel.filament.top-navigation'))),
|
||||
Group::make()
|
||||
->columnSpan(2)
|
||||
->columns(4)
|
||||
->schema([
|
||||
Toggle::make('APP_DEBUG')
|
||||
->label(trans('admin/setting.general.debug_mode'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_DEBUG', (bool) $state))
|
||||
->default(env('APP_DEBUG', config('app.debug'))),
|
||||
ToggleButtons::make('FILAMENT_TOP_NAVIGATION')
|
||||
->label(trans('admin/setting.general.navigation'))
|
||||
->inline()
|
||||
->options([
|
||||
false => trans('admin/setting.general.sidebar'),
|
||||
true => trans('admin/setting.general.topbar'),
|
||||
])
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_TOP_NAVIGATION', (bool) $state))
|
||||
->default(env('FILAMENT_TOP_NAVIGATION', config('panel.filament.top-navigation'))),
|
||||
Select::make('FILAMENT_AVATAR_PROVIDER')
|
||||
->label(trans('admin/setting.general.avatar_provider'))
|
||||
->columnSpan(2)
|
||||
->native(false)
|
||||
->options(collect(AvatarProvider::getAll())->mapWithKeys(fn ($provider) => [$provider->getId() => $provider->getName()]))
|
||||
->selectablePlaceholder(false)
|
||||
->default(env('FILAMENT_AVATAR_PROVIDER', config('panel.filament.avatar-provider'))),
|
||||
]),
|
||||
ToggleButtons::make('PANEL_USE_BINARY_PREFIX')
|
||||
->label(trans('admin/setting.general.unit_prefix'))
|
||||
->inline()
|
||||
|
@ -6,6 +6,7 @@ use App\Filament\Admin\Resources\UserResource\Pages;
|
||||
use App\Filament\Admin\Resources\UserResource\RelationManagers;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
@ -58,8 +59,9 @@ class UserResource extends Resource
|
||||
ImageColumn::make('picture')
|
||||
->visibleFrom('lg')
|
||||
->label('')
|
||||
->extraImgAttributes(['class' => 'rounded-full'])
|
||||
->defaultImageUrl(fn (User $user) => 'https://gravatar.com/avatar/' . md5(strtolower($user->email))),
|
||||
->circular()
|
||||
->alignCenter()
|
||||
->defaultImageUrl(fn (User $user) => Filament::getUserAvatarUrl($user)),
|
||||
TextColumn::make('username')
|
||||
->label(trans('admin/user.username')),
|
||||
TextColumn::make('email')
|
||||
|
@ -19,6 +19,7 @@ use chillerlan\QRCode\QROptions;
|
||||
use DateTimeZone;
|
||||
use Filament\Forms\Components\Actions;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
@ -126,6 +127,11 @@ class EditProfile extends BaseEditProfile
|
||||
->helperText(fn ($state, LanguageService $languageService) => new HtmlString($languageService->isLanguageTranslated($state) ? '' : trans('profile.language_help', ['state' => $state])))
|
||||
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages())
|
||||
->native(false),
|
||||
FileUpload::make('avatar')
|
||||
->visible(fn () => config('panel.filament.avatar-provider') === 'local')
|
||||
->avatar()
|
||||
->directory('avatars')
|
||||
->getUploadedFileNameForStorageUsing(fn () => $this->getUser()->id . '.png'),
|
||||
]),
|
||||
|
||||
Tab::make(trans('profile.tabs.oauth'))
|
||||
|
@ -90,8 +90,8 @@ class UserResource extends Resource
|
||||
ImageColumn::make('picture')
|
||||
->visibleFrom('lg')
|
||||
->label('')
|
||||
->extraImgAttributes(['class' => 'rounded-full'])
|
||||
->defaultImageUrl(fn (User $user) => 'https://gravatar.com/avatar/' . md5(strtolower($user->email))),
|
||||
->alignCenter()->circular()
|
||||
->defaultImageUrl(fn (User $user) => Filament::getUserAvatarUrl($user)),
|
||||
TextColumn::make('username')
|
||||
->searchable(),
|
||||
TextColumn::make('email')
|
||||
|
@ -6,6 +6,7 @@ use App\Traits\HasValidation;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use App\Events\ActivityLogged;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@ -172,9 +173,11 @@ class ActivityLog extends Model implements HasIcon, HasLabel
|
||||
]);
|
||||
}
|
||||
|
||||
$avatarUrl = Filament::getUserAvatarUrl($user);
|
||||
|
||||
return "
|
||||
<div style='display: flex; align-items: center;'>
|
||||
<img width='50px' height='50px' src='{$user->getFilamentAvatarUrl()}' style='margin-right: 15px' />
|
||||
<img width='50px' height='50px' src='{$avatarUrl}' style='margin-right: 15px' />
|
||||
|
||||
<div>
|
||||
<p>$user->username — $this->event</p>
|
||||
|
@ -9,7 +9,6 @@ use App\Facades\Activity;
|
||||
use App\Traits\HasValidation;
|
||||
use DateTimeZone;
|
||||
use Filament\Models\Contracts\FilamentUser;
|
||||
use Filament\Models\Contracts\HasAvatar;
|
||||
use Filament\Models\Contracts\HasName;
|
||||
use Filament\Models\Contracts\HasTenants;
|
||||
use Filament\Panel;
|
||||
@ -50,7 +49,6 @@ use Spatie\Permission\Traits\HasRoles;
|
||||
* @property string|null $totp_secret
|
||||
* @property \Illuminate\Support\Carbon|null $totp_authenticated_at
|
||||
* @property string[]|null $oauth
|
||||
* @property bool $gravatar
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\ApiKey[] $apiKeys
|
||||
@ -77,7 +75,6 @@ use Spatie\Permission\Traits\HasRoles;
|
||||
* @method static Builder|User whereCreatedAt($value)
|
||||
* @method static Builder|User whereEmail($value)
|
||||
* @method static Builder|User whereExternalId($value)
|
||||
* @method static Builder|User whereGravatar($value)
|
||||
* @method static Builder|User whereId($value)
|
||||
* @method static Builder|User whereLanguage($value)
|
||||
* @method static Builder|User whereTimezone($value)
|
||||
@ -90,7 +87,7 @@ use Spatie\Permission\Traits\HasRoles;
|
||||
* @method static Builder|User whereUsername($value)
|
||||
* @method static Builder|User whereUuid($value)
|
||||
*/
|
||||
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasAvatar, HasName, HasTenants, Validatable
|
||||
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasName, HasTenants, Validatable
|
||||
{
|
||||
use Authenticatable;
|
||||
use Authorizable { can as protected canned; }
|
||||
@ -124,7 +121,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
'use_totp',
|
||||
'totp_secret',
|
||||
'totp_authenticated_at',
|
||||
'gravatar',
|
||||
'oauth',
|
||||
'customization',
|
||||
];
|
||||
@ -169,7 +165,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
{
|
||||
return [
|
||||
'use_totp' => 'boolean',
|
||||
'gravatar' => 'boolean',
|
||||
'totp_authenticated_at' => 'datetime',
|
||||
'totp_secret' => 'encrypted',
|
||||
'oauth' => 'array',
|
||||
@ -377,11 +372,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getFilamentAvatarUrl(): ?string
|
||||
{
|
||||
return 'https://gravatar.com/avatar/' . md5(strtolower($this->email));
|
||||
}
|
||||
|
||||
public function canTarget(Model $user): bool
|
||||
{
|
||||
if ($this->isRootAdmin()) {
|
||||
|
@ -10,6 +10,9 @@ use App\Checks\NodeVersionsCheck;
|
||||
use App\Checks\PanelVersionCheck;
|
||||
use App\Checks\ScheduleCheck;
|
||||
use App\Checks\UsedDiskSpaceCheck;
|
||||
use App\Extensions\Avatar\Providers\GravatarProvider;
|
||||
use App\Extensions\Avatar\Providers\LocalAvatarProvider;
|
||||
use App\Extensions\Avatar\Providers\UiAvatarsProvider;
|
||||
use App\Extensions\OAuth\Providers\GitlabProvider;
|
||||
use App\Models;
|
||||
use App\Extensions\Captcha\Providers\TurnstileProvider;
|
||||
@ -115,6 +118,11 @@ class AppServiceProvider extends ServiceProvider
|
||||
// Default Captcha provider
|
||||
TurnstileProvider::register($app);
|
||||
|
||||
// Default Avatar providers
|
||||
GravatarProvider::register();
|
||||
UiAvatarsProvider::register();
|
||||
LocalAvatarProvider::register();
|
||||
|
||||
FilamentColor::register([
|
||||
'danger' => Color::Red,
|
||||
'gray' => Color::Zinc,
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use App\Extensions\Avatar\AvatarProvider;
|
||||
use App\Filament\Pages\Auth\Login;
|
||||
use App\Filament\Pages\Auth\EditProfile;
|
||||
use App\Http\Middleware\LanguageMiddleware;
|
||||
@ -38,6 +39,7 @@ class AdminPanelProvider extends PanelProvider
|
||||
->favicon(config('app.favicon', '/pelican.ico'))
|
||||
->topNavigation(config('panel.filament.top-navigation', true))
|
||||
->maxContentWidth(config('panel.filament.display-width', 'screen-2xl'))
|
||||
->defaultAvatarProvider(fn () => get_class(AvatarProvider::getProvider(config('panel.filament.avatar-provider'))))
|
||||
->login(Login::class)
|
||||
->passwordReset()
|
||||
->userMenuItems([
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use App\Extensions\Avatar\AvatarProvider;
|
||||
use App\Filament\Pages\Auth\Login;
|
||||
use App\Filament\Pages\Auth\EditProfile;
|
||||
use Filament\Facades\Filament;
|
||||
@ -34,6 +35,7 @@ class AppPanelProvider extends PanelProvider
|
||||
->favicon(config('app.favicon', '/pelican.ico'))
|
||||
->topNavigation(config('panel.filament.top-navigation', true))
|
||||
->maxContentWidth(config('panel.filament.display-width', 'screen-2xl'))
|
||||
->defaultAvatarProvider(fn () => get_class(AvatarProvider::getProvider(config('panel.filament.avatar-provider'))))
|
||||
->navigation(false)
|
||||
->profile(EditProfile::class, false)
|
||||
->login(Login::class)
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use App\Extensions\Avatar\AvatarProvider;
|
||||
use App\Filament\App\Resources\ServerResource\Pages\ListServers;
|
||||
use App\Filament\Pages\Auth\Login;
|
||||
use App\Filament\Admin\Resources\ServerResource\Pages\EditServer;
|
||||
@ -41,6 +42,7 @@ class ServerPanelProvider extends PanelProvider
|
||||
->favicon(config('app.favicon', '/pelican.ico'))
|
||||
->topNavigation(config('panel.filament.top-navigation', true))
|
||||
->maxContentWidth(config('panel.filament.display-width', 'screen-2xl'))
|
||||
->defaultAvatarProvider(fn () => get_class(AvatarProvider::getProvider(config('panel.filament.avatar-provider'))))
|
||||
->login(Login::class)
|
||||
->passwordReset()
|
||||
->userMenuItems([
|
||||
|
@ -52,6 +52,7 @@ return [
|
||||
'filament' => [
|
||||
'top-navigation' => env('FILAMENT_TOP_NAVIGATION', false),
|
||||
'display-width' => env('FILAMENT_WIDTH', 'screen-2xl'),
|
||||
'avatar-provider' => env('FILAMENT_AVATAR_PROVIDER', 'gravatar'),
|
||||
],
|
||||
|
||||
'use_binary_prefix' => env('PANEL_USE_BINARY_PREFIX', true),
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('gravatar');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->boolean('gravatar')->default(true);
|
||||
});
|
||||
}
|
||||
};
|
@ -34,6 +34,7 @@ return [
|
||||
'clear' => 'Clear',
|
||||
'set_to_cf' => 'Set to Cloudflare IPs',
|
||||
'display_width' => 'Display Width',
|
||||
'avatar_provider' => 'Avatar Provider',
|
||||
],
|
||||
'captcha' => [
|
||||
'enable' => 'Enable',
|
||||
|
Loading…
x
Reference in New Issue
Block a user