feat: add batch backup templates and update UI for multi-VM job processing

This commit is contained in:
Rizqi 2026-06-27 02:57:46 +07:00
parent 85ca39f0ff
commit 9e5f6bfa24
4 changed files with 87 additions and 16 deletions

View File

@ -1179,7 +1179,7 @@ def job_to_display(jid, info):
'started_fmt': fmt_time(info.get('started')), 'started_fmt': fmt_time(info.get('started')),
'dest': info.get('dest', ''), 'dest': info.get('dest', ''),
'run_dest': info.get('run_dest', ''), 'run_dest': info.get('run_dest', ''),
'replication_dest': info.get('replication_dest', ''), 'replication_dest': info.get('replication_dest') or '',
'compress': info.get('compress', False), 'compress': info.get('compress', False),
'sftp_host': info.get('sftp_host', ''), 'sftp_host': info.get('sftp_host', ''),
'schedule_type': info.get('schedule_type', 'now'), 'schedule_type': info.get('schedule_type', 'now'),
@ -1439,7 +1439,7 @@ def run_job_thread_impl(jid):
# Replicate successful backup if replication_dest is configured # Replicate successful backup if replication_dest is configured
rep_dest = info.get('replication_dest') rep_dest = info.get('replication_dest')
if rep_dest: if rep_dest and str(rep_dest).strip() and str(rep_dest).strip().lower() != 'none':
rep_vm_dest = os.path.join(rep_dest, vm, f"backup-{run_timestamp}") rep_vm_dest = os.path.join(rep_dest, vm, f"backup-{run_timestamp}")
replicate_backup_folder(vm_dest, rep_vm_dest, log_path=log_path) replicate_backup_folder(vm_dest, rep_vm_dest, log_path=log_path)
except Exception as e: except Exception as e:
@ -1467,10 +1467,11 @@ def run_job_thread_impl(jid):
enforce_retention_policy(vm_info, log_path=log_path) enforce_retention_policy(vm_info, log_path=log_path)
# Enforce retention policy on replication target if configured # Enforce retention policy on replication target if configured
if info.get('replication_dest'): rep_dest = info.get('replication_dest')
if rep_dest and str(rep_dest).strip() and str(rep_dest).strip().lower() != 'none':
rep_vm_info = { rep_vm_info = {
'vm_name': vm, 'vm_name': vm,
'dest': info['replication_dest'], 'dest': rep_dest,
'retention_type': info.get('retention_type', 'keep_all'), 'retention_type': info.get('retention_type', 'keep_all'),
'retention_value': info.get('retention_value', 5) 'retention_value': info.get('retention_value', 5)
} }
@ -1536,7 +1537,7 @@ def run_job_thread_impl(jid):
# Replicate successful backup if replication_dest is configured # Replicate successful backup if replication_dest is configured
rep_dest = info.get('replication_dest') rep_dest = info.get('replication_dest')
if rep_dest: if rep_dest and str(rep_dest).strip() and str(rep_dest).strip().lower() != 'none':
rep_run_dest = os.path.join(rep_dest, info['vm_name'], f"backup-{run_timestamp}") rep_run_dest = os.path.join(rep_dest, info['vm_name'], f"backup-{run_timestamp}")
replicate_backup_folder(run_dest, rep_run_dest, log_path=log_path) replicate_backup_folder(run_dest, rep_run_dest, log_path=log_path)
except Exception as e: except Exception as e:
@ -1552,10 +1553,11 @@ def run_job_thread_impl(jid):
enforce_retention_policy(info, log_path=log_path) enforce_retention_policy(info, log_path=log_path)
# Enforce retention policy on replication target if configured # Enforce retention policy on replication target if configured
if info.get('replication_dest'): rep_dest = info.get('replication_dest')
if rep_dest and str(rep_dest).strip() and str(rep_dest).strip().lower() != 'none':
rep_info = { rep_info = {
'vm_name': info['vm_name'], 'vm_name': info['vm_name'],
'dest': info['replication_dest'], 'dest': rep_dest,
'retention_type': info.get('retention_type', 'keep_all'), 'retention_type': info.get('retention_type', 'keep_all'),
'retention_value': info.get('retention_value', 5) 'retention_value': info.get('retention_value', 5)
} }
@ -1879,7 +1881,14 @@ def create_job():
if request.method == 'POST': if request.method == 'POST':
vm_name = request.form.get('vm_name', '').strip() vm_name = request.form.get('vm_name', '').strip()
dest = request.form.get('dest', './backups').strip() dest = request.form.get('dest', './backups').strip()
replication_dest = request.form.get('replication_dest', '').strip() or None # Check if replication is enabled
enable_replication = 'enable_replication' in request.form
if enable_replication:
replication_dest = request.form.get('replication_dest', '').strip()
if not replication_dest or replication_dest.lower() == 'none':
replication_dest = None
else:
replication_dest = None
compress = 'compress' in request.form compress = 'compress' in request.form
no_verify_ssl = 'no_verify_ssl' in request.form no_verify_ssl = 'no_verify_ssl' in request.form
sftp_host = request.form.get('sftp_host', '').strip() or None sftp_host = request.form.get('sftp_host', '').strip() or None
@ -1996,7 +2005,14 @@ def batch_jobs():
if request.method == 'POST': if request.method == 'POST':
vm_names = request.form.getlist('vms') vm_names = request.form.getlist('vms')
dest = request.form.get('dest', './backups').strip() dest = request.form.get('dest', './backups').strip()
replication_dest = request.form.get('replication_dest', '').strip() or None # Check if replication is enabled
enable_replication = 'enable_replication' in request.form
if enable_replication:
replication_dest = request.form.get('replication_dest', '').strip()
if not replication_dest or replication_dest.lower() == 'none':
replication_dest = None
else:
replication_dest = None
compress = 'compress' in request.form compress = 'compress' in request.form
no_verify_ssl = 'no_verify_ssl' in request.form no_verify_ssl = 'no_verify_ssl' in request.form
disk_strategy = request.form.get('disk_strategy', 'all') disk_strategy = request.form.get('disk_strategy', 'all')
@ -2375,7 +2391,14 @@ def edit_job(jobid):
if request.method == 'POST': if request.method == 'POST':
dest = request.form.get('dest', './backups').strip() dest = request.form.get('dest', './backups').strip()
replication_dest = request.form.get('replication_dest', '').strip() or None # Check if replication is enabled
enable_replication = 'enable_replication' in request.form
if enable_replication:
replication_dest = request.form.get('replication_dest', '').strip()
if not replication_dest or replication_dest.lower() == 'none':
replication_dest = None
else:
replication_dest = None
compress = 'compress' in request.form compress = 'compress' in request.form
no_verify_ssl = 'no_verify_ssl' in request.form no_verify_ssl = 'no_verify_ssl' in request.form
schedule_type = request.form.get('schedule_type', 'now') schedule_type = request.form.get('schedule_type', 'now')

