mirror of
https://github.com/pelican-dev/panel.git
synced 2026-07-16 04:03:50 +03:00
Compare commits
4 Commits
pullreques
...
charles/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90c50da83b | ||
|
|
396d71c4a5 | ||
|
|
4e56b857d1 | ||
|
|
e22e07aae0 |
@@ -3,9 +3,10 @@
|
||||
namespace App\Eloquent;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TModel of Model
|
||||
*
|
||||
* @extends Builder<TModel>
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Enums\TablerIcon;
|
||||
use App\Extensions\Avatar\AvatarService;
|
||||
use App\Extensions\Captcha\CaptchaService;
|
||||
use App\Extensions\OAuth\OAuthService;
|
||||
use App\Facades\Activity;
|
||||
use App\Models\Backup;
|
||||
use App\Notifications\MailTested;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
@@ -46,6 +47,7 @@ use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Notification as MailNotification;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Attributes\Locked;
|
||||
|
||||
/**
|
||||
* @property Schema $form
|
||||
@@ -75,9 +77,14 @@ class Settings extends Page implements HasSchemas
|
||||
/** @var array<mixed>|null */
|
||||
public ?array $data = [];
|
||||
|
||||
/** @var array<mixed> Snapshot of normalized form state captured on mount, used to detect which settings changed before saving. */
|
||||
#[Locked]
|
||||
public array $initialSettings = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->form->fill();
|
||||
$this->initialSettings = $this->sanitizeSettingsData($this->form->getState());
|
||||
}
|
||||
|
||||
public function boot(OAuthService $oauthService, AvatarService $avatarService, CaptchaService $captchaService, IconFactory $iconFactory): void
|
||||
@@ -865,8 +872,8 @@ class Settings extends Page implements HasSchemas
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
$data = $this->form->getState();
|
||||
unset($data['ConsoleFonts']);
|
||||
$data = $this->sanitizeSettingsData($this->form->getState());
|
||||
$changedSettings = $this->getChangedSettings($this->initialSettings, $data);
|
||||
|
||||
$data = array_map(function ($value) {
|
||||
// Convert bools to a string, so they are correctly written to the .env file
|
||||
@@ -886,6 +893,14 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
Artisan::call('queue:restart');
|
||||
|
||||
if (!empty($changedSettings)) {
|
||||
Activity::event('admin:settings.update')
|
||||
->actor(user())
|
||||
->property('count', count($changedSettings))
|
||||
->property('settings', implode(', ', $changedSettings))
|
||||
->log();
|
||||
}
|
||||
|
||||
$this->redirect($this->getUrl());
|
||||
|
||||
Notification::make()
|
||||
@@ -901,6 +916,63 @@ class Settings extends Page implements HasSchemas
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function sanitizeSettingsData(array $data): array
|
||||
{
|
||||
unset($data['ConsoleFonts']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $before
|
||||
* @param array<mixed> $after
|
||||
* @return string[]
|
||||
*/
|
||||
private function getChangedSettings(array $before, array $after): array
|
||||
{
|
||||
$changed = [];
|
||||
|
||||
foreach (array_unique(array_merge(array_keys($before), array_keys($after))) as $key) {
|
||||
$old = $before[$key] ?? null;
|
||||
$new = $after[$key] ?? null;
|
||||
|
||||
if ($this->normalizeSettingValue($old) !== $this->normalizeSettingValue($new)) {
|
||||
$changed[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
sort($changed);
|
||||
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a setting value to a normalized string for diffing.
|
||||
* Handles enums, booleans, arrays (JSON-encoded), and scalar values.
|
||||
*/
|
||||
private function normalizeSettingValue(mixed $value): string
|
||||
{
|
||||
if ($value instanceof BackedEnum) {
|
||||
return (string) $value->value;
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return $value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$json = json_encode($value);
|
||||
|
||||
return $json === false ? '' : $json;
|
||||
}
|
||||
|
||||
return (string) ($value ?? '');
|
||||
}
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\ActivityLogs;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\ActivityLogs\Pages\ListActivityLogs;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ActivityLogResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
|
||||
protected static ?string $model = ActivityLog::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::ShieldSearch;
|
||||
|
||||
protected static ?int $navigationSort = 10;
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('admin/log.navigation.admin_audit_log');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return trans('admin/log.model_label');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return trans('admin/log.model_label_plural');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
public static function canCreate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canEdit(Model $record): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canDelete(Model $record): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canView(Model $record): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canViewAny(): bool
|
||||
{
|
||||
$user = user();
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->isRootAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->can('view adminAuditLog') || $user->can('view panelLog');
|
||||
}
|
||||
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListActivityLogs::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\ActivityLogs\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\ActivityLogs\ActivityLogResource;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\User;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class ListActivityLogs extends ListRecords
|
||||
{
|
||||
protected static string $resource = ActivityLogResource::class;
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return trans('admin/log.navigation.admin_audit_log');
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(
|
||||
ActivityLog::query()
|
||||
->where('event', 'like', 'admin:%')
|
||||
->with(['actor'])
|
||||
->latest('timestamp')
|
||||
)
|
||||
->columns([
|
||||
TextColumn::make('actor.username')
|
||||
->label(trans('admin/log.table.actor'))
|
||||
->state(function (ActivityLog $log): string {
|
||||
if ($log->actor instanceof User) {
|
||||
return $log->actor->username;
|
||||
}
|
||||
|
||||
return trans('admin/log.table.system');
|
||||
})
|
||||
->searchable(query: function (Builder $query, string $search): Builder {
|
||||
$escapedSearch = addcslashes($search, '%_\\\\');
|
||||
|
||||
return $query->whereHas('actor', fn (Builder $q) => $q->where('username', 'like', "%{$escapedSearch}%"));
|
||||
}),
|
||||
TextColumn::make('event')
|
||||
->label(trans('admin/log.table.event'))
|
||||
->badge()
|
||||
->searchable(),
|
||||
TextColumn::make('description')
|
||||
->label(trans('admin/log.table.description'))
|
||||
->html()
|
||||
->state(fn (ActivityLog $log) => new HtmlString($log->getLabel()))
|
||||
->grow(),
|
||||
TextColumn::make('ip')
|
||||
->label(trans('admin/log.table.ip'))
|
||||
->visibleFrom('lg')
|
||||
->visible(fn () => user()?->can('seeIps activityLog')),
|
||||
DateTimeColumn::make('timestamp')
|
||||
->label(trans('admin/log.table.timestamp'))
|
||||
->sortable()
|
||||
->since(),
|
||||
])
|
||||
->defaultSort('timestamp', 'desc')
|
||||
->searchable()
|
||||
->filters([
|
||||
SelectFilter::make('event')
|
||||
->label(trans('admin/log.table.event'))
|
||||
->options(fn () => ActivityLog::query()
|
||||
->where('event', 'like', 'admin:%')
|
||||
->distinct()
|
||||
->pluck('event')
|
||||
->mapWithKeys(fn (string $event) => [$event => $event])
|
||||
->toArray())
|
||||
->searchable(),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/log.empty_audit_log'))
|
||||
->emptyStateIcon(TablerIcon::ShieldSearch);
|
||||
}
|
||||
}
|
||||
@@ -489,6 +489,34 @@ class UserResource extends Resource
|
||||
->state(fn (ActivityLog $log) => new HtmlString($log->htmlable())),
|
||||
]),
|
||||
]),
|
||||
Tab::make('admin_log')
|
||||
->visible(function (?User $user): bool {
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$viewer = user();
|
||||
|
||||
return $viewer?->isRootAdmin() || $viewer?->can('view adminAuditLog');
|
||||
})
|
||||
->disabledOn('create')
|
||||
->label(trans('admin/user.tabs.admin_log'))
|
||||
->icon(TablerIcon::ShieldSearch)
|
||||
->schema([
|
||||
Repeater::make('adminLog')
|
||||
->hiddenLabel()
|
||||
->inlineLabel(false)
|
||||
->deletable(false)
|
||||
->addable(false)
|
||||
->relationship('adminLog', function (Builder $query) {
|
||||
$query->orderBy('timestamp', 'desc');
|
||||
})
|
||||
->schema([
|
||||
TextEntry::make('log')
|
||||
->hiddenLabel()
|
||||
->state(fn (ActivityLog $log) => new HtmlString($log->htmlable())),
|
||||
]),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ class ActivityLog extends Model implements HasIcon, HasLabel
|
||||
});
|
||||
|
||||
$keys = $properties->keys()->filter(fn ($key) => Str::endsWith($key, '_count'))->values();
|
||||
if ($keys->containsOneItem()) {
|
||||
if ($keys->hasSole()) {
|
||||
$properties = $properties->merge(['count' => $properties->get($keys[0])])->except([$keys[0]]);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Contracts\Validatable;
|
||||
use App\Exceptions\Service\Egg\HasChildrenException;
|
||||
use App\Exceptions\Service\HasActiveServersException;
|
||||
use App\Models\Traits\HasIcon;
|
||||
use App\Traits\HasAdminActivityLogging;
|
||||
use App\Traits\HasValidation;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -93,6 +94,7 @@ use Illuminate\Support\Str;
|
||||
*/
|
||||
class Egg extends Model implements Validatable
|
||||
{
|
||||
use HasAdminActivityLogging;
|
||||
use HasFactory;
|
||||
use HasIcon;
|
||||
use HasValidation;
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Contracts\Validatable;
|
||||
use App\Exceptions\Service\HasActiveServersException;
|
||||
use App\Repositories\Daemon\DaemonSystemRepository;
|
||||
use App\Traits\HasAdminActivityLogging;
|
||||
use App\Traits\HasValidation;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
@@ -95,6 +96,7 @@ use Symfony\Component\Yaml\Yaml;
|
||||
*/
|
||||
class Node extends Model implements Validatable
|
||||
{
|
||||
use HasAdminActivityLogging;
|
||||
use HasFactory;
|
||||
use HasValidation;
|
||||
use Notifiable;
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Enums\RolePermissionModels;
|
||||
use App\Enums\RolePermissionPrefixes;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Traits\HasAdminActivityLogging;
|
||||
use BackedEnum;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -41,6 +42,7 @@ use Spatie\Permission\Models\Role as BaseRole;
|
||||
*/
|
||||
class Role extends BaseRole
|
||||
{
|
||||
use HasAdminActivityLogging;
|
||||
use HasFactory;
|
||||
|
||||
public const RESOURCE_NAME = 'role';
|
||||
@@ -70,6 +72,9 @@ class Role extends BaseRole
|
||||
'panelLog' => [
|
||||
'view',
|
||||
],
|
||||
'adminAuditLog' => [
|
||||
'view',
|
||||
],
|
||||
'plugin' => [
|
||||
'viewList',
|
||||
'create',
|
||||
@@ -82,6 +87,7 @@ class Role extends BaseRole
|
||||
'health' => TablerIcon::Heart,
|
||||
'activityLog' => TablerIcon::Stack,
|
||||
'panelLog' => TablerIcon::FileInfo,
|
||||
'adminAuditLog' => TablerIcon::ShieldSearch,
|
||||
];
|
||||
|
||||
/** @var array<string, array<string>> */
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Exceptions\Http\Server\ServerStateConflictException;
|
||||
use App\Models\Traits\HasIcon;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use App\Services\Subusers\SubuserDeletionService;
|
||||
use App\Traits\HasAdminActivityLogging;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\CarbonInterface;
|
||||
use Exception;
|
||||
@@ -129,6 +130,7 @@ use Psr\Http\Message\ResponseInterface;
|
||||
*/
|
||||
class Server extends Model implements HasAvatar, Validatable
|
||||
{
|
||||
use HasAdminActivityLogging;
|
||||
use HasFactory;
|
||||
use HasIcon;
|
||||
use HasValidation;
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Events\User\Deleting;
|
||||
use App\Exceptions\DisplayException;
|
||||
use App\Extensions\Avatar\AvatarService;
|
||||
use App\Models\Traits\HasAccessTokens;
|
||||
use App\Traits\HasAdminActivityLogging;
|
||||
use App\Traits\HasValidation;
|
||||
use BackedEnum;
|
||||
use DateTimeZone;
|
||||
@@ -119,6 +120,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
use Authorizable { can as protected canned; }
|
||||
use CanResetPassword;
|
||||
use HasAccessTokens;
|
||||
use HasAdminActivityLogging;
|
||||
use HasFactory;
|
||||
use HasRoles;
|
||||
use HasValidation { getRules as getValidationRules; }
|
||||
@@ -289,6 +291,18 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns activity logs where this user is the actor and the event is an admin action.
|
||||
*
|
||||
* @return HasMany<ActivityLog, $this>
|
||||
*/
|
||||
public function adminLog(): HasMany
|
||||
{
|
||||
return $this->hasMany(ActivityLog::class, 'actor_id')
|
||||
->where('actor_type', 'user')
|
||||
->where('event', 'like', 'admin:%');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the servers that a user can access.
|
||||
* Either because they are an admin or because they are the owner/ a subuser of the server.
|
||||
@@ -526,4 +540,9 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
{
|
||||
$this->update(['mfa_email_enabled' => $condition]);
|
||||
}
|
||||
|
||||
public function getAdminActivityName(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
}
|
||||
|
||||
141
app/Observers/AdminActivityObserver.php
Normal file
141
app/Observers/AdminActivityObserver.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Facades\Activity;
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AdminActivityObserver
|
||||
{
|
||||
/**
|
||||
* Tracks events already logged in this request to avoid duplicates.
|
||||
*
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private static array $logged = [];
|
||||
|
||||
/**
|
||||
* Determines if the current request is being handled by the admin panel.
|
||||
*/
|
||||
private function isAdminPanel(): bool
|
||||
{
|
||||
return Filament::getCurrentPanel()?->getId() === 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an admin activity event for the given model, deduplicating within a single request.
|
||||
*
|
||||
* @param string $event The event name (e.g. 'admin:user.create')
|
||||
* @param Model $model The model being acted upon
|
||||
* @param array<string, mixed> $properties Additional properties to log
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
private function log(string $event, Model $model, array $properties = []): void
|
||||
{
|
||||
if (!$this->isAdminPanel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$actor = user();
|
||||
if (!$actor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
$log = Activity::event($event)
|
||||
->actor($actor)
|
||||
->subject($model);
|
||||
|
||||
foreach ($properties as $propKey => $propValue) {
|
||||
$log->property($propKey, $propValue);
|
||||
}
|
||||
|
||||
$log->log();
|
||||
}
|
||||
|
||||
public function created(Model $model): void
|
||||
{
|
||||
$this->log($this->eventFor($model, 'create'), $model, [
|
||||
'name' => $this->displayNameFor($model),
|
||||
]);
|
||||
}
|
||||
|
||||
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),
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleted(Model $model): void
|
||||
{
|
||||
$this->log($this->eventFor($model, 'delete'), $model, [
|
||||
'name' => $this->displayNameFor($model),
|
||||
]);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private function eventFor(Model $model, string $action): string
|
||||
{
|
||||
return sprintf('admin:%s.%s', $this->resourceNameFor($model), $action);
|
||||
}
|
||||
|
||||
private function resourceNameFor(Model $model): string
|
||||
{
|
||||
$constant = $model::class . '::RESOURCE_NAME';
|
||||
|
||||
if (defined($constant)) {
|
||||
$value = constant($constant);
|
||||
|
||||
if (is_string($value) && $value !== '') {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return Str::of(class_basename($model))->snake()->toString();
|
||||
}
|
||||
|
||||
private function displayNameFor(Model $model): string
|
||||
{
|
||||
if (method_exists($model, 'getAdminActivityName')) {
|
||||
return $model->getAdminActivityName();
|
||||
}
|
||||
|
||||
return (string) $model->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sorted list of attribute names that changed on the given model,
|
||||
* excluding internal timestamps.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function changedFieldsFor(Model $model): array
|
||||
{
|
||||
$fields = collect(array_keys($model->getChanges()))
|
||||
->reject(fn (string $field) => $field === 'updated_at')
|
||||
->values()
|
||||
->all();
|
||||
|
||||
sort($fields);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
||||
26
app/Traits/HasAdminActivityLogging.php
Normal file
26
app/Traits/HasAdminActivityLogging.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Observers\AdminActivityObserver;
|
||||
|
||||
trait HasAdminActivityLogging
|
||||
{
|
||||
public static function bootHasAdminActivityLogging(): void
|
||||
{
|
||||
$observer = new AdminActivityObserver();
|
||||
|
||||
static::created(fn ($model) => $observer->created($model));
|
||||
static::updated(fn ($model) => $observer->updated($model));
|
||||
static::deleted(fn ($model) => $observer->deleted($model));
|
||||
}
|
||||
|
||||
public function getAdminActivityName(): string
|
||||
{
|
||||
if (isset($this->attributes['name'])) {
|
||||
return (string) $this->attributes['name'];
|
||||
}
|
||||
|
||||
return (string) $this->getKey();
|
||||
}
|
||||
}
|
||||
@@ -125,4 +125,34 @@ return [
|
||||
],
|
||||
'crashed' => 'Server crashed',
|
||||
],
|
||||
'admin' => [
|
||||
'settings' => [
|
||||
'update' => 'Updated panel settings (<b>:count</b>): <b>:settings</b>',
|
||||
],
|
||||
'user' => [
|
||||
'create' => 'Created user <b>:name</b>',
|
||||
'update' => 'Updated user <b>:name</b>',
|
||||
'delete' => 'Deleted user <b>:name</b>',
|
||||
],
|
||||
'server' => [
|
||||
'create' => 'Created server <b>:name</b>',
|
||||
'update' => 'Updated server <b>:name</b>',
|
||||
'delete' => 'Deleted server <b>:name</b>',
|
||||
],
|
||||
'node' => [
|
||||
'create' => 'Created node <b>:name</b>',
|
||||
'update' => 'Updated node <b>:name</b>',
|
||||
'delete' => 'Deleted node <b>:name</b>',
|
||||
],
|
||||
'egg' => [
|
||||
'create' => 'Created egg <b>:name</b>',
|
||||
'update' => 'Updated egg <b>:name</b>',
|
||||
'delete' => 'Deleted egg <b>:name</b>',
|
||||
],
|
||||
'role' => [
|
||||
'create' => 'Created role <b>:name</b>',
|
||||
'update' => 'Updated role <b>:name</b>',
|
||||
'delete' => 'Deleted role <b>:name</b>',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
return [
|
||||
'empty_table' => 'Yay! No Errors!',
|
||||
'empty_audit_log' => 'No admin actions have been logged yet.',
|
||||
'model_label' => 'Admin Audit Log',
|
||||
'model_label_plural' => 'Admin Audit Logs',
|
||||
'total_logs' => 'Total Logs',
|
||||
'error' => 'Error',
|
||||
'warning' => 'Warning',
|
||||
@@ -10,6 +13,15 @@ return [
|
||||
'debug' => 'Debug',
|
||||
'navigation' => [
|
||||
'panel_logs' => 'Panel Logs',
|
||||
'admin_audit_log' => 'Admin Audit',
|
||||
],
|
||||
'table' => [
|
||||
'actor' => 'Actor',
|
||||
'event' => 'Event',
|
||||
'description' => 'Description',
|
||||
'ip' => 'IP Address',
|
||||
'timestamp' => 'Time',
|
||||
'system' => 'System',
|
||||
],
|
||||
'actions' => [
|
||||
'upload_logs' => 'Upload Logs?',
|
||||
|
||||
Reference in New Issue
Block a user