Skip to content

Add Admin CRUD#2330

Open
notAreYouScared wants to merge 4 commits into
mainfrom
charles/admin-curd
Open

Add Admin CRUD#2330
notAreYouScared wants to merge 4 commits into
mainfrom
charles/admin-curd

Conversation

@notAreYouScared
Copy link
Copy Markdown
Member

Closes #1585

Adds logging for admin curd actions, and panel setting edits. Also includes a role permission to allow other admins to see it or not.

@notAreYouScared notAreYouScared self-assigned this May 9, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements comprehensive admin activity logging for the Pelican panel. It adds a generic AdminActivityObserver that captures model lifecycle events (create/update/delete) for User, Server, Node, Egg, and Role; a trait-based registration system for wiring the observer; a settings-change tracking layer that detects and logs setting modifications; read-only Filament UI for browsing activity logs with filtering and column configuration; and a new adminAuditLog role permission controlling access to activity logs.

Changes

Admin Activity Logging

Layer / File(s) Summary
Observer Core and Logging Infrastructure
app/Observers/AdminActivityObserver.php
Implements AdminActivityObserver with per-request deduplication, admin-panel guard, and log helper emitting Activity events with actor, subject, and computed properties; helpers construct event names, resolve resource names, extract display names, and collect changed fields.
Observer Registration Trait
app/Traits/HasAdminActivityLogging.php
Provides HasAdminActivityLogging trait with bootHasAdminActivityLogging() registering the observer for model events and getAdminActivityName() returning the model's name attribute or key.
Model Integration and Relationships
app/Models/User.php, app/Models/Server.php, app/Models/Node.php, app/Models/Egg.php, app/Models/Role.php
Applies HasAdminActivityLogging trait to five models; adds User::adminLog() HasMany relationship and overrides getAdminActivityName() to return username; configures Role with adminAuditLog permission (view only) and icon.
Settings Page Change Tracking
app/Filament/Admin/Pages/Settings.php
Captures sanitized initial form state in Locked property on mount; during save, normalizes and compares values to compute changed keys; logs admin:settings.update activity with count and comma-separated key list; centralizes ConsoleFonts removal.
Activity Log Filament Resource and Pages
app/Filament/Admin/Resources/ActivityLogs/ActivityLogResource.php, app/Filament/Admin/Resources/ActivityLogs/Pages/ListActivityLogs.php
Creates read-only ActivityLogResource with conditional listing (root admin or view adminAuditLog/view panelLog permission); ListActivityLogs page filters admin:% events with columns for actor, event, description, IP (conditional), timestamp; includes event SelectFilter and localized empty-state.
User Resource Activity Tab
app/Filament/Admin/Resources/Users/UserResource.php
Adds admin_log tab displaying read-only repeater of user's admin activity entries ordered by timestamp, with HTML-rendered descriptions; visible to root admin or view adminAuditLog permission holders.
Translations and Supporting Changes
lang/en/activity.php, lang/en/admin/log.php, app/Models/ActivityLog.php, app/Eloquent/BackupQueryBuilder.php
Adds admin activity translations for settings and CRUD events; adds audit log UI strings for empty state, labels, and table columns; updates ActivityLog wrapProperties() to use hasSole() for detecting single _count keys; refines BackupQueryBuilder PHPDoc generic type.

