Track last login date and IP

This commit is contained in:
Ryan Scherler
2020-03-22 16:49:34 -07:00
parent 2fffac638e
commit 8f33e0fbdd
25 changed files with 145014 additions and 104 deletions
@@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddLoginFieldsToUsersTable extends Migration
{
/**
* The name of the table for the migration.
*
* @var string
*/
protected $tableName = 'users';
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table($this->tableName, function (Blueprint $table) {
if (!Schema::hasColumn($this->tableName, 'last_login_at')) {
$table->datetime('last_login_at')->nullable();
}
if (!Schema::hasColumn($this->tableName, 'last_login_ip')) {
$table->string('last_login_ip')->nullable();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table($this->tableName, function (Blueprint $table) {
if (!Schema::hasColumn($this->tableName, 'last_login_at')) {
$table->dropColumn('last_login_at');
}
if (!Schema::hasColumn($this->tableName, 'last_login_ip')) {
$table->dropColumn('last_login_ip');
}
});
}
}
+142953 -1
View File
File diff suppressed because one or more lines are too long
+1804 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,4 +1,4 @@
{
"/js/app.js": "/js/app.js?id=f6e8984136ad30e3b70c",
"/css/app.css": "/css/app.css?id=09dc8347908c0e034608"
"/js/app.js": "/js/app.js?id=a996ac7f2a8afb2ad2e7",
"/css/app.css": "/css/app.css?id=fe89a3abc3e5090bfdc5"
}
+2 -4
View File
@@ -22,10 +22,8 @@ return [
'permissions' => 'Permissions',
'signed_in_as' => 'Signed in as',
'created_at' => 'Created',
'account_settings' => 'Account settings',
'user_account' => ':name\'s Account',
'updated_at' => 'Last updated',
];
+2
View File
@@ -14,6 +14,8 @@ return [
'login' => 'Sign in to :name',
'signed_in_as' => 'Signed in as',
'logout' => 'You have been logged out successfully.',
];
+19
View File
@@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Permissions Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used for user specific
| messages that we need to display to the user.
|
*/
'super_admin' => 'Super admin',
'super_admin_info' => 'Super admins have access to all of :name.',
];
+27
View File
@@ -0,0 +1,27 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| User Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used for user specific
| messages that we need to display to the user.
|
*/
'settings' => 'Account settings',
'account' => ':name\'s Account',
'updated' => '**:name** updated successfully.',
'last_login_at' => 'Last login',
'last_login_never' => 'never',
'last_login_ip' => 'Last login IP',
];
+1 -1
View File
@@ -1,4 +1,4 @@
<div x-data="{ tab: '{{ $tab }}' }" {{ $attributes->merge(['class' => 'bg-white overflow-hidden shadow rounded-lg']) }}>
<div x-data="{ tab: '{{ $tab }}' }" {{ $attributes->merge(['class' => 'bg-white overflow-hidden shadow rounded']) }}>
<div role="tablist" class="border-b border-gray-200 bg-gray-50 flex">
@foreach($tabs as $value => $label)
<button role="tab" aria-controls="{{ $value }}-tab" :tabindex="tab === '{{ $value }}' ? 0 : -1" :aria-selected="tab === '{{ $value }}' ? 'true' : 'false'" class="border-r border-gray-200 px-4 py-3 text-gray-500 text-sm leading-tight -mb-px transition duration-200 ease-in-out" :class="{ 'text-current bg-white': tab === '{{ $value }}' }" @click.prevent="tab = '{{ $value }}'">
@@ -1,6 +1,6 @@
<div class="grid grid-cols-1 md:grid-cols-3 md:gap-4 lg:gap-8">
<div class="grid grid-cols-1 md:grid-cols-7 gap-4 lg:gap-8">
<form wire:submit.prevent="update" class="col-span-2">
<form wire:submit.prevent="update" class="md:col-span-5">
<x-filament-tabs tab="account" :tabs="['account' => 'Account', 'permissions' => 'Permissions']">
@@ -19,9 +19,9 @@
<x-filament-tab id="permissions">
<x-filament-input-group>
<x-filament-checkbox name="is_super_admin" label="Super admin?" wire:model="is_super_admin" />
<x-filament-checkbox name="is_super_admin" label="filament::permissions.super_admin" wire:model="is_super_admin" />
<x-slot name="info">
Super admins have access to all of {{ config('app.name') }}.
{{ __('filament::permissions.super_admin_info', ['name' => config('app.name')]) }}
</x-slot>
</x-filament-input-group>
@@ -44,4 +44,26 @@
</form>
<x-filament-well class="md:col-span-2">
<dl class="grid grid-cols-2 gap-2 text-xs leading-tight">
<dt class="text-gray-400">{{ __('filament::admin.created_at') }}</dt>
<dd class="text-right">{{ $user->created_at->fromNow() }}</dd>
<dt class="text-gray-400">{{ __('filament::admin.updated_at') }}</dt>
<dd class="text-right">{{ $user->updated_at->fromNow() }}</dd>
<dt class="text-gray-400">{{ __('filament::user.last_login_at') }}</dt>
<dd class="text-right">{{ $user->last_login_at ? $user->last_login_at->fromNow() : __('filament::user.last_login_never') }}</dd>
@if ($user->last_login_ip)
<dt class="text-gray-400">{{ __('filament::user.last_login_ip') }}</dt>
<dd class="text-right">{{ $user->last_login_ip }}</dd>
@endif
</dl>
</x-filament-well>
</div>
@@ -1 +0,0 @@
<span @if ($classes) class="{{ $classes }}" @endif>{{ $email }}</span>
@@ -1 +0,0 @@
<span @if ($classes) class="{{ $classes }}" @endif>{{ $name }}</span>
+1 -1
View File
@@ -4,7 +4,7 @@
<li wire:key="{{ $user->id }}" class="flex">
<a class="flex-grow p-4 bg-white shadow-sm rounded-md flex items-center" href="{{ route('filament.admin.users.edit', ['user' => $user->id]) }}">
@livewire('filament::user-avatar', [
'user' => $user,
'userId' => $user->id,
'size' => 96,
'classes' => 'h-12 w-12 rounded-full'
], key($user->id))
@@ -1,12 +1,12 @@
<div @click.away="open = false" class="relative" x-data="{ open: false }">
<button @click="open = !open" class="flex items-center">
@livewire('filament::user-avatar', [
'user' => auth()->user(),
'userId' => auth()->user()->id,
'size' => 36,
'classes' => 'h-9 w-9 rounded-full',
])
@livewire('filament::user-name', [
'name' => auth()->user()->name,
@livewire('filament::auth-user-attribute', [
'attribute' => 'name',
'classes' => 'ml-3 text-sm leading-5 font-medium text-white',
])
</button>
@@ -21,16 +21,16 @@
<div class="rounded-md bg-white shadow-xs">
<p class="px-4 py-3">
<span class="block text-sm leading-5">
{{ __('filament::admin.signed_in_as') }}
{{ __('filament::auth.signed_in_as') }}
</span>
@livewire('filament::user-email', [
'email' => auth()->user()->email,
@livewire('filament::auth-user-attribute', [
'attribute' => 'email',
'classes' => 'text-sm leading-5 font-medium text-gray-900',
])
</p>
<div class="border-t border-gray-100"></div>
<div class="py-1">
<a href="{{ route('filament.admin.users.edit', ['user' => auth()->user()->id]) }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ __('filament::admin.account_settings') }}</a>
<a href="{{ route('filament.admin.users.edit', ['user' => auth()->user()->id]) }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ __('filament::user.settings') }}</a>
</div>
<div class="border-t border-gray-100"></div>
<div class="py-1">
+1 -1
View File
@@ -3,5 +3,5 @@
@section('title', $title)
@section('main')
@livewire('filament::user-edit-form', ['user' => $user])
@livewire('filament::user-edit', ['user' => $user])
@endsection
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace Filament\Http\Components;
use Illuminate\View\Component;
class Well extends Component
{
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\View\View|string
*/
public function render()
{
return <<<'blade'
<div {{ $attributes->merge(['class' => 'border border-gray-200 bg-gray-50 overflow-hidden rounded']) }}>
<div class="px-4 py-5 sm:p-6">
{{ $slot }}
</div>
</div>
blade;
}
}
+15 -1
View File
@@ -5,7 +5,6 @@ namespace Filament\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Route;
use Filament\Http\Controllers\Controller;
class LoginController extends Controller
@@ -67,4 +66,19 @@ class LoginController extends Controller
'message' => __('filament::auth.logout'),
]);
}
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
$user->timestamps = false;
$user->last_login_at = now();
$user->last_login_ip = $request->ip();
$user->save();
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ class UserController extends Controller
$this->authorize('edit', $user);
$title = __('filament::admin.user_account', ['name' => $user->name]);
$title = __('filament::user.account', ['name' => $user->name]);
return view('filament::users.edit', compact('title', 'user'));
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace Filament\Http\Livewire;
use Livewire\Component;
class AuthUserAttribute extends Component
{
public $attribute;
public $classes;
protected $listeners = ['authUserUpdated' => 'render'];
public function mount(string $attribute, string $classes = '')
{
$this->attribute = $attribute;
$this->classes = $classes;
}
public function render()
{
return <<<'blade'
<span class="{{ $classes }}">{{ auth()->user()->{$this->attribute} }}</span>
blade;
}
}
+17 -2
View File
@@ -3,6 +3,7 @@
namespace Filament\Http\Livewire;
use Livewire\Component;
use Filament\Contracts\User as UserContract;
class UserAvatar extends Component
{
@@ -12,15 +13,29 @@ class UserAvatar extends Component
protected $listeners = ['userUpdated' => 'render'];
public function mount($user, int $size, string $classes = '')
public function mount(int $userId, int $size, string $classes = '')
{
$this->user = $user;
$this->user = $this->getUser($userId);
$this->size = $size;
$this->classes = $classes;
}
public function updateUser(int $id)
{
if ($this->user->id === $id) {
$this->user = $this->getUser($id);
}
}
public function render()
{
return view('filament::livewire.user-avatar');
}
protected function getUser(int $userId)
{
$userClass = app(UserContract::class);
return $userClass::findOrFail($userId);
}
}
@@ -6,7 +6,7 @@ use Livewire\Component;
use Illuminate\Validation\Rule;
use Spatie\Permission\Contracts\Role as RoleContract;
class UserEditForm extends Component
class UserEdit extends Component
{
public $user;
public $name;
@@ -39,21 +39,27 @@ class UserEditForm extends Component
$this->user->update($validatedData);
$this->emit('notification.notify', [
'type' => 'success',
'message' => "User {$this->name} updated successfully.",
]);
if (isset($validatedData['user_roles'])) {
$this->user->syncRoles($validatedData['user_roles']);
}
$this->emit('userUpdated', $this->user->id);
if (auth()->user()->id === $this->user->id) {
$this->emit('userUpdated', $this->user);
$this->emit('authUserUpdated');
}
$this->emit('notification.notify', [
'type' => 'success',
'message' => __('filament::user.updated', ['name' => $this->name]),
]);
}
public function render()
{
$roleClass = app(RoleContract::class);
return view('filament::livewire.user-edit-form', [
return view('filament::livewire.user-edit', [
'roles' => $roleClass::all(),
]);
}
-28
View File
@@ -1,28 +0,0 @@
<?php
namespace Filament\Http\Livewire;
use Livewire\Component;
class UserEmail extends Component
{
public $email;
public $classes;
protected $listeners = ['userUpdated' => 'updateEmail'];
public function mount(string $email, string $classes = '')
{
$this->email = $email;
$this->classes = $classes;
}
public function updateEmail(array $user) {
$this->email = collect($user)->get('email', $this->email);
}
public function render()
{
return view('filament::livewire.user-email');
}
}
-28
View File
@@ -1,28 +0,0 @@
<?php
namespace Filament\Http\Livewire;
use Livewire\Component;
class UserName extends Component
{
public $name;
public $classes;
protected $listeners = ['userUpdated' => 'updateName'];
public function mount(string $name, string $classes = '')
{
$this->name = $name;
$this->classes = $classes;
}
public function updateName(array $user) {
$this->name = collect($user)->get('name', $this->name);
}
public function render()
{
return view('filament::livewire.user-name');
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ class Users extends Component
$userClass = app(UserContract::class);
return view('filament::livewire.users', [
'users' => $userClass::paginate(9),
'users' => $userClass::paginate(12),
]);
}
}
+21 -12
View File
@@ -15,8 +15,17 @@ trait FilamentUser
*/
public function initializeFilamentUser()
{
$this->mergeFillable(['is_super_admin', 'avatar']);
$this->mergeCasts(['is_super_admin' => 'boolean']);
$this->mergeFillable([
'is_super_admin',
'avatar',
'last_login_at',
'last_login_ip',
]);
$this->mergeCasts([
'is_super_admin' => 'boolean',
'last_login_at' => 'datetime',
]);
}
/**
@@ -30,16 +39,6 @@ trait FilamentUser
return Gravatar::src($this->email, (int) $size);
}
/**
* Merge fillable attributes.
*
* @return void
*/
protected function mergeFillable(array $fillable)
{
$this->fillable(collect($this->fillable)->merge($fillable)->all());
}
/**
* Send the password reset notification.
*
@@ -61,4 +60,14 @@ trait FilamentUser
{
$this->attributes['password'] = Hash::make($pass);
}
/**
* Merge fillable attributes.
*
* @return void
*/
protected function mergeFillable(array $fillable)
{
$this->fillable(collect($this->fillable)->merge($fillable)->all());
}
}