mirror of
https://github.com/pelican-dev/panel.git
synced 2025-10-29 22:36:51 +01:00
General Edit User Improvements (#1779)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com> Co-authored-by: Boy132 <mail@boy132.de>
This commit is contained in:
parent
f6710dbbe4
commit
dbe4bdd62d
@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Extensions\OAuth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Socialite\Contracts\User as OAuthUser;
|
||||
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||
|
||||
class OAuthService
|
||||
@ -43,4 +45,27 @@ class OAuthService
|
||||
|
||||
$this->schemas[$schema->getId()] = $schema;
|
||||
}
|
||||
|
||||
public function linkUser(User $user, OAuthSchemaInterface $schema, OAuthUser $oauthUser): User
|
||||
{
|
||||
$oauth = $user->oauth ?? [];
|
||||
$oauth[$schema->getId()] = $oauthUser->getId();
|
||||
|
||||
$user->update(['oauth' => $oauth]);
|
||||
|
||||
return $user->refresh();
|
||||
}
|
||||
|
||||
public function unlinkUser(User $user, OAuthSchemaInterface $schema): User
|
||||
{
|
||||
$oauth = $user->oauth ?? [];
|
||||
if (!isset($oauth[$schema->getId()])) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
unset($oauth[$schema->getId()]);
|
||||
$user->update(['oauth' => $oauth]);
|
||||
|
||||
return $user->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,8 +48,7 @@ class EditUser extends EditRecord
|
||||
if (!$record instanceof User) {
|
||||
return $record;
|
||||
}
|
||||
|
||||
unset($data['roles']);
|
||||
unset($data['roles'], $data['avatar']);
|
||||
|
||||
return $this->service->handle($record, $data);
|
||||
}
|
||||
|
||||
@ -3,33 +3,56 @@
|
||||
namespace App\Filament\Admin\Resources\Users;
|
||||
|
||||
use App\Enums\CustomizationKey;
|
||||
use App\Extensions\OAuth\OAuthService;
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Admin\Resources\Users\Pages\CreateUser;
|
||||
use App\Filament\Admin\Resources\Users\Pages\EditUser;
|
||||
use App\Filament\Admin\Resources\Users\Pages\ListUsers;
|
||||
use App\Filament\Admin\Resources\Users\Pages\ViewUser;
|
||||
use App\Filament\Admin\Resources\Users\RelationManagers\ServersRelationManager;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\ApiKey;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Models\UserSSHKey;
|
||||
use App\Services\Helpers\LanguageService;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Auth\Notifications\ResetPassword;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Actions;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Auth\Events\PasswordResetLinkSent;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
@ -119,23 +142,188 @@ class UserResource extends Resource
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->columns(['default' => 1, 'lg' => 3])
|
||||
->columns(['default' => 1, 'lg' => 3, 'md' => 2])
|
||||
->components([
|
||||
Tabs::make()
|
||||
->schema([
|
||||
Tab::make('account')
|
||||
->label(trans('profile.tabs.account'))
|
||||
->icon('tabler-user-cog')
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'md' => 3,
|
||||
'lg' => 3,
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('username')
|
||||
->label(trans('admin/user.username'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
])
|
||||
->required()
|
||||
->unique()
|
||||
->maxLength(255),
|
||||
TextInput::make('email')
|
||||
->label(trans('admin/user.email'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
])
|
||||
->email()
|
||||
->required()
|
||||
->unique()
|
||||
->maxLength(255),
|
||||
TextInput::make('password')
|
||||
->label(trans('admin/user.password'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
])
|
||||
->hintIcon(fn ($operation) => $operation === 'create' ? 'tabler-question-mark' : null, fn ($operation) => $operation === 'create' ? trans('admin/user.password_help') : null)
|
||||
->password(),
|
||||
->password()
|
||||
->hintAction(
|
||||
Action::make('password_reset')
|
||||
->label(trans('admin/user.password_reset'))
|
||||
->hidden(fn () => config('mail.default', 'log') === 'log')
|
||||
->icon('tabler-send')
|
||||
->action(function (User $user) {
|
||||
$status = Password::broker(Filament::getPanel('app')->getAuthPasswordBroker())->sendResetLink([
|
||||
'email' => $user->email,
|
||||
],
|
||||
function (User $user, string $token) {
|
||||
$notification = new ResetPassword($token);
|
||||
$notification->url = Filament::getPanel('app')->getResetPasswordUrl($token, $user);
|
||||
|
||||
$user->notify($notification);
|
||||
|
||||
event(new PasswordResetLinkSent($user));
|
||||
},
|
||||
);
|
||||
|
||||
if ($status === Password::RESET_LINK_SENT) {
|
||||
Notification::make()
|
||||
->title(trans('admin/user.password_reset_sent'))
|
||||
->success()
|
||||
->send();
|
||||
} else {
|
||||
Notification::make()
|
||||
->title(trans('admin/user.password_reset_failed'))
|
||||
->body($status)
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
})),
|
||||
TextInput::make('external_id')
|
||||
->label(trans('admin/user.external_id'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
]),
|
||||
Select::make('timezone')
|
||||
->label(trans('profile.timezone'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
])
|
||||
->required()
|
||||
->prefixIcon('tabler-clock-pin')
|
||||
->default(fn () => config('app.timezone', 'UTC'))
|
||||
->selectablePlaceholder(false)
|
||||
->options(fn () => collect(DateTimeZone::listIdentifiers())->mapWithKeys(fn ($tz) => [$tz => $tz]))
|
||||
->searchable()
|
||||
->native(false),
|
||||
Select::make('language')
|
||||
->label(trans('profile.language'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
])
|
||||
->required()
|
||||
->prefixIcon('tabler-flag')
|
||||
->live()
|
||||
->default('en')
|
||||
->searchable()
|
||||
->selectablePlaceholder(false)
|
||||
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages())
|
||||
->native(false),
|
||||
FileUpload::make('avatar')
|
||||
->visible(fn (?User $user, FileUpload $fileUpload) => $user ? $fileUpload->getDisk()->exists($fileUpload->getDirectory() . '/' . $user->id . '.png') : false)
|
||||
->avatar()
|
||||
->directory('avatars')
|
||||
->disk('public')
|
||||
->formatStateUsing(function (FileUpload $fileUpload, ?User $user) {
|
||||
if (!$user) {
|
||||
return null;
|
||||
}
|
||||
$path = $fileUpload->getDirectory() . '/' . $user->id . '.png';
|
||||
if ($fileUpload->getDisk()->exists($path)) {
|
||||
return $path;
|
||||
}
|
||||
})
|
||||
->deleteUploadedFileUsing(function (FileUpload $fileUpload, $file) {
|
||||
if ($file instanceof TemporaryUploadedFile) {
|
||||
return $file->delete();
|
||||
}
|
||||
|
||||
if ($fileUpload->getDisk()->exists($file)) {
|
||||
return $fileUpload->getDisk()->delete($file);
|
||||
}
|
||||
}),
|
||||
Section::make(trans('profile.tabs.oauth'))
|
||||
->visible(fn (?User $user) => $user)
|
||||
->collapsible()
|
||||
->columnSpanFull()
|
||||
->schema(function (OAuthService $oauthService, ?User $user) {
|
||||
|
||||
if (!$user) {
|
||||
return;
|
||||
}
|
||||
$actions = [];
|
||||
foreach ($user->oauth as $schema => $_) {
|
||||
$schema = $oauthService->get($schema);
|
||||
if (!$schema) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = $schema->getId();
|
||||
$name = $schema->getName();
|
||||
$actions[] = Action::make("oauth_$id")
|
||||
->label(trans('profile.unlink', ['name' => $name]))
|
||||
->icon('tabler-unlink')
|
||||
->requiresConfirmation()
|
||||
->color(Color::hex($schema->getHexColor()))
|
||||
->action(function ($livewire) use ($oauthService, $user, $name, $schema) {
|
||||
$oauthService->unlinkUser($user, $schema);
|
||||
$livewire->form->fill($user->attributesToArray());
|
||||
Notification::make()
|
||||
->title(trans('profile.unlinked', ['name' => $name]))
|
||||
->success()
|
||||
->send();
|
||||
});
|
||||
}
|
||||
|
||||
if (!$actions) {
|
||||
return [
|
||||
TextEntry::make('no_oauth')
|
||||
->state(trans('profile.no_oauth'))
|
||||
->hiddenLabel(),
|
||||
];
|
||||
}
|
||||
|
||||
return [Actions::make($actions)];
|
||||
}),
|
||||
]),
|
||||
Tab::make('roles')
|
||||
->label(trans('admin/user.roles'))
|
||||
->icon('tabler-users-group')
|
||||
->components([
|
||||
CheckboxList::make('roles')
|
||||
->hidden(fn (?User $user) => $user && $user->isRootAdmin())
|
||||
->relationship('roles', 'name', fn (Builder $query) => $query->whereNot('id', Role::getRootAdmin()->id))
|
||||
@ -157,6 +345,123 @@ class UserResource extends Resource
|
||||
->dehydrated(false)
|
||||
->label(trans('admin/user.admin_roles'))
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Tab::make('keys')
|
||||
->visible(fn (?User $user) => $user)
|
||||
->label(trans('profile.tabs.keys'))
|
||||
->icon('tabler-key')
|
||||
->schema([
|
||||
Section::make(trans('profile.api_keys'))
|
||||
->columnSpan(2)
|
||||
->schema([
|
||||
Repeater::make('api_keys')
|
||||
->hiddenLabel()
|
||||
->inlineLabel(false)
|
||||
->relationship('apiKeys')
|
||||
->addable(false)
|
||||
->itemLabel(fn ($state) => $state['identifier'])
|
||||
->deleteAction(function (Action $action) {
|
||||
$action->requiresConfirmation()->action(function (array $arguments, Repeater $component, ?User $user) {
|
||||
$items = $component->getState();
|
||||
$key = $items[$arguments['item']] ?? null;
|
||||
|
||||
if ($key) {
|
||||
$apiKey = ApiKey::find($key['id']);
|
||||
if ($apiKey?->exists()) {
|
||||
$apiKey->delete();
|
||||
|
||||
Activity::event('user:api-key.delete')
|
||||
->actor(user())
|
||||
->subject($user)
|
||||
->subject($apiKey)
|
||||
->property('identifier', $apiKey->identifier)
|
||||
->log();
|
||||
}
|
||||
|
||||
unset($items[$arguments['item']]);
|
||||
$component->state($items);
|
||||
$component->callAfterStateUpdated();
|
||||
}
|
||||
});
|
||||
})
|
||||
->schema([
|
||||
TextEntry::make('memo')
|
||||
->hiddenLabel()
|
||||
->state(fn (ApiKey $key) => $key->memo),
|
||||
])
|
||||
->visible(fn (User $user) => $user->apiKeys()->exists()),
|
||||
|
||||
TextEntry::make('no_api_keys')
|
||||
->state(trans('profile.no_api_keys'))
|
||||
->hiddenLabel()
|
||||
->visible(fn (User $user) => !$user->apiKeys()->exists()),
|
||||
]),
|
||||
Section::make(trans('profile.ssh_keys'))->columnSpan(2)
|
||||
->schema([
|
||||
Repeater::make('ssh_keys')
|
||||
->hiddenLabel()
|
||||
->inlineLabel(false)
|
||||
->relationship('sshKeys')
|
||||
->addable(false)
|
||||
->itemLabel(fn ($state) => $state['name'])
|
||||
->deleteAction(function (Action $action) {
|
||||
$action->requiresConfirmation()->action(function (array $arguments, Repeater $component, User $user) {
|
||||
$items = $component->getState();
|
||||
$key = $items[$arguments['item']];
|
||||
|
||||
$sshKey = UserSSHKey::find($key['id'] ?? null);
|
||||
if ($sshKey->exists()) {
|
||||
$sshKey->delete();
|
||||
|
||||
Activity::event('user:ssh-key.delete')
|
||||
->actor(auth()->user())
|
||||
->subject($user)
|
||||
->subject($sshKey)
|
||||
->property('fingerprint', $sshKey->fingerprint)
|
||||
->log();
|
||||
}
|
||||
|
||||
unset($items[$arguments['item']]);
|
||||
|
||||
$component->state($items);
|
||||
|
||||
$component->callAfterStateUpdated();
|
||||
});
|
||||
})
|
||||
->schema(fn () => [
|
||||
TextEntry::make('fingerprint')
|
||||
->hiddenLabel()
|
||||
->state(fn (UserSSHKey $key) => "SHA256:{$key->fingerprint}"),
|
||||
])
|
||||
->visible(fn (User $user) => $user->sshKeys()->exists()),
|
||||
|
||||
TextEntry::make('no_ssh_keys')
|
||||
->state(trans('profile.no_ssh_keys'))
|
||||
->hiddenLabel()
|
||||
->visible(fn (User $user) => !$user->sshKeys()->exists()),
|
||||
]),
|
||||
]),
|
||||
Tab::make('activity')
|
||||
->visible(fn (?User $user) => $user)
|
||||
->disabledOn('create')
|
||||
->label(trans('profile.tabs.activity'))
|
||||
->icon('tabler-history')
|
||||
->schema([
|
||||
Repeater::make('activity')
|
||||
->hiddenLabel()
|
||||
->inlineLabel(false)
|
||||
->deletable(false)
|
||||
->addable(false)
|
||||
->relationship(null, function (Builder $query) {
|
||||
$query->orderBy('timestamp', 'desc');
|
||||
})
|
||||
->schema([
|
||||
TextEntry::make('log')
|
||||
->hiddenLabel()
|
||||
->state(fn (ActivityLog $log) => new HtmlString($log->htmlable())),
|
||||
]),
|
||||
]),
|
||||
])->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -48,6 +48,7 @@ use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
/**
|
||||
* @method User getUser()
|
||||
@ -128,7 +129,7 @@ class EditProfile extends BaseEditProfile
|
||||
->label(trans('profile.timezone'))
|
||||
->required()
|
||||
->prefixIcon('tabler-clock-pin')
|
||||
->default('UTC')
|
||||
->default(config('app.timezone', 'UTC'))
|
||||
->selectablePlaceholder(false)
|
||||
->options(fn () => collect(DateTimeZone::listIdentifiers())->mapWithKeys(fn ($tz) => [$tz => $tz]))
|
||||
->searchable()
|
||||
@ -151,14 +152,20 @@ class EditProfile extends BaseEditProfile
|
||||
->directory('avatars')
|
||||
->disk('public')
|
||||
->getUploadedFileNameForStorageUsing(fn () => $this->getUser()->id . '.png')
|
||||
->hintAction(function (FileUpload $fileUpload) {
|
||||
->formatStateUsing(function (FileUpload $fileUpload) {
|
||||
$path = $fileUpload->getDirectory() . '/' . $this->getUser()->id . '.png';
|
||||
if ($fileUpload->getDisk()->exists($path)) {
|
||||
return $path;
|
||||
}
|
||||
})
|
||||
->deleteUploadedFileUsing(function (FileUpload $fileUpload, $file) {
|
||||
if ($file instanceof TemporaryUploadedFile) {
|
||||
return $file->delete();
|
||||
}
|
||||
|
||||
return Action::make('remove_avatar')
|
||||
->icon('tabler-photo-minus')
|
||||
->iconButton()
|
||||
->hidden(fn () => !$fileUpload->getDisk()->exists($path))
|
||||
->action(fn () => $fileUpload->getDisk()->delete($path));
|
||||
if ($fileUpload->getDisk()->exists($file)) {
|
||||
return $fileUpload->getDisk()->delete($file);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
Tab::make('oauth')
|
||||
@ -292,7 +299,13 @@ class EditProfile extends BaseEditProfile
|
||||
TextEntry::make('memo')
|
||||
->hiddenLabel()
|
||||
->state(fn (ApiKey $key) => $key->memo),
|
||||
]),
|
||||
])
|
||||
->visible(fn (User $user) => $user->apiKeys()->exists()),
|
||||
|
||||
TextEntry::make('no_api_keys')
|
||||
->state(trans('profile.no_api_keys'))
|
||||
->hiddenLabel()
|
||||
->visible(fn (User $user) => !$user->apiKeys()->exists()),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
@ -381,7 +394,13 @@ class EditProfile extends BaseEditProfile
|
||||
TextEntry::make('fingerprint')
|
||||
->hiddenLabel()
|
||||
->state(fn (UserSSHKey $key) => "SHA256:{$key->fingerprint}"),
|
||||
]),
|
||||
])
|
||||
->visible(fn (User $user) => $user->sshKeys()->exists()),
|
||||
|
||||
TextEntry::make('no_ssh_keys')
|
||||
->state(trans('profile.no_ssh_keys'))
|
||||
->hiddenLabel()
|
||||
->visible(fn (User $user) => !$user->sshKeys()->exists()),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
@ -56,7 +56,7 @@ class OAuthController extends Controller
|
||||
$oauthUser = Socialite::driver($driver->getId())->user();
|
||||
|
||||
if ($request->user()) {
|
||||
$this->linkUser($request->user(), $driver, $oauthUser);
|
||||
$this->oauthService->linkUser($request->user(), $driver, $oauthUser);
|
||||
|
||||
return redirect(EditProfile::getUrl(['tab' => 'oauth::data::tab'], panel: 'app'));
|
||||
}
|
||||
@ -69,16 +69,6 @@ class OAuthController extends Controller
|
||||
return $this->handleMissingUser($driver, $oauthUser);
|
||||
}
|
||||
|
||||
private function linkUser(User $user, OAuthSchemaInterface $driver, OAuthUser $oauthUser): User
|
||||
{
|
||||
$oauth = $user->oauth;
|
||||
$oauth[$driver->getId()] = $oauthUser->getId();
|
||||
|
||||
$user->update(['oauth' => $oauth]);
|
||||
|
||||
return $user->refresh();
|
||||
}
|
||||
|
||||
private function handleMissingUser(OAuthSchemaInterface $driver, OAuthUser $oauthUser): RedirectResponse
|
||||
{
|
||||
$email = $oauthUser->getEmail();
|
||||
@ -93,7 +83,7 @@ class OAuthController extends Controller
|
||||
return $this->errorRedirect();
|
||||
}
|
||||
|
||||
$user = $this->linkUser($user, $driver, $oauthUser);
|
||||
$user = $this->oauthService->linkUser($user, $driver, $oauthUser);
|
||||
} else {
|
||||
if (!$driver->shouldCreateMissingUsers()) {
|
||||
return $this->errorRedirect();
|
||||
|
||||
@ -163,6 +163,11 @@ class ActivityLog extends Model implements HasIcon, HasLabel
|
||||
return trans_choice('activity.'.str($this->event)->replace(':', '.'), array_key_exists('count', $properties) ? $properties['count'] : 1, $properties);
|
||||
}
|
||||
|
||||
public function getIp(): ?string
|
||||
{
|
||||
return auth()->user()->can('seeIps activityLog') ? $this->ip : null;
|
||||
}
|
||||
|
||||
public function htmlable(): string
|
||||
{
|
||||
$user = $this->actor;
|
||||
@ -175,6 +180,8 @@ class ActivityLog extends Model implements HasIcon, HasLabel
|
||||
|
||||
$avatarUrl = Filament::getUserAvatarUrl($user);
|
||||
$username = str($user->username)->stripTags();
|
||||
$ip = $this->getIp();
|
||||
$ip = $ip ? $ip . ' — ' : '';
|
||||
|
||||
return "
|
||||
<div style='display: flex; align-items: center;'>
|
||||
@ -183,7 +190,7 @@ class ActivityLog extends Model implements HasIcon, HasLabel
|
||||
<div>
|
||||
<p>$username — $this->event</p>
|
||||
<p>{$this->getLabel()}</p>
|
||||
<p>$this->ip — <span title='{$this->timestamp->format('M j, Y g:ia')}'>{$this->timestamp->diffForHumans()}</span></p>
|
||||
<p>$ip<span title='{$this->timestamp->format('M j, Y g:ia')}'>{$this->timestamp->diffForHumans()}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
";
|
||||
|
||||
@ -9,10 +9,14 @@ return [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'external_id' => 'External ID',
|
||||
'password_help' => 'Providing a user password is optional. New user email will prompt users to create a password the first time they login.',
|
||||
'admin_roles' => 'Admin Roles',
|
||||
'roles' => 'Roles',
|
||||
'no_roles' => 'No Roles',
|
||||
'servers' => 'Servers',
|
||||
'subusers' => 'Subusers',
|
||||
'password_reset' => 'Reset Password',
|
||||
'password_reset_sent' => 'Password Reset E-Mail Sent',
|
||||
'password_reset_failed' => 'Failed to Send Password Reset E-Mail',
|
||||
];
|
||||
|
||||
@ -8,6 +8,7 @@ return [
|
||||
'activity' => 'Activity',
|
||||
'api_keys' => 'API Keys',
|
||||
'ssh_keys' => 'SSH Keys',
|
||||
'keys' => 'Keys',
|
||||
'2fa' => '2FA',
|
||||
'customization' => 'Customization',
|
||||
],
|
||||
@ -62,4 +63,7 @@ return [
|
||||
'navigation' => 'Navigation Type',
|
||||
'top' => 'Topbar',
|
||||
'side' => 'Sidebar',
|
||||
'no_oauth' => 'No Accounts Linked',
|
||||
'no_api_keys' => 'No API Keys',
|
||||
'no_ssh_keys' => 'No SSH Keys',
|
||||
];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user