diff --git a/.gitignore b/.gitignore index 1b4b41e..ff87601 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /persistance/*.db /logs *.env +/wwwroot/downloads/sqlite/*.sql /persistence/*.db-shm /persistence/*.db-wal diff --git a/Watcher/Controllers/DatabaseController.cs b/Watcher/Controllers/DatabaseController.cs new file mode 100644 index 0000000..481077f --- /dev/null +++ b/Watcher/Controllers/DatabaseController.cs @@ -0,0 +1,185 @@ +using System.Runtime.InteropServices; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using System.IO; +using System.Diagnostics; + +namespace Watcher.Controllers +{ + [Authorize] + public class DatabaseController : Controller + { + private readonly string _dbPath = Path.Combine(Directory.GetCurrentDirectory(), "persistence", "watcher.db"); + private readonly string _backupFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "downloads", "sqlite"); + + + public class DumpFileInfo + { + public string? FileName { get; set; } + public long SizeKb { get; set; } + public DateTime Created { get; set; } + } + + + public DatabaseController(IWebHostEnvironment env) + { + _backupFolder = Path.Combine(env.WebRootPath, "downloads", "sqlite"); + + if (!Directory.Exists(_backupFolder)) + Directory.CreateDirectory(_backupFolder); + } + + [HttpPost("/maintenance/sqlite-dump")] + public IActionResult CreateSqlDump() + { + try + { + // Zielordner sicherstellen + if (!Directory.Exists(_backupFolder)) + Directory.CreateDirectory(_backupFolder); + + // Ziel-Dateiname z. B. mit Zeitstempel + var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); + var dumpFileName = $"watcher_dump_{timestamp}.sql"; + var dumpFilePath = Path.Combine(_backupFolder, dumpFileName); + + using var connection = new SqliteConnection($"Data Source={_dbPath}"); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = "SELECT sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL;"; + using var writer = new StreamWriter(dumpFilePath); + + // Write schema + using (var schemaCmd = connection.CreateCommand()) + { + schemaCmd.CommandText = "SELECT sql FROM sqlite_master WHERE type='table'"; + using var reader = schemaCmd.ExecuteReader(); + while (reader.Read()) + { + writer.WriteLine(reader.GetString(0) + ";"); + } + } + + // Write content + using (var tableCmd = connection.CreateCommand()) + { + tableCmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"; + using var tableReader = tableCmd.ExecuteReader(); + while (tableReader.Read()) + { + var tableName = tableReader.GetString(0); + using var dataCmd = connection.CreateCommand(); + dataCmd.CommandText = $"SELECT * FROM {tableName}"; + using var dataReader = dataCmd.ExecuteReader(); + + while (dataReader.Read()) + { + var columns = new string[dataReader.FieldCount]; + var values = new string[dataReader.FieldCount]; + + for (int i = 0; i < dataReader.FieldCount; i++) + { + columns[i] = dataReader.GetName(i); + var val = dataReader.GetValue(i); + values[i] = val == null || val == DBNull.Value + ? "NULL" + : $"'{val.ToString().Replace("'", "''")}'"; + } + + writer.WriteLine($"INSERT INTO {tableName} ({string.Join(",", columns)}) VALUES ({string.Join(",", values)});"); + } + } + } + + writer.Flush(); + + //return Ok($"Dump erfolgreich erstellt: {dumpFileName}"); + + TempData["DumpMessage"] = "SQLite-Dump erfolgreich erstellt."; + return RedirectToAction("UserSettings", "Auth"); + } + catch (Exception ex) + { + //return StatusCode(500, $"Fehler beim Erstellen des Dumps: {ex.Message}"); + TempData["DumpError"] = $"Fehler beim Erstellen des Dumps: {ex.Message}"; + return RedirectToAction("UserSettings", "Auth"); + } + } + + public IActionResult ManageSqlDumps() + { + var files = Directory.GetFiles(_backupFolder, "*.sql") + .Select(f => new DumpFileInfo + { + FileName = Path.GetFileName(f), + SizeKb = new FileInfo(f).Length / 1024, + Created = System.IO.File.GetCreationTime(f) + }) + .OrderByDescending(f => f.Created) + .ToList(); + + return View(files); + } + + [HttpPost] + public IActionResult Delete(string fileName) + { + var filePath = Path.Combine(_backupFolder, fileName); + if (System.IO.File.Exists(filePath)) + { + System.IO.File.Delete(filePath); + TempData["Success"] = $"Backup {fileName} wurde gelöscht."; + } + + return RedirectToAction("ManageSqlDumps"); + } + + // 🔹 4. Dump wiederherstellen + [HttpPost] + public IActionResult Restore(string fileName) + { + var filePath = Path.Combine(_backupFolder, fileName); + if (!System.IO.File.Exists(filePath)) + { + TempData["Error"] = "Dump nicht gefunden."; + return RedirectToAction("ManageSqlDumps"); + } + + try + { + var dbPath = Path.Combine(Directory.GetCurrentDirectory(), "data", "yourdb.db"); // anpassen + + // Leere Datenbank + System.IO.File.WriteAllText(dbPath, ""); + + var psi = new ProcessStartInfo + { + FileName = "sqlite3", + Arguments = $"\"{dbPath}\" \".read \\\"{filePath}\\\"\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + using var proc = Process.Start(psi); + proc.WaitForExit(); + + TempData["Success"] = $"Backup {fileName} wurde wiederhergestellt."; + } + catch (Exception ex) + { + TempData["Error"] = $"Fehler beim Wiederherstellen: {ex.Message}"; + } + + return RedirectToAction("ManageSqlDumps"); + } + } + +} \ No newline at end of file diff --git a/Watcher/Controllers/DownloadController.cs b/Watcher/Controllers/DownloadController.cs index 45a0850..d544652 100644 --- a/Watcher/Controllers/DownloadController.cs +++ b/Watcher/Controllers/DownloadController.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -10,17 +11,17 @@ namespace Watcher.Controllers; [Authorize] public class DownloadController : Controller { - [HttpGet("Download/File/{filename}")] - public IActionResult FileDownload(string filename) + [HttpGet("Download/File/{type}/{filename}")] + public IActionResult FileDownload(string type, string filename) { // Nur erlaubte Endungen zulassen (Sicherheit!) - var allowedExtensions = new[] { ".exe", "" }; + var allowedExtensions = new[] { ".exe", "", ".sql" }; var extension = Path.GetExtension(filename).ToLowerInvariant(); if (!allowedExtensions.Contains(extension)) return BadRequest("Dateityp nicht erlaubt"); - var path = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "downloads", filename); + var path = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "downloads", type, filename); if (!System.IO.File.Exists(path)) return NotFound("Datei nicht gefunden"); diff --git a/Watcher/Views/Auth/UserSettings.cshtml b/Watcher/Views/Auth/UserSettings.cshtml index 49c029f..d2c1bbd 100644 --- a/Watcher/Views/Auth/UserSettings.cshtml +++ b/Watcher/Views/Auth/UserSettings.cshtml @@ -70,14 +70,43 @@
Datenbank-Engine:
@(DbEngine ?? "nicht gefunden") -
- -
- + + @if (DbEngine == "Microsoft.EntityFrameworkCore.Sqlite") + { +
+
+ +
+
+ +
+
+ + } + else if (DbEngine == "Microsoft.EntityFrameworkCore.MySQL") + { +

MySQL Dump aktuell nicht möglich

+ } + + + @if (TempData["DumpMessage"] != null) + { +
+ @TempData["DumpMessage"] +
+ } + @if (TempData["DumpError"] != null) + { +
+ @TempData["DumpError"] +
+ } @@ -95,7 +124,7 @@
...:
- + diff --git a/Watcher/Views/Database/ManageSqlDumps.cshtml b/Watcher/Views/Database/ManageSqlDumps.cshtml new file mode 100644 index 0000000..059aa61 --- /dev/null +++ b/Watcher/Views/Database/ManageSqlDumps.cshtml @@ -0,0 +1,54 @@ +@model List +@{ + ViewData["Title"] = "Datenbank-Dumps"; +} + +

Datenbank-Dumps

+ +@if (TempData["Success"] != null) +{ +
@TempData["Success"]
+} +@if (TempData["Error"] != null) +{ +
@TempData["Error"]
+} + + + + + + + + + + + + @foreach (var dump in Model) + { + + + + + + + } + +
DateinameGröße (KB)ErstelltAktionen
@dump.FileName@dump.SizeKb@dump.Created.ToString("dd.MM.yyyy HH:mm") + + Download + + +
+ +
+ +
+ +
+
diff --git a/Watcher/Views/Server/_ServerCard.cshtml b/Watcher/Views/Server/_ServerCard.cshtml index e11be01..22cf30c 100644 --- a/Watcher/Views/Server/_ServerCard.cshtml +++ b/Watcher/Views/Server/_ServerCard.cshtml @@ -46,8 +46,8 @@ - - 🖥️ Tool herunterladen + + 🖥️ Linux Tool herunterladen diff --git a/Watcher/wwwroot/downloads/heartbeat b/Watcher/wwwroot/downloads/Linux/heartbeat similarity index 100% rename from Watcher/wwwroot/downloads/heartbeat rename to Watcher/wwwroot/downloads/Linux/heartbeat