feat: alle FritzBoxen sichern (ueberspringt statt abzubrechen) + Tastenbestaetigung anzeigen/schalten
Satellit: - fritzbox.backup_all: alle Boxen mit hinterlegtem Login nacheinander; nicht erreichbar (Port 49000), Fehler oder Taste noetig → notieren und weiter. Ergebnis: x von y gesichert + Grund je Box. - fritzbox.second_factor: ist die Zusatzbestaetigung an, gibt es sie, und laesst sie sich per TR-064 schalten (nur wenn die Firmware eine Set- Aktion mit Enable-Argument im Auth-Dienst anbietet). - fritzbox.second_factor_set: schaltet sie (confirmed, Taste per button_wait); sonst not_switchable + Weg ueber die Oberflaeche. Diagnostic (💾-Fenster): Zeile "Tastenbestaetigung: an/aus" mit Aus-/ Einschalten (Rueckfrage, Countdown; nicht schaltbar → Hinweisfenster), Knopf "Alle FritzBoxen sichern" mit Ergebnis je Box. Ausschalten/ Deaktivieren gilt jetzt als gefaehrliche Rueckfrage (roter Knopf). Brain: Beschreibung fuer backup_all (je Satellit, weiter bei Fehlern) und second_factor; Timeout 15 min fuer backup_all. Getestet gegen nachgebaute Box (eine Box unerreichbar → uebersprungen, Taste noetig ohne/mit Warten, Status, Schalten mit und ohne Setter) und das Fenster in headless Chromium. Ob echte FRITZ!OS-Versionen das Schalten per TR-064 anbieten, ist ungeprueft. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+48
-5
@@ -4487,7 +4487,7 @@
|
||||
// uiConfirm → true/false, uiPrompt → Text oder null, uiAlert → undefined.
|
||||
// Erster Absatz der Meldung = Ueberschrift, der Rest = Erklaerung.
|
||||
// Esc / Klick daneben = Abbrechen, Enter = OK.
|
||||
const _UI_DANGER = /l[öo]e?schen|entfernen|zur[üu]cksetzen|NOT-AUS|AGGRESSIV|WIRKLICH|endg[üu]ltig|archivieren|beenden|verloren|[üu]berschrieben|leeren/i;
|
||||
const _UI_DANGER = /l[öo]e?schen|entfernen|zur[üu]cksetzen|NOT-AUS|AGGRESSIV|WIRKLICH|endg[üu]ltig|archivieren|beenden|verloren|[üu]berschrieben|leeren|ausschalten|deaktivier|zur[üu]cksetzen/i;
|
||||
function _uiDialog({ message, input, defaultValue, buttons }) {
|
||||
return new Promise(resolve => {
|
||||
const text = String(message ?? '');
|
||||
@@ -6351,11 +6351,12 @@
|
||||
const _fbFmtDate = iso => { try { return new Date(iso).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' }); } catch (_) { return iso; } };
|
||||
const _fbPwSrc = { backupPass: 'Sicherungs-Kennwort', login: 'FritzBox-Kennwort', angegeben: 'eigenes Kennwort' };
|
||||
async function openFbBackups(sat, ip, name) {
|
||||
fbB = { sat, ip, name, list: [], busy: '' };
|
||||
fbB = { sat, ip, name, list: [], busy: '', sf: null };
|
||||
_fbRender('Lade Sicherungen …');
|
||||
const r = await fbCmd('fritzbox.backups', {});
|
||||
const [r, sf] = await Promise.all([fbCmd('fritzbox.backups', {}), fbCmd('fritzbox.second_factor', {})]);
|
||||
if (!fbB) return;
|
||||
fbB.list = (r.ok && r.result && r.result.sicherungen) || [];
|
||||
fbB.sf = sf.ok ? sf.result : { fehler: sf.error };
|
||||
_fbRender(r.ok ? '' : '⚠ ' + (r.error || 'Fehler'));
|
||||
}
|
||||
function _fbRender(status) {
|
||||
@@ -6373,7 +6374,10 @@
|
||||
<h3 style="margin:0;">💾 Sicherungen: ${escapeHtml(fbB.name)} <span style="color:#8888AA;font-size:12px;">(${escapeHtml(fbB.ip)})</span></h3>
|
||||
<button onclick="closeFbBackups()" style="background:none;border:none;color:#8888AA;font-size:22px;cursor:pointer;">×</button></div>
|
||||
<p style="color:#8888AA;font-size:11px;margin:0 0 10px;">Liegen auf dem Satelliten, die letzten 10 je Box. Verschlüsselt mit dem hinterlegten Sicherungs-Kennwort (🔑).</p>
|
||||
<button class="btn" onclick="fbCreateBackup()" ${fbB.busy ? 'disabled' : ''} style="margin-bottom:10px;">Jetzt sichern</button>
|
||||
${_fbSfLine()}
|
||||
<div style="display:flex;gap:8px;margin-bottom:10px;">
|
||||
<button class="btn" onclick="fbCreateBackup()" ${fbB.busy ? 'disabled' : ''}>Jetzt sichern</button>
|
||||
<button class="btn secondary" onclick="fbBackupAll()" ${fbB.busy ? 'disabled' : ''} title="Alle FritzBoxen mit hinterlegtem Login an diesem Satelliten — nicht erreichbare werden übersprungen">Alle FritzBoxen sichern</button></div>
|
||||
<div id="fb-run" style="${fbB.busy ? '' : 'display:none;'}margin-bottom:10px;padding:10px;border:1px solid #444466;border-radius:6px;"></div>
|
||||
${fbB.list.length ? `<table style="width:100%;border-collapse:collapse;font-size:12px;"><tbody>${rows}</tbody></table>`
|
||||
: '<div style="color:#6a6a88;">Noch keine Sicherung.</div>'}
|
||||
@@ -6393,7 +6397,8 @@
|
||||
};
|
||||
tick();
|
||||
const iv = setInterval(tick, 1000);
|
||||
const r = await fbCmd(cmd, { ...params, button_wait: true, no_file: true }, 170000);
|
||||
// "alle sichern": pro Box bis 2 min Taste — insgesamt grosszuegig warten
|
||||
const r = await fbCmd(cmd, { ...params, button_wait: true, no_file: true }, cmd === 'fritzbox.backup_all' ? 15 * 60000 : 170000);
|
||||
clearInterval(iv);
|
||||
if (!fbB) return r;
|
||||
fbB.busy = '';
|
||||
@@ -6410,6 +6415,44 @@
|
||||
el.innerHTML = `<b style="color:#FF9F0A;">⚠ ${r.timeout ? 'Taste nicht gedrückt' : 'Nicht geklappt'}</b><div style="margin-top:4px;">${escapeHtml(r.error || 'unbekannter Fehler')}</div>`;
|
||||
}
|
||||
}
|
||||
// Tastenbestaetigung der Box: Status + Umschalten (wenn die Firmware es kann)
|
||||
function _fbSfLine() {
|
||||
const sf = fbB.sf;
|
||||
if (!sf) return '<div style="color:#6a6a88;font-size:12px;margin-bottom:8px;">Tastenbestätigung: wird geprüft …</div>';
|
||||
if (sf.fehler) return `<div style="color:#6a6a88;font-size:12px;margin-bottom:8px;">Tastenbestätigung: unbekannt (${escapeHtml(sf.fehler)})</div>`;
|
||||
if (!sf.vorhanden) return '<div style="color:#6a6a88;font-size:12px;margin-bottom:8px;">Tastenbestätigung: gibt es bei dieser Box nicht — alles läuft ohne Taste.</div>';
|
||||
const st = sf.an === true ? '<b style="color:#FFD60A;">an</b>' : sf.an === false ? '<b style="color:#34C759;">aus</b>' : '<b>unbekannt</b>';
|
||||
const btn = sf.an === null ? '' : `<button class="btn secondary" onclick="fbToggleSf(${sf.an ? 'false' : 'true'})" ${fbB.busy ? 'disabled' : ''} style="padding:2px 8px;font-size:11px;margin-left:8px;">${sf.an ? 'Ausschalten' : 'Einschalten'}</button>`;
|
||||
return `<div style="font-size:12px;margin-bottom:8px;">Tastenbestätigung: ${st}${btn}</div>`;
|
||||
}
|
||||
async function fbToggleSf(on) {
|
||||
if (fbB.sf && !fbB.sf.schaltbar) { uiAlert('Lässt sich nicht umschalten\n\n' + (fbB.sf.hinweis || '')); return; }
|
||||
if (!await uiConfirm(`Tastenbestätigung an ${fbB.name} ${on ? 'einschalten' : 'ausschalten'}?\n\n`
|
||||
+ (on ? 'Danach verlangt die Box für Sicherungen und heikle Einstellungen wieder einen Tastendruck.'
|
||||
: 'Danach laufen Sicherungen ohne Tastendruck — auch „alle FritzBoxen sichern“. Wer das Login kennt, kann dann aber auch heikle Einstellungen ohne Bestätigung am Gerät ändern.')
|
||||
+ '\n\nDas Umschalten selbst muss die Box meist per Taste bestätigen (2 Minuten).')) return;
|
||||
const r = await _fbRun(on ? 'Einschalten läuft' : 'Ausschalten läuft', 'fritzbox.second_factor_set', { enable: on, confirmed: true });
|
||||
if (!fbB) return;
|
||||
const sf = await fbCmd('fritzbox.second_factor', {});
|
||||
if (fbB && sf.ok) fbB.sf = sf.result;
|
||||
if (!r.ok && r.not_switchable) { _fbRender(''); uiAlert('Lässt sich nicht umschalten\n\n' + r.error); return; }
|
||||
_fbShowResult(r, on ? 'Tastenbestätigung ist an' : 'Tastenbestätigung ist aus');
|
||||
}
|
||||
async function fbBackupAll() {
|
||||
if (!await uiConfirm('Alle FritzBoxen sichern?\n\nGilt für alle Boxen mit hinterlegtem Login an diesem Satelliten. Nicht erreichbare werden übersprungen. Verlangt eine Box eine Taste, wartet der Satellit bis 2 Minuten — sonst geht es mit der nächsten weiter.')) return;
|
||||
const r = await _fbRun('Sichere alle FritzBoxen', 'fritzbox.backup_all', {});
|
||||
if (!fbB) return;
|
||||
const l = await fbCmd('fritzbox.backups', {});
|
||||
if (fbB && l.ok) fbB.list = l.result.sicherungen || [];
|
||||
_fbRender('');
|
||||
const el = document.getElementById('fb-run');
|
||||
el.style.display = '';
|
||||
if (!r.ok) { el.innerHTML = `<b style="color:#FF9F0A;">⚠ Nicht geklappt</b><div>${escapeHtml(r.error || '')}</div>`; return; }
|
||||
const res = r.result || {};
|
||||
el.innerHTML = `<b>${res.gesichert} von ${res.von} gesichert</b>` + (res.boxen || []).map(b =>
|
||||
`<div style="margin-top:3px;">${b.ok ? '<span style="color:#34C759;">✓</span>' : '<span style="color:#FF9F0A;">✗</span>'} ${escapeHtml(b.box)}`
|
||||
+ (b.ok ? '' : ` — <span style="color:#9a9ab5;">${escapeHtml(b.grund || '')}</span>`) + '</div>').join('');
|
||||
}
|
||||
async function fbCreateBackup() {
|
||||
if (!await uiConfirm(`Sicherung von ${fbB.name} erstellen?\n\nFalls die Box eine Bestätigung verlangt, hast du nach dem Start 2 Minuten, um eine Taste an der FritzBox zu drücken.`)) return;
|
||||
const r = await _fbRun('Sicherung läuft', 'fritzbox.backup', {});
|
||||
|
||||
Reference in New Issue
Block a user