Giter Site home page Giter Site logo

resource-lock's Introduction

Resoure Lock

filament-resource-lock-art

Latest Version on Packagist Total Downloads

Filament Resource Lock is a Filament plugin that adds resource locking functionality to your site. When a user begins editing a resource, Filament Resource Lock automatically locks the resource to prevent other users from editing it at the same time. The resource will be automatically unlocked after a set period of time, or when the user saves or discards their changes.

filament-resource-lock-art

Installation

Plugin Version Filament Version PHP Version
1.x 2.x > 8.0
2.x 3.x > 8.1

Notice - Upgrading to Version 2.1.x :
In case you have published the config, make sure to update the following in your config:

   'resource' => [
       'class' => \Kenepa\ResourceLock\Resources\LockResource::class,
   ],

You can install the package via composer:

composer require kenepa/resource-lock

Then run the installation command to publish and run migration(s)

php artisan resource-lock:install

Register plugin with a panel

use Kenepa\ResourceLock\ResourceLockPlugin;
use Filament\Panel;
 
public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(ResourceLockPlugin::make());
}

You can publish run the config (optional)

php artisan vendor:publish --tag=resource-lock-config

Usage

The Filament Resource Lock package enables you to lock a resource and prevent other users from editing it at the same time. Currently, this package only locks the EditRecord page and the edit modal when editing a simple modal resource. Follow the steps below to add locks to your resources.

Add Locks to your model

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.

// Post.php

use Kenepa\ResourceLock\Models\Concerns\HasLocks;

class Post extends Model
{
    use HasFactory;
    use HasLocks;

    protected $table = 'posts';

    protected $guarded = [];
}

Add Locks to your EditRecord Page

The second step is to add the UsesResourceLock trait to your EditRecord page. The UsesResourceLock trait enables the locking function on your edit page.

// EditPost.php

use Kenepa\ResourceLock\Resources\Pages\Concerns\UsesResourceLock;

class EditPost extends EditRecord
{
    use UsesResourceLock;

    protected static string $resource = PostResource::class;
}

Simple modal Resource

If your resource is a simple modal resource, you'll need to use the UsesSimpleResourceLock trait instead.

// ManagePosts.php

use Kenepa\ResourceLock\Resources\Pages\Concerns\UsesSimpleResourceLock;

class ManagePosts extends ManageRecords
{
    use UsesSimpleResourceLock;

    protected static string $resource = PostResource::class;

}

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

filament-resource-lock-art

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

filament-resource-lock-art

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.

// resource-lock.php

   /*
    |--------------------------------------------------------------------------
    | Resource Unlocker
    |--------------------------------------------------------------------------
    |
    | 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.
    |
    */

    'unlocker' => [
        'limited_access' => true,
        'gate' => 'unlock'
    ],

Example

// Example using gates
// More info about gates: https://laravel.com/docs/authorization#writing-gates
Gate::define('unlock', function (User $user, Post $post) {
  return $user->email === '[email protected]';
});

// Example using spatie permission package
Permission::create(['name' => 'unlock']);

Using custom models

Sometimes, you may have a customized implementation for the User model in your application, or you may want to use a custom class for the ResourceLock functionality. In such cases, you can update the configuration file to specify the new class you want to use. This will ensure that the ResourceLock functionality works as expected with the new implementation.

// resource-lock.php

 /*
    |--------------------------------------------------------------------------
    | Models
    |--------------------------------------------------------------------------
    |
    | The models configuration specifies the classes that represent your application's
    | data objects. This configuration is used by the framework to interact with
    | the application's data models. You can even implement your own ResourceLock model.
    |
    */

    'models' => [
        'User' => \App\Models\CustomUser::class,
         'ResourceLock' => \App\Models\CustomResourceLock::class,
    ],

Displaying the user who has locked the resource

Use the display_resource_lock_owner within the resource-lock.php config to control whether or not the locked resource owner is displayed in the modal. Set the option to true to show the owner's username or other identifying information. The modal can be triggered by a button click or automatically when the resource is accessed.

