diff --git a/app/Filament/Server/Resources/ActivityResource/Pages/ListActivities.php b/app/Filament/Server/Resources/ActivityResource/Pages/ListActivities.php
index 8a19545211..b32ca0c849 100644
--- a/app/Filament/Server/Resources/ActivityResource/Pages/ListActivities.php
+++ b/app/Filament/Server/Resources/ActivityResource/Pages/ListActivities.php
@@ -2,6 +2,7 @@
namespace App\Filament\Server\Resources\ActivityResource\Pages;
+use App\Filament\Admin\Resources\UserResource\Pages\EditUser;
use App\Filament\Server\Resources\ActivityResource;
use App\Models\ActivityLog;
use App\Models\User;
@@ -20,12 +21,16 @@ public function table(Table $table): Table
->columns([
TextColumn::make('event')
->html()
- ->formatStateUsing(fn ($state, ActivityLog $activityLog) => __('activity.'.str($state)->replace(':', '.'))) // TODO: convert properties to a format that trans likes, see ActivityLogEntry.tsx - wrapProperties
- ->description(fn ($state) => $state),
+ ->description(fn ($state) => $state)
+ ->formatStateUsing(function ($state, ActivityLog $activityLog) {
+ $properties = $activityLog->wrapProperties();
+
+ return trans_choice('activity.'.str($state)->replace(':', '.'), array_get($properties, 'count', 1), $properties);
+ }),
TextColumn::make('user')
->state(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User ? $activityLog->actor->username : 'System')
->tooltip(fn (ActivityLog $activityLog) => auth()->user()->can('seeIps activityLog') ? $activityLog->ip : '')
- ->url(fn (ActivityLog $activityLog): string => $activityLog->actor instanceof User ? route('filament.admin.resources.users.edit', ['record' => $activityLog->actor]) : ''),
+ ->url(fn (ActivityLog $activityLog): string => $activityLog->actor instanceof User ? EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin', tenant: null) : ''),
DateTimeColumn::make('timestamp')
->since()
->sortable(),
diff --git a/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php b/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php
index 87e087b42d..6db4fc4e0e 100644
--- a/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php
+++ b/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php
@@ -130,13 +130,17 @@ public function table(Table $table): Table
->required(),
])
->action(function ($data, File $file, DaemonFileRepository $fileRepository) use ($server) {
+ $files = [['to' => $data['name'], 'from' => $file->name]];
+
$fileRepository
->setServer($server)
- ->renameFiles($this->path, [['to' => $data['name'], 'from' => $file->name]]);
+ ->renameFiles($this->path, $files);
Activity::event('server:file.rename')
->property('directory', $this->path)
- ->property('files', [['to' => $data['name'], 'from' => $file->name]])
+ ->property('files', $files)
+ ->property('to', $data['name'])
+ ->property('from', $file->name)
->log();
Notification::make()
@@ -204,13 +208,17 @@ public function table(Table $table): Table
->action(function ($data, File $file, DaemonFileRepository $fileRepository) use ($server) {
$location = resolve_path(join_paths($this->path, $data['location']));
+ $files = [['to' => $location, 'from' => $file->name]];
+
$fileRepository
->setServer($server)
- ->renameFiles($this->path, [['to' => $location, 'from' => $file->name]]);
+ ->renameFiles($this->path, $files);
Activity::event('server:file.rename')
->property('directory', $this->path)
- ->property('files', [['to' => $location, 'from' => $file->name]])
+ ->property('files', $files)
+ ->property('to', $location)
+ ->property('from', $file->name)
->log();
Notification::make()
@@ -309,7 +317,7 @@ public function table(Table $table): Table
Activity::event('server:file.decompress')
->property('directory', $this->path)
- ->property('files', $file->name)
+ ->property('file', $file->name)
->log();
Notification::make()
@@ -342,6 +350,7 @@ public function table(Table $table): Table
BulkActionGroup::make([
BulkAction::make('move')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
+ ->hidden() // TODO
->form([
TextInput::make('location')
->label('File name')
@@ -366,7 +375,7 @@ public function table(Table $table): Table
->log();
Notification::make()
- ->title(count($files) . ' Files were moved from to ' . $location)
+ ->title(count($files) . ' Files were moved from ' . $location)
->success()
->send();
}),
diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php
index 6fc443e8fd..38acc49a15 100644
--- a/app/Http/Controllers/Api/Client/Servers/FileController.php
+++ b/app/Http/Controllers/Api/Client/Servers/FileController.php
@@ -146,13 +146,17 @@ public function create(CreateFolderRequest $request, Server $server): JsonRespon
*/
public function rename(RenameFileRequest $request, Server $server): JsonResponse
{
+ $files = $request->input('files');
+
$this->fileRepository
->setServer($server)
- ->renameFiles($request->input('root'), $request->input('files'));
+ ->renameFiles($request->input('root'), $files);
Activity::event('server:file.rename')
->property('directory', $request->input('root'))
- ->property('files', $request->input('files'))
+ ->property('files', $files)
+ ->property('to', $files['to'])
+ ->property('from', $files['from'])
->log();
return new JsonResponse([], Response::HTTP_NO_CONTENT);
@@ -210,7 +214,7 @@ public function decompress(DecompressFilesRequest $request, Server $server): Jso
Activity::event('server:file.decompress')
->property('directory', $request->input('root'))
- ->property('files', $request->input('file'))
+ ->property('file', $request->input('file'))
->log();
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php
index 83daf066c0..15e2fd1431 100644
--- a/app/Models/ActivityLog.php
+++ b/app/Models/ActivityLog.php
@@ -11,6 +11,7 @@
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Model as IlluminateModel;
+use Illuminate\Support\Str;
/**
* \App\Models\ActivityLog.
@@ -151,8 +152,8 @@ public function htmlable(): string
'username' => 'system',
]);
}
-
- $event = __('activity.'.str($this->event)->replace(':', '.'));
+ $properties = $this->wrapProperties();
+ $event = trans_choice('activity.'.str($this->event)->replace(':', '.'), array_key_exists('count', $properties) ? $properties['count'] : 1, $properties);
return "
@@ -166,4 +167,38 @@ public function htmlable(): string
";
}
+
+ public function wrapProperties(): array
+ {
+ if (!$this->properties || $this->properties->isEmpty()) {
+ return [];
+ }
+
+ $properties = $this->properties->mapWithKeys(function ($value, $key) {
+ if (!is_array($value)) {
+ // Perform some directory normalization at this point.
+ if ($key === 'directory') {
+ $value = str_replace('//', '/', '/' . trim($value, '/') . '/');
+ }
+
+ return [$key => $value];
+ }
+
+ $first = array_first($value);
+
+ // Backwards compatibility for old logs
+ if (is_array($first)) {
+ return ["{$key}_count" => count($value)];
+ }
+
+ return [$key => $first, "{$key}_count" => count($value)];
+ });
+
+ $keys = $properties->keys()->filter(fn ($key) => Str::endsWith($key, '_count'))->values();
+ if ($keys->containsOneItem()) {
+ $properties = $properties->merge(['count' => $properties->get($keys[0])])->except([$keys[0]]);
+ }
+
+ return $properties->toArray();
+ }
}
diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php
index a54cb1f304..a5d3128d54 100644
--- a/app/Transformers/Api/Client/ActivityLogTransformer.php
+++ b/app/Transformers/Api/Client/ActivityLogTransformer.php
@@ -2,7 +2,6 @@
namespace App\Transformers\Api\Client;
-use Illuminate\Support\Str;
use App\Models\User;
use App\Models\ActivityLog;
use Illuminate\Database\Eloquent\Model;
@@ -29,7 +28,7 @@ public function transform(ActivityLog $model): array
'is_api' => !is_null($model->api_key_id),
'ip' => $this->canViewIP($model->actor) ? $model->ip : null,
'description' => $model->description,
- 'properties' => $this->properties($model),
+ 'properties' => $model->wrapProperties(),
'has_additional_metadata' => $this->hasAdditionalMetadata($model),
'timestamp' => $model->timestamp->toAtomString(),
];
@@ -44,42 +43,6 @@ public function includeActor(ActivityLog $model): ResourceAbstract
return $this->item($model->actor, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME);
}
- /**
- * Transforms any array values in the properties into a countable field for easier
- * use within the translation outputs.
- */
- protected function properties(ActivityLog $model): object
- {
- if (!$model->properties || $model->properties->isEmpty()) {
- return (object) [];
- }
-
- $properties = $model->properties
- ->mapWithKeys(function ($value, $key) use ($model) {
- if ($key === 'ip' && $model->actor instanceof User && !$model->actor->is($this->request->user())) {
- return [$key => '[hidden]'];
- }
-
- if (!is_array($value)) {
- // Perform some directory normalization at this point.
- if ($key === 'directory') {
- $value = str_replace('//', '/', '/' . trim($value, '/') . '/');
- }
-
- return [$key => $value];
- }
-
- return [$key => $value, "{$key}_count" => count($value)];
- });
-
- $keys = $properties->keys()->filter(fn ($key) => Str::endsWith($key, '_count'))->values();
- if ($keys->containsOneItem()) {
- $properties = $properties->merge(['count' => $properties->get($keys[0])])->except([$keys[0]]);
- }
-
- return (object) $properties->toArray();
- }
-
/**
* Determines if there are any log properties that we've not already exposed
* in the response language string and that are not just the IP address or
diff --git a/lang/en/activity.php b/lang/en/activity.php
index 3158063b7d..fa712e40d5 100644
--- a/lang/en/activity.php
+++ b/lang/en/activity.php
@@ -15,7 +15,7 @@
'checkpoint' => 'Two-factor authentication requested',
'recovery-token' => 'Used two-factor recovery token',
'token' => 'Solved two-factor challenge',
- 'ip-blocked' => 'Blocked request from unlisted IP address for :identifier',
+ 'ip-blocked' => 'Blocked request from unlisted IP address for :identifier',
'sftp' => [
'fail' => 'Failed SFTP log in',
],
@@ -26,12 +26,12 @@
'password-changed' => 'Changed password',
],
'api-key' => [
- 'create' => 'Created new API key :identifier',
- 'delete' => 'Deleted API key :identifier',
+ 'create' => 'Created new API key :identifier',
+ 'delete' => 'Deleted API key :identifier',
],
'ssh-key' => [
- 'create' => 'Added SSH key :fingerprint to account',
- 'delete' => 'Removed SSH key :fingerprint from account',
+ 'create' => 'Added SSH key :fingerprint to account',
+ 'delete' => 'Removed SSH key :fingerprint from account',
],
'two-factor' => [
'create' => 'Enabled two-factor auth',
@@ -41,7 +41,7 @@
'server' => [
'reinstall' => 'Reinstalled server',
'console' => [
- 'command' => 'Executed ":command" on the server',
+ 'command' => 'Executed ":command" on the server',
],
'power' => [
'start' => 'Started the server',
@@ -52,7 +52,7 @@
'backup' => [
'download' => 'Downloaded the :name backup',
'delete' => 'Deleted the :name backup',
- 'restore' => 'Restored the :name backup (deleted files: :truncate)',
+ 'restore' => 'Restored the :name backup (deleted files: :truncate)',
'restore-complete' => 'Completed restoration of the :name backup',
'restore-failed' => 'Failed to complete restoration of the :name backup',
'start' => 'Started a new backup :name',
@@ -67,40 +67,32 @@
'delete' => 'Deleted database :name',
],
'file' => [
- 'compress_one' => 'Compressed :directory:file',
- 'compress_other' => 'Compressed :count files in :directory',
- 'read' => 'Viewed the contents of :file',
- 'copy' => 'Created a copy of :file',
- 'create-directory' => 'Created directory :directory:name',
- 'decompress' => 'Decompressed :files in :directory',
- 'delete_one' => 'Deleted :directory:files.0',
- 'delete_other' => 'Deleted :count files in :directory',
- 'download' => 'Downloaded :file',
- 'pull' => 'Downloaded a remote file from :url to :directory',
- 'rename_one' => 'Renamed :directory:files.0.from to :directory:files.0.to',
- 'rename_other' => 'Renamed :count files in :directory',
- 'write' => 'Wrote new content to :file',
+ 'compress' => 'Compressed :directory:files|Compressed :count files in :directory',
+ 'read' => 'Viewed the contents of :file',
+ 'copy' => 'Created a copy of :file',
+ 'create-directory' => 'Created directory :directory:name',
+ 'decompress' => 'Decompressed :file in :directory',
+ 'delete' => 'Deleted :directory:files|Deleted :count files in :directory',
+ 'download' => 'Downloaded :file',
+ 'pull' => 'Downloaded a remote file from :url to :directory',
+ 'rename' => 'Renamed :directory:from to :directory:to|Renamed :count files in :directory',
+ 'write' => 'Wrote new content to :file',
'upload' => 'Began a file upload',
- 'uploaded' => 'Uploaded :directory:file',
+ 'uploaded' => 'Uploaded :directory:file',
],
'sftp' => [
'denied' => 'Blocked SFTP access due to permissions',
- 'create_one' => 'Created :files.0',
- 'create_other' => 'Created :count new files',
- 'write_one' => 'Modified the contents of :files.0',
- 'write_other' => 'Modified the contents of :count files',
- 'delete_one' => 'Deleted :files.0',
- 'delete_other' => 'Deleted :count files',
- 'create-directory_one' => 'Created the :files.0 directory',
- 'create-directory_other' => 'Created :count directories',
- 'rename_one' => 'Renamed :files.0.from to :files.0.to',
- 'rename_other' => 'Renamed or moved :count files',
+ 'create' => 'Created :files|Created :count new files',
+ 'write' => 'Modified the contents of :files|Modified the contents of :count files',
+ 'delete' => 'Deleted :files|Deleted :count files',
+ 'create-directory' => 'Created the :files directory|Created :count directories',
+ 'rename' => 'Renamed :from to :to|Renamed or moved :count files',
],
'allocation' => [
- 'create' => 'Added :allocation to the server',
- 'notes' => 'Updated the notes for :allocation from ":old" to ":new"',
- 'primary' => 'Set :allocation as the primary server allocation',
- 'delete' => 'Deleted the :allocation allocation',
+ 'create' => 'Added :allocation to the server',
+ 'notes' => 'Updated the notes for :allocation from ":old" to ":new"',
+ 'primary' => 'Set :allocation as the primary server allocation',
+ 'delete' => 'Deleted the :allocation allocation',
],
'schedule' => [
'create' => 'Created the :name schedule',
@@ -114,8 +106,8 @@
'delete' => 'Deleted a task for the :name schedule',
],
'settings' => [
- 'rename' => 'Renamed the server from :old to :new',
- 'description' => 'Changed the server description from :old to :new',
+ 'rename' => 'Renamed the server from ":old" to ":new"',
+ 'description' => 'Changed the server description from ":old" to ":new"',
],
'startup' => [
'edit' => 'Changed the :variable variable from ":old" to ":new"',