View File

@ -243,8 +243,13 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-check" style="margin-bottom:15px;">
<label class="form-label" for="replication_dest">Replication target path (optional NFS/local)</label> <input type="checkbox" id="enable_replication" name="enable_replication" onchange="toggleReplication()" />
<label for="enable_replication">Enable replication to secondary target (NFS/local)</label>
</div>
<div class="form-group" id="replication_section" style="display:none;">
<label class="form-label" for="replication_dest">Replication target path</label>
<input id="replication_dest" class="form-control" type="text" name="replication_dest" <input id="replication_dest" class="form-control" type="text" name="replication_dest"
placeholder="e.g. /mnt/nfs-backup-replica" /> placeholder="e.g. /mnt/nfs-backup-replica" />
<div id="repNfsTargets" style="margin-top:10px; display:none;"> <div id="repNfsTargets" style="margin-top:10px; display:none;">
@ -730,6 +735,17 @@
weekdayRow.style.display = ''; weekdayRow.style.display = '';
} }
} }
function toggleReplication() {
const chk = document.getElementById('enable_replication');
const sec = document.getElementById('replication_section');
if (chk.checked) {
sec.style.display = '';
} else {
sec.style.display = 'none';
document.getElementById('replication_dest').value = '';
}
}
</script> </script>
{% endblock %} {% endblock %}

View File

@ -236,8 +236,13 @@
value="./backups" placeholder="e.g. /mnt/nfs-backup or /data/vmbackups" required /> value="./backups" placeholder="e.g. /mnt/nfs-backup or /data/vmbackups" required />
</div> </div>
<div class="form-group"> <div class="form-check" style="margin-bottom:15px;">
<label class="form-label" for="replication_dest">Replication target path (optional NFS/local)</label> <input type="checkbox" id="enable_replication" name="enable_replication" onchange="toggleReplication()" />
<label for="enable_replication">Enable replication to secondary target (NFS/local)</label>
</div>
<div class="form-group" id="replication_section" style="display:none;">
<label class="form-label" for="replication_dest">Replication target path</label>
<input id="replication_dest" class="form-control" type="text" name="replication_dest" <input id="replication_dest" class="form-control" type="text" name="replication_dest"
placeholder="e.g. /mnt/nfs-backup-replica" /> placeholder="e.g. /mnt/nfs-backup-replica" />
<div id="repNfsTargets" style="margin-top:10px; display:none;"> <div id="repNfsTargets" style="margin-top:10px; display:none;">
@ -812,5 +817,16 @@
weekdayRow.style.display = ''; weekdayRow.style.display = '';
} }
} }
function toggleReplication() {
const chk = document.getElementById('enable_replication');
const sec = document.getElementById('replication_section');
if (chk.checked) {
sec.style.display = '';
} else {
sec.style.display = 'none';
document.getElementById('replication_dest').value = '';
}
}
</script> </script>
{% endblock %} {% endblock %}

View File

@ -230,8 +230,13 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-check" style="margin-bottom:15px;">
<label class="form-label" for="replication_dest">Replication target path (optional NFS/local)</label> <input type="checkbox" id="enable_replication" name="enable_replication" onchange="toggleReplication()" {% if job.replication_dest %}checked{% endif %} />
<label for="enable_replication">Enable replication to secondary target (NFS/local)</label>
</div>
<div class="form-group" id="replication_section" style="{% if not job.replication_dest %}display:none;{% endif %}">
<label class="form-label" for="replication_dest">Replication target path</label>
<input id="replication_dest" class="form-control" type="text" name="replication_dest" value="{{ job.replication_dest }}" placeholder="e.g. /mnt/nfs-backup-replica" /> <input id="replication_dest" class="form-control" type="text" name="replication_dest" value="{{ job.replication_dest }}" placeholder="e.g. /mnt/nfs-backup-replica" />
<div class="nfs-targets" id="repNfsTargets" style="display:none; margin-top: 10px;"> <div class="nfs-targets" id="repNfsTargets" style="display:none; margin-top: 10px;">
@ -650,5 +655,16 @@
weekdayRow.style.display = ''; weekdayRow.style.display = '';
} }
} }
function toggleReplication() {
const chk = document.getElementById('enable_replication');
const sec = document.getElementById('replication_section');
if (chk.checked) {
sec.style.display = '';
} else {
sec.style.display = 'none';
document.getElementById('replication_dest').value = '';
}
}
</script> </script>
{% endblock %} {% endblock %}