By default, the package displays the name of the user: $userModel->name. However, if your user model doesn't have a name or you want to display a different identifier, you can create a custom action to overwrite the default behavior.

This package uses actions which allows you to implement your own custom logic. An action class is nothing more than a simple class with a method that executes some logic. Learn more about actions

To create a custom action, first create a file within your project and name it CustomGetResourceLockOwnerAction.php, for example. In this file, create a new class that extends the GetResourceLockOwnerAction class and override the execute method to return the desired identifier. For example:

// CustomGetResourceLockOwnerAction.php

namespace App\Actions;

use Kenepa\ResourceLock\Actions\GetResourceLockOwnerAction;

class CustomResourceLockOwnerAction extends GetResourceLockOwnerAction
{
    public function execute($userModel): string|null
    {
        return $userModel->email;
    }
}

Next, register your custom action within the resource-lock.config file. Replace the default get_resource_lock_owner_action value with your custom action's class name. For example:

// resource-lock.php

    'actions' => [
-       'get_resource_lock_owner_action' => \Kenepa\ResourceLock\Actions\GetResourceLockOwnerAction::class
+       'get_resource_lock_owner_action' => \Kenepa\ResourceLock\Actions\CustomGetResourceLockOwnerAction::class   
    ],

Overriding default functionality

If you need some custom functionality beyond what the traits provide, you can override the functions that they use. For example, if you want to change the URL that the "Return" button redirects to, you can override the resourceLockReturnUrl() function. By default, this button takes you to the index page of the resource, but you can change it to whatever URL you want by adding your custom implementation in the resourceLockReturnUrl() function.

For instance, if you want the "Return" button to redirect to https://laracasts.com, you can override the function as follows:

     public function resourceLockReturnUrl(): string 
    {
        return 'https://laracasts.com';
    }

Now the return url will redirect to laracasts.com

This will change the behavior of the "Return" button to redirect to the provided URL.

Publishing migrations, configuration and view

php artisan vendor:publish --tag="resource-lock-migrations"
php artisan migrate

You can publish and run the migrations with:

php artisan vendor:publish --tag="resource-lock-migrations"
php artisan migrate

You can publish the config file with:

php artisan vendor:publish --tag="resource-lock-config"

Optionally, you can publish the views using

Note: Publishing Blade views can introduce breaking changes into your app. If you're interested in how to stay safe, see this article by Dan Harrin.

php artisan vendor:publish --tag="resource-lock-views"

T

Coming soon

  • Locked status indicator for table rows
  • Polling
  • Optimistic Locking

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

License

The MIT License (MIT). Please see License File for more information.

resource-lock's People

Contributors

jehizkia avatar howdu avatar codewithdennis avatar atmonshi avatar adriaardila avatar bashgeek avatar danielbehrendt avatar dariusiii avatar hamrak avatar munaalb avatar olyak95 avatar stereshko avatar mohamedsabil83 avatar teloconfesso avatar

Stargazers

Denis Colli Spalenza avatar Ignacio Muñoz Fernández avatar Atila Silva avatar Yacouba SAWADOGO avatar Mortexa avatar  avatar Alessandro Ribeiro avatar René Henrich avatar Jean-Charles Bournot avatar Douglas WP avatar Kaan avatar Adama KO avatar Lucas Yang avatar Ademir Mazer Jr [ Nuno ] avatar Joris Noordermeer avatar Simon Rigby avatar Onur Keskin, Ph.D. avatar Fritz Bester avatar  avatar Marco Germani avatar Marek Racík avatar mel avatar Chris Reed avatar farid avatar Cha avatar Habib Talib avatar Jasper avatar Jèfferson Gonçalves avatar Loan Besson avatar MEHDI avatar ChaairSoft avatar  avatar Marceau Casals avatar  avatar mohamed ali avatar John Dexter Gamo avatar Roberto Ferrari avatar Marc Beinder avatar Roberto Santana avatar Alberto Benavides avatar Vladislav Kambulov avatar Hugh Messenger avatar Sadegh Barzegar avatar Mostafa Khorashadi Zadeh avatar Diana avatar Nassim avatar Spacetum Co avatar  avatar Brayan avatar  avatar Kieran Proctor avatar Radu Vasile Catalin avatar Darren Glanville avatar Bagaskara Wisnu Gunawan avatar Guus avatar Tom Shafer avatar  avatar MarcS avatar AbdulQader avatar Chris Hardie avatar Lloric Mayuga Garcia avatar Juan Timaná avatar Frrrrr avatar Francesco Apruzzese avatar Ammar Al Nouirah avatar AriaieBOY avatar Rômulo Ramos avatar guanguans avatar Camille Scholtz avatar Sun RA avatar  avatar Angel González avatar Stefan Britz avatar zaX avatar Salvatore Scalzi avatar Musa avatar Maher Almatari avatar SuperDev avatar Mack Hankins avatar  avatar  avatar

