diff --git a/public/app.js b/public/app.js index c23a093..068c2aa 100644 --- a/public/app.js +++ b/public/app.js @@ -10,6 +10,8 @@ document.addEventListener('DOMContentLoaded', () => { let currentPage = 1; let totalResults = 0; const resultsPerPage = 10; + let modListCurrentPage = 1; + let modListSearchQuery = ''; let allProperties = {}; const filteredSettings = [ 'bug-report-link', @@ -79,7 +81,10 @@ document.addEventListener('DOMContentLoaded', () => { connectionStatus: document.getElementById('connectionStatus'), geyserStatus: document.getElementById('geyserStatus'), sftpStatus: document.getElementById('sftpStatus'), - backupBtn: document.getElementById('backupBtn') + backupBtn: document.getElementById('backupBtn'), + modListSearch: document.getElementById('modListSearch'), + clearModListSearch: document.getElementById('clearModListSearch'), + modListPagination: document.getElementById('modListPagination') }; const loadouts = { @@ -162,11 +167,11 @@ document.addEventListener('DOMContentLoaded', () => { connectionStatus: '', geyserStatus: '', sftpStatus: '', - activeNotifications: new Map() // Track active notifications to prevent duplicates + activeNotifications: new Map(), + allMods: [] }; function showNotification(message, type = 'loading', key = null) { - // If a notification for this key exists, remove it if (key && state.activeNotifications.has(key)) { const existing = state.activeNotifications.get(key); existing.notification.style.opacity = '0'; @@ -200,7 +205,6 @@ document.addEventListener('DOMContentLoaded', () => { } function updateNotification(notification, message, type, key = null) { - // Ensure only one notification per key if (key && state.activeNotifications.has(key) && state.activeNotifications.get(key).notification !== notification) { const existing = state.activeNotifications.get(key); existing.notification.style.opacity = '0'; @@ -407,7 +411,7 @@ document.addEventListener('DOMContentLoaded', () => { } const requestId = crypto.randomUUID(); const action = endpoint.replace('/', '').replace('-', ' '); - const key = `action-${action}`; // Unique key per action type + const key = `action-${action}`; const notification = showNotification(`Processing ${action}...`, 'loading', key); pendingRequests.set(requestId, { resolve, reject, notification, key }); ws.send(JSON.stringify({ type: 'request', requestId, endpoint, method, body })); @@ -819,32 +823,8 @@ document.addEventListener('DOMContentLoaded', () => { } if (message.type === 'mod-list' && message.data?.mods) { - const modListHtml = message.data.mods.map(mod => ` -
${mod.name} (${mod.version})
-ID: ${mod.id}
- -${mod.name} (${mod.version})
+ID: ${mod.id}
+ +No mods found.
'; + + if (state.modListHtml !== modListHtml && elements.modList) { + elements.modList.innerHTML = modListHtml; + state.modListHtml = modListHtml; + document.querySelectorAll('.uninstall-mod').forEach(button => { + button.addEventListener('click', async () => { + const modId = button.getAttribute('data-mod-id'); + const key = `action-uninstall-mod-${modId}`; + try { + await wsRequest('/uninstall', 'POST', { mod: modId }); + const response = await wsRequest('/mod-list'); + updateNonDockerUI({ type: 'mod-list', data: response }); + showNotification(`Mod ${modId} uninstalled successfully`, 'success', key); + } catch (error) { + console.error('Uninstall mod error:', error); + showNotification(`Failed to uninstall mod ${modId}: ${error.message}`, 'error', key); + } + }); + }); + } + + updateModListPagination(); + } + async function sendConsoleCommand() { const command = elements.consoleInput.value.trim(); if (command) { @@ -1330,18 +1407,19 @@ document.addEventListener('DOMContentLoaded', () => { const key = `action-update-mods`; const response = await wsRequest('/update-mods', 'POST'); if (response.error) { - updateNotification(notification, `Failed to update mods: ${response.error}`, 'error', key); elements.updateModsOutput.textContent = `Error: ${response.error}`; + showNotification(`Failed to update mods: ${response.error}`, 'error', key); } else { const output = response.output || 'No output from mod update.'; elements.updateModsOutput.textContent = output; + showNotification('Mods updated successfully', 'success', key); } elements.updateModsModal.classList.remove('hidden'); } catch (error) { console.error('Update mods error:', error); - showNotification(`Failed to update mods: ${error.message}`, 'error', 'update-mods-error'); elements.updateModsOutput.textContent = `Error: ${error.message}`; elements.updateModsModal.classList.remove('hidden'); + showNotification(`Failed to update mods: ${error.message}`, 'error', 'update-mods-error'); } } @@ -1390,6 +1468,7 @@ document.addEventListener('DOMContentLoaded', () => { try { const key = `action-my-link`; await wsRequest('/my-link'); + showNotification('Connection link generated successfully', 'success', key); } catch (error) { console.error('Generate connection link error:', error); showNotification(`Failed to generate connection link: ${error.message}`, 'error', 'my-link-error'); @@ -1400,6 +1479,7 @@ document.addEventListener('DOMContentLoaded', () => { try { const key = `action-my-geyser-link`; await wsRequest('/my-geyser-link'); + showNotification('Geyser link generated successfully', 'success', key); } catch (error) { console.error('Generate geyser link error:', error); showNotification(`Failed to generate Geyser link: ${error.message}`, 'error', 'geyser-link-error'); @@ -1410,6 +1490,7 @@ document.addEventListener('DOMContentLoaded', () => { try { const key = `action-my-sftp`; await wsRequest('/my-sftp'); + showNotification('SFTP link generated successfully', 'success', key); } catch (error) { console.error('Generate SFTP link error:', error); showNotification(`Failed to generate SFTP link: ${error.message}`, 'error', 'sftp-link-error'); @@ -1601,6 +1682,20 @@ document.addEventListener('DOMContentLoaded', () => { saveServerProperties(); }); + elements.clearModListSearch.addEventListener('click', clearModListSearch); + + // Debounced search for installed mods + const debouncedModListSearch = debounce((query) => { + modListSearchQuery = query.trim(); + modListCurrentPage = 1; + elements.clearModListSearch.classList.toggle('hidden', !modListSearchQuery); + renderModList(); + }, 300); + + elements.modListSearch.addEventListener('input', (e) => { + debouncedModListSearch(e.target.value); + }); + if (apiKey) { connectWebSocket(); } else { diff --git a/public/index.html b/public/index.html index 3a06dc0..d7c9bae 100644 --- a/public/index.html +++ b/public/index.html @@ -187,7 +187,14 @@