255 lines
11 KiB
HTML
255 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard - Query Builder{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row h-100">
|
|
<!-- Linke Sidebar - Tabellen -->
|
|
<div class="col-md-2 border-end bg-light">
|
|
<div class="p-3">
|
|
<!-- Datenbankauswahl Section -->
|
|
<div class="mb-4">
|
|
<h6><i class="fas fa-database"></i> Datenbankverbindung</h6>
|
|
<select id="database-selector" class="form-select form-select-sm" onchange="onDatabaseChange()">
|
|
<option value="demo">Lade Verbindungen...</option>
|
|
</select>
|
|
<button class="btn btn-sm btn-outline-info mt-2 w-100" onclick="testConnection()">
|
|
<i class="fas fa-plug"></i> Verbindung testen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tabellen Section -->
|
|
<div class="mb-4">
|
|
<h5>
|
|
<i class="fas fa-table"></i> Tabellen
|
|
<div class="float-end">
|
|
<button class="btn btn-sm btn-outline-secondary" id="toggle-columns-btn" onclick="toggleColumnsView()" title="Spalten ein/ausblenden">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="loadTables()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
</div>
|
|
</h5>
|
|
<div id="tables-list">
|
|
<div class="text-muted">Lade Tabellen...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hauptbereich - Query Editor und Ergebnisse -->
|
|
<!-- Mittlere Spalte - Query Editor und Ergebnisse -->
|
|
<div class="col-md-8">
|
|
<div class="p-3">
|
|
<!-- Query Editor Section -->
|
|
<div class="mb-4">
|
|
<h5><i class="fas fa-code"></i> SQL Query Editor</h5>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="position-relative">
|
|
<textarea id="query-input" class="form-control mb-3" rows="8"
|
|
placeholder="Geben Sie hier Ihre Oracle SQL-Query ein... Beispiel für Oracle: SELECT * FROM USERS WHERE ROWNUM <= 10"></textarea>
|
|
<div id="autocomplete-dropdown" class="autocomplete-dropdown"></div>
|
|
</div>
|
|
|
|
<div class="btn-group">
|
|
<button class="btn btn-primary" onclick="executeQuery()">
|
|
<i class="fas fa-play"></i> Query Ausführen
|
|
</button>
|
|
<button type="button" class="btn btn-success" id="save-query-btn">
|
|
<i class="fas fa-save"></i> Query Speichern
|
|
</button>
|
|
<button class="btn btn-secondary" onclick="clearQuery()">
|
|
<i class="fas fa-trash"></i> Löschen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ergebnisse Section -->
|
|
<div class="mb-3">
|
|
<h5><i class="fas fa-table"></i> Query-Ergebnisse</h5>
|
|
<div id="results-container" class="card">
|
|
<div class="card-body">
|
|
<div class="text-muted text-center py-4">
|
|
Führen Sie eine Query aus, um Ergebnisse anzuzeigen
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rechte Sidebar - Gespeicherte Queries -->
|
|
<div class="col-md-2 border-start bg-light">
|
|
<div class="p-2 h-100">
|
|
<div class="mb-3">
|
|
<h6><i class="fas fa-bookmark"></i> Gespeicherte Queries</h6>
|
|
</div>
|
|
|
|
<!-- Query-Suche -->
|
|
<div class="mb-3">
|
|
<input type="text" id="query-search" class="form-control form-control-sm"
|
|
placeholder="Query suchen..." onkeyup="filterSavedQueries()">
|
|
</div>
|
|
|
|
<!-- Gespeicherte Queries Liste -->
|
|
<div id="saved-queries-list" class="overflow-auto" style="max-height: calc(100vh - 250px);">
|
|
{% for query in saved_queries %}
|
|
<div class="card mb-2 saved-query-card" data-query-id="{{ query.id }}">
|
|
<div class="card-body p-2">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div class="flex-grow-1" style="cursor: pointer;" onclick="loadSavedQuery({{ query.id }})">
|
|
<h6 class="card-title mb-1 text-primary">{{ query.name }}</h6>
|
|
<small class="text-muted d-block mb-1">{{ query.created_at.strftime('%d.%m.%Y %H:%M') }}</small>
|
|
<small class="text-muted query-preview">{{ query.query[:100] }}{% if query.query|length > 100 %}...{% endif %}</small>
|
|
</div>
|
|
<div class="dropdown">
|
|
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="loadSavedQuery({{ query.id }})">
|
|
<i class="fas fa-play"></i> Laden
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="editQueryName({{ query.id }})">
|
|
<i class="fas fa-edit"></i> Umbenennen
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="duplicateQuery({{ query.id }})">
|
|
<i class="fas fa-copy"></i> Duplizieren
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-danger" href="#" onclick="deleteSavedQuery({{ query.id }})">
|
|
<i class="fas fa-trash"></i> Löschen
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Platzhalter wenn keine Queries vorhanden -->
|
|
<div id="no-queries-placeholder" class="text-center text-muted py-4" {% if saved_queries %}style="display: none;"{% endif %}>
|
|
<i class="fas fa-bookmark fa-2x mb-2"></i>
|
|
<p>Keine gespeicherten Queries vorhanden.</p>
|
|
<small>Speichern Sie Ihre erste Query mit dem <i class="fas fa-save"></i> Button!</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Save Query Modal -->
|
|
<div class="modal fade" id="saveQueryModal" tabindex="-1">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Query speichern</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="query-name" class="form-label">Query-Name:</label>
|
|
<input type="text" class="form-control" id="query-name"
|
|
placeholder="z.B. Benutzer-Liste" required autofocus>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="button" class="btn btn-success" onclick="saveQuery()">
|
|
<i class="fas fa-save"></i> Speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Query Name Modal -->
|
|
<div class="modal fade" id="editQueryModal" tabindex="-1">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Query umbenennen</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="edit-query-name" class="form-label">Neuer Name:</label>
|
|
<input type="text" class="form-control" id="edit-query-name" required autofocus>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="button" class="btn btn-primary" onclick="updateQueryName()">
|
|
<i class="fas fa-save"></i> Umbenennen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// Verbindungen werden automatisch in app.js geladen
|
|
// currentConnection ist jetzt in app.js definiert
|
|
|
|
// Die loadConnections und onDatabaseChange Funktionen sind jetzt in app.js
|
|
|
|
// Teste Datenbankverbindung
|
|
function testConnection() {
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Teste...';
|
|
button.disabled = true;
|
|
|
|
fetch(`/test_connection/${currentConnection}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showAlert('success', `✓ ${data.message}`);
|
|
} else {
|
|
showAlert('danger', `✗ ${data.message}`);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showAlert('danger', `Fehler beim Testen der Verbindung: ${error}`);
|
|
})
|
|
.finally(() => {
|
|
button.innerHTML = originalText;
|
|
button.disabled = false;
|
|
});
|
|
}
|
|
|
|
// Hilfsfunktion für Alerts
|
|
function showAlert(type, message) {
|
|
// Entferne vorhandene Alerts
|
|
const existingAlert = document.querySelector('.alert');
|
|
if (existingAlert) {
|
|
existingAlert.remove();
|
|
}
|
|
|
|
// Erstelle neues Alert
|
|
const alertDiv = document.createElement('div');
|
|
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
|
|
alertDiv.innerHTML = `
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
|
|
// Füge Alert am Anfang des Contents ein
|
|
const content = document.querySelector('.container-fluid');
|
|
content.insertBefore(alertDiv, content.firstChild);
|
|
|
|
// Auto-hide nach 5 Sekunden
|
|
setTimeout(() => {
|
|
if (alertDiv && alertDiv.parentNode) {
|
|
alertDiv.remove();
|
|
}
|
|
}, 5000);
|
|
}
|
|
</script>
|
|
{% endblock %} |