diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php index 1a0154b6f..7a9b6979b 100644 --- a/app/Transformers/Api/Client/ActivityLogTransformer.php +++ b/app/Transformers/Api/Client/ActivityLogTransformer.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Transformers\Api\Client; +use Illuminate\Support\Str; use Pterodactyl\Models\User; use Pterodactyl\Models\ActivityLog; @@ -22,7 +23,7 @@ class ActivityLogTransformer extends BaseClientTransformer 'is_api' => !is_null($model->api_key_id), 'ip' => $model->ip, 'description' => $model->description, - 'properties' => $model->properties ? $model->properties->toArray() : [], + 'properties' => $this->properties($model), 'has_additional_metadata' => $this->hasAdditionalMetadata($model), 'timestamp' => $model->timestamp->toIso8601String(), ]; @@ -37,6 +38,33 @@ class ActivityLogTransformer extends BaseClientTransformer 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): array + { + if (!$model->properties || $model->properties->isEmpty()) { + return []; + } + + $properties = $model->properties + ->mapWithKeys(function ($value, $key) { + if (!is_array($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 $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/resources/lang/en/activity.php b/resources/lang/en/activity.php index cb33cc971..bcc6abaa0 100644 --- a/resources/lang/en/activity.php +++ b/resources/lang/en/activity.php @@ -15,20 +15,20 @@ return [ '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', ], 'user' => [ 'account' => [ - 'email-changed' => 'Changed email from :old to :new', + 'email-changed' => 'Changed email from :old to :new', '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', @@ -37,64 +37,66 @@ return [ ], 'server' => [ 'backup' => [ - 'download' => 'Downloaded the :name backup', - 'delete' => 'Deleted the :name backup', - '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', - 'complete' => 'Marked the :name backup as complete', - 'fail' => 'Marked the :name backup as failed', - 'lock' => 'Locked the :name backup', - 'unlock' => 'Unlocked the :name backup', + 'download' => 'Downloaded the :name backup', + 'delete' => 'Deleted the :name backup', + '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', + 'complete' => 'Marked the :name backup as complete', + 'fail' => 'Marked the :name backup as failed', + 'lock' => 'Locked the :name backup', + 'unlock' => 'Unlocked the :name backup', ], 'database' => [ - 'create' => 'Created new database :name', - 'rotate-password' => 'Password rotated for database :name', - 'delete' => 'Deleted database :name', + 'create' => 'Created new database :name', + 'rotate-password' => 'Password rotated for database :name', + 'delete' => 'Deleted database :name', ], 'file' => [ - 'compress' => 'Created new file archive of files in :directory', - 'read' => 'Viewed the contents of :file', - 'copy' => 'Created a copy of :file', - 'create-directory' => 'Created a new directory :name in :directory', - 'decompress' => 'Decompressed a file archive in :directory', - 'delete' => 'Deleted files in :directory', - 'download' => 'Downloaded :file', - 'pull' => 'Downloaded a remote file from :url to :directory', - 'rename' => 'Renamed files in :directory', - 'write' => 'Wrote new content to :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 a new directory :name in :directory', + 'decompress' => 'Decompressed :files in :directory', + 'delete_one' => 'Deleted :directory/:files', + 'delete_other' => 'Deleted :count files in :directory', + 'download' => 'Downloaded :file', + 'pull' => 'Downloaded a remote file from :url to :directory', + 'rename' => 'Renamed files in :directory', + 'write' => 'Wrote new content to :file', 'upload' => 'Began a file upload', ], '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' => [ - 'store' => 'Created the :name schedule', - 'update' => 'Updated the :name schedule', - 'execute' => 'Manually executed the :name schedule', - 'delete' => 'Deleted the :name schedule', + 'store' => 'Created the :name schedule', + 'update' => 'Updated the :name schedule', + 'execute' => 'Manually executed the :name schedule', + 'delete' => 'Deleted the :name schedule', ], 'task' => [ - 'create' => 'Created a new ":action" task for the :name schedule', - 'update' => 'Updated the ":action" task for the :name schedule', - 'delete' => 'Deleted a task for the :name schedule', + 'create' => 'Created a new ":action" task for the :name schedule', + 'update' => 'Updated the ":action" task for the :name schedule', + 'delete' => 'Deleted a task for the :name schedule', ], 'settings' => [ - 'rename' => 'Renamed the server from :old to :new', + 'rename' => 'Renamed the server from :old to :new', 'reinstall' => 'Triggered a server reinstall', ], 'startup' => [ - 'edit' => 'Edited the :variable startup variable for the server from ":old" to ":new"', - 'image' => 'Updated the Docker Image for the server from :old to :new', + 'edit' => 'Changed the :variable variable from ":old" to ":new"', + 'image' => 'Updated the Docker Image for the server from :old to :new', ], 'subuser' => [ - 'create' => 'Added :email as a subuser', - 'update' => 'Updated the subuser permissions for :email', - 'delete' => 'Removed :email as a subuser', + 'create' => 'Added :email as a subuser', + 'update' => 'Updated the subuser permissions for :email', + 'delete' => 'Removed :email as a subuser', ], ], ]; diff --git a/resources/scripts/components/elements/activity/ActivityLogEntry.tsx b/resources/scripts/components/elements/activity/ActivityLogEntry.tsx index ac8c00e29..cf613c9c2 100644 --- a/resources/scripts/components/elements/activity/ActivityLogEntry.tsx +++ b/resources/scripts/components/elements/activity/ActivityLogEntry.tsx @@ -27,6 +27,13 @@ export default ({ activity, children }: Props) => { return current.toString(); }; + const properties = Object.keys(activity.properties).reduce((obj, key) => ({ + ...obj, + [key]: key === 'count' || key.endsWith('_count') + ? activity.properties[key] + : `${activity.properties[key]}`, + }), {}); + return (
-
+