- Introduced `prepareForValidation` method to handle input merging for `behind_traefik` and `devices`. - Updated validation rules to conditionally require `subdomain` based on `behind_traefik`. - Added custom attribute names and error messages for better user feedback. - Improved the create VM form to dynamically show/hide subdomain fields based on the `behind_traefik` checkbox state. - Adjusted the user selection logic to display a message when no customers are available.
113 lines
6.1 KiB
PHP
113 lines
6.1 KiB
PHP
@extends('layouts.app')
|
||
@section('title', 'VM erstellen')
|
||
@section('heading', 'Neue VM erstellen')
|
||
|
||
@section('content')
|
||
<form method="POST" action="{{ route('vms.store') }}" class="max-w-3xl space-y-6">
|
||
@csrf
|
||
|
||
<section class="rounded-xl border border-slate-800 bg-slate-900/60 p-6 space-y-4">
|
||
<h2 class="font-semibold">Allgemein</h2>
|
||
<label class="block">
|
||
<span class="text-sm text-slate-400">VM-Name</span>
|
||
<input name="name" value="{{ old('name') }}" required class="mt-1 w-full rounded-lg border border-slate-700 bg-slate-800 px-3 py-2">
|
||
</label>
|
||
|
||
@if(auth()->user()->isAdmin())
|
||
<label class="block">
|
||
<span class="text-sm text-slate-400">Zugewiesener Benutzer</span>
|
||
<select name="user_id" class="mt-1 w-full rounded-lg border border-slate-700 bg-slate-800 px-3 py-2">
|
||
<option value="">— Mir selbst (Admin) —</option>
|
||
@foreach($customers as $c)
|
||
<option value="{{ $c->id }}" @selected(old('user_id') == $c->id)>{{ $c->name }} ({{ $c->email }})</option>
|
||
@endforeach
|
||
</select>
|
||
@if($customers->isEmpty())
|
||
<p class="mt-1 text-xs text-slate-500">Noch keine Kundenkonten – VM wird deinem Admin-Konto zugeordnet. Kunden unter „Benutzer“ anlegen.</p>
|
||
@endif
|
||
</label>
|
||
@endif
|
||
|
||
@php
|
||
$behindTraefik = filter_var(old('behind_traefik', '1'), FILTER_VALIDATE_BOOLEAN);
|
||
@endphp
|
||
<label class="flex items-center gap-2">
|
||
<input type="checkbox" name="behind_traefik" value="1" @checked($behindTraefik) id="behind_traefik" class="rounded border-slate-600 text-cyan-500">
|
||
<span class="text-sm">Hinter Traefik (Subdomain + DNS)</span>
|
||
</label>
|
||
|
||
<div id="traefik-fields" @if(!$behindTraefik) style="display:none" @endif>
|
||
<label class="block">
|
||
<span class="text-sm text-slate-400">Subdomain <span class="text-red-400">*</span></span>
|
||
<div class="mt-1 flex">
|
||
<input name="subdomain" id="subdomain" value="{{ old('subdomain') }}" placeholder="meine-vm"
|
||
class="w-full rounded-l-lg border border-slate-700 bg-slate-800 px-3 py-2 @error('subdomain') border-red-500 @enderror">
|
||
<span class="flex items-center rounded-r-lg border border-l-0 border-slate-700 bg-slate-800 px-3 text-sm text-slate-500">.{{ config('hosting.plesk.base_domain') }}</span>
|
||
</div>
|
||
@error('subdomain')<p class="mt-1 text-xs text-red-400">{{ $message }}</p>@enderror
|
||
<p class="mt-1 text-xs text-slate-500">Pflichtfeld bei „Hinter Traefik“ (nur a-z, 0-9, Bindestrich).</p>
|
||
</label>
|
||
</div>
|
||
|
||
<label class="block">
|
||
<span class="text-sm text-slate-400">Privater IP-Pool</span>
|
||
<select name="ip_pool_id" class="mt-1 w-full rounded-lg border border-slate-700 bg-slate-800 px-3 py-2">
|
||
<option value="">Standard-Pool</option>
|
||
@foreach($privatePools as $pool)
|
||
<option value="{{ $pool->id }}" @selected(old('ip_pool_id') == $pool->id)>{{ $pool->name }} ({{ $pool->freeIpsCount() }} frei)</option>
|
||
@endforeach
|
||
</select>
|
||
</label>
|
||
<p class="text-xs text-slate-500">Ohne Traefik wird zusätzlich eine öffentliche IP aus dem Public-Pool vergeben.</p>
|
||
</section>
|
||
|
||
<section class="rounded-xl border border-slate-800 bg-slate-900/60 p-6 space-y-4">
|
||
<h2 class="font-semibold">Ressourcen</h2>
|
||
<div class="grid gap-4 sm:grid-cols-3">
|
||
<label><span class="text-sm text-slate-400">vCPUs</span>
|
||
<input type="number" name="cpu" value="{{ old('cpu', config('hosting.defaults.cpu')) }}" min="1" max="32" class="mt-1 w-full rounded-lg border border-slate-700 bg-slate-800 px-3 py-2"></label>
|
||
<label><span class="text-sm text-slate-400">RAM (MB)</span>
|
||
<input type="number" name="ram" value="{{ old('ram', config('hosting.defaults.ram')) }}" min="512" class="mt-1 w-full rounded-lg border border-slate-700 bg-slate-800 px-3 py-2"></label>
|
||
<label><span class="text-sm text-slate-400">Disk (GB)</span>
|
||
<input type="number" name="disk" value="{{ old('disk', config('hosting.defaults.disk')) }}" min="10" class="mt-1 w-full rounded-lg border border-slate-700 bg-slate-800 px-3 py-2"></label>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="rounded-xl border border-slate-800 bg-slate-900/60 p-6 space-y-4">
|
||
<h2 class="font-semibold">Installations-ISO (optional)</h2>
|
||
<p class="text-xs text-slate-500">ISO wird beim Provisioning als CD-ROM eingebunden (Boot-Reihenfolge: CD zuerst).</p>
|
||
<select name="install_iso" class="w-full rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-sm">
|
||
<option value="">Keine ISO / Cloud-Init</option>
|
||
@foreach($isos as $iso)
|
||
<option value="{{ $iso['volid'] }}" @selected(old('install_iso') === $iso['volid'])>{{ $iso['label'] }}</option>
|
||
@endforeach
|
||
</select>
|
||
@if(empty($isos))
|
||
<p class="text-xs text-amber-400">Keine ISOs von Proxmox geladen – Storage {{ config('hosting.proxmox.iso_storage') }} prüfen.</p>
|
||
@endif
|
||
</section>
|
||
|
||
<section class="rounded-xl border border-slate-800 bg-slate-900/60 p-6">
|
||
<h2 class="mb-4 font-semibold">Zusätzliche Geräte</h2>
|
||
@include('partials.vm-device-fields', ['deviceTypes' => $deviceTypes])
|
||
</section>
|
||
|
||
<button type="submit" class="rounded-lg bg-cyan-600 px-6 py-2.5 font-medium hover:bg-cyan-500">VM provisionieren</button>
|
||
</form>
|
||
|
||
@push('scripts')
|
||
<script>
|
||
const cb = document.getElementById('behind_traefik');
|
||
const fields = document.getElementById('traefik-fields');
|
||
const subdomain = document.getElementById('subdomain');
|
||
function toggle() {
|
||
const on = cb.checked;
|
||
fields.style.display = on ? 'block' : 'none';
|
||
if (subdomain) subdomain.required = on;
|
||
}
|
||
cb?.addEventListener('change', toggle);
|
||
toggle();
|
||
</script>
|
||
@endpush
|
||
@endsection
|