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:
parent
fec28b2bfa
commit
7f8dffa3ae
@ -124,6 +124,41 @@ def settings_jobs_delete_all():
|
||||
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"])
|
||||
@login_required
|
||||
@roles_required("admin")
|
||||
|
||||
@ -554,9 +554,12 @@
|
||||
<div class="card-header bg-warning">Cleanup orphaned jobs</div>
|
||||
<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>
|
||||
<form method="post" action="{{ url_for('main.settings_jobs_delete_orphaned') }}" onsubmit="return confirm('Delete orphaned jobs and their emails? This cannot be undone.');">
|
||||
<button type="submit" class="btn btn-warning">Delete orphaned jobs</button>
|
||||
</form>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ url_for('main.settings_jobs_orphaned') }}" class="btn btn-outline-warning">Preview orphaned jobs</a>
|
||||
<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>
|
||||
|
||||
@ -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 %}
|
||||
@ -6,6 +6,7 @@ This file documents all changes made to this project via Claude Code.
|
||||
|
||||
### 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 "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
|
||||
- Removed customer name from Autotask ticket title to keep titles concise (format changed from "[Backupchecks] Customer - Job Name - Status" to "[Backupchecks] Job Name - Status")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user