Possibly Related PRs

  • pelican-dev/panel#1779: Both PRs modify the admin Users resource to add an activity/admin-log tab and touch ActivityLog-related code.
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR implements most core requirements: admin action logging with metadata, role-based view permissions, and activity UI. However, it lacks a configurable purge period for old events and does not explicitly show before/after values in all cases. Verify whether the purge period mechanism is implemented elsewhere or document why it was deferred; ensure before/after change metadata is consistently captured for all admin actions.
Title check ❓ Inconclusive The title 'Add Admin CRUD' is vague and generic, using an acronym without context about the actual changes (activity logging, permissions, audit trails). Consider a more descriptive title like 'Add admin activity logging and audit trail' that better conveys the PR's main purpose of implementing comprehensive logging and audit capabilities.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description is related to the changeset, mentioning logging for admin actions, panel setting edits, and role permissions, which align with the implemented changes.
Out of Scope Changes check ✅ Passed Changes are scoped to admin activity logging: observer, trait, models, resources, pages, and language files. Minor updates to BackupQueryBuilder are tangential but support the logging infrastructure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Observers/AdminActivityObserver.php`:
- Around line 75-78: The update handlers in AdminActivityObserver (e.g.,
userUpdated) only log the current name; change them to compute per-field old/new
diffs using the model's dirty attributes (getDirty() or getChanges()) and
getOriginal() while excluding noisy keys like updated_at, then pass a structured
"changes" payload to $this->log (e.g., ['changes' => ['email' => ['old' => ...,
'new' => ...], ...]]); apply the same change to the other update handlers
referenced (the methods at the other ranges) so all admin:*.update events
include per-field old/new values for auditability.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d200f5ac-1dec-45f6-ad5f-affb1c5d9c33

📥 Commits

Reviewing files that changed from the base of the PR and between 38620a9 and e22e07a.

📒 Files selected for processing (9)
  • app/Filament/Admin/Pages/Settings.php
  • app/Filament/Admin/Resources/ActivityLogs/ActivityLogResource.php
  • app/Filament/Admin/Resources/ActivityLogs/Pages/ListActivityLogs.php
  • app/Filament/Admin/Resources/Users/UserResource.php
  • app/Models/ActivityLog.php
  • app/Models/Role.php
  • app/Models/User.php
  • app/Observers/AdminActivityObserver.php
  • app/Providers/AppServiceProvider.php

Comment thread app/Observers/AdminActivityObserver.php Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
app/Observers/AdminActivityObserver.php (1)

77-86: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Update events still lack before/after values for changed fields.

The update handlers only capture which field names changed, not the actual old and new values. Per the PR objectives from issue #1585: "with descriptive entries and metadata, including before/after values where applicable." This is essential for proper audit trail reconstruction—knowing that "email" changed is insufficient without seeing it changed from old@example.com to new@example.com.

The past review comment on this code segment provided a specific implementation approach using getOriginal() and getChanges() to capture per-field diffs while excluding noisy keys.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Observers/AdminActivityObserver.php` around lines 77 - 86, The
userUpdated handler currently only records field names; update it to record
before/after values by using User::getOriginal() and User::getChanges() (or
modify changedFieldsFor to return an associative map of changedField =>
['from'=>original, 'to'=>changed]) and pass that map into $this->log instead of
just implode(', ', $changedFields); exclude noisy keys like updated_at,
last_login, password_digest (or whatever keys changedFieldsFor already filters)
so the log payload contains count, a descriptive name and a 'changes' object
with per-field {from,to} entries; ensure you update any callers of
changedFieldsFor or userUpdated to expect the new shape and preserve the
existing log keys ('name', 'count') while adding the detailed 'changes'
metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@app/Observers/AdminActivityObserver.php`:
- Around line 77-86: The userUpdated handler currently only records field names;
update it to record before/after values by using User::getOriginal() and
User::getChanges() (or modify changedFieldsFor to return an associative map of
changedField => ['from'=>original, 'to'=>changed]) and pass that map into
$this->log instead of just implode(', ', $changedFields); exclude noisy keys
like updated_at, last_login, password_digest (or whatever keys changedFieldsFor
already filters) so the log payload contains count, a descriptive name and a
'changes' object with per-field {from,to} entries; ensure you update any callers
of changedFieldsFor or userUpdated to expect the new shape and preserve the
existing log keys ('name', 'count') while adding the detailed 'changes'
metadata.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e469d3ab-be87-42c6-a0ca-b0baee457bae

📥 Commits

Reviewing files that changed from the base of the PR and between 4e56b85 and 396d71c.

📒 Files selected for processing (4)
  • app/Filament/Admin/Resources/ActivityLogs/ActivityLogResource.php
  • app/Observers/AdminActivityObserver.php
  • app/Providers/AppServiceProvider.php
  • lang/en/activity.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/Providers/AppServiceProvider.php

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Observers/AdminActivityObserver.php`:
- Around line 47-53: The deduplication currently builds $key as
"$event:$model::class:{$model->getKey()}" in AdminActivityObserver which
suppresses subsequent distinct changes to the same record; change the key to
include the change signature (for example a hash/serialized representation of
the changed attributes or getChanges()/getDirty() keys or values) so identical
events with different modifications are not deduplicated — update the $key
construction to combine $event, $model::class, $model->getKey() and a stable
fingerprint of the model's changes before setting/checking self::$logged[$key].
- Around line 72-81: The updated() observer should early-return when no
auditable fields changed: use changedFieldsFor($model) and if it returns an
empty array (count 0) skip calling log() to avoid noisy entries; update the
updated(Model $model) method to check changedFieldsFor($model) and only call
$this->log($this->eventFor($model, 'update'), $model, [...]) when there are
changed fields, keeping existing uses of displayNameFor(), eventFor(), and
log().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 660f4b16-e438-418f-a0dc-1d159e9472a6

