OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

address_show.php

21 KB
<?php $this->layout('layouts/main', ['page_title' => $page_title, 'current_route' => $current_route, 'full_width' => true]); ?>
<?php
$statusPills = ['active'=>['status-pill--success','status-dot--success','Aktiv'],'reserved'=>['status-pill--warning','status-dot--warning','Reserviert'],'offline'=>['status-pill--neutral','','Offline'],'free'=>['status-pill--info','','Frei']];
[$spill,$sdot,$slbl] = $statusPills[$address['status']] ?? ['status-pill--neutral','','?'];
?>
<style>
a { text-decoration:none; } a:hover { text-decoration:none; }
.addr-layout { display:grid; grid-template-columns:1fr 280px; gap:1.75rem; align-items:start; }
.addr-info-list { display:flex; flex-direction:column; gap:0.75rem; }
.addr-info-row { display:flex; flex-direction:column; gap:0.15rem; }
.addr-info-label { font-size:0.72rem; font-weight:700; text-transform:uppercase; letter-spacing:0.05em; color:var(--gray-400); }
.addr-info-value { font-size:0.88rem; color:var(--gray-700); }
.addr-info-grid { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }

/* Detail-Liste */
.addr-detail-row { display:flex; gap:1rem; padding:0.6rem 0; border-bottom:1px solid var(--gray-50); align-items:baseline; }
.addr-detail-row:last-child { border-bottom:none; }
.addr-detail-key { font-size:0.78rem; font-weight:700; color:var(--gray-500); min-width:120px; flex-shrink:0; }
.addr-detail-val { font-size:0.88rem; color:var(--gray-700); flex:1; word-break:break-word; white-space:pre-wrap; }

/* Verlauf / Historie */
.addr-hist-list { display:flex; flex-direction:column; }
.addr-hist-item { padding:0.7rem 0; border-bottom:1px solid var(--gray-100); }
.addr-hist-item:last-child { border-bottom:none; }
.addr-hist-meta { display:flex; align-items:center; gap:0.5rem; flex-wrap:wrap; margin-bottom:0.35rem; }
.addr-hist-badge { font-size:0.66rem; font-weight:700; text-transform:uppercase; letter-spacing:0.04em; padding:0.1rem 0.5rem; border-radius:9999px; }
.addr-hist-time { font-size:0.76rem; color:var(--gray-500); }
.addr-hist-user { font-size:0.76rem; color:var(--gray-400); }
.addr-hist-body { display:flex; flex-direction:column; gap:0.2rem; }
.addr-hist-change { font-size:0.83rem; color:var(--gray-700); line-height:1.5; }
.addr-hist-field { font-weight:600; color:var(--gray-500); }
.addr-hist-old { color:var(--gray-400); text-decoration:line-through; }
.addr-hist-arrow { color:var(--gray-400); margin:0 0.15rem; }
.addr-hist-new { font-weight:600; color:var(--gray-900); }

/* Confirm-Modal: zentrale .modal-*-Komponenten aus app.css; lokal nur Zustands-Toggle und Maße */
.ipam-modal { display:none; z-index:1080; }
.ipam-modal.open { display:flex; }
.ipam-modal .modal-card { max-width:420px; }
.ipam-modal-text { font-size:0.88rem; color:var(--gray-500); margin:0; line-height:1.5; }

@media(max-width:960px) {
    .addr-layout { grid-template-columns:1fr; }
    .addr-info-grid { grid-template-columns:1fr; }
}
</style>

