OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

index.php

18 KB
<?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">&times;</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">&times;</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 &amp; Ö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">&times;</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">&times;</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; ?>