diff --git a/gui_app.py b/gui_app.py index 67e9994..56f6973 100644 --- a/gui_app.py +++ b/gui_app.py @@ -1249,6 +1249,98 @@ def delete_job(jobid): return redirect(url_for('list_jobs')) +@app.route('/job//edit', methods=['GET', 'POST']) +@login_required +def edit_job(jobid): + with jobs_db_lock: + info = jobs.get(jobid) + if not info: + abort(404) + if info.get('status') in ('running', 'queued'): + flash('Cannot edit a running or queued job.', 'danger') + return redirect(url_for('job_detail', jobid=jobid)) + + if request.method == 'POST': + dest = request.form.get('dest', './backups').strip() + compress = 'compress' in request.form + no_verify_ssl = 'no_verify_ssl' in request.form + schedule_type = request.form.get('schedule_type', 'now') + daily_time = request.form.get('daily_time', '02:00') + weekly_day = request.form.get('weekly_day', '0') + weekly_time = request.form.get('weekly_time', '02:00') + + monthly_basis = request.form.get('monthly_basis', 'day_num') + if monthly_basis == 'weekday': + monthly_week_num = request.form.get('monthly_week_num', '1st') + monthly_day_of_week = request.form.get('monthly_day_of_week', 'sun') + monthly_day = f"{monthly_week_num} {monthly_day_of_week}" + monthly_time = request.form.get('monthly_time_2', '02:00') + else: + monthly_day = request.form.get('monthly_day', '1') + monthly_time = request.form.get('monthly_time_1', '02:00') + + interval_hrs = request.form.get('interval_hours', '24') + label = request.form.get('job_label', '').strip() + + if schedule_type == 'daily': + sched_time = daily_time + elif schedule_type == 'weekly': + sched_time = weekly_time + elif schedule_type == 'monthly': + sched_time = monthly_time + else: + sched_time = '' + + try: + retention_value = int(request.form.get('retention_value', '5')) + except ValueError: + retention_value = 5 + retention_type = request.form.get('retention_type', 'keep_all') + use_cbt = request.form.get('use_cbt') == '1' + + # Cancel old schedule if exists + old_sched_id = info.get('schedule_id') + if old_sched_id and scheduler: + try: + scheduler.remove_job(old_sched_id) + except Exception: + pass + info['schedule_id'] = None + + # Update job config + info['label'] = label + info['dest'] = dest + info['compress'] = compress + info['no_verify_ssl'] = no_verify_ssl + info['use_cbt'] = use_cbt + info['retention_type'] = retention_type + info['retention_value'] = retention_value + info['schedule_type'] = schedule_type + info['schedule_time'] = sched_time + info['weekly_day'] = weekly_day + info['monthly_day'] = monthly_day + info['interval_hours'] = interval_hrs + + # Register new schedule if applicable + if schedule_type != 'now' and HAS_SCHEDULER: + new_sched_id = register_scheduler_job(info) + if new_sched_id: + info['schedule_id'] = new_sched_id + info['status'] = 'scheduled' + else: + info['status'] = 'finished' + else: + info['status'] = 'finished' if info.get('status') == 'scheduled' else info.get('status', 'finished') + + save_jobs_db() + flash('Job updated successfully.', 'success') + return redirect(url_for('job_detail', jobid=jobid)) + + # GET: Display edit form + job_disp = job_to_display(jobid, info) + return render_template('edit_job.html', job=job_disp, raw_job=info) + + # ── Template filter ─────────────────────────────────────────────────────────── @app.template_filter('startswith') def startswith_filter(value, prefix): diff --git a/templates/edit_job.html b/templates/edit_job.html new file mode 100644 index 0000000..8d4be50 --- /dev/null +++ b/templates/edit_job.html @@ -0,0 +1,569 @@ +{% extends "base.html" %} +{% set active_page = 'jobs' %} +{% block title %}Edit Job — {{ job.label or ('Job #' + job.id[:8]) }}{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+
+
Edit Backup Job
+
Modify configuration and schedule for job details
+
+ +
+ +
+
+
+ +
+
+ + Target Virtual Machine +
+
+
+
+
{{ job.vm_name }}
+
Disks target: {% if job.disks_count %}{{ job.disks_count }} selected disk(s){% else %}All disks{% endif %}
+
+ Locked Parameter +
+
+
+ + +
+
+ + Configuration Details +
+
+
+ + +
+ +
+ + + + +
+ +
+ + +
+
+
+ + +
+
+ + Backup Strategy +
+
+
+ + + +
+ +
+ + 80–99% transfer bandwidth savings on recurring runs. + The first run triggers a full backup to seed state. Subsequent runs download only modified blocks since the last successful snapshot. +
+
+
+ + +
+
+ + Retention Policy +
+
+
+
+ + +
+
+ + +
+
+
+ {% if job.retention_type == 'keep_count' %} + Only the latest {{ job.retention_value }} successful backups will be kept. Older ones will be deleted automatically. + {% elif job.retention_type == 'keep_days' %} + Backups older than {{ job.retention_value }} days will be deleted automatically. + {% else %} + All successful backup copies will be preserved indefinitely. + {% endif %} +
+
+
+ + +
+
+ + Schedule +
+
+
+ + + + + + + + + +
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + + {% set is_weekday = ' ' in (raw_job.monthly_day|string) %} + {% set day_str = raw_job.monthly_day|string %} + {% set week_num_val = day_str.split(' ')[0] if is_weekday else '1st' %} + {% set dow_val = day_str.split(' ')[1] if is_weekday else 'sun' %} +
+
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+ + Cancel +
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/job_detail.html b/templates/job_detail.html index 32e4f95..a2f056b 100644 --- a/templates/job_detail.html +++ b/templates/job_detail.html @@ -150,6 +150,10 @@ Run Now + + + Edit Job +