Add support for returning transforming activity logs on the front-end
This commit is contained in:
parent
e15985ea39
commit
a5521ecb79
9 changed files with 162 additions and 6 deletions
30
app/Http/Controllers/Api/Client/ActivityLogController.php
Normal file
30
app/Http/Controllers/Api/Client/ActivityLogController.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Client;
|
||||
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Spatie\QueryBuilder\AllowedFilter;
|
||||
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||
use Pterodactyl\Transformers\Api\Client\ActivityLogTransformer;
|
||||
|
||||
class ActivityLogController extends ClientApiController
|
||||
{
|
||||
/**
|
||||
* Returns a paginated set of the user's activity logs.
|
||||
*/
|
||||
public function __invoke(ClientApiRequest $request): array
|
||||
{
|
||||
$activity = QueryBuilder::for($request->user()->activity())
|
||||
->with('actor')
|
||||
->allowedFilters([
|
||||
AllowedFilter::exact('ip'),
|
||||
AllowedFilter::partial('event'),
|
||||
])
|
||||
->paginate(min($request->query('per_page', 50), 100))
|
||||
->appends($request->query());
|
||||
|
||||
return $this->fractal->collection($activity)
|
||||
->transformWith($this->getTransformer(ActivityLogTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ use Illuminate\Database\Eloquent\Model as IlluminateModel;
|
|||
* @property string|null $actor_type
|
||||
* @property int|null $actor_id
|
||||
* @property \Illuminate\Support\Collection|null $properties
|
||||
* @property string $timestamp
|
||||
* @property \Carbon\Carbon $timestamp
|
||||
* @property IlluminateModel|\Eloquent $actor
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ActivityLogSubject[] $subjects
|
||||
* @property int|null $subjects_count
|
||||
|
@ -47,6 +47,8 @@ class ActivityLog extends Model
|
|||
{
|
||||
use MassPrunable;
|
||||
|
||||
public const RESOURCE_NAME = 'activity_log';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $guarded = [
|
||||
|
@ -56,6 +58,7 @@ class ActivityLog extends Model
|
|||
|
||||
protected $casts = [
|
||||
'properties' => 'collection',
|
||||
'timestamp' => 'datetime',
|
||||
];
|
||||
|
||||
protected $with = ['subjects'];
|
||||
|
|
37
app/Transformers/Api/Client/ActivityLogTransformer.php
Normal file
37
app/Transformers/Api/Client/ActivityLogTransformer.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Transformers\Api\Client;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\ActivityLog;
|
||||
|
||||
class ActivityLogTransformer extends BaseClientTransformer
|
||||
{
|
||||
protected array $availableIncludes = ['actor'];
|
||||
|
||||
public function getResourceName(): string
|
||||
{
|
||||
return ActivityLog::RESOURCE_NAME;
|
||||
}
|
||||
|
||||
public function transform(ActivityLog $model): array
|
||||
{
|
||||
return [
|
||||
'batch' => $model->batch,
|
||||
'event' => $model->event,
|
||||
'ip' => $model->ip,
|
||||
'description' => $model->description,
|
||||
'properties' => $model->properties,
|
||||
'timestamp' => $model->timestamp->toIso8601String(),
|
||||
];
|
||||
}
|
||||
|
||||
public function includeActor(ActivityLog $model)
|
||||
{
|
||||
if (!$model->actor instanceof User) {
|
||||
return $this->null();
|
||||
}
|
||||
|
||||
return $this->item($model->actor, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME);
|
||||
}
|
||||
}
|
26
resources/scripts/api/definitions/helpers.ts
Normal file
26
resources/scripts/api/definitions/helpers.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { FractalResponseData, FractalResponseList } from '@/api/http';
|
||||
|
||||
type Transformer<T> = (callback: FractalResponseData) => T;
|
||||
|
||||
const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list';
|
||||
|
||||
function transform<T, M>(data: null | undefined, transformer: Transformer<T>, missing?: M): M;
|
||||
function transform<T, M>(data: FractalResponseData | null | undefined, transformer: Transformer<T>, missing?: M): T | M;
|
||||
function transform<T, M>(data: FractalResponseList | null | undefined, transformer: Transformer<T>, missing?: M): T[] | M;
|
||||
function transform<T> (data: FractalResponseData | FractalResponseList | null | undefined, transformer: Transformer<T>, missing = undefined) {
|
||||
if (data === undefined || data === null) {
|
||||
return missing;
|
||||
}
|
||||
|
||||
if (isList(data)) {
|
||||
return data.data.map(transformer);
|
||||
}
|
||||
|
||||
if (!data || !data.attributes || data.object === 'null_resource') {
|
||||
return missing;
|
||||
}
|
||||
|
||||
return transformer(data);
|
||||
}
|
||||
|
||||
export { transform };
|
3
resources/scripts/api/definitions/index.d.ts
vendored
3
resources/scripts/api/definitions/index.d.ts
vendored
|
@ -1,4 +1,5 @@
|
|||
import { MarkRequired } from 'ts-essentials';
|
||||
import { FractalResponseData, FractalResponseList } from '../http';
|
||||
|
||||
export type UUID = string;
|
||||
|
||||
|
@ -6,7 +7,7 @@ export type UUID = string;
|
|||
export interface Model {}
|
||||
|
||||
interface ModelWithRelationships extends Model {
|
||||
relationships: Record<string, unknown>;
|
||||
relationships: Record<string, FractalResponseData | FractalResponseList | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,16 @@
|
|||
import { Model } from '@/api/definitions';
|
||||
import { Model, UUID } from '@/api/definitions';
|
||||
import { SubuserPermission } from '@/state/server/subusers';
|
||||
|
||||
interface User extends Model {
|
||||
uuid: string;
|
||||
username: string;
|
||||
email: string;
|
||||
image: string;
|
||||
twoFactorEnabled: boolean;
|
||||
createdAt: Date;
|
||||
permissions: SubuserPermission[];
|
||||
can (permission: SubuserPermission): boolean;
|
||||
}
|
||||
|
||||
interface SSHKey extends Model {
|
||||
name: string;
|
||||
|
@ -6,3 +18,15 @@ interface SSHKey extends Model {
|
|||
fingerprint: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
interface ActivityLog extends Model<'actor'> {
|
||||
batch: UUID | null;
|
||||
event: string;
|
||||
ip: string;
|
||||
description: string | null;
|
||||
properties: Record<string, string | unknown>;
|
||||
timestamp: Date;
|
||||
relationships: {
|
||||
actor: User | null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { SSHKey } from '@definitions/user/models';
|
||||
import * as Models from '@definitions/user/models';
|
||||
import { FractalResponseData } from '@/api/http';
|
||||
import { transform } from '@definitions/helpers';
|
||||
|
||||
export default class Transformers {
|
||||
static toSSHKey (data: Record<any, any>): SSHKey {
|
||||
static toSSHKey (data: Record<any, any>): Models.SSHKey {
|
||||
return {
|
||||
name: data.name,
|
||||
publicKey: data.public_key,
|
||||
|
@ -9,6 +11,37 @@ export default class Transformers {
|
|||
createdAt: new Date(data.created_at),
|
||||
};
|
||||
}
|
||||
|
||||
static toUser ({ attributes }: FractalResponseData): Models.User {
|
||||
return {
|
||||
uuid: attributes.uuid,
|
||||
username: attributes.username,
|
||||
email: attributes.email,
|
||||
image: attributes.image,
|
||||
twoFactorEnabled: attributes['2fa_enabled'],
|
||||
permissions: attributes.permissions || [],
|
||||
createdAt: new Date(attributes.created_at),
|
||||
can (permission): boolean {
|
||||
return this.permissions.includes(permission);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static toActivityLog ({ attributes }: FractalResponseData): Models.ActivityLog {
|
||||
const { actor } = attributes.relationships || {};
|
||||
|
||||
return {
|
||||
batch: attributes.batch,
|
||||
event: attributes.event,
|
||||
ip: attributes.ip,
|
||||
description: attributes.description,
|
||||
properties: attributes.properties,
|
||||
timestamp: new Date(attributes.timestamp),
|
||||
relationships: {
|
||||
actor: transform(actor as FractalResponseData, this.toUser, null),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaTransformers {
|
||||
|
|
|
@ -68,7 +68,7 @@ export interface FractalResponseData {
|
|||
object: string;
|
||||
attributes: {
|
||||
[k: string]: any;
|
||||
relationships?: Record<string, FractalResponseData | FractalResponseList>;
|
||||
relationships?: Record<string, FractalResponseData | FractalResponseList | null | undefined>;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ Route::prefix('/account')->middleware(AccountActivitySubject::class)->group(func
|
|||
Route::put('/email', [Client\AccountController::class, 'updateEmail'])->name('api:client.account.update-email');
|
||||
Route::put('/password', [Client\AccountController::class, 'updatePassword'])->name('api:client.account.update-password');
|
||||
|
||||
Route::get('/activity', Client\ActivityLogController::class)->name('api:client.account.activity');
|
||||
|
||||
Route::get('/api-keys', [Client\ApiKeyController::class, 'index']);
|
||||
Route::post('/api-keys', [Client\ApiKeyController::class, 'store']);
|
||||
Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']);
|
||||
|
|
Loading…
Reference in a new issue