Watchers

 avatar Mack Hankins avatar Camille Scholtz avatar  avatar Pavel Andreichenko avatar LUIGI PANNIELLO avatar

resource-lock's Issues

Resource lock not working for modals

In our project we're depening on our modals.
Too bad we cannot use this plugin as it does not support locking modals (in our case specifically slideovers).
We hope to see this functionality working in the future to be able to use this plugin in all our applications.

Unlocking a record doesn't create a new lock

When resourceLockObserverUnlock() is called in src/Resources/Pages/Concerns/UsesResourceLock.php by clicking the button to "Unlock page" the record isn't locked by the user that clicked the button.

I noticed that save() has a call to $this->record->refresh(). I tried adding this to resourceLockObserverUnlock() before attempting to lock the record and it appears to work:

public function resourceLockObserverUnlock()
    {
        if ($this->record->unlock(force: true)) {
            $this->closeLockedResourceModal();
            // refresh the record before attempting to re-lock
            $this->record->refresh();
            $this->record->lock();
        }
    }

Should the record be refreshed prior to attempting to gain a new lock?

Thanks for this great plugin!

Red bar at the bottom of every page after installing

After installing Resource Lock there is a red bar at the bottom of every page.
image

When inspecting the HTML I know this is from this package

<div wire:id="DVMC6eW3Ywy82NGFmshx" x-init="resourceLockObserverInit" class="bg-red-500">

    <script>
        function resourceLockObserverInit() {
            Livewire.emit('resourceLockObserver::init')
        }

        window.onbeforeunload = function () {
            Livewire.emit('resourceLockObserver::unload')
        };

        window.addEventListener('close-modal', event => {
            if (event.detail.id.endsWith('-table-action')) {
                Livewire.emit('resourceLockObserver::unload')
            }
        })
    </script>

    <div x-data="{

        isOpen: false,

        livewire: null,

        close: function () {
            this.isOpen = false

            this.$refs.modalContainer.dispatchEvent(new CustomEvent('modal-closed', { id: 'resourceIsLockedNotice' }))
        },

        open: function () {
            this.isOpen = true

            this.$refs.modalContainer.dispatchEvent(new CustomEvent('modal-opened', { id: 'resourceIsLockedNotice' }))
        },

    }" x-trap.noscroll="isOpen" x-on:close-modal.window="if ($event.detail.id === 'resourceIsLockedNotice') close()" x-on:open-modal.window="if ($event.detail.id === 'resourceIsLockedNotice') open()" role="dialog" aria-modal="true" class="filament-modal inline-block" wire:ignore.self="">
    

    <div x-show="isOpen" x-transition.duration.300ms.opacity="" class="fixed inset-0 z-40 min-h-full overflow-y-auto overflow-x-hidden transition flex items-center" style="display: none;">
        <div aria-hidden="true" class="filament-modal-close-overlay fixed inset-0 w-full h-full bg-black/50"></div>

        <div x-ref="modalContainer" class="relative w-full cursor-pointer pointer-events-none transition my-auto p-4" disabled="disabled">
            <div x-data="{ isShown: false }" x-init="$nextTick(()=> {
                    isShown = isOpen
                    $watch('isOpen', () => isShown = isOpen)
                })" x-show="isShown" x-on:keydown.window.escape="$dispatch('close-modal', { id: 'resourceIsLockedNotice' })" x-transition:enter="ease duration-300" x-transition:leave="ease duration-300" x-transition:enter-start="translate-y-8" x-transition:enter-end="translate-y-0" x-transition:leave-start="translate-y-0" x-transition:leave-end="translate-y-8" class="filament-modal-window w-full py-2 bg-white cursor-default pointer-events-auto dark:bg-gray-800 relative rounded-xl mx-auto max-w-sm" style="display: none;">
                
                <div class="">
                    <div class="space-y-2">
                        
                                            </div>

                    <div class="filament-modal-content space-y-2 p-2">
                        
                                                    <div class="px-4 py-2 space-y-4">
                                <div x-data="{ resourceLockOwner: null}" @open-modal.window="(event) => { resourceLockOwner = event.detail.resourceLockOwner}">
            <div class="flex justify-center ">
                <button type="button" class="filament-icon-button flex items-center justify-center rounded-full relative outline-none hover:bg-gray-500/5 disabled:opacity-70 disabled:cursor-not-allowed disabled:pointer-events-none text-primary-500 focus:bg-primary-500/10 dark:hover:bg-gray-300/5 w-12 h-12">
        
        <svg wire:loading.remove.delay="" wire:target="" class="filament-icon-button-icon w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
  <path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path>
