Verify repository configuration edits
This commit is contained in:
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
85a85800cab7ef561448c8f96d586bd4fd98bd57813241793128a14d2a0d8044 backupper-0.1.10-x86_64-1.txz
|
||||
-1
@@ -1 +0,0 @@
|
||||
d4b45947cfbb8a5273eb90ea10cdd307d751d0529411217ecc37ba0dfe5ea211 backupper-0.1.9-x86_64-1.txz
|
||||
Vendored
+5
-2
@@ -2,13 +2,16 @@
|
||||
<!DOCTYPE PLUGIN [
|
||||
<!ENTITY name "backupper">
|
||||
<!ENTITY author "Backupper Project">
|
||||
<!ENTITY version "0.1.9">
|
||||
<!ENTITY version "0.1.10">
|
||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper.plg">
|
||||
<!ENTITY packageURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper-&version;-x86_64-1.txz">
|
||||
<!ENTITY packageSHA256 "d4b45947cfbb8a5273eb90ea10cdd307d751d0529411217ecc37ba0dfe5ea211">
|
||||
<!ENTITY packageSHA256 "85a85800cab7ef561448c8f96d586bd4fd98bd57813241793128a14d2a0d8044">
|
||||
]>
|
||||
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/BackUpper/issues">
|
||||
<CHANGES>
|
||||
### 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.
|
||||
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
<!DOCTYPE PLUGIN [
|
||||
<!ENTITY name "backupper">
|
||||
<!ENTITY author "Backupper Project">
|
||||
<!ENTITY version "0.1.9">
|
||||
<!ENTITY version "0.1.10">
|
||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper.plg">
|
||||
<!ENTITY packageURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper-&version;-x86_64-1.txz">
|
||||
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
|
||||
]>
|
||||
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/BackUpper/issues">
|
||||
<CHANGES>
|
||||
### 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.
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ Tag="backup restic snapshots restore"
|
||||
<?php
|
||||
$pluginRoot = '/plugins/backupper';
|
||||
?>
|
||||
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/backupper.css?v=9">
|
||||
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/backupper.css?v=10">
|
||||
<div id="backupper-app" data-api="<?= $pluginRoot ?>/api.php">
|
||||
<header class="bu-header">
|
||||
<div><h1>Backupper <span class="bu-version">0.1.9</span></h1><p>Encrypted Restic backups for Unraid</p></div>
|
||||
<div><h1>Backupper <span class="bu-version">0.1.10</span></h1><p>Encrypted Restic backups for Unraid</p></div>
|
||||
<div id="bu-health" class="bu-health" tabindex="0" data-tooltip="Zeigt, ob die Backupper-Hintergrundkomponente erreichbar ist. Beispiel: 'Daemon online' bedeutet, dass Jobs gestartet werden können.">Connecting...</div>
|
||||
</header>
|
||||
<nav class="bu-tabs" aria-label="Backupper sections">
|
||||
@@ -29,4 +29,4 @@ $pluginRoot = '/plugins/backupper';
|
||||
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
|
||||
</div>
|
||||
<script>window.BACKUPPER_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
|
||||
<script src="<?= $pluginRoot ?>/assets/backupper-0.1.9.js"></script>
|
||||
<script src="<?= $pluginRoot ?>/assets/backupper-0.1.10.js"></script>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
return dynamic[name] || `${label} ausführen.`;
|
||||
};
|
||||
const status = (value) => `<span class="bu-status ${esc(value)}" tabindex="0"${tipAttr(`Status dieses Vorgangs. Beispiel: success = erfolgreich, warning = mit Warnung, failed = fehlgeschlagen, running = läuft.`)}>${esc(value)}</span>`;
|
||||
const button = (label, action, kind = '') => `<button class="bu-button ${kind}" data-action="${esc(action)}"${tipAttr(actionTooltip(action, label))}>${esc(label)}</button>`;
|
||||
const button = (label, action, kind = '') => `<button class="bu-button ${kind}" type="button" data-action="${esc(action)}"${tipAttr(actionTooltip(action, label))}>${esc(label)}</button>`;
|
||||
const disabledButton = (label, help) => `<button class="bu-button" type="button" disabled${tipAttr(help)}>${esc(label)}</button>`;
|
||||
const actionGroup = (buttons) => `<div class="bu-actions bu-table-actions">${buttons}</div>`;
|
||||
|
||||
@@ -280,7 +280,11 @@
|
||||
<section class="bu-card" style="margin-top:16px"><h2>ntfy notification</h2><form id="bu-notify-form" class="bu-form"><input type="hidden" name="id" value="${esc(ntfy.id||id('ntfy'))}"><label>Server URL<input name="url" value="${esc(ntfy.url||'https://ntfy.sh')}"></label><label>Topic<input name="topic" value="${esc(ntfy.topic||'')}"></label><label>Token secret ID<input name="tokenRef" value="${esc(ntfy.tokenRef||id('ntfy-token'))}"></label><label>Token<input name="token" type="password"></label><label>Enabled<input name="enabled" type="checkbox" ${ntfy.enabled?'checked':''}></label><div class="wide bu-actions">${button('Save notification','submit-notify','primary')}</div></form></section>`;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user