Версия 0.8.0 #4
@ -1,4 +1,5 @@
|
||||
DOCKER_NGINX_PORT=8080
|
||||
DOCKER_WEBSOCKET_PORT=8081
|
||||
DOCKER_DB_PORT=3306
|
||||
MYSQL_ROOT_PASSWORD=root_pass
|
||||
DB_DATABASE=captcha
|
||||
|
@ -72,3 +72,21 @@ VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||||
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||
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}"
|
||||
|
17
app/application/app/Broadcasting/CreatedCaptchaLog.php
Normal file
17
app/application/app/Broadcasting/CreatedCaptchaLog.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?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,4 +8,9 @@ enum CaptchaLogType: int
|
||||
case Error = 2;
|
||||
case Verified = 3;
|
||||
case ReadVerified = 4;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('captcha_log_type.' . $this->name);
|
||||
}
|
||||
}
|
||||
|
47
app/application/app/Events/CreatedCaptchaLog.php
Normal file
47
app/application/app/Events/CreatedCaptchaLog.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?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,12 +2,42 @@
|
||||
|
||||
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;
|
||||
|
||||
final class DashboardController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
public function __construct(
|
||||
private readonly DashboardService $dashboardService,
|
||||
) { }
|
||||
|
||||
public function index(Request $request): View
|
||||
{
|
||||
return view('private/dashboard/index');
|
||||
$user = $request->user();
|
||||
$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));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?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,6 +4,7 @@
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class Captcha extends Model
|
||||
@ -34,4 +35,9 @@ public function captchaLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(CaptchaLog::class);
|
||||
}
|
||||
|
||||
public function captchaToken(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CaptchaToken::class);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
final class CaptchaLog extends Model
|
||||
{
|
||||
@ -44,4 +45,9 @@ public function scopeLatest(Builder $query): Builder
|
||||
{
|
||||
return $query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function captcha(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Captcha::class);
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,30 @@
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Dto\Repository\CaptchaLogRepository\QuantityByDays;
|
||||
use App\Enums\CaptchaLogType;
|
||||
use App\Models\CaptchaLog;
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use App\Services\Search\Search;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
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
|
||||
{
|
||||
return CaptchaLog::query()
|
||||
@ -32,4 +49,22 @@ public function getCaptchaLogsByTypes(array $types, ?int $captchaId = null, ?int
|
||||
->latest()
|
||||
->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;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?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,6 +4,7 @@
|
||||
|
||||
use App\Dto\HttpUserData;
|
||||
use App\Enums\CaptchaLogType;
|
||||
use App\Events\CreatedCaptchaLog;
|
||||
use App\Models\Captcha;
|
||||
use App\Models\CaptchaLog;
|
||||
use Illuminate\Support\Str;
|
||||
@ -22,12 +23,16 @@ public function handleStore(int $captchaId, CaptchaLogType $captchaLogType, Http
|
||||
$referer = Str::limit($referer, 10000, '');
|
||||
}
|
||||
|
||||
return CaptchaLog::create([
|
||||
$captchaLog = CaptchaLog::create([
|
||||
'captcha_id' => $captchaId,
|
||||
'type' => $captchaLogType,
|
||||
'ip' => $httpUserData->getClientIp(),
|
||||
'user_agent' => $userAgent,
|
||||
'referer' => $referer,
|
||||
]);
|
||||
|
||||
CreatedCaptchaLog::dispatch($captchaLog);
|
||||
|
||||
return $captchaLog;
|
||||
}
|
||||
}
|
||||
|
54
app/application/app/Services/Private/DashboardService.php
Normal file
54
app/application/app/Services/Private/DashboardService.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
24
app/application/app/Services/Private/WebsocketService.php
Normal file
24
app/application/app/Services/Private/WebsocketService.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?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,6 +9,7 @@
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"kor-elf/captcha-rule-for-laravel": "^1.0",
|
||||
"laravel/framework": "^11.0",
|
||||
"laravel/reverb": "@beta",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"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\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
|
||||
|
97
app/application/config/reverb.php
Normal file
97
app/application/config/reverb.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?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,5 +73,6 @@
|
||||
"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 !!!"
|
||||
"Demo Mode": "!!! Demo Mode !!!",
|
||||
"From cannot be greater than To": "From cannot be greater than To."
|
||||
}
|
||||
|
7
app/application/lang/en/captcha_log_type.php
Normal file
7
app/application/lang/en/captcha_log_type.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return [
|
||||
'Created' => 'Creating a captcha',
|
||||
'Error' => 'Error in captcha validation',
|
||||
'Verified' => 'Successfully verified',
|
||||
'ReadVerified' => 'Completed captcha verification',
|
||||
];
|
@ -73,5 +73,6 @@
|
||||
"The time for captcha verification has passed": "Время верификации капчи прошло.",
|
||||
"Captcha does not pass verification": "Капча не проходит проверку.",
|
||||
"Add code to the site": "Добавьте код на сайт",
|
||||
"Demo Mode": "!!! Включён демо режим !!!"
|
||||
"Demo Mode": "!!! Включён демо режим !!!",
|
||||
"From cannot be greater than To": "From не может быть больше To."
|
||||
}
|
||||
|
7
app/application/lang/ru/captcha_log_type.php
Normal file
7
app/application/lang/ru/captcha_log_type.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return [
|
||||
'Created' => 'Создание капчи',
|
||||
'Error' => 'Ошибка в валидации капчи',
|
||||
'Verified' => 'Успешно проверено',
|
||||
'ReadVerified' => 'Завершена проверка капчи',
|
||||
];
|
47
app/application/package-lock.json
generated
47
app/application/package-lock.json
generated
@ -21,7 +21,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.1.2",
|
||||
"laravel-echo": "^1.16.1",
|
||||
"laravel-vite-plugin": "^0.7.2",
|
||||
"pusher-js": "^8.4.0-rc2",
|
||||
"sass-loader": "^13.3.2",
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
@ -1251,6 +1253,15 @@
|
||||
"dev": 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": {
|
||||
"version": "0.7.8",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
|
||||
@ -1436,6 +1447,15 @@
|
||||
"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": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
@ -1723,6 +1743,12 @@
|
||||
"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": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
||||
@ -2755,6 +2781,12 @@
|
||||
"dev": 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": {
|
||||
"version": "0.7.8",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
|
||||
@ -2883,6 +2915,15 @@
|
||||
"dev": 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": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
@ -3056,6 +3097,12 @@
|
||||
"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": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
||||
|
@ -21,7 +21,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.1.2",
|
||||
"laravel-echo": "^1.16.1",
|
||||
"laravel-vite-plugin": "^0.7.2",
|
||||
"pusher-js": "^8.4.0-rc2",
|
||||
"sass-loader": "^13.3.2",
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
|
@ -1,5 +1,40 @@
|
||||
@section('meta_title', __('sections.Dashboard'))
|
||||
@section('h1', __('sections.Dashboard'))
|
||||
<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>
|
||||
|
@ -2,4 +2,6 @@ import.meta.glob([
|
||||
'../images/**',
|
||||
]);
|
||||
import './bootstrap';
|
||||
import './volt.js';
|
||||
import './echo';
|
||||
import './volt';
|
||||
import './dashboard'
|
||||
|
80
app/application/resources/volt/js/dashboard.js
Normal file
80
app/application/resources/volt/js/dashboard.js
Normal file
@ -0,0 +1,80 @@
|
||||
"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 => {
|
||||
|
||||
});
|
||||
}
|
49
app/application/resources/volt/js/echo.js
Normal file
49
app/application/resources/volt/js/echo.js
Normal file
@ -0,0 +1,49 @@
|
||||
"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,8 +21,6 @@ const d = document;
|
||||
import * as bootstrap from 'bootstrap';
|
||||
import Swal from 'sweetalert2';
|
||||
import SmoothScroll from 'smooth-scroll';
|
||||
import Chartist from 'chartist';
|
||||
import 'chartist-plugin-tooltips';
|
||||
|
||||
d.addEventListener("DOMContentLoaded", function(event) {
|
||||
|
||||
@ -176,102 +174,6 @@ 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')) {
|
||||
d.getElementById('loadOnClick').addEventListener('click', function () {
|
||||
var button = this;
|
||||
|
@ -12,3 +12,5 @@
|
||||
| used to check if an authenticated user can listen to the channel.
|
||||
|
|
||||
*/
|
||||
|
||||
Broadcast::channel('chart-captcha-activity', \App\Broadcasting\CreatedCaptchaLog::class);
|
||||
|
@ -20,6 +20,7 @@
|
||||
Route::middleware(['auth', 'verified', 'user.locale'])->group(function () {
|
||||
Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout');
|
||||
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.')
|
||||
->group(function () {
|
||||
Route::get('/', [\App\Http\Controllers\Private\ProfileController::class, 'profile'])->name('edit');
|
||||
@ -34,4 +35,6 @@
|
||||
|
||||
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::get('websockets/settings', [\App\Http\Controllers\Private\WebsocketsController::class, 'settings']);
|
||||
});
|
||||
|
@ -73,7 +73,8 @@ STOPSIGNAL SIGTERM
|
||||
|
||||
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
|
||||
EXPOSE 9000
|
||||
CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock", "--user", "unit", "--group", "unit"]
|
||||
COPY docker/start_prod.sh /usr/local/bin/start
|
||||
CMD ["/usr/local/bin/start"]
|
||||
|
||||
|
||||
FROM BUILD AS DEVELOP
|
||||
@ -91,9 +92,9 @@ RUN chmod 755 /home/unit/docker-entrypoint.sh \
|
||||
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
|
||||
|
||||
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
|
||||
WORKDIR /var/www/html
|
||||
|
21
app/docker/start_dev.sh
Executable file
21
app/docker/start_dev.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/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
|
21
app/docker/start_prod.sh
Executable file
21
app/docker/start_prod.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/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,9 +26,52 @@ services:
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
- reverb
|
||||
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:
|
||||
image: swaggerapi/swagger-ui
|
||||
# restart: always
|
||||
depends_on:
|
||||
- app
|
||||
environment:
|
||||
@ -36,6 +79,7 @@ services:
|
||||
BASE_URL: /api-docs
|
||||
redis:
|
||||
image: redis:3.0-alpine
|
||||
# restart: always
|
||||
db:
|
||||
image: docker.io/mysql:8.0.33
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
|
@ -11,11 +11,41 @@ services:
|
||||
ports:
|
||||
- ${DOCKER_NGINX_PORT}:80
|
||||
app:
|
||||
image: korelf/service-captcha:0.7.1
|
||||
image: korelf/service-captcha:0.8.0
|
||||
# restart: always
|
||||
depends_on:
|
||||
- db
|
||||
- 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
|
||||
redis:
|
||||
image: redis:3.0-alpine
|
||||
|
@ -10,11 +10,54 @@ services:
|
||||
- db
|
||||
- redis
|
||||
- app
|
||||
- reverb
|
||||
app:
|
||||
build:
|
||||
context: app
|
||||
dockerfile: docker/Dockerfile
|
||||
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:
|
||||
- ./app/application:/var/www/html
|
||||
redis:
|
||||
|
Loading…
Reference in New Issue
Block a user