216 lines
7.3 KiB
PHP
216 lines
7.3 KiB
PHP
<?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,
|
||
]);
|
||
}
|
||
}
|
||
}
|