Add loading state to save mapping button and timeout handling

This commit is contained in:
2026-02-04 19:24:02 -05:00
parent cf1428d678
commit d11b656b23
2 changed files with 38 additions and 11 deletions

View File

@@ -841,16 +841,24 @@ public class AdminController : ControllerBase
{ {
_logger.LogInformation("Triggering immediate playlist rebuild for {Playlist} with new manual mapping", decodedName); _logger.LogInformation("Triggering immediate playlist rebuild for {Playlist} with new manual mapping", decodedName);
// Wait for the rebuild to complete before responding to ensure UI gets updated cache // Run rebuild in background with timeout to avoid blocking the response
_ = Task.Run(async () =>
{
try try
{ {
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2)); // 2 minute timeout
await _matchingService.TriggerMatchingForPlaylistAsync(decodedName); await _matchingService.TriggerMatchingForPlaylistAsync(decodedName);
_logger.LogInformation("✓ Playlist {Playlist} rebuilt successfully with manual mapping", decodedName); _logger.LogInformation("✓ Playlist {Playlist} rebuilt successfully with manual mapping", decodedName);
} }
catch (OperationCanceledException)
{
_logger.LogWarning("Playlist rebuild for {Playlist} timed out after 2 minutes", decodedName);
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Failed to rebuild playlist {Playlist} after manual mapping", decodedName); _logger.LogError(ex, "Failed to rebuild playlist {Playlist} after manual mapping", decodedName);
} }
});
} }
else else
{ {

View File

@@ -2042,13 +2042,24 @@
return; return;
} }
// Show loading state
const saveBtn = document.getElementById('map-save-btn');
const originalText = saveBtn.textContent;
saveBtn.textContent = 'Saving...';
saveBtn.disabled = true;
try { try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
const res = await fetch('/api/admin/playlists/' + encodeURIComponent(playlistName) + '/map', { const res = await fetch('/api/admin/playlists/' + encodeURIComponent(playlistName) + '/map', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ spotifyId, jellyfinId }) body: JSON.stringify({ spotifyId, jellyfinId }),
signal: controller.signal
}); });
clearTimeout(timeoutId);
const data = await res.json(); const data = await res.json();
if (res.ok) { if (res.ok) {
@@ -2093,8 +2104,16 @@
showToast(data.error || 'Failed to save mapping', 'error'); showToast(data.error || 'Failed to save mapping', 'error');
} }
} catch (error) { } catch (error) {
if (error.name === 'AbortError') {
showToast('Request timed out - mapping may still be processing', 'warning');
} else {
showToast('Failed to save mapping', 'error'); showToast('Failed to save mapping', 'error');
} }
} finally {
// Reset button state
saveBtn.textContent = originalText;
saveBtn.disabled = false;
}
} }
function escapeJs(text) { function escapeJs(text) {