diff --git a/diagnostic/Dockerfile b/diagnostic/Dockerfile index 5f039bc..a6d5948 100644 --- a/diagnostic/Dockerfile +++ b/diagnostic/Dockerfile @@ -1,7 +1,8 @@ FROM node:22-alpine WORKDIR /app # zip fuer Multi-Datei-Downloads (Brain-Export nutzt tar.gz, Datei-Manager zip) -RUN apk add --no-cache zip +# git fuer Auto-Versionierung von /shared/uploads/ (siehe server.js) +RUN apk add --no-cache zip git COPY package.json ./ RUN npm install --production COPY . . diff --git a/diagnostic/index.html b/diagnostic/index.html index fce8737..4f7f1d1 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -4039,11 +4039,83 @@
${fmtSize(f.size)} ยท ${fmtDate(f.mtime)}
+ `; }).join(''); } + // โ”€โ”€ Versions-Modal โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + async function showVersions(fileName) { + // path-relative-to-/shared/uploads ist hier == fileName, weil unser + // file-Manager-Verzeichnis flach ist + const rel = fileName; + const modal = document.getElementById('versions-modal'); + const title = document.getElementById('versions-title'); + const body = document.getElementById('versions-body'); + title.textContent = `Versionen โ€” ${fileName}`; + body.innerHTML = '
Lade...
'; + modal.style.display = 'flex'; + modal.dataset.path = rel; + try { + const r = await fetch('/api/files-versions?path=' + encodeURIComponent(rel)); + const d = await r.json(); + if (!d.ok) throw new Error(d.error || 'Fehler'); + if (!d.versions.length) { + body.innerHTML = '
Noch keine Versions-Historie (Datei kommt erst nach naechstem Auto-Commit in den Index).
'; + return; + } + const fmtDate = (ms) => new Date(ms).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); + body.innerHTML = d.versions.map(v => { + const isCur = v.isCurrent + ? 'AKTIV' + : ''; + const subjShort = (v.subject || '').slice(0, 60); + return `
+
+
${isCur}${v.hash.slice(0,7)} ยท ${escapeHtml(subjShort)}
+
${fmtDate(v.ts)}
+
+ + ${v.isCurrent ? '' : ``} +
`; + }).join(''); + } catch (e) { + body.innerHTML = `
${escapeHtml(e.message)}
`; + } + } + + function closeVersionsModal() { + document.getElementById('versions-modal').style.display = 'none'; + } + + function downloadVersion(rel, hash) { + const url = '/api/files-version-content?path=' + encodeURIComponent(rel) + '&hash=' + encodeURIComponent(hash); + const a = document.createElement('a'); + a.href = url; + a.download = ''; + document.body.appendChild(a); a.click(); + setTimeout(() => a.remove(), 100); + } + + async function restoreVersion(rel, hash) { + if (!confirm(`Diese Version (${hash.slice(0,7)}) als aktive Version setzen?\n\nDie aktuelle Version bleibt rollback-bar in der Historie.`)) return; + try { + const r = await fetch('/api/files-version-restore', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ path: rel, hash }), + }); + const d = await r.json(); + if (!d.ok) throw new Error(d.error || 'Fehler'); + // Modal neu laden mit aktualisierter Liste + showVersions(rel); + loadFiles(); + } catch (e) { + alert('Restore fehlgeschlagen: ' + e.message); + } + } + async function downloadSelected() { const paths = [...filesSelected]; if (!paths.length) return; @@ -5612,5 +5684,16 @@ // History gleich nach Seitenstart laden damit Browser-Reload nichts verliert. loadAriaStreamHistory(); + + + diff --git a/diagnostic/server.js b/diagnostic/server.js index b51d2cd..edb6582 100644 Binary files a/diagnostic/server.js and b/diagnostic/server.js differ