Export und Aktion gefixt

Dieser Commit ist enthalten in:
2025-06-15 22:22:08 +02:00
Ursprung df60ce6d18
Commit d65e5d333c
2 geänderte Dateien mit 198 neuen und 41 gelöschten Zeilen

Datei anzeigen

@@ -3192,11 +3192,12 @@ def export_resources():
# SQL Query mit Filtern
query = """
SELECT r.id, r.type, r.value, r.status, r.license_id, r.created_at, r.allocated_at,
SELECT r.id, r.resource_type, r.resource_value, r.status, r.allocated_to_license,
r.created_at, r.status_changed_at,
l.license_key, c.name as customer_name, c.email as customer_email,
l.type as license_type
l.license_type
FROM resource_pools r
LEFT JOIN licenses l ON r.license_id = l.id
LEFT JOIN licenses l ON r.allocated_to_license = l.id
LEFT JOIN customers c ON l.customer_id = c.id
WHERE 1=1
"""
@@ -3204,11 +3205,11 @@ def export_resources():
# Filter für Testdaten
if not show_test:
query += " AND (l.is_test = false OR l.is_test IS NULL)"
query += " AND (r.is_test = false OR r.is_test IS NULL)"
# Filter für Ressourcentyp
if filter_type:
query += " AND r.type = %s"
query += " AND r.resource_type = %s"
params.append(filter_type)
# Filter für Status
@@ -3218,7 +3219,7 @@ def export_resources():
# Suchfilter
if search_query:
query += " AND (r.value ILIKE %s OR l.license_key ILIKE %s OR c.name ILIKE %s)"
query += " AND (r.resource_value ILIKE %s OR l.license_key ILIKE %s OR c.name ILIKE %s)"
params.extend([f'%{search_query}%', f'%{search_query}%', f'%{search_query}%'])
query += " ORDER BY r.id DESC"

Datei anzeigen

