Files
HexaHost-Panel/app/Http/Controllers/Web/VmController.php
2026-05-17 13:26:14 +02:00

216 lines
7.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreVmRequest;
use App\Http\Requests\UpdateVmRequest;
use App\Jobs\ProvisionCustomerJob;
use App\Models\Customer;
use App\Models\IpPool;
use App\Models\User;
use App\Models\VmDevice;
use App\Services\Hosting\Proxmox\ProxmoxClient;
use App\Services\Hosting\Provisioning\DeprovisionService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\View\View;
class VmController extends Controller
{
public function index(Request $request): View
{
$this->authorize('viewAny', Customer::class);
$vms = Customer::query()
->forUser($request->user())
->with(['owner', 'ipPool'])
->when($request->query('status'), fn ($q, $status) => $q->where('status', $status))
->latest()
->paginate(15)
->withQueryString();
return view('vms.index', compact('vms'));
}
public function create(Request $request): View
{
$this->authorize('create', Customer::class);
$isos = [];
try {
$isos = app(ProxmoxClient::class)->listIsos();
} catch (\Throwable) {
// Proxmox nicht erreichbar Formular bleibt nutzbar
}
return view('vms.create', [
'privatePools' => IpPool::query()->where('type', 'private')->where('is_active', true)->get(),
'customers' => $request->user()->isAdmin()
? User::query()->where('role', 'customer')->orderBy('name')->get()
: collect(),
'deviceTypes' => VmDevice::typesFor($request->user()),
'templates' => \App\Models\VmTemplate::query()->where('is_active', true)->orderBy('name')->get(),
'isos' => $isos,
]);
}
public function store(StoreVmRequest $request): RedirectResponse
{
$domain = $request->domain();
if ($domain && Customer::query()->where('domain', $domain)->exists()) {
return back()->withErrors(['subdomain' => 'Diese Subdomain ist bereits vergeben.'])->withInput();
}
if (! $domain) {
$domain = 'direct-'.Str::slug($request->validated('name')).'-'.Str::lower(Str::random(6)).'.internal';
}
$vm = Customer::query()->create([
'user_id' => $request->ownerId(),
'name' => $request->validated('name'),
'domain' => $domain,
'behind_traefik' => $request->boolean('behind_traefik'),
'ip_pool_id' => $request->input('ip_pool_id'),
'cpu' => $request->integer('cpu'),
'ram' => $request->integer('ram'),
'disk' => $request->integer('disk'),
'attached_iso' => $request->validated('install_iso'),
'status' => 'pending',
'provisioning_step' => 'queued',
]);
$this->syncDevices($vm, $request->input('devices', []));
ProvisionCustomerJob::dispatch($vm->id);
return redirect()
->route('vms.show', $vm)
->with('success', 'VM-Provisioning wurde gestartet.');
}
public function show(Request $request, Customer $vm): View
{
$this->authorize('view', $vm);
$vm->load([
'owner', 'ipPool', 'devices',
'snapshots' => fn ($q) => $q->latest(),
'backups' => fn ($q) => $q->latest()->limit(10),
'firewallRules',
'metrics' => fn ($q) => $q->limit(48),
'activityLogs' => fn ($q) => $q->limit(15)->with('user'),
]);
$isos = [];
$liveStatus = null;
if ($vm->vmid && $vm->status === 'active') {
try {
$proxmox = app(ProxmoxClient::class);
$isos = $proxmox->listIsos();
} catch (\Throwable) {
//
}
try {
$proxmox ??= app(ProxmoxClient::class);
$liveStatus = $proxmox->normalizeLiveStatus($proxmox->getVMStatus((int) $vm->vmid));
$vm->update([
'proxmox_status' => $liveStatus['status'],
'proxmox_uptime' => $liveStatus['uptime'],
'proxmox_status_at' => now(),
]);
} catch (\Throwable) {
//
}
}
return view('vms.show', compact('vm', 'isos', 'liveStatus'));
}
public function edit(Request $request, Customer $vm): View
{
$this->authorize('update', $vm);
$vm->load('devices');
return view('vms.edit', [
'vm' => $vm,
'deviceTypes' => VmDevice::typesFor($request->user()),
'templates' => \App\Models\VmTemplate::query()->where('is_active', true)->orderBy('name')->get(),
]);
}
public function update(UpdateVmRequest $request, Customer $vm): RedirectResponse
{
$vm->update($request->only(['name', 'cpu', 'ram', 'disk']));
if ($request->has('devices')) {
$vm->devices()->delete();
$this->syncDevices($vm, $request->input('devices', []));
}
if ($vm->vmid && $vm->status === 'active') {
try {
app(\App\Services\Hosting\Proxmox\ProxmoxClient::class)
->updateVmResources((int) $vm->vmid, $vm->cpu, $vm->ram, $vm->disk);
app(\App\Services\Hosting\Proxmox\ProxmoxClient::class)
->applyDevices((int) $vm->vmid, $vm->devices()->get());
} catch (\Throwable $e) {
return back()->with('warning', 'DB gespeichert, Proxmox-Update fehlgeschlagen: '.$e->getMessage());
}
}
return redirect()->route('vms.show', $vm)->with('success', 'VM-Konfiguration gespeichert.');
}
public function destroy(Request $request, Customer $vm, DeprovisionService $deprovision, \App\Services\Hosting\Snapshots\SnapshotService $snapshots): RedirectResponse
{
$this->authorize('delete', $vm);
if ($vm->vmid && $vm->status === 'active') {
try {
$snapshots->autoBeforeDestructive($vm, $request->user(), 'delete');
} catch (\Throwable) {
//
}
}
if ($vm->status === 'pending' && $vm->provisioning_step === 'queued') {
if ($vm->vmid) {
app(\App\Services\Hosting\Provisioning\VmidReservationService::class)
->scheduleRelease((int) $vm->vmid, $vm);
}
$vm->devices()->delete();
$vm->delete();
return redirect()->route('vms.index')->with('success', 'VM-Eintrag gelöscht.');
}
$deprovision->removeVmOnly($vm, $request->user());
return redirect()->route('vms.index')->with('success', 'VM wurde entfernt. VMID wird nach 48h freigegeben.');
}
private function syncDevices(Customer $vm, array $devices): void
{
$allowed = array_keys(VmDevice::typesFor(auth()->user()));
foreach ($devices as $index => $device) {
if (empty($device['type']) || ! in_array($device['type'], $allowed, true)) {
continue;
}
$vm->devices()->create([
'type' => $device['type'],
'slot' => $device['slot'] ?? null,
'config' => $device['config'] ?? [],
'sort_order' => $index,
]);
}
}
}