Fix translations for activity logs (#907)

* fix translations for activity logs

* add backwards compatibility for old logs

* update lang file

* small cleanup

* fix singular/ plural for "file"

* fix for "rename" + disable bulk move (because it's not working)
This commit is contained in:
Boy132 2025-01-23 09:05:23 +01:00 committed by GitHub
parent 262e2fd09a
commit 37ba62410f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 97 additions and 89 deletions

View File

@ -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 @@ class ListActivities extends ListRecords
->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(),

View File

@ -130,13 +130,17 @@ class ListFiles extends ListRecords
->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 @@ class ListFiles extends ListRecords
->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 @@ class ListFiles extends ListRecords
Activity::event('server:file.decompress')
->property('directory', $this->path)
->property('files', $file->name)
->property('file', $file->name)
->log();
Notification::make()
@ -342,6 +350,7 @@ class ListFiles extends ListRecords
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 @@ class ListFiles extends ListRecords
->log();
Notification::make()
->title(count($files) . ' Files were moved from to ' . $location)
->title(count($files) . ' Files were moved from ' . $location)
->success()
->send();
}),

View File

@ -146,13 +146,17 @@ class FileController extends ClientApiController
*/
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 @@ class FileController extends ClientApiController
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);

View File

@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
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 @@ class ActivityLog extends Model
'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 "
<div style='display: flex; align-items: center;'>
@ -166,4 +167,38 @@ class ActivityLog extends Model
</div>
";
}
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();
}
}

View File

@ -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 @@ class ActivityLogTransformer extends BaseClientTransformer
'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 @@ 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): 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

View File

@ -15,7 +15,7 @@ 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 <b>:identifier</b>',
'sftp' => [
'fail' => 'Failed SFTP log in',
],
@ -26,12 +26,12 @@ return [
'password-changed' => 'Changed password',
],
'api-key' => [
'create' => 'Created new API key :identifier',
'delete' => 'Deleted API key :identifier',
'create' => 'Created new API key <b>:identifier</b>',
'delete' => 'Deleted API key <b>:identifier</b>',
],
'ssh-key' => [
'create' => 'Added SSH key :fingerprint to account',
'delete' => 'Removed SSH key :fingerprint from account',
'create' => 'Added SSH key <b>:fingerprint</b> to account',
'delete' => 'Removed SSH key <b>:fingerprint</b> from account',
],
'two-factor' => [
'create' => 'Enabled two-factor auth',
@ -41,7 +41,7 @@ return [
'server' => [
'reinstall' => 'Reinstalled server',
'console' => [
'command' => 'Executed ":command" on the server',
'command' => 'Executed "<b>:command</b>" on the server',
],
'power' => [
'start' => 'Started the server',
@ -52,7 +52,7 @@ return [
'backup' => [
'download' => 'Downloaded the <b>:name</b> backup',
'delete' => 'Deleted the <b>:name</b> backup',
'restore' => 'Restored the <b>:name</b> backup (deleted files: :truncate)',
'restore' => 'Restored the <b>:name</b> backup (deleted files: <b>:truncate</b>)',
'restore-complete' => 'Completed restoration of the <b>:name</b> backup',
'restore-failed' => 'Failed to complete restoration of the <b>:name</b> backup',
'start' => 'Started a new backup <b>:name</b>',
@ -67,40 +67,32 @@ return [
'delete' => 'Deleted database <b>:name</b>',
],
'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<b>:name</b>',
'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 <b>:directory:files</b>|Compressed <b>:count</b> files in <b>:directory</b>',
'read' => 'Viewed the contents of <b>:file</b>',
'copy' => 'Created a copy of <b>:file</b>',
'create-directory' => 'Created directory <b>:directory:name</b>',
'decompress' => 'Decompressed <b>:file</b> in <b>:directory</b>',
'delete' => 'Deleted <b>:directory:files</b>|Deleted <b>:count</b> files in <b>:directory</b>',
'download' => 'Downloaded <b>:file</b>',
'pull' => 'Downloaded a remote file from <b>:url</b> to <b>:directory</b>',
'rename' => 'Renamed <b>:directory:from</b> to <b>:directory:to</b>|Renamed <b>:count</b> files in <b>:directory</b>',
'write' => 'Wrote new content to <b>:file</b>',
'upload' => 'Began a file upload',
'uploaded' => 'Uploaded :directory:file',
'uploaded' => 'Uploaded <b>:directory:file</b>',
],
'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 <b>:files</b>|Created <b>:count</b> new files',
'write' => 'Modified the contents of <b>:files</b>|Modified the contents of <b>:count</b> files',
'delete' => 'Deleted <b>:files</b>|Deleted <b>:count</b> files',
'create-directory' => 'Created the <b>:files</b> directory|Created <b>:count</b> directories',
'rename' => 'Renamed <b>:from</b> to <b>:to</b>|Renamed or moved <b>:count</b> files',
],
'allocation' => [
'create' => 'Added :allocation to the server',
'notes' => 'Updated the notes for :allocation from "<b>:old</b>" to "<b>:new</b>"',
'primary' => 'Set :allocation as the primary server allocation',
'delete' => 'Deleted the :allocation allocation',
'create' => 'Added <b>:allocation</b> to the server',
'notes' => 'Updated the notes for <b>:allocation</b> from "<b>:old</b>" to "<b>:new</b>"',
'primary' => 'Set <b>:allocation</b> as the primary server allocation',
'delete' => 'Deleted the <b>:allocation</b> allocation',
],
'schedule' => [
'create' => 'Created the <b>:name</b> schedule',
@ -114,8 +106,8 @@ return [
'delete' => 'Deleted a task for the <b>:name</b> schedule',
],
'settings' => [
'rename' => 'Renamed the server from <b>:old</b> to <b>:new</b>',
'description' => 'Changed the server description from <b>:old</b> to <b>:new</b>',
'rename' => 'Renamed the server from "<b>:old</b>" to "<b>:new</b>"',
'description' => 'Changed the server description from "<b>:old</b>" to "<b>:new</b>"',
],
'startup' => [
'edit' => 'Changed the <b>:variable</b> variable from "<b>:old</b>" to "<b>:new</b>"',