</svg>
        
            </button>
            </div>
            <p x-show="resourceLockOwner" class="text-center" style="display: none;">
                <span x-text="resourceLockOwner" class="font-bold"></span> is currently editing this page, so it is currently locked for editing.
            </p>
            <p x-show="resourceLockOwner === null" class="text-center">
                This page has been locked because another user is currently editing it.
            </p>
        </div>

        <div x-data="{url: '/'}" @open-modal.window="(event) => { url = event.detail.returnUrl}" class="flex flex-col justify-center space-y-2">

            
            <a class="block filament-button filament-button-size-md inline-flex items-center justify-center py-1 gap-1 font-medium rounded-lg border transition-colors outline-none focus:ring-offset-2 focus:ring-2 focus:ring-inset min-h-[2.25rem] px-4 text-sm text-white shadow focus:ring-white border-transparent bg-primary-600 hover:bg-primary-500 focus:bg-primary-700 focus:ring-offset-primary-700 filament-page-button-action" :href="url" href="/">

            <span>
                Return
            </span>

            </a>
        </div>
                            </div>
                        
                        
                    </div>

                    <div class="space-y-2">
                        
                                            </div>
                </div>
            </div>
        </div>
    </div>
</div>
</div>

Error with simple modal lock

Hello, I'm getting this error when attempting to add the UsesSimpleResourceLock to a simple modal resource (ex. ManageUsers).

[2024-01-25 13:31:54] local.ERROR: Declaration of Kenepa\ResourceLock\Resources\Pages\Concerns\UsesSimpleResourceLock::mountTableAction(string $name, ?string $record = null) must be compatible with Filament\Resources\Pages\ListRecords::mountTableAction(string $name, ?string $record = null, array $arguments = []): mixed {"exception":"[object] (Symfony\\Component\\ErrorHandler\\Error\\FatalError(code: 0): Declaration of Kenepa\\ResourceLock\\Resources\\Pages\\Concerns\\UsesSimpleResourceLock::mountTableAction(string $name, ?string $record = null) must be compatible with Filament\\Resources\\Pages\\ListRecords::mountTableAction(string $name, ?string $record = null, array $arguments = []): mixed at /var/www/nudato-v2/vendor/kenepa/resource-lock/src/Resources/Pages/Concerns/UsesSimpleResourceLock.php:26) [stacktrace] #0 {main} "}

Allow custom/override the ResourceLockResource

Now i believe it is not possible to use / override a custom Resource (for example in the config).

in ResourceLockPlugin.php the resources array could be fetched from config variable .

in resource-lock.php add

