176 lines
5.4 KiB
PHP
176 lines
5.4 KiB
PHP
<?php
|
|
|
|
namespace Plugin\UserOnlineDevices\Controllers;
|
|
|
|
use App\Http\Controllers\PluginController;
|
|
use App\Models\User;
|
|
use App\Services\DeviceStateService;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\URL;
|
|
|
|
class UserOnlineDevicesController extends PluginController
|
|
{
|
|
public function __construct(
|
|
private readonly DeviceStateService $deviceStateService
|
|
) {
|
|
}
|
|
|
|
public function summary(Request $request)
|
|
{
|
|
if ($error = $this->beforePluginAction()) {
|
|
return $this->fail($error);
|
|
}
|
|
|
|
return $this->success($this->buildPayload($request->user()));
|
|
}
|
|
|
|
public function panelUrl(Request $request)
|
|
{
|
|
if ($error = $this->beforePluginAction()) {
|
|
return $this->fail($error);
|
|
}
|
|
|
|
$user = $request->user();
|
|
$ttlMinutes = max(1, (int) $this->getConfig('signed_url_ttl_minutes', 60));
|
|
$expiresAt = now()->addMinutes($ttlMinutes);
|
|
|
|
return $this->success([
|
|
'url' => URL::temporarySignedRoute(
|
|
'user-online-devices.panel',
|
|
$expiresAt,
|
|
['user' => $user->id]
|
|
),
|
|
'expires_at' => $expiresAt->timestamp,
|
|
]);
|
|
}
|
|
|
|
public function userStatus(): View
|
|
{
|
|
abort_unless($this->isPluginEnabled(), 404);
|
|
|
|
return view('UserOnlineDevices::userstatus');
|
|
}
|
|
|
|
public function panel(Request $request, int $user): View
|
|
{
|
|
abort_unless($this->isPluginEnabled(), 404);
|
|
|
|
$targetUser = User::query()->findOrFail($user);
|
|
$payload = $this->buildPayload($targetUser);
|
|
$ttlMinutes = max(1, (int) $this->getConfig('signed_url_ttl_minutes', 60));
|
|
|
|
return view('UserOnlineDevices::panel', [
|
|
'payload' => $payload,
|
|
'snapshotUrl' => URL::temporarySignedRoute(
|
|
'user-online-devices.snapshot',
|
|
now()->addMinutes($ttlMinutes),
|
|
['user' => $targetUser->id]
|
|
),
|
|
'refreshInterval' => max(5, (int) $this->getConfig('refresh_interval_seconds', 15)),
|
|
]);
|
|
}
|
|
|
|
public function snapshot(Request $request, int $user)
|
|
{
|
|
abort_unless($this->isPluginEnabled(), 404);
|
|
|
|
$targetUser = User::query()->findOrFail($user);
|
|
|
|
return response()->json([
|
|
'data' => $this->buildPayload($targetUser),
|
|
]);
|
|
}
|
|
|
|
private function buildPayload(User $user): array
|
|
{
|
|
$devices = $this->deviceStateService->getUsersDevices([$user->id]);
|
|
$ips = collect($devices[$user->id] ?? [])
|
|
->filter(fn ($ip) => is_string($ip) && $ip !== '')
|
|
->unique()
|
|
->values();
|
|
|
|
return [
|
|
'user' => [
|
|
'id' => $user->id,
|
|
'email' => $user->email,
|
|
'online_count' => $ips->count(),
|
|
'last_online_at' => $user->last_online_at,
|
|
],
|
|
'online_devices' => $this->formatOnlineDevices($ips),
|
|
'active_sessions' => $this->shouldShowActiveSessions()
|
|
? $this->formatActiveSessions($user)
|
|
: [],
|
|
'meta' => [
|
|
'generated_at' => time(),
|
|
'refresh_interval_seconds' => max(5, (int) $this->getConfig('refresh_interval_seconds', 15)),
|
|
'mask_ip' => (bool) $this->getConfig('mask_ip', false),
|
|
'show_active_sessions' => $this->shouldShowActiveSessions(),
|
|
],
|
|
];
|
|
}
|
|
|
|
private function formatOnlineDevices(Collection $ips): array
|
|
{
|
|
return $ips->values()->map(function (string $ip, int $index) {
|
|
return [
|
|
'name' => 'Device ' . ($index + 1),
|
|
'ip' => $this->maskIp($ip),
|
|
'raw_ip' => $ip,
|
|
];
|
|
})->all();
|
|
}
|
|
|
|
private function formatActiveSessions(User $user): array
|
|
{
|
|
return $user->tokens()
|
|
->where(function ($query) {
|
|
$query->whereNull('expires_at')
|
|
->orWhere('expires_at', '>', now());
|
|
})
|
|
->orderByDesc('last_used_at')
|
|
->get(['id', 'name', 'last_used_at', 'created_at', 'expires_at'])
|
|
->map(function ($token) {
|
|
return [
|
|
'id' => $token->id,
|
|
'device' => $token->name ?: 'Unknown Session',
|
|
'last_used_at' => $token->last_used_at?->timestamp,
|
|
'created_at' => $token->created_at?->timestamp,
|
|
'expires_at' => $token->expires_at?->timestamp,
|
|
];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
private function shouldShowActiveSessions(): bool
|
|
{
|
|
return (bool) $this->getConfig('show_active_sessions', true);
|
|
}
|
|
|
|
private function maskIp(string $ip): string
|
|
{
|
|
if (!(bool) $this->getConfig('mask_ip', false)) {
|
|
return $ip;
|
|
}
|
|
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
|
$parts = explode('.', $ip);
|
|
$parts[3] = '*';
|
|
return implode('.', $parts);
|
|
}
|
|
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
$parts = explode(':', $ip);
|
|
$count = count($parts);
|
|
if ($count > 2) {
|
|
$parts[$count - 1] = '****';
|
|
$parts[$count - 2] = '****';
|
|
}
|
|
return implode(':', $parts);
|
|
}
|
|
|
|
return $ip;
|
|
}
|
|
}
|