diff --git a/dist/backupper-0.1.9-x86_64-1.txz b/dist/backupper-0.1.10-x86_64-1.txz similarity index 55% rename from dist/backupper-0.1.9-x86_64-1.txz rename to dist/backupper-0.1.10-x86_64-1.txz index 8669276..d76c630 100644 Binary files a/dist/backupper-0.1.9-x86_64-1.txz and b/dist/backupper-0.1.10-x86_64-1.txz differ diff --git a/dist/backupper-0.1.10-x86_64-1.txz.sha256 b/dist/backupper-0.1.10-x86_64-1.txz.sha256 new file mode 100644 index 0000000..9e7057e --- /dev/null +++ b/dist/backupper-0.1.10-x86_64-1.txz.sha256 @@ -0,0 +1 @@ +85a85800cab7ef561448c8f96d586bd4fd98bd57813241793128a14d2a0d8044 backupper-0.1.10-x86_64-1.txz diff --git a/dist/backupper-0.1.9-x86_64-1.txz.sha256 b/dist/backupper-0.1.9-x86_64-1.txz.sha256 deleted file mode 100644 index c99a49c..0000000 --- a/dist/backupper-0.1.9-x86_64-1.txz.sha256 +++ /dev/null @@ -1 +0,0 @@ -d4b45947cfbb8a5273eb90ea10cdd307d751d0529411217ecc37ba0dfe5ea211 backupper-0.1.9-x86_64-1.txz diff --git a/dist/backupper.plg b/dist/backupper.plg index baf1d30..fb95f48 100644 --- a/dist/backupper.plg +++ b/dist/backupper.plg @@ -2,13 +2,16 @@ - + - + ]> +### 0.1.10 +- Verify repository edits against the daemon and handle form submission without losing changes. + ### 0.1.9 - Fix wrapped repository action layout and block initialization of volatile /tmp or /run destinations. diff --git a/plugin/backupper.plg b/plugin/backupper.plg index c1cff4a..3fa4646 100644 --- a/plugin/backupper.plg +++ b/plugin/backupper.plg @@ -2,13 +2,16 @@ - + ]> +### 0.1.10 +- Verify repository edits against the daemon and handle form submission without losing changes. + ### 0.1.9 - Fix wrapped repository action layout and block initialization of volatile /tmp or /run destinations. diff --git a/webgui/Backupper.page b/webgui/Backupper.page index 533b379..3056b38 100644 --- a/webgui/Backupper.page +++ b/webgui/Backupper.page @@ -9,10 +9,10 @@ Tag="backup restic snapshots restore" - +
-

Backupper 0.1.9

Encrypted Restic backups for Unraid

+

Backupper 0.1.10

Encrypted Restic backups for Unraid

Connecting...
- + diff --git a/webgui/assets/backupper.js b/webgui/assets/backupper.js index 2d59715..56df8a7 100644 --- a/webgui/assets/backupper.js +++ b/webgui/assets/backupper.js @@ -102,7 +102,7 @@ return dynamic[name] || `${label} ausführen.`; }; const status = (value) => `${esc(value)}`; - const button = (label, action, kind = '') => ``; + const button = (label, action, kind = '') => ``; const disabledButton = (label, help) => ``; const actionGroup = (buttons) => `
${buttons}
`; @@ -280,7 +280,11 @@

ntfy notification