use Kenepa\ResourceLock\Resources\ResourceLockResource;

 'resource' => [
    'class' => ResourceLockResource::class
 ]

and in the register function of ResourceLockPlugin.php

$panel->resources([
   config('resource-lock.resource.class', ResourceLockResource::class),
]);

lock() method in HasLocks trait does not allow for other guard than default one ('web')

When loggedIn as user with multiple guards configured and the user is not using the 'web' guard... auth()->user()-id IS NULL.

so the auth() should add the ->guard() like so :

In the lock() method in the HasLocks.php trait file on line:

$resourceLock->user_id = auth()->user()->id;

doesn't allow for an custom guard. So you should get the guard name of the current logged in user with this method for example:

/**
     * @return array|null
     */
    private function getCurrentAuthGuardName()
    {
        $guards = array_keys(config('auth.guards'));

        foreach ($guards as $guard) {
            if (app()['auth']->guard($guard)->check()) {
                return $guard;
            }
        }

        return null;
    }

then you could add this:

$guard = $this->getCurrentAuthGuardName();
$resourceLock->user_id = auth()->guard($guard)->user()->id;

Do the same for the other two functions in this Trait:

isLockedByCurrentUser()

and

lockCreatedByCurrentUser()

Suggestion to have a Take-Over button

Is it possible to have a Take-Over button?

If a user wants to take over the resource by clicking the take-over button, the current user (who is updating or editing that resource) will be notified.

Make NavigationLabel and NavigationGroup translatable

getNavigationLabel() and getNavigationGroup() should be also translatable like so:

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'));
    }

Resource lock in not removed when leaving page on Safari

Description

Resource lock not removed after visiting and leaving a resource in Safari browser.
Description: A user has reported an issue where the resource lock remains in place after they visit and leave a resource while using the Safari browser. According to the user's account, the resource lock should be removed automatically when they navigate away from the resource. However, in Safari, the lock remains intact even after leaving the resource.
Expected Behavior: The resource lock should be automatically removed when the user visits and then leaves a resource, regardless of the browser being used.
Steps to Reproduce:
1. Open Safari browser.
2. Visit a resource.
3. Navigate away from the resource.
4. Check if the resource lock is removed or still present.

Save from edit record dont work with filament 3.2.65

on version 3.2.65 the 'save' function by edit record have now 2 parameters:

before:
public function save(bool $shouldRedirect = true): void
after:
save(bool $shouldRedirect = true, bool $shouldSendSavedNotification = true): void

u need to update de edit record.

Issue after composer update

Issue after composer update
i tried to install it again using

composer require kenepa/resource-lock

Then error shows after you fix the issue on this thread #42

Errors on the screenshot

image

Error using the UsesSimpleResourcesLock

im having error using the UsesSimpleResourcesLock
image

use Kenepa\ResourceLock\Resources\Pages\Concerns\UsesSimpleResourceLock;
use UsesSimpleResourceLock;

Declaration of Kenepa\ResourceLock\Resources\Pages\Concerns\UsesSimpleResourceLock::mountTableAction(string $name, ?string $record = null): mixed must be compatible with Filament\Resources\Pages\ListRecords::mountTableAction(string $name, ?string $record = null, array $arguments = []): mixed

Feature Request: Allow Passing Additional Arguments to Gate Definitions and Checks

Description:

Currently, when defining a gate using Gate::define with a policy and method, there's no direct way to pass additional arguments beyond the authenticated user. This limitation makes it difficult to use policies for authorization scenarios that require context beyond the user, such as checking permissions on a specific model instance.

Suggested Solution:

I propose adding the ability to pass additional arguments when defining gates and when performing authorization checks. This could be achieved in a few ways:

Variadic Arguments:

Modify Gate::define to accept a variable number of arguments after the policy and method.
Modify Gate::allows to accept the same variable number of arguments after the gate name.
Internally, pass these additional arguments to the policy method when it's called.

Array of Arguments:

Allow passing an array as the second argument to Gate::define, where the first element is the policy/method and the rest are additional arguments.
Allow passing the same array structure to Gate::allows.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.