muhammadsadeeq/laravel-activitylog-ui
Modern UI for Spatie laravel-activitylog: table, timeline and analytics dashboards with powerful filters, saved views, exports (CSV/Excel/PDF/JSON), caching for fast counts/pagination, and authorization controls. Tailwind + Alpine, no build step.
A security, correctness and interface release, and a major version: the UI now requires authentication by default, exports answer to their owner, unusable parameters are refused rather than reinterpreted, and some public API is gone.
Upgrading from 2.x? The database and models are unchanged — update the package, then read UPGRADING.md.
composer require muhammadsadeeq/laravel-activitylog-ui:"^3.0"
Spatie indexes the log by subject and causer. This UI lists it newest first and groups it by event over a date range, neither of which was indexed. On 204,963 activities that took the first page from 350ms to 21ms:
php artisan vendor:publish --tag="activitylog-ui-migrations"
php artisan migrate
Opt-in, because activity_log is Spatie's table and indexing an established one
locks it while it runs.
authorization.enabled now defaults to true. Previously the UI shipped fully public — a default install served the entire audit log to unauthenticated visitors. Installs that published the config keep their own value and are unaffected; installs that never published it will now require a logged-in user. Set ACTIVITYLOG_UI_AUTHORIZATION=false to restore the old behaviour, and see the authorization section of the README for the full precedence order.access.allowed_users and access.allowed_roles are now actually enforced when authorization.enabled is false. The middleware that enforces them was previously only registered when authorization was enabled, so those lists had no effect at all in that combination.route.middleware no longer replaces the authentication and access middleware; it replaces the base stack only, and the security middleware is appended afterwards.AnalyticsService::getUserActivityProfile() returns first_activity and last_activity as ISO 8601 strings rather than Carbon instances. The default HTTP response is unchanged; direct PHP callers and applications using Carbon::serializeUsing() will see the difference.422 instead of being silently clamped or dropped. per_page=999999 used to become 100 and an unusable causer_id became no causer filter at all, each answering 200 as though nothing had changed — and a dropped filter shows more of the audit log than was asked for. Affects page, per_page, anchor_id, causer_id, subject_id, event_types and the single-value filters, on the list, analytics and export endpoints. Omitted or empty parameters still use the default. See UPGRADING.md.uniqid(). Shared export URLs will stop working across users.ActivitylogService::searchWithSuggestions() and the search() controller action it served. The action had no route, so nothing could reach it, but it was a second implementation of the suggestions feature carrying the same email exposure fixed below. Use getSearchSuggestions().Activity::hasMonotonicKey(). It existed to switch pagination anchoring off entirely for UUID and ULID keys; the anchor now compares (created_at, key), which reduces the key to a tiebreak within one timestamp and makes anchoring worth doing for those keys rather than disabling it. A row inserted at the anchor's exact timestamp with a lower random key can still join the pinned set; an auto-incrementing key has no such window.anchor_id and anchor_time together. Sending one without the other returns 422 rather than being applied as half an anchor. anchor_time now carries microseconds; send back the value you were given rather than reformatting it. Clients using the shipped UI are unaffected.created_at with the primary key as a tiebreak, rather than by the key alone. Backdated or imported activities previously appeared out of chronological order.__PHP_Incomplete_Class and taking the dashboard down (#12). They now store plain arrays, every read is validated before use so a bad entry is discarded and rebuilt, and the keys are versioned so an upgrade cannot read what an older release wrote. Reported by [@djemmal-nour-el-islam](https://github.com/djemmal-nour-el-islam), who also identified getEventTypesWithStyling() as affected.activitylog.activity_model (#9). The table, connection and key metadata now come from the configured model, including causers that live on a different connection from the log itself. Reported by [@fbmfbm](https://github.com/fbmfbm).ui.causer_name_attributes instead of showing "Unknown", and the timeline hint can be dismissed. The mobile layout quirks are addressed by the interface rework, which stacks each row into a block below 960px rather than scrolling the page sideways.created_at, and the two disagree as soon as anything is backdated or imported. On a log of 204,963 activities this made 44,523 of them — a fifth — unreachable on every page after the first, while the footer went on reporting the full count.Error was caught by a guard meant for an unhealthy cache store, so every cold read logged a warning and ran the full scan the lock was added to prevent. performance.filter_lock_wait and performance.filter_lock_ttl are now read./api/search/suggestions loading the entire activity table into memory, which stopped it answering at all on a large log, and publishing causer email addresses regardless of filters.expose_causer_email.AnalyticsService::getUserActivityProfile() loading a causer's complete history to produce six summary figures. For a causer with 3,342 activities it took 8.3 seconds; it now takes 186ms and the numbers are unchanged.0 being validated and then silently dropped, which listed the entire log as though no filter had been applied.owner_id decided who the finished file belonged to.destroy() could not reach the animation loop through the proxy.aria-modal without trapping focus, so Tab left the dialog for controls a screen reader was no longer announcing.activitylog.activity_model running full validation and construction on every key lookup — hundreds of times per page. Key metadata is now settled once per class, while the table and connection are still asked of a freshly built model on every call, so a model that picks either per tenant is followed rather than frozen.created_at stores sub-second precision the anchor was not the anchoring row's own timestamp, so every row sharing that second was excluded from later pages.% or _ on SQLite, which has no LIKE escape character unless one is named.php artisan activitylog-ui:clear-cache for clearing the filter option caches by hand.analytics.max_chart_series — how many event types the trend chart draws before the remainder are summed into a single "Other" series. Defaults to 6.performance.filter_lock_wait and performance.filter_lock_ttl — control the single-flight lock that stops every in-flight request rebuilding the filter caches at once when they expire.activitylog-ui-migrations tag. Spatie indexes the log by subject and causer; this UI lists it newest first and groups it by event over a date range, neither of which was indexed. On 204,963 activities the first page goes from 350ms to 21ms. Opt-in, because activity_log is Spatie's table and indexing an established one locks it while it runs.event name alongside the display label.Thanks to @djemmal-nour-el-islam (#12), @fbmfbm (#9), @femto-code (#10) and @keatliang2005 (#13) for the reports.
Major release aligning with Spatie laravel-activitylog v5 and Laravel 13.
attribute_changes column instead of propertieshasPropertyChanges() deprecated in favor of hasAttributeChanges()attribute_changes column in addition to properties and descriptionattribute_changes fieldrestored event color in analytics chart defaultshasAttributeChanges() and getFormattedChangesAttribute() fall back to properties.old/properties.attributes for unmigrated rowsattribute_changes or properties transparentlyold, attributes) filtered from the Custom Properties panel to prevent duplicationUPGRADING.md with backup-first migration guideFiltersBatchUuid traitsanitizeUuid() method from controllerSee UPGRADING.md for the full migration guide from v1.x.
Full Changelog: https://github.com/MuhammadSadeeq/laravel-activitylog-ui/compare/v1.3.0...v1.3.1
causer_id and subject_id filtering to support both string and integer identifiers by @Khant-Nyarcreated_at to id for faster loading in large databasesHow can I help you explore Laravel packages today?