Add manual track mappings display to web UI

- Shows all manual mappings in Active Playlists tab
- Displays summary counts (total, jellyfin, external)
- Table shows playlist, Spotify ID, type, target, and creation date
- Color-coded badges for jellyfin vs external mappings
- Auto-refreshes every 30 seconds
- Helps review mappings before phasing out local ones
This commit is contained in:
2026-02-06 11:18:48 -05:00
parent 96814aa91b
commit 810247ba8c

View File

@@ -676,6 +676,51 @@
</tbody>
</table>
</div>
<!-- Manual Track Mappings Section -->
<div class="card">
<h2>
Manual Track Mappings
<div class="actions">
<button onclick="fetchTrackMappings()">Refresh</button>
</div>
</h2>
<p style="color: var(--text-secondary); margin-bottom: 12px;">
Manual mappings override automatic matching. <strong>Local (Jellyfin)</strong> mappings will be phased out in favor of the Spotify Import plugin.
</p>
<div id="mappings-summary" style="display: flex; gap: 20px; margin-bottom: 16px; padding: 12px; background: var(--bg-tertiary); border-radius: 6px;">
<div>
<span style="color: var(--text-secondary);">Total:</span>
<span style="font-weight: 600; margin-left: 8px;" id="mappings-total">0</span>
</div>
<div>
<span style="color: var(--text-secondary);">Jellyfin (Local):</span>
<span style="font-weight: 600; margin-left: 8px; color: var(--accent);" id="mappings-jellyfin">0</span>
</div>
<div>
<span style="color: var(--text-secondary);">External:</span>
<span style="font-weight: 600; margin-left: 8px; color: var(--success);" id="mappings-external">0</span>
</div>
</div>
<table class="playlist-table">
<thead>
<tr>
<th>Playlist</th>
<th>Spotify ID</th>
<th>Type</th>
<th>Target</th>
<th>Created</th>
</tr>
</thead>
<tbody id="mappings-table-body">
<tr>
<td colspan="5" class="loading">
<span class="spinner"></span> Loading mappings...
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Configuration Tab -->
@@ -1337,6 +1382,52 @@
}
}
async function fetchTrackMappings() {
try {
const res = await fetch('/api/admin/mappings/tracks');
const data = await res.json();
// Update summary
document.getElementById('mappings-total').textContent = data.totalCount || 0;
document.getElementById('mappings-jellyfin').textContent = data.jellyfinCount || 0;
document.getElementById('mappings-external').textContent = data.externalCount || 0;
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>';
return;
}
tbody.innerHTML = data.mappings.map(m => {
const typeColor = m.type === 'jellyfin' ? 'var(--accent)' : 'var(--success)';
const typeBadge = `<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:0.8rem;background:${typeColor}20;color:${typeColor};font-weight:500;">${m.type}</span>`;
let targetDisplay = '';
if (m.type === 'jellyfin') {
targetDisplay = `<span style="font-family:monospace;font-size:0.85rem;">${m.jellyfinId}</span>`;
} else {
targetDisplay = `<span style="font-family:monospace;font-size:0.85rem;color:var(--success);">${m.externalProvider}/${m.externalId}</span>`;
}
const createdDate = m.createdAt ? new Date(m.createdAt).toLocaleString() : '-';
return `
<tr>
<td><strong>${escapeHtml(m.playlist)}</strong></td>
<td style="font-family:monospace;font-size:0.85rem;color:var(--text-secondary);">${m.spotifyId}</td>
<td>${typeBadge}</td>
<td>${targetDisplay}</td>
<td style="color:var(--text-secondary);font-size:0.85rem;">${createdDate}</td>
</tr>
`;
}).join('');
} catch (error) {
console.error('Failed to fetch track mappings:', error);
showToast('Failed to fetch track mappings', 'error');
}
}
async function fetchConfig() {
try {
const res = await fetch('/api/admin/config');
@@ -2500,6 +2591,7 @@
// Initial load
fetchStatus();
fetchPlaylists();
fetchTrackMappings();
fetchJellyfinUsers();
fetchJellyfinPlaylists();
fetchConfig();
@@ -2508,6 +2600,7 @@
setInterval(() => {
fetchStatus();
fetchPlaylists();
fetchTrackMappings();
}, 30000);
</script>
</body>