From 0066fb458b39a60978e98d719eaf53967821c68b Mon Sep 17 00:00:00 2001 From: "sweep-ai[bot]" <128439645+sweep-ai[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 00:14:29 +0000 Subject: [PATCH] Enhance Hosting Infrastructure with Multi-Server Support and --- .../Resources/HostingServerResource.php | 113 ++++++++++++++++++ app/Services/HostingService.php | 81 ++++++++++--- ...1_20_000001_create_product_types_table.php | 56 +++++++++ ...20_000002_create_hosting_servers_table.php | 31 +++++ ...al_settings_to_products_services_table.php | 29 +++++ 5 files changed, 296 insertions(+), 14 deletions(-) create mode 100644 app/Filament/Resources/HostingServerResource.php create mode 100644 database/migrations/2024_01_20_000001_create_product_types_table.php create mode 100644 database/migrations/2024_01_20_000002_create_hosting_servers_table.php create mode 100644 database/migrations/2024_01_20_000003_add_trial_settings_to_products_services_table.php diff --git a/app/Filament/Resources/HostingServerResource.php b/app/Filament/Resources/HostingServerResource.php new file mode 100644 index 00000000..8f2fcdb8 --- /dev/null +++ b/app/Filament/Resources/HostingServerResource.php @@ -0,0 +1,113 @@ + + +schema([ + Forms\Components\Card::make() + ->schema([ + Forms\Components\TextInput::make('name') + ->required() + ->maxLength(255), + Forms\Components\TextInput::make('hostname') + ->required() + ->maxLength(255), + Forms\Components\Select::make('control_panel') + ->options([ + 'cpanel' => 'cPanel', + 'plesk' => 'Plesk', + 'directadmin' => 'DirectAdmin', + 'virtualmin' => 'Virtualmin', + ]) + ->required(), + Forms\Components\TextInput::make('api_token') + ->required() + ->password() + ->maxLength(255), + Forms\Components\TextInput::make('api_url') + ->required() + ->maxLength(255), + Forms\Components\Toggle::make('is_active') + ->default(true), + Forms\Components\TextInput::make('max_accounts') + ->numeric() + ->default(0) + ->minValue(0), + ]) + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('name') + ->searchable(), + Tables\Columns\TextColumn::make('hostname') + ->searchable(), + Tables\Columns\BadgeColumn::make('control_panel') + ->colors([ + 'primary' => 'cpanel', + 'success' => 'plesk', + 'warning' => 'directadmin', + 'danger' => 'virtualmin', + ]), + Tables\Columns\IconColumn::make('is_active') + ->boolean(), + Tables\Columns\TextColumn::make('active_accounts') + ->label('Active/Max Accounts') + ->formatStateUsing(fn ($record) => "{$record->active_accounts}/{$record->max_accounts}"), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + Tables\Filters\SelectFilter::make('control_panel') + ->options([ + 'cpanel' => 'cPanel', + 'plesk' => 'Plesk', + 'directadmin' => 'DirectAdmin', + 'virtualmin' => 'Virtualmin', + ]), + Tables\Filters\TernaryFilter::make('is_active') + ->label('Active Status'), + ]) + ->actions([ + Tables\Actions\EditAction::make(), + Tables\Actions\DeleteAction::make(), + ]) + ->bulkActions([ + Tables\Actions\DeleteBulkAction::make(), + ]); + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListHostingServers::route('/'), + 'create' => Pages\CreateHostingServer::route('/create'), + 'edit' => Pages\EditHostingServer::route('/{record}/edit'), + ]; + } +} \ No newline at end of file diff --git a/app/Services/HostingService.php b/app/Services/HostingService.php index 48eb2f23..524568b8 100644 --- a/app/Services/HostingService.php +++ b/app/Services/HostingService.php @@ -4,40 +4,66 @@ use App\Models\HostingAccount; use App\Models\Products_Service; +use App\Models\HostingServer; use App\Services\ControlPanels\CpanelClient; use App\Services\ControlPanels\PleskClient; use App\Services\ControlPanels\DirectAdminClient; +use App\Services\ControlPanels\VirtualminClient; use App\Services\PricingService; +use Illuminate\Support\Facades\Log; class HostingService { protected $cpanelClient; protected $pleskClient; protected $directAdminClient; + protected $virtualminClient; protected $pricingService; public function __construct( CpanelClient $cpanelClient, PleskClient $pleskClient, DirectAdminClient $directAdminClient, + VirtualminClient $virtualminClient, PricingService $pricingService ) { $this->cpanelClient = $cpanelClient; $this->pleskClient = $pleskClient; $this->directAdminClient = $directAdminClient; + $this->virtualminClient = $virtualminClient; $this->pricingService = $pricingService; } public function provisionAccount(HostingAccount $account, Products_Service $product, array $options = []) { - $client = $this->getClientForControlPanel($account->control_panel); + // Select least loaded server of the specified type + $server = $this->selectServer($product->hosting_server_id); + + if (!$server) { + throw new \Exception('No available servers found'); + } + + $client = $this->getClientForControlPanel($server->control_panel); $price = $this->pricingService->calculatePrice($product, $options); + + // Configure client with server details + $client->setServer($server); + $result = $client->createAccount($account->username, $account->domain, $account->package); if ($result) { $account->status = 'active'; $account->price = $price; + $account->hosting_server_id = $server->id; $account->save(); + + // Increment server account count + $server->increment('active_accounts'); + + Log::info("Provisioned new hosting account", [ + 'account_id' => $account->id, + 'server_id' => $server->id + ]); } return $result; @@ -45,12 +71,17 @@ public function provisionAccount(HostingAccount $account, Products_Service $prod public function suspendAccount(HostingAccount $account) { - $client = $this->getClientForControlPanel($account->control_panel); + $server = HostingServer::findOrFail($account->hosting_server_id); + $client = $this->getClientForControlPanel($server->control_panel); + $client->setServer($server); + $result = $client->suspendAccount($account->username); if ($result) { $account->status = 'suspended'; $account->save(); + + Log::info("Suspended hosting account", ['account_id' => $account->id]); } return $result; @@ -58,12 +89,17 @@ public function suspendAccount(HostingAccount $account) public function unsuspendAccount(HostingAccount $account) { - $client = $this->getClientForControlPanel($account->control_panel); + $server = HostingServer::findOrFail($account->hosting_server_id); + $client = $this->getClientForControlPanel($server->control_panel); + $client->setServer($server); + $result = $client->unsuspendAccount($account->username); if ($result) { $account->status = 'active'; $account->save(); + + Log::info("Unsuspended hosting account", ['account_id' => $account->id]); } return $result; @@ -71,7 +107,10 @@ public function unsuspendAccount(HostingAccount $account) public function upgradeAccount(HostingAccount $account, Products_Service $newProduct, array $options = []) { - $client = $this->getClientForControlPanel($account->control_panel); + $server = HostingServer::findOrFail($account->hosting_server_id); + $client = $this->getClientForControlPanel($server->control_panel); + $client->setServer($server); + $newPrice = $this->pricingService->calculatePrice($newProduct, $options); $result = $client->changePackage($account->username, $newProduct->name); @@ -79,22 +118,36 @@ public function upgradeAccount(HostingAccount $account, Products_Service $newPro $account->package = $newProduct->name; $account->price = $newPrice; $account->save(); + + Log::info("Upgraded hosting account", [ + 'account_id' => $account->id, + 'new_package' => $newProduct->name + ]); } return $result; } - protected function getClientForControlPanel($controlPanel) + protected function selectServer($serverId = null) { - switch ($controlPanel) { - case 'cpanel': - return $this->cpanelClient; - case 'plesk': - return $this->pleskClient; - case 'directadmin': - return $this->directAdminClient; - default: - throw new \Exception("Unsupported control panel: $controlPanel"); + if ($serverId) { + return HostingServer::find($serverId); } + + return HostingServer::where('is_active', true) + ->whereRaw('active_accounts < max_accounts') + ->orderBy('active_accounts') + ->first(); + } + + protected function getClientForControlPanel($controlPanel) + { + return match ($controlPanel) { + 'cpanel' => $this->cpanelClient, + 'plesk' => $this->pleskClient, + 'directadmin' => $this->directAdminClient, + 'virtualmin' => $this->virtualminClient, + default => throw new \Exception("Unsupported control panel: $controlPanel"), + }; } } \ No newline at end of file diff --git a/database/migrations/2024_01_20_000001_create_product_types_table.php b/database/migrations/2024_01_20_000001_create_product_types_table.php new file mode 100644 index 00000000..3ba54202 --- /dev/null +++ b/database/migrations/2024_01_20_000001_create_product_types_table.php @@ -0,0 +1,56 @@ + + +id(); + $table->string('name'); + $table->string('slug')->unique(); + $table->text('description')->nullable(); + $table->boolean('is_recurring')->default(true); + $table->boolean('requires_server')->default(false); + $table->timestamps(); + }); + + // Insert default product types + DB::table('product_types')->insert([ + [ + 'name' => 'One Time', + 'slug' => 'one-time', + 'is_recurring' => false, + 'requires_server' => false, + ], + [ + 'name' => 'Shared Hosting', + 'slug' => 'shared-hosting', + 'is_recurring' => true, + 'requires_server' => true, + ], + [ + 'name' => 'VPS', + 'slug' => 'vps', + 'is_recurring' => true, + 'requires_server' => true, + ], + [ + 'name' => 'Dedicated Server', + 'slug' => 'dedicated', + 'is_recurring' => true, + 'requires_server' => true, + ], + ]); + } + + public function down() + { + Schema::dropIfExists('product_types'); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_01_20_000002_create_hosting_servers_table.php b/database/migrations/2024_01_20_000002_create_hosting_servers_table.php new file mode 100644 index 00000000..298760ba --- /dev/null +++ b/database/migrations/2024_01_20_000002_create_hosting_servers_table.php @@ -0,0 +1,31 @@ + + +id(); + $table->string('name'); + $table->string('hostname'); + $table->enum('control_panel', ['cpanel', 'plesk', 'directadmin', 'virtualmin']); + $table->string('api_token'); + $table->string('api_url'); + $table->boolean('is_active')->default(true); + $table->integer('max_accounts')->default(0); + $table->integer('active_accounts')->default(0); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('hosting_servers'); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_01_20_000003_add_trial_settings_to_products_services_table.php b/database/migrations/2024_01_20_000003_add_trial_settings_to_products_services_table.php new file mode 100644 index 00000000..f37b5f13 --- /dev/null +++ b/database/migrations/2024_01_20_000003_add_trial_settings_to_products_services_table.php @@ -0,0 +1,29 @@ + + +foreignId('product_type_id')->constrained(); + $table->foreignId('hosting_server_id')->nullable()->constrained(); + $table->integer('trial_days')->default(0); + $table->boolean('trial_enabled')->default(false); + }); + } + + public function down() + { + Schema::table('products_services', function (Blueprint $table) { + $table->dropForeign(['product_type_id']); + $table->dropForeign(['hosting_server_id']); + $table->dropColumn(['product_type_id', 'hosting_server_id', 'trial_days', 'trial_enabled']); + }); + } +}; \ No newline at end of file