📥 Commits

Reviewing files that changed from the base of the PR and between 396d71c and 90c50da.

📒 Files selected for processing (9)
  • app/Eloquent/BackupQueryBuilder.php
  • app/Models/Egg.php
  • app/Models/Node.php
  • app/Models/Role.php
  • app/Models/Server.php
  • app/Models/User.php
  • app/Observers/AdminActivityObserver.php
  • app/Traits/HasAdminActivityLogging.php
  • lang/en/admin/log.php
✅ Files skipped from review due to trivial changes (4)
  • lang/en/admin/log.php
  • app/Models/Egg.php
  • app/Eloquent/BackupQueryBuilder.php
  • app/Traits/HasAdminActivityLogging.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/Models/Role.php
  • app/Models/User.php

Comment on lines +47 to +53
// Deduplicate identical events for the same record within a single request.
$key = $event . ':' . $model::class . ':' . $model->getKey();
if (isset(self::$logged[$key])) {
return;
}
self::$logged[$key] = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Deduplication key can drop valid audit entries.

Line 48 deduplicates by {event}:{model}:{id} only. If the same record is updated twice in one request with different changes, the second event is suppressed and the audit trail is incomplete.

Suggested fix
-        $key = $event . ':' . $model::class . ':' . $model->getKey();
+        $fingerprint = md5(serialize($properties));
+        $key = $event . ':' . $model::class . ':' . $model->getKey() . ':' . $fingerprint;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Observers/AdminActivityObserver.php` around lines 47 - 53, The
deduplication currently builds $key as "$event:$model::class:{$model->getKey()}"
in AdminActivityObserver which suppresses subsequent distinct changes to the
same record; change the key to include the change signature (for example a
hash/serialized representation of the changed attributes or
getChanges()/getDirty() keys or values) so identical events with different
modifications are not deduplicated — update the $key construction to combine
$event, $model::class, $model->getKey() and a stable fingerprint of the model's
changes before setting/checking self::$logged[$key].

Comment on lines +72 to +81
public function updated(Model $model): void
{
$changedFields = $this->changedFieldsFor($model);
$name = $this->displayNameFor($model);

$this->log($this->eventFor($model, 'update'), $model, [
'name' => empty($changedFields) ? $name : sprintf('%s (%s)', $name, implode(', ', $changedFields)),
'count' => count($changedFields),
'changes' => implode(', ', $changedFields),
]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Skip update logs when no auditable fields changed.

Line 77 still logs updates when changedFieldsFor() returns empty (for example, only updated_at changed). That creates noisy entries with count = 0 and empty changes.

Suggested fix
     public function updated(Model $model): void
     {
         $changedFields = $this->changedFieldsFor($model);
+        if ($changedFields === []) {
+            return;
+        }
+
         $name = $this->displayNameFor($model);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Observers/AdminActivityObserver.php` around lines 72 - 81, The updated()
observer should early-return when no auditable fields changed: use
changedFieldsFor($model) and if it returns an empty array (count 0) skip calling
log() to avoid noisy entries; update the updated(Model $model) method to check
changedFieldsFor($model) and only call $this->log($this->eventFor($model,
'update'), $model, [...]) when there are changed fields, keeping existing uses
of displayNameFor(), eventFor(), and log().

@Boy132 Boy132 changed the title Add Admin CURD Add Admin CRUD May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Admin Action Logging

1 participant