diff --git a/README.md b/README.md
index ad8de67..305e25a 100644
--- a/README.md
+++ b/README.md
@@ -39,9 +39,9 @@ the [EditRecord](https://filamentphp.com/docs/2.x/admin/resources/editing-record
a [simple modal resource.](https://filamentphp.com/docs/2.x/admin/resources/getting-started#simple-modal-resources)
Follow the steps below to add locks to your resources.
-### Add Locks to your modal
+### Add Locks to your model
-The first step is to add the HasLocks trait to the modal of your resource. The HasLocks trait enables the locking
+The first step is to add the HasLocks trait to the model of your resource. The HasLocks trait enables the locking
functionality on your model.
```php
@@ -95,13 +95,21 @@ class ListExamples extends ManageRecords
And that's it! Your resource is now able to be locked. Refer to the documentation below for more information on how to
configure the locking functionality.
+## Resource Lock manager
+
+
+
+The package also provides a simple way to manage and view all your active and expired locks within your app. And it also
+provides a way to quickly unlock all resources or specific locks.
+
## Configuration
### Access
-You can restrict the access to the **Unlock** button by adjusting the access variable. Enabling the "limited" key and
+You can restrict the access to the **Unlock** button or to the resource manager by adjusting the access variable.
+Enabling the "limited" key and
setting it to true allows you to specify either a Laravel Gate class or a permission name from
the [Spatie Permissions package](https://github.com/spatie/laravel-permission).
@@ -119,11 +127,24 @@ the [Spatie Permissions package](https://github.com/spatie/laravel-permission).
*/
'unlocker' => [
- 'limited_access' => false,
+ 'limited_access' => true,
'gate' => 'unlock-resource'
],
```
+Example
+
+```php
+
+// Example using gates
+Gate::define('unlock-resource', function (User $user, Post $post) {
+ return $user->email === 'admin@mail.com';
+});
+
+// Example using spatie permission package
+Permission::create(['name' => 'unlock-resource']);
+```
+
### Using custom models
Sometimes, you may have a customized implementation for the User model in your application, or you may want to use a
@@ -199,6 +220,12 @@ Optionally, you can publish the views using
php artisan vendor:publish --tag="resource-lock-views"
```
+## Coming soon
+
+- Locked status indicator for table rows
+- Polling
+- Displaying which users has locked a resource
+
## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
diff --git a/composer.json b/composer.json
index 647d439..e1123b5 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
{
"name": "kenepa/resource-lock",
- "description": "This is my package resource lock",
+ "description": "Filament Resource Lock is a Filament plugin that adds resource locking functionality to your site.",
"keywords": [
"Kenepa",
"laravel",
diff --git a/config/resource-lock.php b/config/resource-lock.php
index e38ca8d..15f7d9d 100644
--- a/config/resource-lock.php
+++ b/config/resource-lock.php
@@ -20,12 +20,13 @@
/*
|--------------------------------------------------------------------------
- | Resource Unlocker
+ | Resource Unlocker Button
|--------------------------------------------------------------------------
|
| The unlocker configuration specifies whether limited access is enabled for
- | the resource lock feature. If limited access is enabled, only specific
- | users or roles will be able to unlock locked resources.
+ | the resource unlock button. If limited access is enabled, only specific
+ | users or roles will be able to unlock locked resources directly from
+ | the modal.
|
*/
@@ -34,6 +35,25 @@
// 'gate' => ''
],
+ /*
+ |--------------------------------------------------------------------------
+ | Resource Lock Manager
+ |--------------------------------------------------------------------------
+ |
+ | The resource lock manager provides a simple way to view all resource locks
+ | of your application. It provides several ways to quickly unlock all or
+ | specific resources within your app.
+ |
+ */
+
+ 'manager' => [
+ 'navigation_label' => 'Resource Lock Manager',
+ 'navigation_group' => 'Ticket',
+ 'navigation_sort' => 1,
+ 'limited_access' => true,
+ 'gate' => 'manager'
+ ],
+
/*
|--------------------------------------------------------------------------
| Lock timeout (in minutes)
@@ -49,15 +69,16 @@
/*
|--------------------------------------------------------------------------
- | Throw Forbidden Exception
+ | Check Locks before saving
|--------------------------------------------------------------------------
|
- | The throw_forbidden_exception configuration specifies whether a 403 forbidden
- | exception should be thrown if a tech-savvy user is able to bypass the locked
- | resource modal and attempt to save the resource.
+ | The check_locks_before_saving configuration specifies whether a lock of a resource will be checked
+ | before saving a resource if a tech-savvy user is able to bypass the locked
+ | resource modal and attempt to save the resource. In some cases you may want to turns this off.
+ | It's recommended to keep this on.
|
*/
- 'throw_forbidden_exception' => true,
+ 'check_locks_before_saving' => true,
];
diff --git a/resources/lang/de/manager.php b/resources/lang/de/manager.php
new file mode 100644
index 0000000..1c45b53
--- /dev/null
+++ b/resources/lang/de/manager.php
@@ -0,0 +1,10 @@
+ 'Aktiv',
+ 'expired' => 'Abgelaufen',
+ 'unlock' => 'Entsperren',
+ 'unlocked' => 'Entsperrter Ressourcen',
+ 'unlocked_selected' => 'Ausgewählte Ressourcen entsperrt',
+ 'unlock_all' => 'Alle Ressourcen entsperren'
+];
\ No newline at end of file
diff --git a/resources/lang/en/manager.php b/resources/lang/en/manager.php
new file mode 100644
index 0000000..c7179c1
--- /dev/null
+++ b/resources/lang/en/manager.php
@@ -0,0 +1,10 @@
+ 'Active',
+ 'expired' => 'Expired',
+ 'unlock' => 'Unlock',
+ 'unlocked' => 'Unlocked resource',
+ 'unlocked_selected' => 'Unlocked selected resources',
+ 'unlock_all' => 'Unlock all resources'
+];
\ No newline at end of file
diff --git a/resources/lang/es/manager.php b/resources/lang/es/manager.php
new file mode 100644
index 0000000..5513241
--- /dev/null
+++ b/resources/lang/es/manager.php
@@ -0,0 +1,10 @@
+ 'Activo',
+ 'expired' => 'Expirado',
+ 'unlock' => 'Desbloquear',
+ 'unlocked' => 'Recurso desbloqueado',
+ 'unlocked_selected' => 'Recursos seleccionados desbloqueados',
+ 'unlock_all' => 'Desbloquear todos los recursos'
+];
\ No newline at end of file
diff --git a/resources/lang/fr/manager.php b/resources/lang/fr/manager.php
new file mode 100644
index 0000000..d783755
--- /dev/null
+++ b/resources/lang/fr/manager.php
@@ -0,0 +1,10 @@
+ 'Actif',
+ 'expired' => 'Expiré',
+ 'unlock' => 'Déverrouiller',
+ 'unlocked' => 'Ressource déverrouillée',
+ 'unlocked_selected' => 'Ressources sélectionnées déverrouillées',
+ 'unlock_all' => 'Déverrouiller toutes les ressources'
+];
\ No newline at end of file
diff --git a/resources/lang/nl/manager.php b/resources/lang/nl/manager.php
new file mode 100644
index 0000000..d7aa602
--- /dev/null
+++ b/resources/lang/nl/manager.php
@@ -0,0 +1,10 @@
+ 'Actief',
+ 'expired' => 'Verlopen',
+ 'unlock' => 'Ontgrendelen',
+ 'unlocked' => 'Ontgrendelde resource',
+ 'unlocked_selected' => 'Geselecteerde resource ontgrendeld',
+ 'unlock_all' => 'Alle resources ontgrendelen'
+];
\ No newline at end of file
diff --git a/src/Models/ResourceLock.php b/src/Models/ResourceLock.php
index 1e84aaf..9173fab 100644
--- a/src/Models/ResourceLock.php
+++ b/src/Models/ResourceLock.php
@@ -2,6 +2,7 @@
namespace Kenepa\ResourceLock\Models;
+use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -20,4 +21,11 @@ public function lockable(): MorphTo
{
return $this->morphTo();
}
+
+ public function isExpired(): bool
+ {
+ $expiredDate = (new Carbon($this->updated_at))->addMinutes(config('resource-lock.lock_timeout'));
+
+ return Carbon::now()->greaterThan($expiredDate);
+ }
}
diff --git a/src/ResourceLockServiceProvider.php b/src/ResourceLockServiceProvider.php
index f2c95e9..08f3d7e 100644
--- a/src/ResourceLockServiceProvider.php
+++ b/src/ResourceLockServiceProvider.php
@@ -5,6 +5,7 @@
use Filament\Facades\Filament;
use Filament\PluginServiceProvider;
use Illuminate\Support\Facades\Blade;
+use Kenepa\ResourceLock\Resources\ResourceLockResource;
use Livewire\Livewire;
use Spatie\LaravelPackageTools\Commands\InstallCommand;
use Spatie\LaravelPackageTools\Package;
@@ -14,7 +15,7 @@ class ResourceLockServiceProvider extends PluginServiceProvider
public static string $name = 'resource-lock';
protected array $resources = [
- // CustomResource::class,
+ ResourceLockResource::class,
];
public function configurePackage(Package $package): void
diff --git a/src/Resources/Pages/Concerns/UsesResourceLock.php b/src/Resources/Pages/Concerns/UsesResourceLock.php
index 39d86c3..cd0ca40 100644
--- a/src/Resources/Pages/Concerns/UsesResourceLock.php
+++ b/src/Resources/Pages/Concerns/UsesResourceLock.php
@@ -67,8 +67,13 @@ public function resourceLockObserverUnlock()
*/
public function save(bool $shouldRedirect = true): void
{
- if (config('resource-lock.throw_forbidden_exception', true)) {
- abort_unless($this->record->isLocked() && $this->record->isLockedByCurrentUser(), 403);
+ if (config('resource-lock.check_locks_before_saving', true)) {
+ $this->record->refresh();
+ if ($this->record->isLocked() && !$this->record->isLockedByCurrentUser()) {
+ $this->checkIfResourceLockHasExpired($this->record);
+ $this->lockResource($this->record);
+ return;
+ }
}
parent::save($shouldRedirect);
diff --git a/src/Resources/Pages/Concerns/UsesSimpleResourceLock.php b/src/Resources/Pages/Concerns/UsesSimpleResourceLock.php
index 91eee54..b81fd67 100644
--- a/src/Resources/Pages/Concerns/UsesSimpleResourceLock.php
+++ b/src/Resources/Pages/Concerns/UsesSimpleResourceLock.php
@@ -33,6 +33,18 @@ public function mountTableAction(string $name, ?string $record = null)
$this->lockResource($this->resourceRecord);
}
+ public function callMountedTableAction(?string $arguments = null) {
+ if (config('resource-lock.check_locks_before_saving', true)) {
+ $this->resourceRecord->refresh();
+ if ($this->resourceRecord->isLocked() && !$this->resourceRecord->isLockedByCurrentUser()) {
+ $this->checkIfResourceLockHasExpired($this->resourceRecord);
+ $this->lockResource($this->resourceRecord);
+ return;
+ }
+ }
+ parent::callMountedTableAction($arguments);
+ }
+
public function resourceLockObserverUnload()
{
$this->resourceRecord->unlock();
diff --git a/src/Resources/ResourceLockResource.php b/src/Resources/ResourceLockResource.php
new file mode 100644
index 0000000..81ff7d2
--- /dev/null
+++ b/src/Resources/ResourceLockResource.php
@@ -0,0 +1,124 @@
+schema([
+ //
+ ]);
+ }
+
+ public static function table(Table $table): Table
+ {
+ return $table
+ ->columns([
+ TextColumn::make('id')->label('Lock ID'),
+ TextColumn::make('user.id')->label('User ID'),
+ TextColumn::make('lockable.id')->label('Lockable ID'),
+ TextColumn::make('lockable_type'),
+ TextColumn::make('created_at'),
+ TextColumn::make('updated_at'),
+ BadgeColumn::make('updated_at')->label('Expired')
+ ->color(static function ($record): string {
+ if ($record->isExpired()) {
+ return 'warning';
+ }
+ return 'success';
+ })
+ ->icon(static function ($record): string {
+ if ($record->isExpired()) {
+ return 'heroicon-o-lock-open';
+ }
+
+ return 'heroicon-o-lock-closed';
+ })->formatStateUsing(static function ($record) {
+ if ($record->isExpired()) {
+ return __('resource-lock::manager.expired');
+ }
+
+ return __('resource-lock::manager.active');
+ })
+ ])
+ ->filters([
+ //
+ ])
+ ->actions([
+ Tables\Actions\DeleteAction::make()
+ ->icon('heroicon-o-lock-open')
+ ->successNotificationTitle(__('resource-lock::manager.unlocked'))
+ ->label(__('resource-lock::manager.unlock')),
+ ])
+ ->bulkActions([
+ Tables\Actions\DeleteBulkAction::make()
+ ->deselectRecordsAfterCompletion()
+ ->requiresConfirmation()
+ ->icon('heroicon-o-lock-open')
+ ->successNotificationTitle(__('resource-lock::manager.unlocked_selected'))
+ ->label(__('resource-lock::manager.unlock')),
+ ]);
+ }
+
+ public static function getPages(): array
+ {
+ return [
+ 'index' => ManageResourceLocks::route('/'),
+ ];
+ }
+
+ public static function canViewAny(): bool
+ {
+ if (config('resource-lock.manager.limited_access')) {
+ return Gate::allows(config('resource-lock.manager.gate'));
+ }
+
+ return true;
+ }
+
+ public static function canDeleteAny(): bool
+ {
+ if (config('resource-lock.manager.limited_access')) {
+ return Gate::allows(config('resource-lock.manager.gate'));
+ }
+
+ return true;
+ }
+
+ public static function getNavigationLabel(): string
+ {
+ return config('resource-lock.manager.navigation_label','Resource Lock Manager');
+ }
+
+ public static function getNavigationGroup(): ?string
+ {
+ return config('resource-lock.manager.navigation_group');
+ }
+
+ protected static function getNavigationSort(): ?int
+ {
+ return config('resource-lock.manager.navigation_sort');
+ }
+}
diff --git a/src/Resources/ResourceLockResource/ManageResourceLocks.php b/src/Resources/ResourceLockResource/ManageResourceLocks.php
new file mode 100644
index 0000000..c73e07d
--- /dev/null
+++ b/src/Resources/ResourceLockResource/ManageResourceLocks.php
@@ -0,0 +1,26 @@
+icon('heroicon-o-lock-open')
+ ->action(fn () => ResourceLock::truncate())
+ ->requiresConfirmation()
+ ];
+ }
+}