diff --git a/allstarr/wwwroot/index.html b/allstarr/wwwroot/index.html index 4517a035..274217a1 100644 --- a/allstarr/wwwroot/index.html +++ b/allstarr/wwwroot/index.html @@ -87,6 +87,7 @@ + @@ -106,6 +107,7 @@
Dashboard
Link Playlists
Injected Playlists
+
Song Migration
Kept Downloads
Scrobbling
Configuration
@@ -374,6 +376,61 @@ + +
+
+

+ Song Migration +
+ + +
+

+

+ Tracks from your injected Spotify playlists that are not in your Jellyfin library. + This includes tracks matched through external providers (SquidWTF, Deezer, Qobuz) and tracks that + are still missing. Use the CSV export to download these tracks with your preferred tool. +

+
+
+
+ Playlists: + 0 +
+
+ External: + 0 +
+
+ Missing: + 0 +
+
+ Total to Migrate: + 0 +
+
+ + + + + + + + + + + + + + +
NameNon-Local TracksStatus...
+ Loading playlists... +
+
+
+
diff --git a/allstarr/wwwroot/js/dashboard-data.js b/allstarr/wwwroot/js/dashboard-data.js index dea25c70..7208cd64 100644 --- a/allstarr/wwwroot/js/dashboard-data.js +++ b/allstarr/wwwroot/js/dashboard-data.js @@ -382,6 +382,15 @@ function startDashboardRefresh() { fetchDownloads(); } + const songMigrationTab = document.getElementById("tab-song-migration"); + if ( + songMigrationTab && + songMigrationTab.classList.contains("active") && + typeof window.fetchSongMigration === "function" + ) { + window.fetchSongMigration(); + } + const endpointsTab = document.getElementById("tab-endpoints"); if (endpointsTab && endpointsTab.classList.contains("active")) { fetchEndpointUsage(); @@ -418,13 +427,15 @@ async function loadDashboardData() { function startDownloadActivityStream() { if (!isAdminSession()) return; - + if (downloadActivityEventSource) { downloadActivityEventSource.close(); } - downloadActivityEventSource = new EventSource("/api/admin/downloads/activity"); - + downloadActivityEventSource = new EventSource( + "/api/admin/downloads/activity", + ); + downloadActivityEventSource.onmessage = (event) => { try { const downloads = JSON.parse(event.data); @@ -450,40 +461,50 @@ function renderDownloadActivity(downloads) { } const statusIcons = { - 0: '⏳', // NotStarted + 0: "⏳", // NotStarted 1: ' Downloading', // InProgress - 2: '✅ Completed', // Completed - 3: '❌ Failed' // Failed + 2: "✅ Completed", // Completed + 3: "❌ Failed", // Failed }; - const html = downloads.map(d => { - const downloadProgress = clampProgress(d.progress); - const playbackProgress = clampProgress(d.playbackProgress); + const html = downloads + .map((d) => { + const downloadProgress = clampProgress(d.progress); + const playbackProgress = clampProgress(d.playbackProgress); - // Determine elapsed/duration text - let timeText = ""; - if (d.startedAt) { - const start = new Date(d.startedAt); - const end = d.completedAt ? new Date(d.completedAt) : new Date(); - const diffSecs = Math.floor((end.getTime() - start.getTime()) / 1000); - timeText = diffSecs < 60 ? `${diffSecs}s` : `${Math.floor(diffSecs/60)}m ${diffSecs%60}s`; - } + // Determine elapsed/duration text + let timeText = ""; + if (d.startedAt) { + const start = new Date(d.startedAt); + const end = d.completedAt ? new Date(d.completedAt) : new Date(); + const diffSecs = Math.floor((end.getTime() - start.getTime()) / 1000); + timeText = + diffSecs < 60 + ? `${diffSecs}s` + : `${Math.floor(diffSecs / 60)}m ${diffSecs % 60}s`; + } - const progressMeta = []; - if (typeof d.durationSeconds === "number" && typeof d.playbackPositionSeconds === "number") { - progressMeta.push(`${formatSeconds(d.playbackPositionSeconds)} / ${formatSeconds(d.durationSeconds)}`); - } else if (typeof d.durationSeconds === "number") { - progressMeta.push(formatSeconds(d.durationSeconds)); - } - if (d.requestedForStreaming) { - progressMeta.push("stream"); - } + const progressMeta = []; + if ( + typeof d.durationSeconds === "number" && + typeof d.playbackPositionSeconds === "number" + ) { + progressMeta.push( + `${formatSeconds(d.playbackPositionSeconds)} / ${formatSeconds(d.durationSeconds)}`, + ); + } else if (typeof d.durationSeconds === "number") { + progressMeta.push(formatSeconds(d.durationSeconds)); + } + if (d.requestedForStreaming) { + progressMeta.push("stream"); + } - const progressMetaText = progressMeta.length > 0 - ? `
${progressMeta.map(escapeHtml).join(" • ")}
` - : ""; + const progressMetaText = + progressMeta.length > 0 + ? `
${progressMeta.map(escapeHtml).join(" • ")}
` + : ""; - const progressBar = ` + const progressBar = `