- These are the Spotify playlists currently being monitored and filled with tracks from your music service. + These are the Spotify playlists currently being injected into Jellyfin with tracks from your music service.
| Name | Spotify ID | +Sync Schedule | Tracks | Completion | Cache Age | @@ -675,7 +676,7 @@|||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| + | Loading playlists... | |||||||||||
| No playlists configured. Link playlists from the Jellyfin Playlists tab. | ||||||||||||
| No playlists configured. Link playlists from the Jellyfin Playlists tab. | ||||||||||||
| ${escapeHtml(p.name)} | ${p.id || '-'} | ++ ${escapeHtml(syncSchedule)} + + | ${statsHtml}${breakdown} |
@@ -2025,7 +2032,14 @@
const res = await fetch('/api/admin/spotify/user-playlists');
if (!res.ok) {
const error = await res.json();
- console.error('Failed to fetch Spotify playlists:', error);
+ console.error('Failed to fetch Spotify playlists:', res.status, error);
+
+ // Show user-friendly error message
+ if (res.status === 429) {
+ showToast('Spotify rate limit reached. Please wait a moment and try again.', 'warning', 5000);
+ } else if (res.status === 401) {
+ showToast('Spotify authentication failed. Check your sp_dc cookie.', 'error', 5000);
+ }
return [];
}
const data = await res.json();
@@ -2563,6 +2577,39 @@
}
}
+ async function editPlaylistSchedule(playlistName, currentSchedule) {
+ const newSchedule = prompt(`Edit sync schedule for "${playlistName}"\n\nCron format: minute hour day month dayofweek\nExamples:\n• 0 8 * * 1 = Monday 8 AM\n• 0 6 * * * = Daily 6 AM\n• 0 20 * * 5 = Friday 8 PM`, currentSchedule);
+
+ if (!newSchedule || newSchedule === currentSchedule) return;
+
+ // Validate cron format
+ const cronParts = newSchedule.trim().split(/\s+/);
+ if (cronParts.length !== 5) {
+ showToast('Invalid cron format. Expected: minute hour day month dayofweek', 'error');
+ return;
+ }
+
+ try {
+ const res = await fetch(`/api/admin/playlists/${encodeURIComponent(playlistName)}/schedule`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ syncSchedule: newSchedule.trim() })
+ });
+
+ if (res.ok) {
+ showToast('Sync schedule updated!', 'success');
+ showRestartBanner();
+ fetchPlaylists();
+ } else {
+ const error = await res.json();
+ showToast(error.error || 'Failed to update schedule', 'error');
+ }
+ } catch (error) {
+ console.error('Failed to update schedule:', error);
+ showToast('Failed to update schedule', 'error');
+ }
+ }
+
async function removePlaylist(name) {
if (!confirm(`Remove playlist "${name}"?`)) return;
| ||||||||