2024-08-03 21:13:17 +02:00
|
|
|
<?php
|
|
|
|
|
|
2024-12-08 19:57:00 +01:00
|
|
|
namespace App\Livewire\Installer;
|
2024-08-03 21:13:17 +02:00
|
|
|
|
2026-01-27 11:36:07 +01:00
|
|
|
use App\Enums\TablerIcon;
|
2025-12-29 17:24:02 -05:00
|
|
|
use App\Jobs\InstallEgg;
|
2024-12-08 19:57:00 +01:00
|
|
|
use App\Livewire\Installer\Steps\CacheStep;
|
|
|
|
|
use App\Livewire\Installer\Steps\DatabaseStep;
|
2025-12-29 17:24:02 -05:00
|
|
|
use App\Livewire\Installer\Steps\EggSelectionStep;
|
2024-12-08 19:57:00 +01:00
|
|
|
use App\Livewire\Installer\Steps\EnvironmentStep;
|
|
|
|
|
use App\Livewire\Installer\Steps\QueueStep;
|
|
|
|
|
use App\Livewire\Installer\Steps\RequirementsStep;
|
|
|
|
|
use App\Livewire\Installer\Steps\SessionStep;
|
2024-09-27 15:32:22 -04:00
|
|
|
use App\Models\User;
|
2025-09-04 20:17:59 +02:00
|
|
|
use App\Services\Helpers\LanguageService;
|
2024-08-03 21:13:17 +02:00
|
|
|
use App\Services\Users\UserCreationService;
|
2024-08-09 08:23:03 +02:00
|
|
|
use App\Traits\CheckMigrationsTrait;
|
|
|
|
|
use App\Traits\EnvironmentWriterTrait;
|
2026-01-15 20:55:53 +01:00
|
|
|
use App\Traits\Filament\CanCustomizeSteps;
|
2024-08-03 21:13:17 +02:00
|
|
|
use Exception;
|
2025-09-08 13:12:33 -04:00
|
|
|
use Filament\Actions\Action;
|
2025-08-30 14:37:59 +02:00
|
|
|
use Filament\Facades\Filament;
|
2025-09-04 20:17:59 +02:00
|
|
|
use Filament\Forms\Components\Select;
|
2025-09-08 13:12:33 -04:00
|
|
|
use Filament\Forms\Concerns\InteractsWithForms;
|
2025-09-24 13:34:19 +02:00
|
|
|
use Filament\Forms\Contracts\HasForms;
|
2024-08-03 21:13:17 +02:00
|
|
|
use Filament\Notifications\Notification;
|
|
|
|
|
use Filament\Pages\SimplePage;
|
2025-09-08 13:12:33 -04:00
|
|
|
use Filament\Schemas\Components\Component;
|
|
|
|
|
use Filament\Schemas\Components\Grid;
|
2025-09-24 13:34:19 +02:00
|
|
|
use Filament\Schemas\Components\Wizard;
|
2026-01-15 20:55:53 +01:00
|
|
|
use Filament\Schemas\Components\Wizard\Step;
|
2025-09-08 13:12:33 -04:00
|
|
|
use Filament\Schemas\Schema;
|
|
|
|
|
use Filament\Support\Enums\Width;
|
2024-10-12 16:19:52 +02:00
|
|
|
use Filament\Support\Exceptions\Halt;
|
2025-09-04 20:17:59 +02:00
|
|
|
use Illuminate\Foundation\Application;
|
2024-08-03 21:13:17 +02:00
|
|
|
use Illuminate\Support\Facades\Artisan;
|
|
|
|
|
use Illuminate\Support\Facades\Blade;
|
|
|
|
|
use Illuminate\Support\HtmlString;
|
|
|
|
|
|
|
|
|
|
/**
|
2025-09-08 13:12:33 -04:00
|
|
|
* @property Schema $form
|
2024-08-03 21:13:17 +02:00
|
|
|
*/
|
|
|
|
|
class PanelInstaller extends SimplePage implements HasForms
|
|
|
|
|
{
|
2026-01-15 20:55:53 +01:00
|
|
|
use CanCustomizeSteps;
|
2024-08-09 08:23:03 +02:00
|
|
|
use CheckMigrationsTrait;
|
2024-08-03 21:13:17 +02:00
|
|
|
use EnvironmentWriterTrait;
|
|
|
|
|
use InteractsWithForms;
|
|
|
|
|
|
2025-09-08 13:12:33 -04:00
|
|
|
/** @var array<string, mixed> */
|
2024-10-19 21:00:11 -04:00
|
|
|
public array $data = [];
|
2024-08-03 21:13:17 +02:00
|
|
|
|
2025-09-08 13:12:33 -04:00
|
|
|
protected string $view = 'filament.pages.installer';
|
2024-08-03 21:13:17 +02:00
|
|
|
|
2025-09-04 21:39:10 +02:00
|
|
|
public function getTitle(): string
|
|
|
|
|
{
|
|
|
|
|
return trans('installer.title');
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-08 13:12:33 -04:00
|
|
|
public function getMaxContentWidth(): Width|string
|
2024-08-03 21:13:17 +02:00
|
|
|
{
|
2025-12-29 17:24:02 -05:00
|
|
|
return Width::ScreenTwoExtraLarge;
|
2024-08-03 21:13:17 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-12 16:19:52 +02:00
|
|
|
public static function isInstalled(): bool
|
2024-08-03 21:13:17 +02:00
|
|
|
{
|
2025-01-16 14:53:50 -05:00
|
|
|
return config('app.installed');
|
2024-09-27 15:32:22 -04:00
|
|
|
}
|
|
|
|
|
|
2024-10-19 21:00:11 -04:00
|
|
|
public function mount(): void
|
2024-09-27 15:32:22 -04:00
|
|
|
{
|
2024-10-12 16:19:52 +02:00
|
|
|
abort_if(self::isInstalled(), 404);
|
2024-09-27 15:32:22 -04:00
|
|
|
|
2024-08-03 21:13:17 +02:00
|
|
|
$this->form->fill();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 20:55:53 +01:00
|
|
|
/** @return Component[] */
|
2024-08-03 21:13:17 +02:00
|
|
|
protected function getFormSchema(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
2025-09-04 20:17:59 +02:00
|
|
|
Grid::make()
|
|
|
|
|
->schema([
|
|
|
|
|
$this->getLanguageComponent(),
|
|
|
|
|
]),
|
2026-01-15 20:55:53 +01:00
|
|
|
Wizard::make($this->getSteps())
|
2024-08-03 21:13:17 +02:00
|
|
|
->persistStepInQueryString()
|
2026-01-15 20:55:53 +01:00
|
|
|
->nextAction(fn (Action $action) => $action->keyBindings('enter'))
|
2024-08-03 21:13:17 +02:00
|
|
|
->submitAction(new HtmlString(Blade::render(<<<'BLADE'
|
|
|
|
|
<x-filament::button
|
|
|
|
|
type="submit"
|
|
|
|
|
size="sm"
|
2024-08-09 08:23:03 +02:00
|
|
|
wire:loading.attr="disabled"
|
2024-08-03 21:13:17 +02:00
|
|
|
>
|
2025-09-04 21:39:10 +02:00
|
|
|
{{ trans('installer.finish') }}
|
2025-12-29 17:24:02 -05:00
|
|
|
<x-filament::loading-indicator wire:loading class="h-4 w-4" />
|
2024-08-03 21:13:17 +02:00
|
|
|
</x-filament::button>
|
|
|
|
|
BLADE))),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 20:55:53 +01:00
|
|
|
/**
|
|
|
|
|
* @return Step[]
|
|
|
|
|
*
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
|
|
|
|
protected function getDefaultSteps(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
RequirementsStep::make(),
|
|
|
|
|
EnvironmentStep::make($this),
|
|
|
|
|
DatabaseStep::make($this),
|
|
|
|
|
EggSelectionStep::make(),
|
|
|
|
|
CacheStep::make($this),
|
|
|
|
|
QueueStep::make($this),
|
|
|
|
|
SessionStep::make(),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 20:17:59 +02:00
|
|
|
protected function getLanguageComponent(): Component
|
|
|
|
|
{
|
|
|
|
|
return Select::make('language')
|
|
|
|
|
->hiddenLabel()
|
|
|
|
|
->prefix(trans('profile.language'))
|
2026-01-27 11:36:07 +01:00
|
|
|
->prefixIcon(TablerIcon::Flag)
|
2025-09-04 20:17:59 +02:00
|
|
|
->required()
|
|
|
|
|
->live()
|
|
|
|
|
->default('en')
|
|
|
|
|
->selectablePlaceholder(false)
|
|
|
|
|
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages())
|
|
|
|
|
->afterStateUpdated(fn ($state, Application $app) => $app->setLocale($state ?? config('app.locale')))
|
|
|
|
|
->columnStart(4);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-03 21:13:17 +02:00
|
|
|
protected function getFormStatePath(): ?string
|
|
|
|
|
{
|
|
|
|
|
return 'data';
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-05 13:21:34 +00:00
|
|
|
public function submit(UserCreationService $userCreationService): void
|
2024-08-03 21:13:17 +02:00
|
|
|
{
|
2024-12-01 04:04:40 +01:00
|
|
|
try {
|
|
|
|
|
// Disable installer
|
|
|
|
|
$this->writeToEnvironment(['APP_INSTALLED' => 'true']);
|
|
|
|
|
|
|
|
|
|
// Run migrations
|
|
|
|
|
$this->runMigrations();
|
2024-10-12 16:19:52 +02:00
|
|
|
|
2024-12-01 04:04:40 +01:00
|
|
|
// Create admin user & login
|
|
|
|
|
$user = $this->createAdminUser($userCreationService);
|
|
|
|
|
auth()->guard()->login($user, true);
|
2024-11-14 00:15:48 +01:00
|
|
|
|
2024-12-01 04:04:40 +01:00
|
|
|
// Write session data at the very end to avoid "page expired" errors
|
|
|
|
|
$this->writeToEnv('env_session');
|
2024-10-12 16:19:52 +02:00
|
|
|
|
2025-12-29 17:24:02 -05:00
|
|
|
// Install selected eggs
|
2026-01-05 13:21:34 +00:00
|
|
|
$this->installEggs();
|
2025-12-29 17:24:02 -05:00
|
|
|
|
2024-12-01 04:04:40 +01:00
|
|
|
// Redirect to admin panel
|
2025-08-30 14:37:59 +02:00
|
|
|
$this->redirect(Filament::getPanel('admin')->getUrl());
|
2024-12-01 04:04:40 +01:00
|
|
|
} catch (Halt) {
|
|
|
|
|
}
|
2024-08-03 21:13:17 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-12 16:19:52 +02:00
|
|
|
public function writeToEnv(string $key): void
|
2024-08-03 21:13:17 +02:00
|
|
|
{
|
|
|
|
|
try {
|
2024-10-12 16:19:52 +02:00
|
|
|
$variables = array_get($this->data, $key);
|
2024-11-14 00:15:48 +01:00
|
|
|
$variables = array_filter($variables); // Filter array to remove NULL values
|
2024-08-03 21:13:17 +02:00
|
|
|
$this->writeToEnvironment($variables);
|
2024-10-12 16:19:52 +02:00
|
|
|
} catch (Exception $exception) {
|
|
|
|
|
report($exception);
|
|
|
|
|
|
|
|
|
|
Notification::make()
|
2025-09-04 20:17:59 +02:00
|
|
|
->title(trans('installer.exceptions.write_env'))
|
2024-10-12 16:19:52 +02:00
|
|
|
->body($exception->getMessage())
|
|
|
|
|
->danger()
|
|
|
|
|
->persistent()
|
|
|
|
|
->send();
|
2024-08-03 21:13:17 +02:00
|
|
|
|
2025-09-04 20:17:59 +02:00
|
|
|
throw new Halt(trans('installer.exceptions.write_env'));
|
2024-10-12 16:19:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-01 04:04:40 +01:00
|
|
|
public function runMigrations(): void
|
2024-10-12 16:19:52 +02:00
|
|
|
{
|
|
|
|
|
try {
|
2024-08-03 21:13:17 +02:00
|
|
|
Artisan::call('migrate', [
|
|
|
|
|
'--force' => true,
|
|
|
|
|
'--seed' => true,
|
|
|
|
|
]);
|
2024-10-12 16:19:52 +02:00
|
|
|
} catch (Exception $exception) {
|
|
|
|
|
report($exception);
|
2024-08-03 21:13:17 +02:00
|
|
|
|
2024-10-12 16:19:52 +02:00
|
|
|
Notification::make()
|
2025-09-04 20:17:59 +02:00
|
|
|
->title(trans('installer.database.exceptions.migration'))
|
2024-10-12 16:19:52 +02:00
|
|
|
->body($exception->getMessage())
|
|
|
|
|
->danger()
|
|
|
|
|
->persistent()
|
|
|
|
|
->send();
|
2024-08-03 21:13:17 +02:00
|
|
|
|
2025-09-04 20:17:59 +02:00
|
|
|
throw new Halt(trans('installer.exceptions.migration'));
|
2024-10-12 16:19:52 +02:00
|
|
|
}
|
2024-08-03 21:13:17 +02:00
|
|
|
|
2024-10-12 16:19:52 +02:00
|
|
|
if (!$this->hasCompletedMigrations()) {
|
2024-08-03 21:13:17 +02:00
|
|
|
Notification::make()
|
2025-09-04 20:17:59 +02:00
|
|
|
->title(trans('installer.database.exceptions.migration'))
|
2024-10-12 16:19:52 +02:00
|
|
|
->danger()
|
|
|
|
|
->persistent()
|
2024-08-03 21:13:17 +02:00
|
|
|
->send();
|
|
|
|
|
|
2025-09-04 20:17:59 +02:00
|
|
|
throw new Halt(trans('installer.database.exceptions.migration'));
|
2024-10-12 16:19:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-09-27 15:34:26 -04:00
|
|
|
|
2024-11-14 00:15:48 +01:00
|
|
|
public function createAdminUser(UserCreationService $userCreationService): User
|
2024-10-12 16:19:52 +02:00
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$userData = array_get($this->data, 'user');
|
|
|
|
|
$userData['root_admin'] = true;
|
2024-11-14 00:15:48 +01:00
|
|
|
|
|
|
|
|
return $userCreationService->handle($userData);
|
2024-08-03 21:13:17 +02:00
|
|
|
} catch (Exception $exception) {
|
2024-08-09 08:23:03 +02:00
|
|
|
report($exception);
|
|
|
|
|
|
2024-08-03 21:13:17 +02:00
|
|
|
Notification::make()
|
2025-09-04 20:17:59 +02:00
|
|
|
->title(trans('installer.exceptions.create_user'))
|
2024-08-03 21:13:17 +02:00
|
|
|
->body($exception->getMessage())
|
|
|
|
|
->danger()
|
2024-08-09 08:23:03 +02:00
|
|
|
->persistent()
|
2024-08-03 21:13:17 +02:00
|
|
|
->send();
|
2024-10-12 16:19:52 +02:00
|
|
|
|
2025-09-04 20:17:59 +02:00
|
|
|
throw new Halt(trans('installer.exceptions.create_user'));
|
2024-08-03 21:13:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-29 17:24:02 -05:00
|
|
|
|
2026-01-05 13:21:34 +00:00
|
|
|
public function installEggs(): void
|
2025-12-29 17:24:02 -05:00
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$selectedEggs = array_get($this->data, 'eggs', []);
|
2026-01-05 13:21:34 +00:00
|
|
|
if (!$selectedEggs) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-29 17:24:02 -05:00
|
|
|
|
|
|
|
|
foreach ($selectedEggs as $category => $eggs) {
|
|
|
|
|
foreach ($eggs as $downloadUrl) {
|
|
|
|
|
InstallEgg::dispatch($downloadUrl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Notification::make()
|
|
|
|
|
->title(trans('installer.egg.background_install_started'))
|
|
|
|
|
->body(trans('installer.egg.background_install_description', ['count' => array_sum(array_map('count', $selectedEggs))]))
|
|
|
|
|
->success()
|
|
|
|
|
->persistent()
|
|
|
|
|
->send();
|
|
|
|
|
} catch (Exception $exception) {
|
|
|
|
|
report($exception);
|
|
|
|
|
|
|
|
|
|
Notification::make()
|
|
|
|
|
->title(trans('installer.egg.exceptions.installation_failed'))
|
|
|
|
|
->body($exception->getMessage())
|
|
|
|
|
->danger()
|
|
|
|
|
->persistent()
|
|
|
|
|
->send();
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-03 21:13:17 +02:00
|
|
|
}
|