Quelltext des aktuellen Standes
<?php
$this->layout('layouts/main', [
'page_title' => 'Hosts',
'current_route' => 'vms',
'full_width' => true,
]);
$hosts = $hosts ?? [];
$clusters = $clusters ?? [];
$can_manage_hosts = $can_manage_hosts ?? false;
$hypLabels = [
'vmware_esxi' => 'VMware ESXi',
'proxmox' => 'Proxmox VE',
'hyper_v' => 'Hyper-V',
'kvm' => 'KVM / QEMU',
'xen' => 'Xen',
'other' => 'Sonstige',
];
$statusCfg = [
'online' => ['label' => 'Online', 'cls' => 'status-pill--success'],
'offline' => ['label' => 'Offline', 'cls' => 'status-pill--neutral'],
'maintenance' => ['label' => 'Wartung', 'cls' => 'status-pill--warning'],
];
?>
<div class="vhl-wrap">
<div class="content-header">
<div>
<h1><?= $this->te('Hosts') ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item"><a href="/vms"><?= $this->te('Virtualisierung') ?></a></li>
<li class="breadcrumb-item active"><?= $this->te('Hosts') ?></li>
</ol>
</div>
<?php if ($can_manage_hosts): ?>
<div class="content-header-actions">
<a href="/vms/hosts/create" class="btn btn-primary">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="15" height="15"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
<?= $this->te('Neuer Host') ?>
</a>
</div>
<?php endif; ?>
</div>
<div class="page-body">
<!-- Filter-Card: Sub-Navigation + Filter (wie /orders, /status, /xdr) -->
<div class="card card-outline card-primary mb-3">
<div class="filter-card-body">
<nav class="subnav" style="flex:1 1 100%;margin-bottom:0;">
<a href="/vms" class="subnav-tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
<?= $this->te('VMs') ?>
</a>
<a href="/vms/hosts" class="subnav-tab active">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
<?= $this->te('Hosts') ?>
</a>
<a href="/vms/clusters" class="subnav-tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><circle cx="12" cy="5" r="3"/><circle cx="5" cy="19" r="3"/><circle cx="19" cy="19" r="3"/><line x1="12" y1="8" x2="5" y2="16"/><line x1="12" y1="8" x2="19" y2="16"/></svg>
<?= $this->te('Cluster') ?>
</a>
</nav>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center;flex:1 1 100%;">
<div style="position:relative;flex:1;min-width:200px;">
<input type="search" id="vhlSearch" class="form-control" style="padding-left:2rem;" placeholder="<?= $this->te('Host suchen…') ?>" autocomplete="off">
</div>
<select id="vhlStatus" class="form-control" style="width:auto;">
<option value=""><?= $this->te('Alle Status') ?></option>
<option value="online"><?= $this->te('Online') ?></option>
<option value="offline"><?= $this->te('Offline') ?></option>
<option value="maintenance"><?= $this->te('Wartung') ?></option>
</select>
<?php if (!empty($clusters)): ?>
<select id="vhlCluster" class="form-control" style="width:auto;">
<option value=""><?= $this->te('Alle Cluster') ?></option>
<?php foreach ($clusters as $c): ?>
<option value="<?= $this->e(strtolower($c['name'])) ?>"><?= $this->e($c['name']) ?></option>
<?php endforeach; ?>
</select>
<?php endif; ?>
</div>
</div>
</div>
<!-- Tabelle / Leer -->
<?php if (empty($hosts)): ?>
<div class="empty-state">
<span class="empty-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" width="44" height="44"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
</span>
<h2><?= $this->te('Noch keine Hosts angelegt') ?></h2>
<p><?= $this->te('Legen Sie den ersten Hypervisor-Host an.') ?></p>
<?php if ($can_manage_hosts): ?>
<a href="/vms/hosts/create" class="btn btn-primary"><?= $this->te('Neuen Host anlegen') ?></a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="table-wrapper">
<table class="data-table" id="vhlTable">
<thead>
<tr>
<th scope="col" style="width:120px;"><?= $this->te('Status') ?></th>
<th scope="col"><?= $this->te('Hostname') ?></th>
<th scope="col"><?= $this->te('Hypervisor') ?></th>
<th scope="col"><?= $this->te('IP-Adresse') ?></th>
<th scope="col"><?= $this->te('Cluster') ?></th>
<th scope="col">CPU</th>
<th scope="col">RAM</th>
<th scope="col"><?= $this->te('VMs') ?></th>
<th scope="col"><?= $this->te('Auslastung') ?></th>
<th scope="col" style="width:80px;"></th>
</tr>
</thead>
<tbody>
<?php foreach ($hosts as $h): ?>
<?php
$st = $h['status'] ?? 'online';
$cfg = $statusCfg[$st] ?? ['label' => $st, 'cls' => 'status-pill--neutral'];
$ramTotal = (float)($h['ram_gb'] ?? 0);
$ramAlloc = (float)($h['allocated_ram_gb'] ?? 0);
$cpuTotal = (int)($h['cpu_cores'] ?? 0);
$cpuAlloc = (int)($h['allocated_cpu'] ?? 0);
$ramPct = $ramTotal > 0 ? min(100, round($ramAlloc / $ramTotal * 100)) : 0;
$cpuPct = $cpuTotal > 0 ? min(100, round($cpuAlloc / $cpuTotal * 100)) : 0;
$barClr = fn($p) => $p >= 90 ? '#dc2626' : ($p >= 75 ? '#d97706' : '#16a34a');
?>
<tr class="clickable"
onclick="window.location='/vms/hosts/<?= $h['id'] ?>'"
data-name="<?= $this->e(strtolower($h['name'])) ?>"
data-status="<?= $this->e($st) ?>"
data-cluster="<?= $this->e(strtolower($h['cluster_name'] ?? '')) ?>">
<td>
<span class="status-pill <?= $cfg['cls'] ?>"><?= $cfg['label'] ?></span>
</td>
<td>
<a href="/vms/hosts/<?= $h['id'] ?>" class="vhl-link"><?= $this->e($h['name']) ?></a>
<?php if (!empty($h['location'])): ?>
<div class="vhl-sub"><?= $this->e($h['location']) ?></div>
<?php endif; ?>
</td>
<td>
<span class="vhl-hyp-pill"><?= $this->e($hypLabels[$h['hypervisor'] ?? ''] ?? $h['hypervisor'] ?? '—') ?></span>
</td>
<td class="vhl-mono vhl-muted"><?= $this->e($h['ip_address'] ?: '—') ?></td>
<td class="vhl-muted"><?= $this->e($h['cluster_name'] ?? '—') ?></td>
<td class="vhl-muted"><?= $cpuTotal > 0 ? $cpuTotal . ' Kerne' : '—' ?></td>
<td class="vhl-muted"><?= $ramTotal > 0 ? number_format($ramTotal, 0, ',', '.') . ' GB' : '—' ?></td>
<td class="vhl-muted"><?= (int)($h['vm_count'] ?? 0) ?> VMs</td>
<td>
<?php if ($ramTotal > 0 || $cpuTotal > 0): ?>
<div style="display:flex;flex-direction:column;gap:3px;">
<div style="display:flex;align-items:center;gap:6px;">
<span style="font-size:.68rem;color:var(--gray-400);width:28px;">RAM</span>
<div class="vhl-bar"><div class="vhl-bar-fill" style="width:<?= $ramPct ?>%;background:<?= $barClr($ramPct) ?>;"></div></div>
<span style="font-size:.7rem;color:var(--gray-500);"><?= $ramPct ?>%</span>
</div>
<div style="display:flex;align-items:center;gap:6px;">
<span style="font-size:.68rem;color:var(--gray-400);width:28px;">CPU</span>
<div class="vhl-bar"><div class="vhl-bar-fill" style="width:<?= $cpuPct ?>%;background:<?= $barClr($cpuPct) ?>;"></div></div>
<span style="font-size:.7rem;color:var(--gray-500);"><?= $cpuPct ?>%</span>
</div>
</div>
<?php else: ?>
<span class="vhl-muted">—</span>
<?php endif; ?>
</td>
<td class="vhl-actions" onclick="event.stopPropagation()">
<?php if ($can_manage_hosts): ?>
<a href="/vms/hosts/<?= $h['id'] ?>/edit" class="btn btn-xs btn-default" title="<?= $this->te('Bearbeiten') ?>">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="13" height="13"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
</a>
<form method="POST" action="/vms/hosts/<?= $h['id'] ?>/delete" style="display:inline;"
onsubmit="return confirm('Host "<?= $this->e(addslashes($h['name'])) ?>" wirklich löschen?')">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<button class="btn btn-xs btn-danger" title="<?= $this->te('Löschen') ?>">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="13" height="13"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="vhl-footer">
<span class="vhl-count" id="vhlCount"><?= $this->te(count($hosts) === 1 ? '{anzahl} Host' : '{anzahl} Hosts', ['anzahl' => count($hosts)]) ?></span>
</div>
<?php endif; ?>
</div>
</div>
<script>
(function () {
var search = document.getElementById('vhlSearch');
var stSel = document.getElementById('vhlStatus');
var clSel = document.getElementById('vhlCluster');
var tbody = document.querySelector('#vhlTable tbody');
var count = document.getElementById('vhlCount');
if (!tbody) return;
function filter() {
var q = (search ? search.value : '').toLowerCase();
var st = stSel ? stSel.value : '';
var cl = (clSel ? clSel.value : '').toLowerCase();
var n = 0;
tbody.querySelectorAll('tr').forEach(function(tr) {
var ok = (!q || (tr.dataset.name||'').includes(q))
&& (!st || (tr.dataset.status||'') === st)
&& (!cl || (tr.dataset.cluster||'').includes(cl));
tr.style.display = ok ? '' : 'none';
if (ok) n++;
});
if (count) count.textContent = n + ' Host' + (n !== 1 ? 's' : '');
}
if (search) search.addEventListener('input', filter);
if (stSel) stSel.addEventListener('change', filter);
if (clSel) clSel.addEventListener('change', filter);
})();
</script>
<style>
/* ====================================================
Host-Übersicht · Prefix: vhl-
==================================================== */
.vhl-wrap { display:flex; flex-direction:column; }
.vhl-hyp-pill {
display:inline-block; font-size:0.7rem; font-weight:600; padding:0.15rem 0.5rem;
border-radius:0.25rem; background:var(--color-primary-pale); color:var(--blue-700); white-space:nowrap;
}
.vhl-link { color:var(--gray-900); font-weight:500; text-decoration:none; }
.vhl-link:hover { color:var(--color-primary); text-decoration:underline; }
.vhl-sub { font-size:0.75rem; color:var(--gray-400); margin-top:0.125rem; }
.vhl-muted { color:var(--gray-500); }
.vhl-mono { font-size:0.825rem; font-variant-numeric:tabular-nums; }
.vhl-bar { width:60px; height:5px; border-radius:3px; background:var(--gray-200); overflow:hidden; }
.vhl-bar-fill { height:100%; border-radius:3px; transition:width .3s; }
.vhl-actions { text-align:right; white-space:nowrap; }
.vhl-footer { display:flex; justify-content:space-between; align-items:center; margin-top:0.75rem; padding:0 0.25rem; }
.vhl-count { font-size:0.8rem; color:var(--gray-400); }
</style>