DB Import und Export sowie Backup und kleinere Fehlerbeseitigung
This commit is contained in:
1 parent
91cc898ec7
commit
1c176ea621
24 files changed
+1081
-96
No files matched your search
+151
-21
@@ -12,13 +12,33 @@
|
||||
// `js/board/board.js`, `js/people/people.js`, `js/projects/projects.js` und `js/load/load.js`; das Speichern
|
||||
// ist über `saveSyncTimer` (in `js/globals.js` deklariert) um 500 ms debounced.
|
||||
|
||||
// save: synchronisiert die aktuellen Daten zum Server.
|
||||
// Jede Sitzung wird als eigenes JSON-Dokument gespeichert. Personen und Rollen
|
||||
// werden in der Sitzung selbst abgelegt.
|
||||
function save(){
|
||||
// save: plant einen (debounced) Sync. Standardmäßig wird die aktuell gewählte
|
||||
// Woche als geändert markiert, da sich die meisten Bearbeitungen auf die
|
||||
// aktuelle Sitzung beziehen. Optionen:
|
||||
// { week:false } – aktuelle Woche NICHT markieren (z. B. reine Personen-Aktionen)
|
||||
// { people:true } – zusätzlich die globale Personenliste als geändert markieren
|
||||
function save(opts = {}){
|
||||
if(opts.week !== false){
|
||||
const w = currentWeek();
|
||||
if(w) dirtyWeeks.add(w.label);
|
||||
}
|
||||
if(opts.people) dirtyPeople = true;
|
||||
scheduleSessionSync();
|
||||
}
|
||||
|
||||
// Dirty-Tracking: merkt sich, welche Wochen bzw. ob die globale Personenliste
|
||||
// seit dem letzten Sync lokal geändert wurden. Gespeichert wird nur das, was
|
||||
// sich wirklich geändert hat (statt wie früher immer alle Wochen).
|
||||
let dirtyWeeks = new Set();
|
||||
let dirtyPeople = false;
|
||||
|
||||
// In-Flight-Schutz für syncAllToServer: läuft bereits ein Sync, wird kein
|
||||
// zweiter gestartet (sonst senden zwei Syncs dieselbe Woche mit gleicher
|
||||
// Revisions-Basis und der zweite bekäme einen scheinbaren 409-Konflikt).
|
||||
// Stattdessen wird ein Nachlauf-Sync eingereiht und nach Abschluss ausgeführt.
|
||||
let syncInFlight = false;
|
||||
let syncQueued = false;
|
||||
|
||||
// scheduleSessionSync: plant den Speichervorgang für alle Wochen und Personen
|
||||
// über einen 500-ms-Timer (Debounce). Bei erneutem Aufruf wird ein laufender
|
||||
// Timer zurückgesetzt, damit aufeinanderfolgende Änderungen in einem Sync enden.
|
||||
@@ -27,30 +47,133 @@ function scheduleSessionSync(){
|
||||
saveSyncTimer = setTimeout(() => { syncAllToServer(); saveSyncTimer = null; }, 500);
|
||||
}
|
||||
|
||||
// syncAllToServer: schreibt die gesamte Datenbank als JSON-Dokumente zum Server.
|
||||
// Zuerst wird die globale Personenliste (`save-people`) gespeichert, danach jede
|
||||
// Sitzung einzeln über `sendWeekToServer()`.
|
||||
// Gibt nichts zurück; Fehler werden nur per console.error protokolliert.
|
||||
// syncAllToServer: schreibt nur die seit dem letzten Sync geänderten Wochen
|
||||
// plus – falls markiert – die globale Personenliste zum Server. Konflikte
|
||||
// (409) auf einer Woche werden über handleWeekConflict() gemeldet. Fehlerhaft
|
||||
// gespeicherte Wochen bleiben dirty, damit der nächste Sync sie erneut
|
||||
// versucht; nur erfolgreich gespeicherte werden aus dem Dirty-Satz entfernt.
|
||||
function syncAllToServer(){
|
||||
// Zuerst globale Personenliste speichern
|
||||
saveGlobalPeopleToServer();
|
||||
// Dann jede Sitzung speichern
|
||||
db.weeks.forEach(w => {
|
||||
sendWeekToServer(w)
|
||||
.then(res => { if(res && res.ok) console.log('Sitzung gespeichert:', w.label); })
|
||||
.catch(err => { console.error('Sitzung konnte nicht gespeichert werden:', w.label, err.message || err); });
|
||||
if(syncInFlight){ syncQueued = true; return; }
|
||||
syncInFlight = true;
|
||||
|
||||
// Zuerst globale Personenliste speichern, falls geändert
|
||||
const jobs = [];
|
||||
if(dirtyPeople) jobs.push(saveGlobalPeopleToServer());
|
||||
|
||||
const weekLabels = Array.from(dirtyWeeks);
|
||||
weekLabels.forEach(label => {
|
||||
const w = db.weeks.find(x => x.label === label);
|
||||
if(!w){ dirtyWeeks.delete(label); return; }
|
||||
jobs.push(sendWeekToServer(w)
|
||||
.then(res => {
|
||||
if(res && res.ok){
|
||||
console.log('Sitzung gespeichert:', w.label);
|
||||
// Lokale Revisions-Basis aktualisieren, damit spätere eigene Saves
|
||||
// nicht fälschlich als Konflikt gemeldet werden.
|
||||
if(res.updatedAt) w.updatedAt = res.updatedAt;
|
||||
dirtyWeeks.delete(label);
|
||||
} else {
|
||||
console.error('Sitzung konnte nicht gespeichert werden:', w.label, res && res.error ? res.error : 'unbekannter Fehler');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
if(err && err.conflict){
|
||||
dirtyWeeks.delete(label);
|
||||
handleWeekConflict(w, err);
|
||||
} else {
|
||||
console.error('Sitzung konnte nicht gespeichert werden:', w.label, err.message || err);
|
||||
// Label bleibt dirty -> der nächste Sync versucht es erneut.
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
Promise.all(jobs).then(() => {
|
||||
syncInFlight = false;
|
||||
if(syncQueued){ syncQueued = false; syncAllToServer(); }
|
||||
});
|
||||
}
|
||||
|
||||
// markWeekDirty: markiert eine bestimmte Woche (label) als geändert – für
|
||||
// Aktionen, die Wochen AUFßER der aktuellen betreffen (z. B. Auto-Lock).
|
||||
function markWeekDirty(label){
|
||||
if(typeof label === 'string' && label) dirtyWeeks.add(label);
|
||||
}
|
||||
|
||||
// markPeopleDirty: markiert die globale Personenliste als geändert.
|
||||
function markPeopleDirty(){
|
||||
dirtyPeople = true;
|
||||
}
|
||||
|
||||
// handleWeekConflict: wird aufgerufen, wenn der Server eine zwischenzeitlich
|
||||
// geänderte Woche meldet (409). Nur diese eine Woche wird vom Server neu
|
||||
// geladen; andere lokal geänderte Wochen (dirtyWeeks) und die Personenliste
|
||||
// bleiben unangetastet und gehen nicht verloren.
|
||||
function handleWeekConflict(week, err){
|
||||
const label = (week && week.label) || (err && err.label) || '';
|
||||
alert('Achtung: ' + label + ' wurde zwischenzeitlich von einer anderen Person '
|
||||
+ 'geändert. Ihre Änderungen an dieser Sitzung wurden nicht gespeichert; '
|
||||
+ 'die aktuellen Daten dieser Sitzung werden geladen.');
|
||||
if(label) reloadWeekFromServer(label);
|
||||
else load();
|
||||
}
|
||||
|
||||
// reloadWeekFromServer: lädt die aktuelle Abteilung neu und ersetzt NUR die
|
||||
// übergebene Woche (bzw. entfernt sie, falls sie am Server gelöscht wurde).
|
||||
// Der Dirty-Zustand der konfliktbehafteten Woche wurde bereits von
|
||||
// syncAllToServer entfernt; alle anderen Dirty-Änderungen bleiben erhalten.
|
||||
function reloadWeekFromServer(label){
|
||||
const requestedDepartment = currentDepartment;
|
||||
fetch(baseUrl() + '/load-all' + departmentParam())
|
||||
.then(r => { if(!r.ok) throw new Error('Server HTTP '+r.status); return r.json(); })
|
||||
.then(data => {
|
||||
if(currentDepartment !== requestedDepartment) return;
|
||||
const fresh = (Array.isArray(data.weeks) ? data.weeks : [])
|
||||
.find(w => w && w.label === label);
|
||||
const idx = db.weeks.findIndex(w => w.label === label);
|
||||
if(idx >= 0){
|
||||
if(fresh){
|
||||
// Felder absichern (wie in load()), damit keine kaputten Daten crashen
|
||||
if(!Array.isArray(fresh.people)) fresh.people = [];
|
||||
if(!Array.isArray(fresh.projects)) fresh.projects = [];
|
||||
if(!fresh.attendance) fresh.attendance = {};
|
||||
db.weeks[idx] = fresh;
|
||||
} else {
|
||||
db.weeks.splice(idx, 1);
|
||||
}
|
||||
// Wurde die aktuelle Woche entfernt, auf die letzte verbleibende wechseln
|
||||
if(!db.weeks.some(w => w.id === db.currentWeek)){
|
||||
db.currentWeek = db.weeks.length ? db.weeks[db.weeks.length - 1].id : null;
|
||||
}
|
||||
}
|
||||
renderAll();
|
||||
showTab(currentTab);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Neu laden der Sitzung fehlgeschlagen:', err);
|
||||
load();
|
||||
});
|
||||
}
|
||||
|
||||
// saveGlobalPeopleToServer: speichert die globale Personenliste der aktuellen
|
||||
// Abteilung über `sendPeopleToServer()` (JSON-Array).
|
||||
// Leere Personenlisten werden übersprungen (kein Schreibvorgang).
|
||||
// Abteilung über `sendPeopleToServer()` (JSON-Array). Auch eine leere Liste
|
||||
// wird gespeichert, damit das Löschen der letzten Person erhalten bleibt. Bei
|
||||
// Erfolg wird der Dirty-Status für Personen zurückgesetzt, bei Fehler bleibt
|
||||
// er erhalten (Retry beim nächsten Sync). Rückgabe: Promise.
|
||||
function saveGlobalPeopleToServer(){
|
||||
const people = db.globalPeople[currentDepartment] || [];
|
||||
if(people.length === 0) return;
|
||||
sendPeopleToServer(people)
|
||||
.then(res => { if(res && res.ok) console.log('Globale Personenliste gespeichert'); })
|
||||
.catch(err => { console.error('Globale Personenliste konnte nicht gespeichert werden:', err.message || err); });
|
||||
return sendPeopleToServer(people)
|
||||
.then(res => {
|
||||
if(res && res.ok){
|
||||
dirtyPeople = false;
|
||||
console.log('Globale Personenliste gespeichert');
|
||||
} else {
|
||||
console.error('Globale Personenliste konnte nicht gespeichert werden:', res && res.error ? res.error : 'unbekannter Fehler');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Globale Personenliste konnte nicht gespeichert werden:', err.message || err);
|
||||
// dirtyPeople bleibt true -> der nächste Sync versucht es erneut.
|
||||
});
|
||||
}
|
||||
|
||||
// switchDepartment: wechselt die aktive Abteilung (z.B. "ELT") und lädt deren
|
||||
@@ -63,6 +186,8 @@ function switchDepartment(dept){
|
||||
// Sync nicht nach dem Reset mit dem neuen currentDepartment feuert und die
|
||||
// Daten der falschen Abteilung schreibt.
|
||||
if(saveSyncTimer){ clearTimeout(saveSyncTimer); saveSyncTimer = null; }
|
||||
dirtyWeeks.clear();
|
||||
dirtyPeople = false;
|
||||
currentDepartment = dept;
|
||||
saveUiState();
|
||||
updateRouteUrl(dept, currentTab);
|
||||
@@ -89,6 +214,11 @@ function switchDepartment(dept){
|
||||
// die Ansicht. Kein Parameter, keine Rückgabe; bei Fehlern oder fehlenden
|
||||
// Daten werden Beispieldaten über `seed()` geladen.
|
||||
function load(){
|
||||
// Nach einem (Neu-)Load sind die lokalen Daten serverseitig aktuell:
|
||||
// laufenden Debounce abbrechen und Dirty-Zustand verwerfen.
|
||||
if(saveSyncTimer){ clearTimeout(saveSyncTimer); saveSyncTimer = null; }
|
||||
dirtyWeeks.clear();
|
||||
dirtyPeople = false;
|
||||
if(!window.fetch){
|
||||
alert('Ihr Browser unterstützt keine Netzwerk-Anfragen. Bitte verwenden Sie einen aktuellen Browser.');
|
||||
seed(); return;
|
||||
|
||||
Reference in new issue
Block a user