Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix translations for activity logs #907

Merged
merged 7 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(),
Expand Down
21 changes: 15 additions & 6 deletions app/Filament/Server/Resources/FileResource/Pages/ListFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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')
Expand All @@ -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();
}),
Expand Down
10 changes: 7 additions & 3 deletions app/Http/Controllers/Api/Client/Servers/FileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
39 changes: 37 additions & 2 deletions app/Models/ActivityLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 "
<div style='display: flex; align-items: center;'>
Expand All @@ -166,4 +167,38 @@ public function htmlable(): string
</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();
}
}
39 changes: 1 addition & 38 deletions app/Transformers/Api/Client/ActivityLogTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(),
];
Expand All @@ -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
Expand Down
66 changes: 29 additions & 37 deletions lang/en/activity.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <b>:identifier</b>',
'sftp' => [
'fail' => 'Failed SFTP log in',
],
Expand All @@ -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 <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',
Expand All @@ -41,7 +41,7 @@
'server' => [
'reinstall' => 'Reinstalled server',
'console' => [
'command' => 'Executed ":command" on the server',
'command' => 'Executed "<b>:command</b>" on the server',
],
'power' => [
'start' => 'Started the server',
Expand All @@ -52,7 +52,7 @@
'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>',
Expand All @@ -67,40 +67,32 @@
'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',
Expand All @@ -114,8 +106,8 @@
'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>"',
Expand Down
Loading