Update über den Git-Server, Tests, Repo ohne node_modules und Sicherungen
- Admin → Update: neuere Versionen (Tags) vom Git-Server anzeigen und per Knopf einspielen; ausgeführt vom Dienst auf dem Rechner (Docker/update-einrichten.sh, Docker/update-ausfuehren.sh), nur vorwärts, Fehler bei ausbleibendem Neustart, abgebrochene Läufe sperren nicht - Tests (npm test): Zusammenführen paralleler Änderungen, Rechteprüfung beim Speichern; SESSIONS_DIR lenkt den Datenordner für Testserver um - node_modules, Sicherungen und Uploads aus dem Repo genommen - cors und requireRole() entfernt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e12936444d
commit
fd44db2213
2357 files changed
+588
-514948
No files matched your search
+125
-1
@@ -41,7 +41,7 @@
|
||||
//
|
||||
// Dass der Bereich für Nicht-Administratoren gar nicht erst auftaucht, ist
|
||||
// wie überall in dieser App ein reines UI-Gate; die eigentliche Sperre sitzt
|
||||
// serverseitig in requireRole('admin') auf PUT /app-settings,
|
||||
// serverseitig in requireRecht() (je Abschnitt ein Recht) auf PUT /app-settings,
|
||||
// PUT /honorar-config, POST/PATCH/DELETE /lph-templates, POST/DELETE /holidays,
|
||||
// GET /backup, POST /import und POST /delete-week.
|
||||
|
||||
@@ -65,6 +65,7 @@ const ADMIN_SECTIONS = [
|
||||
{ key: 'planchecker', title: 'Plan-Checker-Merkmale', recht: 'a.checker' },
|
||||
{ key: 'betrieb', title: 'Betrieb & Sicherheit', recht: 'a.betrieb' },
|
||||
{ key: 'teams', title: 'Teams', recht: 'a.teams' },
|
||||
{ key: 'update', title: 'Update', recht: 'a.update' },
|
||||
{ key: 'papierkorb', title: 'Papierkorb', recht: 'a.papierkorb' },
|
||||
{ key: 'datenbank', title: 'Datenbank & Sitzungen', recht: ['a.backup', 'a.import'] },
|
||||
];
|
||||
@@ -168,6 +169,7 @@ function showAdminSection(key){
|
||||
// frisch holen.
|
||||
if(key === 'papierkorb') loadAdminPapierkorb();
|
||||
if(key === 'teams') loadAdminTeams();
|
||||
if(key === 'update') loadAdminUpdate();
|
||||
if(key === 'rollen' && typeof renderRollenRechte === 'function') renderRollenRechte();
|
||||
|
||||
try {
|
||||
@@ -1113,6 +1115,128 @@ async function adminProjektEndgueltigLoeschen(id){
|
||||
// ---------- Teams (Admin → Teams, Server: /admin/teams*) ----------
|
||||
// Die Webhook-Adresse ist ein Geheimnis und kommt nie zurück in den Browser;
|
||||
// angezeigt wird nur, ob eine eingetragen ist (und an welchen Host).
|
||||
// ---------- Update über den Git-Server (Admin → Update) ----------
|
||||
//
|
||||
// Versionen sind Tags vX.Y.Z auf dem Git-Server; gesucht wird nur auf
|
||||
// Knopfdruck (GET /admin/update?suchen=1). „Update installieren“ legt einen
|
||||
// Auftrag ab, den der Dienst auf dem Rechner ausführt
|
||||
// (Docker/update-ausfuehren.sh). Währenddessen fragt die Karte alle 5 s nach
|
||||
// dem Stand — auch über den Neustart des Servers hinweg, in dem die Anfrage
|
||||
// scheitert.
|
||||
let updateTimer = null;
|
||||
let updateZiel = ''; // Version, auf die gerade aktualisiert wird
|
||||
let updateSuche = null; // letzte Antwort von „Nach Updates suchen“
|
||||
let updateBestaetigen = ''; // Version, deren Knopf auf „Wirklich?“ steht
|
||||
|
||||
function updateDatum(iso){
|
||||
const d = iso ? new Date(iso) : null;
|
||||
return d && !isNaN(d) ? d.toLocaleDateString('de-DE') + ', ' + d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) : '';
|
||||
}
|
||||
|
||||
async function loadAdminUpdate(suchen){
|
||||
const el = document.getElementById('adminUpdateInhalt');
|
||||
if(!el) return;
|
||||
let d;
|
||||
try {
|
||||
d = await fetch(baseUrl() + '/admin/update' + (suchen ? '?suchen=1' : '')).then(r => r.json());
|
||||
} catch(e){
|
||||
// Während des Neustarts ist der Server kurz weg — dann weiter warten.
|
||||
if(updateZiel){ el.innerHTML = updateLaufHtml({ version: updateZiel, schritt: 'Neustart', laeuft: true }, true); updateWeiterFragen(); }
|
||||
else el.innerHTML = '<span class="settings-warn">Konnte nicht geladen werden.</span>';
|
||||
return;
|
||||
}
|
||||
if(!d || !d.ok){ el.innerHTML = '<span class="settings-warn">Keine Berechtigung.</span>'; return; }
|
||||
if(suchen) updateSuche = d;
|
||||
|
||||
const inst = d.installiert;
|
||||
const st = d.status;
|
||||
const laeuft = d.auftragOffen || (st && st.laeuft);
|
||||
if(laeuft) updateZiel = (st && st.laeuft && st.version) || updateZiel;
|
||||
|
||||
let html = `<div class="teams-status${inst && inst.version ? ' ist-an' : ''}">
|
||||
<span class="teams-punkt" aria-hidden="true"></span>
|
||||
<span class="teams-status-text">${inst && inst.version
|
||||
? '<b>Installiert: <span class="update-version">' + esc(inst.version) + '</span></b><span>' + esc(inst.commit || '') + (inst.am ? ' · seit ' + esc(updateDatum(inst.am)) : '') + '</span>'
|
||||
: '<b>Installierte Version unbekannt</b><span>Wird beim Einrichten des Update-Dienstes vermerkt</span>'}</span>
|
||||
</div>`;
|
||||
|
||||
if(!d.eingerichtet){
|
||||
html += '<p class="settings-warn">Der Git-Server ist nicht eingetragen (UPDATE_REPO_API in docker-compose.yml).</p>';
|
||||
} else if(!d.dienst){
|
||||
html += '<p class="settings-warn">Der Update-Dienst ist auf diesem Rechner nicht eingerichtet. Einmal im Repo ausführen: <code>sudo bash Docker/update-einrichten.sh</code></p>';
|
||||
}
|
||||
|
||||
if(laeuft){
|
||||
html += updateLaufHtml(st && st.laeuft ? st : { version: updateZiel, schritt: 'Auftrag übergeben', laeuft: true });
|
||||
el.innerHTML = html;
|
||||
updateWeiterFragen();
|
||||
return;
|
||||
}
|
||||
if(updateZiel){
|
||||
// Lauf beendet: fertig oder Fehler.
|
||||
const ok = st && !st.fehler && inst && inst.version === updateZiel;
|
||||
html += ok
|
||||
? `<div class="update-meldung ist-ok"><b>Aktualisiert auf ${esc(updateZiel)}</b><span>Bitte die Seite neu laden, damit der Browser die neue Version verwendet.</span>
|
||||
<button type="button" class="small" onclick="location.reload()">Seite neu laden</button></div>`
|
||||
: '';
|
||||
if(ok) updateZiel = '';
|
||||
}
|
||||
if(st && st.fehler){
|
||||
html += `<div class="update-meldung ist-fehler"><b>Update auf ${esc(st.version || '')} fehlgeschlagen</b>
|
||||
<span>${esc(st.schritt || '')}: ${esc(st.fehler)}${st.am ? ' (' + esc(updateDatum(st.am)) + ')' : ''}</span>
|
||||
${d.log ? '<pre class="update-log">' + esc(d.log) + '</pre>' : ''}</div>`;
|
||||
updateZiel = '';
|
||||
}
|
||||
|
||||
const s = updateSuche;
|
||||
if(s && s.fehler) html += '<p class="settings-warn">' + esc(s.fehler) + '</p>';
|
||||
else if(s && s.neuer && !s.neuer.length) html += '<p class="hint">Keine neuere Version. Neueste auf dem Git-Server: <span class="update-version">' + esc(s.neueste || '–') + '</span></p>';
|
||||
else if(s && s.neuer){
|
||||
const neueste = s.neuer[0].version;
|
||||
html += '<div class="update-liste">' + s.neuer.map(t => `<div class="update-eintrag">
|
||||
<span class="update-version">${esc(t.version)}</span>
|
||||
<span class="update-text">${esc(t.text.replace(/^v\d+\.\d+\.\d+:\s*/, '') || 'ohne Beschreibung')}</span>
|
||||
<span class="update-datum">${esc(updateDatum(t.datum))}</span></div>`).join('') + '</div>';
|
||||
const wirklich = updateBestaetigen === neueste;
|
||||
html += `<div class="teams-aktionen">
|
||||
<button type="button" class="small${wirklich ? ' danger' : ''}" ${d.dienst && inst && inst.version ? '' : 'disabled'} onclick="updateInstallieren('${esc(neueste)}')">
|
||||
${wirklich ? 'Wirklich? App ist einige Minuten weg' : 'Update auf ' + esc(neueste) + ' installieren'}</button>
|
||||
${wirklich ? '<button type="button" class="secondary small" onclick="updateBestaetigen=\'\';loadAdminUpdate()">Abbrechen</button>' : ''}
|
||||
</div>`;
|
||||
}
|
||||
html += `<div class="teams-aktionen"><button type="button" class="secondary small" ${d.eingerichtet ? '' : 'disabled'} onclick="loadAdminUpdate(true)">Nach Updates suchen</button></div>`;
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
// updateLaufHtml: Fortschritt eines laufenden Updates.
|
||||
function updateLaufHtml(st, serverWeg){
|
||||
const schritte = ['Version holen', 'Container bauen', 'Neustart'];
|
||||
const i = Math.max(0, schritte.indexOf(st.schritt));
|
||||
return `<div class="update-meldung ist-lauf"><b>Update auf ${esc(st.version || '')} läuft</b>
|
||||
<ol class="update-schritte">${schritte.map((x, j) => `<li class="${j < i ? 'ist-fertig' : (j === i ? 'ist-jetzt' : '')}">${esc(x)}</li>`).join('')}</ol>
|
||||
<span>${serverWeg ? 'Der Server startet gerade neu …' : 'Das Bauen dauert auf dem Pi einige Minuten. Diese Seite kann offen bleiben.'}</span></div>`;
|
||||
}
|
||||
|
||||
function updateWeiterFragen(){
|
||||
clearTimeout(updateTimer);
|
||||
updateTimer = setTimeout(() => {
|
||||
if(currentTab === 'admin' && adminSection === 'update') loadAdminUpdate();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
async function updateInstallieren(version){
|
||||
if(updateBestaetigen !== version){ updateBestaetigen = version; loadAdminUpdate(); return; }
|
||||
updateBestaetigen = '';
|
||||
try {
|
||||
const r = await fetch(baseUrl() + '/admin/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ version }) });
|
||||
const d = await r.json().catch(() => ({}));
|
||||
if(!r.ok || !d.ok){ alert(d.error || 'Update konnte nicht gestartet werden.'); return loadAdminUpdate(); }
|
||||
updateZiel = version;
|
||||
updateSuche = null;
|
||||
} catch(e){ alert('Server nicht erreichbar.'); }
|
||||
loadAdminUpdate();
|
||||
}
|
||||
|
||||
async function loadAdminTeams(){
|
||||
const el = document.getElementById('adminTeamsInhalt');
|
||||
if(!el) return;
|
||||
|
||||
Reference in new issue
Block a user