Compare commits
No commits in common. "57030158741eea7068c15ee6a95dc25825f0a950" and "20ed4860dae7f9046ee166e8cad46493874ce729" have entirely different histories.
5703015874
...
20ed4860da
@ -1,5 +1,4 @@
|
|||||||
DOCKER_NGINX_PORT=8080
|
DOCKER_NGINX_PORT=8080
|
||||||
DOCKER_WEBSOCKET_PORT=8081
|
|
||||||
DOCKER_DB_PORT=3306
|
DOCKER_DB_PORT=3306
|
||||||
MYSQL_ROOT_PASSWORD=root_pass
|
MYSQL_ROOT_PASSWORD=root_pass
|
||||||
DB_DATABASE=captcha
|
DB_DATABASE=captcha
|
||||||
|
@ -72,21 +72,3 @@ VITE_PUSHER_HOST="${PUSHER_HOST}"
|
|||||||
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||||
|
|
||||||
REVERB_APP_ID=
|
|
||||||
REVERB_APP_KEY=
|
|
||||||
REVERB_APP_SECRET=
|
|
||||||
REVERB_HOST="reverb"
|
|
||||||
REVERB_PORT=9000
|
|
||||||
REVERB_SCHEME=http
|
|
||||||
# * or localhost.com or localhost.com, localhost.net
|
|
||||||
REVERB_ALLOWED_ORIGINS="*"
|
|
||||||
|
|
||||||
REVERB_HOST_CLIENT="localhost"
|
|
||||||
REVERB_PORT_CLIENT=8081
|
|
||||||
REVERB_SCHEME_CLIENT=http
|
|
||||||
|
|
||||||
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
|
|
||||||
VITE_REVERB_HOST="${REVERB_HOST}"
|
|
||||||
VITE_REVERB_PORT="${REVERB_PORT}"
|
|
||||||
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Broadcasting;
|
|
||||||
|
|
||||||
use App\Models\CaptchaToken;
|
|
||||||
use App\Models\User;
|
|
||||||
|
|
||||||
final readonly class CreatedCaptchaLog
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Authenticate the user's access to the channel.
|
|
||||||
*/
|
|
||||||
public function join(User $user): bool
|
|
||||||
{
|
|
||||||
return $user->can('viewAny', CaptchaToken::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Dto\Repository\CaptchaLogRepository;
|
|
||||||
|
|
||||||
use App\Enums\CaptchaLogType;
|
|
||||||
|
|
||||||
final class QuantityByDays
|
|
||||||
{
|
|
||||||
private array $days = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $day ("Y-m-d")
|
|
||||||
* @param CaptchaLogType $type
|
|
||||||
* @param int $count
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function add(string $day, CaptchaLogType $type, int $count): void
|
|
||||||
{
|
|
||||||
if (!isset($this->days[$day])) {
|
|
||||||
$this->days[$day] = [];
|
|
||||||
}
|
|
||||||
$this->days[$day][$type->value] = $count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $day ("Y-m-d")
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getDay(string $day): array
|
|
||||||
{
|
|
||||||
return $this->days[$day] ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDays(): array
|
|
||||||
{
|
|
||||||
return $this->days;
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,9 +8,4 @@ enum CaptchaLogType: int
|
|||||||
case Error = 2;
|
case Error = 2;
|
||||||
case Verified = 3;
|
case Verified = 3;
|
||||||
case ReadVerified = 4;
|
case ReadVerified = 4;
|
||||||
|
|
||||||
public function getTitle(): string
|
|
||||||
{
|
|
||||||
return __('captcha_log_type.' . $this->name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Events;
|
|
||||||
|
|
||||||
use App\Helpers\Helpers;
|
|
||||||
use App\Models\CaptchaLog;
|
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
final class CreatedCaptchaLog implements ShouldBroadcast
|
|
||||||
{
|
|
||||||
use SerializesModels, Dispatchable;
|
|
||||||
|
|
||||||
public array $chartCaptchaActivity = [];
|
|
||||||
public array $captchaLog = [];
|
|
||||||
|
|
||||||
public function __construct(CaptchaLog $captchaLog) {
|
|
||||||
$this->chartCaptchaActivity = [
|
|
||||||
'date' => $captchaLog->created_at->format('Y-m-d'),
|
|
||||||
'meta' => $captchaLog->type->getTitle(),
|
|
||||||
'type' => $captchaLog->type->value,
|
|
||||||
];
|
|
||||||
$link = '';
|
|
||||||
$title = '';
|
|
||||||
if ($captchaLog->captcha && $captchaLog->captcha->captchaToken) {
|
|
||||||
$link = route('captcha-tokens.edit', ['captcha_token' => $captchaLog->captcha->captcha_token_id], false);
|
|
||||||
$title = $captchaLog->captcha->captchaToken->title;
|
|
||||||
}
|
|
||||||
$this->captchaLog = [
|
|
||||||
'created_at' => $captchaLog->created_at->format("d.m.Y H:i:s"),
|
|
||||||
'link' => $link,
|
|
||||||
'title' => $title,
|
|
||||||
'type' => $captchaLog->type->getTitle(),
|
|
||||||
'ip' => $captchaLog->ip,
|
|
||||||
'user_agent' => $captchaLog->user_agent,
|
|
||||||
'referer' => $captchaLog->referer,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function broadcastOn(): Channel
|
|
||||||
{
|
|
||||||
return new PrivateChannel('chart-captcha-activity');
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,42 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Private;
|
namespace App\Http\Controllers\Private;
|
||||||
|
|
||||||
use App\Http\Resources\Private\Dashboard\ChartCaptchaActivity;
|
|
||||||
use App\Services\Private\DashboardService;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
final class DashboardController extends Controller
|
final class DashboardController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function index(): View
|
||||||
private readonly DashboardService $dashboardService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public function index(Request $request): View
|
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
return view('private/dashboard/index');
|
||||||
$limit = 30;
|
|
||||||
$with = ['captcha', 'captcha.captchaToken'];
|
|
||||||
$result = $this->dashboardService->captchaLog($user, $limit, $with);
|
|
||||||
if ($result->isError()) {
|
|
||||||
$this->errors($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('private/dashboard/index', $result->getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function chartCaptchaActivity(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$user = $request->user();
|
|
||||||
$from = Carbon::now()->subDays(7);
|
|
||||||
$to = Carbon::now();
|
|
||||||
$result = $this->dashboardService->chartCaptchaActivity($user, $from, $to);
|
|
||||||
if (!$result->isSuccess()) {
|
|
||||||
return response()->json($result->getData())->setStatusCode($result->getCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(new ChartCaptchaActivity($result));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
final class UsersController extends Controller
|
class UsersController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly UserService $userService
|
private readonly UserService $userService
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Private;
|
|
||||||
|
|
||||||
use App\Http\Resources\Private\Websockets\Setting;
|
|
||||||
use App\Services\Private\WebsocketService;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
|
|
||||||
final class WebsocketsController extends Controller
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly WebsocketService $websocketService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public function settings(): JsonResponse
|
|
||||||
{
|
|
||||||
$result = $this->websocketService->settings();
|
|
||||||
if (!$result->isSuccess()) {
|
|
||||||
return response()->json($result->getData())->setStatusCode($result->getCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(new Setting($result));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Http\Resources\Private\Dashboard;
|
|
||||||
|
|
||||||
use App\Enums\CaptchaLogType;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
|
||||||
|
|
||||||
final class ChartCaptchaActivity extends JsonResource
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \App\ServiceResults\Private\Dashboard\ChartCaptchaActivity
|
|
||||||
*/
|
|
||||||
public $resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform the resource into an array.
|
|
||||||
*
|
|
||||||
* @param Request $request
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function toArray(Request $request): array
|
|
||||||
{
|
|
||||||
$period = $this->resource->getFrom()->diff($this->resource->getTo())->stepBy('day');
|
|
||||||
$days = [];
|
|
||||||
$values = [];
|
|
||||||
$types = [];
|
|
||||||
foreach (CaptchaLogType::cases() as $type) {
|
|
||||||
$values[$type->value] = [];
|
|
||||||
$types[$type->value] = [
|
|
||||||
'meta' => $type->getTitle(),
|
|
||||||
'value' => 0,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
foreach ($period as $item) {
|
|
||||||
$day = $item->format('Y-m-d');
|
|
||||||
$days[] = $day;
|
|
||||||
$quantity = $this->resource->getQuantityByDays()->getDay($day);
|
|
||||||
foreach (CaptchaLogType::cases() as $type) {
|
|
||||||
$values[$type->value][] = [
|
|
||||||
'meta' => $type->getTitle(),
|
|
||||||
'value' => $quantity[$type->value] ?? 0,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'days' => $days,
|
|
||||||
'values' => $values,
|
|
||||||
'types' => $types,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Http\Resources\Private\Websockets;
|
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
|
||||||
|
|
||||||
final class Setting extends JsonResource
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \App\ServiceResults\Private\Websocket\Setting
|
|
||||||
*/
|
|
||||||
public $resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform the resource into an array.
|
|
||||||
*
|
|
||||||
* @param Request $request
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function toArray(Request $request): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'key' => $this->resource->getKey(),
|
|
||||||
'wsHost' => $this->resource->getWsHost(),
|
|
||||||
'wsPort' => $this->resource->getWsPort(),
|
|
||||||
'wssPort' => $this->resource->getWssPort(),
|
|
||||||
'forceTLS' => $this->resource->isForceTLS(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
final class Captcha extends Model
|
final class Captcha extends Model
|
||||||
@ -35,9 +34,4 @@ public function captchaLogs(): HasMany
|
|||||||
{
|
{
|
||||||
return $this->hasMany(CaptchaLog::class);
|
return $this->hasMany(CaptchaLog::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function captchaToken(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(CaptchaToken::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
|
|
||||||
final class CaptchaLog extends Model
|
final class CaptchaLog extends Model
|
||||||
{
|
{
|
||||||
@ -45,9 +44,4 @@ public function scopeLatest(Builder $query): Builder
|
|||||||
{
|
{
|
||||||
return $query->orderBy('created_at', 'desc');
|
return $query->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function captcha(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Captcha::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,30 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Repositories;
|
namespace App\Repositories;
|
||||||
|
|
||||||
use App\Dto\Repository\CaptchaLogRepository\QuantityByDays;
|
|
||||||
use App\Enums\CaptchaLogType;
|
use App\Enums\CaptchaLogType;
|
||||||
use App\Models\CaptchaLog;
|
use App\Models\CaptchaLog;
|
||||||
use App\Services\Search\CreateSearchInstanceCommand;
|
|
||||||
use App\Services\Search\Search;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
|
|
||||||
final class CaptchaLogRepository
|
final class CaptchaLogRepository
|
||||||
{
|
{
|
||||||
public function __construct(
|
|
||||||
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public function getCaptchaLogs(array $with = []): Search
|
|
||||||
{
|
|
||||||
$query = CaptchaLog::query()
|
|
||||||
->with($with)
|
|
||||||
->latest();
|
|
||||||
|
|
||||||
return $this->createSearchInstanceCommand->execute($query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function countByType(CaptchaLogType $type, ?int $captchaId = null): int
|
public function countByType(CaptchaLogType $type, ?int $captchaId = null): int
|
||||||
{
|
{
|
||||||
return CaptchaLog::query()
|
return CaptchaLog::query()
|
||||||
@ -49,22 +32,4 @@ public function getCaptchaLogsByTypes(array $types, ?int $captchaId = null, ?int
|
|||||||
->latest()
|
->latest()
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countByDays(Carbon $from, Carbon $to): QuantityByDays
|
|
||||||
{
|
|
||||||
$count = CaptchaLog::query()
|
|
||||||
->selectRaw('DATE_FORMAT(created_at, \'%Y-%m-%d\') AS date, type, COUNT(id) AS count')
|
|
||||||
->where('created_at', '>=', $from)
|
|
||||||
->where('created_at', '<=', $to)
|
|
||||||
->groupBy('date', 'type')
|
|
||||||
->orderBy('date', 'asc')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
$quantity = new QuantityByDays();
|
|
||||||
foreach ($count as $item) {
|
|
||||||
$quantity->add($item->date, $item->type, $item->count);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $quantity;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\ServiceResults\Private\Dashboard;
|
|
||||||
|
|
||||||
use App\Dto\Repository\CaptchaLogRepository\QuantityByDays;
|
|
||||||
use App\ServiceResults\ServiceResult;
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
|
|
||||||
final class ChartCaptchaActivity extends ServiceResult
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly Carbon $from,
|
|
||||||
private readonly Carbon $to,
|
|
||||||
private readonly QuantityByDays $quantityByDays,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public function getFrom(): Carbon
|
|
||||||
{
|
|
||||||
return $this->from;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTo(): Carbon
|
|
||||||
{
|
|
||||||
return $this->to;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQuantityByDays(): QuantityByDays
|
|
||||||
{
|
|
||||||
return $this->quantityByDays;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\ServiceResults\Private\Websocket;
|
|
||||||
|
|
||||||
use App\ServiceResults\ServiceResult;
|
|
||||||
|
|
||||||
final class Setting extends ServiceResult
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly string $key,
|
|
||||||
private readonly string $wsHost,
|
|
||||||
private readonly int $wsPort,
|
|
||||||
private readonly int $wssPort,
|
|
||||||
private readonly bool $forceTLS,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public function getKey(): string
|
|
||||||
{
|
|
||||||
return $this->key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getWsHost(): string
|
|
||||||
{
|
|
||||||
return $this->wsHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getWsPort(): int
|
|
||||||
{
|
|
||||||
return $this->wsPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getWssPort(): int
|
|
||||||
{
|
|
||||||
return $this->wssPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isForceTLS(): bool
|
|
||||||
{
|
|
||||||
return $this->forceTLS;
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use App\Dto\HttpUserData;
|
use App\Dto\HttpUserData;
|
||||||
use App\Enums\CaptchaLogType;
|
use App\Enums\CaptchaLogType;
|
||||||
use App\Events\CreatedCaptchaLog;
|
|
||||||
use App\Models\Captcha;
|
use App\Models\Captcha;
|
||||||
use App\Models\CaptchaLog;
|
use App\Models\CaptchaLog;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@ -23,16 +22,12 @@ public function handleStore(int $captchaId, CaptchaLogType $captchaLogType, Http
|
|||||||
$referer = Str::limit($referer, 10000, '');
|
$referer = Str::limit($referer, 10000, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
$captchaLog = CaptchaLog::create([
|
return CaptchaLog::create([
|
||||||
'captcha_id' => $captchaId,
|
'captcha_id' => $captchaId,
|
||||||
'type' => $captchaLogType,
|
'type' => $captchaLogType,
|
||||||
'ip' => $httpUserData->getClientIp(),
|
'ip' => $httpUserData->getClientIp(),
|
||||||
'user_agent' => $userAgent,
|
'user_agent' => $userAgent,
|
||||||
'referer' => $referer,
|
'referer' => $referer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
CreatedCaptchaLog::dispatch($captchaLog);
|
|
||||||
|
|
||||||
return $captchaLog;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services\Private;
|
|
||||||
|
|
||||||
use App\Enums\CaptchaLogType;
|
|
||||||
use App\Models\CaptchaToken;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Repositories\CaptchaLogRepository;
|
|
||||||
use App\ServiceResults\Private\Dashboard\ChartCaptchaActivity;
|
|
||||||
use App\ServiceResults\ServiceResultArray;
|
|
||||||
use App\ServiceResults\ServiceResultError;
|
|
||||||
use App\Services\Service;
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
|
|
||||||
final class DashboardService extends Service
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly CaptchaLogRepository $captchaLogRepository,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public function captchaLog(User $user, int $limit = 10, array $with = []): ServiceResultError | ServiceResultArray
|
|
||||||
{
|
|
||||||
if ($user->cannot('viewAny', CaptchaToken::class)) {
|
|
||||||
return $this->result([
|
|
||||||
'items' => collect([]),
|
|
||||||
'user' => $user,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->result([
|
|
||||||
'items' => $this->captchaLogRepository->getCaptchaLogs($with)->get($limit),
|
|
||||||
'user' => $user,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function chartCaptchaActivity(User $user, Carbon $from, Carbon $to): ServiceResultError | ChartCaptchaActivity
|
|
||||||
{
|
|
||||||
if ($user->cannot('viewAny', CaptchaToken::class)) {
|
|
||||||
return $this->errFobidden(__('Access is denied'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($to < $from) {
|
|
||||||
return $this->errValidate(__('From cannot be greater than To'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$quantity = $this->captchaLogRepository->countByDays($from, $to);
|
|
||||||
|
|
||||||
return new ChartCaptchaActivity(
|
|
||||||
from: $from,
|
|
||||||
to: $to,
|
|
||||||
quantityByDays: $quantity,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services\Private;
|
|
||||||
|
|
||||||
use App\ServiceResults\Private\Websocket\Setting;
|
|
||||||
use App\ServiceResults\ServiceResultError;
|
|
||||||
use App\Services\Service;
|
|
||||||
|
|
||||||
final class WebsocketService extends Service
|
|
||||||
{
|
|
||||||
public function settings(): ServiceResultError | Setting
|
|
||||||
{
|
|
||||||
$config = config('reverb.apps');
|
|
||||||
$config = $config['apps'][0] ?? [];
|
|
||||||
|
|
||||||
return new Setting(
|
|
||||||
key: $config['key'] ?? '',
|
|
||||||
wsHost: $config['options_for_client']['host'] ?? '',
|
|
||||||
wsPort: (int) $config['options_for_client']['port'] ?? 80,
|
|
||||||
wssPort: 443,
|
|
||||||
forceTLS: $config['options_for_client']['useTLS'] ?? false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,7 +9,6 @@
|
|||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^7.2",
|
||||||
"kor-elf/captcha-rule-for-laravel": "^1.0",
|
"kor-elf/captcha-rule-for-laravel": "^1.0",
|
||||||
"laravel/framework": "^11.0",
|
"laravel/framework": "^11.0",
|
||||||
"laravel/reverb": "@beta",
|
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/tinker": "^2.8"
|
"laravel/tinker": "^2.8"
|
||||||
},
|
},
|
||||||
|
1035
app/application/composer.lock
generated
1035
app/application/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -215,7 +215,7 @@
|
|||||||
*/
|
*/
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
App\Providers\AuthServiceProvider::class,
|
App\Providers\AuthServiceProvider::class,
|
||||||
App\Providers\BroadcastServiceProvider::class,
|
// App\Providers\BroadcastServiceProvider::class,
|
||||||
App\Providers\EventServiceProvider::class,
|
App\Providers\EventServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default Reverb Server
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This option controls the default server used by Reverb to handle
|
|
||||||
| incoming messages as well as broadcasting message to all your
|
|
||||||
| connected clients. At this time only "reverb" is supported.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'default' => env('REVERB_SERVER', 'reverb'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Reverb Servers
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here you may define details for each of the supported Reverb servers.
|
|
||||||
| Each server has its own configuration options that are defined in
|
|
||||||
| the array below. You should ensure all the options are present.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'servers' => [
|
|
||||||
|
|
||||||
'reverb' => [
|
|
||||||
'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
|
|
||||||
'port' => env('REVERB_SERVER_PORT', 8080),
|
|
||||||
'hostname' => env('REVERB_HOST'),
|
|
||||||
'options' => [
|
|
||||||
'tls' => [],
|
|
||||||
],
|
|
||||||
'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000),
|
|
||||||
'scaling' => [
|
|
||||||
'enabled' => env('REVERB_SCALING_ENABLED', false),
|
|
||||||
'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
|
|
||||||
'server' => [
|
|
||||||
'url' => env('REDIS_URL'),
|
|
||||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
|
||||||
'port' => env('REDIS_PORT', '6379'),
|
|
||||||
'username' => env('REDIS_USERNAME'),
|
|
||||||
'password' => env('REDIS_PASSWORD'),
|
|
||||||
'database' => env('REDIS_DB', '0'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
|
|
||||||
'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
|
|
||||||
],
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Reverb Applications
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here you may define how Reverb applications are managed. If you choose
|
|
||||||
| to use the "config" provider, you may define an array of apps which
|
|
||||||
| your server will support, including their connection credentials.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'apps' => [
|
|
||||||
|
|
||||||
'provider' => 'config',
|
|
||||||
|
|
||||||
'apps' => [
|
|
||||||
[
|
|
||||||
'key' => env('REVERB_APP_KEY'),
|
|
||||||
'secret' => env('REVERB_APP_SECRET'),
|
|
||||||
'app_id' => env('REVERB_APP_ID'),
|
|
||||||
'options' => [
|
|
||||||
'host' => env('REVERB_HOST'),
|
|
||||||
'port' => env('REVERB_PORT', 443),
|
|
||||||
'scheme' => env('REVERB_SCHEME', 'https'),
|
|
||||||
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
|
|
||||||
],
|
|
||||||
'allowed_origins' => explode(',', env('REVERB_ALLOWED_ORIGINS', '*')),
|
|
||||||
'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
|
|
||||||
'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
|
|
||||||
'options_for_client' => [
|
|
||||||
'host' => env('REVERB_HOST_CLIENT'),
|
|
||||||
'port' => env('REVERB_PORT_CLIENT', 443),
|
|
||||||
'scheme' => env('REVERB_SCHEME_CLIENT', 'https'),
|
|
||||||
'useTLS' => env('REVERB_SCHEME_CLIENT', 'https') === 'https',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
@ -73,6 +73,5 @@
|
|||||||
"The time for captcha verification has passed": "The time for captcha verification has passed.",
|
"The time for captcha verification has passed": "The time for captcha verification has passed.",
|
||||||
"Captcha does not pass verification": "Captcha does not pass verification.",
|
"Captcha does not pass verification": "Captcha does not pass verification.",
|
||||||
"Add code to the site": "Add code to the site",
|
"Add code to the site": "Add code to the site",
|
||||||
"Demo Mode": "!!! Demo Mode !!!",
|
"Demo Mode": "!!! Demo Mode !!!"
|
||||||
"From cannot be greater than To": "From cannot be greater than To."
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
return [
|
|
||||||
'Created' => 'Creating a captcha',
|
|
||||||
'Error' => 'Error in captcha validation',
|
|
||||||
'Verified' => 'Successfully verified',
|
|
||||||
'ReadVerified' => 'Completed captcha verification',
|
|
||||||
];
|
|
@ -73,6 +73,5 @@
|
|||||||
"The time for captcha verification has passed": "Время верификации капчи прошло.",
|
"The time for captcha verification has passed": "Время верификации капчи прошло.",
|
||||||
"Captcha does not pass verification": "Капча не проходит проверку.",
|
"Captcha does not pass verification": "Капча не проходит проверку.",
|
||||||
"Add code to the site": "Добавьте код на сайт",
|
"Add code to the site": "Добавьте код на сайт",
|
||||||
"Demo Mode": "!!! Включён демо режим !!!",
|
"Demo Mode": "!!! Включён демо режим !!!"
|
||||||
"From cannot be greater than To": "From не может быть больше To."
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
return [
|
|
||||||
'Created' => 'Создание капчи',
|
|
||||||
'Error' => 'Ошибка в валидации капчи',
|
|
||||||
'Verified' => 'Успешно проверено',
|
|
||||||
'ReadVerified' => 'Завершена проверка капчи',
|
|
||||||
];
|
|
47
app/application/package-lock.json
generated
47
app/application/package-lock.json
generated
@ -21,9 +21,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios": "^1.1.2",
|
"axios": "^1.1.2",
|
||||||
"laravel-echo": "^1.16.1",
|
|
||||||
"laravel-vite-plugin": "^0.7.2",
|
"laravel-vite-plugin": "^0.7.2",
|
||||||
"pusher-js": "^8.4.0-rc2",
|
|
||||||
"sass-loader": "^13.3.2",
|
"sass-loader": "^13.3.2",
|
||||||
"vite": "^4.0.0"
|
"vite": "^4.0.0"
|
||||||
}
|
}
|
||||||
@ -1253,15 +1251,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/laravel-echo": {
|
|
||||||
"version": "1.16.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.16.1.tgz",
|
|
||||||
"integrity": "sha512-++Ylb6M3ariC9Rk5WE5gZjj6wcEV5kvLF8b+geJ5/rRIfdoOA+eG6b9qJPrarMD9rY28Apx+l3eelIrCc2skVg==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/laravel-vite-plugin": {
|
"node_modules/laravel-vite-plugin": {
|
||||||
"version": "0.7.8",
|
"version": "0.7.8",
|
||||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
|
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
|
||||||
@ -1447,15 +1436,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pusher-js": {
|
|
||||||
"version": "8.4.0-rc2",
|
|
||||||
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0-rc2.tgz",
|
|
||||||
"integrity": "sha512-d87GjOEEl9QgO5BWmViSqW0LOzPvybvX6WA9zLUstNdB57jVJuR27zHkRnrav2a3+zAMlHbP2Og8wug+rG8T+g==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tweetnacl": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/randombytes": {
|
"node_modules/randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
@ -1743,12 +1723,6 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tweetnacl": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
||||||
@ -2781,12 +2755,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"laravel-echo": {
|
|
||||||
"version": "1.16.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.16.1.tgz",
|
|
||||||
"integrity": "sha512-++Ylb6M3ariC9Rk5WE5gZjj6wcEV5kvLF8b+geJ5/rRIfdoOA+eG6b9qJPrarMD9rY28Apx+l3eelIrCc2skVg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"laravel-vite-plugin": {
|
"laravel-vite-plugin": {
|
||||||
"version": "0.7.8",
|
"version": "0.7.8",
|
||||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
|
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
|
||||||
@ -2915,15 +2883,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"pusher-js": {
|
|
||||||
"version": "8.4.0-rc2",
|
|
||||||
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0-rc2.tgz",
|
|
||||||
"integrity": "sha512-d87GjOEEl9QgO5BWmViSqW0LOzPvybvX6WA9zLUstNdB57jVJuR27zHkRnrav2a3+zAMlHbP2Og8wug+rG8T+g==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"tweetnacl": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"randombytes": {
|
"randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
@ -3097,12 +3056,6 @@
|
|||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tweetnacl": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"update-browserslist-db": {
|
"update-browserslist-db": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
||||||
|
@ -21,9 +21,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios": "^1.1.2",
|
"axios": "^1.1.2",
|
||||||
"laravel-echo": "^1.16.1",
|
|
||||||
"laravel-vite-plugin": "^0.7.2",
|
"laravel-vite-plugin": "^0.7.2",
|
||||||
"pusher-js": "^8.4.0-rc2",
|
|
||||||
"sass-loader": "^13.3.2",
|
"sass-loader": "^13.3.2",
|
||||||
"vite": "^4.0.0"
|
"vite": "^4.0.0"
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,5 @@
|
|||||||
@section('meta_title', __('sections.Dashboard'))
|
@section('meta_title', __('sections.Dashboard'))
|
||||||
@section('h1', __('sections.Dashboard'))
|
@section('h1', __('sections.Dashboard'))
|
||||||
<x-private.layout>
|
<x-private.layout>
|
||||||
<div class="card border-0 shadow mb-4">
|
|
||||||
<div class="card-body">
|
|
||||||
@can('viewAny', \App\Models\CaptchaToken::class)
|
|
||||||
<div id="chart-сaptcha-activity"></div>
|
|
||||||
<div class="mt-4 table-responsive">
|
|
||||||
<table class="table table-centered table-nowrap mb-0 rounded">
|
|
||||||
<thead class="thead-light">
|
|
||||||
<tr>
|
|
||||||
<th class="border-0">{{ __('validation.attributes.date') }}</th>
|
|
||||||
<th class="border-0">Capctha</th>
|
|
||||||
<th class="border-0"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="captcha-log">
|
|
||||||
@foreach($items as $item)
|
|
||||||
<tr>
|
|
||||||
<td>{{ $item->created_at->timezone(\App\Helpers\Helpers::getUserTimeZone())->format("d.m.Y H:i:s") }}</td>
|
|
||||||
<td>
|
|
||||||
@if($item->captcha && $item->captcha->captchaToken)
|
|
||||||
<a href="{{ route('captcha-tokens.edit', ['captcha_token' => $item->captcha->captcha_token_id]) }}">{{ $item->captcha->captchaToken->title }}</a>
|
|
||||||
@endif
|
|
||||||
<p><strong>{{ $item->type->getTitle() }}</strong></p>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<p><strong>IP:</strong> {{ $item->ip }}</p>
|
|
||||||
<p><strong>User Agent:</strong> {{ $item->user_agent }}</p>
|
|
||||||
<p><strong>referer:</strong> {{ $item->referer }}</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
@endcan
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-private.layout>
|
</x-private.layout>
|
||||||
|
@ -2,6 +2,4 @@ import.meta.glob([
|
|||||||
'../images/**',
|
'../images/**',
|
||||||
]);
|
]);
|
||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
import './echo';
|
import './volt.js';
|
||||||
import './volt';
|
|
||||||
import './dashboard'
|
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import Chartist from "chartist";
|
|
||||||
import 'chartist-plugin-tooltips';
|
|
||||||
|
|
||||||
if(document.querySelector('#chart-сaptcha-activity')) {
|
|
||||||
let chartCaptchaActivity = null;
|
|
||||||
axios.get('/dashboard/chart-captcha-activity')
|
|
||||||
.then(response => {
|
|
||||||
chartCaptchaActivity = new Chartist.Line('#chart-сaptcha-activity', {
|
|
||||||
labels: response.data.days,
|
|
||||||
series: response.data.values,
|
|
||||||
}, {
|
|
||||||
low: 0,
|
|
||||||
showArea: true,
|
|
||||||
fullWidth: true,
|
|
||||||
plugins: [
|
|
||||||
Chartist.plugins.tooltip()
|
|
||||||
],
|
|
||||||
axisX: {
|
|
||||||
// On the x-axis start means top and end means bottom
|
|
||||||
position: 'end',
|
|
||||||
showGrid: true,
|
|
||||||
},
|
|
||||||
axisY: {
|
|
||||||
// On the y-axis start means left and end means right
|
|
||||||
showGrid: true,
|
|
||||||
showLabel: true,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let currentDate = response.data.days[response.data.days.length - 1];
|
|
||||||
|
|
||||||
window.websocket.add(function (echo) {
|
|
||||||
echo.private('chart-captcha-activity')
|
|
||||||
.listen('CreatedCaptchaLog', (e) => {
|
|
||||||
let options = chartCaptchaActivity.data;
|
|
||||||
|
|
||||||
if (currentDate !== e.chartCaptchaActivity.date) {
|
|
||||||
currentDate = e.chartCaptchaActivity.date;
|
|
||||||
|
|
||||||
options['labels'].push(currentDate);
|
|
||||||
options['series'].forEach(function(series, i) {
|
|
||||||
series.push(response.data.types[i]);
|
|
||||||
options['series'].push(series);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let values = options['series'][e.chartCaptchaActivity['type'] - 1];
|
|
||||||
options['series'][e.chartCaptchaActivity['type'] - 1][values.length - 1]['value']++;
|
|
||||||
chartCaptchaActivity.update(options);
|
|
||||||
|
|
||||||
let captchaLog = document.querySelector('#captcha-log');
|
|
||||||
if (captchaLog) {
|
|
||||||
let tr = document.createElement("tr");
|
|
||||||
let link = '';
|
|
||||||
if (e.captchaLog.link !== '' && e.captchaLog.title !== '') {
|
|
||||||
link = `<a href="${e.captchaLog.link}">${e.captchaLog.title}</a>`;
|
|
||||||
}
|
|
||||||
tr.innerHTML = `
|
|
||||||
<td>${e.captchaLog.created_at}</td>
|
|
||||||
<td>
|
|
||||||
${link}
|
|
||||||
<p><strong>${e.captchaLog.type}</strong></p>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<p><strong>IP:</strong> ${e.captchaLog.ip}</p>
|
|
||||||
<p><strong>User Agent:</strong> ${e.captchaLog.user_agent}</p>
|
|
||||||
<p><strong>referer:</strong> ${e.captchaLog.referer}</p>
|
|
||||||
</td>
|
|
||||||
`;
|
|
||||||
|
|
||||||
captchaLog.prepend(tr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import Echo from 'laravel-echo';
|
|
||||||
import Pusher from 'pusher-js';
|
|
||||||
|
|
||||||
class websocket {
|
|
||||||
_echo = null;
|
|
||||||
pusher = Pusher;
|
|
||||||
|
|
||||||
callbacks = []
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
let _this = this;
|
|
||||||
axios.get('/websockets/settings')
|
|
||||||
.then(response => {
|
|
||||||
_this._echo = new Echo({
|
|
||||||
broadcaster: 'reverb',
|
|
||||||
key: response.data.key,
|
|
||||||
wsHost: response.data.wsHost,
|
|
||||||
wsPort: response.data.wsPort,
|
|
||||||
wssPort: response.data.wssPort,
|
|
||||||
forceTLS: response.data.forceTLS,
|
|
||||||
enabledTransports: ['ws', 'wss'],
|
|
||||||
});
|
|
||||||
_this.startCallbacks();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
add(callback) {
|
|
||||||
if (this._echo !== null) {
|
|
||||||
callback(this._echo)
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.callbacks.push(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
startCallbacks() {
|
|
||||||
let _this = this;
|
|
||||||
this.callbacks.forEach(function(callback) {
|
|
||||||
callback(_this._echo)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.websocket = new websocket();
|
|
@ -21,6 +21,8 @@ const d = document;
|
|||||||
import * as bootstrap from 'bootstrap';
|
import * as bootstrap from 'bootstrap';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
import SmoothScroll from 'smooth-scroll';
|
import SmoothScroll from 'smooth-scroll';
|
||||||
|
import Chartist from 'chartist';
|
||||||
|
import 'chartist-plugin-tooltips';
|
||||||
|
|
||||||
d.addEventListener("DOMContentLoaded", function(event) {
|
d.addEventListener("DOMContentLoaded", function(event) {
|
||||||
|
|
||||||
@ -174,6 +176,102 @@ d.addEventListener("DOMContentLoaded", function(event) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Chartist
|
||||||
|
|
||||||
|
if(d.querySelector('.ct-chart-sales-value')) {
|
||||||
|
//Chart 5
|
||||||
|
new Chartist.Line('.ct-chart-sales-value', {
|
||||||
|
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||||
|
series: [
|
||||||
|
[0, 10, 30, 40, 80, 60, 100]
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
low: 0,
|
||||||
|
showArea: true,
|
||||||
|
fullWidth: true,
|
||||||
|
plugins: [
|
||||||
|
Chartist.plugins.tooltip()
|
||||||
|
],
|
||||||
|
axisX: {
|
||||||
|
// On the x-axis start means top and end means bottom
|
||||||
|
position: 'end',
|
||||||
|
showGrid: true
|
||||||
|
},
|
||||||
|
axisY: {
|
||||||
|
// On the y-axis start means left and end means right
|
||||||
|
showGrid: false,
|
||||||
|
showLabel: false,
|
||||||
|
labelInterpolationFnc: function(value) {
|
||||||
|
return '$' + (value / 1) + 'k';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(d.querySelector('.ct-chart-ranking')) {
|
||||||
|
var chart = new Chartist.Bar('.ct-chart-ranking', {
|
||||||
|
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||||
|
series: [
|
||||||
|
[1, 5, 2, 5, 4, 3],
|
||||||
|
[2, 3, 4, 8, 1, 2],
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
low: 0,
|
||||||
|
showArea: true,
|
||||||
|
plugins: [
|
||||||
|
Chartist.plugins.tooltip()
|
||||||
|
],
|
||||||
|
axisX: {
|
||||||
|
// On the x-axis start means top and end means bottom
|
||||||
|
position: 'end'
|
||||||
|
},
|
||||||
|
axisY: {
|
||||||
|
// On the y-axis start means left and end means right
|
||||||
|
showGrid: false,
|
||||||
|
showLabel: false,
|
||||||
|
offset: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chart.on('draw', function(data) {
|
||||||
|
if(data.type === 'line' || data.type === 'area') {
|
||||||
|
data.element.animate({
|
||||||
|
d: {
|
||||||
|
begin: 2000 * data.index,
|
||||||
|
dur: 2000,
|
||||||
|
from: data.path.clone().scale(1, 0).translate(0, data.chartRect.height()).stringify(),
|
||||||
|
to: data.path.clone().stringify(),
|
||||||
|
easing: Chartist.Svg.Easing.easeOutQuint
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(d.querySelector('.ct-chart-traffic-share')) {
|
||||||
|
var data = {
|
||||||
|
series: [70, 20, 10]
|
||||||
|
};
|
||||||
|
|
||||||
|
var sum = function(a, b) { return a + b };
|
||||||
|
|
||||||
|
new Chartist.Pie('.ct-chart-traffic-share', data, {
|
||||||
|
labelInterpolationFnc: function(value) {
|
||||||
|
return Math.round(value / data.series.reduce(sum) * 100) + '%';
|
||||||
|
},
|
||||||
|
low: 0,
|
||||||
|
high: 8,
|
||||||
|
donut: true,
|
||||||
|
donutWidth: 20,
|
||||||
|
donutSolid: true,
|
||||||
|
fullWidth: false,
|
||||||
|
showLabel: false,
|
||||||
|
plugins: [
|
||||||
|
Chartist.plugins.tooltip()
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (d.getElementById('loadOnClick')) {
|
if (d.getElementById('loadOnClick')) {
|
||||||
d.getElementById('loadOnClick').addEventListener('click', function () {
|
d.getElementById('loadOnClick').addEventListener('click', function () {
|
||||||
var button = this;
|
var button = this;
|
||||||
|
@ -12,5 +12,3 @@
|
|||||||
| used to check if an authenticated user can listen to the channel.
|
| used to check if an authenticated user can listen to the channel.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Broadcast::channel('chart-captcha-activity', \App\Broadcasting\CreatedCaptchaLog::class);
|
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
Route::middleware(['auth', 'verified', 'user.locale'])->group(function () {
|
Route::middleware(['auth', 'verified', 'user.locale'])->group(function () {
|
||||||
Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout');
|
Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout');
|
||||||
Route::get('/', [\App\Http\Controllers\Private\DashboardController::class, 'index'])->name('home');
|
Route::get('/', [\App\Http\Controllers\Private\DashboardController::class, 'index'])->name('home');
|
||||||
Route::get('/dashboard/chart-captcha-activity', [\App\Http\Controllers\Private\DashboardController::class, 'chartCaptchaActivity']);
|
|
||||||
Route::prefix('profile')->as('profile.')
|
Route::prefix('profile')->as('profile.')
|
||||||
->group(function () {
|
->group(function () {
|
||||||
Route::get('/', [\App\Http\Controllers\Private\ProfileController::class, 'profile'])->name('edit');
|
Route::get('/', [\App\Http\Controllers\Private\ProfileController::class, 'profile'])->name('edit');
|
||||||
@ -35,6 +34,4 @@
|
|||||||
|
|
||||||
Route::resource('roles', \App\Http\Controllers\Private\RolesController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['role' => '[0-9]+']);
|
Route::resource('roles', \App\Http\Controllers\Private\RolesController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['role' => '[0-9]+']);
|
||||||
Route::resource('captcha-tokens', \App\Http\Controllers\Private\CaptchaTokensController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['captcha_token' => '[0-9]+']);
|
Route::resource('captcha-tokens', \App\Http\Controllers\Private\CaptchaTokensController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['captcha_token' => '[0-9]+']);
|
||||||
|
|
||||||
Route::get('websockets/settings', [\App\Http\Controllers\Private\WebsocketsController::class, 'settings']);
|
|
||||||
});
|
});
|
||||||
|
@ -73,8 +73,7 @@ STOPSIGNAL SIGTERM
|
|||||||
|
|
||||||
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
|
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
COPY docker/start_prod.sh /usr/local/bin/start
|
CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock", "--user", "unit", "--group", "unit"]
|
||||||
CMD ["/usr/local/bin/start"]
|
|
||||||
|
|
||||||
|
|
||||||
FROM BUILD AS DEVELOP
|
FROM BUILD AS DEVELOP
|
||||||
@ -92,9 +91,9 @@ RUN chmod 755 /home/unit/docker-entrypoint.sh \
|
|||||||
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
|
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock", "--user", "unit", "--group", "unit"]
|
||||||
|
|
||||||
|
|
||||||
COPY docker/start_dev.sh /usr/local/bin/start
|
|
||||||
CMD ["/usr/local/bin/start"]
|
|
||||||
|
|
||||||
FROM BUILD AS ARTISAN
|
FROM BUILD AS ARTISAN
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
set -e
|
|
||||||
role=${CONTAINER_ROLE:-app}
|
|
||||||
if [ "$role" = "app" ]; then
|
|
||||||
exec unitd --no-daemon --control unix:/var/run/control.unit.sock --user unit --group unit
|
|
||||||
elif [ "$role" = "queue" ]; then
|
|
||||||
echo "Running the queue..."
|
|
||||||
php /var/www/html/artisan queue:work --verbose --sleep=5 --tries=10
|
|
||||||
elif [ "$role" = "websockets" ]; then
|
|
||||||
echo "Running the websockets..."
|
|
||||||
php /var/www/html/artisan reverb:start --port=9000
|
|
||||||
elif [ "$role" = "scheduler" ]; then
|
|
||||||
while [ true ]
|
|
||||||
do
|
|
||||||
php /var/www/html/artisan schedule:run --verbose --no-interaction &
|
|
||||||
sleep 60
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "Could not match the container role \"$role\""
|
|
||||||
exit 1
|
|
||||||
fi
|
|
@ -1,21 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
set -e
|
|
||||||
role=${CONTAINER_ROLE:-app}
|
|
||||||
if [ "$role" = "app" ]; then
|
|
||||||
exec unitd --no-daemon --control unix:/var/run/control.unit.sock --user unit --group unit
|
|
||||||
elif [ "$role" = "queue" ]; then
|
|
||||||
echo "Running the queue..."
|
|
||||||
php /var/www/html/artisan queue:work --verbose --sleep=5 --tries=10 --max-time=3600
|
|
||||||
elif [ "$role" = "websockets" ]; then
|
|
||||||
echo "Running the websockets..."
|
|
||||||
php /var/www/html/artisan reverb:start --port=9000
|
|
||||||
elif [ "$role" = "scheduler" ]; then
|
|
||||||
while [ true ]
|
|
||||||
do
|
|
||||||
php /var/www/html/artisan schedule:run --verbose --no-interaction &
|
|
||||||
sleep 60
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "Could not match the container role \"$role\""
|
|
||||||
exit 1
|
|
||||||
fi
|
|
@ -26,52 +26,9 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
- redis
|
- redis
|
||||||
- reverb
|
|
||||||
env_file: app/application/.env
|
env_file: app/application/.env
|
||||||
queue:
|
|
||||||
build:
|
|
||||||
context: app
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
target: PRODUCTION
|
|
||||||
# restart: always
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
CONTAINER_ROLE: queue
|
|
||||||
volumes:
|
|
||||||
- ./app/application:/var/www/html
|
|
||||||
reverb:
|
|
||||||
build:
|
|
||||||
context: app
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
target: PRODUCTION
|
|
||||||
# restart: always
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
CONTAINER_ROLE: websockets
|
|
||||||
ports:
|
|
||||||
- ${DOCKER_WEBSOCKET_PORT}:9000
|
|
||||||
volumes:
|
|
||||||
- ./app/application:/var/www/html
|
|
||||||
scheduler:
|
|
||||||
build:
|
|
||||||
context: app
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
target: PRODUCTION
|
|
||||||
# restart: always
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
CONTAINER_ROLE: scheduler
|
|
||||||
volumes:
|
|
||||||
- ./app/application:/var/www/html
|
|
||||||
swagger:
|
swagger:
|
||||||
image: swaggerapi/swagger-ui
|
image: swaggerapi/swagger-ui
|
||||||
# restart: always
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- app
|
- app
|
||||||
environment:
|
environment:
|
||||||
@ -79,7 +36,6 @@ services:
|
|||||||
BASE_URL: /api-docs
|
BASE_URL: /api-docs
|
||||||
redis:
|
redis:
|
||||||
image: redis:3.0-alpine
|
image: redis:3.0-alpine
|
||||||
# restart: always
|
|
||||||
db:
|
db:
|
||||||
image: docker.io/mysql:8.0.33
|
image: docker.io/mysql:8.0.33
|
||||||
command: --default-authentication-plugin=mysql_native_password
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
|
@ -11,41 +11,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- ${DOCKER_NGINX_PORT}:80
|
- ${DOCKER_NGINX_PORT}:80
|
||||||
app:
|
app:
|
||||||
image: korelf/service-captcha:0.8.0
|
image: korelf/service-captcha:0.7.1
|
||||||
# restart: always
|
# restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
- redis
|
- redis
|
||||||
- reverb
|
|
||||||
env_file: app/.env
|
|
||||||
queue:
|
|
||||||
image: korelf/service-captcha:0.8.0
|
|
||||||
# restart: always
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
CONTAINER_ROLE: queue
|
|
||||||
env_file: app/.env
|
|
||||||
reverb:
|
|
||||||
image: korelf/service-captcha:0.8.0
|
|
||||||
# restart: always
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
CONTAINER_ROLE: websockets
|
|
||||||
env_file: app/.env
|
|
||||||
ports:
|
|
||||||
- ${DOCKER_WEBSOCKET_PORT}:9000
|
|
||||||
scheduler:
|
|
||||||
image: korelf/service-captcha:0.8.0
|
|
||||||
# restart: always
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
CONTAINER_ROLE: scheduler
|
|
||||||
env_file: app/.env
|
env_file: app/.env
|
||||||
redis:
|
redis:
|
||||||
image: redis:3.0-alpine
|
image: redis:3.0-alpine
|
||||||
|
@ -10,54 +10,11 @@ services:
|
|||||||
- db
|
- db
|
||||||
- redis
|
- redis
|
||||||
- app
|
- app
|
||||||
- reverb
|
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
context: app
|
context: app
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
target: DEVELOP
|
target: DEVELOP
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
- reverb
|
|
||||||
volumes:
|
|
||||||
- ./app/application:/var/www/html
|
|
||||||
queue:
|
|
||||||
build:
|
|
||||||
context: app
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
target: DEVELOP
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
CONTAINER_ROLE: queue
|
|
||||||
volumes:
|
|
||||||
- ./app/application:/var/www/html
|
|
||||||
reverb:
|
|
||||||
build:
|
|
||||||
context: app
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
target: DEVELOP
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
CONTAINER_ROLE: websockets
|
|
||||||
ports:
|
|
||||||
- ${DOCKER_WEBSOCKET_PORT}:9000
|
|
||||||
volumes:
|
|
||||||
- ./app/application:/var/www/html
|
|
||||||
scheduler:
|
|
||||||
build:
|
|
||||||
context: app
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
target: DEVELOP
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
CONTAINER_ROLE: scheduler
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./app/application:/var/www/html
|
- ./app/application:/var/www/html
|
||||||
redis:
|
redis:
|
||||||
|
Loading…
Reference in New Issue
Block a user