Add Select All, Fix Selected & Fix All to Library Maintenance findings
This commit is contained in:
parent
162542a0cf
commit
cc62541a65
5 changed files with 199 additions and 1 deletions
|
|
@ -1043,6 +1043,55 @@ class RepairWorker:
|
|||
if conn:
|
||||
conn.close()
|
||||
|
||||
def bulk_fix_findings(self, job_id: str = None, severity: str = None,
|
||||
finding_ids: List[int] = None) -> dict:
|
||||
"""Fix all pending fixable findings matching filters. Returns {fixed, failed, skipped}."""
|
||||
conn = None
|
||||
try:
|
||||
conn = self.db._get_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Build query for pending fixable findings
|
||||
fixable_types = ('dead_file', 'orphan_file', 'track_number_mismatch',
|
||||
'missing_cover_art', 'metadata_gap', 'duplicate_tracks', 'mbid_mismatch')
|
||||
placeholders = ','.join(['?'] * len(fixable_types))
|
||||
where_parts = [f"finding_type IN ({placeholders})", "status = 'pending'"]
|
||||
params = list(fixable_types)
|
||||
|
||||
if finding_ids:
|
||||
id_placeholders = ','.join(['?'] * len(finding_ids))
|
||||
where_parts.append(f"id IN ({id_placeholders})")
|
||||
params.extend(finding_ids)
|
||||
if job_id:
|
||||
where_parts.append("job_id = ?")
|
||||
params.append(job_id)
|
||||
if severity:
|
||||
where_parts.append("severity = ?")
|
||||
params.append(severity)
|
||||
|
||||
where = f"WHERE {' AND '.join(where_parts)}"
|
||||
cursor.execute(f"SELECT id FROM repair_findings {where}", params)
|
||||
ids_to_fix = [row[0] for row in cursor.fetchall()]
|
||||
conn.close()
|
||||
conn = None
|
||||
|
||||
fixed = 0
|
||||
failed = 0
|
||||
for fid in ids_to_fix:
|
||||
result = self.fix_finding(fid)
|
||||
if result.get('success'):
|
||||
fixed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
return {'fixed': fixed, 'failed': failed, 'total': len(ids_to_fix)}
|
||||
except Exception as e:
|
||||
logger.error("Error bulk fixing findings: %s", e, exc_info=True)
|
||||
return {'fixed': 0, 'failed': 0, 'total': 0, 'error': str(e)}
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def bulk_update_findings(self, finding_ids: List[int], action: str) -> int:
|
||||
"""Bulk resolve or dismiss findings. Returns count updated."""
|
||||
conn = None
|
||||
|
|
|
|||
|
|
@ -39306,6 +39306,31 @@ def repair_finding_dismiss(finding_id):
|
|||
logger.error(f"Error dismissing finding {finding_id}: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/repair/findings/bulk-fix', methods=['POST'])
|
||||
def repair_findings_bulk_fix():
|
||||
"""Bulk fix all pending fixable findings matching filters"""
|
||||
try:
|
||||
if repair_worker is None:
|
||||
return jsonify({'error': 'Repair worker not initialized'}), 400
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
job_id = data.get('job_id') or None
|
||||
severity = data.get('severity') or None
|
||||
finding_ids = data.get('ids') or None
|
||||
|
||||
result = repair_worker.bulk_fix_findings(
|
||||
job_id=job_id, severity=severity, finding_ids=finding_ids
|
||||
)
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'fixed': result.get('fixed', 0),
|
||||
'failed': result.get('failed', 0),
|
||||
'total': result.get('total', 0)
|
||||
}), 200
|
||||
except Exception as e:
|
||||
logger.error(f"Error bulk fixing findings: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/repair/findings/bulk', methods=['POST'])
|
||||
def repair_findings_bulk():
|
||||
"""Bulk resolve or dismiss findings"""
|
||||
|
|
|
|||
|
|
@ -5794,9 +5794,15 @@
|
|||
<option value="dismissed">Dismissed</option>
|
||||
</select>
|
||||
</div>
|
||||
<label class="repair-select-all" title="Select all on this page">
|
||||
<input type="checkbox" id="repair-select-all-cb" onchange="toggleSelectAllFindings(this.checked)">
|
||||
<span>Select All</span>
|
||||
</label>
|
||||
<div class="repair-findings-bulk" id="repair-findings-bulk" style="display:none;">
|
||||
<span class="repair-bulk-count" id="repair-bulk-count"></span>
|
||||
<button class="repair-bulk-btn fix" onclick="bulkFixFindings()">Fix Selected</button>
|
||||
<button class="repair-bulk-btn" onclick="bulkRepairAction('dismiss')">Dismiss Selected</button>
|
||||
<button class="repair-bulk-btn fix-all" id="repair-fix-all-btn" style="display:none;" onclick="fixAllMatchingFindings()">Fix All</button>
|
||||
</div>
|
||||
<button class="repair-clear-btn" onclick="clearRepairFindings()" title="Clear findings matching current filters">Clear Findings</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -51607,6 +51607,7 @@ function updateRepairStatusFromData(data) {
|
|||
let _repairCurrentTab = 'jobs';
|
||||
let _repairFindingsPage = 0;
|
||||
let _repairSelectedFindings = new Set();
|
||||
let _repairFindingsTotal = 0;
|
||||
const REPAIR_FINDINGS_PAGE_SIZE = 30;
|
||||
let _repairJobsCache = {}; // Cache job data for help modal
|
||||
|
||||
|
|
@ -52154,8 +52155,11 @@ async function loadRepairFindings() {
|
|||
const items = data.items || [];
|
||||
|
||||
_repairSelectedFindings.clear();
|
||||
_repairFindingsTotal = data.total || 0;
|
||||
const bulkBar = document.getElementById('repair-findings-bulk');
|
||||
if (bulkBar) bulkBar.style.display = 'none';
|
||||
const selectAllCb = document.getElementById('repair-select-all-cb');
|
||||
if (selectAllCb) { selectAllCb.checked = false; selectAllCb.indeterminate = false; }
|
||||
|
||||
if (items.length === 0) {
|
||||
container.innerHTML = `<div class="repair-empty-state">
|
||||
|
|
@ -52537,8 +52541,88 @@ function toggleFindingSelect(id, checked) {
|
|||
if (checked) _repairSelectedFindings.add(id);
|
||||
else _repairSelectedFindings.delete(id);
|
||||
|
||||
_updateFindingsBulkBar();
|
||||
}
|
||||
|
||||
function _updateFindingsBulkBar() {
|
||||
const bulkBar = document.getElementById('repair-findings-bulk');
|
||||
if (bulkBar) bulkBar.style.display = _repairSelectedFindings.size > 0 ? '' : 'none';
|
||||
const count = _repairSelectedFindings.size;
|
||||
if (bulkBar) bulkBar.style.display = count > 0 ? '' : 'none';
|
||||
const countEl = document.getElementById('repair-bulk-count');
|
||||
if (countEl) countEl.textContent = count > 0 ? `${count} selected` : '';
|
||||
|
||||
// Show "Fix All (N)" when all on page are selected and there are more pages
|
||||
const fixAllBtn = document.getElementById('repair-fix-all-btn');
|
||||
if (fixAllBtn && _repairFindingsTotal > 0) {
|
||||
const allPageSelected = count > 0 && count >= document.querySelectorAll('.repair-finding-card').length;
|
||||
fixAllBtn.style.display = (allPageSelected && _repairFindingsTotal > count) ? '' : 'none';
|
||||
fixAllBtn.textContent = `Fix All ${_repairFindingsTotal}`;
|
||||
}
|
||||
|
||||
// Sync "Select All" checkbox
|
||||
const selectAllCb = document.getElementById('repair-select-all-cb');
|
||||
if (selectAllCb) {
|
||||
const totalOnPage = document.querySelectorAll('.repair-finding-card').length;
|
||||
selectAllCb.checked = totalOnPage > 0 && count >= totalOnPage;
|
||||
selectAllCb.indeterminate = count > 0 && count < totalOnPage;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelectAllFindings(checked) {
|
||||
const checkboxes = document.querySelectorAll('.repair-finding-select input[type="checkbox"]');
|
||||
checkboxes.forEach(cb => {
|
||||
cb.checked = checked;
|
||||
const card = cb.closest('.repair-finding-card');
|
||||
if (card) {
|
||||
const id = parseInt(card.dataset.id);
|
||||
if (checked) _repairSelectedFindings.add(id);
|
||||
else _repairSelectedFindings.delete(id);
|
||||
}
|
||||
});
|
||||
_updateFindingsBulkBar();
|
||||
}
|
||||
|
||||
async function fixAllMatchingFindings() {
|
||||
const jobFilter = document.getElementById('repair-findings-job-filter');
|
||||
const severityFilter = document.getElementById('repair-findings-severity-filter');
|
||||
const jobId = jobFilter ? jobFilter.value : '';
|
||||
const severity = severityFilter ? severityFilter.value : '';
|
||||
|
||||
const scopeLabel = jobId ? jobId.replace(/_/g, ' ') : 'all jobs';
|
||||
if (!await showConfirmDialog({
|
||||
title: 'Fix All Findings',
|
||||
message: `Apply fixes to all ${_repairFindingsTotal} pending fixable findings for ${scopeLabel}? This may delete files or remove database entries depending on finding type.`,
|
||||
confirmText: 'Fix All',
|
||||
destructive: true
|
||||
})) return;
|
||||
|
||||
showToast(`Fixing ${_repairFindingsTotal} findings...`, 'info');
|
||||
|
||||
try {
|
||||
const body = {};
|
||||
if (jobId) body.job_id = jobId;
|
||||
if (severity) body.severity = severity;
|
||||
|
||||
const response = await fetch('/api/repair/findings/bulk-fix', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
showToast(`Fixed ${result.fixed}${result.failed ? `, ${result.failed} failed` : ''} of ${result.total}`, result.fixed > 0 ? 'success' : 'error');
|
||||
} else {
|
||||
showToast(result.error || 'Bulk fix failed', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in bulk fix:', error);
|
||||
showToast('Error applying bulk fix', 'error');
|
||||
}
|
||||
|
||||
_repairSelectedFindings.clear();
|
||||
loadRepairFindingsDashboard();
|
||||
loadRepairFindings();
|
||||
updateRepairStatus();
|
||||
}
|
||||
|
||||
function renderRepairFindingsPagination(total, currentPage) {
|
||||
|
|
|
|||
|
|
@ -41158,11 +41158,35 @@ tr.tag-diff-same {
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.repair-select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
.repair-select-all input[type="checkbox"] {
|
||||
accent-color: rgb(var(--accent-rgb, 99, 102, 241));
|
||||
cursor: pointer;
|
||||
}
|
||||
.repair-select-all:hover {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.repair-findings-bulk {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.repair-bulk-count {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.repair-bulk-btn {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
|
|
@ -41190,6 +41214,16 @@ tr.tag-diff-same {
|
|||
background: rgba(var(--accent-rgb, 99, 102, 241), 0.2);
|
||||
}
|
||||
|
||||
.repair-bulk-btn.fix-all {
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
border-color: rgba(245, 158, 11, 0.3);
|
||||
color: #fbbf24;
|
||||
font-weight: 600;
|
||||
}
|
||||
.repair-bulk-btn.fix-all:hover {
|
||||
background: rgba(245, 158, 11, 0.22);
|
||||
}
|
||||
|
||||
.repair-clear-btn {
|
||||
margin-left: auto;
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
|
|
|
|||
Loading…
Reference in a new issue