mumbullet/web/script.js
2025-06-21 11:47:06 -06:00

287 lines
9.6 KiB
JavaScript

// DOM Elements
const queueTab = document.getElementById('queueTab');
const usersTab = document.getElementById('usersTab');
const cacheTab = document.getElementById('cacheTab');
const logoutButton = document.getElementById('logoutButton');
const queueSection = document.getElementById('queueSection');
const usersSection = document.getElementById('usersSection');
const cacheSection = document.getElementById('cacheSection');
const nowPlaying = document.getElementById('nowPlaying');
const queueList = document.getElementById('queueList');
const clearQueueButton = document.getElementById('clearQueueButton');
const usersTable = document.getElementById('usersTable');
const addUserButton = document.getElementById('addUserButton');
const addUserModal = document.getElementById('addUserModal');
const addUserForm = document.getElementById('addUserForm');
const cancelAddUser = document.getElementById('cancelAddUser');
const cacheSongCount = document.getElementById('cacheSongCount');
const cacheSize = document.getElementById('cacheSize');
const cacheMaxSize = document.getElementById('cacheMaxSize');
const cacheUsage = document.getElementById('cacheUsage');
const clearCacheButton = document.getElementById('clearCacheButton');
// Tab switching
function switchTab(tab, section) {
// Remove active class from all tabs and sections
[queueTab, usersTab, cacheTab].forEach(t => t.classList.remove('active'));
[queueSection, usersSection, cacheSection].forEach(s => s.classList.remove('active'));
// Add active class to selected tab and section
tab.classList.add('active');
section.classList.add('active');
}
queueTab.addEventListener('click', () => switchTab(queueTab, queueSection));
usersTab.addEventListener('click', () => switchTab(usersTab, usersSection));
cacheTab.addEventListener('click', () => switchTab(cacheTab, cacheSection));
// Logout
logoutButton.addEventListener('click', async () => {
try {
await fetch('/api/logout', {
method: 'POST',
credentials: 'include', // Important for cookies to work
});
window.location.href = '/login';
} catch (error) {
console.error('Logout failed:', error);
}
});
// Queue management
function formatDuration(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
async function loadQueue() {
try {
const response = await fetch('/api/queue', {
credentials: 'include', // Important for cookies to work
});
const responseData = await response.json();
// Update now playing
if (responseData.current_song) {
nowPlaying.innerHTML = `
<div class="song-info">
<h4>${responseData.current_song.title}</h4>
<p>${formatDuration(responseData.current_song.duration)}</p>
</div>
<div class="song-status">
<span class="badge">${responseData.state}</span>
</div>
`;
} else {
nowPlaying.innerHTML = '<p>Nothing playing</p>';
}
// Update queue
if (responseData.queue && responseData.queue.length > 0) {
queueList.innerHTML = responseData.queue.map((songItem, idx) => {
if (songItem.is_current) {
return `
<li class="now-playing">
<div class="song-info">
<strong>${songItem.title}</strong>
<span>${formatDuration(songItem.duration)}</span>
</div>
<div class="song-status">
<span class="badge">Now Playing</span>
</div>
</li>
`;
} else {
return `
<li>
<div class="song-info">
<strong>${idx + 1}. ${songItem.title}</strong>
<span>${formatDuration(songItem.duration)}</span>
</div>
</li>
`;
}
}).join('');
} else {
queueList.innerHTML = '<li class="empty-message">Queue is empty</li>';
}
} catch (error) {
console.error('Failed to load queue:', error);
queueList.innerHTML = '<li class="empty-message">Failed to load queue</li>';
}
}
clearQueueButton.addEventListener('click', async () => {
if (!confirm('Are you sure you want to clear the queue?')) return;
try {
await fetch('/api/queue', {
method: 'DELETE',
credentials: 'include', // Important for cookies to work
});
loadQueue();
} catch (error) {
console.error('Failed to clear queue:', error);
alert('Failed to clear queue');
}
});
// User management
async function loadUsers() {
try {
const response = await fetch('/api/users', {
credentials: 'include', // Important for cookies to work
});
const responseData = await response.json();
if (responseData.users && responseData.users.length > 0) {
const tbody = usersTable.querySelector('tbody');
tbody.innerHTML = responseData.users.map(userItem => `
<tr>
<td>${userItem.username}</td>
<td>
<select class="permission-select" data-username="${userItem.username}">
<option value="0" ${userItem.permission_level == 0 ? 'selected' : ''}>None</option>
<option value="1" ${userItem.permission_level == 1 ? 'selected' : ''}>View Only</option>
<option value="2" ${userItem.permission_level == 2 ? 'selected' : ''}>Read/Write</option>
<option value="3" ${userItem.permission_level == 3 ? 'selected' : ''}>Admin</option>
</select>
</td>
<td>
<button class="save-permission" data-username="${userItem.username}">Save</button>
</td>
</tr>
`).join('');
// Add event listeners to save buttons
document.querySelectorAll('.save-permission').forEach(button => {
button.addEventListener('click', async () => {
const username = button.dataset.username;
const select = document.querySelector(`.permission-select[data-username="${username}"]`);
const permissionLevel = parseInt(select.value);
try {
await fetch(`/api/users/${username}/permissions`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ permission_level: permissionLevel }),
credentials: 'include', // Important for cookies to work
});
alert(`Permission updated for ${username}`);
} catch (error) {
console.error('Failed to update permission:', error);
alert('Failed to update permission');
}
});
});
} else {
usersTable.querySelector('tbody').innerHTML = '<tr class="empty-message"><td colspan="3">No users found</td></tr>';
}
} catch (error) {
console.error('Failed to load users:', error);
usersTable.querySelector('tbody').innerHTML = '<tr class="empty-message"><td colspan="3">Failed to load users</td></tr>';
}
}
// Add user modal
addUserButton.addEventListener('click', () => {
addUserModal.classList.add('active');
});
cancelAddUser.addEventListener('click', () => {
addUserModal.classList.remove('active');
addUserForm.reset();
});
addUserForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('newUsername').value;
const permissionLevel = parseInt(document.getElementById('newPermissionLevel').value);
try {
await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, permission_level: permissionLevel }),
credentials: 'include', // Important for cookies to work
});
addUserModal.classList.remove('active');
addUserForm.reset();
loadUsers();
} catch (error) {
console.error('Failed to add user:', error);
alert('Failed to add user');
}
});
// Cache management
async function loadCacheStats() {
try {
const response = await fetch('/api/cache', {
credentials: 'include', // Important for cookies to work
headers: {
'Accept': 'application/json'
}
});
const stats = await response.json();
cacheSongCount.textContent = stats.songCount;
cacheSize.textContent = `${stats.totalSizeMb} MB`;
cacheMaxSize.textContent = `${stats.maxSizeMb} MB`;
const usagePercent = stats.maxSizeMb > 0
? (stats.totalSizeMb / stats.maxSizeMb) * 100
: 0;
cacheUsage.style.width = `${Math.min(usagePercent, 100)}%`;
// Change color based on usage
if (usagePercent > 90) {
cacheUsage.style.backgroundColor = 'var(--danger-color)';
} else if (usagePercent > 70) {
cacheUsage.style.backgroundColor = 'var(--accent-color)';
} else {
cacheUsage.style.backgroundColor = 'var(--primary-color)';
}
} catch (error) {
console.error('Failed to load cache stats:', error);
}
}
clearCacheButton.addEventListener('click', async () => {
if (!confirm('Are you sure you want to clear the cache? This will delete all downloaded audio files.')) return;
try {
await fetch('/api/cache', {
method: 'DELETE',
credentials: 'include', // Important for cookies to work
});
loadCacheStats();
} catch (error) {
console.error('Failed to clear cache:', error);
alert('Failed to clear cache');
}
});
// Initial load
document.addEventListener('DOMContentLoaded', () => {
loadQueue();
loadUsers();
loadCacheStats();
// Refresh data periodically
setInterval(loadQueue, 10000); // Every 10 seconds
setInterval(loadCacheStats, 30000); // Every 30 seconds
});