├── .github └── workflows │ ├── dependabot-auto-merge.yml │ ├── fix-php-code-style-issues.yml │ ├── phpstan.yml │ ├── run-tests.yml │ └── update-changelog.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── composer.json ├── config └── filament-auditing.php ├── phpstan-baseline.neon ├── phpstan.neon.dist ├── phpunit.xml.dist ├── resources ├── lang │ ├── ar │ │ └── filament-auditing.php │ ├── de │ │ └── filament-auditing.php │ ├── en │ │ └── filament-auditing.php │ ├── es │ │ └── filament-auditing.php │ ├── fr │ │ └── filament-auditing.php │ ├── ko │ │ └── filament-auditing.php │ └── pt_BR │ │ └── filament-auditing.php └── views │ └── tables │ ├── columns │ └── key-value.blade.php │ └── custom-audit-content.blade.php ├── src ├── FilamentAuditingServiceProvider.php └── RelationManagers │ └── AuditsRelationManager.php └── tests ├── ArchTest.php ├── ExampleTest.php ├── Pest.php └── TestCase.php /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: dependabot-auto-merge 2 | on: pull_request_target 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 5 12 | if: ${{ github.actor == 'dependabot[bot]' }} 13 | steps: 14 | 15 | - name: Dependabot metadata 16 | id: metadata 17 | uses: dependabot/fetch-metadata@v2.3.0 18 | with: 19 | github-token: "${{ secrets.GITHUB_TOKEN }}" 20 | 21 | - name: Auto-merge Dependabot PRs for semver-minor updates 22 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} 23 | run: gh pr merge --auto --merge "$PR_URL" 24 | env: 25 | PR_URL: ${{github.event.pull_request.html_url}} 26 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 27 | 28 | - name: Auto-merge Dependabot PRs for semver-patch updates 29 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} 30 | run: gh pr merge --auto --merge "$PR_URL" 31 | env: 32 | PR_URL: ${{github.event.pull_request.html_url}} 33 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 34 | -------------------------------------------------------------------------------- /.github/workflows/fix-php-code-style-issues.yml: -------------------------------------------------------------------------------- 1 | name: Fix PHP code style issues 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.php' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | php-code-styling: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 5 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | with: 20 | ref: ${{ github.head_ref }} 21 | 22 | - name: Fix PHP code style issues 23 | uses: aglipanci/laravel-pint-action@2.5 24 | 25 | - name: Commit changes 26 | uses: stefanzweifel/git-auto-commit-action@v5 27 | with: 28 | commit_message: Fix styling 29 | -------------------------------------------------------------------------------- /.github/workflows/phpstan.yml: -------------------------------------------------------------------------------- 1 | name: PHPStan 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.php' 7 | - 'phpstan.neon.dist' 8 | - '.github/workflows/phpstan.yml' 9 | 10 | jobs: 11 | phpstan: 12 | name: phpstan 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 5 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Setup PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: '8.3' 22 | coverage: none 23 | 24 | - name: Install composer dependencies 25 | uses: ramsey/composer-install@v3 26 | 27 | - name: Run PHPStan 28 | run: ./vendor/bin/phpstan --error-format=github 29 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.php' 7 | - '.github/workflows/run-tests.yml' 8 | - 'phpunit.xml.dist' 9 | - 'composer.json' 10 | - 'composer.lock' 11 | 12 | jobs: 13 | test: 14 | runs-on: ${{ matrix.os }} 15 | timeout-minutes: 5 16 | strategy: 17 | fail-fast: true 18 | matrix: 19 | os: [ubuntu-latest] 20 | php: [8.4, 8.3, 8.2] 21 | laravel: [12.*, 11.*,10.*] 22 | stability: [prefer-lowest, prefer-stable] 23 | include: 24 | - laravel: 12.* 25 | testbench: 10.* 26 | - laravel: 11.* 27 | testbench: 9.* 28 | - laravel: 10.* 29 | testbench: 8.* 30 | 31 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} 32 | 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@v4 36 | 37 | - name: Setup PHP 38 | uses: shivammathur/setup-php@v2 39 | with: 40 | php-version: ${{ matrix.php }} 41 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 42 | coverage: none 43 | 44 | - name: Setup problem matchers 45 | run: | 46 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 47 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 48 | 49 | - name: Install dependencies 50 | run: | 51 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 52 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 53 | 54 | - name: List Installed Dependencies 55 | run: composer show -D 56 | 57 | - name: Execute tests 58 | run: vendor/bin/pest --ci 59 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: "Update Changelog" 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | update: 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 5 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | with: 19 | ref: main 20 | 21 | - name: Update Changelog 22 | uses: stefanzweifel/changelog-updater-action@v1 23 | with: 24 | latest-version: ${{ github.event.release.name }} 25 | release-notes: ${{ github.event.release.body }} 26 | 27 | - name: Commit updated CHANGELOG 28 | uses: stefanzweifel/git-auto-commit-action@v5 29 | with: 30 | branch: main 31 | commit_message: Update CHANGELOG 32 | file_pattern: CHANGELOG.md 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /vendor 3 | .phpunit.result.cache 4 | Homestead.json 5 | Homestead.yaml 6 | auth.json 7 | npm-debug.log 8 | yarn-error.log 9 | /.idea 10 | /.vscode 11 | /build 12 | /coverage 13 | .DS_Store 14 | composer.phar 15 | phpunit.xml 16 | phpstan.neon 17 | composer.lock 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to the "Filament Laravel Auditing" will be documented in this file. 4 | 5 | ## v3.1.1 - 2025-06-04 6 | 7 | ### What's Changed 8 | 9 | * feat: add support to pt_BR by @samuelterra22 in https://github.com/TappNetwork/filament-auditing/pull/42 10 | 11 | ### New Contributors 12 | 13 | * @samuelterra22 made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/42 14 | 15 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.1.0...v3.1.1 16 | 17 | ## v3.1.0 - 2025-03-15 18 | 19 | ### What's Changed 20 | 21 | * Laravel 12 Support by @swilla in https://github.com/TappNetwork/filament-auditing/pull/39 22 | 23 | ### New Contributors 24 | 25 | * @swilla made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/39 26 | 27 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.9...v3.1.0 28 | 29 | ## v3.0.9 - 2025-03-08 30 | 31 | ### What's Changed 32 | 33 | * add empty state heading by @erossdev in https://github.com/TappNetwork/filament-auditing/pull/38 34 | 35 | ### New Contributors 36 | 37 | * @erossdev made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/38 38 | 39 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.8...v3.0.9 40 | 41 | ## v3.0.8 - 2024-12-12 42 | 43 | ### What's Changed 44 | 45 | * Allow multiple parameter methods when extending columns by @intrepidws in https://github.com/TappNetwork/filament-auditing/pull/34 46 | 47 | ### New Contributors 48 | 49 | * @intrepidws made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/34 50 | 51 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.7...v3.0.8 52 | 53 | ## v3.0.7 - 2024-08-15 54 | 55 | ### What's Changed 56 | 57 | * Add the ability to customize the presentation by @andreia in https://github.com/TappNetwork/filament-auditing/pull/26 58 | 59 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.6...v3.0.7 60 | 61 | ## v3.0.6 - 2024-05-31 62 | 63 | ### What's Changed 64 | 65 | * German translation add by @DGINXREAL in https://github.com/TappNetwork/filament-auditing/pull/28 66 | 67 | ### New Contributors 68 | 69 | * @DGINXREAL made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/28 70 | 71 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.5...v3.0.6 72 | 73 | ## v3.0.5 - 2024-03-24 74 | 75 | ### What's Changed 76 | 77 | * Korean translations add by @corean in https://github.com/TappNetwork/filament-auditing/pull/27 78 | 79 | ### New Contributors 80 | 81 | * @corean made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/27 82 | 83 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.4...v3.0.5 84 | 85 | ## v3.0.4 - 2024-02-05 86 | 87 | ### What's Changed 88 | 89 | * use Filament::auth() instead of auth() by @eelco2k in https://github.com/TappNetwork/filament-auditing/pull/23 90 | * Add is_lazy configuration by @andreia in https://github.com/TappNetwork/filament-auditing/pull/24 91 | 92 | ### New Contributors 93 | 94 | * @eelco2k made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/23 95 | 96 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.3...v3.0.4 97 | 98 | ## v3.0.3 - 2024-01-10 99 | 100 | ### What's Changed 101 | 102 | * Added fr_FR translation file for 3.x by @loanbesson in https://github.com/TappNetwork/filament-auditing/pull/19 103 | 104 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.2...v3.0.3 105 | 106 | ## v3.0.2 - 2023-09-24 107 | 108 | ### What's Changed 109 | 110 | - Add updateAuditsRelationManager event listener by @andreia in https://github.com/TappNetwork/filament-auditing/pull/17 111 | 112 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.1...v3.0.2 113 | 114 | ## v3.0.1 - 2023-09-24 115 | 116 | ### What's Changed 117 | 118 | - Ensure it does not break with null values. by @haringsrob in https://github.com/TappNetwork/filament-auditing/pull/16 119 | 120 | ### New Contributors 121 | 122 | - @haringsrob made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/16 123 | 124 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.0...v3.0.1 125 | 126 | ## 1.0 127 | 128 | - Initial release 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Filament Laravel Auditing 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/tapp/filament-auditing.svg?style=flat-square)](https://packagist.org/packages/tapp/filament-auditing) 4 | ![Code Style Action Status](https://github.com/TappNetwork/filament-auditing/actions/workflows/pint.yml/badge.svg) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/tapp/filament-auditing.svg?style=flat-square)](https://packagist.org/packages/tapp/filament-auditing) 6 | 7 | A Filament plugin for [Laravel Auditing](https://laravel-auditing.com/) package. 8 | This plugin contains a relation manager for audits that you can add to your Filament resources. 9 | 10 | This package provides a Filament resource manager that shows a table with all audits on view and edit pages and allows 11 | restore audits. 12 | 13 | ## Installation 14 | 15 | > **Note** 16 | > This plugin uses the [Laravel Auditing](https://laravel-auditing.com/) package. First install and configure this 17 | > package. 18 | 19 | You can install the plugin via composer: 20 | 21 | ```bash 22 | composer require tapp/filament-auditing:"^3.0" 23 | ``` 24 | 25 | > **Note** 26 | > For **Filament 2.x** check the **[2.x](https://github.com//TappNetwork/filament-auditing/tree/2.x)** branch 27 | 28 | You can publish the view files with: 29 | 30 | ```bash 31 | php artisan vendor:publish --tag="filament-auditing-views" 32 | ``` 33 | You can publish the translation files with: 34 | 35 | ```bash 36 | php artisan vendor:publish --tag="filament-auditing-translations" 37 | ``` 38 | 39 | You can publish the config file with: 40 | 41 | ```bash 42 | php artisan vendor:publish --tag="filament-auditing-config" 43 | ``` 44 | 45 | This is the content of the published config file: 46 | 47 | ```php 48 | [ 53 | 'column' => 'created_at', 54 | 'direction' => 'desc', 55 | ], 56 | 57 | 'is_lazy' => true, 58 | 59 | 'audits_extend' => [ 60 | // 'url' => [ 61 | // 'class' => \Filament\Tables\Columns\TextColumn::class, 62 | // 'methods' => [ 63 | // 'sortable', 64 | // 'searchable' => true, 65 | // 'default' => 'N/A' 66 | // ] 67 | // ], 68 | ] 69 | 70 | 'custom_audits_view' => false, 71 | 72 | 'custom_view_parameters' => [ 73 | ], 74 | 75 | 'mapping' => [ 76 | ], 77 | ]; 78 | ``` 79 | 80 | The `audits_sort` can be used to change the default sort on the audits table. 81 | 82 | ## Usage 83 | 84 | To show the audits table in your Filament resource, just add `AuditsRelationManager::class` on your 85 | resource's `getRelations` method: 86 | 87 | ```php 88 | use Tapp\FilamentAuditing\RelationManagers\AuditsRelationManager; 89 | 90 | public static function getRelations(): array 91 | { 92 | return [ 93 | // ... 94 | AuditsRelationManager::class, 95 | ]; 96 | } 97 | ``` 98 | 99 | That's it, you're all set! 100 | 101 | If you access your resource, and edit some data, you will now see the audits table on edit and view pages. 102 | 103 | ### Extending Columns 104 | 105 | In case you need to add a column to the AuditsRelationManager that does 106 | not already exist in the table, you can add it in the config using the format denoted in the example below, and it will be 107 | prepended to the table builder. The name of the column to be added is the key of an associative array that contains other information about the class, as shown in the example below. The class instance of the column must be added, but the methods can be left out if not required, or added wherever necessary. 108 | 109 | ```php 110 | [ 115 | 'url' => [ 116 | 'class' => \Filament\Tables\Columns\TextColumn::class, // required 117 | 'methods' => [ 118 | 'sortable', 119 | 'default' => 'NIL', 120 | ], 121 | ], 122 | ] 123 | 124 | ]; 125 | ``` 126 | 127 | After adding this information in the config, please run this command for changes to take place. 128 | 129 | ```bash 130 | php artisan optimize 131 | ``` 132 | 133 | Methods with two or more parameters can be specified with an array like so: 134 | 135 | ```php 136 | [ 141 | 'created_at' => [ 142 | 'class' => \Filament\Tables\Columns\TextColumn::class, // required 143 | 'methods' => [ 144 | 'sortable', 145 | 'date' => ['Y-m-d H:i:s', 'America/New_York'], 146 | ], 147 | ], 148 | ] 149 | 150 | ]; 151 | ``` 152 | 153 | ### Custom View Data Formatting 154 | 155 | If you want to modify the content of the audit to display the old and new values in a specific way, such as showing the value of a specific column instead of the ID for relationships, or even customize the entire view to display data in a different way than the default table, you can use one of these methods described below (first, make sure the plugin views are published): 156 | 157 | #### Show a related column instead of foreign id 158 | 159 | To use another field to be displayed for relationships instead of the foreign id, in old and new values, you can add on the `mapping` array, in `filament-auditing.php` config file, the label and field that should be displayed, as well as the related model, using the foreign key as the array key. For example, on an `user` relationship with the `user_id` foreing key, this config will display the user `name` along with the `User` label: 160 | 161 | ```bash 162 | 'mapping' => [ 163 | 'user_id' => [ 164 | 'model' => App\Models\User::class, 165 | 'field' => 'name', 166 | 'label' => 'User', 167 | ], 168 | ], 169 | ``` 170 | 171 | And you'd like to customize the view, you can do it in the published view `views/vendor/filament-auditing/tables/columns/key-value.blade.php` file. 172 | 173 | #### Customizing the Old and New Values 174 | 175 | If you need to customize the presentation for other old and new values, besides the related fields, you can add a `formatAuditFieldsForPresentation($field, $record)` method on the model that is auditable, with two parameters: 176 | - the first parameter contains the name of the field (`old_values` or `new_values`). 177 | - the second parameter contains de current audit record 178 | 179 | This method must return the formatted audit fields. 180 | 181 | For example, let's say you have an `Article` model that is auditable and contains a related user, and you added a `formatAuditFieldsForPresentation($field, $record)` method that returns the related user name instead of the id, and the data formatted with some HTML code: 182 | 183 | ```php 184 | {$field}); 204 | 205 | $formattedResult = ''; 220 | 221 | return new HtmlString($formattedResult); 222 | } 223 | 224 | public function user(): BelongsTo 225 | { 226 | return $this->belongsTo(User::class); 227 | } 228 | } 229 | ``` 230 | 231 | #### Customizing the Entire View Content 232 | 233 | If you'd like to customize the entire view content, you may set the `custom_audits_view` config value to `true` on `config/filament-auditing.php` file: 234 | 235 | ```php 236 | 'custom_audits_view' => true, 237 | ``` 238 | 239 | This modification will allow you to take full control of the display and tailor it to your specific requirements. You can now add your custom content on `resources/views/vendor/filament-auditing/tables/custom-audit-content.blade.php` file. 240 | For example: 241 | 242 | ```php 243 | @php 244 | $type = (string) str(class_basename($owner))->lower(); 245 | @endphp 246 | 247 | @if(isset($records)) 248 | 249 | 250 | @foreach($headers as $header) 251 | 252 | {{$header}} 253 | 254 | @endforeach 255 | 256 | @foreach($records as $audit) 257 | 258 | @foreach ($audit->getModified() as $attribute => $modified) 259 | 260 | @lang($type.'.metadata', $audit->getMetadata()) 261 |
262 | @php 263 | $current = $type.'.'.$audit->event.'.modified.'.$attribute; 264 | 265 | $modified['new'] = $owner->formatFieldForPresentation($attribute, $modified['new']); 266 | 267 | if (isset($modified['old'])) { 268 | $modified['old'] = $owner->formatFieldForPresentation($attribute, $modified['old']); 269 | } 270 | @endphp 271 | 272 | @lang($current, $modified) 273 |
274 | @endforeach 275 |
276 | @endforeach 277 |
278 | @else 279 |
280 | @lang($type.'.unavailable_audits') 281 |
282 | @endif 283 | ``` 284 | 285 | The owner record is available to this view via `$owner` variable. To pass some additional parameters to the view, you may use the `custom_view_parameters` config: 286 | 287 | ```php 288 | 'custom_view_parameters' => [ 289 | 'headers' => [ 290 | 'Audit', 291 | ], 292 | ], 293 | ``` 294 | 295 | To format a field, you may also add a `formatFieldForPresentation` method on the owner model, with the field name and value as parameters, like in the example above. This method must return a formatted field. 296 | 297 | For example, in an `Article` model, to return the name of the related user: 298 | 299 | ```php 300 | public function formatFieldForPresentation($field, $value) 301 | { 302 | return match($field) { 303 | 'user_id' => $value ? optional(User::find($value))->name : $value, 304 | default => $value, 305 | }; 306 | } 307 | ``` 308 | 309 | An example of the `article.php` lang file content used in the `custom-audit-content.blade.php` view code above: 310 | 311 | ```php 312 | 'No article audits available', 316 | 317 | 'metadata' => 'On :audit_created_at, :user_name [:audit_ip_address] :audit_event this record via :audit_url', 318 | 319 | 'updated' => [ 320 | 'modified' => [ 321 | 'order' => 'The Order has been modified from :old to :new', 322 | 'title' => 'The Title has been modified from :old to :new', 323 | 'content' => 'The Content has been modified from :old to :new', 324 | 'user_id' => 'The User has been modified from :old to :new', 325 | ], 326 | ], 327 | ]; 328 | ``` 329 | 330 | ### Permissions 331 | 332 | Two permissions are registered by default, allowing access to: 333 | 334 | - `audit`: view audits 335 | - `restoreAudit`: restore audits 336 | 337 | You can override these permissions by adding a policy with `audit` and `restoreAudit`. 338 | 339 | ### Event emitted 340 | 341 | The `auditRestored` event is emitted when an audit is restored, so you could register a listener using the $listeners property to execute some extra code after the audit is restored. 342 | 343 | E.g.: on Edit page of your resource: 344 | 345 | ```php 346 | protected $listeners = [ 347 | 'auditRestored', 348 | ]; 349 | 350 | public function auditRestored() 351 | { 352 | // your code 353 | } 354 | ``` 355 | 356 | ### Event listener 357 | 358 | The audits relation manager listen to the `updateAuditsRelationManager` event to refresh the audits table. 359 | 360 | So you can dispatch this event in the Edit page of your resource (e.g.: in a edit page of a `PostResource` -> `app/Filament/Resources/PostResource/Pages/EditPost.php`) when the form is updated: 361 | 362 | ```php 363 | protected function afterSave(): void 364 | { 365 | $this->dispatch('updateAuditsRelationManager'); 366 | } 367 | ``` 368 | 369 | > [!WARNING] 370 | > When dispaching this event, set the [is_lazy](https://filamentphp.com/docs/3.x/panels/resources/relation-managers#disabling-lazy-loading) configuration to `false`, on `filament-auditing.php` 371 | > config file, to avoid this exception: "Typed property Filament\Resources\RelationManagers\RelationManager::$table 372 | > must not be accessed before initialization" 373 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tapp/filament-auditing", 3 | "description": "Filament Laravel Auditing plugin.", 4 | "keywords": [ 5 | "tapp network", 6 | "filament", 7 | "laravel", 8 | "auditing", 9 | "audit" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Tapp Network", 15 | "email": "steve@tappnetwork.com", 16 | "role": "Developer" 17 | }, 18 | { 19 | "name": "Tapp Network", 20 | "email": "andreia.bohner@tappnetwork.com", 21 | "role": "Developer" 22 | } 23 | ], 24 | "homepage": "https://github.com/TappNetwork/filament-auditing", 25 | "support": { 26 | "issues": "https://github.com/TappNetwork/filament-auditing/issues", 27 | "source": "https://github.com/TappNetwork/filament-auditing" 28 | }, 29 | "require": { 30 | "php": "^8.2", 31 | "filament/filament": "^3.0-stable", 32 | "owen-it/laravel-auditing": "^14.0||^13.0", 33 | "spatie/laravel-package-tools": "^1.16" 34 | }, 35 | "require-dev": { 36 | "larastan/larastan": "^2.9||^3.0", 37 | "laravel/pint": "^1.14", 38 | "nunomaduro/collision": "^8.1.1||^7.10.0", 39 | "orchestra/testbench": "^10.0.0||^9.0.0||^8.22.0", 40 | "pestphp/pest": "^3.0||^2.34", 41 | "pestphp/pest-plugin-arch": "^3.0||^2.7", 42 | "pestphp/pest-plugin-laravel": "^3.0||^2.3", 43 | "phpstan/extension-installer": "^1.3||^2.0", 44 | "phpstan/phpstan-deprecation-rules": "^1.1||^2.0", 45 | "phpstan/phpstan-phpunit": "^1.3||^2.0", 46 | "spatie/laravel-ray": "^1.35" 47 | }, 48 | "autoload": { 49 | "psr-4": { 50 | "Tapp\\FilamentAuditing\\": "src" 51 | } 52 | }, 53 | "autoload-dev": { 54 | "psr-4": { 55 | "Tapp\\FilamentAuditing\\Tests\\": "tests/", 56 | "Workbench\\App\\": "workbench/app/" 57 | } 58 | }, 59 | "config": { 60 | "sort-packages": true, 61 | "allow-plugins": { 62 | "pestphp/pest-plugin": true, 63 | "phpstan/extension-installer": true 64 | } 65 | }, 66 | "extra": { 67 | "laravel": { 68 | "providers": [ 69 | "Tapp\\FilamentAuditing\\FilamentAuditingServiceProvider" 70 | ] 71 | } 72 | }, 73 | "minimum-stability": "dev", 74 | "prefer-stable": true 75 | } 76 | -------------------------------------------------------------------------------- /config/filament-auditing.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'column' => 'created_at', 7 | 'direction' => 'desc', 8 | ], 9 | 10 | 'is_lazy' => true, 11 | 12 | /** 13 | * Extending Columns 14 | * -------------------------------------------------------------------------- 15 | * In case you need to add a column to the AuditsRelationManager that does 16 | * not already exist in the table, you can add it here, and it will be 17 | * prepended to the table builder. 18 | */ 19 | 'audits_extend' => [ 20 | // 'url' => [ 21 | // 'class' => \Filament\Tables\Columns\TextColumn::class, 22 | // 'methods' => [ 23 | // 'sortable', 24 | // 'searchable' => true, 25 | // 'default' => 'N/A' 26 | // ] 27 | // ], 28 | ], 29 | 30 | 'custom_audits_view' => false, 31 | 32 | 'custom_view_parameters' => [ 33 | ], 34 | 35 | 'mapping' => [ 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: '#^Method Spatie\\LaravelPackageTools\\Package\:\:hasTranslations\(\) invoked with 1 parameter, 0 required\.$#' 5 | identifier: arguments.count 6 | count: 1 7 | path: src/FilamentAuditingServiceProvider.php 8 | 9 | - 10 | message: '#^Access to an undefined property OwenIt\\Auditing\\Contracts\\Audit\:\:\$event\.$#' 11 | identifier: property.notFound 12 | count: 1 13 | path: src/RelationManagers/AuditsRelationManager.php 14 | 15 | - 16 | message: '#^Call to function is_array\(\) with array will always evaluate to true\.$#' 17 | identifier: function.alreadyNarrowedType 18 | count: 1 19 | path: src/RelationManagers/AuditsRelationManager.php 20 | 21 | - 22 | message: '#^Call to function method_exists\(\) with Illuminate\\Database\\Eloquent\\Model and ''formatAuditFieldsFo…'' will always evaluate to true\.$#' 23 | identifier: function.alreadyNarrowedType 24 | count: 2 25 | path: src/RelationManagers/AuditsRelationManager.php 26 | 27 | - 28 | message: '#^Unreachable statement \- code above always terminates\.$#' 29 | identifier: deadCode.unreachable 30 | count: 1 31 | path: src/RelationManagers/AuditsRelationManager.php 32 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 4 6 | paths: 7 | - src 8 | - config 9 | tmpDir: build/phpstan 10 | checkOctaneCompatibility: true 11 | checkModelProperties: true 12 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | tests 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ./src 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/lang/ar/filament-auditing.php: -------------------------------------------------------------------------------- 1 | 'سجل التعديلات', 12 | 'table.empty_state_heading' => 'لا توجد عمليات تدقيق', 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Table Columns 17 | |-------------------------------------------------------------------------- 18 | */ 19 | 20 | 'column.user_name' => 'المستخدم', 21 | 'column.event' => 'العملية', 22 | 'column.created_at' => 'تاريخ التعديل', 23 | 'column.old_values' => 'القيم السابقة', 24 | 'column.new_values' => 'القيم الجديدة', 25 | 'column.new_values' => 'القيم الجديدة', 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | Table Actions 30 | |-------------------------------------------------------------------------- 31 | */ 32 | 33 | 'action.restore' => 'استعادة', 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Notifications 38 | |-------------------------------------------------------------------------- 39 | */ 40 | 41 | 'notification.restored' => 'تمت الاستعادة', 42 | 'notification.unchanged' => 'لا توجد تعديلات', 43 | 44 | ]; 45 | -------------------------------------------------------------------------------- /resources/lang/de/filament-auditing.php: -------------------------------------------------------------------------------- 1 | 'Audits', 12 | 'table.empty_state_heading' => 'Keine Audits', 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Table Columns 17 | |-------------------------------------------------------------------------- 18 | */ 19 | 20 | 'column.user_name' => 'Benutzer', 21 | 'column.event' => 'Event', 22 | 'column.created_at' => 'Erstellt am', 23 | 'column.old_values' => 'Alter Wert', 24 | 'column.new_values' => 'Neuer Wert', 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Table Actions 29 | |-------------------------------------------------------------------------- 30 | */ 31 | 32 | 'action.restore' => 'Wiederherstellen', 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Notifications 37 | |-------------------------------------------------------------------------- 38 | */ 39 | 40 | 'notification.restored' => 'Audit wiederhergestellt', 41 | 'notification.unchanged' => 'Es gibt nichts zu ändern', 42 | 43 | ]; 44 | -------------------------------------------------------------------------------- /resources/lang/en/filament-auditing.php: -------------------------------------------------------------------------------- 1 | 'Audits', 12 | 'table.empty_state_heading' => 'No audits', 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Table Columns 17 | |-------------------------------------------------------------------------- 18 | */ 19 | 20 | 'column.user_name' => 'User', 21 | 'column.event' => 'Event', 22 | 'column.created_at' => 'Created', 23 | 'column.old_values' => 'Old Values', 24 | 'column.new_values' => 'New Values', 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Table Actions 29 | |-------------------------------------------------------------------------- 30 | */ 31 | 32 | 'action.restore' => 'Restore', 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Notifications 37 | |-------------------------------------------------------------------------- 38 | */ 39 | 40 | 'notification.restored' => 'Audit restored', 41 | 'notification.unchanged' => 'Nothing to change', 42 | 43 | ]; 44 | -------------------------------------------------------------------------------- /resources/lang/es/filament-auditing.php: -------------------------------------------------------------------------------- 1 | 'Auditorías', 12 | 'table.empty_state_heading' => 'Sin auditorías', 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Table Columns 17 | |-------------------------------------------------------------------------- 18 | */ 19 | 20 | 'column.user_name' => 'Usuario', 21 | 'column.event' => 'Evento', 22 | 'column.created_at' => 'Creado en', 23 | 'column.old_values' => 'Valores antiguos', 24 | 'column.new_values' => 'Nuevos valores', 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Table Actions 29 | |-------------------------------------------------------------------------- 30 | */ 31 | 32 | 'action.restore' => 'Restaurar', 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Notifications 37 | |-------------------------------------------------------------------------- 38 | */ 39 | 40 | 'notification.restored' => 'Auditoría restaurada', 41 | 'notification.unchanged' => 'Nada que cambiar', 42 | 43 | ]; 44 | -------------------------------------------------------------------------------- /resources/lang/fr/filament-auditing.php: -------------------------------------------------------------------------------- 1 | 'Audits', 12 | 'table.empty_state_heading' => "Pas d'audits", 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Table Columns 17 | |-------------------------------------------------------------------------- 18 | */ 19 | 20 | 'column.user_name' => 'Utilisateur', 21 | 'column.event' => 'Événement', 22 | 'column.created_at' => 'Créé', 23 | 'column.old_values' => 'Anciennes valeurs', 24 | 'column.new_values' => 'Nouvelles valeurs', 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Table Actions 29 | |-------------------------------------------------------------------------- 30 | */ 31 | 32 | 'action.restore' => 'Restaurer', 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Notifications 37 | |-------------------------------------------------------------------------- 38 | */ 39 | 40 | 'notification.restored' => 'Audit restauré', 41 | 'notification.unchanged' => 'Rien à modifier', 42 | 43 | ]; 44 | -------------------------------------------------------------------------------- /resources/lang/ko/filament-auditing.php: -------------------------------------------------------------------------------- 1 | '변경이력', 12 | 'table.empty_state_heading' => '감사 없음', 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Table Columns 17 | |-------------------------------------------------------------------------- 18 | */ 19 | 20 | 'column.user_name' => '사용자', 21 | 'column.event' => '이벤트', 22 | 'column.created_at' => '등록됨', 23 | 'column.old_values' => '기존 내용', 24 | 'column.new_values' => '신규 내용', 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Table Actions 29 | |-------------------------------------------------------------------------- 30 | */ 31 | 32 | 'action.restore' => '복원', 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Notifications 37 | |-------------------------------------------------------------------------- 38 | */ 39 | 40 | 'notification.restored' => '변경이력 복원', 41 | 'notification.unchanged' => '변경할 사항이 없습니다.', 42 | 43 | ]; 44 | -------------------------------------------------------------------------------- /resources/lang/pt_BR/filament-auditing.php: -------------------------------------------------------------------------------- 1 | 'Auditorias', 12 | 'table.empty_state_heading' => 'Nenhuma auditoria', 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Table Columns 17 | |-------------------------------------------------------------------------- 18 | */ 19 | 20 | 'column.user_name' => 'Usuário', 21 | 'column.event' => 'Evento', 22 | 'column.created_at' => 'Criado em', 23 | 'column.old_values' => 'Valores Antigos', 24 | 'column.new_values' => 'Novos Valores', 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Table Actions 29 | |-------------------------------------------------------------------------- 30 | */ 31 | 32 | 'action.restore' => 'Restaurar', 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Notifications 37 | |-------------------------------------------------------------------------- 38 | */ 39 | 40 | 'notification.restored' => 'Auditoria restaurada', 41 | 'notification.unchanged' => 'Nada a alterar', 42 | 43 | ]; 44 | -------------------------------------------------------------------------------- /resources/views/tables/columns/key-value.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $data = isset($state) ? $state : $getState() 3 | @endphp 4 | 5 |
6 | 26 |
27 | -------------------------------------------------------------------------------- /resources/views/tables/custom-audit-content.blade.php: -------------------------------------------------------------------------------- 1 | {{-- add your custom code here --}} 2 | -------------------------------------------------------------------------------- /src/FilamentAuditingServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('filament-auditing') 16 | ->hasConfigFile('filament-auditing') 17 | ->hasTranslations('filament-auditing') 18 | ->hasViews('filament-auditing'); 19 | } 20 | 21 | public function packageBooted(): void 22 | { 23 | parent::packageBooted(); 24 | 25 | // Default values for view and restore audits. Add a policy to override these values 26 | Gate::define('audit', function ($user, $resource) { 27 | return true; 28 | }); 29 | 30 | Gate::define('restoreAudit', function ($user, $resource) { 31 | return true; 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/RelationManagers/AuditsRelationManager.php: -------------------------------------------------------------------------------- 1 | '$refresh']; 25 | 26 | public static function isLazy(): bool 27 | { 28 | return config('filament-auditing.is_lazy'); 29 | } 30 | 31 | public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool 32 | { 33 | return Filament::auth()->user()->can('audit', $ownerRecord); 34 | } 35 | 36 | public static function getTitle(Model $ownerRecord, string $pageClass): string 37 | { 38 | return trans('filament-auditing::filament-auditing.table.heading'); 39 | } 40 | 41 | public function table(Table $table): Table 42 | { 43 | $oldValuesColumn = 44 | method_exists($this->getOwnerRecord(), 'formatAuditFieldsForPresentation') 45 | ? 46 | Tables\Columns\TextColumn::make('old_values') 47 | ->formatStateUsing(fn (Column $column, $record, $state) => method_exists($this->getOwnerRecord(), 'formatAuditFieldsForPresentation') ? $this->getOwnerRecord()->formatAuditFieldsForPresentation($column->getName(), $record) : $state) 48 | ->label(trans('filament-auditing::filament-auditing.column.old_values')) 49 | : 50 | Tables\Columns\TextColumn::make('old_values') 51 | ->formatStateUsing(fn (Column $column, $record, $state): View => view('filament-auditing::tables.columns.key-value', ['state' => $this->mapRelatedColumns($column->getState(), $record)])) 52 | ->label(trans('filament-auditing::filament-auditing.column.old_values')); 53 | 54 | $newValuesColumn = 55 | method_exists($this->getOwnerRecord(), 'formatAuditFieldsForPresentation') 56 | ? 57 | Tables\Columns\TextColumn::make('new_values') 58 | ->formatStateUsing(fn (Column $column, $record, $state) => method_exists($this->getOwnerRecord(), 'formatAuditFieldsForPresentation') ? $this->getOwnerRecord()->formatAuditFieldsForPresentation($column->getName(), $record) : $state) 59 | ->label(trans('filament-auditing::filament-auditing.column.new_values')) 60 | : 61 | Tables\Columns\TextColumn::make('new_values') 62 | ->formatStateUsing(fn (Column $column, $record, $state): View => view('filament-auditing::tables.columns.key-value', ['state' => $this->mapRelatedColumns($column->getState(), $record)])) 63 | ->label(trans('filament-auditing::filament-auditing.column.new_values')); 64 | 65 | return $table 66 | ->modifyQueryUsing(fn (Builder $query) => $query->with('user')->orderBy(config('filament-auditing.audits_sort.column'), config('filament-auditing.audits_sort.direction'))) 67 | ->content(fn (): ?View => config('filament-auditing.custom_audits_view') ? view('filament-auditing::tables.custom-audit-content', Arr::add(self::customViewParameters(), 'owner', $this->getOwnerRecord())) : null) 68 | ->emptyStateHeading(trans('filament-auditing::filament-auditing.table.empty_state_heading')) 69 | ->columns(Arr::flatten([ 70 | Tables\Columns\TextColumn::make('user.name') 71 | ->label(trans('filament-auditing::filament-auditing.column.user_name')), 72 | Tables\Columns\TextColumn::make('event') 73 | ->label(trans('filament-auditing::filament-auditing.column.event')), 74 | Tables\Columns\TextColumn::make('created_at') 75 | ->since() 76 | ->label(trans('filament-auditing::filament-auditing.column.created_at')), 77 | $oldValuesColumn, 78 | $newValuesColumn, 79 | self::extraColumns(), 80 | ])) 81 | ->filters([ 82 | // 83 | ]) 84 | ->headerActions([ 85 | // 86 | ]) 87 | ->actions([ 88 | Tables\Actions\Action::make('restore') 89 | ->label(trans('filament-auditing::filament-auditing.action.restore')) 90 | ->action(fn (Audit $record) => static::restoreAuditSelected($record)) 91 | ->icon('heroicon-o-arrow-path') 92 | ->requiresConfirmation() 93 | ->visible(fn (Audit $record, RelationManager $livewire): bool => Filament::auth()->user()->can('restoreAudit', $livewire->ownerRecord) && $record->event === 'updated') 94 | ->after(function ($livewire) { 95 | $livewire->dispatch('auditRestored'); 96 | }), 97 | ]) 98 | ->bulkActions([ 99 | // 100 | ]); 101 | } 102 | 103 | protected static function customViewParameters(): array 104 | { 105 | return config('filament-auditing.custom_view_parameters'); 106 | } 107 | 108 | protected function mapRelatedColumns($state, $record) 109 | { 110 | $relationshipsToUpdate = Arr::wrap(config('filament-auditing.mapping')); 111 | 112 | if (count($relationshipsToUpdate) !== 0) { 113 | foreach ($relationshipsToUpdate as $key => $relationship) { 114 | if (array_key_exists($key, $state)) { 115 | $state[$relationship['label']] = $relationship['model']::find($state[$key])?->{$relationship['field']}; 116 | unset($state[$key]); 117 | } 118 | } 119 | } 120 | 121 | return $state; 122 | } 123 | 124 | protected static function extraColumns() 125 | { 126 | return Arr::map(config('filament-auditing.audits_extend'), function ($buildParameters, $columnName) { 127 | return collect($buildParameters)->pipeThrough([ 128 | function ($collection) use ($columnName) { 129 | $columnClass = (string) $collection->get('class'); 130 | 131 | if (! is_null($collection->get('methods'))) { 132 | $columnClass = $columnClass::make($columnName); 133 | 134 | collect($collection->get('methods'))->transform(function ($value, $key) use ($columnClass) { 135 | if (is_numeric($key)) { 136 | return $columnClass->$value(); 137 | } 138 | 139 | return $columnClass->$key(...Arr::wrap($value)); 140 | }); 141 | 142 | return $columnClass; 143 | } 144 | 145 | return $columnClass::make($columnName); 146 | }, 147 | ]); 148 | }); 149 | } 150 | 151 | protected static function restoreAuditSelected($audit) 152 | { 153 | $morphClass = Relation::getMorphedModel($audit->auditable_type) ?? $audit->auditable_type; 154 | 155 | $record = $morphClass::find($audit->auditable_id); 156 | 157 | if (! $record) { 158 | self::unchangedAuditNotification(); 159 | 160 | return; 161 | } 162 | 163 | if ($audit->event !== 'updated') { 164 | self::unchangedAuditNotification(); 165 | 166 | return; 167 | } 168 | 169 | $restore = $audit->old_values; 170 | 171 | Arr::pull($restore, 'id'); 172 | 173 | if (is_array($restore)) { 174 | 175 | foreach ($restore as $key => $item) { 176 | $decode = json_decode($item); 177 | 178 | if (json_last_error() === JSON_ERROR_NONE) { 179 | $restore[$key] = $decode; 180 | } 181 | } 182 | 183 | $record->fill($restore); 184 | $record->save(); 185 | 186 | self::restoredAuditNotification(); 187 | 188 | return; 189 | } 190 | 191 | self::unchangedAuditNotification(); 192 | } 193 | 194 | protected static function restoredAuditNotification() 195 | { 196 | Notification::make() 197 | ->title(trans('filament-auditing::filament-auditing.notification.restored')) 198 | ->success() 199 | ->send(); 200 | } 201 | 202 | protected static function unchangedAuditNotification() 203 | { 204 | Notification::make() 205 | ->title(trans('filament-auditing::filament-auditing.notification.unchanged')) 206 | ->warning() 207 | ->send(); 208 | } 209 | 210 | protected function canCreate(): bool 211 | { 212 | return false; 213 | } 214 | 215 | protected function canEdit(Model $record): bool 216 | { 217 | return false; 218 | } 219 | 220 | protected function canDelete(Model $record): bool 221 | { 222 | return false; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /tests/ArchTest.php: -------------------------------------------------------------------------------- 1 | expect(['dd', 'dump', 'ray']) 5 | ->each->not->toBeUsed(); 6 | -------------------------------------------------------------------------------- /tests/ExampleTest.php: -------------------------------------------------------------------------------- 1 | toBeTrue(); 5 | }); 6 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in(__DIR__); 6 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('database.default', 'testing'); 25 | 26 | /* 27 | $migration = include __DIR__.'/../database/migrations/create_filament-maillog_table.php.stub'; 28 | $migration->up(); 29 | */ 30 | } 31 | } 32 | --------------------------------------------------------------------------------