Add delete button for manual track mappings

- Added DELETE /api/admin/mappings/tracks endpoint
- Removes mapping from JSON file and Redis cache
- Deletes file if it becomes empty after removal
- Added 'Remove' button to each mapping in web UI
- Enhanced confirm dialog explaining consequences for both local and external mappings
- Supports removing both Jellyfin (local) and external provider mappings
- Allows phasing out local mappings in favor of Spotify Import plugin
This commit is contained in:
2026-02-06 11:36:51 -05:00
parent 810247ba8c
commit a56b2c3ea3
2 changed files with 89 additions and 2 deletions

View File

@@ -710,11 +710,12 @@
<th>Type</th>
<th>Target</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="mappings-table-body">
<tr>
<td colspan="5" class="loading">
<td colspan="6" class="loading">
<span class="spinner"></span> Loading mappings...
</td>
</tr>
@@ -1395,7 +1396,7 @@
const tbody = document.getElementById('mappings-table-body');
if (data.mappings.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--text-secondary);padding:40px;">No manual mappings found.</td></tr>';
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:var(--text-secondary);padding:40px;">No manual mappings found.</td></tr>';
return;
}
@@ -1419,6 +1420,9 @@
<td>${typeBadge}</td>
<td>${targetDisplay}</td>
<td style="color:var(--text-secondary);font-size:0.85rem;">${createdDate}</td>
<td>
<button class="danger" style="padding:4px 12px;font-size:0.8rem;" onclick="deleteTrackMapping('${escapeHtml(m.playlist)}', '${m.spotifyId}')">Remove</button>
</td>
</tr>
`;
}).join('');
@@ -1428,6 +1432,29 @@
}
}
async function deleteTrackMapping(playlist, spotifyId) {
if (!confirm(`Remove manual mapping for ${spotifyId} in playlist "${playlist}"?\n\nThis will:\n• Delete the manual mapping from the cache\n• Allow the track to be matched automatically again\n• For local (Jellyfin) tracks: Stop injecting locally if now available via Spotify Import plugin\n• For external tracks: Allow re-matching with potentially better results\n\nThis action cannot be undone.`)) {
return;
}
try {
const res = await fetch(`/api/admin/mappings/tracks?playlist=${encodeURIComponent(playlist)}&spotifyId=${encodeURIComponent(spotifyId)}`, {
method: 'DELETE'
});
if (res.ok) {
showToast('Mapping removed successfully', 'success');
await fetchTrackMappings();
} else {
const error = await res.json();
showToast(error.error || 'Failed to remove mapping', 'error');
}
} catch (error) {
console.error('Failed to delete mapping:', error);
showToast('Failed to remove mapping', 'error');
}
}
async function fetchConfig() {
try {
const res = await fetch('/api/admin/config');