467 lines
15 KiB
Plaintext
467 lines
15 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>Datenbankverwaltung</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
|
<link rel="stylesheet" href="/css/style.css">
|
|
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css">
|
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
background: #f4f6f9;
|
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu;
|
|
}
|
|
|
|
.layout {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* Sidebar */
|
|
.sidebar {
|
|
width: 240px;
|
|
background: #111827;
|
|
color: white;
|
|
padding: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.logo {
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
margin-bottom: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 12px 15px;
|
|
border-radius: 8px;
|
|
color: #cbd5e1;
|
|
text-decoration: none;
|
|
margin-bottom: 6px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.nav-item:hover {
|
|
background: #1f2937;
|
|
color: white;
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: #2563eb;
|
|
color: white;
|
|
}
|
|
|
|
.sidebar .spacer {
|
|
flex: 1;
|
|
}
|
|
|
|
.nav-item.locked {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
.nav-item.locked:hover {
|
|
background: transparent;
|
|
color: #cbd5e1;
|
|
}
|
|
|
|
/* Main */
|
|
.main {
|
|
flex: 1;
|
|
padding: 24px;
|
|
overflow: auto;
|
|
}
|
|
|
|
/* ✅ Systeminfo Tabelle kompakt */
|
|
.table-systeminfo {
|
|
table-layout: auto;
|
|
width: 100%;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.table-systeminfo th,
|
|
.table-systeminfo td {
|
|
padding: 6px 8px;
|
|
}
|
|
|
|
.table-systeminfo th:first-child,
|
|
.table-systeminfo td:first-child {
|
|
width: 1%;
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="layout">
|
|
|
|
<!-- ✅ ADMIN SIDEBAR -->
|
|
<%- include("../partials/admin-sidebar", { user, active: "database" }) %>
|
|
|
|
<!-- ✅ MAIN CONTENT -->
|
|
<div class="main">
|
|
|
|
<nav class="navbar navbar-dark bg-dark position-relative px-3 rounded mb-4">
|
|
<div class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white">
|
|
<i class="bi bi-hdd-stack fs-4"></i>
|
|
<span class="fw-semibold fs-5">Datenbankverwaltung</span>
|
|
</div>
|
|
|
|
<div class="ms-auto">
|
|
<a href="/dashboard" class="btn btn-outline-light btn-sm">⬅️ Dashboard</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container-fluid">
|
|
|
|
<!-- ✅ Flash Messages -->
|
|
<%- include("../partials/flash") %>
|
|
|
|
<!-- ✅ Statusanzeige (Verbindung OK / Fehler) -->
|
|
<% if (testResult) { %>
|
|
<div class="alert <%= testResult.ok ? 'alert-success' : 'alert-danger' %>">
|
|
<%= testResult.message %>
|
|
</div>
|
|
<% } %>
|
|
|
|
<div class="card shadow">
|
|
<div class="card-body">
|
|
|
|
<h4 class="mb-3">Datenbank Tools</h4>
|
|
|
|
<div class="alert alert-warning">
|
|
<b>Hinweis:</b> Diese Funktionen sind nur für <b>Admins</b> sichtbar und sollten mit Vorsicht benutzt werden.
|
|
</div>
|
|
|
|
<!-- ✅ DB Einstellungen -->
|
|
<div class="card border mb-4">
|
|
<div class="card-body">
|
|
|
|
<div class="mb-3">
|
|
<h5 class="card-title m-0">🔧 Datenbankverbindung ändern</h5>
|
|
</div>
|
|
|
|
<% if (!dbConfig) { %>
|
|
<div class="alert alert-danger">
|
|
❌ Keine Datenbank-Konfiguration gefunden (config.enc fehlt oder ungültig).
|
|
</div>
|
|
<% } %>
|
|
|
|
<!-- ✅ Speichern + testen -->
|
|
<form id="dbForm" method="POST" action="/admin/database" class="row g-3">
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">DB Host</label>
|
|
<input
|
|
type="text"
|
|
name="host"
|
|
class="form-control db-input"
|
|
value="<%= dbConfig?.host || '' %>"
|
|
required
|
|
disabled
|
|
/>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">DB Name</label>
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
class="form-control db-input"
|
|
value="<%= dbConfig?.name || '' %>"
|
|
required
|
|
disabled
|
|
/>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">DB User</label>
|
|
<input
|
|
type="text"
|
|
name="user"
|
|
class="form-control db-input"
|
|
value="<%= dbConfig?.user || '' %>"
|
|
required
|
|
disabled
|
|
/>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">DB Passwort</label>
|
|
<input
|
|
type="password"
|
|
name="password"
|
|
class="form-control db-input"
|
|
value="<%= dbConfig?.password || '' %>"
|
|
required
|
|
disabled
|
|
/>
|
|
</div>
|
|
|
|
<!-- ✅ BUTTON LEISTE -->
|
|
<div class="col-12 d-flex align-items-center gap-2 flex-wrap">
|
|
|
|
<!-- 🔒 Bearbeiten -->
|
|
<button id="toggleEditBtn" type="button" class="btn btn-outline-warning">
|
|
<i class="bi bi-lock-fill"></i> Bearbeiten
|
|
</button>
|
|
|
|
<!-- ✅ Speichern -->
|
|
<button id="saveBtn" class="btn btn-primary" disabled>
|
|
✅ Speichern & testen
|
|
</button>
|
|
|
|
<!-- 🔍 Nur testen -->
|
|
<button id="testBtn" type="button" class="btn btn-outline-success" disabled>
|
|
🔍 Nur testen
|
|
</button>
|
|
|
|
<!-- ↩ Zurücksetzen direkt neben "Nur testen" -->
|
|
<a href="/admin/database" class="btn btn-outline-secondary ms-2">
|
|
Zurücksetzen
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<div class="text-muted small">
|
|
Standardmäßig sind die Felder gesperrt. Erst auf <b>Bearbeiten</b> klicken.
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- ✅ Hidden Form für Test -->
|
|
<form id="testForm" method="POST" action="/admin/database/test"></form>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ✅ Backup + Restore + Systeminfo -->
|
|
<div class="row g-3">
|
|
|
|
<!-- ✅ Backup -->
|
|
<div class="col-md-6">
|
|
<div class="card border">
|
|
<div class="card-body">
|
|
<h5 class="card-title">📦 Backup</h5>
|
|
<p class="text-muted small mb-3">
|
|
Erstellt ein SQL Backup der kompletten Datenbank.
|
|
</p>
|
|
|
|
<form method="POST" action="/admin/database/backup">
|
|
<button class="btn btn-outline-primary">
|
|
Backup erstellen
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ✅ Restore -->
|
|
<div class="col-md-6">
|
|
<div class="card border">
|
|
<div class="card-body">
|
|
<h5 class="card-title">♻️ Restore</h5>
|
|
<p class="text-muted small mb-3">
|
|
Wähle ein Backup aus dem Ordner <b>/backups</b> und stelle die Datenbank wieder her.
|
|
</p>
|
|
|
|
<% if (!backupFiles || backupFiles.length === 0) { %>
|
|
<div class="alert alert-secondary mb-2">
|
|
Keine Backups im Ordner <b>/backups</b> gefunden.
|
|
</div>
|
|
<% } %>
|
|
|
|
<form method="POST" action="/admin/database/restore">
|
|
<!-- ✅ Scroll Box -->
|
|
<div
|
|
class="border rounded p-2 mb-2"
|
|
style="max-height: 210px; overflow-y: auto; background: #fff;"
|
|
>
|
|
<% (backupFiles || []).forEach((f, index) => { %>
|
|
<label
|
|
class="d-flex align-items-center gap-2 p-2 rounded"
|
|
style="cursor:pointer;"
|
|
>
|
|
<input
|
|
type="radio"
|
|
name="backupFile"
|
|
value="<%= f %>"
|
|
<%= index === 0 ? "checked" : "" %>
|
|
<%= (!backupFiles || backupFiles.length === 0) ? "disabled" : "" %>
|
|
/>
|
|
<span style="font-size: 14px;"><%= f %></span>
|
|
</label>
|
|
<% }) %>
|
|
</div>
|
|
|
|
<button
|
|
class="btn btn-outline-danger"
|
|
onclick="return confirm('⚠️ Achtung! Restore überschreibt Datenbankdaten. Wirklich fortfahren?');"
|
|
<%= (!backupFiles || backupFiles.length === 0) ? "disabled" : "" %>
|
|
>
|
|
Restore starten
|
|
</button>
|
|
</form>
|
|
|
|
<div class="text-muted small mt-2">
|
|
Es werden die neuesten Backups zuerst angezeigt. Wenn mehr vorhanden sind, kannst du scrollen.
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ✅ Systeminfo (kompakt wie gewünscht) -->
|
|
<div class="col-md-12">
|
|
<div class="card border">
|
|
<div class="card-body">
|
|
<h5 class="card-title">🔍 Systeminfo</h5>
|
|
|
|
<% if (!systemInfo) { %>
|
|
<p class="text-muted small mb-0">Keine Systeminfos verfügbar.</p>
|
|
|
|
<% } else if (systemInfo.error) { %>
|
|
<div class="alert alert-danger">
|
|
❌ Systeminfo konnte nicht geladen werden: <%= systemInfo.error %>
|
|
</div>
|
|
|
|
<% } else { %>
|
|
|
|
<div class="row g-3">
|
|
|
|
<!-- ✅ LINKS: Quick Infos -->
|
|
<div class="col-lg-4">
|
|
<div class="border rounded p-3 bg-white h-100">
|
|
|
|
<div class="mb-3">
|
|
<div class="text-muted small">DB Version</div>
|
|
<div class="fw-semibold"><%= systemInfo.version %></div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<div class="text-muted small">Tabellen</div>
|
|
<div class="fw-semibold"><%= systemInfo.tableCount %></div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="text-muted small">DB Größe</div>
|
|
<div class="fw-semibold"><%= systemInfo.dbSizeMB %> MB</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ✅ RECHTS: Tabellenübersicht -->
|
|
<div class="col-lg-8">
|
|
<div class="border rounded p-3 bg-white h-100">
|
|
<div class="text-muted small mb-2">Tabellenübersicht</div>
|
|
|
|
<div style="max-height: 220px; overflow-y: auto;">
|
|
<table class="table table-sm table-bordered align-middle mb-0 table-systeminfo">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Tabellenname</th>
|
|
<th style="width: 90px;" class="text-end">Rows</th>
|
|
<th style="width: 110px;" class="text-end">MB</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
<% systemInfo.tables.forEach(t => { %>
|
|
<tr>
|
|
<td><%= t.name %></td>
|
|
<td class="text-end"><%= t.row_count %></td>
|
|
<td class="text-end"><%= t.size_mb %></td>
|
|
</tr>
|
|
<% }) %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div> <!-- row g-3 -->
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
const toggleBtn = document.getElementById("toggleEditBtn");
|
|
const inputs = document.querySelectorAll(".db-input");
|
|
const saveBtn = document.getElementById("saveBtn");
|
|
const testBtn = document.getElementById("testBtn");
|
|
const testForm = document.getElementById("testForm");
|
|
|
|
let editMode = false;
|
|
|
|
function updateUI() {
|
|
inputs.forEach((inp) => {
|
|
inp.disabled = !editMode;
|
|
});
|
|
|
|
saveBtn.disabled = !editMode;
|
|
testBtn.disabled = !editMode;
|
|
|
|
if (editMode) {
|
|
toggleBtn.innerHTML = '<i class="bi bi-unlock-fill"></i> Sperren';
|
|
toggleBtn.classList.remove("btn-outline-warning");
|
|
toggleBtn.classList.add("btn-outline-success");
|
|
} else {
|
|
toggleBtn.innerHTML = '<i class="bi bi-lock-fill"></i> Bearbeiten';
|
|
toggleBtn.classList.remove("btn-outline-success");
|
|
toggleBtn.classList.add("btn-outline-warning");
|
|
}
|
|
}
|
|
|
|
toggleBtn.addEventListener("click", () => {
|
|
editMode = !editMode;
|
|
updateUI();
|
|
});
|
|
|
|
// ✅ „Nur testen“ Button -> hidden form füllen -> submit
|
|
testBtn.addEventListener("click", () => {
|
|
testForm.querySelectorAll("input[type='hidden']").forEach((x) => x.remove());
|
|
|
|
inputs.forEach((inp) => {
|
|
const hidden = document.createElement("input");
|
|
hidden.type = "hidden";
|
|
hidden.name = inp.name;
|
|
hidden.value = inp.value;
|
|
testForm.appendChild(hidden);
|
|
});
|
|
|
|
testForm.submit();
|
|
});
|
|
|
|
updateUI();
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|