Files
2026-02-08 13:10:38 -05:00

384 lines
17 KiB
JavaScript

/**
* Remote Mappings (Movie Hunt) - table-based UI with edit modal
* Attaches to window.RemoteMappings. Load after settings core.
*/
(function() {
'use strict';
window.RemoteMappings = {
currentMappings: [],
editingIndex: null,
downloadClients: [],
refreshList: function() {
const tbody = document.getElementById('remote-mappings-table-body');
if (!tbody) return;
// Fetch mappings
fetch('./api/movie-hunt/remote-mappings')
.then(r => r.json())
.then(data => {
this.currentMappings = (data && data.mappings) ? data.mappings : [];
// Clear and rebuild table
tbody.innerHTML = '';
if (this.currentMappings.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center; padding: 20px; color: #94a3b8;">No remote path mappings configured</td></tr>';
return;
}
this.currentMappings.forEach((mapping, idx) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${this.escapeHtml(mapping.host || '')}</td>
<td>${this.escapeHtml(mapping.local_path || '')}</td>
<td>${this.escapeHtml(mapping.remote_path || '')}</td>
<td class="remote-mappings-actions-col">
<button class="btn-edit-mapping" data-index="${idx}" title="Edit">
<i class="fas fa-edit"></i>
</button>
</td>
`;
tbody.appendChild(row);
});
})
.catch(err => {
console.error('[RemoteMappings] Error loading mappings:', err);
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center; padding: 20px; color: #fca5a5;">Error loading remote path mappings</td></tr>';
});
},
loadDownloadClients: function() {
// Load download clients from the clients API
fetch('./api/clients')
.then(r => r.json())
.then(data => {
this.downloadClients = (data && data.clients) ? data.clients : [];
this.populateHostDropdown();
})
.catch(err => {
console.error('[RemoteMappings] Error loading download clients:', err);
this.downloadClients = [];
this.populateHostDropdown();
});
},
populateHostDropdown: function() {
const hostSelect = document.getElementById('remote-mapping-host');
if (!hostSelect) return;
// Clear existing options except the first one
hostSelect.innerHTML = '<option value="">Select a download client...</option>';
// Add download clients
this.downloadClients.forEach(client => {
const host = `${client.host || ''}:${client.port || ''}`;
const option = document.createElement('option');
option.value = host;
option.textContent = `${client.name || 'Unknown'} (${host})`;
hostSelect.appendChild(option);
});
},
openAddModal: function() {
this.editingIndex = null;
this.loadDownloadClients();
const modal = document.getElementById('remote-mapping-edit-modal');
const title = document.getElementById('remote-mapping-modal-title');
const deleteBtn = document.getElementById('remote-mapping-modal-delete');
const hostSelect = document.getElementById('remote-mapping-host');
const remotePathInput = document.getElementById('remote-mapping-remote-path');
const localPathInput = document.getElementById('remote-mapping-local-path');
if (title) title.textContent = 'Add Remote Path Mapping';
if (deleteBtn) deleteBtn.style.display = 'none';
if (hostSelect) hostSelect.value = '';
if (remotePathInput) remotePathInput.value = '';
if (localPathInput) localPathInput.value = '';
if (modal) {
if (modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
modal.style.display = 'flex';
document.body.classList.add('remote-mapping-edit-modal-open');
}
},
openEditModal: function(index) {
if (index < 0 || index >= this.currentMappings.length) return;
this.editingIndex = index;
this.loadDownloadClients();
const mapping = this.currentMappings[index];
const modal = document.getElementById('remote-mapping-edit-modal');
const title = document.getElementById('remote-mapping-modal-title');
const deleteBtn = document.getElementById('remote-mapping-modal-delete');
const hostSelect = document.getElementById('remote-mapping-host');
const remotePathInput = document.getElementById('remote-mapping-remote-path');
const localPathInput = document.getElementById('remote-mapping-local-path');
if (title) title.textContent = 'Edit Remote Path Mapping';
if (deleteBtn) deleteBtn.style.display = 'flex';
if (hostSelect) hostSelect.value = mapping.host || '';
if (remotePathInput) remotePathInput.value = mapping.remote_path || '';
if (localPathInput) localPathInput.value = mapping.local_path || '';
if (modal) {
if (modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
modal.style.display = 'flex';
document.body.classList.add('remote-mapping-edit-modal-open');
}
},
closeModal: function() {
const modal = document.getElementById('remote-mapping-edit-modal');
if (modal) {
modal.style.display = 'none';
document.body.classList.remove('remote-mapping-edit-modal-open');
}
this.editingIndex = null;
},
saveMapping: function() {
const hostSelect = document.getElementById('remote-mapping-host');
const remotePathInput = document.getElementById('remote-mapping-remote-path');
const localPathInput = document.getElementById('remote-mapping-local-path');
const host = (hostSelect && hostSelect.value) ? hostSelect.value.trim() : '';
const remotePath = (remotePathInput && remotePathInput.value) ? remotePathInput.value.trim() : '';
const localPath = (localPathInput && localPathInput.value) ? localPathInput.value.trim() : '';
if (!host) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Please select a host', 'error');
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Please select a host', 'error');
else alert('Please select a host');
}
return;
}
if (!remotePath) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Remote path is required', 'error');
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Remote path is required', 'error');
else alert('Remote path is required');
}
return;
}
if (!localPath) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Local path is required', 'error');
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Local path is required', 'error');
else alert('Local path is required');
}
return;
}
const payload = {
host: host,
remote_path: remotePath,
local_path: localPath
};
let url, method;
if (this.editingIndex !== null) {
// Update existing mapping
url = `./api/movie-hunt/remote-mappings/${this.editingIndex}`;
method = 'PUT';
} else {
// Add new mapping
url = './api/movie-hunt/remote-mappings';
method = 'POST';
}
fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(r => r.json())
.then(data => {
if (data.success) {
this.closeModal();
this.refreshList();
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Remote path mapping saved', 'success');
}
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification(data.message || 'Failed to save mapping', 'error');
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification(data.message || 'Failed to save mapping', 'error');
else alert(data.message || 'Failed to save mapping');
}
}
})
.catch(err => {
console.error('[RemoteMappings] Save error:', err);
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Failed to save mapping', 'error');
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Failed to save mapping', 'error');
else alert('Failed to save mapping');
}
});
},
deleteMapping: function() {
if (this.editingIndex === null) return;
const mapping = this.currentMappings[this.editingIndex];
const confirmMsg = `Delete remote path mapping for ${mapping.host || 'this host'}?`;
const self = this;
const idx = this.editingIndex;
const doDelete = function() {
fetch(`./api/movie-hunt/remote-mappings/${idx}`, {
method: 'DELETE'
})
.then(r => r.json())
.then(data => {
if (data.success) {
self.closeModal();
self.refreshList();
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Remote path mapping deleted', 'success');
}
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification(data.message || 'Failed to delete mapping', 'error');
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification(data.message || 'Failed to delete mapping', 'error');
else alert(data.message || 'Failed to delete mapping');
}
}
})
.catch(err => {
console.error('[RemoteMappings] Delete error:', err);
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Failed to delete mapping', 'error');
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) window.huntarrUI.showNotification('Failed to delete mapping', 'error');
else alert('Failed to delete mapping');
}
});
};
if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
window.HuntarrConfirm.show({
title: 'Delete Remote Path Mapping',
message: confirmMsg,
confirmLabel: 'Delete',
onConfirm: doDelete
});
} else {
if (!confirm(confirmMsg)) return;
doDelete();
}
},
escapeHtml: function(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
},
init: function() {
// Event listeners for table actions (edit buttons)
const tbody = document.getElementById('remote-mappings-table-body');
if (tbody) {
tbody.addEventListener('click', (e) => {
const editBtn = e.target.closest('.btn-edit-mapping');
if (editBtn) {
const index = parseInt(editBtn.dataset.index, 10);
this.openEditModal(index);
}
});
}
// Add button
const addBtn = document.getElementById('add-remote-mapping-btn');
if (addBtn) {
addBtn.addEventListener('click', () => {
this.openAddModal();
});
}
// Modal close buttons
const closeBtn = document.getElementById('remote-mapping-edit-modal-close');
const cancelBtn = document.getElementById('remote-mapping-modal-cancel');
const backdrop = document.getElementById('remote-mapping-edit-modal-backdrop');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
this.closeModal();
});
}
if (cancelBtn) {
cancelBtn.addEventListener('click', () => {
this.closeModal();
});
}
if (backdrop) {
backdrop.addEventListener('click', () => {
this.closeModal();
});
}
// Save button
const saveBtn = document.getElementById('remote-mapping-modal-save');
if (saveBtn) {
saveBtn.addEventListener('click', () => {
this.saveMapping();
});
}
// Delete button
const deleteBtn = document.getElementById('remote-mapping-modal-delete');
if (deleteBtn) {
deleteBtn.addEventListener('click', () => {
this.deleteMapping();
});
}
// Local path browse: use Root Folders file browser (same design and API)
const browseLocalBtn = document.getElementById('remote-mapping-browse-local-btn');
const localPathInput = document.getElementById('remote-mapping-local-path');
if (browseLocalBtn && localPathInput && window.RootFolders && typeof window.RootFolders.openBrowseModal === 'function') {
browseLocalBtn.addEventListener('click', () => {
window.RootFolders.openBrowseModal(localPathInput);
});
}
// ESC key to close modal
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const modal = document.getElementById('remote-mapping-edit-modal');
if (modal && modal.style.display === 'flex') {
this.closeModal();
}
}
});
}
};
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
window.RemoteMappings.init();
});
} else {
window.RemoteMappings.init();
}
})();