Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-16 19:56:44 +02:00
parent a67e527c1d
commit 3dfbd33656
8 changed files with 152 additions and 10 deletions

View File

@@ -93,13 +93,8 @@ public class TicketPlugin extends JavaPlugin {
} }
}); });
// Versionsprüfung der config.yml // Versionsprüfung & automatische Migration der config.yml
String configVersion = getConfig().getString("version", ""); migrateConfig();
String expectedVersion = "2.5";
if (!expectedVersion.equals(configVersion)) {
getLogger().warning("[WARNUNG] config.yml-Version (" + configVersion
+ ") stimmt nicht mit der erwarteten Version (" + expectedVersion + ") überein!");
}
debug = getConfig().getBoolean("debug", false); debug = getConfig().getBoolean("debug", false);
@@ -269,6 +264,49 @@ public class TicketPlugin extends JavaPlugin {
}; };
} }
// ─────────────────────────── Config-Migration ─────────────────────────
/**
* Ergänzt fehlende Schlüssel in der config.yml automatisch aus der
* Standardkonfiguration (config.yml im JAR) und aktualisiert die Version.
* Bestehende Werte des Servers werden dabei NICHT überschrieben.
*/
private void migrateConfig() {
final String EXPECTED = "2.5";
String current = getConfig().getString("version", "");
if (EXPECTED.equals(current)) return; // nichts zu tun
getLogger().info("[Config] Alte Version erkannt (" + current + "). Starte automatische Migration auf " + EXPECTED + " ...");
// Alle Werte aus der Jar-Default-Config als Fallback setzen
// (addDefault überschreibt KEINE bereits gesetzten Werte)
org.bukkit.configuration.file.YamlConfiguration defaults =
org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(
new java.io.InputStreamReader(
java.util.Objects.requireNonNull(getResource("config.yml")),
java.nio.charset.StandardCharsets.UTF_8));
int added = 0;
for (String key : defaults.getKeys(true)) {
if (!getConfig().contains(key)) {
getConfig().set(key, defaults.get(key));
getLogger().info("[Config] Neuer Schlüssel ergänzt: " + key);
added++;
}
}
// Version hochsetzen
getConfig().set("version", EXPECTED);
saveConfig();
if (added > 0) {
getLogger().info("[Config] Migration abgeschlossen: " + added + " neue Schlüssel ergänzt, Version → " + EXPECTED);
} else {
getLogger().info("[Config] Migration abgeschlossen: Keine neuen Schlüssel. Version → " + EXPECTED);
}
}
// ─────────────────────────── Getter ──────────────────────────────────── // ─────────────────────────── Getter ────────────────────────────────────
/** /**

View File

@@ -1330,6 +1330,58 @@ public class DatabaseManager {
// ─────────────────────────── Archivierung ────────────────────────────── // ─────────────────────────── Archivierung ──────────────────────────────
/**
* Verschiebt ein einzelnes geschlossenes Ticket ins Archiv.
* Das Ticket wird aus der aktiven Datenbank entfernt und in archive.json / archive.yml gespeichert.
*
* @param ticketId ID des zu archivierenden Tickets
* @return true wenn erfolgreich archiviert
*/
@SuppressWarnings("unchecked")
public boolean archiveTicket(int ticketId) {
Ticket t = getTicketById(ticketId);
if (t == null) return false;
// Ins Archiv schreiben
File archiveFile = new File(plugin.getDataFolder(), archiveFileName);
JSONArray arr = new JSONArray();
if (archiveFile.exists()) {
try (FileReader fr = new FileReader(archiveFile)) {
Object parsed = new JSONParser().parse(fr);
if (parsed instanceof JSONArray oldArr) arr.addAll(oldArr);
} catch (Exception ignored) {}
}
arr.add(ticketToJson(t));
try (FileWriter fw = new FileWriter(archiveFile)) {
fw.write(arr.toJSONString());
} catch (Exception e) {
sendError("Fehler beim Archivieren von Ticket #" + ticketId + ": " + e.getMessage());
return false;
}
// Aus aktiver DB entfernen
if (useMySQL) {
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement("DELETE FROM tickets WHERE id = ?")) {
ps.setInt(1, ticketId);
ps.executeUpdate();
} catch (Exception e) {
sendError("Fehler beim Entfernen von Ticket #" + ticketId + " aus DB: " + e.getMessage());
return false;
}
} else {
dataConfig.set("tickets." + ticketId, null);
try { dataConfig.save(dataFile); } catch (Exception e) {
sendError("Fehler beim Speichern nach Archivierung: " + e.getMessage());
return false;
}
}
if (plugin != null)
plugin.getLogger().info("[Archiv] Ticket #" + ticketId + " manuell archiviert.");
return true;
}
public int archiveClosedTickets() { public int archiveClosedTickets() {
List<Ticket> all = getAllTickets(); List<Ticket> all = getAllTickets();
List<Ticket> toArchive = new ArrayList<>(); List<Ticket> toArchive = new ArrayList<>();

View File

@@ -180,7 +180,7 @@ public class ApiHandler extends BaseHandler implements HttpHandler {
// Ersteller benachrichtigen (online oder Pending) // Ersteller benachrichtigen (online oder Pending)
String notifyMsg = plugin.lang().format("comment.notify-online", String notifyMsg = plugin.lang().format("comment.notify-online",
"{id}", String.valueOf(ticketId), "{id}", String.valueOf(ticketId),
"{player}", authorDisplay, "{author}", authorDisplay,
"{message}", msg); "{message}", msg);
Bukkit.getScheduler().runTask(plugin, () -> { Bukkit.getScheduler().runTask(plugin, () -> {
org.bukkit.entity.Player creator = Bukkit.getPlayer(ticket.getCreatorUUID()); org.bukkit.entity.Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
@@ -246,6 +246,21 @@ public class ApiHandler extends BaseHandler implements HttpHandler {
sendJson(ex, 200, json.toString()); sendJson(ex, 200, json.toString());
} }
case "archive" -> {
if (!method.equals("POST")) { sendJson(ex, 405, err("Method not allowed")); return; }
if (!session.isAdmin()) { sendJson(ex, 403, err("Kein Zugriff")); return; }
if (ticket.getStatus() != TicketStatus.CLOSED) {
sendJson(ex, 400, err("Nur geschlossene Tickets können archiviert werden")); return;
}
boolean ok = db.archiveTicket(ticketId);
if (ok) {
plugin.getTicketCache().invalidate(ticketId);
sendJson(ex, 200, "{\"ok\":true}");
} else {
sendJson(ex, 500, err("Archivieren fehlgeschlagen"));
}
}
default -> sendJson(ex, 404, err("Unbekannte Aktion: " + action)); default -> sendJson(ex, 404, err("Unbekannte Aktion: " + action));
} }
} }

View File

@@ -377,6 +377,27 @@ public class StaticHandler implements HttpHandler {
s.append("if(r.ok){location.reload();}else{toast('Fehler: '+r.error,'error');}"); s.append("if(r.ok){location.reload();}else{toast('Fehler: '+r.error,'error');}");
s.append("}"); s.append("}");
// Ticket: archive (geschlossenes Ticket ins Archiv verschieben)
s.append("async function archiveTicket(id){");
s.append("if(!confirm('Ticket #'+id+' ins Archiv verschieben?'))return;");
s.append("var r=await api('/ticket/'+id+'/archive','POST');");
s.append("if(r.ok){location.href='/tickets';}else{toast('Fehler: '+r.error,'error');}");
s.append("}");
// Ticket: restore (aus Archiv wiederherstellen)
s.append("async function restoreTicket(id){");
s.append("if(!confirm('Ticket #'+id+' wiederherstellen?'))return;");
s.append("var r=await api('/archive/'+id+'/restore','POST');");
s.append("if(r.ok){location.href='/tickets';}else{toast('Fehler: '+r.error,'error');}");
s.append("}");
// Ticket: delete from archive (permanent)
s.append("async function deleteArchivedTicket(id){");
s.append("if(!confirm('Ticket #'+id+' wirklich PERMANENT loeschen? Diese Aktion kann nicht rueckgaengig gemacht werden!'))return;");
s.append("var r=await api('/archive/'+id+'/delete','POST');");
s.append("if(r.ok){location.href='/tickets?status=ARCHIVED';}else{toast('Fehler: '+r.error,'error');}");
s.append("}");
// FAQ: delete // FAQ: delete
s.append("async function deleteFaq(id){"); s.append("async function deleteFaq(id){");
s.append("if(!confirm('FAQ #'+id+' wirklich loeschen?'))return;"); s.append("if(!confirm('FAQ #'+id+' wirklich loeschen?'))return;");

View File

@@ -344,6 +344,16 @@ public class TicketsHandler extends BaseHandler implements HttpHandler {
sb.append("</div></div>"); sb.append("</div></div>");
} }
// ── Ins Archiv verschieben (nur bei geschlossenen Tickets, nur Admin) ──
if (t.getStatus() == TicketStatus.CLOSED && !fromArchive && session.isAdmin()) {
sb.append("<div class='card'><div class='card-title'>")
.append(escHtml(wl(plugin, "detail-section-actions")))
.append("</div><div style='display:flex;gap:.75rem'>");
sb.append("<button class='btn btn-warning' onclick='archiveTicket(").append(t.getId()).append(")'>")
.append("🗃 ").append(escHtml(wl(plugin, "detail-btn-archive"))).append("</button>");
sb.append("</div></div>");
}
// ── Archiv-Aktionen (nur für archivierte Tickets) ── // ── Archiv-Aktionen (nur für archivierte Tickets) ──
if (fromArchive && session.isAdmin()) { if (fromArchive && session.isAdmin()) {
sb.append("<div class='card'><div class='card-title'>") sb.append("<div class='card'><div class='card-title'>")

View File

@@ -576,6 +576,7 @@ web:
login-blocked: "Zu viele Fehlversuche. Bitte warte {seconds} Sekunden." login-blocked: "Zu viele Fehlversuche. Bitte warte {seconds} Sekunden."
archive-btn-restore: "Wiederherstellen" archive-btn-restore: "Wiederherstellen"
archive-btn-delete: "Permanent löschen" archive-btn-delete: "Permanent löschen"
detail-btn-archive: "Ins Archiv verschieben"
login-label-user: "Benutzername" login-label-user: "Benutzername"
login-label-pass: "Passwort" login-label-pass: "Passwort"
login-btn: "Anmelden" login-btn: "Anmelden"
@@ -614,11 +615,13 @@ web:
tickets-col-prio: "Priorität" tickets-col-prio: "Priorität"
tickets-col-status: "Status" tickets-col-status: "Status"
tickets-col-created: "Erstellt" tickets-col-created: "Erstellt"
nav-archive: "Archiv"
filter-all-status: "Alle Status" filter-all-status: "Alle Status"
filter-open: "Offen" filter-open: "Offen"
filter-claimed: "Angenommen" filter-claimed: "Angenommen"
filter-forwarded: "Weitergeleitet" filter-forwarded: "Weitergeleitet"
filter-closed: "Geschlossen" filter-closed: "Geschlossen"
filter-archived: "Archiviert"
filter-all-cat: "Alle Kategorien" filter-all-cat: "Alle Kategorien"
filter-all-prio: "Alle Prioritäten" filter-all-prio: "Alle Prioritäten"
filter-low: "Niedrig" filter-low: "Niedrig"

View File

@@ -576,6 +576,7 @@ web:
login-error: "Username or password incorrect." login-error: "Username or password incorrect."
archive-btn-restore: "Restore" archive-btn-restore: "Restore"
archive-btn-delete: "Delete permanently" archive-btn-delete: "Delete permanently"
detail-btn-archive: "Move to archive"
login-label-user: "Username" login-label-user: "Username"
login-label-pass: "Password" login-label-pass: "Password"
login-btn: "Sign In" login-btn: "Sign In"
@@ -614,11 +615,13 @@ web:
tickets-col-prio: "Priority" tickets-col-prio: "Priority"
tickets-col-status: "Status" tickets-col-status: "Status"
tickets-col-created: "Created" tickets-col-created: "Created"
nav-archive: "Archive"
filter-all-status: "All Statuses" filter-all-status: "All Statuses"
filter-open: "Open" filter-open: "Open"
filter-claimed: "Claimed" filter-claimed: "Claimed"
filter-forwarded: "Forwarded" filter-forwarded: "Forwarded"
filter-closed: "Closed" filter-closed: "Closed"
filter-archived: "Archived"
filter-all-cat: "All Categories" filter-all-cat: "All Categories"
filter-all-prio: "All Priorities" filter-all-prio: "All Priorities"
filter-low: "Low" filter-low: "Low"

View File

@@ -1,5 +1,5 @@
name: TicketSystem name: TicketSystem
version: 1.1.2 version: 1.1.4
main: de.ticketsystem.TicketPlugin main: de.ticketsystem.TicketPlugin
api-version: 1.20 api-version: 1.20
author: M_Viper author: M_Viper