<div>
    <!-- Content Header -->
    <div class="content-header">
        <div>
            <h1 style="font-family:monospace;"><?= $this->e($address['ip_address']) ?></h1>
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
                <li class="breadcrumb-item"><a href="/ipam">IPAM</a></li>
                <li class="breadcrumb-item"><a href="/ipam/subnets/<?= $address['subnet_id'] ?>"><?= $this->e($address['subnet_name']) ?></a></li>
                <li class="breadcrumb-item active"><?= $this->e($address['ip_address']) ?></li>
            </ol>
        </div>
        <?php if ($can_manage_ips): ?>
        <div class="content-header-actions">
            <span class="status-pill <?= $spill ?>">
                <span class="status-dot <?= $sdot ?>"></span>
                <?= $slbl ?>
            </span>
            <a href="/ipam/addresses/<?= $address['id'] ?>/edit" class="btn btn-default btn-sm"><?= $this->te('Bearbeiten') ?></a>
            <button type="button" class="btn btn-danger btn-sm"
                    onclick="ipamConfirm('IP-Adresse löschen?', 'Die Adresse <?= $this->e($address['ip_address']) ?> wird unwiderruflich gelöscht.', function(){ document.getElementById('addrDeleteForm').submit(); })">
                <?= $this->te('Löschen') ?>
            </button>
            <form method="POST" action="/ipam/addresses/<?= $address['id'] ?>/delete" id="addrDeleteForm" style="display:none;">
                <input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
            </form>
        </div>
        <?php endif; ?>
    </div>

    <div class="page-body addr-layout">
        <div>
            <!-- Basisinfos -->
            <div class="detail-card">
                <div class="detail-card-header"><span class="detail-card-title"><?= $this->te('Allgemein') ?></span></div>
                <div class="detail-card-body">
                    <div class="addr-info-grid">
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('IP-Adresse') ?></span>
                            <span class="addr-info-value" style="font-family:monospace;font-weight:700;"><?= $this->e($address['ip_address']) ?></span>
                        </div>
                        <?php if ($address['hostname']): ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Hostname') ?></span>
                            <span class="addr-info-value" style="font-weight:600;"><?= $this->e($address['hostname']) ?></span>
                        </div>
                        <?php endif; ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Typ') ?></span>
                            <span class="addr-info-value"><?= $this->e($type_labels[$address['type']] ?? $address['type']) ?></span>
                        </div>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Status') ?></span>
                            <span class="addr-info-value"><?= $this->e($status_labels[$address['status']] ?? $address['status']) ?></span>
                        </div>
                        <?php if ($address['mac_address']): ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('MAC-Adresse') ?></span>
                            <span class="addr-info-value" style="font-family:monospace;"><?= $this->e($address['mac_address']) ?></span>
                        </div>
                        <?php endif; ?>
                        <?php if (!empty($address['asset_id'])): ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Verknüpftes Asset') ?></span>
                            <span class="addr-info-value">
                                <a href="/inventory/<?= (int)$address['asset_id'] ?>"><?= $this->e($address['linked_asset_inventory'] ?? ('Asset #' . (int)$address['asset_id'])) ?></a>
                                <?php if (!empty($address['linked_asset_serial'])): ?>
                                    <span style="color:var(--gray-500);">(S/N: <?= $this->e($address['linked_asset_serial']) ?>)</span>
                                <?php endif; ?>
                            </span>
                        </div>
                        <?php endif; ?>
                        <?php if ($address['responsible']): ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Verantwortlich') ?></span>
                            <span class="addr-info-value"><?= $this->e($address['responsible']) ?></span>
                        </div>
                        <?php endif; ?>
                        <?php if (!empty($address['last_seen_at'])): ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Zuletzt online') ?></span>
                            <span class="addr-info-value"><?= $this->te('{zeitpunkt} Uhr', ['zeitpunkt' => date('d.m.Y H:i', strtotime($address['last_seen_at']))]) ?></span>
                        </div>
                        <?php endif; ?>
                        <?php if (!empty($address['subnet_gateway']) && $address['subnet_gateway'] === $address['ip_address']): ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Rolle') ?></span>
                            <span class="addr-info-value"><span class="status-pill status-pill--purple"><?= $this->te('Gateway') ?></span></span>
                        </div>
                        <?php endif; ?>
                    </div>
                    <?php if ($address['description']): ?>
                    <div style="margin-top:1rem;padding-top:1rem;border-top:1px solid var(--gray-100);">
                        <span class="addr-info-label"><?= $this->te('Beschreibung') ?></span>
                        <p style="font-size:0.88rem;color:var(--gray-700);margin:0.35rem 0 0;white-space:pre-line;"><?= $this->e($address['description']) ?></p>
                    </div>
                    <?php endif; ?>
                </div>
            </div>

            <!-- Zusatzinfos / Server-Details -->
            <?php if (!empty($details)): ?>
            <div class="detail-card">
                <div class="detail-card-header"><span class="detail-card-title"><?= $this->te('Details') ?></span></div>
                <div class="detail-card-body" style="padding:0.5rem 1.25rem;">
                    <?php foreach ($details as $d): ?>
                    <div class="addr-detail-row">
                        <span class="addr-detail-key"><?= $this->e($d['detail_key']) ?></span>
                        <span class="addr-detail-val"><?= $this->e($d['detail_val'] ?? '') ?></span>
                    </div>
                    <?php endforeach; ?>
                </div>
            </div>
            <?php endif; ?>

            <!-- Änderungs-Historie -->
            <div class="detail-card">
                <div class="detail-card-header"><span class="detail-card-title"><?= $this->te('Verlauf') ?></span></div>
                <div class="detail-card-body" style="padding:<?= empty($history) ? '1.25rem' : '0.5rem 1.25rem 1rem' ?>;">
                    <?php if (empty($history)): ?>
                        <p style="font-size:0.85rem;color:var(--gray-400);margin:0;"><?= $this->te('Noch keine Änderungen protokolliert.') ?></p>
                    <?php else:
                        // Wertanzeige: Status/Typ in Klartext-Labels übersetzen
                        $fmtVal = function ($field, $val) use ($status_labels, $type_labels) {
                            if ($val === null || $val === '') return '—';
                            if ($field === 'status') return $status_labels[$val] ?? $val;
                            if ($field === 'type')   return $type_labels[$val] ?? $val;
                            return $val;
                        };
                        $srcStyle = [
                            'manual'     => 'background:#e0e7ff;color:#3730a3;',
                            'web_scan'   => 'background:#cffafe;color:#155e75;',
                            'scan_agent' => 'background:#dcfce7;color:#15803d;',
                            'system'     => 'background:#f3f4f6;color:#6b7280;',
                        ];
                    ?>
                    <div class="addr-hist-list">
                        <?php foreach ($history as $h):
                            $src = $h['source'] ?? 'system';
                            $changes = json_decode($h['changes'] ?? '[]', true) ?: [];
                        ?>
                        <div class="addr-hist-item">
                            <div class="addr-hist-meta">
                                <span class="addr-hist-badge" style="<?= $srcStyle[$src] ?? $srcStyle['system'] ?>"><?= $this->e($source_labels[$src] ?? $src) ?></span>
                                <span class="addr-hist-time"><?= $this->te('{zeitpunkt} Uhr', ['zeitpunkt' => date('d.m.Y H:i', strtotime($h['created_at']))]) ?></span>
                                <?php if (!empty($h['user_name'])): ?>
                                    <span class="addr-hist-user">· <?= $this->e($h['user_name']) ?></span>
                                <?php endif; ?>
                            </div>
                            <div class="addr-hist-body">
                                <?php if (($h['action'] ?? '') === 'created'): ?>
                                    <div class="addr-hist-change"><strong><?= $this->te('Eintrag angelegt') ?></strong><?php if ($changes): ?> <?= $this->te('mit') ?>
                                        <?= implode(', ', array_map(fn($c) => $this->e($c['label']) . ' „' . $this->e($fmtVal($c['field'], $c['new'])) . '“', $changes)) ?>
                                    <?php endif; ?></div>
                                <?php elseif (empty($changes)): ?>
                                    <div class="addr-hist-change"><?= $this->te('Aktualisiert') ?></div>
                                <?php else: foreach ($changes as $c): ?>
                                    <div class="addr-hist-change">
                                        <span class="addr-hist-field"><?= $this->e($c['label']) ?>:</span>
                                        <span class="addr-hist-old"><?= $this->e($fmtVal($c['field'], $c['old'])) ?></span>
                                        <span class="addr-hist-arrow">→</span>
                                        <span class="addr-hist-new"><?= $this->e($fmtVal($c['field'], $c['new'])) ?></span>
                                    </div>
                                <?php endforeach; endif; ?>
                            </div>
                        </div>
                        <?php endforeach; ?>
                    </div>
                    <?php endif; ?>
                </div>
            </div>
        </div>

        <!-- Sidebar -->
        <div>
            <div class="detail-card">
                <div class="detail-card-header"><span class="detail-card-title"><?= $this->te('Subnetz') ?></span></div>
                <div class="detail-card-body">
                    <a href="/ipam/subnets/<?= $address['subnet_id'] ?>" style="display:flex;align-items:center;gap:0.75rem;padding:0.6rem;background:var(--gray-50);border-radius:0.25rem;border:1px solid var(--gray-200);color:inherit;transition:background 0.1s;" onmouseover="this.style.background='#f3f4f6'" onmouseout="this.style.background='#f9fafb'">
                        <span style="width:10px;height:10px;border-radius:50%;background:<?= $this->e($address['subnet_color']) ?>;flex-shrink:0;"></span>
                        <div>
                            <div style="font-size:0.88rem;font-weight:700;color:var(--gray-900);"><?= $this->e($address['subnet_name']) ?></div>
                            <div style="font-size:0.78rem;color:var(--gray-500);font-family:monospace;"><?= $this->e($address['subnet_network']) ?>/<?= $address['subnet_cidr'] ?></div>
                        </div>
                    </a>
                    <?php if (!empty($address['subnet_gateway']) || !empty($address['subnet_dns'])): ?>
                    <div style="margin-top:0.75rem;display:flex;flex-direction:column;gap:0.6rem;">
                        <?php if (!empty($address['subnet_gateway'])): ?>
                        <div class="addr-info-row"><span class="addr-info-label"><?= $this->te('Gateway') ?></span><span class="addr-info-value" style="font-family:monospace;"><?= $this->e($address['subnet_gateway']) ?></span></div>
                        <?php endif; ?>
                        <?php if (!empty($address['subnet_dns'])): ?>
                        <div class="addr-info-row"><span class="addr-info-label">DNS</span><span class="addr-info-value" style="font-family:monospace;"><?= $this->e($address['subnet_dns']) ?></span></div>
                        <?php endif; ?>
                    </div>
                    <?php endif; ?>
                </div>
            </div>

            <?php
            $lvm  = $links['vm']    ?? null;
            $luni = $links['unifi'] ?? null;
            if ($lvm || $luni):
            ?>
            <!-- Modul-übergreifende Verknüpfungen -->
            <div class="detail-card">
                <div class="detail-card-header"><span class="detail-card-title"><?= $this->te('Verknüpfungen') ?></span></div>
                <div class="detail-card-body">
                    <div class="addr-info-list">
                        <?php if ($lvm): ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Virtuelle Maschine') ?></span>
                            <span class="addr-info-value"><a href="/vms/<?= (int)$lvm['id'] ?>"><?= $this->e($lvm['name'] ?: ('VM #' . (int)$lvm['id'])) ?></a></span>
                        </div>
                        <?php endif; ?>
                        <?php if ($luni): ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te($luni['kind'] === 'device' ? 'UniFi-Gerät' : 'UniFi-Client') ?></span>
                            <span class="addr-info-value">
                                <a href="/unifi" title="<?= $this->te('Zur UniFi-Übersicht') ?>"><?= $this->e($luni['name']) ?></a>
                                <?php if (!empty($luni['mac'])): ?><span style="color:var(--gray-400);font-family:monospace;font-size:0.78rem;"> · <?= $this->e($luni['mac']) ?></span><?php endif; ?>
                            </span>
                        </div>
                        <?php endif; ?>
                    </div>
                </div>
            </div>
            <?php endif; ?>

            <div class="detail-card">
                <div class="detail-card-header"><span class="detail-card-title"><?= $this->te('Protokoll') ?></span></div>
                <div class="detail-card-body">
                    <div class="addr-info-list">
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Angelegt von') ?></span>
                            <span class="addr-info-value"><?= $this->e($address['created_by_name'] ?? '–') ?></span>
                        </div>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Angelegt am') ?></span>
                            <span class="addr-info-value"><?= $this->te('{zeitpunkt} Uhr', ['zeitpunkt' => date('d.m.Y H:i', strtotime($address['created_at']))]) ?></span>
                        </div>
                        <?php if ($address['updated_by_name'] && $address['updated_by'] !== $address['created_by']): ?>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Zuletzt geändert von') ?></span>
                            <span class="addr-info-value"><?= $this->e($address['updated_by_name']) ?></span>
                        </div>
                        <div class="addr-info-row">
                            <span class="addr-info-label"><?= $this->te('Zuletzt geändert') ?></span>
                            <span class="addr-info-value"><?= $this->te('{zeitpunkt} Uhr', ['zeitpunkt' => date('d.m.Y H:i', strtotime($address['updated_at']))]) ?></span>
                        </div>
                        <?php endif; ?>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Confirm-Modal -->
