mirror of
https://github.com/pelican-dev/panel.git
synced 2025-05-20 07:34:45 +02:00
Settings page (#486)
* remove old settings stuff * add basic settings page * add some settings * add "test mail" button * fix mail fields not updating * fix phpstan * fix default for "top navigation" * force toggle buttons to be bool * force toggle to be bool * add class to view to allow customization * add mailgun settings * add notification settings * add timeout settings * organize tabs into sub-functions * add more settings * add backup settings * add sections to mail settings * add setting for trusted_proxies * fix unsaved data alert not showing * fix clear action * Fix clear action v2 TagsInput expects an array, not a string, fails on saving when using `''` * Add App favicon * Remove defaults, collapse misc sections * Move Save btn, Add API rate limit * small cleanup --------- Co-authored-by: notCharles <charles@pelican.dev>
This commit is contained in:
parent
d89af243a8
commit
a58e159478
@ -4,7 +4,6 @@ APP_KEY=
|
|||||||
APP_TIMEZONE=UTC
|
APP_TIMEZONE=UTC
|
||||||
APP_URL=http://panel.test
|
APP_URL=http://panel.test
|
||||||
APP_LOCALE=en
|
APP_LOCALE=en
|
||||||
APP_ENVIRONMENT_ONLY=true
|
|
||||||
|
|
||||||
LOG_CHANNEL=daily
|
LOG_CHANNEL=daily
|
||||||
LOG_STACK=single
|
LOG_STACK=single
|
||||||
|
3
.github/workflows/ci.yaml
vendored
3
.github/workflows/ci.yaml
vendored
@ -32,7 +32,6 @@ jobs:
|
|||||||
APP_KEY: ThisIsARandomStringForTests12345
|
APP_KEY: ThisIsARandomStringForTests12345
|
||||||
APP_TIMEZONE: UTC
|
APP_TIMEZONE: UTC
|
||||||
APP_URL: http://localhost/
|
APP_URL: http://localhost/
|
||||||
APP_ENVIRONMENT_ONLY: "true"
|
|
||||||
CACHE_DRIVER: array
|
CACHE_DRIVER: array
|
||||||
MAIL_MAILER: array
|
MAIL_MAILER: array
|
||||||
SESSION_DRIVER: array
|
SESSION_DRIVER: array
|
||||||
@ -106,7 +105,6 @@ jobs:
|
|||||||
APP_KEY: ThisIsARandomStringForTests12345
|
APP_KEY: ThisIsARandomStringForTests12345
|
||||||
APP_TIMEZONE: UTC
|
APP_TIMEZONE: UTC
|
||||||
APP_URL: http://localhost/
|
APP_URL: http://localhost/
|
||||||
APP_ENVIRONMENT_ONLY: "true"
|
|
||||||
CACHE_DRIVER: array
|
CACHE_DRIVER: array
|
||||||
MAIL_MAILER: array
|
MAIL_MAILER: array
|
||||||
SESSION_DRIVER: array
|
SESSION_DRIVER: array
|
||||||
@ -170,7 +168,6 @@ jobs:
|
|||||||
APP_KEY: ThisIsARandomStringForTests12345
|
APP_KEY: ThisIsARandomStringForTests12345
|
||||||
APP_TIMEZONE: UTC
|
APP_TIMEZONE: UTC
|
||||||
APP_URL: http://localhost/
|
APP_URL: http://localhost/
|
||||||
APP_ENVIRONMENT_ONLY: "true"
|
|
||||||
CACHE_DRIVER: array
|
CACHE_DRIVER: array
|
||||||
MAIL_MAILER: array
|
MAIL_MAILER: array
|
||||||
SESSION_DRIVER: array
|
SESSION_DRIVER: array
|
||||||
|
@ -38,8 +38,7 @@ class AppSettingsCommand extends Command
|
|||||||
{--queue= : The queue driver backend to use.}
|
{--queue= : The queue driver backend to use.}
|
||||||
{--redis-host= : Redis host to use for connections.}
|
{--redis-host= : Redis host to use for connections.}
|
||||||
{--redis-pass= : Password used to connect to redis.}
|
{--redis-pass= : Password used to connect to redis.}
|
||||||
{--redis-port= : Port to connect to redis over.}
|
{--redis-port= : Port to connect to redis over.}';
|
||||||
{--settings-ui= : Enable or disable the settings UI.}';
|
|
||||||
|
|
||||||
protected array $variables = [];
|
protected array $variables = [];
|
||||||
|
|
||||||
@ -87,12 +86,6 @@ class AppSettingsCommand extends Command
|
|||||||
array_key_exists($selected, self::QUEUE_DRIVERS) ? $selected : null
|
array_key_exists($selected, self::QUEUE_DRIVERS) ? $selected : null
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!is_null($this->option('settings-ui'))) {
|
|
||||||
$this->variables['APP_ENVIRONMENT_ONLY'] = $this->option('settings-ui') == 'true' ? 'false' : 'true';
|
|
||||||
} else {
|
|
||||||
$this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm(__('commands.appsettings.comment.settings_ui'), true) ? 'false' : 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure session cookies are set as "secure" when using HTTPS
|
// Make sure session cookies are set as "secure" when using HTTPS
|
||||||
if (str_starts_with($this->variables['APP_URL'], 'https://')) {
|
if (str_starts_with($this->variables['APP_URL'], 'https://')) {
|
||||||
$this->variables['SESSION_SECURE_COOKIE'] = 'true';
|
$this->variables['SESSION_SECURE_COOKIE'] = 'true';
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Filament\Clusters;
|
|
||||||
|
|
||||||
use Filament\Clusters\Cluster;
|
|
||||||
|
|
||||||
class Settings extends Cluster
|
|
||||||
{
|
|
||||||
protected static ?string $navigationIcon = 'tabler-settings';
|
|
||||||
}
|
|
570
app/Filament/Pages/Settings.php
Normal file
570
app/Filament/Pages/Settings.php
Normal file
@ -0,0 +1,570 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Pages;
|
||||||
|
|
||||||
|
use App\Models\Backup;
|
||||||
|
use App\Notifications\MailTested;
|
||||||
|
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||||
|
use Exception;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Forms\Components\Actions\Action as FormAction;
|
||||||
|
use Filament\Forms\Components\Section;
|
||||||
|
use Filament\Forms\Components\Tabs;
|
||||||
|
use Filament\Forms\Components\Tabs\Tab;
|
||||||
|
use Filament\Forms\Components\TagsInput;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Components\Toggle;
|
||||||
|
use Filament\Forms\Components\ToggleButtons;
|
||||||
|
use Filament\Forms\Concerns\InteractsWithForms;
|
||||||
|
use Filament\Forms\Contracts\HasForms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Forms\Get;
|
||||||
|
use Filament\Forms\Set;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Pages\Concerns\HasUnsavedDataChangesAlert;
|
||||||
|
use Filament\Pages\Concerns\InteractsWithHeaderActions;
|
||||||
|
use Filament\Pages\Page;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Notification as MailNotification;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property Form $form
|
||||||
|
*/
|
||||||
|
class Settings extends Page implements HasForms
|
||||||
|
{
|
||||||
|
use EnvironmentWriterTrait;
|
||||||
|
use HasUnsavedDataChangesAlert;
|
||||||
|
use InteractsWithForms;
|
||||||
|
use InteractsWithHeaderActions;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'tabler-settings';
|
||||||
|
protected static ?string $navigationGroup = 'Advanced';
|
||||||
|
|
||||||
|
protected static string $view = 'filament.pages.settings';
|
||||||
|
|
||||||
|
public ?array $data = [];
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->form->fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getFormSchema(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Tabs::make('Tabs')
|
||||||
|
->columns()
|
||||||
|
->persistTabInQueryString()
|
||||||
|
->tabs([
|
||||||
|
Tab::make('general')
|
||||||
|
->label('General')
|
||||||
|
->icon('tabler-home')
|
||||||
|
->schema($this->generalSettings()),
|
||||||
|
Tab::make('recaptcha')
|
||||||
|
->label('reCAPTCHA')
|
||||||
|
->icon('tabler-shield')
|
||||||
|
->schema($this->recaptchaSettings()),
|
||||||
|
Tab::make('mail')
|
||||||
|
->label('Mail')
|
||||||
|
->icon('tabler-mail')
|
||||||
|
->schema($this->mailSettings()),
|
||||||
|
Tab::make('backup')
|
||||||
|
->label('Backup')
|
||||||
|
->icon('tabler-box')
|
||||||
|
->schema($this->backupSettings()),
|
||||||
|
Tab::make('misc')
|
||||||
|
->label('Misc')
|
||||||
|
->icon('tabler-tool')
|
||||||
|
->schema($this->miscSettings()),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generalSettings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
TextInput::make('APP_NAME')
|
||||||
|
->label('App Name')
|
||||||
|
->required()
|
||||||
|
->default(env('APP_NAME', 'Pelican')),
|
||||||
|
TextInput::make('APP_FAVICON')
|
||||||
|
->label('App Favicon')
|
||||||
|
->hintIcon('tabler-question-mark')
|
||||||
|
->hintIconTooltip('Favicons should be placed in the public folder, located in the root panel directory.')
|
||||||
|
->required()
|
||||||
|
->default(env('APP_FAVICON', './pelican.ico')),
|
||||||
|
Toggle::make('APP_DEBUG')
|
||||||
|
->label('Enable Debug Mode?')
|
||||||
|
->inline(false)
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set('APP_DEBUG', (bool) $state))
|
||||||
|
->default(env('RECAPTCHA_ENABLED', config('recaptcha.enabled'))),
|
||||||
|
ToggleButtons::make('FILAMENT_TOP_NAVIGATION')
|
||||||
|
->label('Navigation')
|
||||||
|
->grouped()
|
||||||
|
->options([
|
||||||
|
false => 'Sidebar',
|
||||||
|
true => 'Topbar',
|
||||||
|
])
|
||||||
|
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_TOP_NAVIGATION', (bool) $state))
|
||||||
|
->default(env('FILAMENT_TOP_NAVIGATION', config('panel.filament.top-navigation'))),
|
||||||
|
ToggleButtons::make('PANEL_USE_BINARY_PREFIX')
|
||||||
|
->label('Unit prefix')
|
||||||
|
->grouped()
|
||||||
|
->options([
|
||||||
|
false => 'Decimal Prefix (MB/ GB)',
|
||||||
|
true => 'Binary Prefix (MiB/ GiB)',
|
||||||
|
])
|
||||||
|
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_USE_BINARY_PREFIX', (bool) $state))
|
||||||
|
->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))),
|
||||||
|
ToggleButtons::make('APP_2FA_REQUIRED')
|
||||||
|
->label('2FA Requirement')
|
||||||
|
->grouped()
|
||||||
|
->options([
|
||||||
|
0 => 'Not required',
|
||||||
|
1 => 'Required for only Admins',
|
||||||
|
2 => 'Required for all Users',
|
||||||
|
])
|
||||||
|
->formatStateUsing(fn ($state): int => (int) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set('APP_2FA_REQUIRED', (int) $state))
|
||||||
|
->default(env('APP_2FA_REQUIRED', config('panel.auth.2fa_required'))),
|
||||||
|
TagsInput::make('TRUSTED_PROXIES')
|
||||||
|
->label('Trusted Proxies')
|
||||||
|
->separator()
|
||||||
|
->splitKeys(['Tab', ' '])
|
||||||
|
->placeholder('New IP or IP Range')
|
||||||
|
->default(env('TRUSTED_PROXIES', config('trustedproxy.proxies')))
|
||||||
|
->hintActions([
|
||||||
|
FormAction::make('clear')
|
||||||
|
->label('Clear')
|
||||||
|
->color('danger')
|
||||||
|
->icon('tabler-trash')
|
||||||
|
->requiresConfirmation()
|
||||||
|
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
|
||||||
|
FormAction::make('cloudflare')
|
||||||
|
->label('Set to Cloudflare IPs')
|
||||||
|
->icon('tabler-brand-cloudflare')
|
||||||
|
->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',
|
||||||
|
])),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function recaptchaSettings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Toggle::make('RECAPTCHA_ENABLED')
|
||||||
|
->label('Enable reCAPTCHA?')
|
||||||
|
->inline(false)
|
||||||
|
->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')
|
||||||
|
->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')
|
||||||
|
->label('Secret Key')
|
||||||
|
->required()
|
||||||
|
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
|
||||||
|
->default(env('RECAPTCHA_SECRET_KEY', config('recaptcha.secret_key'))),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mailSettings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
ToggleButtons::make('MAIL_MAILER')
|
||||||
|
->label('Mail Driver')
|
||||||
|
->columnSpanFull()
|
||||||
|
->grouped()
|
||||||
|
->options([
|
||||||
|
'log' => 'Print mails to Log',
|
||||||
|
'smtp' => 'SMTP Server',
|
||||||
|
'sendmail' => 'sendmail Binary',
|
||||||
|
'mailgun' => 'Mailgun',
|
||||||
|
'mandrill' => 'Mandrill',
|
||||||
|
'postmark' => 'Postmark',
|
||||||
|
])
|
||||||
|
->live()
|
||||||
|
->default(env('MAIL_MAILER', config('mail.default')))
|
||||||
|
->hintAction(
|
||||||
|
FormAction::make('test')
|
||||||
|
->label('Send Test Mail')
|
||||||
|
->icon('tabler-send')
|
||||||
|
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
|
||||||
|
->action(function () {
|
||||||
|
try {
|
||||||
|
MailNotification::route('mail', auth()->user()->email)
|
||||||
|
->notify(new MailTested(auth()->user()));
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title('Test Mail sent')
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
Notification::make()
|
||||||
|
->title('Test Mail failed')
|
||||||
|
->body($exception->getMessage())
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
Section::make('"From" Settings')
|
||||||
|
->description('Set the Address and Name used as "From" in mails.')
|
||||||
|
->columns()
|
||||||
|
->schema([
|
||||||
|
TextInput::make('MAIL_FROM_ADDRESS')
|
||||||
|
->label('From Address')
|
||||||
|
->required()
|
||||||
|
->email()
|
||||||
|
->default(env('MAIL_FROM_ADDRESS', config('mail.from.address'))),
|
||||||
|
TextInput::make('MAIL_FROM_NAME')
|
||||||
|
->label('From Name')
|
||||||
|
->required()
|
||||||
|
->default(env('MAIL_FROM_NAME', config('mail.from.name'))),
|
||||||
|
]),
|
||||||
|
Section::make('SMTP Configuration')
|
||||||
|
->columns()
|
||||||
|
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'smtp')
|
||||||
|
->schema([
|
||||||
|
TextInput::make('MAIL_HOST')
|
||||||
|
->label('SMTP Host')
|
||||||
|
->required()
|
||||||
|
->default(env('MAIL_HOST', config('mail.mailers.smtp.host'))),
|
||||||
|
TextInput::make('MAIL_PORT')
|
||||||
|
->label('SMTP Port')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(1)
|
||||||
|
->maxValue(65535)
|
||||||
|
->default(env('MAIL_PORT', config('mail.mailers.smtp.port'))),
|
||||||
|
TextInput::make('MAIL_USERNAME')
|
||||||
|
->label('SMTP Username')
|
||||||
|
->required()
|
||||||
|
->default(env('MAIL_USERNAME', config('mail.mailers.smtp.username'))),
|
||||||
|
TextInput::make('MAIL_PASSWORD')
|
||||||
|
->label('SMTP Password')
|
||||||
|
->password()
|
||||||
|
->revealable()
|
||||||
|
->default(env('MAIL_PASSWORD')),
|
||||||
|
ToggleButtons::make('MAIL_ENCRYPTION')
|
||||||
|
->label('SMTP encryption')
|
||||||
|
->required()
|
||||||
|
->grouped()
|
||||||
|
->options(['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'])
|
||||||
|
->default(env('MAIL_ENCRYPTION', config('mail.mailers.smtp.encryption', 'tls'))),
|
||||||
|
]),
|
||||||
|
Section::make('Mailgun Configuration')
|
||||||
|
->columns()
|
||||||
|
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'mailgun')
|
||||||
|
->schema([
|
||||||
|
TextInput::make('MAILGUN_DOMAIN')
|
||||||
|
->label('Mailgun Domain')
|
||||||
|
->required()
|
||||||
|
->default(env('MAILGUN_DOMAIN', config('services.mailgun.domain'))),
|
||||||
|
TextInput::make('MAILGUN_SECRET')
|
||||||
|
->label('Mailgun Secret')
|
||||||
|
->required()
|
||||||
|
->default(env('MAIL_USERNAME', config('services.mailgun.secret'))),
|
||||||
|
TextInput::make('MAILGUN_ENDPOINT')
|
||||||
|
->label('Mailgun Endpoint')
|
||||||
|
->required()
|
||||||
|
->default(env('MAILGUN_ENDPOINT', config('services.mailgun.endpoint'))),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function backupSettings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
ToggleButtons::make('APP_BACKUP_DRIVER')
|
||||||
|
->label('Backup Driver')
|
||||||
|
->columnSpanFull()
|
||||||
|
->grouped()
|
||||||
|
->options([
|
||||||
|
Backup::ADAPTER_DAEMON => 'Wings',
|
||||||
|
Backup::ADAPTER_AWS_S3 => 'S3',
|
||||||
|
])
|
||||||
|
->live()
|
||||||
|
->default(env('APP_BACKUP_DRIVER', config('backups.default'))),
|
||||||
|
Section::make('Throttles')
|
||||||
|
->description('Configure how many backups can be created in a period. Set period to 0 to disable this throttle.')
|
||||||
|
->columns()
|
||||||
|
->schema([
|
||||||
|
TextInput::make('BACKUP_THROTTLE_LIMIT')
|
||||||
|
->label('Limit')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(1)
|
||||||
|
->default(config('backups.throttles.limit')),
|
||||||
|
TextInput::make('BACKUP_THROTTLE_PERIOD')
|
||||||
|
->label('Period')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(0)
|
||||||
|
->suffix('Seconds')
|
||||||
|
->default(config('backups.throttles.period')),
|
||||||
|
]),
|
||||||
|
Section::make('S3 Configuration')
|
||||||
|
->columns()
|
||||||
|
->visible(fn (Get $get) => $get('APP_BACKUP_DRIVER') === Backup::ADAPTER_AWS_S3)
|
||||||
|
->schema([
|
||||||
|
TextInput::make('AWS_DEFAULT_REGION')
|
||||||
|
->label('Default Region')
|
||||||
|
->required()
|
||||||
|
->default(config('backups.disks.s3.region')),
|
||||||
|
TextInput::make('AWS_ACCESS_KEY_ID')
|
||||||
|
->label('Access Key ID')
|
||||||
|
->required()
|
||||||
|
->default(config('backups.disks.s3.key')),
|
||||||
|
TextInput::make('AWS_SECRET_ACCESS_KEY')
|
||||||
|
->label('Secret Access Key')
|
||||||
|
->required()
|
||||||
|
->default(config('backups.disks.s3.secret')),
|
||||||
|
TextInput::make('AWS_BACKUPS_BUCKET')
|
||||||
|
->label('Bucket')
|
||||||
|
->required()
|
||||||
|
->default(config('backups.disks.s3.bucket')),
|
||||||
|
TextInput::make('AWS_ENDPOINT')
|
||||||
|
->label('Endpoint')
|
||||||
|
->required()
|
||||||
|
->default(config('backups.disks.s3.endpoint')),
|
||||||
|
Toggle::make('AWS_USE_PATH_STYLE_ENDPOINT')
|
||||||
|
->label('Use path style endpoint?')
|
||||||
|
->inline(false)
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->live()
|
||||||
|
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set('AWS_USE_PATH_STYLE_ENDPOINT', (bool) $state))
|
||||||
|
->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function miscSettings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Section::make('Automatic Allocation Creation')
|
||||||
|
->description('Toggle if Users can create allocations via the client area.')
|
||||||
|
->columns()
|
||||||
|
->collapsible()
|
||||||
|
->collapsed()
|
||||||
|
->schema([
|
||||||
|
Toggle::make('PANEL_CLIENT_ALLOCATIONS_ENABLED')
|
||||||
|
->label('Allow Users to create allocations?')
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->live()
|
||||||
|
->columnSpanFull()
|
||||||
|
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_CLIENT_ALLOCATIONS_ENABLED', (bool) $state))
|
||||||
|
->default(env('PANEL_CLIENT_ALLOCATIONS_ENABLED', config('panel.client_features.allocations.enabled'))),
|
||||||
|
TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_START')
|
||||||
|
->label('Starting Port')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(1024)
|
||||||
|
->maxValue(65535)
|
||||||
|
->visible(fn (Get $get) => $get('PANEL_CLIENT_ALLOCATIONS_ENABLED'))
|
||||||
|
->default(env('PANEL_CLIENT_ALLOCATIONS_RANGE_START')),
|
||||||
|
TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_END')
|
||||||
|
->label('Ending Port')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(1024)
|
||||||
|
->maxValue(65535)
|
||||||
|
->visible(fn (Get $get) => $get('PANEL_CLIENT_ALLOCATIONS_ENABLED'))
|
||||||
|
->default(env('PANEL_CLIENT_ALLOCATIONS_RANGE_END')),
|
||||||
|
]),
|
||||||
|
Section::make('Mail Notifications')
|
||||||
|
->description('Toggle which mail notifications should be sent to Users.')
|
||||||
|
->columns()
|
||||||
|
->collapsible()
|
||||||
|
->collapsed()
|
||||||
|
->schema([
|
||||||
|
Toggle::make('PANEL_SEND_INSTALL_NOTIFICATION')
|
||||||
|
->label('Server Installed')
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->live()
|
||||||
|
->columnSpanFull()
|
||||||
|
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_INSTALL_NOTIFICATION', (bool) $state))
|
||||||
|
->default(env('PANEL_SEND_INSTALL_NOTIFICATION', config('panel.email.send_install_notification'))),
|
||||||
|
Toggle::make('PANEL_SEND_REINSTALL_NOTIFICATION')
|
||||||
|
->label('Server Reinstalled')
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->live()
|
||||||
|
->columnSpanFull()
|
||||||
|
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_REINSTALL_NOTIFICATION', (bool) $state))
|
||||||
|
->default(env('PANEL_SEND_REINSTALL_NOTIFICATION', config('panel.email.send_reinstall_notification'))),
|
||||||
|
]),
|
||||||
|
Section::make('Connections')
|
||||||
|
->description('Timeouts used when making requests.')
|
||||||
|
->columns()
|
||||||
|
->collapsible()
|
||||||
|
->collapsed()
|
||||||
|
->schema([
|
||||||
|
TextInput::make('GUZZLE_TIMEOUT')
|
||||||
|
->label('Request Timeout')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(15)
|
||||||
|
->maxValue(60)
|
||||||
|
->suffix('Seconds')
|
||||||
|
->default(env('GUZZLE_TIMEOUT', config('panel.guzzle.timeout'))),
|
||||||
|
TextInput::make('GUZZLE_CONNECT_TIMEOUT')
|
||||||
|
->label('Connect Timeout')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(5)
|
||||||
|
->maxValue(60)
|
||||||
|
->suffix('Seconds')
|
||||||
|
->default(env('GUZZLE_CONNECT_TIMEOUT', config('panel.guzzle.connect_timeout'))),
|
||||||
|
]),
|
||||||
|
Section::make('Activity Logs')
|
||||||
|
->description('Configure how often old activity logs should be pruned and whether admin activities should be logged.')
|
||||||
|
->columns()
|
||||||
|
->collapsible()
|
||||||
|
->collapsed()
|
||||||
|
->schema([
|
||||||
|
TextInput::make('APP_ACTIVITY_PRUNE_DAYS')
|
||||||
|
->label('Prune age')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(1)
|
||||||
|
->maxValue(365)
|
||||||
|
->suffix('Days')
|
||||||
|
->default(env('APP_ACTIVITY_PRUNE_DAYS', config('activity.prune_days'))),
|
||||||
|
Toggle::make('APP_ACTIVITY_HIDE_ADMIN')
|
||||||
|
->label('Hide admin activities?')
|
||||||
|
->inline(false)
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->live()
|
||||||
|
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||||
|
->afterStateUpdated(fn ($state, Set $set) => $set('APP_ACTIVITY_HIDE_ADMIN', (bool) $state))
|
||||||
|
->default(env('APP_ACTIVITY_HIDE_ADMIN', config('activity.hide_admin_activity'))),
|
||||||
|
]),
|
||||||
|
Section::make('API')
|
||||||
|
->description('Defines the rate limit for the number of requests per minute that can be executed.')
|
||||||
|
->columns()
|
||||||
|
->collapsible()
|
||||||
|
->collapsed()
|
||||||
|
->schema([
|
||||||
|
TextInput::make('APP_API_CLIENT_RATELIMIT')
|
||||||
|
->label('Client API Rate Limit')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(1)
|
||||||
|
->suffix('Requests Per Minute')
|
||||||
|
->default(env('APP_API_CLIENT_RATELIMIT', config('http.rate_limit.client'))),
|
||||||
|
TextInput::make('APP_API_APPLICATION_RATELIMIT')
|
||||||
|
->label('Application API Rate Limit')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(1)
|
||||||
|
->suffix('Requests Per Minute')
|
||||||
|
->default(env('APP_API_APPLICATION_RATELIMIT', config('http.rate_limit.application'))),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getFormStatePath(): ?string
|
||||||
|
{
|
||||||
|
return 'data';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasUnsavedDataChangesAlert(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = $this->form->getState();
|
||||||
|
|
||||||
|
$this->writeToEnvironment($data);
|
||||||
|
|
||||||
|
Artisan::call('config:clear');
|
||||||
|
Artisan::call('queue:restart');
|
||||||
|
|
||||||
|
$this->rememberData();
|
||||||
|
|
||||||
|
$this->redirect($this->getUrl());
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title('Settings saved')
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
Notification::make()
|
||||||
|
->title('Save failed')
|
||||||
|
->body($exception->getMessage())
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Action::make('save')
|
||||||
|
->action('save')
|
||||||
|
->keyBindings(['mod+s']),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
protected function getFormActions(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Settings;
|
|
||||||
|
|
||||||
use App\Models\Setting;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\Settings\AdvancedSettingsFormRequest;
|
|
||||||
|
|
||||||
class AdvancedController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* AdvancedController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private AlertsMessageBag $alert,
|
|
||||||
private Kernel $kernel,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render advanced Panel settings UI.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
$showRecaptchaWarning = false;
|
|
||||||
if (
|
|
||||||
config('recaptcha._shipped_secret_key') === config('recaptcha.secret_key')
|
|
||||||
|| config('recaptcha._shipped_website_key') === config('recaptcha.website_key')
|
|
||||||
) {
|
|
||||||
$showRecaptchaWarning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('admin.settings.advanced', [
|
|
||||||
'showRecaptchaWarning' => $showRecaptchaWarning,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \App\Exceptions\Model\DataValidationException
|
|
||||||
*/
|
|
||||||
public function update(AdvancedSettingsFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
foreach ($request->normalize() as $key => $value) {
|
|
||||||
Setting::set('settings::' . $key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->kernel->call('queue:restart');
|
|
||||||
$this->alert->success('Advanced settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.settings.advanced');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Settings;
|
|
||||||
|
|
||||||
use App\Models\Setting;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Traits\Helpers\AvailableLanguages;
|
|
||||||
use App\Services\Helpers\SoftwareVersionService;
|
|
||||||
use App\Http\Requests\Admin\Settings\BaseSettingsFormRequest;
|
|
||||||
|
|
||||||
class IndexController extends Controller
|
|
||||||
{
|
|
||||||
use AvailableLanguages;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IndexController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private AlertsMessageBag $alert,
|
|
||||||
private Kernel $kernel,
|
|
||||||
private SoftwareVersionService $versionService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the UI for basic Panel settings.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
return view('admin.settings.index', [
|
|
||||||
'version' => $this->versionService,
|
|
||||||
'languages' => $this->getAvailableLanguages(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle settings update.
|
|
||||||
*
|
|
||||||
* @throws \App\Exceptions\Model\DataValidationException
|
|
||||||
*/
|
|
||||||
public function update(BaseSettingsFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
foreach ($request->normalize() as $key => $value) {
|
|
||||||
Setting::set('settings::' . $key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->kernel->call('queue:restart');
|
|
||||||
$this->alert->success('Panel settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.settings');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Settings;
|
|
||||||
|
|
||||||
use App\Models\Setting;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
|
||||||
use App\Notifications\MailTested;
|
|
||||||
use Illuminate\Support\Facades\Notification;
|
|
||||||
use App\Exceptions\DisplayException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Providers\SettingsServiceProvider;
|
|
||||||
use App\Http\Requests\Admin\Settings\MailSettingsFormRequest;
|
|
||||||
|
|
||||||
class MailController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* MailController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private Kernel $kernel,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render UI for editing mail settings. This UI should only display if
|
|
||||||
* the server is configured to send mail using SMTP.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
return view('admin.settings.mail', [
|
|
||||||
'disabled' => config('mail.default') !== 'smtp',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to update SMTP mail settings.
|
|
||||||
*
|
|
||||||
* @throws DisplayException
|
|
||||||
* @throws \App\Exceptions\Model\DataValidationException
|
|
||||||
*/
|
|
||||||
public function update(MailSettingsFormRequest $request): Response
|
|
||||||
{
|
|
||||||
if (config('mail.default') !== 'smtp') {
|
|
||||||
throw new DisplayException('This feature is only available if SMTP is the selected email driver for the Panel.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$values = $request->normalize();
|
|
||||||
if (array_get($values, 'mail:mailers:smtp:password') === '!e') {
|
|
||||||
$values['mail:mailers:smtp:password'] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($values as $key => $value) {
|
|
||||||
if (in_array($key, SettingsServiceProvider::getEncryptedKeys()) && !empty($value)) {
|
|
||||||
$value = encrypt($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Setting::set('settings::' . $key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->kernel->call('queue:restart');
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submit a request to send a test mail message.
|
|
||||||
*/
|
|
||||||
public function test(Request $request): Response
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Notification::route('mail', $request->user()->email)
|
|
||||||
->notify(new MailTested($request->user()));
|
|
||||||
} catch (\Exception $exception) {
|
|
||||||
return response($exception->getMessage(), 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,62 +24,4 @@ class Setting extends Model
|
|||||||
'key' => 'required|string|between:1,255',
|
'key' => 'required|string|between:1,255',
|
||||||
'value' => 'string',
|
'value' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
private static array $cache = [];
|
|
||||||
|
|
||||||
private static array $databaseMiss = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a new persistent setting in the database.
|
|
||||||
*/
|
|
||||||
public static function set(string $key, string $value = null): void
|
|
||||||
{
|
|
||||||
// Clear item from the cache.
|
|
||||||
self::clearCache($key);
|
|
||||||
|
|
||||||
self::query()->updateOrCreate(['key' => $key], ['value' => $value ?? '']);
|
|
||||||
|
|
||||||
self::$cache[$key] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a persistent setting from the database.
|
|
||||||
*/
|
|
||||||
public static function get(string $key, mixed $default = null): mixed
|
|
||||||
{
|
|
||||||
// If item has already been requested return it from the cache. If
|
|
||||||
// we already know it is missing, immediately return the default value.
|
|
||||||
if (array_key_exists($key, self::$cache)) {
|
|
||||||
return self::$cache[$key];
|
|
||||||
} elseif (array_key_exists($key, self::$databaseMiss)) {
|
|
||||||
return value($default);
|
|
||||||
}
|
|
||||||
|
|
||||||
$instance = self::query()->where('key', $key)->first();
|
|
||||||
if (is_null($instance)) {
|
|
||||||
self::$databaseMiss[$key] = true;
|
|
||||||
|
|
||||||
return value($default);
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$cache[$key] = $instance->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a key from the database cache.
|
|
||||||
*/
|
|
||||||
public static function forget(string $key)
|
|
||||||
{
|
|
||||||
self::clearCache($key);
|
|
||||||
|
|
||||||
return self::query()->where('key', $key)->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a key from the cache.
|
|
||||||
*/
|
|
||||||
private static function clearCache(string $key): void
|
|
||||||
{
|
|
||||||
unset(self::$cache[$key], self::$databaseMiss[$key]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -87,11 +87,6 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
// Only load the settings service provider if the environment is configured to allow it.
|
|
||||||
if (!config('panel.load_environment_only', false) && $this->app->environment() !== 'testing') {
|
|
||||||
$this->app->register(SettingsServiceProvider::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->app->singleton('extensions.themes', function () {
|
$this->app->singleton('extensions.themes', function () {
|
||||||
return new Theme();
|
return new Theme();
|
||||||
});
|
});
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Providers;
|
|
||||||
|
|
||||||
use App\Models\Setting;
|
|
||||||
use Exception;
|
|
||||||
use Psr\Log\LoggerInterface as Log;
|
|
||||||
use Illuminate\Database\QueryException;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
|
|
||||||
class SettingsServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* An array of configuration keys to override with database values
|
|
||||||
* if they exist.
|
|
||||||
*/
|
|
||||||
protected array $keys = [
|
|
||||||
'app:name',
|
|
||||||
'app:locale',
|
|
||||||
'recaptcha:enabled',
|
|
||||||
'recaptcha:secret_key',
|
|
||||||
'recaptcha:website_key',
|
|
||||||
'panel:guzzle:timeout',
|
|
||||||
'panel:guzzle:connect_timeout',
|
|
||||||
'panel:console:count',
|
|
||||||
'panel:console:frequency',
|
|
||||||
'panel:auth:2fa_required',
|
|
||||||
'panel:client_features:allocations:enabled',
|
|
||||||
'panel:client_features:allocations:range_start',
|
|
||||||
'panel:client_features:allocations:range_end',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keys specific to the mail driver that are only grabbed from the database
|
|
||||||
* when using the SMTP driver.
|
|
||||||
*/
|
|
||||||
protected array $emailKeys = [
|
|
||||||
'mail:mailers:smtp:host',
|
|
||||||
'mail:mailers:smtp:port',
|
|
||||||
'mail:mailers:smtp:encryption',
|
|
||||||
'mail:mailers:smtp:username',
|
|
||||||
'mail:mailers:smtp:password',
|
|
||||||
'mail:from:address',
|
|
||||||
'mail:from:name',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keys that are encrypted and should be decrypted when set in the
|
|
||||||
* configuration array.
|
|
||||||
*/
|
|
||||||
protected static array $encrypted = [
|
|
||||||
'mail:mailers:smtp:password',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Boot the service provider.
|
|
||||||
*/
|
|
||||||
public function boot(Log $log): void
|
|
||||||
{
|
|
||||||
// Only set the email driver settings from the database if we
|
|
||||||
// are configured using SMTP as the driver.
|
|
||||||
if (config('mail.default') === 'smtp') {
|
|
||||||
$this->keys = array_merge($this->keys, $this->emailKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$values = Setting::all()->mapWithKeys(function ($setting) {
|
|
||||||
return [$setting->key => $setting->value];
|
|
||||||
})->toArray();
|
|
||||||
} catch (QueryException $exception) {
|
|
||||||
$log->notice('A query exception was encountered while trying to load settings from the database: ' . $exception->getMessage());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->keys as $key) {
|
|
||||||
$value = array_get($values, 'settings::' . $key, config(str_replace(':', '.', $key)));
|
|
||||||
if (in_array($key, self::$encrypted)) {
|
|
||||||
try {
|
|
||||||
$value = decrypt($value);
|
|
||||||
} catch (Exception) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (strtolower($value)) {
|
|
||||||
case 'true':
|
|
||||||
case '(true)':
|
|
||||||
$value = true;
|
|
||||||
break;
|
|
||||||
case 'false':
|
|
||||||
case '(false)':
|
|
||||||
$value = false;
|
|
||||||
break;
|
|
||||||
case 'empty':
|
|
||||||
case '(empty)':
|
|
||||||
$value = '';
|
|
||||||
break;
|
|
||||||
case 'null':
|
|
||||||
case '(null)':
|
|
||||||
$value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
config()->set(str_replace(':', '.', $key), $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getEncryptedKeys(): array
|
|
||||||
{
|
|
||||||
return self::$encrypted;
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Facade;
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
'name' => env('APP_NAME', 'Pelican'),
|
'name' => env('APP_NAME', 'Pelican'),
|
||||||
|
'favicon' => env('APP_FAVICON', './pelican.ico'),
|
||||||
|
|
||||||
'version' => 'canary',
|
'version' => 'canary',
|
||||||
|
|
||||||
|
@ -1,18 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Restricted Environment
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Set this environment variable to true to enable a restricted configuration
|
|
||||||
| setup on the panel. When set to true, configurations stored in the
|
|
||||||
| database will not be applied.
|
|
||||||
*/
|
|
||||||
|
|
||||||
'load_environment_only' => (bool) env('APP_ENVIRONMENT_ONLY', false),
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Authentication
|
| Authentication
|
||||||
|
@ -6,7 +6,6 @@ return [
|
|||||||
'author' => 'Provide the email address that eggs exported by this Panel should be from. This should be a valid email address.',
|
'author' => 'Provide the email address that eggs exported by this Panel should be from. This should be a valid email address.',
|
||||||
'url' => 'The application URL MUST begin with https:// or http:// depending on if you are using SSL or not. If you do not include the scheme your emails and other content will link to the wrong location.',
|
'url' => 'The application URL MUST begin with https:// or http:// depending on if you are using SSL or not. If you do not include the scheme your emails and other content will link to the wrong location.',
|
||||||
'timezone' => "The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference https://php.net/manual/en/timezones.php.",
|
'timezone' => "The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference https://php.net/manual/en/timezones.php.",
|
||||||
'settings_ui' => 'Enable UI based settings editor?',
|
|
||||||
],
|
],
|
||||||
'redis' => [
|
'redis' => [
|
||||||
'note' => 'You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.',
|
'note' => 'You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.',
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
@extends('layouts.admin')
|
|
||||||
@include('partials/admin.settings.nav', ['activeTab' => 'advanced'])
|
|
||||||
|
|
||||||
@section('title')
|
|
||||||
Advanced Settings
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@section('content-header')
|
|
||||||
<h1>Advanced Settings<small>Configure advanced settings for Panel.</small></h1>
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
|
||||||
<li class="active">Settings</li>
|
|
||||||
</ol>
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
@yield('settings::nav')
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<form action="" method="POST">
|
|
||||||
<div class="box">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">reCAPTCHA</h3>
|
|
||||||
</div>
|
|
||||||
<div class="box-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Status</label>
|
|
||||||
<div>
|
|
||||||
<select class="form-control" name="recaptcha:enabled">
|
|
||||||
<option value="true">Enabled</option>
|
|
||||||
<option value="false" @if(old('recaptcha:enabled', config('recaptcha.enabled')) == '0') selected @endif>Disabled</option>
|
|
||||||
</select>
|
|
||||||
<p class="text-muted small">If enabled, login forms and password reset forms will do a silent captcha check and display a visible captcha if needed.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Site Key</label>
|
|
||||||
<div>
|
|
||||||
<input type="text" required class="form-control" name="recaptcha:website_key" value="{{ old('recaptcha:website_key', config('recaptcha.website_key')) }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Secret Key</label>
|
|
||||||
<div>
|
|
||||||
<input type="text" required class="form-control" name="recaptcha:secret_key" value="{{ old('recaptcha:secret_key', config('recaptcha.secret_key')) }}">
|
|
||||||
<p class="text-muted small">Used for communication between your site and Google. Be sure to keep it a secret.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if($showRecaptchaWarning)
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<div class="alert alert-warning no-margin">
|
|
||||||
You are currently using reCAPTCHA keys that were shipped with this Panel. For improved security it is recommended to <a href="https://www.google.com/recaptcha/admin">generate new invisible reCAPTCHA keys</a> that tied specifically to your website.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">HTTP Connections</h3>
|
|
||||||
</div>
|
|
||||||
<div class="box-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="form-group col-md-6">
|
|
||||||
<label class="control-label">Connection Timeout</label>
|
|
||||||
<div>
|
|
||||||
<input type="number" required class="form-control" name="panel:guzzle:connect_timeout" value="{{ old('panel:guzzle:connect_timeout', config('panel.guzzle.connect_timeout')) }}">
|
|
||||||
<p class="text-muted small">The amount of time in seconds to wait for a connection to be opened before throwing an error.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-6">
|
|
||||||
<label class="control-label">Request Timeout</label>
|
|
||||||
<div>
|
|
||||||
<input type="number" required class="form-control" name="panel:guzzle:timeout" value="{{ old('panel:guzzle:timeout', config('panel.guzzle.timeout')) }}">
|
|
||||||
<p class="text-muted small">The amount of time in seconds to wait for a request to be completed before throwing an error.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">Automatic Allocation Creation</h3>
|
|
||||||
</div>
|
|
||||||
<div class="box-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Status</label>
|
|
||||||
<div>
|
|
||||||
<select class="form-control" name="panel:client_features:allocations:enabled">
|
|
||||||
<option value="false">Disabled</option>
|
|
||||||
<option value="true" @if(old('panel:client_features:allocations:enabled', config('panel.client_features.allocations.enabled'))) selected @endif>Enabled</option>
|
|
||||||
</select>
|
|
||||||
<p class="text-muted small">If enabled users will have the option to automatically create new allocations for their server via the frontend.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Starting Port</label>
|
|
||||||
<div>
|
|
||||||
<input type="number" class="form-control" name="panel:client_features:allocations:range_start" value="{{ old('panel:client_features:allocations:range_start', config('panel.client_features.allocations.range_start')) }}">
|
|
||||||
<p class="text-muted small">The starting port in the range that can be automatically allocated.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Ending Port</label>
|
|
||||||
<div>
|
|
||||||
<input type="number" class="form-control" name="panel:client_features:allocations:range_end" value="{{ old('panel:client_features:allocations:range_end', config('panel.client_features.allocations.range_end')) }}">
|
|
||||||
<p class="text-muted small">The ending port in the range that can be automatically allocated.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box box-primary">
|
|
||||||
<div class="box-footer">
|
|
||||||
{{ csrf_field() }}
|
|
||||||
<button type="submit" name="_method" value="PATCH" class="btn btn-sm btn-primary pull-right">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
@ -1,75 +0,0 @@
|
|||||||
@extends('layouts.admin')
|
|
||||||
@include('partials/admin.settings.nav', ['activeTab' => 'basic'])
|
|
||||||
|
|
||||||
@section('title')
|
|
||||||
Settings
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@section('content-header')
|
|
||||||
<h1>Panel Settings<small>Configure Panel to your liking.</small></h1>
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
|
||||||
<li class="active">Settings</li>
|
|
||||||
</ol>
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
@yield('settings::nav')
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<div class="box">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">Panel Settings</h3>
|
|
||||||
</div>
|
|
||||||
<form action="{{ route('admin.settings') }}" method="POST">
|
|
||||||
<div class="box-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Company Name</label>
|
|
||||||
<div>
|
|
||||||
<input type="text" class="form-control" name="app:name" value="{{ old('app:name', config('app.name')) }}" />
|
|
||||||
<p class="text-muted"><small>This is the name that is used throughout the panel and in emails sent to clients.</small></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Require 2-Factor Authentication</label>
|
|
||||||
<div>
|
|
||||||
<div class="btn-group" data-toggle="buttons">
|
|
||||||
@php
|
|
||||||
$level = old('panel:auth:2fa_required', config('panel.auth.2fa_required'));
|
|
||||||
@endphp
|
|
||||||
<label class="btn btn-primary @if ($level == 0) active @endif">
|
|
||||||
<input type="radio" name="panel:auth:2fa_required" autocomplete="off" value="0" @if ($level == 0) checked @endif> Not Required
|
|
||||||
</label>
|
|
||||||
<label class="btn btn-primary @if ($level == 1) active @endif">
|
|
||||||
<input type="radio" name="panel:auth:2fa_required" autocomplete="off" value="1" @if ($level == 1) checked @endif> Admin Only
|
|
||||||
</label>
|
|
||||||
<label class="btn btn-primary @if ($level == 2) active @endif">
|
|
||||||
<input type="radio" name="panel:auth:2fa_required" autocomplete="off" value="2" @if ($level == 2) checked @endif> All Users
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<p class="text-muted"><small>If enabled, any account falling into the selected grouping will be required to have 2-Factor authentication enabled to use the Panel.</small></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Default Language</label>
|
|
||||||
<div>
|
|
||||||
<select name="app:locale" class="form-control">
|
|
||||||
@foreach($languages as $key => $value)
|
|
||||||
<option value="{{ $key }}" @if(config('app.locale') === $key) selected @endif>{{ $value }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
<p class="text-muted"><small>The default language to use when rendering UI components.</small></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box-footer">
|
|
||||||
{!! csrf_field() !!}
|
|
||||||
<button type="submit" name="_method" value="PATCH" class="btn btn-sm btn-primary pull-right">Save</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
@ -1,202 +0,0 @@
|
|||||||
@extends('layouts.admin')
|
|
||||||
@include('partials/admin.settings.nav', ['activeTab' => 'mail'])
|
|
||||||
|
|
||||||
@section('title')
|
|
||||||
Mail Settings
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@section('content-header')
|
|
||||||
<h1>Mail Settings<small>Configure how email sending should be handled.</small></h1>
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
|
||||||
<li class="active">Settings</li>
|
|
||||||
</ol>
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
@yield('settings::nav')
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<div class="box">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">Email Settings</h3>
|
|
||||||
</div>
|
|
||||||
@if($disabled)
|
|
||||||
<div class="box-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<div class="alert alert-info no-margin-bottom">
|
|
||||||
This interface is limited to instances using SMTP as the mail driver. Please either use <code>php artisan p:environment:mail</code> command to update your email settings, or set <code>MAIL_DRIVER=smtp</code> in your environment file.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<form>
|
|
||||||
<div class="box-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="form-group col-md-6">
|
|
||||||
<label class="control-label">SMTP Host</label>
|
|
||||||
<div>
|
|
||||||
<input required type="text" class="form-control" name="mail:mailers:smtp:host" value="{{ old('mail:mailers:smtp:host', config('mail.mailers.smtp.host')) }}" />
|
|
||||||
<p class="text-muted small">Enter the SMTP server address that mail should be sent through.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-2">
|
|
||||||
<label class="control-label">SMTP Port</label>
|
|
||||||
<div>
|
|
||||||
<input required type="number" class="form-control" name="mail:mailers:smtp:port" value="{{ old('mail:mailers:smtp:port', config('mail.mailers.smtp.port')) }}" />
|
|
||||||
<p class="text-muted small">Enter the SMTP server port that mail should be sent through.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label">Encryption</label>
|
|
||||||
<div>
|
|
||||||
@php
|
|
||||||
$encryption = old('mail:mailers:smtp:encryption', config('mail.mailers.smtp.encryption'));
|
|
||||||
@endphp
|
|
||||||
<select name="mail:mailers:smtp:encryption" class="form-control">
|
|
||||||
<option value="" @if($encryption === '') selected @endif>None</option>
|
|
||||||
<option value="tls" @if($encryption === 'tls') selected @endif>Transport Layer Security (TLS)</option>
|
|
||||||
<option value="ssl" @if($encryption === 'ssl') selected @endif>Secure Sockets Layer (SSL)</option>
|
|
||||||
</select>
|
|
||||||
<p class="text-muted small">Select the type of encryption to use when sending mail.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-6">
|
|
||||||
<label class="control-label">Username <span class="field-optional"></span></label>
|
|
||||||
<div>
|
|
||||||
<input type="text" class="form-control" name="mail:mailers:smtp:username" value="{{ old('mail:mailers:smtp:username', config('mail.mailers.smtp.username')) }}" />
|
|
||||||
<p class="text-muted small">The username to use when connecting to the SMTP server.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-6">
|
|
||||||
<label class="control-label">Password <span class="field-optional"></span></label>
|
|
||||||
<div>
|
|
||||||
<input type="password" class="form-control" name="mail:mailers:smtp:password"/>
|
|
||||||
<p class="text-muted small">The password to use in conjunction with the SMTP username. Leave blank to continue using the existing password. To set the password to an empty value enter <code>!e</code> into the field.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<hr />
|
|
||||||
<div class="form-group col-md-6">
|
|
||||||
<label class="control-label">Mail From</label>
|
|
||||||
<div>
|
|
||||||
<input required type="email" class="form-control" name="mail:from:address" value="{{ old('mail:from:address', config('mail.from.address')) }}" />
|
|
||||||
<p class="text-muted small">Enter an email address that all outgoing emails will originate from.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-6">
|
|
||||||
<label class="control-label">Mail From Name <span class="field-optional"></span></label>
|
|
||||||
<div>
|
|
||||||
<input type="text" class="form-control" name="mail:from:name" value="{{ old('mail:from:name', config('mail.from.name')) }}" />
|
|
||||||
<p class="text-muted small">The name that emails should appear to come from.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box-footer">
|
|
||||||
{{ csrf_field() }}
|
|
||||||
<div class="pull-right">
|
|
||||||
<button type="button" id="testButton" class="btn btn-sm btn-success">Test</button>
|
|
||||||
<button type="button" id="saveButton" class="btn btn-sm btn-primary">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@section('footer-scripts')
|
|
||||||
@parent
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function saveSettings() {
|
|
||||||
return $.ajax({
|
|
||||||
method: 'PATCH',
|
|
||||||
url: '/admin/settings/mail',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify({
|
|
||||||
'mail:mailers:smtp:host': $('input[name="mail:mailers:smtp:host"]').val(),
|
|
||||||
'mail:mailers:smtp:port': $('input[name="mail:mailers:smtp:port"]').val(),
|
|
||||||
'mail:mailers:smtp:encryption': $('select[name="mail:mailers:smtp:encryption"]').val(),
|
|
||||||
'mail:mailers:smtp:username': $('input[name="mail:mailers:smtp:username"]').val(),
|
|
||||||
'mail:mailers:smtp:password': $('input[name="mail:mailers:smtp:password"]').val(),
|
|
||||||
'mail:from:address': $('input[name="mail:from:address"]').val(),
|
|
||||||
'mail:from:name': $('input[name="mail:from:name"]').val()
|
|
||||||
}),
|
|
||||||
headers: { 'X-CSRF-Token': $('input[name="_token"]').val() }
|
|
||||||
}).fail(function (jqXHR) {
|
|
||||||
showErrorDialog(jqXHR, 'save');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSettings() {
|
|
||||||
swal({
|
|
||||||
type: 'info',
|
|
||||||
title: 'Test Mail Settings',
|
|
||||||
text: 'Click "Test" to begin the test.',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: 'Test',
|
|
||||||
closeOnConfirm: false,
|
|
||||||
showLoaderOnConfirm: true
|
|
||||||
}, function () {
|
|
||||||
$.ajax({
|
|
||||||
method: 'POST',
|
|
||||||
url: '/admin/settings/mail/test',
|
|
||||||
headers: { 'X-CSRF-TOKEN': $('input[name="_token"]').val() }
|
|
||||||
}).fail(function (jqXHR) {
|
|
||||||
showErrorDialog(jqXHR, 'test');
|
|
||||||
}).done(function () {
|
|
||||||
swal({
|
|
||||||
title: 'Success',
|
|
||||||
text: 'The test message was sent successfully.',
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveAndTestSettings() {
|
|
||||||
saveSettings().done(testSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showErrorDialog(jqXHR, verb) {
|
|
||||||
console.error(jqXHR);
|
|
||||||
var errorText = '';
|
|
||||||
if (!jqXHR.responseJSON) {
|
|
||||||
errorText = jqXHR.responseText;
|
|
||||||
} else if (jqXHR.responseJSON.error) {
|
|
||||||
errorText = jqXHR.responseJSON.error;
|
|
||||||
} else if (jqXHR.responseJSON.errors) {
|
|
||||||
$.each(jqXHR.responseJSON.errors, function (i, v) {
|
|
||||||
if (v.detail) {
|
|
||||||
errorText += v.detail + ' ';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
swal({
|
|
||||||
title: 'Whoops!',
|
|
||||||
text: 'An error occurred while attempting to ' + verb + ' mail settings: ' + errorText,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
$('#testButton').on('click', saveAndTestSettings);
|
|
||||||
$('#saveButton').on('click', function () {
|
|
||||||
saveSettings().done(function () {
|
|
||||||
swal({
|
|
||||||
title: 'Success',
|
|
||||||
text: 'Mail settings have been updated successfully and the queue worker was restarted to apply these changes.',
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@endsection
|
|
15
resources/views/filament/pages/settings.blade.php
Normal file
15
resources/views/filament/pages/settings.blade.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<x-filament-panels::page
|
||||||
|
@class([
|
||||||
|
'fi-page-settings'
|
||||||
|
])
|
||||||
|
>
|
||||||
|
<x-filament-panels::form
|
||||||
|
id="form"
|
||||||
|
:wire:key="$this->getId() . '.forms.' . $this->getFormStatePath()"
|
||||||
|
wire:submit="save"
|
||||||
|
>
|
||||||
|
{{ $this->form }}
|
||||||
|
</x-filament-panels::form>
|
||||||
|
|
||||||
|
<x-filament-panels::page.unsaved-data-changes-alert />
|
||||||
|
</x-filament-panels::page>
|
@ -96,11 +96,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="header">OTHER</li>
|
<li class="header">OTHER</li>
|
||||||
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.settings') ?: 'active' }}">
|
|
||||||
<a href="{{ route('admin.settings')}}">
|
|
||||||
<i class="fa fa-wrench"></i> <span>Settings</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.api') ?: 'active' }}">
|
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.api') ?: 'active' }}">
|
||||||
<a href="{{ route('admin.api.index')}}">
|
<a href="{{ route('admin.api.index')}}">
|
||||||
<i class="fa fa-gamepad"></i> <span>Application API</span>
|
<i class="fa fa-gamepad"></i> <span>Application API</span>
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
@include('partials/admin.settings.notice')
|
|
||||||
|
|
||||||
@section('settings::nav')
|
|
||||||
@yield('settings::notice')
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<div class="nav-tabs-custom nav-tabs-floating">
|
|
||||||
<ul class="nav nav-tabs">
|
|
||||||
<li @if($activeTab === 'basic')class="active"@endif><a href="{{ route('admin.settings') }}">General</a></li>
|
|
||||||
<li @if($activeTab === 'mail')class="active"@endif><a href="{{ route('admin.settings.mail') }}">Mail</a></li>
|
|
||||||
<li @if($activeTab === 'advanced')class="active"@endif><a href="{{ route('admin.settings.advanced') }}">Advanced</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
@ -1,11 +0,0 @@
|
|||||||
@section('settings::notice')
|
|
||||||
@if(config('panel.load_environment_only', false))
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
Your Panel is currently configured to read settings from the environment only. You will need to set <code>APP_ENVIRONMENT_ONLY=false</code> in your environment file in order to load settings dynamically.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@endsection
|
|
@ -40,26 +40,6 @@ Route::prefix('databases')->group(function () {
|
|||||||
Route::delete('/view/{host:id}', [Admin\DatabaseController::class, 'delete']);
|
Route::delete('/view/{host:id}', [Admin\DatabaseController::class, 'delete']);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Settings Controller Routes
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Endpoint: /admin/settings
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
Route::prefix('settings')->group(function () {
|
|
||||||
Route::get('/', [Admin\Settings\IndexController::class, 'index'])->name('admin.settings');
|
|
||||||
Route::get('/mail', [Admin\Settings\MailController::class, 'index'])->name('admin.settings.mail');
|
|
||||||
Route::get('/advanced', [Admin\Settings\AdvancedController::class, 'index'])->name('admin.settings.advanced');
|
|
||||||
|
|
||||||
Route::post('/mail/test', [Admin\Settings\MailController::class, 'test'])->name('admin.settings.mail.test');
|
|
||||||
|
|
||||||
Route::patch('/', [Admin\Settings\IndexController::class, 'update']);
|
|
||||||
Route::patch('/mail', [Admin\Settings\MailController::class, 'update']);
|
|
||||||
Route::patch('/advanced', [Admin\Settings\AdvancedController::class, 'update']);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| User Controller Routes
|
| User Controller Routes
|
||||||
|
Loading…
x
Reference in New Issue
Block a user