Add preview page for orphaned jobs before deletion

Added verification step before deleting orphaned jobs:
- New GET endpoint /settings/jobs/orphaned to preview the list
- Shows detailed table with job name, backup software/type, customer ID,
  run count, and email count
- "Preview orphaned jobs" button on maintenance page
- Delete button on preview page shows exact count
- Summary shows total jobs, runs, and emails to be deleted

This allows admins to verify which jobs will be deleted before
taking the destructive action.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Ivo Oskamp 2026-02-09 14:07:12 +01:00
parent fec28b2bfa
commit 7f8dffa3ae
4 changed files with 129 additions and 3 deletions

View File

@ -124,6 +124,41 @@ def settings_jobs_delete_all():
return redirect(url_for("main.settings")) return redirect(url_for("main.settings"))
@main_bp.route("/settings/jobs/orphaned", methods=["GET"])
@login_required
@roles_required("admin")
def settings_jobs_orphaned():
"""Show list of orphaned jobs for verification before deletion."""
# Find jobs without valid customer
orphaned_jobs = Job.query.outerjoin(Customer, Job.customer_id == Customer.id).filter(
db.or_(
Job.customer_id.is_(None),
Customer.id.is_(None)
)
).order_by(Job.job_name.asc()).all()
# Build list with details
jobs_list = []
for job in orphaned_jobs:
run_count = JobRun.query.filter_by(job_id=job.id).count()
mail_count = JobRun.query.filter_by(job_id=job.id).filter(JobRun.mail_message_id.isnot(None)).count()
jobs_list.append({
"id": job.id,
"job_name": job.job_name or "Unnamed",
"backup_software": job.backup_software or "-",
"backup_type": job.backup_type or "-",
"customer_id": job.customer_id,
"run_count": run_count,
"mail_count": mail_count,
})
return render_template(
"main/settings_orphaned_jobs.html",
orphaned_jobs=jobs_list,
)
@main_bp.route("/settings/jobs/delete-orphaned", methods=["POST"]) @main_bp.route("/settings/jobs/delete-orphaned", methods=["POST"])
@login_required @login_required
@roles_required("admin") @roles_required("admin")

View File

@ -554,9 +554,12 @@
<div class="card-header bg-warning">Cleanup orphaned jobs</div> <div class="card-header bg-warning">Cleanup orphaned jobs</div>
<div class="card-body"> <div class="card-body">
<p class="mb-3">Delete jobs that are no longer linked to an existing customer. Related emails and runs will be <strong>permanently deleted</strong> from the database.</p> <p class="mb-3">Delete jobs that are no longer linked to an existing customer. Related emails and runs will be <strong>permanently deleted</strong> from the database.</p>
<form method="post" action="{{ url_for('main.settings_jobs_delete_orphaned') }}" onsubmit="return confirm('Delete orphaned jobs and their emails? This cannot be undone.');"> <div class="d-flex gap-2">
<button type="submit" class="btn btn-warning">Delete orphaned jobs</button> <a href="{{ url_for('main.settings_jobs_orphaned') }}" class="btn btn-outline-warning">Preview orphaned jobs</a>
</form> <form method="post" action="{{ url_for('main.settings_jobs_delete_orphaned') }}" onsubmit="return confirm('Delete orphaned jobs and their emails? This cannot be undone.');" class="d-inline">
<button type="submit" class="btn btn-warning">Delete orphaned jobs</button>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,87 @@
{% extends "layout/base.html" %}
{% block title %}Orphaned Jobs Preview{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2>Orphaned Jobs Preview</h2>
<p class="text-muted mb-0">Jobs without a valid customer link</p>
</div>
<a href="{{ url_for('main.settings', section='maintenance') }}" class="btn btn-outline-secondary">Back to Settings</a>
</div>
{% if orphaned_jobs %}
<div class="alert alert-warning">
<strong>⚠️ Warning:</strong> Found {{ orphaned_jobs|length }} orphaned job(s). Review the list below before deleting.
</div>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span>Orphaned Jobs List</span>
<form method="post" action="{{ url_for('main.settings_jobs_delete_orphaned') }}" onsubmit="return confirm('Delete all {{ orphaned_jobs|length }} orphaned jobs and their emails? This cannot be undone.');">
<button type="submit" class="btn btn-sm btn-danger">Delete All ({{ orphaned_jobs|length }} jobs)</button>
</form>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Job Name</th>
<th>Backup Software</th>
<th>Backup Type</th>
<th>Customer ID</th>
<th class="text-end">Runs</th>
<th class="text-end">Emails</th>
</tr>
</thead>
<tbody>
{% for job in orphaned_jobs %}
<tr>
<td>{{ job.job_name }}</td>
<td>{{ job.backup_software }}</td>
<td>{{ job.backup_type }}</td>
<td>
{% if job.customer_id %}
<span class="badge bg-danger">{{ job.customer_id }} (deleted)</span>
{% else %}
<span class="badge bg-secondary">NULL</span>
{% endif %}
</td>
<td class="text-end">{{ job.run_count }}</td>
<td class="text-end">{{ job.mail_count }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr class="table-light">
<td colspan="4"><strong>Total</strong></td>
<td class="text-end"><strong>{{ orphaned_jobs|sum(attribute='run_count') }}</strong></td>
<td class="text-end"><strong>{{ orphaned_jobs|sum(attribute='mail_count') }}</strong></td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="alert alert-info">
<strong> What will be deleted:</strong>
<ul class="mb-0">
<li>{{ orphaned_jobs|length }} job(s)</li>
<li>{{ orphaned_jobs|sum(attribute='run_count') }} job run(s)</li>
<li>{{ orphaned_jobs|sum(attribute='mail_count') }} email(s)</li>
<li>All related data (backup objects, ticket/remark links, scopes, overrides)</li>
</ul>
</div>
{% else %}
<div class="alert alert-success">
<strong>✅ No orphaned jobs found.</strong>
<p class="mb-0">All jobs are properly linked to existing customers.</p>
</div>
{% endif %}
</div>
{% endblock %}

View File

@ -6,6 +6,7 @@ This file documents all changes made to this project via Claude Code.
### Added ### Added
- Added "Cleanup orphaned jobs" maintenance option in Settings → Maintenance to delete jobs without valid customer links and their associated emails/runs permanently from database (useful when customers are removed) - Added "Cleanup orphaned jobs" maintenance option in Settings → Maintenance to delete jobs without valid customer links and their associated emails/runs permanently from database (useful when customers are removed)
- Added "Preview orphaned jobs" button to show detailed list of jobs to be deleted with run/email counts before confirming deletion (verification step for safety)
### Changed ### Changed
- Removed customer name from Autotask ticket title to keep titles concise (format changed from "[Backupchecks] Customer - Job Name - Status" to "[Backupchecks] Job Name - Status") - Removed customer name from Autotask ticket title to keep titles concise (format changed from "[Backupchecks] Customer - Job Name - Status" to "[Backupchecks] Job Name - Status")