Fix web UI config persistence and cookie age tracking

- Add SPOTIFY_API_SESSION_COOKIE_SET_DATE to docker-compose.yml env mapping
- Mount .env file in container for web UI to update
- Add SessionCookieSetDate loading in Program.cs
- Improve .env update logic with better error handling and logging
- Auto-initialize cookie date when cookie exists but date not set
- Simplify local vs external track detection in Jellyfin playlists
- Enhanced Spotify playlist ID parsing (supports ID, URI, and URL formats)
- Better UI clarity: renamed tabs to 'Link Playlists' and 'Active Playlists'
This commit is contained in:
2026-02-03 16:50:13 -05:00
parent 79a9e4063d
commit 5606706dc8
7 changed files with 202 additions and 99 deletions

View File

@@ -493,8 +493,8 @@
<div class="tabs">
<div class="tab active" data-tab="dashboard">Dashboard</div>
<div class="tab" data-tab="jellyfin-playlists">Jellyfin Playlists</div>
<div class="tab" data-tab="playlists">Configured Playlists</div>
<div class="tab" data-tab="jellyfin-playlists">Link Playlists</div>
<div class="tab" data-tab="playlists">Active Playlists</div>
<div class="tab" data-tab="config">Configuration</div>
</div>
@@ -554,18 +554,18 @@
</div>
</div>
<!-- Jellyfin Playlists Tab -->
<!-- Link Playlists Tab -->
<div class="tab-content" id="tab-jellyfin-playlists">
<div class="card">
<h2>
Jellyfin Playlists
Link Jellyfin Playlists to Spotify
<div class="actions">
<button onclick="fetchJellyfinPlaylists()">Refresh</button>
</div>
</h2>
<p style="color: var(--text-secondary); margin-bottom: 16px;">
Link Jellyfin playlists to Spotify playlists to fill in missing tracks.
Select a user and/or library to filter playlists.
Connect your Jellyfin playlists to Spotify playlists. Allstarr will automatically fill in missing tracks from Spotify using your preferred music service (SquidWTF/Deezer/Qobuz).
<br><strong>Tip:</strong> Use the sp_dc cookie method for best results - it's simpler and more reliable.
</p>
<div style="display: flex; gap: 16px; margin-bottom: 16px; flex-wrap: wrap;">
@@ -599,16 +599,18 @@
</div>
</div>
<!-- Playlists Tab -->
<!-- Active Playlists Tab -->
<div class="tab-content" id="tab-playlists">
<div class="card">
<h2>
Configured Playlists
Active Spotify Playlists
<div class="actions">
<button onclick="refreshPlaylists()">Refresh All</button>
<button class="primary" onclick="openAddPlaylist()">Add Playlist</button>
</div>
</h2>
<p style="color: var(--text-secondary); margin-bottom: 12px;">
These are the Spotify playlists currently being monitored and filled with tracks from your music service.
</p>
<table class="playlist-table">
<thead>
<tr>
@@ -812,8 +814,7 @@
<div class="modal-content">
<h3>Link to Spotify Playlist</h3>
<p style="color: var(--text-secondary); margin-bottom: 16px;">
Enter the Spotify playlist ID to link with this Jellyfin playlist.
Allstarr will fill in missing tracks from Spotify.
Enter the Spotify playlist ID or URL. Allstarr will automatically download missing tracks from your configured music service.
</p>
<div class="form-group">
<label>Jellyfin Playlist</label>
@@ -821,9 +822,11 @@
<input type="hidden" id="link-jellyfin-id">
</div>
<div class="form-group">
<label>Spotify Playlist ID</label>
<input type="text" id="link-spotify-id" placeholder="e.g., 37i9dQZF1DX0XUsuxWHRQd">
<small style="color: var(--text-secondary);">Get this from Spotify URL or Spotify Import plugin</small>
<label>Spotify Playlist ID or URL</label>
<input type="text" id="link-spotify-id" placeholder="37i9dQZF1DXcBWIGoYBM5M or spotify:playlist:... or full URL">
<small style="color: var(--text-secondary); display: block; margin-top: 4px;">
Accepts: <code>37i9dQZF1DXcBWIGoYBM5M</code>, <code>spotify:playlist:37i9dQZF1DXcBWIGoYBM5M</code>, or full Spotify URL
</small>
</div>
<div class="modal-actions">
<button onclick="closeModal('link-playlist-modal')">Cancel</button>
@@ -884,9 +887,9 @@
function formatCookieAge(setDateStr, hasCookie = false) {
if (!setDateStr) {
if (hasCookie) {
return { text: 'Unknown age', class: 'warning', detail: 'Initializing tracking...', needsInit: true };
return { text: 'Unknown age', class: 'warning', detail: 'Cookie date not tracked', needsInit: true };
}
return { text: 'No cookie', class: '', detail: '' };
return { text: 'No cookie', class: '', detail: '', needsInit: false };
}
const setDate = new Date(setDateStr);
@@ -913,7 +916,7 @@
else if (remaining > 0) detail = 'Cookie may expire soon!';
else detail = 'Cookie may have expired - update if having issues';
return { text, class: status, detail };
return { text, class: status, detail, needsInit: false };
}
// Initialize cookie date if cookie exists but date is not set
@@ -921,9 +924,15 @@
try {
const res = await fetch('/api/admin/config/init-cookie-date', { method: 'POST' });
if (res.ok) {
console.log('Cookie date initialized successfully');
// Refresh status after initialization
fetchStatus();
fetchConfig();
setTimeout(() => {
fetchStatus();
fetchConfig();
}, 500);
} else {
const data = await res.json();
console.log('Cookie date init response:', data);
}
} catch (error) {
console.error('Failed to init cookie date:', error);
@@ -974,6 +983,7 @@
// Auto-init cookie date if cookie exists but date is not set
if (age.needsInit) {
console.log('Cookie exists but date not set, initializing...');
initCookieDate();
}
}
@@ -1145,12 +1155,23 @@
return;
}
// Extract ID from URL if pasted
// Extract ID from various Spotify formats:
// - spotify:playlist:37i9dQZF1DXcBWIGoYBM5M
// - https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M
// - 37i9dQZF1DXcBWIGoYBM5M
let cleanSpotifyId = spotifyId;
if (spotifyId.includes('spotify.com/playlist/')) {
// Handle spotify: URI format
if (spotifyId.startsWith('spotify:playlist:')) {
cleanSpotifyId = spotifyId.replace('spotify:playlist:', '');
}
// Handle URL format
else if (spotifyId.includes('spotify.com/playlist/')) {
const match = spotifyId.match(/playlist\/([a-zA-Z0-9]+)/);
if (match) cleanSpotifyId = match[1];
}
// Remove any query parameters or trailing slashes
cleanSpotifyId = cleanSpotifyId.split('?')[0].split('#')[0].replace(/\/$/, '');
try {
const res = await fetch(`/api/admin/jellyfin/playlists/${encodeURIComponent(jellyfinId)}/link`, {