Separate online backup creation, import and management sections
CI / verify (push) Waiting to run

This commit is contained in:
Sucukdeluxe
2026-09-22 05:58:33 +02:00
parent ed76e7ea43
commit c86605d67b
5 changed files with 50 additions and 19 deletions
+2
View File
@@ -6,6 +6,7 @@ Multi-Hoster-Upload ist eine Electron-Desktopanwendung für Windows, die große
## Aktueller Zustand ## Aktueller Zustand
- Online-Backup-Seite nach `2.1.49`: drei klar abgegrenzte Bereiche in Reihenfolge „Schlüssel erstellen“, „Schlüssel importieren“, „Auf diesem Gerät erstellt“. Vorhandene IDs, Import-/Exportlogik, Statusmeldungen und Suchziele bleiben erhalten. Neue Überschriften auf Deutsch/Englisch; konsistente Karten ohne Akzentstreifen. Electron-Test nutzt jetzt das echte Seitenmarkup und prüft Modulzuordnung, Ausrichtung sowie Überlauf bei 1000/600/360 px. Lokale Entwicklerversion automatisch neu gestartet; breiter Screenshot visuell geprüft. Noch nicht veröffentlicht.
- Lokale Entwicklung auf Nutzerwunsch dauerhaft mit automatischem Neustart: `.artifacts/dev-watch.cjs` überwacht Main-/Preload-Dateien, `lib`, `renderer` und den lokalen Bootstrap `.artifacts/menu-test.cjs`. Start über `node .artifacts/dev-watch.cjs`; separater Fenstertitel „Multi-Hoster Entwicklung (automatischer Neustart)“. Persistentes Testprofil unter `%LOCALAPPDATA%/Multi-Hoster-Upload-Development`, ohne automatische Accountchecks/Ordnerüberwachung/Remote-Steuerung initialisiert. Installierte Anwendung bleibt getrennt. Neustart durch CSS-Dateiänderung anhand gewechselter Electron-PID und erneut sichtbarem Fenster verifiziert. Bei weiteren Code-/UI-Änderungen diesen Watcher verwenden und den Neustart prüfen. - Lokale Entwicklung auf Nutzerwunsch dauerhaft mit automatischem Neustart: `.artifacts/dev-watch.cjs` überwacht Main-/Preload-Dateien, `lib`, `renderer` und den lokalen Bootstrap `.artifacts/menu-test.cjs`. Start über `node .artifacts/dev-watch.cjs`; separater Fenstertitel „Multi-Hoster Entwicklung (automatischer Neustart)“. Persistentes Testprofil unter `%LOCALAPPDATA%/Multi-Hoster-Upload-Development`, ohne automatische Accountchecks/Ordnerüberwachung/Remote-Steuerung initialisiert. Installierte Anwendung bleibt getrennt. Neustart durch CSS-Dateiänderung anhand gewechselter Electron-PID und erneut sichtbarem Fenster verifiziert. Bei weiteren Code-/UI-Änderungen diesen Watcher verwenden und den Neustart prüfen.
- Online-Backup-Gültigkeitsdauer nach `2.1.49`, noch nicht veröffentlicht: Auswahl und Erstellen-Button verwenden 40 px Höhe und 14 px Schrift; Label und Auswahl sind zur Importzeile ausgerichtet. Footer bricht bei Platzmangel um, auf schmalen Fenstern untereinander. Leere Statuszeile belegt keinen Platz; echte Meldungen bleiben sichtbar. Nativer Select und 14 px Pfeil-Innenabstand bleiben erhalten. Electron-Layoutregression bei 1000, 600 und 360 px; Screenshot der breiten Darstellung visuell geprüft. - Online-Backup-Gültigkeitsdauer nach `2.1.49`, noch nicht veröffentlicht: Auswahl und Erstellen-Button verwenden 40 px Höhe und 14 px Schrift; Label und Auswahl sind zur Importzeile ausgerichtet. Footer bricht bei Platzmangel um, auf schmalen Fenstern untereinander. Leere Statuszeile belegt keinen Platz; echte Meldungen bleiben sichtbar. Nativer Select und 14 px Pfeil-Innenabstand bleiben erhalten. Electron-Layoutregression bei 1000, 600 und 360 px; Screenshot der breiten Darstellung visuell geprüft.
- Nach `2.1.49`, noch nicht veröffentlicht: Backup-Untermenü erhält eine durchgängige Hover-Fläche über den sichtbaren Abstand und 250 ms abbrechbare Schließverzögerung. Hover plus Klick bleibt offen, erneutes Betreten startet keine neue Öffnungsanimation; Tastaturfokus hält das Untermenü offen. Pfeil und Pfeiltasten folgen der linken Öffnungsrichtung, Escape schließt nur die aktuelle Untermenüebene. Fremde `animationend`-Ereignisse schließen kein übergeordnetes Panel mehr. Electron-Regression prüft DOM-Treffertests im Abstand, verzögerte Ein-/Austritte, Klick, Fokus und Escape bei 850 und 600 Pixel Fensterbreite. Kein Release beauftragt. - Nach `2.1.49`, noch nicht veröffentlicht: Backup-Untermenü erhält eine durchgängige Hover-Fläche über den sichtbaren Abstand und 250 ms abbrechbare Schließverzögerung. Hover plus Klick bleibt offen, erneutes Betreten startet keine neue Öffnungsanimation; Tastaturfokus hält das Untermenü offen. Pfeil und Pfeiltasten folgen der linken Öffnungsrichtung, Escape schließt nur die aktuelle Untermenüebene. Fremde `animationend`-Ereignisse schließen kein übergeordnetes Panel mehr. Electron-Regression prüft DOM-Treffertests im Abstand, verzögerte Ein-/Austritte, Klick, Fokus und Escape bei 850 und 600 Pixel Fensterbreite. Kein Release beauftragt.
@@ -91,6 +92,7 @@ npm audit --omit=dev
## Zuletzt verifiziert ## Zuletzt verifiziert
- Getrennte Online-Backup-Module: 839 Haupttests, 18 Servertests und Lint erfolgreich. Echtes Modulmarkup in Electron bei drei Fensterbreiten geprüft; Entwicklerversion nach Renderer-Änderung mit neuem Prozess sichtbar bestätigt. Noch kein Release.
- Gültigkeitsdauer-Layout: neue Electron-Regression erfolgreich, Screenshot visuell geprüft, Lint und 18 Servertests erfolgreich. Hauptlauf 838/839 erfolgreich: Menü-Fokustest reagierte einmal zu früh auf asynchronen Fokus. Test wartet jetzt begrenzt auf den tatsächlichen Fokus statt pauschal 240 ms; kompletter Wiederholungslauf der betroffenen Datei 25/25 erfolgreich. Kein Release erstellt. - Gültigkeitsdauer-Layout: neue Electron-Regression erfolgreich, Screenshot visuell geprüft, Lint und 18 Servertests erfolgreich. Hauptlauf 838/839 erfolgreich: Menü-Fokustest reagierte einmal zu früh auf asynchronen Fokus. Test wartet jetzt begrenzt auf den tatsächlichen Fokus statt pauschal 240 ms; kompletter Wiederholungslauf der betroffenen Datei 25/25 erfolgreich. Kein Release erstellt.
- Backup-Untermenü nach `2.1.49`: 838 Haupttests, 18 Servertests und Lint erfolgreich. Gezielter Electron-Test nach abschließender Fokuskorrektur erneut erfolgreich; ein Mausklick auf den Trigger hält das Menü beim Verlassen nicht unbeabsichtigt offen. Änderungen noch nicht veröffentlicht. - Backup-Untermenü nach `2.1.49`: 838 Haupttests, 18 Servertests und Lint erfolgreich. Gezielter Electron-Test nach abschließender Fokuskorrektur erneut erfolgreich; ein Mausklick auf den Trigger hält das Menü beim Verlassen nicht unbeabsichtigt offen. Änderungen noch nicht veröffentlicht.
- Release `2.1.49`: Titel, Tags, englischer GitHub- und deutscher Forgejo-Changelog, jeweils vier Assets und Update-Metadaten geprüft. Alle acht veröffentlichten Dateien erneut heruntergeladen und Größe sowie SHA-512 mit lokalen Dateien verglichen. Echter Updater erkennt `2.1.49` ausgehend von `2.1.48` und akzeptiert englische Hinweise sowie Forgejo-Manifest. Öffentlicher Quellcheck einschließlich Screenshots erfolgreich (164 Dateien). Keine produktive Serveränderung erforderlich. - Release `2.1.49`: Titel, Tags, englischer GitHub- und deutscher Forgejo-Changelog, jeweils vier Assets und Update-Metadaten geprüft. Alle acht veröffentlichten Dateien erneut heruntergeladen und Größe sowie SHA-512 mit lokalen Dateien verglichen. Echter Updater erkennt `2.1.49` ausgehend von `2.1.48` und akzeptiert englische Hinweise sowie Forgejo-Manifest. Öffentlicher Quellcheck einschließlich Screenshots erfolgreich (164 Dateien). Keine produktive Serveränderung erforderlich.
+21 -15
View File
@@ -6609,21 +6609,8 @@ function renderSettings() {
<h3 id="onlineBackupHeading">Verschlüsseltes Online-Backup</h3> <h3 id="onlineBackupHeading">Verschlüsseltes Online-Backup</h3>
<p>Die Verschlüsselung findet ausschließlich auf diesem Gerät statt. Der Server speichert nur verschlüsselte Daten.</p> <p>Die Verschlüsselung findet ausschließlich auf diesem Gerät statt. Der Server speichert nur verschlüsselte Daten.</p>
</div> </div>
<div class="online-backup-key-row" data-settings-search-entry data-settings-search-section="Online-Backup"> <section class="online-backup-module" aria-labelledby="onlineBackupCreateHeading">
<label for="onlineBackupKeyInput">Schlüssel importieren</label> <h4 id="onlineBackupCreateHeading">Schlüssel erstellen</h4>
<input type="password" class="key-input" id="onlineBackupKeyInput" maxlength="75" pattern="MHU2-[A-Za-z0-9_-]{70}" spellcheck="false" autocomplete="off" placeholder="MHU2-…">
<button class="btn btn-secondary" id="restoreOnlineBackupBtn" disabled>Importieren</button>
</div>
<p class="online-backup-warning">Behandle den Schlüssel wie ein Passwort. Wer ihn besitzt, kann die verschlüsselten Einstellungen entschlüsseln.</p>
<section class="online-backup-managed" aria-labelledby="managedOnlineBackupHeading" data-settings-search-entry data-settings-search-section="Online-Backup" data-settings-search-label="Online-Backups verwalten" data-settings-search-control="managedOnlineBackupHeading">
<h4 id="managedOnlineBackupHeading" tabindex="-1">Auf diesem Gerät erstellt</h4>
<div class="online-backup-managed-list" id="managedOnlineBackupList"></div>
<div class="online-backup-refresh-status" id="managedOnlineBackupRefreshStatus" role="status" hidden>
<span id="managedOnlineBackupRefreshMessage"></span>
<button class="btn btn-secondary" id="reloadManagedOnlineBackupsBtn" type="button">Erneut laden</button>
</div>
</section>
<div class="online-backup-status" id="onlineBackupStatus" role="status" aria-live="polite"></div>
<footer class="online-backup-footer" data-settings-search-entry data-settings-search-section="Online-Backup" data-settings-search-label="Neuen Schlüssel erzeugen"> <footer class="online-backup-footer" data-settings-search-entry data-settings-search-section="Online-Backup" data-settings-search-label="Neuen Schlüssel erzeugen">
<div class="online-backup-retention-field"> <div class="online-backup-retention-field">
<label for="onlineBackupRetentionSelect">Gültigkeitsdauer</label> <label for="onlineBackupRetentionSelect">Gültigkeitsdauer</label>
@@ -6640,6 +6627,25 @@ function renderSettings() {
<button class="btn btn-primary" id="createOnlineBackupBtn">Neuen Schlüssel erzeugen</button> <button class="btn btn-primary" id="createOnlineBackupBtn">Neuen Schlüssel erzeugen</button>
</footer> </footer>
</section> </section>
<section class="online-backup-module" aria-labelledby="onlineBackupImportHeading">
<h4 id="onlineBackupImportHeading">Schlüssel importieren</h4>
<div class="online-backup-key-row" data-settings-search-entry data-settings-search-section="Online-Backup" data-settings-search-label="Schlüssel importieren">
<label for="onlineBackupKeyInput">Online-Schlüssel</label>
<input type="password" class="key-input" id="onlineBackupKeyInput" maxlength="75" pattern="MHU2-[A-Za-z0-9_-]{70}" spellcheck="false" autocomplete="off" placeholder="MHU2-…">
<button class="btn btn-secondary" id="restoreOnlineBackupBtn" disabled>Importieren</button>
</div>
</section>
<p class="online-backup-warning">Behandle den Schlüssel wie ein Passwort. Wer ihn besitzt, kann die verschlüsselten Einstellungen entschlüsseln.</p>
<div class="online-backup-status" id="onlineBackupStatus" role="status" aria-live="polite"></div>
<section class="online-backup-module online-backup-managed" aria-labelledby="managedOnlineBackupHeading" data-settings-search-entry data-settings-search-section="Online-Backup" data-settings-search-label="Online-Backups verwalten" data-settings-search-control="managedOnlineBackupHeading">
<h4 id="managedOnlineBackupHeading" tabindex="-1">Auf diesem Gerät erstellt</h4>
<div class="online-backup-managed-list" id="managedOnlineBackupList"></div>
<div class="online-backup-refresh-status" id="managedOnlineBackupRefreshStatus" role="status" hidden>
<span id="managedOnlineBackupRefreshMessage"></span>
<button class="btn btn-secondary" id="reloadManagedOnlineBackupsBtn" type="button">Erneut laden</button>
</div>
</section>
</section>
<div class="settings-section-label">Lokales Datei-Backup</div> <div class="settings-section-label">Lokales Datei-Backup</div>
<div class="settings-row local-backup-password-row"> <div class="settings-row local-backup-password-row">
<div class="settings-copy"> <div class="settings-copy">
+2
View File
@@ -380,6 +380,8 @@
['Verschlüsseltes Online-Backup', 'Encrypted online backup'], ['Verschlüsseltes Online-Backup', 'Encrypted online backup'],
['Die Verschlüsselung findet ausschließlich auf diesem Gerät statt. Der Server speichert nur verschlüsselte Daten.', 'Encryption takes place only on this device. The server stores encrypted data only.'], ['Die Verschlüsselung findet ausschließlich auf diesem Gerät statt. Der Server speichert nur verschlüsselte Daten.', 'Encryption takes place only on this device. The server stores encrypted data only.'],
['Neuen Schlüssel erzeugen', 'Generate new key'], ['Neuen Schlüssel erzeugen', 'Generate new key'],
['Schlüssel erstellen', 'Create key'],
['Online-Schlüssel', 'Online key'],
['Gültigkeitsdauer', 'Validity period'], ['Gültigkeitsdauer', 'Validity period'],
['24 Stunden', '24 hours'], ['24 Stunden', '24 hours'],
['3 Tage', '3 days'], ['3 Tage', '3 days'],
+16
View File
@@ -2250,6 +2250,22 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
min-width: 0; min-width: 0;
} }
.online-backup-module {
display: grid;
gap: 16px;
min-width: 0;
padding: 16px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg-card);
}
.online-backup-module h4 {
margin: 0;
font-size: 14px;
font-weight: 600;
}
.online-backup-retention-field { .online-backup-retention-field {
display: grid; display: grid;
grid-template-columns: 150px minmax(210px, 300px); grid-template-columns: 150px minmax(210px, 300px);
+9 -4
View File
@@ -180,12 +180,17 @@ app.whenReady().then(async () => {
const root=process.env.MHU_RETENTION_ROOT; const root=process.env.MHU_RETENTION_ROOT;
const css=fs.readFileSync(path.join(root,'renderer/styles.css'),'utf8'); const css=fs.readFileSync(path.join(root,'renderer/styles.css'),'utf8');
const source=fs.readFileSync(path.join(root,'renderer/app.js'),'utf8'); const source=fs.readFileSync(path.join(root,'renderer/app.js'),'utf8');
const footer=source.match(/<footer class="online-backup-footer"[\\s\\S]*?<\\/footer>/)[0]; const panel=source.slice(source.indexOf('<section class="online-backup-panel"'),source.indexOf('<div class="settings-section-label">Lokales Datei-Backup</div>'));
const win=new BrowserWindow({show:false,width:1000,height:500,webPreferences:{backgroundThrottling:false}}); const win=new BrowserWindow({show:false,width:1000,height:800,webPreferences:{backgroundThrottling:false}});
const wc=win.webContents; const wc=win.webContents;
for(const width of [1000,600,360]) { for(const width of [1000,600,360]) {
win.setContentSize(width,500); win.setContentSize(width,800);
await wc.loadURL('data:text/html;charset=utf-8,'+encodeURIComponent('<style>'+css+'</style><main id="settings-view" style="display:block;padding:16px;width:100%"><section class="online-backup-panel"><h3>Verschlüsseltes Online-Backup</h3><div class="online-backup-key-row"><label>Schlüssel importieren</label><input class="key-input"><button class="btn btn-secondary">Importieren</button></div><div class="online-backup-status"></div>'+footer+'</section></main>')); await wc.loadURL('data:text/html;charset=utf-8,'+encodeURIComponent('<style>'+css+'</style><main id="settings-view" style="display:block;padding:16px;width:100%">'+panel+'</main>'));
assert.deepEqual(await wc.executeJavaScript('Array.from(document.querySelectorAll(".online-backup-module")).map(module=>({heading:module.getAttribute("aria-labelledby"),create:!!module.querySelector("#createOnlineBackupBtn"),import:!!module.querySelector("#restoreOnlineBackupBtn"),manage:!!module.querySelector("#managedOnlineBackupList")}))'),[
{heading:'onlineBackupCreateHeading',create:true,import:false,manage:false},
{heading:'onlineBackupImportHeading',create:false,import:true,manage:false},
{heading:'managedOnlineBackupHeading',create:false,import:false,manage:true}
]);
const layout=await wc.executeJavaScript('(() => { const s=document.querySelector("select"), b=document.querySelector("footer button"), l=document.querySelector(".online-backup-retention-field label"), i=document.querySelector(".key-input"); const rect=e=>{const r=e.getBoundingClientRect();return {left:r.left,right:r.right,top:r.top,bottom:r.bottom,height:r.height}}; return {select:rect(s),button:rect(b),input:rect(i),labelFont:getComputedStyle(l).fontSize,selectFont:getComputedStyle(s).fontSize,buttonFont:getComputedStyle(b).fontSize,arrowRight:getComputedStyle(s.parentElement,"::after").right,empty:getComputedStyle(document.querySelector(".online-backup-status")).display,scroll:document.documentElement.scrollWidth,viewport:innerWidth}; })()'); const layout=await wc.executeJavaScript('(() => { const s=document.querySelector("select"), b=document.querySelector("footer button"), l=document.querySelector(".online-backup-retention-field label"), i=document.querySelector(".key-input"); const rect=e=>{const r=e.getBoundingClientRect();return {left:r.left,right:r.right,top:r.top,bottom:r.bottom,height:r.height}}; return {select:rect(s),button:rect(b),input:rect(i),labelFont:getComputedStyle(l).fontSize,selectFont:getComputedStyle(s).fontSize,buttonFont:getComputedStyle(b).fontSize,arrowRight:getComputedStyle(s.parentElement,"::after").right,empty:getComputedStyle(document.querySelector(".online-backup-status")).display,scroll:document.documentElement.scrollWidth,viewport:innerWidth}; })()');
assert.equal(layout.select.height,40); assert.equal(layout.select.height,40);
assert.equal(layout.button.height,40); assert.equal(layout.button.height,40);