mirror of
https://github.com/pelican-dev/panel.git
synced 2026-05-04 18:00:48 +03:00
Compare commits
41 Commits
v1.0.0-bet
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ac03160c3 | ||
|
|
fe4668a517 | ||
|
|
6125b07afa | ||
|
|
9717aa4b5f | ||
|
|
9491322d8c | ||
|
|
8ed6bb4d8b | ||
|
|
a787af7a06 | ||
|
|
d9016702d6 | ||
|
|
d565441b6a | ||
|
|
cb522b24ef | ||
|
|
b85b17f080 | ||
|
|
47bd7289b1 | ||
|
|
a9b76a0f51 | ||
|
|
8eebb82eba | ||
|
|
b3501be6ec | ||
|
|
ac67656d82 | ||
|
|
968239beb3 | ||
|
|
7514206186 | ||
|
|
1a8321c937 | ||
|
|
340ae8099b | ||
|
|
9d02aeb130 | ||
|
|
cf57c28c40 | ||
|
|
382dcb3868 | ||
|
|
f793b49a81 | ||
|
|
41ddae1ba0 | ||
|
|
e717e20996 | ||
|
|
b5145b016b | ||
|
|
95a8f72058 | ||
|
|
19548338ee | ||
|
|
a8356fc5d2 | ||
|
|
7a447b04d5 | ||
|
|
45699e1614 | ||
|
|
cde3546889 | ||
|
|
3f9c1dbc3c | ||
|
|
bc2df22d78 | ||
|
|
1a3dc5c743 | ||
|
|
fdd1b3798c | ||
|
|
288cbee32f | ||
|
|
a70a060350 | ||
|
|
590569a131 | ||
|
|
7acc8782bb |
6
.github/docker/entrypoint.sh
vendored
6
.github/docker/entrypoint.sh
vendored
@@ -52,11 +52,11 @@ crond -L /var/log/crond -l 5
|
||||
export SUPERVISORD_CADDY=false
|
||||
|
||||
## disable caddy if SKIP_CADDY is set
|
||||
if [[ -z $SKIP_CADDY ]]; then
|
||||
if [[ "${SKIP_CADDY:-}" == "true" ]]; then
|
||||
echo "Starting PHP-FPM only"
|
||||
else
|
||||
echo "Starting PHP-FPM and Caddy"
|
||||
export SUPERVISORD_CADDY=true
|
||||
else
|
||||
echo "Starting PHP-FPM only"
|
||||
fi
|
||||
|
||||
chown -R www-data:www-data /pelican-data/.env /pelican-data/database
|
||||
|
||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -87,7 +87,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3]
|
||||
database: ["mariadb:10.3", "mariadb:10.11", "mariadb:11.4"]
|
||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
|
||||
@@ -26,8 +26,8 @@ class InfoCommand extends Command
|
||||
{
|
||||
$this->output->title('Version Information');
|
||||
$this->table([], [
|
||||
['Panel Version', $this->versionService->versionData()['version']],
|
||||
['Latest Version', $this->versionService->getPanel()],
|
||||
['Panel Version', $this->versionService->currentPanelVersion()],
|
||||
['Latest Version', $this->versionService->latestPanelVersion()],
|
||||
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||
], 'compact');
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||
use App\Console\Commands\Schedule\ProcessRunnableCommand;
|
||||
use App\Jobs\NodeStatistics;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Webhook;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Database\Console\PruneCommand;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
@@ -48,5 +49,9 @@ class Kernel extends ConsoleKernel
|
||||
if (config('activity.prune_days')) {
|
||||
$schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily();
|
||||
}
|
||||
|
||||
if (config('panel.webhook.prune_days')) {
|
||||
$schedule->command(PruneCommand::class, ['--model' => [Webhook::class]])->daily();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class FailedCaptcha extends Event
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public string $ip, public string $domain)
|
||||
public function __construct(public string $ip, public ?string $message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class AccountNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class AutoDeploymentException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -10,7 +10,6 @@ use Illuminate\Http\Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Container\Container;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
@@ -67,9 +66,6 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders());
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
app(AlertsMessageBag::class)->danger($this->getMessage())->flash();
|
||||
|
||||
return redirect()->back()->withInput();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@ use GuzzleHttp\Exception\GuzzleException;
|
||||
use App\Exceptions\DisplayException;
|
||||
use Illuminate\Support\Facades\Context;
|
||||
|
||||
/**
|
||||
* @method \GuzzleHttp\Exception\GuzzleException getPrevious()
|
||||
*/
|
||||
class DaemonConnectionException extends DisplayException
|
||||
{
|
||||
private int $statusCode = Response::HTTP_GATEWAY_TIMEOUT;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Http\Server;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class FileTypeNotEditableException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Repository\Daemon;
|
||||
|
||||
use App\Exceptions\Repository\RepositoryException;
|
||||
|
||||
class InvalidPowerSignalException extends RepositoryException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Repository;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class RepositoryException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Allocation;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class AllocationDoesNotBelongToServerException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Egg;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class BadJsonFormatException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Egg;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class NoParentConfigurationFoundException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Schedule\Task;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class TaskIntervalTooLongException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Server;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class RequiredVariableMissingException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Transformer;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class InvalidTransformerLevelException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class Theme extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'extensions.themes';
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Themes;
|
||||
|
||||
class Theme
|
||||
{
|
||||
public function js(string $path): string
|
||||
{
|
||||
return sprintf('<script src="%s"></script>' . PHP_EOL, $this->getUrl($path));
|
||||
}
|
||||
|
||||
public function css(string $path): string
|
||||
{
|
||||
return sprintf('<link media="all" type="text/css" rel="stylesheet" href="%s"/>' . PHP_EOL, $this->getUrl($path));
|
||||
}
|
||||
|
||||
protected function getUrl(string $path): string
|
||||
{
|
||||
return '/themes/panel/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use App\Services\Activity\ActivityLogBatchService;
|
||||
|
||||
class LogBatch extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return ActivityLogBatchService::class;
|
||||
}
|
||||
}
|
||||
36
app/Filament/Pages/Auth/Login.php
Normal file
36
app/Filament/Pages/Auth/Login.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Auth;
|
||||
|
||||
use Coderflex\FilamentTurnstile\Forms\Components\Turnstile;
|
||||
use Filament\Pages\Auth\Login as BaseLogin;
|
||||
|
||||
class Login extends BaseLogin
|
||||
{
|
||||
protected function getForms(): array
|
||||
{
|
||||
return [
|
||||
'form' => $this->form(
|
||||
$this->makeForm()
|
||||
->schema([
|
||||
$this->getEmailFormComponent(),
|
||||
$this->getPasswordFormComponent(),
|
||||
$this->getRememberFormComponent(),
|
||||
Turnstile::make('captcha')
|
||||
->hidden(!config('turnstile.turnstile_enabled'))
|
||||
->validationMessages([
|
||||
'required' => config('turnstile.error_messages.turnstile_check_message'),
|
||||
]),
|
||||
])
|
||||
->statePath('data'),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function throwFailureValidationException(): never
|
||||
{
|
||||
$this->dispatch('reset-captcha');
|
||||
|
||||
parent::throwFailureValidationException();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Filament\Resources\NodeResource\Pages\CreateNode;
|
||||
use App\Filament\Resources\NodeResource\Pages\ListNodes;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Node;
|
||||
@@ -39,8 +40,8 @@ class Dashboard extends Page
|
||||
{
|
||||
return [
|
||||
'inDevelopment' => config('app.version') === 'canary',
|
||||
'version' => $this->softwareVersionService->versionData()['version'],
|
||||
'latestVersion' => $this->softwareVersionService->getPanel(),
|
||||
'version' => $this->softwareVersionService->currentPanelVersion(),
|
||||
'latestVersion' => $this->softwareVersionService->latestPanelVersion(),
|
||||
'isLatest' => $this->softwareVersionService->isLatestPanel(),
|
||||
'eggsCount' => Egg::query()->count(),
|
||||
'nodesList' => ListNodes::getUrl(),
|
||||
@@ -65,13 +66,13 @@ class Dashboard extends Page
|
||||
CreateAction::make()
|
||||
->label(trans('dashboard/index.sections.intro-first-node.button_label'))
|
||||
->icon('tabler-server-2')
|
||||
->url(route('filament.admin.resources.nodes.create')),
|
||||
->url(CreateNode::getUrl()),
|
||||
],
|
||||
'supportActions' => [
|
||||
CreateAction::make()
|
||||
->label(trans('dashboard/index.sections.intro-support.button_donate'))
|
||||
->icon('tabler-cash')
|
||||
->url($this->softwareVersionService->getDonations(), true)
|
||||
->url('https://pelican.dev/donate', true)
|
||||
->color('success'),
|
||||
],
|
||||
'helpActions' => [
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
namespace App\Filament\Pages\Installer;
|
||||
|
||||
use App\Filament\Pages\Dashboard;
|
||||
use App\Filament\Pages\Installer\Steps\AdminUserStep;
|
||||
use App\Filament\Pages\Installer\Steps\CompletedStep;
|
||||
use App\Filament\Pages\Installer\Steps\CacheStep;
|
||||
use App\Filament\Pages\Installer\Steps\DatabaseStep;
|
||||
use App\Filament\Pages\Installer\Steps\EnvironmentStep;
|
||||
use App\Filament\Pages\Installer\Steps\RedisStep;
|
||||
use App\Filament\Pages\Installer\Steps\QueueStep;
|
||||
use App\Filament\Pages\Installer\Steps\RequirementsStep;
|
||||
use App\Filament\Pages\Installer\Steps\SessionStep;
|
||||
use App\Models\User;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use App\Traits\CheckMigrationsTrait;
|
||||
@@ -19,7 +19,6 @@ use Filament\Forms\Components\Wizard;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\SimplePage;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
@@ -43,8 +42,6 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
|
||||
protected static string $view = 'filament.pages.installer';
|
||||
|
||||
private User $user;
|
||||
|
||||
public function getMaxWidth(): MaxWidth|string
|
||||
{
|
||||
return MaxWidth::SevenExtraLarge;
|
||||
@@ -70,10 +67,9 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
RequirementsStep::make(),
|
||||
EnvironmentStep::make($this),
|
||||
DatabaseStep::make($this),
|
||||
RedisStep::make($this)
|
||||
->hidden(fn (Get $get) => $get('env_general.SESSION_DRIVER') != 'redis' && $get('env_general.QUEUE_CONNECTION') != 'redis' && $get('env_general.CACHE_STORE') != 'redis'),
|
||||
AdminUserStep::make($this),
|
||||
CompletedStep::make(),
|
||||
CacheStep::make($this),
|
||||
QueueStep::make($this),
|
||||
SessionStep::make(),
|
||||
])
|
||||
->persistStepInQueryString()
|
||||
->nextAction(fn (Action $action) => $action->keyBindings('enter'))
|
||||
@@ -95,14 +91,17 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
return 'data';
|
||||
}
|
||||
|
||||
public function submit(): Redirector|RedirectResponse
|
||||
public function submit(UserCreationService $userCreationService): Redirector|RedirectResponse
|
||||
{
|
||||
// Disable installer
|
||||
$this->writeToEnvironment(['APP_INSTALLED' => 'true']);
|
||||
|
||||
// Login user
|
||||
$this->user ??= User::all()->filter(fn ($user) => $user->isRootAdmin())->first();
|
||||
auth()->guard()->login($this->user, true);
|
||||
// Create admin user & login
|
||||
$user = $this->createAdminUser($userCreationService);
|
||||
auth()->guard()->login($user, true);
|
||||
|
||||
// Write session data at the very end to avoid "page expired" errors
|
||||
$this->writeToEnv('env_session');
|
||||
|
||||
// Redirect to admin panel
|
||||
return redirect(Dashboard::getUrl());
|
||||
@@ -112,6 +111,7 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
{
|
||||
try {
|
||||
$variables = array_get($this->data, $key);
|
||||
$variables = array_filter($variables); // Filter array to remove NULL values
|
||||
$this->writeToEnvironment($variables);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
@@ -161,12 +161,13 @@ class PanelInstaller extends SimplePage implements HasForms
|
||||
}
|
||||
}
|
||||
|
||||
public function createAdminUser(UserCreationService $userCreationService): void
|
||||
public function createAdminUser(UserCreationService $userCreationService): User
|
||||
{
|
||||
try {
|
||||
$userData = array_get($this->data, 'user');
|
||||
$userData['root_admin'] = true;
|
||||
$this->user = $userCreationService->handle($userData);
|
||||
|
||||
return $userCreationService->handle($userData);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
|
||||
class AdminUserStep
|
||||
{
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('user')
|
||||
->label('Admin User')
|
||||
->schema([
|
||||
TextInput::make('user.email')
|
||||
->label('Admin E-Mail')
|
||||
->required()
|
||||
->email()
|
||||
->placeholder('admin@example.com'),
|
||||
TextInput::make('user.username')
|
||||
->label('Admin Username')
|
||||
->required()
|
||||
->placeholder('admin'),
|
||||
TextInput::make('user.password')
|
||||
->label('Admin Password')
|
||||
->required()
|
||||
->password()
|
||||
->revealable(),
|
||||
])
|
||||
->afterValidation(fn (UserCreationService $service) => $installer->createAdminUser($service));
|
||||
}
|
||||
}
|
||||
123
app/Filament/Pages/Installer/Steps/CacheStep.php
Normal file
123
app/Filament/Pages/Installer/Steps/CacheStep.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Redis\RedisManager;
|
||||
|
||||
class CacheStep
|
||||
{
|
||||
public const CACHE_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('cache')
|
||||
->label('Cache')
|
||||
->columns()
|
||||
->schema([
|
||||
ToggleButtons::make('env_cache.CACHE_STORE')
|
||||
->label('Cache Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for caching. We recommend "Filesystem".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::CACHE_DRIVERS)
|
||||
->default(config('cache.default'))
|
||||
->columnSpanFull()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
if ($state !== 'redis') {
|
||||
$set('env_cache.REDIS_HOST', null);
|
||||
$set('env_cache.REDIS_PORT', null);
|
||||
$set('env_cache.REDIS_USERNAME', null);
|
||||
$set('env_cache.REDIS_PASSWORD', null);
|
||||
} else {
|
||||
$set('env_cache.REDIS_HOST', $get('env_cache.REDIS_HOST') ?? '127.0.0.1');
|
||||
$set('env_cache.REDIS_PORT', $get('env_cache.REDIS_PORT') ?? '6379');
|
||||
$set('env_cache.REDIS_USERNAME', null);
|
||||
}
|
||||
}),
|
||||
TextInput::make('env_cache.REDIS_HOST')
|
||||
->label('Redis Host')
|
||||
->placeholder('127.0.0.1')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The host of your redis server. Make sure it is reachable.')
|
||||
->required(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis')
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.host') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
TextInput::make('env_cache.REDIS_PORT')
|
||||
->label('Redis Port')
|
||||
->placeholder('6379')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The port of your redis server.')
|
||||
->required(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis')
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.port') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
TextInput::make('env_cache.REDIS_USERNAME')
|
||||
->label('Redis Username')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The name of your redis user. Can be empty')
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.username') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
TextInput::make('env_cache.REDIS_PASSWORD')
|
||||
->label('Redis Password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The password for your redis user. Can be empty.')
|
||||
->password()
|
||||
->revealable()
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.password') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
])
|
||||
->afterValidation(function (Get $get, Application $app) use ($installer) {
|
||||
$driver = $get('env_cache.CACHE_STORE');
|
||||
|
||||
if (!self::testConnection($app, $driver, $get('env_cache.REDIS_HOST'), $get('env_cache.REDIS_PORT'), $get('env_cache.REDIS_USERNAME'), $get('env_cache.REDIS_PASSWORD'))) {
|
||||
throw new Halt('Redis connection failed');
|
||||
}
|
||||
|
||||
$installer->writeToEnv('env_cache');
|
||||
});
|
||||
}
|
||||
|
||||
private static function testConnection(Application $app, string $driver, ?string $host, null|string|int $port, ?string $username, ?string $password): bool
|
||||
{
|
||||
if ($driver !== 'redis') {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$redis = new RedisManager($app, 'predis', [
|
||||
'default' => [
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
],
|
||||
]);
|
||||
|
||||
$redis->connection()->command('ping');
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Redis connection failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
class CompletedStep
|
||||
{
|
||||
public static function make(): Step
|
||||
{
|
||||
return Step::make('complete')
|
||||
->label('Setup complete')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString('The setup is nearly complete!<br>As last step you need to create a new cronjob that runs every minute to process specific tasks, such as session cleanup and scheduled tasks, and also create a queue worker.')),
|
||||
TextInput::make('crontab')
|
||||
->label(new HtmlString('Run the following command to setup your crontab. Note that <code>www-data</code> is your webserver user. On some systems this username might be different!'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('(crontab -l -u www-data 2>/dev/null; echo "* * * * * php ' . base_path() . '/artisan schedule:run >> /dev/null 2>&1") | crontab -u www-data -'),
|
||||
TextInput::make('queueService')
|
||||
->label(new HtmlString('To setup the queue worker service you simply have to run the following command.'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('sudo php ' . base_path() . '/artisan p:environment:queue-service'),
|
||||
Placeholder::make('')
|
||||
->content('After you finished these two last tasks you can click on "Finish" and use your new panel! Have fun!'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -5,62 +5,92 @@ namespace App\Filament\Pages\Installer\Steps;
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DatabaseStep
|
||||
{
|
||||
public const DATABASE_DRIVERS = [
|
||||
'sqlite' => 'SQLite',
|
||||
'mariadb' => 'MariaDB',
|
||||
'mysql' => 'MySQL',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('database')
|
||||
->label('Database')
|
||||
->columns()
|
||||
->schema([
|
||||
TextInput::make('env_database.DB_DATABASE')
|
||||
->label(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite' ? 'Database Path' : 'Database Name')
|
||||
->columnSpanFull()
|
||||
ToggleButtons::make('env_database.DB_CONNECTION')
|
||||
->label('Database Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite' ? 'The path of your .sqlite file relative to the database folder.' : 'The name of the panel database.')
|
||||
->hintIconTooltip('The driver used for the panel database. We recommend "SQLite".')
|
||||
->required()
|
||||
->default(fn (Get $get) => env('DB_DATABASE', $get('env_general.DB_CONNECTION') === 'sqlite' ? 'database.sqlite' : 'panel')),
|
||||
->inline()
|
||||
->options(self::DATABASE_DRIVERS)
|
||||
->default(config('database.default'))
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
$set('env_database.DB_DATABASE', $state === 'sqlite' ? 'database.sqlite' : 'panel');
|
||||
|
||||
if ($state === 'sqlite') {
|
||||
$set('env_database.DB_HOST', null);
|
||||
$set('env_database.DB_PORT', null);
|
||||
$set('env_database.DB_USERNAME', null);
|
||||
$set('env_database.DB_PASSWORD', null);
|
||||
} else {
|
||||
$set('env_database.DB_HOST', $get('env_database.DB_HOST') ?? '127.0.0.1');
|
||||
$set('env_database.DB_PORT', $get('env_database.DB_PORT') ?? '3306');
|
||||
$set('env_database.DB_USERNAME', $get('env_database.DB_USERNAME') ?? 'pelican');
|
||||
}
|
||||
}),
|
||||
TextInput::make('env_database.DB_DATABASE')
|
||||
->label(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'Database Path' : 'Database Name')
|
||||
->placeholder(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'database.sqlite' : 'panel')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'The path of your .sqlite file relative to the database folder.' : 'The name of the panel database.')
|
||||
->required()
|
||||
->default('database.sqlite'),
|
||||
TextInput::make('env_database.DB_HOST')
|
||||
->label('Database Host')
|
||||
->placeholder('127.0.0.1')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The host of your database. Make sure it is reachable.')
|
||||
->required(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite')
|
||||
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_HOST', '127.0.0.1') : null)
|
||||
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
|
||||
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
TextInput::make('env_database.DB_PORT')
|
||||
->label('Database Port')
|
||||
->placeholder('3306')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The port of your database.')
|
||||
->required(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite')
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_PORT', 3306) : null)
|
||||
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
|
||||
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
TextInput::make('env_database.DB_USERNAME')
|
||||
->label('Database Username')
|
||||
->placeholder('pelican')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The name of your database user.')
|
||||
->required(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite')
|
||||
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_USERNAME', 'pelican') : null)
|
||||
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
|
||||
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
TextInput::make('env_database.DB_PASSWORD')
|
||||
->label('Database Password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The password of your database user. Can be empty.')
|
||||
->password()
|
||||
->revealable()
|
||||
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_PASSWORD') : null)
|
||||
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
])
|
||||
->afterValidation(function (Get $get) use ($installer) {
|
||||
$driver = $get('env_general.DB_CONNECTION');
|
||||
$driver = $get('env_database.DB_CONNECTION');
|
||||
|
||||
if (!self::testConnection($driver, $get('env_database.DB_HOST'), $get('env_database.DB_PORT'), $get('env_database.DB_DATABASE'), $get('env_database.DB_USERNAME'), $get('env_database.DB_PASSWORD'))) {
|
||||
throw new Halt('Database connection failed');
|
||||
|
||||
@@ -3,40 +3,12 @@
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Set;
|
||||
|
||||
class EnvironmentStep
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
|
||||
public const CACHE_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public const SESSION_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'database' => 'Database',
|
||||
'cookie' => 'Cookie',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public const QUEUE_DRIVERS = [
|
||||
'database' => 'Database',
|
||||
'sync' => 'Sync',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public const DATABASE_DRIVERS = [
|
||||
'sqlite' => 'SQLite',
|
||||
'mariadb' => 'MariaDB',
|
||||
'mysql' => 'MySQL',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('environment')
|
||||
@@ -54,44 +26,26 @@ class EnvironmentStep
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('This will be the URL you access your Panel from.')
|
||||
->required()
|
||||
->default(url(''))
|
||||
->live()
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('env_general.SESSION_SECURE_COOKIE', str_starts_with($state, 'https://') ? 'true' : 'false')),
|
||||
TextInput::make('env_general.SESSION_SECURE_COOKIE')
|
||||
->hidden()
|
||||
->default(str_starts_with(url(''), 'https://') ? 'true' : 'false'),
|
||||
ToggleButtons::make('env_general.CACHE_STORE')
|
||||
->label('Cache Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for caching. We recommend "Filesystem".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::CACHE_DRIVERS)
|
||||
->default(config('cache.default', 'file')),
|
||||
ToggleButtons::make('env_general.SESSION_DRIVER')
|
||||
->label('Session Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for storing sessions. We recommend "Filesystem" or "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::SESSION_DRIVERS)
|
||||
->default(config('session.driver', 'file')),
|
||||
ToggleButtons::make('env_general.QUEUE_CONNECTION')
|
||||
->label('Queue Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for handling queues. We recommend "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::QUEUE_DRIVERS)
|
||||
->default(config('queue.default', 'database')),
|
||||
ToggleButtons::make('env_general.DB_CONNECTION')
|
||||
->label('Database Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for the panel database. We recommend "SQLite".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::DATABASE_DRIVERS)
|
||||
->default(config('database.default', 'sqlite')),
|
||||
->default(url('')),
|
||||
Fieldset::make('adminuser')
|
||||
->label('Admin User')
|
||||
->columns(3)
|
||||
->schema([
|
||||
TextInput::make('user.email')
|
||||
->label('E-Mail')
|
||||
->required()
|
||||
->email()
|
||||
->placeholder('admin@example.com'),
|
||||
TextInput::make('user.username')
|
||||
->label('Username')
|
||||
->required()
|
||||
->placeholder('admin'),
|
||||
TextInput::make('user.password')
|
||||
->label('Password')
|
||||
->required()
|
||||
->password()
|
||||
->revealable(),
|
||||
]),
|
||||
])
|
||||
->afterValidation(fn () => $installer->writeToEnv('env_general'));
|
||||
}
|
||||
|
||||
64
app/Filament/Pages/Installer/Steps/QueueStep.php
Normal file
64
app/Filament/Pages/Installer/Steps/QueueStep.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
class QueueStep
|
||||
{
|
||||
public const QUEUE_DRIVERS = [
|
||||
'database' => 'Database',
|
||||
'redis' => 'Redis',
|
||||
'sync' => 'Sync',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('queue')
|
||||
->label('Queue')
|
||||
->columns()
|
||||
->schema([
|
||||
ToggleButtons::make('env_queue.QUEUE_CONNECTION')
|
||||
->label('Queue Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for handling queues. We recommend "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::QUEUE_DRIVERS)
|
||||
->disableOptionWhen(fn ($value, Get $get) => $value === 'redis' && $get('env_cache.CACHE_STORE') !== 'redis')
|
||||
->default(config('queue.default')),
|
||||
Toggle::make('done')
|
||||
->label('I have done both steps below.')
|
||||
->accepted(fn () => !file_exists('/.dockerenv'))
|
||||
->inline(false)
|
||||
->validationMessages([
|
||||
'accepted' => 'You need to do both steps before continuing!',
|
||||
])
|
||||
->hidden(fn () => file_exists('/.dockerenv')),
|
||||
TextInput::make('crontab')
|
||||
->label(new HtmlString('Run the following command to set up your crontab. Note that <code>www-data</code> is your webserver user. On some systems this username might be different!'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('(crontab -l -u www-data 2>/dev/null; echo "* * * * * php ' . base_path() . '/artisan schedule:run >> /dev/null 2>&1") | crontab -u www-data -')
|
||||
->hidden(fn () => file_exists('/.dockerenv'))
|
||||
->columnSpanFull(),
|
||||
TextInput::make('queueService')
|
||||
->label(new HtmlString('To setup the queue worker service you simply have to run the following command.'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('sudo php ' . base_path() . '/artisan p:environment:queue-service')
|
||||
->hidden(fn () => file_exists('/.dockerenv'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->afterValidation(function () use ($installer) {
|
||||
$installer->writeToEnv('env_queue');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class RedisStep
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('redis')
|
||||
->label('Redis')
|
||||
->columns()
|
||||
->schema([
|
||||
TextInput::make('env_redis.REDIS_HOST')
|
||||
->label('Redis Host')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The host of your redis server. Make sure it is reachable.')
|
||||
->required()
|
||||
->default(config('database.redis.default.host')),
|
||||
TextInput::make('env_redis.REDIS_PORT')
|
||||
->label('Redis Port')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The port of your redis server.')
|
||||
->required()
|
||||
->default(config('database.redis.default.port')),
|
||||
TextInput::make('env_redis.REDIS_USERNAME')
|
||||
->label('Redis Username')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The name of your redis user. Can be empty')
|
||||
->default(config('database.redis.default.username')),
|
||||
TextInput::make('env_redis.REDIS_PASSWORD')
|
||||
->label('Redis Password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The password for your redis user. Can be empty.')
|
||||
->password()
|
||||
->revealable()
|
||||
->default(config('database.redis.default.password')),
|
||||
])
|
||||
->afterValidation(function (Get $get) use ($installer) {
|
||||
if (!self::testConnection($get('env_redis.REDIS_HOST'), $get('env_redis.REDIS_PORT'), $get('env_redis.REDIS_USERNAME'), $get('env_redis.REDIS_PASSWORD'))) {
|
||||
throw new Halt('Redis connection failed');
|
||||
}
|
||||
|
||||
$installer->writeToEnv('env_redis');
|
||||
});
|
||||
}
|
||||
|
||||
private static function testConnection(string $host, null|string|int $port, ?string $username, ?string $password): bool
|
||||
{
|
||||
try {
|
||||
config()->set('database.redis._panel_install_test', [
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
Redis::connection('_panel_install_test')->command('ping');
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Redis connection failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
38
app/Filament/Pages/Installer/Steps/SessionStep.php
Normal file
38
app/Filament/Pages/Installer/Steps/SessionStep.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
|
||||
class SessionStep
|
||||
{
|
||||
public const SESSION_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'database' => 'Database',
|
||||
'cookie' => 'Cookie',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public static function make(): Step
|
||||
{
|
||||
return Step::make('session')
|
||||
->label('Session')
|
||||
->schema([
|
||||
ToggleButtons::make('env_session.SESSION_DRIVER')
|
||||
->label('Session Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for storing sessions. We recommend "Filesystem" or "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::SESSION_DRIVERS)
|
||||
->disableOptionWhen(fn ($value, Get $get) => $value === 'redis' && $get('env_cache.CACHE_STORE') !== 'redis')
|
||||
->default(config('session.driver')),
|
||||
TextInput::make('env_session.SESSION_SECURE_COOKIE')
|
||||
->hidden()
|
||||
->default(request()->isSecure()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use App\Traits\EnvironmentWriterTrait;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Actions\Action as FormAction;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
@@ -24,8 +25,11 @@ use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Concerns\HasUnsavedDataChangesAlert;
|
||||
use Filament\Pages\Concerns\InteractsWithHeaderActions;
|
||||
use Filament\Pages\Page;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Notification as MailNotification;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
/**
|
||||
* @property Form $form
|
||||
@@ -67,10 +71,11 @@ class Settings extends Page implements HasForms
|
||||
->label('General')
|
||||
->icon('tabler-home')
|
||||
->schema($this->generalSettings()),
|
||||
Tab::make('recaptcha')
|
||||
->label('reCAPTCHA')
|
||||
Tab::make('captcha')
|
||||
->label('Captcha')
|
||||
->icon('tabler-shield')
|
||||
->schema($this->recaptchaSettings()),
|
||||
->schema($this->captchaSettings())
|
||||
->columns(3),
|
||||
Tab::make('mail')
|
||||
->label('Mail')
|
||||
->icon('tabler-mail')
|
||||
@@ -146,7 +151,7 @@ class Settings extends Page implements HasForms
|
||||
->separator()
|
||||
->splitKeys(['Tab', ' '])
|
||||
->placeholder('New IP or IP Range')
|
||||
->default(env('TRUSTED_PROXIES', config('trustedproxy.proxies')))
|
||||
->default(env('TRUSTED_PROXIES', implode(',', config('trustedproxy.proxies'))))
|
||||
->hintActions([
|
||||
FormAction::make('clear')
|
||||
->label('Clear')
|
||||
@@ -159,56 +164,71 @@ class Settings extends Page implements HasForms
|
||||
->label('Set to Cloudflare IPs')
|
||||
->icon('tabler-brand-cloudflare')
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [
|
||||
'173.245.48.0/20',
|
||||
'103.21.244.0/22',
|
||||
'103.22.200.0/22',
|
||||
'103.31.4.0/22',
|
||||
'141.101.64.0/18',
|
||||
'108.162.192.0/18',
|
||||
'190.93.240.0/20',
|
||||
'188.114.96.0/20',
|
||||
'197.234.240.0/22',
|
||||
'198.41.128.0/17',
|
||||
'162.158.0.0/15',
|
||||
'104.16.0.0/13',
|
||||
'104.24.0.0/14',
|
||||
'172.64.0.0/13',
|
||||
'131.0.72.0/22',
|
||||
])),
|
||||
->action(function (Client $client, Set $set) {
|
||||
$ips = collect();
|
||||
try {
|
||||
$response = $client->request(
|
||||
'GET',
|
||||
'https://api.cloudflare.com/client/v4/ips',
|
||||
config('panel.guzzle')
|
||||
);
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$result = json_decode($response->getBody(), true)['result'];
|
||||
foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) {
|
||||
$ips->push(...data_get($result, $value));
|
||||
}
|
||||
$ips->unique();
|
||||
}
|
||||
} catch (GuzzleException $e) {
|
||||
}
|
||||
|
||||
$set('TRUSTED_PROXIES', $ips->values()->all());
|
||||
}),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
private function recaptchaSettings(): array
|
||||
private function captchaSettings(): array
|
||||
{
|
||||
return [
|
||||
Toggle::make('RECAPTCHA_ENABLED')
|
||||
->label('Enable reCAPTCHA?')
|
||||
Toggle::make('TURNSTILE_ENABLED')
|
||||
->label('Enable Turnstile Captcha?')
|
||||
->inline(false)
|
||||
->columnSpan(1)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('RECAPTCHA_ENABLED', (bool) $state))
|
||||
->default(env('RECAPTCHA_ENABLED', config('recaptcha.enabled'))),
|
||||
TextInput::make('RECAPTCHA_DOMAIN')
|
||||
->label('Domain')
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_ENABLED', (bool) $state))
|
||||
->default(env('TURNSTILE_ENABLED', config('turnstile.turnstile_enabled'))),
|
||||
Placeholder::make('info')
|
||||
->columnSpan(2)
|
||||
->content(new HtmlString('<p>You can generate the keys on your <u><a href="https://developers.cloudflare.com/turnstile/get-started/#get-a-sitekey-and-secret-key" target="_blank">Cloudflare Dashboard</a></u>. A Cloudflare account is required.</p>')),
|
||||
TextInput::make('TURNSTILE_SITE_KEY')
|
||||
->label('Site Key')
|
||||
->required()
|
||||
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
|
||||
->default(env('RECAPTCHA_DOMAIN', config('recaptcha.domain'))),
|
||||
TextInput::make('RECAPTCHA_WEBSITE_KEY')
|
||||
->label('Website Key')
|
||||
->required()
|
||||
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
|
||||
->default(env('RECAPTCHA_WEBSITE_KEY', config('recaptcha.website_key'))),
|
||||
TextInput::make('RECAPTCHA_SECRET_KEY')
|
||||
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
|
||||
->default(env('TURNSTILE_SITE_KEY', config('turnstile.turnstile_site_key')))
|
||||
->placeholder('1x00000000000000000000AA'),
|
||||
TextInput::make('TURNSTILE_SECRET_KEY')
|
||||
->label('Secret Key')
|
||||
->required()
|
||||
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
|
||||
->default(env('RECAPTCHA_SECRET_KEY', config('recaptcha.secret_key'))),
|
||||
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
|
||||
->default(env('TURNSTILE_SECRET_KEY', config('turnstile.secret_key')))
|
||||
->placeholder('1x0000000000000000000000000000000AA'),
|
||||
Toggle::make('TURNSTILE_VERIFY_DOMAIN')
|
||||
->label('Verify domain?')
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_VERIFY_DOMAIN', (bool) $state))
|
||||
->default(env('TURNSTILE_VERIFY_DOMAIN', config('turnstile.turnstile_verify_domain'))),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -540,7 +560,21 @@ class Settings extends Page implements HasForms
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_EDITABLE_SERVER_DESCRIPTIONS', (bool) $state))
|
||||
->default(env('PANEL_EDITABLE_SERVER_DESCRIPTIONS', config('panel.editable_server_descriptions'))),
|
||||
]),
|
||||
|
||||
Section::make('Webhook')
|
||||
->description('Configure how often old webhook logs should be pruned.')
|
||||
->columns()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('APP_WEBHOOK_PRUNE_DAYS')
|
||||
->label('Prune age')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(365)
|
||||
->suffix('Days')
|
||||
->default(env('APP_WEBHOOK_PRUNE_DAYS', config('panel.webhook.prune_days'))),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CreateApiKey extends CreateRecord
|
||||
{
|
||||
@@ -41,7 +42,7 @@ class CreateApiKey extends CreateRecord
|
||||
'md' => 2,
|
||||
])
|
||||
->schema(
|
||||
collect(ApiKey::RESOURCES)->map(fn ($resource) => ToggleButtons::make("r_$resource")
|
||||
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||
->label(str($resource)->replace('_', ' ')->title())->inline()
|
||||
->options([
|
||||
0 => 'None',
|
||||
@@ -87,4 +88,20 @@ class CreateApiKey extends CreateRecord
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
{
|
||||
$permissions = [];
|
||||
|
||||
foreach (ApiKey::getPermissionList() as $permission) {
|
||||
if (isset($data['permissions_' . $permission])) {
|
||||
$permissions[$permission] = intval($data['permissions_' . $permission]);
|
||||
unset($data['permissions_' . $permission]);
|
||||
}
|
||||
}
|
||||
|
||||
$data['permissions'] = $permissions;
|
||||
|
||||
return parent::handleRecordCreation($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ class ListEggs extends ListRecords
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -158,6 +159,7 @@ class ListEggs extends ListRecords
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Filament\Resources\NodeResource\Pages;
|
||||
|
||||
use App\Filament\Resources\NodeResource;
|
||||
use App\Models\Node;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use App\Services\Nodes\NodeAutoDeployService;
|
||||
use App\Services\Nodes\NodeUpdateService;
|
||||
use Filament\Actions;
|
||||
@@ -55,7 +56,7 @@ class EditNode extends EditRecord
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->label('Wings Version')
|
||||
->content(fn (Node $node) => $node->systemInformation()['version'] ?? 'Unknown'),
|
||||
->content(fn (Node $node, SoftwareVersionService $versionService) => ($node->systemInformation()['version'] ?? 'Unknown') . ' (Latest: ' . $versionService->latestWingsVersion() . ')'),
|
||||
Placeholder::make('')
|
||||
->label('CPU Threads')
|
||||
->content(fn (Node $node) => $node->systemInformation()['cpu_count'] ?? 0),
|
||||
|
||||
@@ -48,6 +48,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
// All assigned allocations
|
||||
->checkIfRecordIsSelectableUsing(fn (Allocation $allocation) => $allocation->server_id === null)
|
||||
->searchable()
|
||||
->selectCurrentPageOnly() //Prevent people from trying to nuke 30,000 ports at once.... -,-
|
||||
->columns([
|
||||
TextColumn::make('id'),
|
||||
TextColumn::make('port')
|
||||
@@ -65,12 +66,6 @@ class AllocationsRelationManager extends RelationManager
|
||||
->searchable()
|
||||
->label('IP'),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\Action::make('create new allocation')->label('Create Allocations')
|
||||
->form(fn () => [
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Enums\RolePermissionModels;
|
||||
use App\Enums\RolePermissionPrefixes;
|
||||
use App\Filament\Resources\RoleResource\Pages;
|
||||
use App\Models\Role;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\Component;
|
||||
@@ -71,7 +70,7 @@ class RoleResource extends Resource
|
||||
->disabled(fn (Get $get) => $get('name') === Role::ROOT_ADMIN),
|
||||
TextInput::make('guard_name')
|
||||
->label('Guard Name')
|
||||
->default(Filament::getCurrentPanel()?->getAuthGuard() ?? '')
|
||||
->default(Role::DEFAULT_GUARD_NAME)
|
||||
->nullable()
|
||||
->hidden(),
|
||||
Fieldset::make('Permissions')
|
||||
|
||||
@@ -787,6 +787,7 @@ class CreateServer extends CreateRecord
|
||||
->schema([
|
||||
Select::make('select_image')
|
||||
->label('Image Name')
|
||||
->live()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('image', $state))
|
||||
->options(function ($state, Get $get, Set $set) {
|
||||
$egg = Egg::query()->find($get('egg_id'));
|
||||
@@ -811,7 +812,7 @@ class CreateServer extends CreateRecord
|
||||
|
||||
TextInput::make('image')
|
||||
->label('Image')
|
||||
->debounce(500)
|
||||
->required()
|
||||
->afterStateUpdated(function ($state, Get $get, Set $set) {
|
||||
$egg = Egg::query()->find($get('egg_id'));
|
||||
$images = $egg->docker_images ?? [];
|
||||
|
||||
@@ -5,16 +5,19 @@ namespace App\Filament\Resources\ServerResource\Pages;
|
||||
use App\Enums\ContainerStatus;
|
||||
use App\Enums\ServerState;
|
||||
use App\Filament\Resources\ServerResource;
|
||||
use App\Http\Controllers\Admin\ServersController;
|
||||
use App\Filament\Resources\ServerResource\RelationManagers\AllocationsRelationManager;
|
||||
use App\Models\Database;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Mount;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerVariable;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use App\Services\Servers\RandomWordService;
|
||||
use App\Services\Servers\ReinstallServerService;
|
||||
use App\Services\Servers\ServerDeletionService;
|
||||
use App\Services\Servers\SuspensionService;
|
||||
use App\Services\Servers\ToggleInstallService;
|
||||
use App\Services\Servers\TransferServerService;
|
||||
use Closure;
|
||||
use Exception;
|
||||
@@ -25,6 +28,7 @@ use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
@@ -417,6 +421,7 @@ class EditServer extends EditRecord
|
||||
->schema([
|
||||
Select::make('select_image')
|
||||
->label('Image Name')
|
||||
->live()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('image', $state))
|
||||
->options(function ($state, Get $get, Set $set) {
|
||||
$egg = Egg::query()->find($get('egg_id'));
|
||||
@@ -436,7 +441,7 @@ class EditServer extends EditRecord
|
||||
|
||||
TextInput::make('image')
|
||||
->label('Image')
|
||||
->debounce(500)
|
||||
->required()
|
||||
->afterStateUpdated(function ($state, Get $get, Set $set) {
|
||||
$egg = Egg::query()->find($get('egg_id'));
|
||||
$images = $egg->docker_images ?? [];
|
||||
@@ -450,7 +455,7 @@ class EditServer extends EditRecord
|
||||
->placeholder('Enter a custom Image')
|
||||
->columnSpan(2),
|
||||
|
||||
Forms\Components\KeyValue::make('docker_labels')
|
||||
KeyValue::make('docker_labels')
|
||||
->label('Container Labels')
|
||||
->keyLabel('Label Name')
|
||||
->valueLabel('Label Description')
|
||||
@@ -596,8 +601,8 @@ class EditServer extends EditRecord
|
||||
->schema([
|
||||
CheckboxList::make('mounts')
|
||||
->relationship('mounts')
|
||||
->options(fn (Server $server) => $server->node->mounts->mapWithKeys(fn ($mount) => [$mount->id => $mount->name]))
|
||||
->descriptions(fn (Server $server) => $server->node->mounts->mapWithKeys(fn ($mount) => [$mount->id => "$mount->source -> $mount->target"]))
|
||||
->options(fn (Server $server) => $server->node->mounts->filter(fn (Mount $mount) => $mount->eggs->contains($server->egg))->mapWithKeys(fn (Mount $mount) => [$mount->id => $mount->name]))
|
||||
->descriptions(fn (Server $server) => $server->node->mounts->mapWithKeys(fn (Mount $mount) => [$mount->id => "$mount->source -> $mount->target"]))
|
||||
->label('Mounts')
|
||||
->helperText(fn (Server $server) => $server->node->mounts->isNotEmpty() ? '' : 'No Mounts exist for this Node')
|
||||
->columnSpanFull(),
|
||||
@@ -673,8 +678,8 @@ class EditServer extends EditRecord
|
||||
Action::make('toggleInstall')
|
||||
->label('Toggle Install Status')
|
||||
->disabled(fn (Server $server) => $server->isSuspended())
|
||||
->action(function (ServersController $serversController, Server $server) {
|
||||
$serversController->toggleInstall($server);
|
||||
->action(function (ToggleInstallService $service, Server $server) {
|
||||
$service->handle($server);
|
||||
|
||||
$this->refreshFormData(['status', 'docker']);
|
||||
}),
|
||||
@@ -760,7 +765,7 @@ class EditServer extends EditRecord
|
||||
->modalHeading('Are you sure you want to reinstall this server?')
|
||||
->modalDescription('!! This can result in unrecoverable data loss !!')
|
||||
->disabled(fn (Server $server) => $server->isSuspended())
|
||||
->action(fn (ServersController $serversController, Server $server) => $serversController->reinstallServer($server)),
|
||||
->action(fn (ReinstallServerService $service, Server $server) => $service->handle($server)),
|
||||
])->fullWidth(),
|
||||
ToggleButtons::make('')
|
||||
->hint('This will reinstall the server with the assigned egg install script.'),
|
||||
@@ -826,7 +831,7 @@ class EditServer extends EditRecord
|
||||
public function getRelationManagers(): array
|
||||
{
|
||||
return [
|
||||
ServerResource\RelationManagers\AllocationsRelationManager::class,
|
||||
AllocationsRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->selectCurrentPageOnly()
|
||||
->recordTitleAttribute('ip')
|
||||
->recordTitle(fn (Allocation $allocation) => "$allocation->ip:$allocation->port")
|
||||
->checkIfRecordIsSelectableUsing(fn (Allocation $record) => $record->id !== $this->getOwnerRecord()->allocation_id)
|
||||
@@ -149,7 +150,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
->multiple()
|
||||
->associateAnother(false)
|
||||
->preloadRecordSelect()
|
||||
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node))
|
||||
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id'))
|
||||
->label('Add Allocation'),
|
||||
])
|
||||
->bulkActions([
|
||||
|
||||
@@ -91,6 +91,7 @@ class ListUsers extends ListRecords
|
||||
TextInput::make('username')
|
||||
->alphaNum()
|
||||
->required()
|
||||
->unique()
|
||||
->maxLength(255),
|
||||
TextInput::make('email')
|
||||
->email()
|
||||
|
||||
@@ -4,13 +4,7 @@ namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\WebhookResource\Pages;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class WebhookResource extends Resource
|
||||
{
|
||||
@@ -22,41 +16,9 @@ class WebhookResource extends Resource
|
||||
|
||||
protected static ?string $label = 'Webhooks';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('endpoint')->activeUrl()->required(),
|
||||
TextInput::make('description')->nullable(),
|
||||
CheckboxList::make('events')->lazy()->options(
|
||||
fn () => WebhookConfiguration::filamentCheckboxList()
|
||||
)
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->gridDirection('row')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('description'),
|
||||
TextColumn::make('endpoint'),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
return static::getModel()::count() ?: null;
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
|
||||
@@ -3,9 +3,34 @@
|
||||
namespace App\Filament\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Resources\WebhookResource;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateWebhookConfiguration extends CreateRecord
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('endpoint')
|
||||
->activeUrl()
|
||||
->required(),
|
||||
TextInput::make('description')
|
||||
->required(),
|
||||
CheckboxList::make('events')
|
||||
->lazy()
|
||||
->options(fn () => WebhookConfiguration::filamentCheckboxList())
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->gridDirection('row')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,56 @@
|
||||
|
||||
namespace App\Filament\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Filament\Resources\WebhookResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditWebhookConfiguration extends EditRecord
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('endpoint')
|
||||
->label('Endpoint')
|
||||
->activeUrl()
|
||||
->required(),
|
||||
TextInput::make('description')
|
||||
->label('Description')
|
||||
->required(),
|
||||
CheckboxList::make('events')
|
||||
->label('Events')
|
||||
->lazy()
|
||||
->options(fn () => WebhookConfiguration::filamentCheckboxList())
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->gridDirection('row')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->label('Delete')
|
||||
->modalHeading('Are you sure you want to delete this?')
|
||||
->modalDescription('')
|
||||
->modalSubmitActionLabel('Delete'),
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,50 @@
|
||||
namespace App\Filament\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Resources\WebhookResource;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
|
||||
class ListWebhookConfigurations extends ListRecords
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('description')
|
||||
->label('Description'),
|
||||
TextColumn::make('endpoint')
|
||||
->label('Endpoint'),
|
||||
])
|
||||
->actions([
|
||||
DeleteAction::make()
|
||||
->label('Delete'),
|
||||
EditAction::make()
|
||||
->label('Edit'),
|
||||
])
|
||||
->emptyStateIcon('tabler-webhook')
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading('No Webhooks')
|
||||
->emptyStateActions([
|
||||
CreateAction::make('create')
|
||||
->label('Create Webhook')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->label('Create Webhook')
|
||||
->hidden(fn () => WebhookConfiguration::count() <= 0),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
|
||||
final class Time
|
||||
{
|
||||
/**
|
||||
* Gets the time offset from the provided timezone relative to UTC as a number. This
|
||||
* is used in the database configuration since we can't always rely on there being support
|
||||
* for named timezones in MySQL.
|
||||
*
|
||||
* Returns the timezone as a string like +08:00 or -05:00 depending on the app timezone.
|
||||
*/
|
||||
public static function getMySQLTimezoneOffset(string $timezone): string
|
||||
{
|
||||
return CarbonImmutable::now($timezone)->getTimezone()->toOffsetName();
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Models\ApiKey;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Api\KeyCreationService;
|
||||
use App\Http\Requests\Admin\Api\StoreApplicationApiKeyRequest;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
/**
|
||||
* ApiController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private AlertsMessageBag $alert,
|
||||
private KeyCreationService $keyCreationService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render view showing all of a user's application API keys.
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$keys = $request->user()->apiKeys()
|
||||
->where('key_type', ApiKey::TYPE_APPLICATION)
|
||||
->get();
|
||||
|
||||
return view('admin.api.index', [
|
||||
'keys' => $keys,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render view allowing an admin to create a new application API key.
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
$resources = AdminAcl::getResourceList();
|
||||
sort($resources);
|
||||
|
||||
return view('admin.api.new', [
|
||||
'resources' => $resources,
|
||||
'permissions' => [
|
||||
'r' => AdminAcl::READ,
|
||||
'rw' => AdminAcl::READ | AdminAcl::WRITE,
|
||||
'n' => AdminAcl::NONE,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the new key and redirect the user back to the application key listing.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function store(StoreApplicationApiKeyRequest $request): RedirectResponse
|
||||
{
|
||||
$this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([
|
||||
'memo' => $request->input('memo'),
|
||||
'user_id' => $request->user()->id,
|
||||
], $request->getKeyPermissions());
|
||||
|
||||
$this->alert->success('A new application API key has been generated for your account.')->flash();
|
||||
|
||||
return redirect()->route('admin.api.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an application API key from the database.
|
||||
*/
|
||||
public function delete(Request $request, string $identifier): Response
|
||||
{
|
||||
$request->user()->apiKeys()
|
||||
->where('key_type', ApiKey::TYPE_APPLICATION)
|
||||
->where('identifier', $identifier)
|
||||
->delete();
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
|
||||
class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* BaseController constructor.
|
||||
*/
|
||||
public function __construct(private SoftwareVersionService $version)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the admin index view.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
return view('admin.index', ['version' => $this->version]);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\Node;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\DatabaseHost;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Databases\Hosts\HostUpdateService;
|
||||
use App\Http\Requests\Admin\DatabaseHostFormRequest;
|
||||
use App\Services\Databases\Hosts\HostCreationService;
|
||||
use App\Services\Databases\Hosts\HostDeletionService;
|
||||
|
||||
class DatabaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* DatabaseController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private AlertsMessageBag $alert,
|
||||
private HostCreationService $creationService,
|
||||
private HostDeletionService $deletionService,
|
||||
private HostUpdateService $updateService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display database host index.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$hosts = DatabaseHost::query()
|
||||
->withCount('databases')
|
||||
->with('node')
|
||||
->get();
|
||||
|
||||
return view('admin.databases.index', [
|
||||
'nodes' => Node::all(),
|
||||
'hosts' => $hosts,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display database host to user.
|
||||
*/
|
||||
public function view(DatabaseHost $host): View
|
||||
{
|
||||
$databases = $host->databases()->with('server')->paginate(25);
|
||||
|
||||
return view('admin.databases.view', [
|
||||
'nodes' => Node::all(),
|
||||
'host' => $host,
|
||||
'databases' => $databases,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request to create a new database host.
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function create(DatabaseHostFormRequest $request): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$host = $this->creationService->handle($request->normalize());
|
||||
} catch (\Exception $exception) {
|
||||
if ($exception instanceof \PDOException || $exception->getPrevious() instanceof \PDOException) {
|
||||
$this->alert->danger(
|
||||
sprintf('There was an error while trying to connect to the host or while executing a query: "%s"', $exception->getMessage())
|
||||
)->flash();
|
||||
|
||||
return redirect()->route('admin.databases')->withInput($request->validated());
|
||||
} else {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
$this->alert->success('Successfully created a new database host on the system.')->flash();
|
||||
|
||||
return redirect()->route('admin.databases.view', $host->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle updating database host.
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function update(DatabaseHostFormRequest $request, DatabaseHost $host): RedirectResponse
|
||||
{
|
||||
$redirect = redirect()->route('admin.databases.view', $host->id);
|
||||
|
||||
try {
|
||||
$this->updateService->handle($host->id, $request->normalize());
|
||||
$this->alert->success('Database host was updated successfully.')->flash();
|
||||
} catch (\Exception $exception) {
|
||||
// Catch any SQL related exceptions and display them back to the user, otherwise just
|
||||
// throw the exception like normal and move on with it.
|
||||
if ($exception instanceof \PDOException || $exception->getPrevious() instanceof \PDOException) {
|
||||
$this->alert->danger(
|
||||
sprintf('There was an error while trying to connect to the host or while executing a query: "%s"', $exception->getMessage())
|
||||
)->flash();
|
||||
|
||||
return $redirect->withInput($request->normalize());
|
||||
} else {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request to delete a database host.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\HasActiveServersException
|
||||
*/
|
||||
public function delete(int $host): RedirectResponse
|
||||
{
|
||||
$this->deletionService->handle($host);
|
||||
$this->alert->success('The requested database host has been deleted from the system.')->flash();
|
||||
|
||||
return redirect()->route('admin.databases');
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Eggs;
|
||||
|
||||
use App\Exceptions\Service\Egg\NoParentConfigurationFoundException;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Egg;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Illuminate\View\Factory as ViewFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\Egg\EggFormRequest;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
class EggController extends Controller
|
||||
{
|
||||
/**
|
||||
* EggController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected AlertsMessageBag $alert,
|
||||
protected ViewFactory $view
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render eggs listing page.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
return view('admin.eggs.index', [
|
||||
'eggs' => Egg::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request to display the Egg creation page.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
$eggs = Egg::all();
|
||||
\JavaScript::put(['eggs' => $eggs->keyBy('id')]);
|
||||
|
||||
return view('admin.eggs.new', ['eggs' => $eggs]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request to store a new Egg.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws \App\Exceptions\Service\Egg\NoParentConfigurationFoundException
|
||||
*/
|
||||
public function store(EggFormRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
$data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null);
|
||||
$data['author'] = $request->user()->email;
|
||||
|
||||
$data['config_from'] = array_get($data, 'config_from');
|
||||
if (!is_null($data['config_from'])) {
|
||||
$parentEgg = Egg::query()->find(array_get($data, 'config_from'));
|
||||
throw_unless($parentEgg, new NoParentConfigurationFoundException(trans('exceptions.egg.invalid_copy_id')));
|
||||
}
|
||||
|
||||
$egg = Egg::query()->create(array_merge($data, [
|
||||
'uuid' => Uuid::uuid4()->toString(),
|
||||
]));
|
||||
|
||||
$this->alert->success(trans('admin/eggs.notices.egg_created'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', $egg->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request to view a single Egg.
|
||||
*/
|
||||
public function view(Egg $egg): View
|
||||
{
|
||||
return view('admin.eggs.view', [
|
||||
'egg' => $egg,
|
||||
'images' => array_map(
|
||||
fn ($key, $value) => $key === $value ? $value : "$key|$value",
|
||||
array_keys($egg->docker_images),
|
||||
$egg->docker_images,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request to update an Egg.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws \App\Exceptions\Service\Egg\NoParentConfigurationFoundException
|
||||
*/
|
||||
public function update(EggFormRequest $request, Egg $egg): RedirectResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
$data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null);
|
||||
|
||||
$eggId = array_get($data, 'config_from');
|
||||
$copiedFromEgg = Egg::query()->find($eggId);
|
||||
|
||||
throw_unless($copiedFromEgg, new NoParentConfigurationFoundException(trans('exceptions.egg.invalid_copy_id')));
|
||||
|
||||
$egg->update($data);
|
||||
|
||||
$this->alert->success(trans('admin/eggs.notices.updated'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', $egg->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request to destroy an egg.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\Egg\HasChildrenException
|
||||
* @throws \App\Exceptions\Service\HasActiveServersException
|
||||
*/
|
||||
public function destroy(Egg $egg): RedirectResponse
|
||||
{
|
||||
$egg->delete();
|
||||
|
||||
$this->alert->success(trans('admin/eggs.notices.deleted'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', $egg->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a string of docker image data into the expected egg format.
|
||||
*/
|
||||
protected function normalizeDockerImages(?string $input = null): array
|
||||
{
|
||||
$data = array_map(fn ($value) => trim($value), explode("\n", $input ?? ''));
|
||||
|
||||
$images = [];
|
||||
// Iterate over the image data provided and convert it into a name => image
|
||||
// pairing that is used to improve the display on the front-end.
|
||||
foreach ($data as $value) {
|
||||
$parts = explode('|', $value, 2);
|
||||
$images[$parts[0]] = empty($parts[1]) ? $parts[0] : $parts[1];
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Eggs;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Egg;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Illuminate\View\Factory as ViewFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Eggs\Scripts\InstallScriptService;
|
||||
use App\Http\Requests\Admin\Egg\EggScriptFormRequest;
|
||||
|
||||
class EggScriptController extends Controller
|
||||
{
|
||||
/**
|
||||
* EggScriptController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected AlertsMessageBag $alert,
|
||||
protected InstallScriptService $installScriptService,
|
||||
protected ViewFactory $view
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle requests to render installation script for an Egg.
|
||||
*/
|
||||
public function index(int $egg): View
|
||||
{
|
||||
$egg = Egg::with('scriptFrom', 'configFrom')
|
||||
->where('id', $egg)
|
||||
->firstOrFail();
|
||||
|
||||
$copy = Egg::query()
|
||||
->whereNull('copy_script_from')
|
||||
->whereNot('id', $egg->id)
|
||||
->firstOrFail();
|
||||
|
||||
$rely = Egg::query()->where('copy_script_from', $egg->id)->firstOrFail();
|
||||
|
||||
return view('admin.eggs.scripts', [
|
||||
'copyFromOptions' => $copy,
|
||||
'relyOnScript' => $rely,
|
||||
'egg' => $egg,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request to update the installation script for an Egg.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function update(EggScriptFormRequest $request, Egg $egg): RedirectResponse
|
||||
{
|
||||
$this->installScriptService->handle($egg, $request->normalize());
|
||||
$this->alert->success(trans('admin/eggs.notices.script_updated'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.scripts', $egg);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Eggs;
|
||||
|
||||
use App\Models\Egg;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use App\Http\Requests\Admin\Egg\EggImportFormRequest;
|
||||
|
||||
class EggShareController extends Controller
|
||||
{
|
||||
/**
|
||||
* EggShareController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected AlertsMessageBag $alert,
|
||||
protected EggExporterService $exporterService,
|
||||
protected EggImporterService $importerService,
|
||||
) {
|
||||
}
|
||||
|
||||
public function export(Egg $egg): Response
|
||||
{
|
||||
$filename = trim(preg_replace('/\W/', '-', kebab_case($egg->name)), '-');
|
||||
|
||||
return response($this->exporterService->handle($egg->id), 200, [
|
||||
'Content-Transfer-Encoding' => 'binary',
|
||||
'Content-Description' => 'File Transfer',
|
||||
'Content-Disposition' => 'attachment; filename=egg-' . $filename . '.json',
|
||||
'Content-Type' => 'application/json',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a new egg using an XML file.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws \App\Exceptions\Service\Egg\BadJsonFormatException
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException
|
||||
*/
|
||||
public function import(EggImportFormRequest $request): RedirectResponse
|
||||
{
|
||||
$egg = $this->importerService->fromFile($request->file('import_file'));
|
||||
$this->alert->success(trans('admin/eggs.notices.imported'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', ['egg' => $egg->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing Egg using a new imported file.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws \App\Exceptions\Service\Egg\BadJsonFormatException
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException
|
||||
*/
|
||||
public function update(EggImportFormRequest $request, Egg $egg): RedirectResponse
|
||||
{
|
||||
$this->importerService->fromFile($request->file('import_file'), $egg);
|
||||
$this->alert->success(trans('admin/eggs.notices.updated_via_import'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', ['egg' => $egg]);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Eggs;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Egg;
|
||||
use App\Models\EggVariable;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Illuminate\View\Factory as ViewFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Eggs\Variables\VariableUpdateService;
|
||||
use App\Http\Requests\Admin\Egg\EggVariableFormRequest;
|
||||
use App\Services\Eggs\Variables\VariableCreationService;
|
||||
|
||||
class EggVariableController extends Controller
|
||||
{
|
||||
/**
|
||||
* EggVariableController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected AlertsMessageBag $alert,
|
||||
protected VariableCreationService $creationService,
|
||||
protected VariableUpdateService $updateService,
|
||||
protected ViewFactory $view
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request to view the variables attached to an Egg.
|
||||
*/
|
||||
public function view(int $egg): View
|
||||
{
|
||||
$egg = Egg::with('variables')->findOrFail($egg);
|
||||
|
||||
return view('admin.eggs.variables', ['egg' => $egg]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request to create a new Egg variable.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws \App\Exceptions\Service\Egg\Variable\BadValidationRuleException
|
||||
* @throws \App\Exceptions\Service\Egg\Variable\ReservedVariableNameException
|
||||
*/
|
||||
public function store(EggVariableFormRequest $request, Egg $egg): RedirectResponse
|
||||
{
|
||||
$this->creationService->handle($egg->id, $request->normalize());
|
||||
$this->alert->success(trans('admin/eggs.variables.notices.variable_created'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.variables', $egg->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request to update an existing Egg variable.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws \App\Exceptions\Service\Egg\Variable\ReservedVariableNameException
|
||||
*/
|
||||
public function update(EggVariableFormRequest $request, Egg $egg, EggVariable $variable): RedirectResponse
|
||||
{
|
||||
$this->updateService->handle($variable, $request->normalize());
|
||||
$this->alert->success(trans('admin/eggs.variables.notices.variable_updated', [
|
||||
'variable' => $variable->name,
|
||||
]))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.variables', $egg->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request to delete an existing Egg variable from the Panel.
|
||||
*/
|
||||
public function destroy(int $egg, EggVariable $variable): RedirectResponse
|
||||
{
|
||||
$variable->delete();
|
||||
$this->alert->success(trans('admin/eggs.variables.notices.variable_deleted', [
|
||||
'variable' => $variable->name,
|
||||
]))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.variables', $egg);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\Node;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Nodes\NodeAutoDeployService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class NodeAutoDeployController extends Controller
|
||||
{
|
||||
/**
|
||||
* NodeAutoDeployController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly NodeAutoDeployService $nodeAutoDeployService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the API request and returns the deployment command.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function __invoke(Request $request, Node $node): JsonResponse
|
||||
{
|
||||
$command = $this->nodeAutoDeployService->handle($request, $node);
|
||||
|
||||
return new JsonResponse(['command' => $command]);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Nodes;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Node;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class NodeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns a listing of nodes on the system.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$nodes = QueryBuilder::for(
|
||||
Node::query()->withCount('servers')
|
||||
)
|
||||
->allowedFilters(['uuid', 'name'])
|
||||
->allowedSorts(['id'])
|
||||
->paginate(25);
|
||||
|
||||
return view('admin.nodes.index', ['nodes' => $nodes]);
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Nodes;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Node;
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Models\Allocation;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Traits\Controllers\JavascriptInjection;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
|
||||
class NodeViewController extends Controller
|
||||
{
|
||||
use JavascriptInjection;
|
||||
|
||||
public const THRESHOLD_PERCENTAGE_LOW = 75;
|
||||
|
||||
public const THRESHOLD_PERCENTAGE_MEDIUM = 90;
|
||||
|
||||
/**
|
||||
* NodeViewController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private SoftwareVersionService $versionService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns index view for a specific node on the system.
|
||||
*/
|
||||
public function index(Node $node): View
|
||||
{
|
||||
$node->loadCount('servers');
|
||||
|
||||
return view('admin.nodes.view.index', [
|
||||
'node' => $node,
|
||||
'version' => $this->versionService,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings page for a specific node.
|
||||
*/
|
||||
public function settings(Node $node): View
|
||||
{
|
||||
return view('admin.nodes.view.settings', [
|
||||
'node' => $node,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the node configuration page for a specific node.
|
||||
*/
|
||||
public function configuration(Node $node): View
|
||||
{
|
||||
return view('admin.nodes.view.configuration', compact('node'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the node allocation management page.
|
||||
*/
|
||||
public function allocations(Node $node): View
|
||||
{
|
||||
$node->setRelation(
|
||||
'allocations',
|
||||
$node->allocations()
|
||||
->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')
|
||||
->orderByRaw('INET_ATON(ip) ASC')
|
||||
->orderBy('port')
|
||||
->with('server:id,name')
|
||||
->paginate(50)
|
||||
);
|
||||
|
||||
$this->plainInject(['node' => Collection::wrap($node)->only(['id'])]);
|
||||
|
||||
return view('admin.nodes.view.allocation', [
|
||||
'node' => $node,
|
||||
'allocations' => Allocation::query()->where('node_id', $node->id)
|
||||
->groupBy('ip')
|
||||
->orderByRaw('INET_ATON(ip) ASC')
|
||||
->get(['ip']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a listing of servers that exist for this specific node.
|
||||
*/
|
||||
public function servers(Node $node): View
|
||||
{
|
||||
$this->plainInject([
|
||||
'node' => Collection::wrap($node->makeVisible(['daemon_token_id', 'daemon_token']))
|
||||
->only(['scheme', 'fqdn', 'daemon_listen', 'daemon_token_id', 'daemon_token']),
|
||||
]);
|
||||
|
||||
return view('admin.nodes.view.servers', [
|
||||
'node' => $node,
|
||||
'servers' => $node->servers()->with(['user', 'egg'])->paginate(25),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Nodes;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Node;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Repositories\Daemon\DaemonConfigurationRepository;
|
||||
|
||||
class SystemInformationController extends Controller
|
||||
{
|
||||
/**
|
||||
* SystemInformationController constructor.
|
||||
*/
|
||||
public function __construct(private DaemonConfigurationRepository $repository)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns system information from the Daemon.
|
||||
*
|
||||
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function __invoke(Request $request, Node $node): JsonResponse
|
||||
{
|
||||
$data = $this->repository->setNode($node)->getSystemInformation();
|
||||
|
||||
return new JsonResponse([
|
||||
'version' => $data['version'] ?? '',
|
||||
'system' => [
|
||||
'type' => Str::title($data['os'] ?? 'Unknown'),
|
||||
'arch' => $data['architecture'] ?? '--',
|
||||
'release' => $data['kernel_version'] ?? '--',
|
||||
'cpus' => $data['cpu_count'] ?? 0,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Node;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Models\Allocation;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Illuminate\View\Factory as ViewFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Nodes\NodeUpdateService;
|
||||
use Illuminate\Cache\Repository as CacheRepository;
|
||||
use App\Services\Nodes\NodeCreationService;
|
||||
use App\Services\Nodes\NodeDeletionService;
|
||||
use App\Services\Allocations\AssignmentService;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use App\Http\Requests\Admin\Node\NodeFormRequest;
|
||||
use App\Http\Requests\Admin\Node\AllocationFormRequest;
|
||||
use App\Http\Requests\Admin\Node\AllocationAliasFormRequest;
|
||||
|
||||
class NodesController extends Controller
|
||||
{
|
||||
/**
|
||||
* NodesController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected AlertsMessageBag $alert,
|
||||
protected AssignmentService $assignmentService,
|
||||
protected CacheRepository $cache,
|
||||
protected NodeCreationService $creationService,
|
||||
protected NodeDeletionService $deletionService,
|
||||
protected NodeUpdateService $updateService,
|
||||
protected SoftwareVersionService $versionService,
|
||||
protected ViewFactory $view
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays create new node page.
|
||||
*/
|
||||
public function create(): View|RedirectResponse
|
||||
{
|
||||
return view('admin.nodes.new');
|
||||
}
|
||||
|
||||
/**
|
||||
* Post controller to create a new node on the system.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function store(NodeFormRequest $request): RedirectResponse
|
||||
{
|
||||
$node = $this->creationService->handle($request->normalize());
|
||||
$this->alert->info(trans('admin/node.notices.node_created'))->flash();
|
||||
|
||||
return redirect()->route('admin.nodes.view.allocation', $node->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates settings for a node.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function updateSettings(NodeFormRequest $request, Node $node): RedirectResponse
|
||||
{
|
||||
$this->updateService->handle($node, $request->normalize(), $request->input('reset_secret') === 'on');
|
||||
$this->alert->success(trans('admin/node.notices.node_updated'))->flash();
|
||||
|
||||
return redirect()->route('admin.nodes.view.settings', $node->id)->withInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single allocation from a node.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\Allocation\ServerUsingAllocationException
|
||||
*/
|
||||
public function allocationRemoveSingle(int $node, Allocation $allocation): Response
|
||||
{
|
||||
$allocation->delete();
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes multiple individual allocations from a node.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\Allocation\ServerUsingAllocationException
|
||||
*/
|
||||
public function allocationRemoveMultiple(Request $request, int $node): Response
|
||||
{
|
||||
$allocations = $request->input('allocations');
|
||||
foreach ($allocations as $rawAllocation) {
|
||||
$allocation = new Allocation();
|
||||
$allocation->id = $rawAllocation['id'];
|
||||
$this->allocationRemoveSingle($node, $allocation);
|
||||
}
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all allocations for a specific IP at once on a node.
|
||||
*/
|
||||
public function allocationRemoveBlock(Request $request, int $node): RedirectResponse
|
||||
{
|
||||
/** @var Node $node */
|
||||
$node = Node::query()->findOrFail($node);
|
||||
$node->allocations()
|
||||
->where('ip', $request->input('ip'))
|
||||
->whereNull('server_id')
|
||||
->delete();
|
||||
|
||||
$this->alert->success(trans('admin/node.notices.unallocated_deleted', ['ip' => $request->input('ip')]))
|
||||
->flash();
|
||||
|
||||
return redirect()->route('admin.nodes.view.allocation', $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an alias for a specific allocation on a node.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function allocationSetAlias(AllocationAliasFormRequest $request): \Symfony\Component\HttpFoundation\Response
|
||||
{
|
||||
$allocation = Allocation::query()->findOrFail($request->input('allocation_id'));
|
||||
$alias = (empty($request->input('alias'))) ? null : $request->input('alias');
|
||||
$allocation->update(['ip_alias' => $alias]);
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new allocations on a node.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\Allocation\CidrOutOfRangeException
|
||||
* @throws \App\Exceptions\Service\Allocation\InvalidPortMappingException
|
||||
* @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException
|
||||
* @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException
|
||||
*/
|
||||
public function createAllocation(AllocationFormRequest $request, Node $node): RedirectResponse
|
||||
{
|
||||
$this->assignmentService->handle($node, $request->normalize());
|
||||
$this->alert->success(trans('admin/node.notices.allocations_added'))->flash();
|
||||
|
||||
return redirect()->route('admin.nodes.view.allocation', $node->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a node from the system.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
*/
|
||||
public function delete(int|Node $node): RedirectResponse
|
||||
{
|
||||
$this->deletionService->handle($node);
|
||||
$this->alert->success(trans('admin/node.notices.node_deleted'))->flash();
|
||||
|
||||
return redirect()->route('admin.nodes');
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Servers;
|
||||
|
||||
use App\Models\Egg;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Node;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\ServerFormRequest;
|
||||
use App\Services\Servers\ServerCreationService;
|
||||
|
||||
class CreateServerController extends Controller
|
||||
{
|
||||
/**
|
||||
* CreateServerController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private AlertsMessageBag $alert,
|
||||
private ServerCreationService $creationService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the create server page.
|
||||
*/
|
||||
public function index(): View|RedirectResponse
|
||||
{
|
||||
$nodes = Node::all();
|
||||
if (count($nodes) < 1) {
|
||||
$this->alert->warning(trans('admin/server.alerts.node_required'))->flash();
|
||||
|
||||
return redirect()->route('admin.nodes');
|
||||
}
|
||||
|
||||
$eggs = Egg::with('variables')->get();
|
||||
|
||||
\JavaScript::put([
|
||||
'nodeData' => Node::getForServerCreation(),
|
||||
'eggs' => $eggs->keyBy('id'),
|
||||
]);
|
||||
|
||||
return view('admin.servers.new', [
|
||||
'eggs' => $eggs,
|
||||
'nodes' => Node::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new server on the remote system.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \App\Exceptions\Service\Deployment\NoViableAllocationException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function store(ServerFormRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->except(['_token']);
|
||||
if (!empty($data['custom_image'])) {
|
||||
$data['image'] = $data['custom_image'];
|
||||
unset($data['custom_image']);
|
||||
}
|
||||
|
||||
$server = $this->creationService->handle($data);
|
||||
|
||||
$this->alert->success(trans('admin/server.alerts.server_created'))->flash();
|
||||
|
||||
return new RedirectResponse('/admin/servers/view/' . $server->id);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Servers;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Server;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Spatie\QueryBuilder\AllowedFilter;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Filters\AdminServerFilter;
|
||||
|
||||
class ServerController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns all the servers that exist on the system using a paginated result set. If
|
||||
* a query is passed along in the request it is also passed to the repository function.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$servers = QueryBuilder::for(Server::query()->with('node', 'user', 'allocation'))
|
||||
->allowedFilters([
|
||||
AllowedFilter::exact('owner_id'),
|
||||
AllowedFilter::custom('*', new AdminServerFilter()),
|
||||
])
|
||||
->paginate(config()->get('panel.paginate.admin.servers'));
|
||||
|
||||
return view('admin.servers.index', ['servers' => $servers]);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Servers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Server;
|
||||
use App\Services\Servers\TransferServerService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
|
||||
class ServerTransferController extends Controller
|
||||
{
|
||||
/**
|
||||
* ServerTransferController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private AlertsMessageBag $alert,
|
||||
private TransferServerService $transferServerService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a transfer of a server to a new node.
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function transfer(Request $request, Server $server): RedirectResponse
|
||||
{
|
||||
$validatedData = $request->validate([
|
||||
'node_id' => 'required|exists:nodes,id',
|
||||
'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
|
||||
'allocation_additional' => 'nullable',
|
||||
]);
|
||||
|
||||
if ($this->transferServerService->handle($server, $validatedData)) {
|
||||
$this->alert->success(trans('admin/server.alerts.transfer_started'))->flash();
|
||||
} else {
|
||||
$this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash();
|
||||
}
|
||||
|
||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Servers;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Mount;
|
||||
use App\Models\Node;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Server;
|
||||
use App\Exceptions\DisplayException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Servers\EnvironmentService;
|
||||
use App\Traits\Controllers\JavascriptInjection;
|
||||
|
||||
class ServerViewController extends Controller
|
||||
{
|
||||
use JavascriptInjection;
|
||||
|
||||
/**
|
||||
* ServerViewController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly EnvironmentService $environmentService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index view for a server.
|
||||
*/
|
||||
public function index(Server $server): View
|
||||
{
|
||||
return view('admin.servers.view.index', compact('server'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server details page.
|
||||
*/
|
||||
public function details(Server $server): View
|
||||
{
|
||||
return view('admin.servers.view.details', compact('server'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view of server build settings.
|
||||
*/
|
||||
public function build(Server $server): View
|
||||
{
|
||||
$allocations = $server->node->allocations->toBase();
|
||||
|
||||
return view('admin.servers.view.build', [
|
||||
'server' => $server,
|
||||
'assigned' => $allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'),
|
||||
'unassigned' => $allocations->where('server_id', null)->sortBy('port')->sortBy('ip'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server startup management page.
|
||||
*/
|
||||
public function startup(Server $server): View
|
||||
{
|
||||
$variables = $this->environmentService->handle($server);
|
||||
$eggs = Egg::all()->keyBy('id');
|
||||
|
||||
$this->plainInject([
|
||||
'server' => $server,
|
||||
'server_variables' => $variables,
|
||||
'eggs' => $eggs,
|
||||
]);
|
||||
|
||||
return view('admin.servers.view.startup', compact('server', 'eggs'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the databases that exist for the server.
|
||||
*/
|
||||
public function database(Server $server): View
|
||||
{
|
||||
return view('admin.servers.view.database', [
|
||||
'hosts' => DatabaseHost::all(),
|
||||
'server' => $server,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the mounts that exist for the server.
|
||||
*/
|
||||
public function mounts(Server $server): View
|
||||
{
|
||||
$server->load('mounts');
|
||||
|
||||
$mounts = Mount::query()
|
||||
->whereHas('eggs', fn ($q) => $q->where('id', $server->egg_id))
|
||||
->whereHas('nodes', fn ($q) => $q->where('id', $server->node_id))
|
||||
->get();
|
||||
|
||||
return view('admin.servers.view.mounts', [
|
||||
'mounts' => $mounts,
|
||||
'server' => $server,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base server management page, or an exception if the server
|
||||
* is in a state that cannot be recovered from.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
*/
|
||||
public function manage(Server $server): View
|
||||
{
|
||||
if ($server->status === ServerState::InstallFailed) {
|
||||
throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.');
|
||||
}
|
||||
|
||||
// Check if the panel doesn't have at least 2 nodes configured.
|
||||
$nodeCount = Node::query()->count();
|
||||
$canTransfer = false;
|
||||
if ($nodeCount >= 2) {
|
||||
$canTransfer = true;
|
||||
}
|
||||
|
||||
\JavaScript::put([
|
||||
'nodeData' => Node::getForServerCreation(),
|
||||
]);
|
||||
|
||||
return view('admin.servers.view.manage', [
|
||||
'nodes' => Node::all(),
|
||||
'server' => $server,
|
||||
'canTransfer' => $canTransfer,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server deletion page.
|
||||
*/
|
||||
public function delete(Server $server): View
|
||||
{
|
||||
return view('admin.servers.view.delete', compact('server'));
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Models\Mount;
|
||||
use App\Models\Server;
|
||||
use App\Models\Database;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use App\Exceptions\DisplayException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use App\Services\Servers\SuspensionService;
|
||||
use App\Services\Servers\ServerDeletionService;
|
||||
use App\Services\Servers\ReinstallServerService;
|
||||
use App\Exceptions\Model\DataValidationException;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use App\Services\Servers\BuildModificationService;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use App\Services\Servers\DetailsModificationService;
|
||||
use App\Services\Servers\StartupModificationService;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Services\Servers\ServerConfigurationStructureService;
|
||||
use App\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest;
|
||||
|
||||
class ServersController extends Controller
|
||||
{
|
||||
/**
|
||||
* ServersController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected AlertsMessageBag $alert,
|
||||
protected BuildModificationService $buildModificationService,
|
||||
protected DaemonServerRepository $daemonServerRepository,
|
||||
protected DatabaseManagementService $databaseManagementService,
|
||||
protected DatabasePasswordService $databasePasswordService,
|
||||
protected ServerDeletionService $deletionService,
|
||||
protected DetailsModificationService $detailsModificationService,
|
||||
protected ReinstallServerService $reinstallService,
|
||||
protected ServerConfigurationStructureService $serverConfigurationStructureService,
|
||||
protected StartupModificationService $startupModificationService,
|
||||
protected SuspensionService $suspensionService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the details for a server.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function setDetails(Request $request, Server $server): RedirectResponse
|
||||
{
|
||||
$this->detailsModificationService->handle($server, $request->only([
|
||||
'owner_id', 'external_id', 'name', 'description',
|
||||
]));
|
||||
|
||||
$this->alert->success(trans('admin/server.alerts.details_updated'))->flash();
|
||||
|
||||
return redirect()->route('admin.servers.view.details', $server->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the installation status for a server.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function toggleInstall(Server $server): void
|
||||
{
|
||||
if ($server->status === ServerState::InstallFailed) {
|
||||
throw new DisplayException(trans('admin/server.exceptions.marked_as_failed'));
|
||||
}
|
||||
|
||||
$server->status = $server->isInstalled() ? ServerState::Installing : null;
|
||||
$server->save();
|
||||
|
||||
Notification::make()
|
||||
->title('Success!')
|
||||
->body(trans('admin/server.alerts.install_toggled'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinstalls the server with the currently assigned service.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function reinstallServer(Server $server): void
|
||||
{
|
||||
$this->reinstallService->handle($server);
|
||||
|
||||
Notification::make()
|
||||
->title('Success!')
|
||||
->body(trans('admin/server.alerts.server_reinstalled'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the suspension status for a server.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function manageSuspension(Request $request, Server $server): RedirectResponse
|
||||
{
|
||||
$this->suspensionService->toggle($server, $request->input('action'));
|
||||
$this->alert->success(trans('admin/server.alerts.suspension_toggled', [
|
||||
'status' => $request->input('action') . 'ed',
|
||||
]))->flash();
|
||||
|
||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the build configuration for a server.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function updateBuild(Request $request, Server $server): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->buildModificationService->handle($server, $request->only([
|
||||
'allocation_id', 'add_allocations', 'remove_allocations',
|
||||
'memory', 'swap', 'io', 'cpu', 'threads', 'disk',
|
||||
'database_limit', 'allocation_limit', 'backup_limit', 'oom_killer',
|
||||
]));
|
||||
} catch (DataValidationException $exception) {
|
||||
throw new ValidationException($exception->getValidator());
|
||||
}
|
||||
|
||||
$this->alert->success(trans('admin/server.alerts.build_updated'))->flash();
|
||||
|
||||
return redirect()->route('admin.servers.view.build', $server->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the server deletion process.
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function delete(Request $request, Server $server): RedirectResponse
|
||||
{
|
||||
$this->deletionService->withForce($request->filled('force_delete'))->handle($server);
|
||||
$this->alert->success(trans('admin/server.alerts.server_deleted'))->flash();
|
||||
|
||||
return redirect()->route('admin.servers');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the startup command as well as variables.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function saveStartup(Request $request, Server $server): RedirectResponse
|
||||
{
|
||||
$data = $request->except('_token');
|
||||
if (!empty($data['custom_docker_image'])) {
|
||||
$data['docker_image'] = $data['custom_docker_image'];
|
||||
unset($data['custom_docker_image']);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->startupModificationService
|
||||
->setUserLevel(User::USER_LEVEL_ADMIN)
|
||||
->handle($server, $data);
|
||||
} catch (DataValidationException $exception) {
|
||||
throw new ValidationException($exception->getValidator());
|
||||
}
|
||||
|
||||
$this->alert->success(trans('admin/server.alerts.startup_changed'))->flash();
|
||||
|
||||
return redirect()->route('admin.servers.view.startup', $server->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new database assigned to a specific server.
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function newDatabase(StoreServerDatabaseRequest $request, Server $server): RedirectResponse
|
||||
{
|
||||
$this->databaseManagementService->create($server, [
|
||||
'database' => DatabaseManagementService::generateUniqueDatabaseName($request->input('database'), $server->id),
|
||||
'remote' => $request->input('remote'),
|
||||
'database_host_id' => $request->input('database_host_id'),
|
||||
'max_connections' => $request->input('max_connections'),
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.servers.view.database', $server->id)->withInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the database password for a specific database on this server.
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function resetDatabasePassword(Request $request, Server $server): Response
|
||||
{
|
||||
/** @var \App\Models\Database $database */
|
||||
$database = $server->databases()->findOrFail($request->input('database'));
|
||||
|
||||
$this->databasePasswordService->handle($database);
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a database from a server.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deleteDatabase(Server $server, Database $database): Response
|
||||
{
|
||||
$this->databaseManagementService->delete($database);
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a mount to a server.
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function addMount(Request $request, Server $server): RedirectResponse
|
||||
{
|
||||
$server->mounts()->attach($request->input('mount_id'));
|
||||
|
||||
$this->alert->success('Mount was added successfully.')->flash();
|
||||
|
||||
return redirect()->route('admin.servers.view.mounts', $server->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a mount from a server.
|
||||
*/
|
||||
public function deleteMount(Server $server, Mount $mount): RedirectResponse
|
||||
{
|
||||
$server->mounts()->detach($mount);
|
||||
|
||||
$this->alert->success('Mount was removed successfully.')->flash();
|
||||
|
||||
return redirect()->route('admin.servers.view.mounts', $server->id);
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Illuminate\View\Factory as ViewFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\Translation\Translator;
|
||||
use App\Services\Users\UserUpdateService;
|
||||
use App\Traits\Helpers\AvailableLanguages;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use App\Http\Requests\Admin\UserFormRequest;
|
||||
use App\Http\Requests\Admin\NewUserFormRequest;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
use AvailableLanguages;
|
||||
|
||||
/**
|
||||
* UserController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected AlertsMessageBag $alert,
|
||||
protected UserCreationService $creationService,
|
||||
protected Translator $translator,
|
||||
protected UserUpdateService $updateService,
|
||||
protected ViewFactory $view
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display user index page.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$users = QueryBuilder::for(
|
||||
User::query()->select('users.*')
|
||||
->selectRaw('COUNT(DISTINCT(subusers.id)) as subuser_of_count')
|
||||
->selectRaw('COUNT(DISTINCT(servers.id)) as servers_count')
|
||||
->leftJoin('subusers', 'subusers.user_id', '=', 'users.id')
|
||||
->leftJoin('servers', 'servers.owner_id', '=', 'users.id')
|
||||
->groupBy('users.id')
|
||||
)
|
||||
->allowedFilters(['username', 'email', 'uuid'])
|
||||
->allowedSorts(['id', 'uuid'])
|
||||
->paginate(50);
|
||||
|
||||
return view('admin.users.index', ['users' => $users]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display new user page.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('admin.users.new', [
|
||||
'languages' => $this->getAvailableLanguages(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display user view page.
|
||||
*/
|
||||
public function view(User $user): View
|
||||
{
|
||||
return view('admin.users.view', [
|
||||
'user' => $user,
|
||||
'languages' => $this->getAvailableLanguages(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user from the system.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
*/
|
||||
public function delete(User $user): RedirectResponse
|
||||
{
|
||||
$user->delete();
|
||||
|
||||
return redirect()->route('admin.users');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function store(NewUserFormRequest $request): RedirectResponse
|
||||
{
|
||||
$user = $this->creationService->handle($request->normalize());
|
||||
$this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash();
|
||||
|
||||
return redirect()->route('admin.users.view', $user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a user on the system.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function update(UserFormRequest $request, User $user): RedirectResponse
|
||||
{
|
||||
$this->updateService
|
||||
->setUserLevel(User::USER_LEVEL_ADMIN)
|
||||
->handle($user, $request->normalize());
|
||||
|
||||
$this->alert->success(trans('admin/user.notices.account_updated'))->flash();
|
||||
|
||||
return redirect()->route('admin.users.view', $user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON response of users on the system.
|
||||
*/
|
||||
public function json(Request $request): JsonResponse
|
||||
{
|
||||
// Handle single user requests | TODO: Separate this out into its own method
|
||||
if ($userId = $request->query('user_id')) {
|
||||
$user = User::query()->findOrFail($userId);
|
||||
$user['md5'] = md5(strtolower($user->email));
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
// Handle all users list
|
||||
$userPaginator = QueryBuilder::for(User::query())->allowedFilters(['email'])->paginate(25);
|
||||
|
||||
/** @var User[] $users */
|
||||
$users = $userPaginator->items();
|
||||
|
||||
return response()->json(collect($users)->map(function (User $user) {
|
||||
$user['md5'] = md5(strtolower($user->email));
|
||||
|
||||
return $user;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Remote;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Servers\EnvironmentService;
|
||||
|
||||
class EggInstallController extends Controller
|
||||
{
|
||||
/**
|
||||
* EggInstallController constructor.
|
||||
*/
|
||||
public function __construct(private EnvironmentService $environment)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request to get script and installation information for a server
|
||||
* that is being created on the node.
|
||||
*/
|
||||
public function index(Request $request, string $uuid): JsonResponse
|
||||
{
|
||||
$node = $request->attributes->get('node');
|
||||
|
||||
$server = Server::query()
|
||||
->with('egg.scriptFrom')
|
||||
->where('uuid', $uuid)
|
||||
->where('node_id', $node->id)
|
||||
->firstOrFail();
|
||||
|
||||
$egg = $server->egg;
|
||||
|
||||
return response()->json([
|
||||
'scripts' => [
|
||||
'install' => !$egg->copy_script_install ? null : str_replace(["\r\n", "\n", "\r"], "\n", $egg->copy_script_install),
|
||||
'privileged' => $egg->script_is_privileged,
|
||||
],
|
||||
'config' => [
|
||||
'container' => $egg->copy_script_container,
|
||||
'entry' => $egg->copy_script_entry,
|
||||
],
|
||||
'env' => $this->environment->handle($server),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware\Admin\Servers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Models\Server;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class ServerInstalled
|
||||
{
|
||||
/**
|
||||
* Checks that the server is installed before allowing access through the route.
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next): mixed
|
||||
{
|
||||
/** @var \App\Models\Server|null $server */
|
||||
$server = $request->route()->parameter('server');
|
||||
|
||||
if (!$server instanceof Server) {
|
||||
throw new NotFoundHttpException('No server resource was located in the request parameters.');
|
||||
}
|
||||
|
||||
if (!$server->isInstalled()) {
|
||||
throw new HttpException(Response::HTTP_FORBIDDEN, 'Access to this resource is not allowed due to the current installation state.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
class AdminAuthenticate
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next): mixed
|
||||
{
|
||||
if (!$request->user() || !$request->user()->isRootAdmin()) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use App\Exceptions\Http\TwoFactorAuthRequiredException;
|
||||
|
||||
class RequireTwoFactorAuthentication
|
||||
@@ -20,13 +19,6 @@ class RequireTwoFactorAuthentication
|
||||
*/
|
||||
protected string $redirectRoute = '/account';
|
||||
|
||||
/**
|
||||
* RequireTwoFactorAuthentication constructor.
|
||||
*/
|
||||
public function __construct(private AlertsMessageBag $alert)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the user state on the incoming request to determine if they should be allowed to
|
||||
* proceed or not. This checks if the Panel is configured to require 2FA on an account in
|
||||
@@ -62,8 +54,6 @@ class RequireTwoFactorAuthentication
|
||||
throw new TwoFactorAuthRequiredException();
|
||||
}
|
||||
|
||||
$this->alert->danger(trans('auth.2fa_must_be_enabled'))->flash();
|
||||
|
||||
return redirect()->to($this->redirectRoute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Events\Auth\FailedCaptcha;
|
||||
use Coderflex\LaravelTurnstile\Facades\LaravelTurnstile;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
readonly class VerifyReCaptcha
|
||||
@@ -18,7 +18,7 @@ readonly class VerifyReCaptcha
|
||||
|
||||
public function handle(Request $request, \Closure $next): mixed
|
||||
{
|
||||
if (!config('recaptcha.enabled')) {
|
||||
if (!config('turnstile.turnstile_enabled')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
@@ -26,40 +26,30 @@ readonly class VerifyReCaptcha
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if ($request->filled('g-recaptcha-response')) {
|
||||
$client = new Client();
|
||||
$res = $client->post(config('recaptcha.domain'), [
|
||||
'form_params' => [
|
||||
'secret' => config('recaptcha.secret_key'),
|
||||
'response' => $request->input('g-recaptcha-response'),
|
||||
],
|
||||
]);
|
||||
if ($request->filled('cf-turnstile-response')) {
|
||||
$response = LaravelTurnstile::validate($request->get('cf-turnstile-response'));
|
||||
|
||||
if ($res->getStatusCode() === 200) {
|
||||
$result = json_decode($res->getBody());
|
||||
|
||||
if ($result->success && (!config('recaptcha.verify_domain') || $this->isResponseVerified($result, $request))) {
|
||||
return $next($request);
|
||||
}
|
||||
if ($response['success'] && $this->isResponseVerified($response['hostname'] ?? '', $request)) {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
event(new FailedCaptcha($request->ip(), $result->hostname ?? null));
|
||||
event(new FailedCaptcha($request->ip(), $response['message'] ?? null));
|
||||
|
||||
throw new HttpException(Response::HTTP_BAD_REQUEST, 'Failed to validate reCAPTCHA data.');
|
||||
throw new HttpException(Response::HTTP_BAD_REQUEST, 'Failed to validate turnstile captcha data.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the response from the recaptcha servers was valid.
|
||||
*/
|
||||
private function isResponseVerified(\stdClass $result, Request $request): bool
|
||||
private function isResponseVerified(string $hostname, Request $request): bool
|
||||
{
|
||||
if (!config('recaptcha.verify_domain')) {
|
||||
return false;
|
||||
if (!config('turnstile.turnstile_verify_domain')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$url = parse_url($request->url());
|
||||
|
||||
return $result->hostname === array_get($url, 'host');
|
||||
return $hostname === array_get($url, 'host');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
abstract class AdminFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* The rules to apply to the incoming form request.
|
||||
*/
|
||||
abstract public function rules(): array;
|
||||
|
||||
/**
|
||||
* Determine if the user is an admin and has permission to access this
|
||||
* form controller in the first place.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
if (is_null($this->user())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->user()->isRootAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only the fields that we are interested in from the request.
|
||||
* This will include empty fields as a null value.
|
||||
*/
|
||||
public function normalize(?array $only = null): array
|
||||
{
|
||||
return $this->only($only ?? array_keys($this->rules()));
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Api;
|
||||
|
||||
use App\Models\ApiKey;
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class StoreApplicationApiKeyRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* @throws \ReflectionException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$modelRules = ApiKey::getRules();
|
||||
|
||||
return collect(AdminAcl::getResourceList())->mapWithKeys(function ($resource) use ($modelRules) {
|
||||
return [AdminAcl::COLUMN_IDENTIFIER . $resource => $modelRules['r_' . $resource]];
|
||||
})->merge(['memo' => $modelRules['memo']])->toArray();
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'memo' => 'Description',
|
||||
];
|
||||
}
|
||||
|
||||
public function getKeyPermissions(): array
|
||||
{
|
||||
return collect($this->validated())->filter(function ($value, $key) {
|
||||
return substr($key, 0, strlen(AdminAcl::COLUMN_IDENTIFIER)) === AdminAcl::COLUMN_IDENTIFIER;
|
||||
})->toArray();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
class BaseFormRequest extends AdminFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'company' => 'required|between:1,256',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use App\Models\DatabaseHost;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
|
||||
class DatabaseHostFormRequest extends AdminFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
if ($this->method() !== 'POST') {
|
||||
return DatabaseHost::getRulesForUpdate($this->route()->parameter('host'));
|
||||
}
|
||||
|
||||
return DatabaseHost::getRules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify submitted data before it is passed off to the validator.
|
||||
*/
|
||||
protected function getValidatorInstance(): Validator
|
||||
{
|
||||
if (!$this->filled('node_id')) {
|
||||
$this->merge(['node_id' => null]);
|
||||
}
|
||||
|
||||
return parent::getValidatorInstance();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Egg;
|
||||
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class EggFormRequest extends AdminFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'docker_images' => 'required|string',
|
||||
'force_outgoing_ip' => 'sometimes|boolean',
|
||||
'file_denylist' => 'array',
|
||||
'startup' => 'required|string',
|
||||
'config_from' => 'sometimes|bail|nullable|numeric',
|
||||
'config_stop' => 'required_without:config_from|nullable|string|max:255',
|
||||
'config_startup' => 'required_without:config_from|nullable|json',
|
||||
'config_logs' => 'required_without:config_from|nullable|json',
|
||||
'config_files' => 'required_without:config_from|nullable|json',
|
||||
];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->sometimes('config_from', 'exists:eggs,id', function () {
|
||||
return (int) $this->input('config_from') !== 0;
|
||||
});
|
||||
}
|
||||
|
||||
public function validated($key = null, $default = null): array
|
||||
{
|
||||
$data = parent::validated();
|
||||
|
||||
return array_merge($data, [
|
||||
'force_outgoing_ip' => array_get($data, 'force_outgoing_ip', false),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Egg;
|
||||
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class EggImportFormRequest extends AdminFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'import_file' => 'bail|required|file|max:1000|mimetypes:application/json,text/plain',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Egg;
|
||||
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class EggScriptFormRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* Return the rules to be used when validating the data sent in the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'script_install' => 'sometimes|nullable|string',
|
||||
'script_is_privileged' => 'sometimes|required|boolean',
|
||||
'script_entry' => 'sometimes|required|string',
|
||||
'script_container' => 'sometimes|required|string',
|
||||
'copy_script_from' => 'sometimes|nullable|numeric',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Egg;
|
||||
|
||||
use App\Models\EggVariable;
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class EggVariableFormRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* Define rules for validation of this request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|min:1|max:255',
|
||||
'description' => 'sometimes|nullable|string',
|
||||
'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES,
|
||||
'options' => 'sometimes|required|array',
|
||||
'rules' => 'bail|required|string',
|
||||
'default_value' => 'present',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class NewUserFormRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* Rules to apply to requests for updating or creating a user
|
||||
* in the Admin CP.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return Collection::make(
|
||||
User::getRules()
|
||||
)->only([
|
||||
'email',
|
||||
'username',
|
||||
'name_first',
|
||||
'name_last',
|
||||
'password',
|
||||
'language',
|
||||
])->toArray();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Node;
|
||||
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class AllocationAliasFormRequest extends AdminFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'alias' => 'present|nullable|string',
|
||||
'allocation_id' => 'required|numeric|exists:allocations,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Node;
|
||||
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class AllocationFormRequest extends AdminFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'allocation_ip' => 'required|string',
|
||||
'allocation_alias' => 'sometimes|nullable|string|max:255',
|
||||
'allocation_ports' => 'required|array',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Node;
|
||||
|
||||
use App\Models\Node;
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class NodeFormRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* Get rules to apply to data in this request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
if ($this->method() === 'PATCH') {
|
||||
return Node::getRulesForUpdate($this->route()->parameter('node'));
|
||||
}
|
||||
|
||||
return Node::getRules();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class ServerFormRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* Rules to be applied to this request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = Server::getRules();
|
||||
$rules['description'][] = 'nullable';
|
||||
$rules['custom_image'] = 'sometimes|nullable|string';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run validation after the rules above have been applied.
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$validator->sometimes('node_id', 'required|numeric|bail|exists:nodes,id', function ($input) {
|
||||
return !$input->auto_deploy;
|
||||
});
|
||||
|
||||
$validator->sometimes('allocation_id', [
|
||||
'required',
|
||||
'numeric',
|
||||
'bail',
|
||||
Rule::exists('allocations', 'id')->where(function ($query) {
|
||||
$query->where('node_id', $this->input('node_id'));
|
||||
$query->whereNull('server_id');
|
||||
}),
|
||||
], function ($input) {
|
||||
return !$input->auto_deploy;
|
||||
});
|
||||
|
||||
$validator->sometimes('allocation_additional.*', [
|
||||
'sometimes',
|
||||
'required',
|
||||
'numeric',
|
||||
Rule::exists('allocations', 'id')->where(function ($query) {
|
||||
$query->where('node_id', $this->input('node_id'));
|
||||
$query->whereNull('server_id');
|
||||
}),
|
||||
], function ($input) {
|
||||
return !$input->auto_deploy;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Servers\Databases;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class StoreServerDatabaseRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* Validation rules for database creation.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'database' => [
|
||||
'required',
|
||||
'string',
|
||||
'min:1',
|
||||
'max:24',
|
||||
Rule::unique('databases')->where(function (Builder $query) {
|
||||
$query->where('database_host_id', $this->input('database_host_id') ?? 0);
|
||||
}),
|
||||
],
|
||||
'max_connections' => 'nullable',
|
||||
'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/',
|
||||
'database_host_id' => 'required|integer|exists:database_hosts,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Settings;
|
||||
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class AdvancedSettingsFormRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* Return all the rules to apply to this request's data.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'recaptcha:enabled' => 'required|in:true,false',
|
||||
'recaptcha:secret_key' => 'required|string|max:255',
|
||||
'recaptcha:website_key' => 'required|string|max:255',
|
||||
'panel:guzzle:timeout' => 'required|integer|between:1,60',
|
||||
'panel:guzzle:connect_timeout' => 'required|integer|between:1,60',
|
||||
'panel:client_features:allocations:enabled' => 'required|in:true,false',
|
||||
'panel:client_features:allocations:range_start' => [
|
||||
'nullable',
|
||||
'required_if:panel:client_features:allocations:enabled,true',
|
||||
'integer',
|
||||
'between:1024,65535',
|
||||
],
|
||||
'panel:client_features:allocations:range_end' => [
|
||||
'nullable',
|
||||
'required_if:panel:client_features:allocations:enabled,true',
|
||||
'integer',
|
||||
'between:1024,65535',
|
||||
'gt:panel:client_features:allocations:range_start',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'recaptcha:enabled' => 'reCAPTCHA Enabled',
|
||||
'recaptcha:secret_key' => 'reCAPTCHA Secret Key',
|
||||
'recaptcha:website_key' => 'reCAPTCHA Website Key',
|
||||
'panel:guzzle:timeout' => 'HTTP Request Timeout',
|
||||
'panel:guzzle:connect_timeout' => 'HTTP Connection Timeout',
|
||||
'panel:client_features:allocations:enabled' => 'Auto Create Allocations Enabled',
|
||||
'panel:client_features:allocations:range_start' => 'Starting Port',
|
||||
'panel:client_features:allocations:range_end' => 'Ending Port',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Settings;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Traits\Helpers\AvailableLanguages;
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class BaseSettingsFormRequest extends AdminFormRequest
|
||||
{
|
||||
use AvailableLanguages;
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'app:name' => 'required|string|max:255',
|
||||
'panel:auth:2fa_required' => 'required|integer|in:0,1,2',
|
||||
'app:locale' => ['required', 'string', Rule::in(array_keys($this->getAvailableLanguages()))],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'app:name' => 'Company Name',
|
||||
'panel:auth:2fa_required' => 'Require 2-Factor Authentication',
|
||||
'app:locale' => 'Default Language',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\Settings;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Http\Requests\Admin\AdminFormRequest;
|
||||
|
||||
class MailSettingsFormRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* Return rules to validate mail settings POST data against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'mail:mailers:smtp:host' => 'required|string',
|
||||
'mail:mailers:smtp:port' => 'required|integer|between:1,65535',
|
||||
'mail:mailers:smtp:encryption' => ['present', Rule::in([null, 'tls', 'ssl'])],
|
||||
'mail:mailers:smtp:username' => 'nullable|string|max:255',
|
||||
'mail:mailers:smtp:password' => 'nullable|string|max:255',
|
||||
'mail:from:address' => 'required|string|email',
|
||||
'mail:from:name' => 'nullable|string|max:255',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default normalization function for this type of request
|
||||
* as we need to accept empty values on the keys.
|
||||
*/
|
||||
public function normalize(?array $only = null): array
|
||||
{
|
||||
$keys = array_flip(array_keys($this->rules()));
|
||||
|
||||
if (empty($this->input('mail:mailers:smtp:password'))) {
|
||||
unset($keys['mail:mailers:smtp:password']);
|
||||
}
|
||||
|
||||
return $this->only(array_flip($keys));
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class UserFormRequest extends AdminFormRequest
|
||||
{
|
||||
/**
|
||||
* Rules to apply to requests for updating or creating a user
|
||||
* in the Admin CP.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return Collection::make(
|
||||
User::getRulesForUpdate($this->route()->parameter('user'))
|
||||
)->only([
|
||||
'email',
|
||||
'username',
|
||||
'name_first',
|
||||
'name_last',
|
||||
'password',
|
||||
'language',
|
||||
])->toArray();
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Allocations;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\Allocation;
|
||||
|
||||
class DeleteAllocationRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS;
|
||||
protected ?string $resource = Allocation::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::WRITE;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Allocations;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\Allocation;
|
||||
|
||||
class GetAllocationsRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS;
|
||||
protected ?string $resource = Allocation::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::READ;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Allocations;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\Allocation;
|
||||
|
||||
class StoreAllocationRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS;
|
||||
protected ?string $resource = Allocation::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::WRITE;
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\DatabaseHosts;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\DatabaseHost;
|
||||
|
||||
class DeleteDatabaseHostRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_DATABASE_HOSTS;
|
||||
protected ?string $resource = DatabaseHost::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::WRITE;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\DatabaseHosts;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\DatabaseHost;
|
||||
|
||||
class GetDatabaseHostRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_DATABASE_HOSTS;
|
||||
protected ?string $resource = DatabaseHost::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::READ;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
|
||||
class StoreDatabaseHostRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_DATABASE_HOSTS;
|
||||
protected ?string $resource = DatabaseHost::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::WRITE;
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Eggs;
|
||||
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Models\Egg;
|
||||
|
||||
class GetEggRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_EGGS;
|
||||
protected ?string $resource = Egg::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::READ;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Eggs;
|
||||
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Models\Egg;
|
||||
|
||||
class GetEggsRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_EGGS;
|
||||
protected ?string $resource = Egg::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::READ;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Mounts;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\Mount;
|
||||
|
||||
class DeleteMountRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_MOUNTS;
|
||||
protected ?string $resource = Mount::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::WRITE;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Mounts;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\Mount;
|
||||
|
||||
class GetMountRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_MOUNTS;
|
||||
protected ?string $resource = Mount::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::READ;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Mounts;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\Mount;
|
||||
|
||||
class StoreMountRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_MOUNTS;
|
||||
protected ?string $resource = Mount::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::WRITE;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Nodes;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\Node;
|
||||
|
||||
class DeleteNodeRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = AdminAcl::RESOURCE_NODES;
|
||||
protected ?string $resource = Node::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::WRITE;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user