Änderung der Datenbankanbindung
This commit is contained in:
parent
bc7dfc0210
commit
d38add6270
@ -1 +1 @@
|
||||
G/kDLEJ/LddnnNnginIGYSM4Ax0g5pJaF0lrdOXke51cz3jSTrZxP7rjTXRlqLcoUJhPaVLvjb/DcyNYB/C339a+PFWyIdWYjSb6G4aPkD8J21yFWDDLpc08bXvoAx2PeE+Fc9v5mJUGDVv2wQoDvkHqIpN8ewrfRZ6+JF3OfQ==
|
||||
4PsgCvoOJLNXPpxOHOvm+KbVYz3pNxg8oOXO7zoH3MPffEhZLI7i5qf3o6oqZDI04us8xSSz9j3KIN+Atno/VFlYzSoq3ki1F+WSTz37LfcE3goPqhm6UaH8c9lHdulemH9tqgGq/DxgbKaup5t/ZJnLseaHHpdyTZok1jWULN0nlDuL/HvVVtqw5sboPqU=
|
||||
1
db.js
1
db.js
@ -11,6 +11,7 @@ function initPool() {
|
||||
|
||||
return mysql.createPool({
|
||||
host: config.db.host,
|
||||
port: config.db.port || 3306,
|
||||
user: config.db.user,
|
||||
password: config.db.password,
|
||||
database: config.db.name,
|
||||
|
||||
@ -71,6 +71,7 @@ router.get("/database", requireAdmin, async (req, res) => {
|
||||
if (cfg?.db) {
|
||||
const conn = await mysql.createConnection({
|
||||
host: cfg.db.host,
|
||||
port: Number(cfg.db.port || 3306), // ✅ WICHTIG: Port nutzen
|
||||
user: cfg.db.user,
|
||||
password: cfg.db.password,
|
||||
database: cfg.db.name,
|
||||
@ -131,26 +132,46 @@ router.get("/database", requireAdmin, async (req, res) => {
|
||||
dbConfig: cfg?.db || null,
|
||||
testResult: null,
|
||||
backupFiles,
|
||||
systemInfo, // ✅ DAS HAT GEFEHLT
|
||||
systemInfo,
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ Nur testen (ohne speichern)
|
||||
router.post("/database/test", requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { host, user, password, name } = req.body;
|
||||
const backupDir = path.join(__dirname, "..", "backups");
|
||||
|
||||
if (!host || !user || !password || !name) {
|
||||
function getBackupFiles() {
|
||||
try {
|
||||
if (fs.existsSync(backupDir)) {
|
||||
return fs
|
||||
.readdirSync(backupDir)
|
||||
.filter((f) => f.toLowerCase().endsWith(".sql"))
|
||||
.sort()
|
||||
.reverse();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("❌ Backup Ordner Fehler:", err);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const { host, port, user, password, name } = req.body;
|
||||
|
||||
if (!host || !port || !user || !password || !name) {
|
||||
const cfg = loadConfig();
|
||||
return res.render("admin/database", {
|
||||
user: req.session.user,
|
||||
dbConfig: cfg?.db || null,
|
||||
testResult: { ok: false, message: "❌ Bitte alle Felder ausfüllen." },
|
||||
backupFiles: getBackupFiles(),
|
||||
systemInfo: null,
|
||||
});
|
||||
}
|
||||
|
||||
const conn = await mysql.createConnection({
|
||||
host,
|
||||
port: Number(port),
|
||||
user,
|
||||
password,
|
||||
database: name,
|
||||
@ -161,8 +182,10 @@ router.post("/database/test", requireAdmin, async (req, res) => {
|
||||
|
||||
return res.render("admin/database", {
|
||||
user: req.session.user,
|
||||
dbConfig: { host, user, password, name },
|
||||
dbConfig: { host, port: Number(port), user, password, name }, // ✅ PORT bleibt drin!
|
||||
testResult: { ok: true, message: "✅ Verbindung erfolgreich!" },
|
||||
backupFiles: getBackupFiles(),
|
||||
systemInfo: null,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("❌ DB TEST ERROR:", err);
|
||||
@ -174,22 +197,59 @@ router.post("/database/test", requireAdmin, async (req, res) => {
|
||||
ok: false,
|
||||
message: "❌ Verbindung fehlgeschlagen: " + err.message,
|
||||
},
|
||||
backupFiles: getBackupFiles(),
|
||||
systemInfo: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ DB Settings speichern + Verbindung testen
|
||||
router.post("/database", requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { host, user, password, name } = req.body;
|
||||
|
||||
if (!host || !user || !password || !name) {
|
||||
req.flash("error", "❌ Bitte alle Felder ausfüllen.");
|
||||
return res.redirect("/admin/database");
|
||||
function flashSafe(type, msg) {
|
||||
if (typeof req.flash === "function") {
|
||||
req.flash(type, msg);
|
||||
return;
|
||||
}
|
||||
req.session.flash = req.session.flash || [];
|
||||
req.session.flash.push({ type, message: msg });
|
||||
}
|
||||
|
||||
const backupDir = path.join(__dirname, "..", "backups");
|
||||
|
||||
// ✅ backupFiles immer bereitstellen
|
||||
function getBackupFiles() {
|
||||
try {
|
||||
if (fs.existsSync(backupDir)) {
|
||||
return fs
|
||||
.readdirSync(backupDir)
|
||||
.filter((f) => f.toLowerCase().endsWith(".sql"))
|
||||
.sort()
|
||||
.reverse();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("❌ Backup Ordner Fehler:", err);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const { host, port, user, password, name } = req.body;
|
||||
|
||||
if (!host || !port || !user || !password || !name) {
|
||||
flashSafe("danger", "❌ Bitte alle Felder ausfüllen.");
|
||||
return res.render("admin/database", {
|
||||
user: req.session.user,
|
||||
dbConfig: req.body,
|
||||
testResult: { ok: false, message: "❌ Bitte alle Felder ausfüllen." },
|
||||
backupFiles: getBackupFiles(),
|
||||
systemInfo: null,
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ Verbindung testen
|
||||
const conn = await mysql.createConnection({
|
||||
host,
|
||||
port: Number(port),
|
||||
user,
|
||||
password,
|
||||
database: name,
|
||||
@ -198,25 +258,51 @@ router.post("/database", requireAdmin, async (req, res) => {
|
||||
await conn.query("SELECT 1");
|
||||
await conn.end();
|
||||
|
||||
// ✅ Speichern in config.enc
|
||||
// ✅ Speichern inkl. Port
|
||||
const current = loadConfig() || {};
|
||||
current.db = { host, user, password, name };
|
||||
current.db = {
|
||||
host,
|
||||
port: Number(port),
|
||||
user,
|
||||
password,
|
||||
name,
|
||||
};
|
||||
saveConfig(current);
|
||||
|
||||
// ✅ DB Pool resetten (falls vorhanden)
|
||||
// ✅ Pool reset
|
||||
if (typeof db.resetPool === "function") {
|
||||
db.resetPool();
|
||||
}
|
||||
|
||||
req.flash(
|
||||
"success",
|
||||
"✅ DB Einstellungen gespeichert + Verbindung erfolgreich getestet.",
|
||||
);
|
||||
return res.redirect("/admin/database");
|
||||
flashSafe("success", "✅ DB Einstellungen gespeichert!");
|
||||
|
||||
// ✅ DIREKT NEU LADEN aus config.enc (damit wirklich die gespeicherten Werte drin stehen)
|
||||
const freshCfg = loadConfig();
|
||||
|
||||
return res.render("admin/database", {
|
||||
user: req.session.user,
|
||||
dbConfig: freshCfg?.db || null,
|
||||
testResult: {
|
||||
ok: true,
|
||||
message: "✅ Gespeichert und Verbindung getestet.",
|
||||
},
|
||||
backupFiles: getBackupFiles(),
|
||||
systemInfo: null,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("❌ DB UPDATE ERROR:", err);
|
||||
req.flash("error", "❌ Verbindung fehlgeschlagen: " + err.message);
|
||||
return res.redirect("/admin/database");
|
||||
flashSafe("danger", "❌ Verbindung fehlgeschlagen: " + err.message);
|
||||
|
||||
return res.render("admin/database", {
|
||||
user: req.session.user,
|
||||
dbConfig: req.body,
|
||||
testResult: {
|
||||
ok: false,
|
||||
message: "❌ Verbindung fehlgeschlagen: " + err.message,
|
||||
},
|
||||
backupFiles: getBackupFiles(),
|
||||
systemInfo: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,380 +1,178 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Datenbankverwaltung</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<%- include("../partials/page-header", {
|
||||
user,
|
||||
title: "Datenbankverwaltung",
|
||||
subtitle: "",
|
||||
showUserName: true
|
||||
}) %>
|
||||
|
||||
<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>
|
||||
<div class="content p-4">
|
||||
|
||||
<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' %>">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row g-3">
|
||||
|
||||
<!-- ✅ Sidebar -->
|
||||
<div class="col-md-3 col-lg-2 p-0">
|
||||
<%- include("../partials/admin-sidebar", { user, active: "database" }) %>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Content -->
|
||||
<div class="col-md-9 col-lg-10">
|
||||
|
||||
<!-- ✅ DB Konfiguration -->
|
||||
<div class="card shadow mb-3">
|
||||
<div class="card-body">
|
||||
|
||||
<h4 class="mb-3">
|
||||
<i class="bi bi-sliders"></i> Datenbank Konfiguration
|
||||
</h4>
|
||||
|
||||
<p class="text-muted mb-4">
|
||||
Hier kannst du die DB-Verbindung testen und speichern.
|
||||
</p>
|
||||
|
||||
<!-- ✅ TEST (ohne speichern) + SPEICHERN -->
|
||||
<form method="POST" action="/admin/database/test" class="row g-3 mb-3" autocomplete="off">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Host / IP</label>
|
||||
<input
|
||||
type="text"
|
||||
name="host"
|
||||
class="form-control"
|
||||
value="<%= dbConfig?.host || '' %>"
|
||||
autocomplete="off"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Port</label>
|
||||
<input
|
||||
type="number"
|
||||
name="port"
|
||||
class="form-control"
|
||||
value="<%= dbConfig?.port || 3306 %>"
|
||||
autocomplete="off"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Datenbank</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
class="form-control"
|
||||
value="<%= dbConfig?.name || '' %>"
|
||||
autocomplete="off"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Benutzer</label>
|
||||
<input
|
||||
type="text"
|
||||
name="user"
|
||||
class="form-control"
|
||||
value="<%= dbConfig?.user || '' %>"
|
||||
autocomplete="off"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Passwort</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
class="form-control"
|
||||
value="<%= dbConfig?.password || '' %>"
|
||||
autocomplete="off"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="col-12 d-flex flex-wrap gap-2">
|
||||
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
<i class="bi bi-plug"></i> Verbindung testen
|
||||
</button>
|
||||
|
||||
<!-- ✅ Speichern + Testen -->
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-success"
|
||||
formaction="/admin/database"
|
||||
>
|
||||
<i class="bi bi-save"></i> Speichern
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<% if (typeof testResult !== "undefined" && testResult) { %>
|
||||
<div class="alert <%= testResult.ok ? 'alert-success' : 'alert-danger' %> mb-0">
|
||||
<%= testResult.message %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="card shadow">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ System Info -->
|
||||
<div class="card shadow mb-3">
|
||||
<div class="card-body">
|
||||
|
||||
<h4 class="mb-3">Datenbank Tools</h4>
|
||||
<h4 class="mb-3">
|
||||
<i class="bi bi-info-circle"></i> Systeminformationen
|
||||
</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.
|
||||
<% if (typeof systemInfo !== "undefined" && systemInfo?.error) { %>
|
||||
|
||||
<div class="alert alert-danger mb-0">
|
||||
❌ Fehler beim Auslesen der Datenbankinfos:
|
||||
<div class="mt-2"><code><%= systemInfo.error %></code></div>
|
||||
</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 { %>
|
||||
<% } else if (typeof systemInfo !== "undefined" && systemInfo) { %>
|
||||
|
||||
<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 class="col-md-4">
|
||||
<div class="border rounded p-3 h-100">
|
||||
<div class="text-muted small">MySQL Version</div>
|
||||
<div class="fw-bold"><%= systemInfo.version %></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 class="col-md-4">
|
||||
<div class="border rounded p-3 h-100">
|
||||
<div class="text-muted small">Anzahl Tabellen</div>
|
||||
<div class="fw-bold"><%= systemInfo.tableCount %></div>
|
||||
</div>
|
||||
</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">
|
||||
<div class="col-md-4">
|
||||
<div class="border rounded p-3 h-100">
|
||||
<div class="text-muted small">Datenbankgröße</div>
|
||||
<div class="fw-bold"><%= systemInfo.dbSizeMB %> MB</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (systemInfo.tables && systemInfo.tables.length > 0) { %>
|
||||
<hr>
|
||||
|
||||
<h6 class="mb-2">Tabellenübersicht</h6>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Tabellenname</th>
|
||||
<th style="width: 90px;" class="text-end">Rows</th>
|
||||
<th style="width: 110px;" class="text-end">MB</th>
|
||||
<th>Tabelle</th>
|
||||
<th class="text-end">Zeilen</th>
|
||||
<th class="text-end">Größe (MB)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -389,78 +187,66 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<% } else { %>
|
||||
|
||||
<div class="alert alert-warning mb-0">
|
||||
⚠️ Keine Systeminfos verfügbar (DB ist evtl. nicht konfiguriert oder Verbindung fehlgeschlagen).
|
||||
</div>
|
||||
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- row g-3 -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Backup & Restore -->
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
|
||||
<h4 class="mb-3">
|
||||
<i class="bi bi-hdd-stack"></i> Backup & Restore
|
||||
</h4>
|
||||
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
|
||||
<!-- ✅ Backup erstellen -->
|
||||
<form action="/admin/database/backup" method="POST">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-download"></i> Backup erstellen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- ✅ Restore auswählen -->
|
||||
<form action="/admin/database/restore" method="POST">
|
||||
<div class="input-group">
|
||||
|
||||
<select name="backupFile" class="form-select" required>
|
||||
<option value="">Backup auswählen...</option>
|
||||
|
||||
<% (typeof backupFiles !== "undefined" && backupFiles ? backupFiles : []).forEach(file => { %>
|
||||
<option value="<%= file %>"><%= file %></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
|
||||
<button type="submit" class="btn btn-warning">
|
||||
<i class="bi bi-upload"></i> Restore starten
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<% if (typeof backupFiles === "undefined" || !backupFiles || backupFiles.length === 0) { %>
|
||||
<div class="alert alert-secondary mt-3 mb-0">
|
||||
ℹ️ Noch keine Backups vorhanden.
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@ -63,5 +63,17 @@
|
||||
<% } %>
|
||||
</a>
|
||||
|
||||
<!-- ✅ Datenbank -->
|
||||
<a
|
||||
href="<%= hrefIfAllowed(isAdmin, '/admin/database') %>"
|
||||
class="nav-item <%= active === 'database' ? 'active' : '' %> <%= lockClass(isAdmin) %>"
|
||||
title="<%= isAdmin ? '' : 'Nur Admin' %>"
|
||||
>
|
||||
<i class="bi bi-hdd-stack"></i> Datenbank
|
||||
<% if (!isAdmin) { %>
|
||||
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
|
||||
<% } %>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user