mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
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:
@@ -676,6 +676,51 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Configuration Tab -->
|
<!-- 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() {
|
async function fetchConfig() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/admin/config');
|
const res = await fetch('/api/admin/config');
|
||||||
@@ -2500,6 +2591,7 @@
|
|||||||
// Initial load
|
// Initial load
|
||||||
fetchStatus();
|
fetchStatus();
|
||||||
fetchPlaylists();
|
fetchPlaylists();
|
||||||
|
fetchTrackMappings();
|
||||||
fetchJellyfinUsers();
|
fetchJellyfinUsers();
|
||||||
fetchJellyfinPlaylists();
|
fetchJellyfinPlaylists();
|
||||||
fetchConfig();
|
fetchConfig();
|
||||||
@@ -2508,6 +2600,7 @@
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
fetchStatus();
|
fetchStatus();
|
||||||
fetchPlaylists();
|
fetchPlaylists();
|
||||||
|
fetchTrackMappings();
|
||||||
}, 30000);
|
}, 30000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user