${button('Save notification','submit-notify','primary')}
`; } - async function saveConfig() { await api('/v1/config',{method:'PUT',body:JSON.stringify(state.config)}); notify('Configuration saved'); } + async function saveConfig() { + state.config = await api('/v1/config',{method:'PUT',body:JSON.stringify(state.config)}); + notify('Configuration saved'); + return state.config; + } const lines = (value) => value.split('\n').map(v=>v.trim()).filter(Boolean); async function handle(action, element) { @@ -317,11 +321,16 @@ state.config.jobs=state.config.jobs.filter(j=>j.id!==job.id).concat(job); await saveConfig(); render(); } if (kind === 'submit-repo') { - const f=new FormData(document.getElementById('bu-repo-form')); const repo={schemaVersion:1,id:f.get('id')||id('repo'),name:f.get('name'),type:f.get('type'),location:f.get('location'),passwordRef:f.get('passwordRef'),credentialRef:f.get('credentialRef')||'',mount:f.get('managed')==='on'?{managed:true,remote:f.get('remote'),mountPoint:f.get('mountPoint')}:null,options:f.get('type')==='sftp'?{knownHostsPath:f.get('knownHostsPath')}:{}}; + const f=new FormData(document.getElementById('bu-repo-form')); const repo={schemaVersion:1,id:f.get('id')||id('repo'),name:f.get('name').trim(),type:f.get('type'),location:f.get('location').trim(),passwordRef:f.get('passwordRef').trim(),credentialRef:f.get('credentialRef').trim(),mount:f.get('managed')==='on'?{managed:true,remote:f.get('remote').trim(),mountPoint:f.get('mountPoint').trim()}:null,options:f.get('type')==='sftp'?{knownHostsPath:f.get('knownHostsPath').trim()}:{}}; if (volatileLocation(repo) && !confirm('WARNING: This destination is under /tmp or /run and will be lost after an Unraid reboot. Save it anyway?')) return; if(f.get('password')) await api(`/v1/secrets/${encodeURIComponent(repo.passwordRef)}`,{method:'PUT',body:JSON.stringify({type:'restic-password',value:f.get('password')})}); if(f.get('credential')) { if(!repo.credentialRef) repo.credentialRef=id('mount-credential'); await api(`/v1/secrets/${encodeURIComponent(repo.credentialRef)}`,{method:'PUT',body:JSON.stringify({type:'mount-credential',value:f.get('credential')})}); } - state.config.repositories=state.config.repositories.filter(r=>r.id!==repo.id).concat(repo); await saveConfig(); render(); + state.config.repositories=state.config.repositories.filter(r=>r.id!==repo.id).concat(repo); + await saveConfig(); + state.config = await api('/v1/config'); + const saved = state.config.repositories.find(r=>r.id===repo.id); + if (!saved || saved.location !== repo.location) throw new Error(`Repository location was not saved. Expected: ${repo.location}`); + render(); notify(`Repository saved: ${saved.location}`); } if (kind === 'submit-restore') { const f=new FormData(document.getElementById('bu-restore-form')); await api('/v1/restores',{method:'POST',body:JSON.stringify({schemaVersion:1,id:'',repositoryId:f.get('repositoryId'),snapshotId:f.get('snapshotId'),includes:lines(f.get('includes')),target:f.get('target'),inPlace:f.get('inPlace')==='on',confirmed:f.get('confirmed')==='on'})}); notify('Restore queued'); state.view='activity'; render(); } if (kind === 'submit-settings') { const f=new FormData(document.getElementById('bu-settings-form')); state.config.settings={resticPath:f.get('resticPath'),restoreRoot:f.get('restoreRoot'),persistentLogDir:f.get('persistentLogDir'),catchUpWindowHours:Number(f.get('catchUp')),checkCron:f.get('checkCron'),pruneCron:f.get('pruneCron')}; await saveConfig(); } @@ -330,6 +339,13 @@ document.querySelector('.bu-tabs').addEventListener('click', e => { const button=e.target.closest('[data-view]'); if(!button)return; state.view=button.dataset.view; document.querySelectorAll('.bu-tabs button').forEach(x=>x.classList.toggle('active',x===button)); render(); }); content.addEventListener('click', e => { const target=e.target.closest('[data-action]'); if(!target)return; e.preventDefault(); handle(target.dataset.action,target); }); + content.addEventListener('submit', e => { + const form = e.target.closest('form'); + if (!form) return; + e.preventDefault(); + const action = form.id === 'bu-repo-form' ? 'submit-repo' : form.id === 'bu-job-form' ? 'submit-job' : form.id === 'bu-restore-form' ? 'submit-restore' : form.id === 'bu-settings-form' ? 'submit-settings' : form.id === 'bu-notify-form' ? 'submit-notify' : ''; + if (action) handle(action); + }); root.addEventListener('mouseover', e => { const target = e.target.closest('[data-tooltip]'); if (!target || target.contains(e.relatedTarget)) return;