OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

hosts_index.php

14 KB
<?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 &quot;<?= $this->e(addslashes($h['name'])) ?>&quot; 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>