@@ -165,6 +165,33 @@
margin-bottom: 1rem;
opacity: 0.5;
}
/* Dropdown Verbesserungen */
.dropdown-menu {
min-width: 220px;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
border: none;
}
.dropdown-item {
padding: 0.5rem 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
transition: all 0.2s ease;
}
.dropdown-item:hover {
background-color: #f8f9fa;
transform: translateX(2px);
}
.dropdown-item i {
width: 20px;
text-align: center;
}
/* Quick Action Buttons */
.btn-success {
font-weight: 500;
}
</style>
{% endblock %}
@@ -292,13 +319,17 @@
<h5 class="mb-0">📋 Ressourcen-Liste</h5>
<div class="d-flex align-items-center gap-2">
<div class="dropdown">
<button class="btn btn-sm btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<button class="btn btn-sm btn-secondary dropdown-toggle"
type="button"
id="exportDropdown"
data-bs-toggle="dropdown"
aria-expanded="false">
<i class="bi bi-download"></i> Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/export/resources?format=excel&type={{ filter_type }}&status={{ filter_status }}&search={{ search_query }}&show_test={{ show_test }}">
<ul class="dropdown-menu" aria-labelledby="exportDropdown">
<li><a class="dropdown-item" href="/export/resources?format=excel&type={{ resource_type }}&status={{ status_filter }}&search={{ search }}&show_test={{ show_test }}">
<i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li>
<li><a class="dropdown-item" href="/export/resources?format=csv&type={{ filter_type }}&status={{ filter_status }}&search={{ search_query }}&show_test={{ show_test }}">
<li><a class="dropdown-item" href="/export/resources?format=csv&type={{ resource_type }}&status={{ status_filter }}&search={{ search }}&show_test={{ show_test }}">
<i class="bi bi-file-earmark-text"></i> CSV Export</a></li>
</ul>
</div>
@@ -318,7 +349,7 @@
<th width="140">Status</th>
<th>Zugewiesen an</th>
<th width="180">Letzte Änderung</th>
<th width="140" class="text-center">Aktionen</th>
<th width="200" class="text-center">Aktionen</th>
</tr>
</thead>
<tbody>
@@ -343,7 +374,7 @@
<code class="me-2">{{ resource[2] }}</code>
<button class="copy-btn" onclick="copyToClipboard('{{ resource[2] }}', this)"
title="Kopieren">
<i class="fas fa-copy"></i>
<i class="bi bi-clipboard"></i>
</button>
</div>
</td>
@@ -394,29 +425,96 @@
{% endif %}
</td>
<td class="text-center">
<a href="{{ url_for('resource_history', resource_id=resource[0]) }}"
class="btn btn-action btn-sm btn-outline-info"
title="Historie anzeigen">
<i class="fas fa-history"></i>
</a>
{% if resource[3] == 'available' %}
<button class="btn btn-action btn-sm btn-outline-warning"
onclick="showQuarantineModal({{ resource[0] }})"
title="In Quarantäne setzen">
<i class="fas fa-ban"></i>
</button>
{% elif resource[3] == 'quarantine' %}
<form method="post" action="{{ url_for('release_resources') }}"
style="display: inline;">
{% if resource[3] == 'quarantine' %}
<!-- Quick Action für Quarantäne -->
<form method="post" action="/resources/release"
style="display: inline-block; margin-right: 5px;">
<input type="hidden" name="resource_ids[]" value="{{ resource[0] }}">
<button type="submit"
class="btn btn-action btn-sm btn-outline-success"
title="Ressource freigeben">
<i class="fas fa-check"></i>
class="btn btn-sm btn-success">
<i class="bi bi-check-circle"></i> Freigeben
</button>
</form>
{% endif %}
<!-- Dropdown für weitere Aktionen -->
<div class="dropdown" style="display: inline-block;">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton{{ resource[0] }}"
data-bs-toggle="dropdown"
aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i> Aktionen
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ resource[0] }}">
<!-- Historie immer verfügbar -->
<li>
<a class="dropdown-item"
href="{{ url_for('resource_history', resource_id=resource[0]) }}">
<i class="bi bi-clock-history text-info"></i> Historie anzeigen
</a>
</li>
<li><hr class="dropdown-divider"></li>
{% if resource[3] == 'available' %}
<!-- Aktionen für verfügbare Ressourcen -->
<li>
<button class="dropdown-item"
onclick="showQuarantineModal({{ resource[0] }})">
<i class="bi bi-exclamation-triangle text-warning"></i> In Quarantäne setzen
</button>
</li>
{% elif resource[3] == 'allocated' %}
<!-- Aktionen für zugeteilte Ressourcen -->
{% if resource[4] %}
<li>
<a class="dropdown-item"
href="{{ url_for('edit_license', license_id=resource[4]) }}?ref=resources{{ '&show_test=true' if show_test else '' }}">
<i class="bi bi-file-text text-primary"></i> Lizenz bearbeiten
</a>
</li>
{% endif %}
{% if resource[10] %}
<li>
<a class="dropdown-item"
href="{{ url_for('customers_licenses', customer_id=resource[10], show_test=show_test) }}">
<i class="bi bi-person text-primary"></i> Kunde anzeigen
</a>
</li>
{% endif %}
{% elif resource[3] == 'quarantine' %}
<!-- Aktionen für Quarantäne-Ressourcen -->
<li>
<form method="post" action="/resources/release"
style="display: contents;">
<input type="hidden" name="resource_ids[]" value="{{ resource[0] }}">
<button type="submit" class="dropdown-item">
<i class="bi bi-check-circle text-success"></i> Ressource freigeben
</button>
</form>
</li>
{% if resource[9] %}
<li>
<button class="dropdown-item"
onclick="extendQuarantine({{ resource[0] }})">
<i class="bi bi-calendar-plus text-warning"></i> Quarantäne verlängern
</button>
</li>
{% endif %}
{% endif %}
<li><hr class="dropdown-divider"></li>
<!-- Kopieren immer verfügbar -->
<li>
<button class="dropdown-item"
onclick="copyToClipboard('{{ resource[2] }}', this)">
<i class="bi bi-clipboard text-secondary"></i> Ressource kopieren
</button>
</li>
</ul>
</div>
</td>
</tr>
{% endfor %}
@@ -425,7 +523,7 @@
</div>
{% else %}
<div class="empty-state">
<i class="fas fa-inbox"></i>
<i class="bi bi-inbox"></i>
<h4>Keine Ressourcen gefunden</h4>
<p>Ändern Sie Ihre Filterkriterien oder fügen Sie neue Ressourcen hinzu.</p>
</div>
@@ -440,13 +538,13 @@
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link"
href="{{ url_for('resources', page=1, type=resource_type, status=status_filter, search=search, show_test=show_test) }}">
<i class="fas fa-angle-double-left"></i> Erste
<i class="bi bi-chevron-double-left"></i> Erste
</a>
</li>
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link"
href="{{ url_for('resources', page=page-1, type=resource_type, status=status_filter, search=search, show_test=show_test) }}">
<i class="fas fa-angle-left"></i> Zurück
<i class="bi bi-chevron-left"></i> Zurück
</a>
</li>
@@ -464,13 +562,13 @@
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link"
href="{{ url_for('resources', page=page+1, type=resource_type, status=status_filter, search=search, show_test=show_test) }}">
Weiter <i class="fas fa-angle-right"></i>
Weiter <i class="bi bi-chevron-right"></i>
</a>
</li>
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link"
href="{{ url_for('resources', page=total_pages, type=resource_type, status=status_filter, search=search, show_test=show_test) }}">
Letzte <i class="fas fa-angle-double-right"></i>
Letzte <i class="bi bi-chevron-double-right"></i>
</a>
</li>
</ul>
@@ -568,6 +666,12 @@
</div>
<script>
// Debug Bootstrap
console.log('Bootstrap loaded:', typeof bootstrap !== 'undefined');
if (typeof bootstrap !== 'undefined') {
console.log('Bootstrap version:', bootstrap.Dropdown.VERSION);
}
// Live-Filtering
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('filterForm');
@@ -590,21 +694,36 @@ document.addEventListener('DOMContentLoaded', function() {
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
// Bootstrap Dropdowns explizit initialisieren
var dropdownElementList = [].slice.call(document.querySelectorAll('.dropdown-toggle'))
var dropdownList = dropdownElementList.map(function (dropdownToggleEl) {
return new bootstrap.Dropdown(dropdownToggleEl)
});
// Export Dropdown manuell aktivieren falls nötig
const exportBtn = document.getElementById('exportDropdown');
if (exportBtn) {
exportBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
const dropdown = bootstrap.Dropdown.getOrCreateInstance(this);
dropdown.toggle();
});
}
});
// Copy to Clipboard mit besserem Feedback
function copyToClipboard(text, button) {
navigator.clipboard.writeText(text).then(() => {
// Button Icon ändern
const icon = button.querySelector('i');
icon.classList.remove('fa-copy');
icon.classList.add('fa-check');
// Original Text speichern
const originalText = button.innerHTML;
button.innerHTML = '<i class="bi bi-check"></i> Kopiert!';
button.classList.add('copied');
// Nach 2 Sekunden zurücksetzen
setTimeout(() => {
icon.classList.remove('fa-check');
icon.classList.add('fa-copy');
button.innerHTML = originalText;
button.classList.remove('copied');
}, 2000);
});
@@ -626,5 +745,42 @@ function toggleTestResources() {
currentUrl.searchParams.set('show_test', showTest);
window.location.href = currentUrl.toString();
}
// Quarantäne verlängern
function extendQuarantine(resourceId) {
if (confirm('Möchten Sie die Quarantäne für diese Ressource verlängern?')) {
// TODO: Implementiere Modal für Quarantäne-Verlängerung
alert('Diese Funktion wird noch implementiert.');
}
}
// Fallback für Dropdowns
document.addEventListener('click', function(e) {
// Prüfe ob ein Dropdown-Toggle geklickt wurde
if (e.target.closest('.dropdown-toggle')) {
const button = e.target.closest('.dropdown-toggle');
const dropdown = button.nextElementSibling;
if (dropdown && dropdown.classList.contains('dropdown-menu')) {
// Toggle show class
dropdown.classList.toggle('show');
button.setAttribute('aria-expanded', dropdown.classList.contains('show'));
// Schließe andere Dropdowns
document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
if (menu !== dropdown) {
menu.classList.remove('show');
menu.previousElementSibling.setAttribute('aria-expanded', 'false');
}
});
}
} else {
// Schließe alle Dropdowns wenn außerhalb geklickt
document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
menu.classList.remove('show');
menu.previousElementSibling.setAttribute('aria-expanded', 'false');
});
}
});
</script>
{% endblock %}