initial commit
This commit is contained in:
129
app/Models/Customer.php
Normal file
129
app/Models/Customer.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Customer extends Model
|
||||
{
|
||||
protected $table = 'customers';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'ip_pool_id',
|
||||
'name',
|
||||
'domain',
|
||||
'vmid',
|
||||
'ip_address',
|
||||
'public_ip',
|
||||
'behind_traefik',
|
||||
'provision_mode',
|
||||
'hosting_plan_id',
|
||||
'whmcs_service_id',
|
||||
'cpu',
|
||||
'ram',
|
||||
'disk',
|
||||
'attached_iso',
|
||||
'proxmox_status',
|
||||
'proxmox_uptime',
|
||||
'proxmox_status_at',
|
||||
'status',
|
||||
'provisioning_step',
|
||||
'error_message',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'vmid' => 'integer',
|
||||
'cpu' => 'integer',
|
||||
'ram' => 'integer',
|
||||
'disk' => 'integer',
|
||||
'behind_traefik' => 'boolean',
|
||||
'proxmox_uptime' => 'integer',
|
||||
'proxmox_status_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function owner(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function ipPool(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(IpPool::class, 'ip_pool_id');
|
||||
}
|
||||
|
||||
public function plan(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(HostingPlan::class, 'hosting_plan_id');
|
||||
}
|
||||
|
||||
public function whmcsService(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(WhmcsService::class, 'whmcs_service_id');
|
||||
}
|
||||
|
||||
public function vmidReservation(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(VmidReservation::class);
|
||||
}
|
||||
|
||||
public function devices(): HasMany
|
||||
{
|
||||
return $this->hasMany(VmDevice::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function snapshots(): HasMany
|
||||
{
|
||||
return $this->hasMany(VmSnapshot::class);
|
||||
}
|
||||
|
||||
public function backups(): HasMany
|
||||
{
|
||||
return $this->hasMany(VmBackup::class);
|
||||
}
|
||||
|
||||
public function firewallRules(): HasMany
|
||||
{
|
||||
return $this->hasMany(VmFirewallRule::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function metrics(): HasMany
|
||||
{
|
||||
return $this->hasMany(VmMetric::class)->orderByDesc('recorded_at');
|
||||
}
|
||||
|
||||
public function activityLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(VmActivityLog::class)->latest();
|
||||
}
|
||||
|
||||
public function isRunning(): bool
|
||||
{
|
||||
return $this->proxmox_status === 'running';
|
||||
}
|
||||
|
||||
public function scopeForUser(Builder $query, User $user): Builder
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $query->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
public function isProvisionable(): bool
|
||||
{
|
||||
return in_array($this->status, ['pending', 'failed'], true);
|
||||
}
|
||||
|
||||
public function displayName(): string
|
||||
{
|
||||
return $this->name.($this->vmid ? " (#{$this->vmid})" : '');
|
||||
}
|
||||
}
|
||||
30
app/Models/CustomerIsoUpload.php
Normal file
30
app/Models/CustomerIsoUpload.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class CustomerIsoUpload extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'filename',
|
||||
'volid',
|
||||
'size_bytes',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'size_bytes' => 'integer',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
41
app/Models/HostingPlan.php
Normal file
41
app/Models/HostingPlan.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class HostingPlan extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
'name',
|
||||
'cpu',
|
||||
'ram',
|
||||
'disk',
|
||||
'max_backups',
|
||||
'allow_public_ip',
|
||||
'allow_iso_upload',
|
||||
'is_active',
|
||||
'whmcs_product_id',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'cpu' => 'integer',
|
||||
'ram' => 'integer',
|
||||
'disk' => 'integer',
|
||||
'max_backups' => 'integer',
|
||||
'allow_public_ip' => 'boolean',
|
||||
'allow_iso_upload' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
'whmcs_product_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function customers(): HasMany
|
||||
{
|
||||
return $this->hasMany(Customer::class, 'hosting_plan_id');
|
||||
}
|
||||
}
|
||||
74
app/Models/IpPool.php
Normal file
74
app/Models/IpPool.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\IpPoolType;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class IpPool extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'type',
|
||||
'start_ip',
|
||||
'end_ip',
|
||||
'gateway',
|
||||
'cidr',
|
||||
'description',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'type' => IpPoolType::class,
|
||||
'cidr' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function vms(): HasMany
|
||||
{
|
||||
return $this->hasMany(Customer::class, 'ip_pool_id');
|
||||
}
|
||||
|
||||
public function totalIps(): int
|
||||
{
|
||||
$start = ip2long($this->start_ip);
|
||||
$end = ip2long($this->end_ip);
|
||||
|
||||
if ($start === false || $end === false || $end < $start) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) ($end - $start + 1);
|
||||
}
|
||||
|
||||
public function usedIpsCount(): int
|
||||
{
|
||||
$query = Customer::query()
|
||||
->where('ip_pool_id', $this->id)
|
||||
->whereIn('status', ['pending', 'active']);
|
||||
|
||||
if ($this->type === IpPoolType::Public) {
|
||||
return $query->whereNotNull('public_ip')->count();
|
||||
}
|
||||
|
||||
return $query->whereNotNull('ip_address')->count();
|
||||
}
|
||||
|
||||
public function freeIpsCount(): int
|
||||
{
|
||||
return max(0, $this->totalIps() - $this->usedIpsCount());
|
||||
}
|
||||
|
||||
public function containsIp(string $ip): bool
|
||||
{
|
||||
$long = ip2long($ip);
|
||||
$start = ip2long($this->start_ip);
|
||||
$end = ip2long($this->end_ip);
|
||||
|
||||
return $long !== false && $long >= $start && $long <= $end;
|
||||
}
|
||||
}
|
||||
32
app/Models/SystemSetting.php
Normal file
32
app/Models/SystemSetting.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class SystemSetting extends Model
|
||||
{
|
||||
protected $primaryKey = 'key';
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $fillable = ['key', 'value'];
|
||||
|
||||
public static function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return Cache::remember("setting.{$key}", 300, function () use ($key, $default) {
|
||||
$row = static::query()->find($key);
|
||||
|
||||
return $row?->value ?? $default;
|
||||
});
|
||||
}
|
||||
|
||||
public static function set(string $key, mixed $value): void
|
||||
{
|
||||
static::query()->updateOrCreate(['key' => $key], ['value' => (string) $value]);
|
||||
Cache::forget("setting.{$key}");
|
||||
}
|
||||
}
|
||||
46
app/Models/User.php
Normal file
46
app/Models/User.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
#[Fillable(['name', 'email', 'password', 'role', 'is_active', 'two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at'])]
|
||||
#[Hidden(['password', 'remember_token', 'two_factor_secret', 'two_factor_recovery_codes'])]
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'role' => UserRole::class,
|
||||
'is_active' => 'boolean',
|
||||
'two_factor_confirmed_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function vms(): HasMany
|
||||
{
|
||||
return $this->hasMany(Customer::class, 'user_id');
|
||||
}
|
||||
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->role === UserRole::Admin;
|
||||
}
|
||||
|
||||
public function isCustomer(): bool
|
||||
{
|
||||
return $this->role === UserRole::Customer;
|
||||
}
|
||||
}
|
||||
35
app/Models/VmActivityLog.php
Normal file
35
app/Models/VmActivityLog.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class VmActivityLog extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'customer_id',
|
||||
'user_id',
|
||||
'action',
|
||||
'status',
|
||||
'message',
|
||||
'meta',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'meta' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function vm(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
26
app/Models/VmBackup.php
Normal file
26
app/Models/VmBackup.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class VmBackup extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'customer_id', 'user_id', 'storage', 'volume_id', 'status', 'size_bytes', 'completed_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'size_bytes' => 'integer',
|
||||
'completed_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function vm(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
}
|
||||
58
app/Models/VmDevice.php
Normal file
58
app/Models/VmDevice.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class VmDevice extends Model
|
||||
{
|
||||
public const TYPE_DISK = 'disk';
|
||||
|
||||
public const TYPE_NETWORK = 'network';
|
||||
|
||||
public const TYPE_USB = 'usb';
|
||||
|
||||
public const TYPE_PCI = 'pci';
|
||||
|
||||
public static function types(): array
|
||||
{
|
||||
return [
|
||||
self::TYPE_DISK => 'Zusätzliche Festplatte',
|
||||
self::TYPE_NETWORK => 'Netzwerk-Interface',
|
||||
self::TYPE_USB => 'USB-Gerät',
|
||||
self::TYPE_PCI => 'PCI Passthrough',
|
||||
];
|
||||
}
|
||||
|
||||
public static function typesFor(?\App\Models\User $user = null): array
|
||||
{
|
||||
$types = self::types();
|
||||
if ($user && ! $user->isAdmin()) {
|
||||
return array_intersect_key($types, array_flip([self::TYPE_DISK, self::TYPE_NETWORK]));
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
'customer_id',
|
||||
'type',
|
||||
'slot',
|
||||
'config',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'config' => 'array',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function vm(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
}
|
||||
26
app/Models/VmFirewallRule.php
Normal file
26
app/Models/VmFirewallRule.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class VmFirewallRule extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'customer_id', 'direction', 'action', 'protocol', 'port', 'source', 'is_active', 'sort_order',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function vm(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
}
|
||||
32
app/Models/VmMetric.php
Normal file
32
app/Models/VmMetric.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class VmMetric extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'customer_id', 'cpu', 'mem', 'maxmem', 'disk', 'maxdisk', 'recorded_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'cpu' => 'float',
|
||||
'mem' => 'integer',
|
||||
'maxmem' => 'integer',
|
||||
'disk' => 'integer',
|
||||
'maxdisk' => 'integer',
|
||||
'recorded_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function vm(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
}
|
||||
26
app/Models/VmSnapshot.php
Normal file
26
app/Models/VmSnapshot.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class VmSnapshot extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'customer_id', 'name', 'proxmox_snapshot_id', 'auto_created', 'expires_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'auto_created' => 'boolean',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function vm(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
}
|
||||
20
app/Models/VmTemplate.php
Normal file
20
app/Models/VmTemplate.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class VmTemplate extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'slug', 'name', 'proxmox_template_vmid', 'os_family', 'is_active',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'proxmox_template_vmid' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
36
app/Models/VmidReservation.php
Normal file
36
app/Models/VmidReservation.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class VmidReservation extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'vmid',
|
||||
'customer_id',
|
||||
'status',
|
||||
'release_at',
|
||||
'released_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'vmid' => 'integer',
|
||||
'release_at' => 'datetime',
|
||||
'released_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function customer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
public function isBlocking(int $vmid): bool
|
||||
{
|
||||
return in_array($this->status, ['reserved', 'active', 'pending_release'], true);
|
||||
}
|
||||
}
|
||||
45
app/Models/WhmcsService.php
Normal file
45
app/Models/WhmcsService.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class WhmcsService extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'whmcs_service_id',
|
||||
'whmcs_client_id',
|
||||
'whmcs_order_id',
|
||||
'customer_id',
|
||||
'user_id',
|
||||
'hosting_plan_id',
|
||||
'status',
|
||||
'config',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'whmcs_service_id' => 'integer',
|
||||
'whmcs_client_id' => 'integer',
|
||||
'whmcs_order_id' => 'integer',
|
||||
'config' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function customer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function plan(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(HostingPlan::class, 'hosting_plan_id');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user