Skip to content

Commit

Permalink
Merge pull request #331 from liberu-billing/sweep/Enhance-Hosting-Inf…
Browse files Browse the repository at this point in the history
…rastructure-with-Multi-Server-Support-and-Product-Type-Management

Enhance Hosting Infrastructure with Multi-Server Support and Product Type Management
  • Loading branch information
curtisdelicata authored Dec 25, 2024
2 parents ceaec3a + 0066fb4 commit 57e892a
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 14 deletions.
113 changes: 113 additions & 0 deletions app/Filament/Resources/HostingServerResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@


<?php

namespace App\Filament\Resources;

use App\Filament\Resources\HostingServerResource\Pages;
use App\Models\HostingServer;
use Filament\Forms;
use Filament\Resources\Form;
use Filament\Resources\Resource;
use Filament\Resources\Table;
use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;

class HostingServerResource extends Resource
{
protected static ?string $model = HostingServer::class;

protected static ?string $navigationIcon = 'heroicon-o-server';
protected static ?string $navigationGroup = 'Hosting';

public static function form(Form $form): Form
{
return $form
->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'),
];
}
}
81 changes: 67 additions & 14 deletions app/Services/HostingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,97 +4,150 @@

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;
}

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;
}

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;
}

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);

if ($result) {
$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"),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up()
{
Schema::create('product_types', function (Blueprint $table) {
$table->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');
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up()
{
Schema::create('hosting_servers', function (Blueprint $table) {
$table->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');
}
};
Loading

0 comments on commit 57e892a

Please sign in to comment.