<div class="modal-overlay ipam-modal" id="ipamModal">
    <div class="modal-card">
        <div class="modal-header">
            <h2 id="ipamModalTitle"><?= $this->te('Bestätigen') ?></h2>
            <button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" id="ipamModalClose">&times;</button>
        </div>
        <div class="modal-body">
            <p class="ipam-modal-text" id="ipamModalText"></p>
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-default btn-sm" id="ipamModalCancel"><?= $this->te('Abbrechen') ?></button>
            <button type="button" class="btn btn-danger btn-sm" id="ipamModalOk"><?= $this->te('Löschen') ?></button>
        </div>
    </div>
</div>
<script>
var _ipamOnOk = null;
function ipamConfirm(title, text, onOk) {
    document.getElementById('ipamModalTitle').textContent = title || 'Bestätigen';
    document.getElementById('ipamModalText').textContent  = text || '';
    _ipamOnOk = onOk;
    document.getElementById('ipamModal').classList.add('open');
}
(function(){
    var m = document.getElementById('ipamModal');
    function close(){ m.classList.remove('open'); _ipamOnOk = null; }
    document.getElementById('ipamModalCancel').addEventListener('click', close);
    document.getElementById('ipamModalClose').addEventListener('click', close);
    document.getElementById('ipamModalOk').addEventListener('click', function(){ var f=_ipamOnOk; close(); if(f) f(); });
    m.addEventListener('click', function(e){ if(e.target === m) close(); });
    document.addEventListener('keydown', function(e){ if(e.key==='Escape' && m.classList.contains('open')) close(); });
})();
</script>