Quelltext des aktuellen Standes
<?php $this->layout('layouts/main', [
'page_title' => $page_title,
'current_route' => $current_route,
'full_width' => true,
]); ?>
<style>
a { text-decoration:none; } a:hover { text-decoration:none; }
.nd-wrap { display:flex; flex-direction:column; }
.nd-body { padding:1.5rem 1rem 3rem; background:var(--color-bg-alt); flex:1; }
/* Grid */
.nd-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); gap:1rem; }
/* Card */
.nd-card { background:var(--color-bg); border:1px solid rgba(0,0,0,.125);
border-radius:0.25rem; overflow:hidden;
box-shadow:0 0 1px rgba(0,0,0,.125),0 1px 3px rgba(0,0,0,.2);
transition:box-shadow 0.15s; display:flex; flex-direction:column; }
.nd-card:hover { box-shadow:0 0 1px rgba(0,0,0,.125),0 4px 8px rgba(0,0,0,.15); }
.nd-card-stripe { height:3px; background:var(--color-primary); }
.nd-card-link { display:block; text-decoration:none; color:inherit; flex:1; }
.nd-card-preview { height:100px; background:linear-gradient(135deg,var(--sky-100) 0%,var(--sky-200) 100%);
display:flex; align-items:center; justify-content:center; color:var(--color-primary);
overflow:hidden; }
.nd-card-preview img { width:100%; height:100%; object-fit:contain; padding:4px; background:var(--color-bg); }
.nd-card-body { padding:0.875rem 1rem 0.75rem; }
.nd-card-title { font-size:0.9rem; font-weight:600; color:var(--color-gray-900);
margin-bottom:0.2rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.nd-card-desc { font-size:0.77rem; color:var(--color-secondary);
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.nd-card-footer { padding:0.45rem 1rem; background:var(--color-gray-50);
border-top:1px solid rgba(0,0,0,.125); font-size:0.72rem; color:var(--color-secondary);
display:flex; align-items:center; justify-content:space-between; }
.nd-card-actions { display:flex; gap:0.2rem; }
.nd-action-btn { background:none; border:none; cursor:pointer; padding:0.1rem 0.35rem;
border-radius:0.25rem; font-size:0.82rem; color:var(--color-secondary); transition:background 0.1s; line-height:1.4; }
.nd-action-btn:hover { background:var(--color-gray-200); }
.nd-action-delete:hover { color:var(--bs-danger); background:var(--alert-danger-bg); }
/* Modals nutzen die zentralen Komponenten aus app.css (.modal-overlay/.modal-card);
lokal nur Ebene und modul-eigene Breite. Sichtbarkeit läuft über style.display. */
.nd-modal { z-index:1055; }
.nd-modal .modal-card { max-width:460px; }
@media(max-width:768px) {
.nd-body { padding:1rem 0.75rem 2rem; }
.nd-grid { grid-template-columns:1fr 1fr; }
}
@media(max-width:480px) {
.nd-grid { grid-template-columns:1fr; }
}
</style>
<div class="nd-wrap">
<!-- Content Header -->
<div class="content-header">
<div>
<h1><?= $this->te('Netzwerkpläne') ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item active"><?= $this->te('Netzwerkpläne') ?></li>
</ol>
</div>
<?php if ($can_edit && $drawio_installed): ?>
<div class="content-header-actions">
<button class="btn btn-default btn-sm" onclick="document.getElementById('nd-upload-modal').style.display='flex'">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:-2px;margin-right:3px;"><polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg>
<?= $this->te('Hochladen') ?>
</button>
<button class="btn btn-primary btn-sm" onclick="document.getElementById('nd-new-modal').style.display='flex'">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:-2px;margin-right:3px;"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
<?= $this->te('Neuer Plan') ?>
</button>
</div>
<?php endif; ?>
</div>
<div class="nd-body">
<?php if (!$drawio_installed): ?>
<div class="alert alert-warning" style="margin-bottom:1.25rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:-3px;margin-right:6px;"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<strong><?= $this->te('Diagram Editor nicht installiert.') ?></strong>
<?php if ($this->hasCapability('admin_netdiagram')): ?>
Bitte im <a href="/admin/netdiagram/drawio"><?= $this->te('Admin-Bereich installieren') ?></a>.
<?php else: ?>
<?= $this->te('Bitte einen Administrator bitten, den Editor zu installieren.') ?>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (empty($diagrams)): ?>
<div style="text-align:center;padding:3.5rem 1rem;color:var(--gray-400);">
<svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" style="margin:0 auto 1rem;display:block;opacity:.5;">
<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 9h6M9 12h6M9 15h4"/>
</svg>
<p style="font-size:0.92rem;margin:0 0 1rem;color:var(--color-secondary);"><?= $this->te('Noch keine Netzwerkpläne vorhanden.') ?></p>
<?php if ($can_edit && $drawio_installed): ?>
<div style="display:flex;gap:0.75rem;justify-content:center;">
<button class="btn btn-default btn-sm" onclick="document.getElementById('nd-upload-modal').style.display='flex'"><?= $this->te('Diagramm hochladen') ?></button>
<button class="btn btn-primary btn-sm" onclick="document.getElementById('nd-new-modal').style.display='flex'"><?= $this->te('Neuen Plan erstellen') ?></button>
</div>
<?php endif; ?>
</div>
<?php else: ?>
<div class="nd-grid">
<?php foreach ($diagrams as $d): ?>
<div class="nd-card">
<div class="nd-card-stripe"></div>
<a href="/netdiagram/<?= (int)$d['id'] ?>/edit" class="nd-card-link">
<div class="nd-card-preview">
<?php if (!empty($d['preview_svg'])): ?>
<img src="data:image/svg+xml;base64,<?= base64_encode($d['preview_svg']) ?>"
alt="Vorschau <?= $this->e($d['name']) ?>">
<?php else: ?>
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" opacity=".45">
<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/>
<path d="M21 15l-5-5L5 21"/><path d="M14 14l3-3 4 4"/>
</svg>
<?php endif; ?>
</div>
<div class="nd-card-body">
<div class="nd-card-title" title="<?= $this->e($d['name']) ?>"><?= $this->e($d['name']) ?></div>
<?php if (!empty($d['description'])): ?>
<div class="nd-card-desc" title="<?= $this->e($d['description']) ?>"><?= $this->e($d['description']) ?></div>
<?php endif; ?>
</div>
</a>
<div class="nd-card-footer">
<span><?= date('d.m.Y H:i', strtotime($d['updated_at'])) ?></span>
<?php if ($can_edit): ?>
<div class="nd-card-actions">
<button class="nd-action-btn" title="<?= $this->te('Umbenennen') ?>"
data-rename-id="<?= (int)$d['id'] ?>"
data-rename-name="<?= $this->e($d['name']) ?>">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>
</button>
<button class="nd-action-btn nd-action-delete" title="<?= $this->te('Löschen') ?>"
data-delete-id="<?= (int)$d['id'] ?>"
data-delete-name="<?= $this->e($d['name']) ?>">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>
</button>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<?php if ($can_edit && $drawio_installed): ?>
<!-- Neuer Plan Modal -->
<div id="nd-new-modal" class="modal-overlay nd-modal" style="display:none;">
<div class="modal-card">
<div class="modal-header">
<h2><?= $this->te('Neuer Netzwerkplan') ?></h2>
<button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" data-close="nd-new-modal">×</button>
</div>
<form method="POST" action="/netdiagram">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<div class="modal-body">
<div class="form-group">
<label for="nd-new-name"><?= $this->te('Name') ?> <span class="text-danger">*</span></label>
<input type="text" id="nd-new-name" name="name" class="form-control"
placeholder="<?= $this->te('z.B. Netzwerk Standort Berlin') ?>" maxlength="200" required autofocus>
</div>
<div class="form-group mb-0">
<label for="nd-new-desc"><?= $this->te('Beschreibung') ?> <small class="text-muted"><?= $this->te('(optional)') ?></small></label>
<input type="text" id="nd-new-desc" name="description" class="form-control"
placeholder="<?= $this->te('Kurze Beschreibung') ?>" maxlength="500">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-close="nd-new-modal"><?= $this->te('Abbrechen') ?></button>
<button type="submit" class="btn btn-primary"><?= $this->te('Plan erstellen') ?></button>
</div>
</form>
</div>
</div>
<!-- Upload Modal -->
<div id="nd-upload-modal" class="modal-overlay nd-modal" style="display:none;">
<div class="modal-card">
<div class="modal-header">
<h2><?= $this->te('Diagrammdatei hochladen') ?></h2>
<button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" data-close="nd-upload-modal">×</button>
</div>
<form method="POST" action="/netdiagram/upload" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<div class="modal-body">
<div class="form-group mb-0">
<label for="nd-file"><?= $this->te('Diagrammdatei (.drawio oder .xml)') ?></label>
<input type="file" id="nd-file" name="drawio_file" class="form-control"
accept=".drawio,.xml" required>
<div class="form-hint"><?= $this->te('Max. 10 MB. Der Dateiname wird als Plan-Name verwendet.') ?></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-close="nd-upload-modal"><?= $this->te('Abbrechen') ?></button>
<button type="submit" class="btn btn-primary">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:-2px;margin-right:3px;"><polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg>
<?= $this->te('Hochladen & Öffnen') ?>
</button>
</div>
</form>
</div>
</div>
<?php endif; ?>
<?php if ($can_edit): ?>
<!-- Rename Modal -->
<div id="nd-rename-modal" class="modal-overlay nd-modal" style="display:none;">
<div class="modal-card">
<div class="modal-header">
<h2><?= $this->te('Plan umbenennen') ?></h2>
<button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" data-close="nd-rename-modal">×</button>
</div>
<div class="modal-body">
<div class="form-group mb-0">
<label for="nd-rename-input"><?= $this->te('Neuer Name') ?></label>
<input type="text" id="nd-rename-input" class="form-control" maxlength="200">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-close="nd-rename-modal"><?= $this->te('Abbrechen') ?></button>
<button type="button" class="btn btn-primary" id="nd-rename-confirm"><?= $this->te('Umbenennen') ?></button>
</div>
</div>
</div>
<!-- Delete Modal -->
<div id="nd-delete-modal" class="modal-overlay nd-modal" style="display:none;">
<div class="modal-card">
<div class="modal-header">
<h2><?= $this->te('Plan löschen') ?></h2>
<button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" data-close="nd-delete-modal">×</button>
</div>
<div class="modal-body">
<p style="margin:0;"><?= $this->t('Möchten Sie den Plan <strong id="nd-delete-name"></strong> wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.') ?></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-close="nd-delete-modal"><?= $this->te('Abbrechen') ?></button>
<form id="nd-delete-form" method="POST" style="display:inline;">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<button type="submit" class="btn btn-danger"><?= $this->te('Löschen') ?></button>
</form>
</div>
</div>
</div>
<script>
(function () {
var CSRF = <?= json_encode($this->csrf()) ?>;
// Close buttons (data-close attribute)
document.querySelectorAll('[data-close]').forEach(function(btn) {
btn.addEventListener('click', function() {
document.getElementById(btn.dataset.close).style.display = 'none';
});
});
// Close on backdrop click
document.querySelectorAll('.nd-modal').forEach(function(bd) {
bd.addEventListener('click', function(e) { if (e.target === bd) bd.style.display = 'none'; });
});
document.addEventListener('keydown', function(e) {
if (e.key !== 'Escape') return;
document.querySelectorAll('.nd-modal').forEach(function(bd) { bd.style.display = 'none'; });
});
// ---- Rename ----
var renameModal = document.getElementById('nd-rename-modal');
var renameInput = document.getElementById('nd-rename-input');
var renameConfirm = document.getElementById('nd-rename-confirm');
var pendingRenameId = null;
document.querySelectorAll('[data-rename-id]').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.preventDefault(); e.stopPropagation();
pendingRenameId = this.dataset.renameId;
renameInput.value = this.dataset.renameName;
renameModal.style.display = 'flex';
setTimeout(function() { renameInput.focus(); renameInput.select(); }, 50);
});
});
renameInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); renameConfirm.click(); }
if (e.key === 'Escape') renameModal.style.display = 'none';
});
renameConfirm.addEventListener('click', function() {
var name = renameInput.value.trim();
if (!name || !pendingRenameId) return;
renameConfirm.disabled = true;
fetch('/netdiagram/' + pendingRenameId + '/rename', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: '_csrf=' + encodeURIComponent(CSRF) + '&name=' + encodeURIComponent(name)
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.ok) { location.reload(); }
else { renameConfirm.disabled = false; alert('Fehler: ' + (d.error || 'Umbenennen fehlgeschlagen')); }
})
.catch(function() { renameConfirm.disabled = false; });
});
// ---- Delete ----
var deleteModal = document.getElementById('nd-delete-modal');
var deleteName = document.getElementById('nd-delete-name');
var deleteForm = document.getElementById('nd-delete-form');
document.querySelectorAll('[data-delete-id]').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.preventDefault(); e.stopPropagation();
deleteName.textContent = this.dataset.deleteName;
deleteForm.action = '/netdiagram/' + this.dataset.deleteId + '/delete';
deleteModal.style.display = 'flex';
});
});
}());
</script>
<?php endif; ?>