mirror of
https://github.com/pelican-dev/panel.git
synced 2025-05-19 17:34:45 +02:00
Update phpstan to latest (#804)
* Fix these * Update phpstan * Transform these into their identifiers instead * Fix custom rule * License is wrong * Update these * Pint fixes * Fix this * Consolidate these * Never supported PHP 7 * Better evaluation * Fixes * Don’t need ignore * Replace trait with service * Subusers are simply the many to many relationship between Servers and Users * Adjust to remove ignores * Use new query builder instead! * wip * Update composer * Quick fixes * Use realtime facade * Small fixes * Convert to static to avoid new * Update to statics * Don’t modify protected properties directly * Run pint * Change to correct method * Give up and use the facade * Make sure this route is available * Filament hasn’t been loaded yet * This can be readonly * Typehint * These are no longer used * Quick fixes * Need doc block help * Always true * We use caddy with docker * Pint * Fix phpstan issues * Remove unused import --------- Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>
This commit is contained in:
parent
02c4eb19f0
commit
ad1a9cd33f
75
.github/docker/default.conf
vendored
75
.github/docker/default.conf
vendored
@ -1,75 +0,0 @@
|
||||
# If using Ubuntu this file should be placed in:
|
||||
# /etc/nginx/sites-available/
|
||||
#
|
||||
# If using CentOS this file should be placed in:
|
||||
# /etc/nginx/conf.d/
|
||||
#
|
||||
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Pterodactyl®
|
||||
# Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /app/public;
|
||||
index index.html index.htm index.php;
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
access_log off;
|
||||
error_log /var/log/nginx/panel.app-error.log error;
|
||||
|
||||
# allow larger file uploads and longer script runtimes
|
||||
client_max_body_size 100m;
|
||||
client_body_timeout 120s;
|
||||
|
||||
sendfile off;
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
# the fastcgi_pass path needs to be changed accordingly when using CentOS
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
fastcgi_intercept_errors off;
|
||||
fastcgi_buffer_size 16k;
|
||||
fastcgi_buffers 4 16k;
|
||||
fastcgi_connect_timeout 300;
|
||||
fastcgi_send_timeout 300;
|
||||
fastcgi_read_timeout 300;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
70
.github/docker/default_ssl.conf
vendored
70
.github/docker/default_ssl.conf
vendored
@ -1,70 +0,0 @@
|
||||
# If using Ubuntu this file should be placed in:
|
||||
# /etc/nginx/sites-available/
|
||||
#
|
||||
server {
|
||||
listen 80;
|
||||
server_name <domain>;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name <domain>;
|
||||
|
||||
root /app/public;
|
||||
index index.php;
|
||||
|
||||
access_log /var/log/nginx/panel.app-access.log;
|
||||
error_log /var/log/nginx/panel.app-error.log error;
|
||||
|
||||
# allow larger file uploads and longer script runtimes
|
||||
client_max_body_size 100m;
|
||||
client_body_timeout 120s;
|
||||
|
||||
sendfile off;
|
||||
|
||||
# strengthen ssl security
|
||||
ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
||||
|
||||
# See the link below for more SSL information:
|
||||
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
|
||||
#
|
||||
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
||||
|
||||
# Add headers to serve security related headers
|
||||
add_header Strict-Transport-Security "max-age=15768000; preload;";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
add_header Content-Security-Policy "frame-ancestors 'self'";
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
fastcgi_intercept_errors off;
|
||||
fastcgi_buffer_size 16k;
|
||||
fastcgi_buffers 4 16k;
|
||||
fastcgi_connect_timeout 300;
|
||||
fastcgi_send_timeout 300;
|
||||
fastcgi_read_timeout 300;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
16
.github/docker/www.conf
vendored
16
.github/docker/www.conf
vendored
@ -1,16 +0,0 @@
|
||||
[www]
|
||||
|
||||
user = nginx
|
||||
group = nginx
|
||||
|
||||
listen = 127.0.0.1:9000
|
||||
listen.owner = nginx
|
||||
listen.group = nginx
|
||||
listen.mode = 0750
|
||||
|
||||
pm = ondemand
|
||||
pm.max_children = 9
|
||||
pm.process_idle_timeout = 10s
|
||||
pm.max_requests = 200
|
||||
|
||||
clear_env = no
|
@ -6,6 +6,7 @@ use Illuminate\Console\Command;
|
||||
use App\Models\Schedule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use App\Services\Schedules\ProcessScheduleService;
|
||||
use Throwable;
|
||||
|
||||
class ProcessRunnableCommand extends Command
|
||||
{
|
||||
@ -13,10 +14,7 @@ class ProcessRunnableCommand extends Command
|
||||
|
||||
protected $description = 'Process schedules in the database and determine which are ready to run.';
|
||||
|
||||
/**
|
||||
* Handle command execution.
|
||||
*/
|
||||
public function handle(): int
|
||||
public function handle(ProcessScheduleService $processScheduleService): int
|
||||
{
|
||||
$schedules = Schedule::query()
|
||||
->with('tasks')
|
||||
@ -35,7 +33,7 @@ class ProcessRunnableCommand extends Command
|
||||
$bar = $this->output->createProgressBar(count($schedules));
|
||||
foreach ($schedules as $schedule) {
|
||||
$bar->clear();
|
||||
$this->processSchedule($schedule);
|
||||
$this->processSchedule($processScheduleService, $schedule);
|
||||
$bar->advance();
|
||||
$bar->display();
|
||||
}
|
||||
@ -50,20 +48,20 @@ class ProcessRunnableCommand extends Command
|
||||
* never throw an exception out, otherwise you'll end up killing the entire run group causing
|
||||
* any other schedules to not process correctly.
|
||||
*/
|
||||
protected function processSchedule(Schedule $schedule): void
|
||||
protected function processSchedule(ProcessScheduleService $processScheduleService, Schedule $schedule): void
|
||||
{
|
||||
if ($schedule->tasks->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->getLaravel()->make(ProcessScheduleService::class)->handle($schedule);
|
||||
$processScheduleService->handle($schedule);
|
||||
|
||||
$this->line(trans('command/messages.schedule.output_line', [
|
||||
'schedule' => $schedule->name,
|
||||
'id' => $schedule->id,
|
||||
]));
|
||||
} catch (\Throwable|\Exception $exception) {
|
||||
} catch (Throwable $exception) {
|
||||
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
||||
|
||||
$this->error(__('commands.schedule.process.no_tasks') . " #$schedule->id: " . $exception->getMessage());
|
||||
|
@ -19,26 +19,13 @@ class BulkPowerActionCommand extends Command
|
||||
|
||||
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
|
||||
|
||||
/**
|
||||
* BulkPowerActionCommand constructor.
|
||||
*/
|
||||
public function __construct(private DaemonPowerRepository $powerRepository, private ValidatorFactory $validator)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the bulk power request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function handle(): void
|
||||
public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory $validator): void
|
||||
{
|
||||
$action = $this->argument('action');
|
||||
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
|
||||
$servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers'));
|
||||
|
||||
$validator = $this->validator->make([
|
||||
$validator = $validator->make([
|
||||
'action' => $action,
|
||||
'nodes' => $nodes,
|
||||
'servers' => $servers,
|
||||
@ -64,11 +51,14 @@ class BulkPowerActionCommand extends Command
|
||||
}
|
||||
|
||||
$bar = $this->output->createProgressBar($count);
|
||||
$powerRepository = $this->powerRepository;
|
||||
// @phpstan-ignore-next-line
|
||||
$this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) {
|
||||
|
||||
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $powerRepository, &$bar): mixed {
|
||||
$bar->clear();
|
||||
|
||||
if (!$server instanceof Server) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$powerRepository->setServer($server)->send($action);
|
||||
} catch (Exception $exception) {
|
||||
@ -82,6 +72,8 @@ class BulkPowerActionCommand extends Command
|
||||
|
||||
$bar->advance();
|
||||
$bar->display();
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
$this->line('');
|
||||
|
@ -39,10 +39,6 @@ class UpgradeCommand extends Command
|
||||
$this->line($this->getUrl());
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.4.0') < 0) {
|
||||
$this->error(__('commands.upgrade.php_version') . ' [' . PHP_VERSION . '].');
|
||||
}
|
||||
|
||||
$user = 'www-data';
|
||||
$group = 'www-data';
|
||||
if ($this->input->isInteractive()) {
|
||||
|
24
app/Eloquent/BackupQueryBuilder.php
Normal file
24
app/Eloquent/BackupQueryBuilder.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Eloquent;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @extends Builder<TModel>
|
||||
*/
|
||||
class BackupQueryBuilder extends Builder
|
||||
{
|
||||
public function nonFailed(): self
|
||||
{
|
||||
$this->where(function (Builder $query) {
|
||||
$query
|
||||
->whereNull('completed_at')
|
||||
->orWhere('is_successful', true);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -91,7 +91,7 @@ enum EditorLanguages: string implements HasLabel
|
||||
case yaml = 'yaml';
|
||||
case json = 'json';
|
||||
|
||||
public function getLabel(): ?string
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
@ -179,10 +180,7 @@ class Handler extends ExceptionHandler
|
||||
return response()->json(['errors' => $errors], $exception->status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the exception as a JSONAPI representation for use on API requests.
|
||||
*/
|
||||
protected function convertExceptionToArray(\Throwable $e, array $override = []): array
|
||||
public static function exceptionToArray(Throwable $e, array $override = []): array
|
||||
{
|
||||
$match = self::$exceptionResponseCodes[get_class($e)] ?? null;
|
||||
|
||||
@ -214,7 +212,7 @@ class Handler extends ExceptionHandler
|
||||
'trace' => Collection::make($e->getTrace())
|
||||
->map(fn ($trace) => Arr::except($trace, ['args']))
|
||||
->all(),
|
||||
'previous' => Collection::make($this->extractPrevious($e))
|
||||
'previous' => Collection::make(self::extractPrevious($e))
|
||||
->map(fn ($exception) => $exception->getTrace())
|
||||
->map(fn ($trace) => Arr::except($trace, ['args']))
|
||||
->all(),
|
||||
@ -225,6 +223,14 @@ class Handler extends ExceptionHandler
|
||||
return ['errors' => [array_merge($error, $override)]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the exception as a JSONAPI representation for use on API requests.
|
||||
*/
|
||||
protected function convertExceptionToArray(Throwable $e, array $override = []): array
|
||||
{
|
||||
return self::exceptionToArray($e, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of exceptions that should not be reported.
|
||||
*/
|
||||
@ -251,15 +257,12 @@ class Handler extends ExceptionHandler
|
||||
* Extracts all the previous exceptions that lead to the one passed into this
|
||||
* function being thrown.
|
||||
*
|
||||
* @return \Throwable[]
|
||||
* @return Throwable[]
|
||||
*/
|
||||
protected function extractPrevious(\Throwable $e): array
|
||||
public static function extractPrevious(Throwable $e): array
|
||||
{
|
||||
$previous = [];
|
||||
while ($value = $e->getPrevious()) {
|
||||
if (!$value instanceof \Throwable) {
|
||||
break;
|
||||
}
|
||||
$previous[] = $value;
|
||||
$e = $value;
|
||||
}
|
||||
@ -273,7 +276,6 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public static function toArray(\Throwable $e): array
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return (new self(app()))->convertExceptionToArray($e);
|
||||
return self::exceptionToArray($e);
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,5 @@
|
||||
<?php
|
||||
|
||||
/* The MIT License (MIT)
|
||||
|
||||
Pterodactyl®
|
||||
Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE. */
|
||||
|
||||
namespace App\Extensions\Filesystem;
|
||||
|
||||
use Aws\S3\S3ClientInterface;
|
||||
|
@ -5,6 +5,7 @@ namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||
use App\Filament\Admin\Resources\UserResource;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Services\Helpers\LanguageService;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
@ -40,7 +41,7 @@ class EditUser extends EditRecord
|
||||
->required()
|
||||
->hidden()
|
||||
->default('en')
|
||||
->options(fn (User $user) => $user->getAvailableLanguages()),
|
||||
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages()),
|
||||
Hidden::make('skipValidation')
|
||||
->default(true),
|
||||
CheckboxList::make('roles')
|
||||
|
@ -34,7 +34,7 @@ class ServersRelationManager extends RelationManager
|
||||
->label('Suspend All Servers')
|
||||
->color('warning')
|
||||
->action(function (SuspensionService $suspensionService) use ($user) {
|
||||
collect($user->servers()->get())->filter(fn ($server) => !$server->isSuspended())
|
||||
collect($user->servers)->filter(fn ($server) => !$server->isSuspended())
|
||||
->each(fn ($server) => $suspensionService->handle($server, SuspendAction::Suspend));
|
||||
}),
|
||||
Actions\Action::make('toggleUnsuspend')
|
||||
|
@ -10,7 +10,6 @@ use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class ImportEggAction extends Action
|
||||
{
|
||||
@ -55,7 +54,6 @@ class ImportEggAction extends Action
|
||||
$this->action(function (array $data, EggImporterService $eggImportService): void {
|
||||
try {
|
||||
if (!empty($data['egg'])) {
|
||||
/** @var TemporaryUploadedFile[] $eggFile */
|
||||
$eggFile = $data['egg'];
|
||||
|
||||
foreach ($eggFile as $file) {
|
||||
|
@ -10,7 +10,6 @@ use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class ImportEggAction extends Action
|
||||
{
|
||||
@ -55,7 +54,6 @@ class ImportEggAction extends Action
|
||||
$this->action(function (array $data, EggImporterService $eggImportService): void {
|
||||
try {
|
||||
if (!empty($data['egg'])) {
|
||||
/** @var TemporaryUploadedFile[] $eggFile */
|
||||
$eggFile = $data['egg'];
|
||||
|
||||
foreach ($eggFile as $file) {
|
||||
|
@ -23,6 +23,6 @@ class DateTimeColumn extends TextColumn
|
||||
|
||||
public function getTimezone(): string
|
||||
{
|
||||
return auth()->user()?->timezone ?? config('app.timezone', 'UTC');
|
||||
return auth()->user()->timezone ?? config('app.timezone', 'UTC');
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use App\Facades\Activity;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\ApiKey;
|
||||
use App\Models\User;
|
||||
use App\Services\Helpers\LanguageService;
|
||||
use App\Services\Users\ToggleTwoFactorService;
|
||||
use App\Services\Users\TwoFactorSetupService;
|
||||
use App\Services\Users\UserUpdateService;
|
||||
@ -115,13 +116,12 @@ class EditProfile extends BaseEditProfile
|
||||
->prefixIcon('tabler-flag')
|
||||
->live()
|
||||
->default('en')
|
||||
->helperText(fn (User $user, $state) => new HtmlString($user->isLanguageTranslated($state) ? '' : "
|
||||
->helperText(fn ($state, LanguageService $languageService) => new HtmlString($languageService->isLanguageTranslated($state) ? '' : "
|
||||
Your language ($state) has not been translated yet!
|
||||
But never fear, you can help fix that by
|
||||
<a style='color: rgb(56, 189, 248)' href='https://crowdin.com/project/pelican-dev'>contributing directly here</a>.
|
||||
")
|
||||
)
|
||||
->options(fn (User $user) => $user->getAvailableLanguages()),
|
||||
"))
|
||||
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages()),
|
||||
]),
|
||||
|
||||
Tab::make('OAuth')
|
||||
@ -208,38 +208,30 @@ class EditProfile extends BaseEditProfile
|
||||
'addLogoSpace' => true,
|
||||
'logoSpaceWidth' => 13,
|
||||
'logoSpaceHeight' => 13,
|
||||
'version' => Version::AUTO,
|
||||
// 'outputInterface' => QRSvgWithLogo::class,
|
||||
'outputBase64' => false,
|
||||
'eccLevel' => EccLevel::H, // ECC level H is necessary when using logos
|
||||
'addQuietzone' => true,
|
||||
// 'drawLightModules' => true,
|
||||
'connectPaths' => true,
|
||||
'drawCircularModules' => true,
|
||||
// 'circleRadius' => 0.45,
|
||||
'svgDefs' => '
|
||||
<linearGradient id="gradient" x1="100%" y2="100%">
|
||||
<stop stop-color="#7dd4fc" offset="0"/>
|
||||
<stop stop-color="#38bdf8" offset="0.5"/>
|
||||
<stop stop-color="#0369a1" offset="1"/>
|
||||
</linearGradient>
|
||||
<style><![CDATA[
|
||||
.dark{fill: url(#gradient);}
|
||||
.light{fill: #000;}
|
||||
]]></style>
|
||||
',
|
||||
]);
|
||||
|
||||
// https://github.com/chillerlan/php-qrcode/blob/main/examples/svgWithLogo.php
|
||||
|
||||
// QROptions
|
||||
// @phpstan-ignore property.protected
|
||||
$options->version = Version::AUTO;
|
||||
// $options->outputInterface = QRSvgWithLogo::class;
|
||||
// @phpstan-ignore property.protected
|
||||
$options->outputBase64 = false;
|
||||
// @phpstan-ignore property.protected
|
||||
$options->eccLevel = EccLevel::H; // ECC level H is necessary when using logos
|
||||
// @phpstan-ignore property.protected
|
||||
$options->addQuietzone = true;
|
||||
// $options->drawLightModules = true;
|
||||
// @phpstan-ignore property.protected
|
||||
$options->connectPaths = true;
|
||||
// @phpstan-ignore property.protected
|
||||
$options->drawCircularModules = true;
|
||||
// $options->circleRadius = 0.45;
|
||||
|
||||
// @phpstan-ignore property.protected
|
||||
$options->svgDefs = '<linearGradient id="gradient" x1="100%" y2="100%">
|
||||
<stop stop-color="#7dd4fc" offset="0"/>
|
||||
<stop stop-color="#38bdf8" offset="0.5"/>
|
||||
<stop stop-color="#0369a1" offset="1"/>
|
||||
</linearGradient>
|
||||
<style><![CDATA[
|
||||
.dark{fill: url(#gradient);}
|
||||
.light{fill: #000;}
|
||||
]]></style>';
|
||||
|
||||
$image = (new QRCode($options))->render($url);
|
||||
|
||||
return [
|
||||
|
@ -6,7 +6,6 @@ use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Filament\Server\Resources\DatabaseResource;
|
||||
use App\Models\Database;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
@ -99,7 +98,7 @@ class ListDatabases extends ListRecords
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->placeholder('Select Database Host')
|
||||
->options(fn () => $server->node->databaseHosts->mapWithKeys(fn (DatabaseHost $databaseHost) => [$databaseHost->id => $databaseHost->name])),
|
||||
->options(fn () => $server->node->databaseHosts->mapWithKeys(fn ($databaseHost) => [$databaseHost->id => $databaseHost->name])),
|
||||
TextInput::make('database')
|
||||
->columnSpan(1)
|
||||
->label('Database Name')
|
||||
|
@ -355,8 +355,7 @@ class ListFiles extends ListRecords
|
||||
->action(function (Collection $files, $data, DaemonFileRepository $fileRepository) use ($server) {
|
||||
$location = resolve_path(join_paths($this->path, $data['location']));
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$files = $files->map(fn ($file) => ['to' => $location, 'from' => $file->name])->toArray();
|
||||
$files = $files->map(fn ($file) => ['to' => $location, 'from' => $file['name']])->toArray();
|
||||
$fileRepository
|
||||
->setServer($server)
|
||||
->renameFiles($this->path, $files);
|
||||
@ -374,8 +373,7 @@ class ListFiles extends ListRecords
|
||||
BulkAction::make('archive')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
|
||||
->action(function (Collection $files, DaemonFileRepository $fileRepository) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
$files = $files->map(fn ($file) => $file->name)->toArray();
|
||||
$files = $files->map(fn ($file) => $file['name'])->toArray();
|
||||
|
||||
$fileRepository
|
||||
->setServer($server)
|
||||
@ -396,8 +394,7 @@ class ListFiles extends ListRecords
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_DELETE, $server))
|
||||
->action(function (Collection $files, DaemonFileRepository $fileRepository) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
$files = $files->map(fn ($file) => $file->name)->toArray();
|
||||
$files = $files->map(fn ($file) => $file['name'])->toArray();
|
||||
$fileRepository
|
||||
->setServer($server)
|
||||
->deleteFiles($this->path, $files);
|
||||
|
@ -30,16 +30,25 @@ class ServerConsole extends Widget
|
||||
|
||||
public string $input = '';
|
||||
|
||||
private GetUserPermissionsService $getUserPermissionsService;
|
||||
|
||||
private NodeJWTService $nodeJWTService;
|
||||
|
||||
public function boot(GetUserPermissionsService $getUserPermissionsService, NodeJWTService $nodeJWTService): void
|
||||
{
|
||||
$this->getUserPermissionsService = $getUserPermissionsService;
|
||||
$this->nodeJWTService = $nodeJWTService;
|
||||
}
|
||||
|
||||
protected function getToken(): string
|
||||
{
|
||||
if (!$this->user || !$this->server || $this->user->cannot(Permission::ACTION_WEBSOCKET_CONNECT, $this->server)) {
|
||||
throw new HttpForbiddenException('You do not have permission to connect to this server\'s websocket.');
|
||||
}
|
||||
// @phpstan-ignore-next-line
|
||||
$permissions = app(GetUserPermissionsService::class)->handle($this->server, $this->user);
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return app(NodeJWTService::class)
|
||||
$permissions = $this->getUserPermissionsService->handle($this->server, $this->user);
|
||||
|
||||
return $this->nodeJWTService
|
||||
->setExpiresAt(now()->addMinutes(10)->toImmutable())
|
||||
->setUser($this->user)
|
||||
->setClaims([
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Client;
|
||||
|
||||
use Illuminate\Auth\SessionGuard;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
@ -63,8 +64,7 @@ class AccountController extends ClientApiController
|
||||
// other devices functionality to work.
|
||||
$guard->setUser($user);
|
||||
|
||||
// This method doesn't exist in the stateless Sanctum world.
|
||||
if (method_exists($guard, 'logoutOtherDevices')) {
|
||||
if ($guard instanceof SessionGuard) {
|
||||
$guard->logoutOtherDevices($request->input('password'));
|
||||
}
|
||||
|
||||
|
@ -22,14 +22,11 @@ use App\Http\Requests\Api\Client\Servers\Backups\RestoreBackupRequest;
|
||||
|
||||
class BackupController extends ClientApiController
|
||||
{
|
||||
/**
|
||||
* BackupController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private DaemonBackupRepository $daemonRepository,
|
||||
private DeleteBackupService $deleteBackupService,
|
||||
private InitiateBackupService $initiateBackupService,
|
||||
private DownloadLinkService $downloadLinkService,
|
||||
private readonly DaemonBackupRepository $daemonRepository,
|
||||
private readonly DeleteBackupService $deleteBackupService,
|
||||
private readonly InitiateBackupService $initiateBackupService,
|
||||
private readonly DownloadLinkService $downloadLinkService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@ -38,7 +35,7 @@ class BackupController extends ClientApiController
|
||||
* Returns all the backups for a given server instance in a paginated
|
||||
* result set.
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function index(Request $request, Server $server): array
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ namespace App\Http\Controllers\Api\Client\Servers;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Models\Server;
|
||||
use App\Facades\Activity;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use App\Http\Controllers\Api\Client\ClientApiController;
|
||||
@ -28,7 +27,7 @@ class CommandController extends ClientApiController
|
||||
$previous = $exception->getPrevious();
|
||||
|
||||
if ($previous instanceof BadResponseException) {
|
||||
if ($previous->getResponse() instanceof ResponseInterface && $previous->getResponse()->getStatusCode() === Response::HTTP_BAD_GATEWAY) {
|
||||
if ($previous->getResponse()->getStatusCode() === Response::HTTP_BAD_GATEWAY) {
|
||||
throw new HttpException(Response::HTTP_BAD_GATEWAY, 'Server must be online in order to send commands.', $exception);
|
||||
}
|
||||
}
|
||||
|
@ -158,6 +158,6 @@ class SftpAuthenticationController extends Controller
|
||||
{
|
||||
$username = explode('.', strrev($request->input('username', '')));
|
||||
|
||||
return strtolower(strrev($username[0] ?? '') . '|' . $request->ip());
|
||||
return strtolower(strrev($username[0]) . '|' . $request->ip());
|
||||
}
|
||||
}
|
||||
|
@ -1,106 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Auth\Events\Failed;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use App\Events\Auth\DirectLogin;
|
||||
use App\Exceptions\DisplayException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
|
||||
abstract class AbstractLoginController extends Controller
|
||||
{
|
||||
use AuthenticatesUsers;
|
||||
|
||||
protected AuthManager $auth;
|
||||
|
||||
/**
|
||||
* Lockout time for failed login requests.
|
||||
*/
|
||||
protected int $lockoutTime;
|
||||
|
||||
/**
|
||||
* After how many attempts should logins be throttled and locked.
|
||||
*/
|
||||
protected int $maxLoginAttempts;
|
||||
|
||||
/**
|
||||
* Where to redirect users after login / registration.
|
||||
*/
|
||||
protected string $redirectTo = '/';
|
||||
|
||||
/**
|
||||
* LoginController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->lockoutTime = config('auth.lockout.time');
|
||||
$this->maxLoginAttempts = config('auth.lockout.attempts');
|
||||
$this->auth = Container::getInstance()->make(AuthManager::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the failed login response instance.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
*/
|
||||
protected function sendFailedLoginResponse(Request $request, ?Authenticatable $user = null, ?string $message = null): never
|
||||
{
|
||||
$this->incrementLoginAttempts($request);
|
||||
$this->fireFailedLoginEvent($user, [
|
||||
$this->getField($request->input('user')) => $request->input('user'),
|
||||
]);
|
||||
|
||||
if ($request->route()->named('auth.login-checkpoint')) {
|
||||
throw new DisplayException($message ?? trans('auth.two_factor.checkpoint_failed'));
|
||||
}
|
||||
|
||||
throw new DisplayException(trans('auth.failed'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the response after the user was authenticated.
|
||||
*/
|
||||
protected function sendLoginResponse(User $user, Request $request): JsonResponse
|
||||
{
|
||||
$request->session()->remove('auth_confirmation_token');
|
||||
$request->session()->regenerate();
|
||||
|
||||
$this->clearLoginAttempts($request);
|
||||
|
||||
$this->auth->guard()->login($user, true);
|
||||
|
||||
Event::dispatch(new DirectLogin($user, true));
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => [
|
||||
'complete' => true,
|
||||
'intended' => $this->redirectPath(),
|
||||
'user' => $user->toReactObject(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is logging in using an email or username.
|
||||
*/
|
||||
protected function getField(?string $input = null): string
|
||||
{
|
||||
return ($input && str_contains($input, '@')) ? 'email' : 'username';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a failed login event.
|
||||
*/
|
||||
protected function fireFailedLoginEvent(?Authenticatable $user = null, array $credentials = []): void
|
||||
{
|
||||
Event::dispatch(new Failed('auth', $user, $credentials));
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Events\Auth\FailedPasswordReset;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
|
||||
class ForgotPasswordController extends Controller
|
||||
{
|
||||
use SendsPasswordResetEmails;
|
||||
|
||||
/**
|
||||
* Get the response for a failed password reset link.
|
||||
*/
|
||||
protected function sendResetLinkFailedResponse(Request $request, string $response): JsonResponse
|
||||
{
|
||||
// As noted in #358 we will return success even if it failed
|
||||
// to avoid pointing out that an account does or does not
|
||||
// exist on the system.
|
||||
event(new FailedPasswordReset($request->ip(), $request->input('email')));
|
||||
|
||||
return $this->sendResetLinkResponse($request, Password::RESET_LINK_SENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response for a successful password reset link.
|
||||
*/
|
||||
protected function sendResetLinkResponse(Request $request, string $response): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'status' => trans($response),
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Carbon\CarbonInterface;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use App\Events\Auth\ProvidedAuthenticationToken;
|
||||
use App\Http\Requests\Auth\LoginCheckpointRequest;
|
||||
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
|
||||
|
||||
class LoginCheckpointController extends AbstractLoginController
|
||||
{
|
||||
private const TOKEN_EXPIRED_MESSAGE = 'The authentication token provided has expired, please refresh the page and try again.';
|
||||
|
||||
/**
|
||||
* LoginCheckpointController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private Google2FA $google2FA,
|
||||
private ValidationFactory $validation
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a login where the user is required to provide a TOTP authentication
|
||||
* token. Once a user has reached this stage it is assumed that they have already
|
||||
* provided a valid username and password.
|
||||
*
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
|
||||
* @throws \Exception
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function __invoke(LoginCheckpointRequest $request): JsonResponse
|
||||
{
|
||||
if ($this->hasTooManyLoginAttempts($request)) {
|
||||
$this->sendLockoutResponse($request);
|
||||
}
|
||||
|
||||
$details = $request->session()->get('auth_confirmation_token');
|
||||
if (!$this->hasValidSessionData($details)) {
|
||||
$this->sendFailedLoginResponse($request, null, self::TOKEN_EXPIRED_MESSAGE);
|
||||
}
|
||||
|
||||
if (!hash_equals($request->input('confirmation_token') ?? '', $details['token_value'])) {
|
||||
$this->sendFailedLoginResponse($request);
|
||||
}
|
||||
|
||||
$user = User::query()->find($details['user_id']);
|
||||
if (!$user) {
|
||||
$this->sendFailedLoginResponse($request, null, self::TOKEN_EXPIRED_MESSAGE);
|
||||
}
|
||||
|
||||
// Recovery tokens go through a slightly different pathway for usage.
|
||||
if (!is_null($recoveryToken = $request->input('recovery_token'))) {
|
||||
if ($this->isValidRecoveryToken($user, $recoveryToken)) {
|
||||
Event::dispatch(new ProvidedAuthenticationToken($user, true));
|
||||
|
||||
return $this->sendLoginResponse($user, $request);
|
||||
}
|
||||
} else {
|
||||
if ($this->google2FA->verifyKey($user->totp_secret, (string) $request->input('authentication_code'), config('panel.auth.2fa.window'))) {
|
||||
Event::dispatch(new ProvidedAuthenticationToken($user));
|
||||
|
||||
return $this->sendLoginResponse($user, $request);
|
||||
}
|
||||
}
|
||||
|
||||
$this->sendFailedLoginResponse($request, $user, !empty($recoveryToken) ? 'The recovery token provided is not valid.' : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given recovery token is valid for the user account. If we find a matching token
|
||||
* it will be deleted from the database.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function isValidRecoveryToken(User $user, string $value): bool
|
||||
{
|
||||
foreach ($user->recoveryTokens as $token) {
|
||||
if (password_verify($value, $token->token)) {
|
||||
$token->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the data provided from the session is valid or not. This
|
||||
* will return false if the data is invalid, or if more time has passed than
|
||||
* was configured when the session was written.
|
||||
*/
|
||||
protected function hasValidSessionData(array $data): bool
|
||||
{
|
||||
$validator = $this->validation->make($data, [
|
||||
'user_id' => 'required|integer|min:1',
|
||||
'token_value' => 'required|string',
|
||||
'expires_at' => 'required',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$data['expires_at'] instanceof CarbonInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data['expires_at']->isBefore(CarbonImmutable::now())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Livewire\Installer\PanelInstaller;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Facades\Activity;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class LoginController extends AbstractLoginController
|
||||
{
|
||||
/**
|
||||
* Handle all incoming requests for the authentication routes and render the
|
||||
* base authentication view component. React will take over at this point and
|
||||
* turn the login area into an SPA.
|
||||
*/
|
||||
public function index(): View|RedirectResponse
|
||||
{
|
||||
if (!PanelInstaller::isInstalled()) {
|
||||
return redirect('/installer');
|
||||
}
|
||||
|
||||
return view('templates/auth.core');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a login request to the application.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function login(Request $request): JsonResponse
|
||||
{
|
||||
if ($this->hasTooManyLoginAttempts($request)) {
|
||||
$this->fireLockoutEvent($request);
|
||||
$this->sendLockoutResponse($request);
|
||||
}
|
||||
|
||||
$username = $request->input('user');
|
||||
$user = User::query()->where($this->getField($username), $username)->first();
|
||||
if (!$user) {
|
||||
$this->sendFailedLoginResponse($request);
|
||||
}
|
||||
|
||||
// Ensure that the account is using a valid username and password before trying to
|
||||
// continue. Previously this was handled in the 2FA checkpoint, however that has
|
||||
// a flaw in which you can discover if an account exists simply by seeing if you
|
||||
// can proceed to the next step in the login process.
|
||||
if (!password_verify($request->input('password'), $user->password)) {
|
||||
$this->sendFailedLoginResponse($request, $user);
|
||||
}
|
||||
|
||||
if (!$user->use_totp) {
|
||||
return $this->sendLoginResponse($user, $request);
|
||||
}
|
||||
|
||||
Activity::event('auth:checkpoint')->withRequestMetadata()->subject($user)->log();
|
||||
|
||||
$request->session()->put('auth_confirmation_token', [
|
||||
'user_id' => $user->id,
|
||||
'token_value' => $token = Str::random(64),
|
||||
'expires_at' => CarbonImmutable::now()->addMinutes(5),
|
||||
]);
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => [
|
||||
'complete' => false,
|
||||
'confirmation_token' => $token,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
@ -16,12 +16,9 @@ use Illuminate\Http\Request;
|
||||
|
||||
class OAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* OAuthController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private AuthManager $auth,
|
||||
private UserUpdateService $updateService
|
||||
private readonly AuthManager $auth,
|
||||
private readonly UserUpdateService $updateService
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
@ -1,100 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Contracts\Hashing\Hasher;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use App\Exceptions\DisplayException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use App\Http\Requests\Auth\ResetPasswordRequest;
|
||||
|
||||
class ResetPasswordController extends Controller
|
||||
{
|
||||
use ResetsPasswords;
|
||||
|
||||
/**
|
||||
* The URL to redirect users to after password reset.
|
||||
*/
|
||||
public string $redirectTo = '/';
|
||||
|
||||
protected bool $hasTwoFactor = false;
|
||||
|
||||
/**
|
||||
* ResetPasswordController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private Hasher $hasher,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Reset the given user's password.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
*/
|
||||
public function __invoke(ResetPasswordRequest $request): JsonResponse
|
||||
{
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise, we will parse the error and return the response.
|
||||
$response = $this->broker()->reset(
|
||||
$this->credentials($request),
|
||||
function ($user, $password) {
|
||||
$this->resetPassword($user, $password);
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
if ($response === Password::PASSWORD_RESET) {
|
||||
return $this->sendResetResponse();
|
||||
}
|
||||
|
||||
throw new DisplayException(trans($response));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the given user's password. If the user has two-factor authentication enabled on their
|
||||
* account do not automatically log them in. In those cases, send the user back to the login
|
||||
* form with a note telling them their password was changed and to log back in.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\CanResetPassword|\App\Models\User $user
|
||||
* @param string $password
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
protected function resetPassword($user, $password): void
|
||||
{
|
||||
/** @var User $user */
|
||||
$user->password = $this->hasher->make($password);
|
||||
$user->setRememberToken(Str::random(60));
|
||||
$user->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
|
||||
// If the user is not using 2FA log them in, otherwise skip this step and force a
|
||||
// fresh login where they'll be prompted to enter a token.
|
||||
if (!$user->use_totp) {
|
||||
$this->guard()->login($user);
|
||||
}
|
||||
|
||||
$this->hasTwoFactor = $user->use_totp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a successful password reset response back to the callee.
|
||||
*/
|
||||
protected function sendResetResponse(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'redirect_to' => $this->redirectTo,
|
||||
'send_to_login' => $this->hasTwoFactor,
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Base;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\View\Factory as ViewFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
/**
|
||||
* IndexController constructor.
|
||||
*/
|
||||
public function __construct(protected ViewFactory $view) {}
|
||||
|
||||
/**
|
||||
* Returns listing of user's servers.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
return view('templates/base.core');
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Base;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Translation\Translator;
|
||||
use Illuminate\Contracts\Translation\Loader;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class LocaleController extends Controller
|
||||
{
|
||||
protected Loader $loader;
|
||||
|
||||
public function __construct(Translator $translator)
|
||||
{
|
||||
$this->loader = $translator->getLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns translation data given a specific locale and namespace.
|
||||
*/
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$locales = explode(' ', $request->input('locale') ?? '');
|
||||
$namespaces = explode(' ', $request->input('namespace') ?? '');
|
||||
|
||||
$response = [];
|
||||
foreach ($locales as $locale) {
|
||||
$response[$locale] = [];
|
||||
foreach ($namespaces as $namespace) {
|
||||
$response[$locale][$namespace] = $this->i18n(
|
||||
$this->loader->load($locale, str_replace('.', '/', $namespace))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse($response, 200, [
|
||||
// Cache this in the browser for an hour, and allow the browser to use a stale
|
||||
// cache for up to a day after it was created while it fetches an updated set
|
||||
// of translation keys.
|
||||
'Cache-Control' => 'public, max-age=3600, stale-while-revalidate=86400',
|
||||
'ETag' => md5(json_encode($response, JSON_THROW_ON_ERROR)),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert standard Laravel translation keys that look like ":foo"
|
||||
* into key structures that are supported by the front-end i18n
|
||||
* library, like "{{foo}}".
|
||||
*/
|
||||
protected function i18n(array $data): array
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$data[$key] = $this->i18n($value);
|
||||
} else {
|
||||
// Find a Laravel style translation replacement in the string and replace it with
|
||||
// one that the front-end is able to use. This won't always be present, especially
|
||||
// for complex strings or things where we'd never have a backend component anyways.
|
||||
//
|
||||
// For example:
|
||||
// "Hello :name, the :notifications.0.title notification needs :count actions :foo.0.bar."
|
||||
//
|
||||
// Becomes:
|
||||
// "Hello {{name}}, the {{notifications.0.title}} notification needs {{count}} actions {{foo.0.bar}}."
|
||||
$data[$key] = preg_replace('/:([\w.-]+\w)([^\w:]?|$)/m', '{{$1}}$2', $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -2,24 +2,17 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Filament\App\Resources\ServerResource\Pages\ListServers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
|
||||
class RedirectIfAuthenticated
|
||||
readonly class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
* RedirectIfAuthenticated constructor.
|
||||
*/
|
||||
public function __construct(private AuthManager $authManager) {}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next, ?string $guard = null): mixed
|
||||
{
|
||||
if ($this->authManager->guard($guard)->check()) {
|
||||
return redirect(ListServers::getUrl());
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
|
@ -47,8 +47,7 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
|
||||
public static function isInstalled(): bool
|
||||
{
|
||||
// This defaults to true so existing panels count as "installed"
|
||||
return env('APP_INSTALLED', true);
|
||||
return config('app.installed');
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
|
@ -14,7 +14,8 @@ class RequirementsStep
|
||||
|
||||
public static function make(): Step
|
||||
{
|
||||
$correctPhpVersion = version_compare(PHP_VERSION, self::MIN_PHP_VERSION) >= 0;
|
||||
$compare = version_compare(phpversion(), self::MIN_PHP_VERSION);
|
||||
$correctPhpVersion = $compare >= 0;
|
||||
|
||||
$fields = [
|
||||
Section::make('PHP Version')
|
||||
|
@ -85,12 +85,7 @@ class ActivityLog extends Model
|
||||
|
||||
public function actor(): MorphTo
|
||||
{
|
||||
$morph = $this->morphTo();
|
||||
if (method_exists($morph, 'withTrashed')) {
|
||||
return $morph->withTrashed();
|
||||
}
|
||||
|
||||
return $morph;
|
||||
return $this->morphTo()->withTrashed();
|
||||
}
|
||||
|
||||
public function subjects(): HasMany
|
||||
|
@ -60,7 +60,7 @@ class AuditLog extends Model
|
||||
*/
|
||||
public static function instance(string $action, array $metadata, bool $isSystem = false): self
|
||||
{
|
||||
/** @var \Illuminate\Http\Request $request */
|
||||
/** @var ?Request $request */
|
||||
$request = Container::getInstance()->make('request');
|
||||
if ($isSystem || !$request instanceof Request) {
|
||||
$request = null;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use App\Eloquent\BackupQueryBuilder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
@ -81,10 +81,11 @@ class Backup extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a query filtering only non-failed backups for a specific server.
|
||||
* @param \Illuminate\Database\Query\Builder $query
|
||||
* @return BackupQueryBuilder<\Illuminate\Database\Eloquent\Model>
|
||||
*/
|
||||
public function scopeNonFailed(Builder $query): void
|
||||
public function newEloquentBuilder($query): BackupQueryBuilder
|
||||
{
|
||||
$query->whereNull('completed_at')->orWhere('is_successful', true);
|
||||
return new BackupQueryBuilder($query);
|
||||
}
|
||||
}
|
||||
|
@ -130,8 +130,7 @@ class File extends Model
|
||||
public function getRows(): array
|
||||
{
|
||||
try {
|
||||
/** @var DaemonFileRepository $fileRepository */
|
||||
$fileRepository = app(DaemonFileRepository::class)->setServer(self::$server); // @phpstan-ignore-line
|
||||
$fileRepository = (new DaemonFileRepository())->setServer(self::$server);
|
||||
|
||||
if (!is_null(self::$searchTerm)) {
|
||||
$contents = cache()->remember('file_search_' . self::$path . '_' . self::$searchTerm, now()->addMinute(), fn () => $fileRepository->search(self::$searchTerm, self::$path));
|
||||
|
@ -307,8 +307,7 @@ class Node extends Model
|
||||
{
|
||||
return once(function () {
|
||||
try {
|
||||
// @phpstan-ignore-next-line
|
||||
return resolve(DaemonConfigurationRepository::class)
|
||||
return (new DaemonConfigurationRepository())
|
||||
->setNode($this)
|
||||
->getSystemInformation();
|
||||
} catch (Exception $exception) {
|
||||
|
@ -342,6 +342,9 @@ class Server extends Model
|
||||
return $this->hasOne(ServerTransfer::class)->whereNull('successful')->orderByDesc('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<Backup, $this>
|
||||
*/
|
||||
public function backups(): HasMany
|
||||
{
|
||||
return $this->hasMany(Backup::class);
|
||||
@ -487,7 +490,7 @@ class Server extends Model
|
||||
public function condition(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->isSuspended() ? ServerState::Suspended : $this->status?->value ?? $this->retrieveStatus(),
|
||||
get: fn () => $this->isSuspended() ? ServerState::Suspended : $this->status->value ?? $this->retrieveStatus(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@ use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use App\Models\Traits\HasAccessTokens;
|
||||
use Illuminate\Auth\Passwords\CanResetPassword;
|
||||
use App\Traits\Helpers\AvailableLanguages;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\Access\Authorizable;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
@ -30,6 +29,7 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
||||
use App\Notifications\SendPasswordReset as ResetPasswordNotification;
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Database\Eloquent\Model as IlluminateModel;
|
||||
use ResourceBundle;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
/**
|
||||
@ -89,7 +89,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
{
|
||||
use Authenticatable;
|
||||
use Authorizable {can as protected canned; }
|
||||
use AvailableLanguages;
|
||||
use CanResetPassword;
|
||||
use HasAccessTokens;
|
||||
use HasRoles;
|
||||
@ -179,9 +178,8 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (self $user) {
|
||||
$user->uuid = Str::uuid()->toString();
|
||||
|
||||
$user->timezone = env('APP_TIMEZONE', 'UTC');
|
||||
$user->uuid ??= Str::uuid()->toString();
|
||||
$user->timezone ??= config('app.timezone');
|
||||
|
||||
return true;
|
||||
});
|
||||
@ -206,8 +204,8 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
{
|
||||
$rules = parent::getRules();
|
||||
|
||||
$rules['language'][] = new In(array_keys((new self())->getAvailableLanguages()));
|
||||
$rules['timezone'][] = new In(array_values(DateTimeZone::listIdentifiers()));
|
||||
$rules['language'][] = new In(array_values(array_filter(ResourceBundle::getLocales(''), fn ($lang) => preg_match('/^[a-z]{2}$/', $lang))));
|
||||
$rules['timezone'][] = new In(DateTimeZone::listIdentifiers());
|
||||
$rules['username'][] = new Username();
|
||||
|
||||
return $rules;
|
||||
@ -257,6 +255,8 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
|
||||
/**
|
||||
* Returns all servers that a user owns.
|
||||
*
|
||||
* @return HasMany<Server, $this>
|
||||
*/
|
||||
public function servers(): HasMany
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
||||
class ForbiddenGlobalFunctionsRule implements Rule
|
||||
{
|
||||
@ -28,7 +29,10 @@ class ForbiddenGlobalFunctionsRule implements Rule
|
||||
$functionName = (string) $node->name;
|
||||
if (in_array($functionName, $this->forbiddenFunctions, true)) {
|
||||
return [
|
||||
sprintf('Usage of global function "%s" is forbidden.', $functionName),
|
||||
RuleErrorBuilder::message(sprintf(
|
||||
'Usage of global function "%s" is forbidden.',
|
||||
$functionName,
|
||||
))->identifier('myCustomRules.forbiddenGlobalFunctions')->build(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Services\Activity;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Throwable;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Models\ActivityLog;
|
||||
@ -141,9 +142,8 @@ class ActivityLogService
|
||||
|
||||
try {
|
||||
return $this->save();
|
||||
} catch (\Throwable|\Exception $exception) {
|
||||
} catch (Throwable $exception) {
|
||||
if (config('app.env') !== 'production') {
|
||||
/* @noinspection PhpUnhandledExceptionInspection */
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
@ -216,9 +216,7 @@ class ActivityLogService
|
||||
if ($actor = $this->targetable->actor()) {
|
||||
$this->actor($actor);
|
||||
} elseif ($user = $this->manager->guard()->user()) {
|
||||
if ($user instanceof Model) {
|
||||
$this->actor($user);
|
||||
}
|
||||
$this->actor($user);
|
||||
}
|
||||
|
||||
return $this->activity;
|
||||
|
@ -14,7 +14,7 @@ use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||||
|
||||
class InitiateBackupService
|
||||
{
|
||||
private ?array $ignoredFiles;
|
||||
private array $ignoredFiles;
|
||||
|
||||
private bool $isLocked = false;
|
||||
|
||||
@ -22,10 +22,10 @@ class InitiateBackupService
|
||||
* InitiateBackupService constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private ConnectionInterface $connection,
|
||||
private DaemonBackupRepository $daemonBackupRepository,
|
||||
private DeleteBackupService $deleteBackupService,
|
||||
private BackupManager $backupManager
|
||||
private readonly ConnectionInterface $connection,
|
||||
private readonly DaemonBackupRepository $daemonBackupRepository,
|
||||
private readonly DeleteBackupService $deleteBackupService,
|
||||
private readonly BackupManager $backupManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
@ -8,18 +8,10 @@ use App\Models\Database;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Exceptions\Service\Database\NoSuitableDatabaseHostException;
|
||||
|
||||
class DeployServerDatabaseService
|
||||
readonly class DeployServerDatabaseService
|
||||
{
|
||||
/**
|
||||
* DeployServerDatabaseService constructor.
|
||||
*/
|
||||
public function __construct(private DatabaseManagementService $managementService) {}
|
||||
|
||||
/**
|
||||
* @throws \Throwable
|
||||
* @throws \App\Exceptions\Service\Database\TooManyDatabasesException
|
||||
* @throws \App\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
|
||||
*/
|
||||
public function handle(Server $server, array $data): Database
|
||||
{
|
||||
Assert::notEmpty($data['database'] ?? null);
|
||||
|
40
app/Services/Helpers/LanguageService.php
Normal file
40
app/Services/Helpers/LanguageService.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Helpers;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Locale;
|
||||
|
||||
class LanguageService
|
||||
{
|
||||
public const TRANSLATED_COMPLETELY = [
|
||||
'ar',
|
||||
'cz',
|
||||
'da',
|
||||
'de',
|
||||
'dk',
|
||||
'en',
|
||||
'es',
|
||||
'fi',
|
||||
'ja',
|
||||
'nl',
|
||||
'pl',
|
||||
'sk',
|
||||
'ru',
|
||||
'tr',
|
||||
];
|
||||
|
||||
public function isLanguageTranslated(string $countryCode = 'en'): bool
|
||||
{
|
||||
return in_array($countryCode, self::TRANSLATED_COMPLETELY, true);
|
||||
}
|
||||
|
||||
public function getAvailableLanguages(string $path = 'lang'): array
|
||||
{
|
||||
return collect(File::directories(base_path($path)))->mapWithKeys(function ($path) {
|
||||
$code = basename($path);
|
||||
|
||||
return [$code => title_case(Locale::getDisplayName($code, $code))];
|
||||
})->toArray();
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Services\Nodes;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use DateTimeImmutable;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\Node;
|
||||
use App\Models\User;
|
||||
@ -18,7 +19,7 @@ class NodeJWTService
|
||||
|
||||
private ?User $user = null;
|
||||
|
||||
private ?\DateTimeImmutable $expiresAt;
|
||||
private DateTimeImmutable $expiresAt;
|
||||
|
||||
private ?string $subject = null;
|
||||
|
||||
@ -73,9 +74,7 @@ class NodeJWTService
|
||||
->issuedAt(CarbonImmutable::now())
|
||||
->canOnlyBeUsedAfter(CarbonImmutable::now()->subMinutes(5));
|
||||
|
||||
if ($this->expiresAt) {
|
||||
$builder = $builder->expiresAt($this->expiresAt);
|
||||
}
|
||||
$builder = $builder->expiresAt($this->expiresAt);
|
||||
|
||||
if (!empty($this->subject)) {
|
||||
$builder = $builder->relatedTo($this->subject)->withHeader('sub', $this->subject);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services\Schedules;
|
||||
|
||||
use App\Models\Task;
|
||||
use Exception;
|
||||
use App\Models\Schedule;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
@ -12,18 +13,14 @@ use App\Repositories\Daemon\DaemonServerRepository;
|
||||
|
||||
class ProcessScheduleService
|
||||
{
|
||||
/**
|
||||
* ProcessScheduleService constructor.
|
||||
*/
|
||||
public function __construct(private ConnectionInterface $connection, private Dispatcher $dispatcher, private DaemonServerRepository $serverRepository) {}
|
||||
|
||||
/**
|
||||
* Process a schedule and push the first task onto the queue worker.
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function handle(Schedule $schedule, bool $now = false): void
|
||||
{
|
||||
/** @var ?Task $task */
|
||||
$task = $schedule->tasks()->orderBy('sequence_id')->first();
|
||||
|
||||
if (!$task) {
|
||||
|
@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits\Helpers;
|
||||
|
||||
use Locale;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
trait AvailableLanguages
|
||||
{
|
||||
private ?Filesystem $filesystem = null;
|
||||
|
||||
public const TRANSLATED = [
|
||||
'ar',
|
||||
'cz',
|
||||
'da',
|
||||
'de',
|
||||
'dk',
|
||||
'en',
|
||||
'es',
|
||||
'fi',
|
||||
'ja',
|
||||
'nl',
|
||||
'pl',
|
||||
'sk',
|
||||
'ru',
|
||||
'tr',
|
||||
];
|
||||
|
||||
/**
|
||||
* Return all the available languages on the Panel based on those
|
||||
* that are present in the language folder.
|
||||
*/
|
||||
public function getAvailableLanguages(): array
|
||||
{
|
||||
return collect($this->getFilesystemInstance()->directories(base_path('lang')))->mapWithKeys(function ($path) {
|
||||
$code = basename($path);
|
||||
|
||||
$value = Locale::getDisplayName($code, $code);
|
||||
|
||||
return [$code => title_case($value)];
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
public function isLanguageTranslated(string $countryCode = 'en'): bool
|
||||
{
|
||||
return in_array($countryCode, self::TRANSLATED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the filesystem for getting a folder listing.
|
||||
*/
|
||||
private function getFilesystemInstance(): Filesystem
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->filesystem = $this->filesystem ?: app()->make(Filesystem::class);
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ abstract class BaseTransformer extends TransformerAbstract
|
||||
/**
|
||||
* BaseTransformer constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
final public function __construct()
|
||||
{
|
||||
// Transformers allow for dependency injection on the handle method.
|
||||
if (method_exists($this, 'handle')) {
|
||||
@ -40,7 +40,7 @@ abstract class BaseTransformer extends TransformerAbstract
|
||||
/**
|
||||
* Sets the request on the instance.
|
||||
*/
|
||||
public function setRequest(Request $request): self
|
||||
public function setRequest(Request $request): static
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
@ -49,13 +49,10 @@ abstract class BaseTransformer extends TransformerAbstract
|
||||
|
||||
/**
|
||||
* Returns a new transformer instance with the request set on the instance.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function fromRequest(Request $request): self
|
||||
public static function fromRequest(Request $request): static
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return app(static::class)->setRequest($request);
|
||||
return (new static())->setRequest($request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,8 +49,7 @@ if (!function_exists('join_paths')) {
|
||||
if (!function_exists('resolve_path')) {
|
||||
function resolve_path(string $path): string
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
$parts = array_filter(explode('/', $path), 'strlen');
|
||||
$parts = array_filter(explode('/', $path), fn (string $p) => strlen($p) > 0);
|
||||
|
||||
$absolutes = [];
|
||||
foreach ($parts as $part) {
|
||||
|
@ -49,7 +49,7 @@
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^3.0",
|
||||
"fakerphp/faker": "^1.23.1",
|
||||
"larastan/larastan": "^2.9.6",
|
||||
"larastan/larastan": "^3.0",
|
||||
"laravel/pint": "^1.15.3",
|
||||
"laravel/sail": "^1.29.1",
|
||||
"mockery/mockery": "^1.6.11",
|
||||
|
702
composer.lock
generated
702
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,8 @@ return [
|
||||
|
||||
'timezone' => 'UTC',
|
||||
|
||||
'installed' => env('APP_INSTALLED', true),
|
||||
|
||||
'exceptions' => [
|
||||
'report_all' => env('APP_REPORT_ALL_EXCEPTIONS', false),
|
||||
],
|
||||
|
15
phpstan.neon
15
phpstan.neon
@ -12,6 +12,15 @@ parameters:
|
||||
level: 6
|
||||
|
||||
ignoreErrors:
|
||||
- '#no value type specified in iterable#'
|
||||
- '#Unable to resolve the template type#'
|
||||
- '#does not specify its types#'
|
||||
- identifier: argument.templateType
|
||||
- identifier: missingType.generics
|
||||
- identifier: missingType.iterableValue
|
||||
- identifier: property.notFound
|
||||
|
||||
# We are getting and setting environment variables directly
|
||||
-
|
||||
identifier: larastan.noEnvCallsOutsideOfConfig
|
||||
paths:
|
||||
- app/Console/Commands/Environment/*.php
|
||||
- app/Extensions/OAuth/Providers/*.php
|
||||
- app/Filament/Admin/Pages/Settings.php
|
||||
|
@ -3,5 +3,7 @@
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Auth;
|
||||
|
||||
Route::redirect('/login', '/login')->name('auth.login');
|
||||
|
||||
Route::get('/oauth/redirect/{driver}', [Auth\OAuthController::class, 'redirect'])->name('auth.oauth.redirect');
|
||||
Route::get('/oauth/callback/{driver}', [Auth\OAuthController::class, 'callback'])->name('auth.oauth.callback')->withoutMiddleware('guest');
|
||||
|
Loading…
x
Reference in New Issue
Block a user