Split 3803-line app.js into focused modules: - core.js: shared state, utilities, i18n, API commands, MDI icons - player.js: tabs, theme, accent, vinyl, visualizer, UI updates - websocket.js: connection, auth, reconnection - scripts.js: scripts CRUD, quick access, execution dialog - callbacks.js: callbacks CRUD - browser.js: media file browser, thumbnails, pagination, search - links.js: links CRUD, header links, display controls - main.js: DOMContentLoaded init orchestrator Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
210 lines
7.9 KiB
JavaScript
210 lines
7.9 KiB
JavaScript
// ============================================================
|
|
// Callbacks: CRUD management
|
|
// ============================================================
|
|
|
|
let callbackFormDirty = false;
|
|
|
|
let _loadCallbacksPromise = null;
|
|
async function loadCallbacksTable() {
|
|
if (_loadCallbacksPromise) return _loadCallbacksPromise;
|
|
_loadCallbacksPromise = _loadCallbacksTableImpl();
|
|
_loadCallbacksPromise.finally(() => { _loadCallbacksPromise = null; });
|
|
return _loadCallbacksPromise;
|
|
}
|
|
|
|
async function _loadCallbacksTableImpl() {
|
|
const token = localStorage.getItem('media_server_token');
|
|
const tbody = document.getElementById('callbacksTableBody');
|
|
|
|
try {
|
|
const response = await fetch('/api/callbacks/list', {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch callbacks');
|
|
}
|
|
|
|
const callbacksList = await response.json();
|
|
|
|
if (callbacksList.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="4" class="empty-state"><div class="empty-state-illustration"><svg viewBox="0 0 64 64"><circle cx="32" cy="32" r="24"/><path d="M32 20v12l8 8"/></svg><p>' + t('callbacks.empty') + '</p></div></td></tr>';
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = callbacksList.map(callback => `
|
|
<tr>
|
|
<td><code>${escapeHtml(callback.name)}</code></td>
|
|
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
|
title="${escapeHtml(callback.command)}">${escapeHtml(callback.command)}</td>
|
|
<td>${callback.timeout}s</td>
|
|
<td>
|
|
<div class="action-buttons">
|
|
<button class="action-btn execute" data-action="execute" data-callback-name="${escapeHtml(callback.name)}" title="Execute callback">
|
|
<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
|
</button>
|
|
<button class="action-btn" data-action="edit" data-callback-name="${escapeHtml(callback.name)}" title="Edit callback">
|
|
<svg viewBox="0 0 24 24"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
|
|
</button>
|
|
<button class="action-btn delete" data-action="delete" data-callback-name="${escapeHtml(callback.name)}" title="Delete callback">
|
|
<svg viewBox="0 0 24 24"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
} catch (error) {
|
|
console.error('Error loading callbacks:', error);
|
|
tbody.innerHTML = '<tr><td colspan="4" class="empty-state" style="color: var(--error);">Failed to load callbacks</td></tr>';
|
|
}
|
|
}
|
|
|
|
function showAddCallbackDialog() {
|
|
const dialog = document.getElementById('callbackDialog');
|
|
const form = document.getElementById('callbackForm');
|
|
const title = document.getElementById('callbackDialogTitle');
|
|
|
|
form.reset();
|
|
document.getElementById('callbackIsEdit').value = 'false';
|
|
document.getElementById('callbackName').disabled = false;
|
|
title.textContent = t('callbacks.dialog.add');
|
|
|
|
callbackFormDirty = false;
|
|
|
|
document.body.classList.add('dialog-open');
|
|
dialog.showModal();
|
|
}
|
|
|
|
async function showEditCallbackDialog(callbackName) {
|
|
const token = localStorage.getItem('media_server_token');
|
|
const dialog = document.getElementById('callbackDialog');
|
|
const title = document.getElementById('callbackDialogTitle');
|
|
|
|
try {
|
|
const response = await fetch('/api/callbacks/list', {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch callback details');
|
|
}
|
|
|
|
const callbacksList = await response.json();
|
|
const callback = callbacksList.find(c => c.name === callbackName);
|
|
|
|
if (!callback) {
|
|
showToast('Callback not found', 'error');
|
|
return;
|
|
}
|
|
|
|
document.getElementById('callbackIsEdit').value = 'true';
|
|
document.getElementById('callbackName').value = callbackName;
|
|
document.getElementById('callbackName').disabled = true;
|
|
document.getElementById('callbackCommand').value = callback.command;
|
|
document.getElementById('callbackTimeout').value = callback.timeout;
|
|
document.getElementById('callbackWorkingDir').value = callback.working_dir || '';
|
|
|
|
title.textContent = t('callbacks.dialog.edit');
|
|
callbackFormDirty = false;
|
|
|
|
document.body.classList.add('dialog-open');
|
|
dialog.showModal();
|
|
} catch (error) {
|
|
console.error('Error loading callback for edit:', error);
|
|
showToast('Failed to load callback details', 'error');
|
|
}
|
|
}
|
|
|
|
async function closeCallbackDialog() {
|
|
if (callbackFormDirty) {
|
|
if (!await showConfirm(t('callbacks.confirm.unsaved'))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const dialog = document.getElementById('callbackDialog');
|
|
callbackFormDirty = false;
|
|
dialog.close();
|
|
document.body.classList.remove('dialog-open');
|
|
}
|
|
|
|
async function saveCallback(event) {
|
|
event.preventDefault();
|
|
|
|
const submitBtn = event.target.querySelector('button[type="submit"]');
|
|
if (submitBtn) submitBtn.disabled = true;
|
|
|
|
const token = localStorage.getItem('media_server_token');
|
|
const isEdit = document.getElementById('callbackIsEdit').value === 'true';
|
|
const callbackName = document.getElementById('callbackName').value;
|
|
|
|
const data = {
|
|
command: document.getElementById('callbackCommand').value,
|
|
timeout: parseInt(document.getElementById('callbackTimeout').value) || 30,
|
|
working_dir: document.getElementById('callbackWorkingDir').value || null,
|
|
shell: true
|
|
};
|
|
|
|
const endpoint = isEdit ?
|
|
`/api/callbacks/update/${callbackName}` :
|
|
`/api/callbacks/create/${callbackName}`;
|
|
|
|
const method = isEdit ? 'PUT' : 'POST';
|
|
|
|
try {
|
|
const response = await fetch(endpoint, {
|
|
method,
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok && result.success) {
|
|
showToast(`Callback ${isEdit ? 'updated' : 'created'} successfully`, 'success');
|
|
callbackFormDirty = false;
|
|
closeCallbackDialog();
|
|
loadCallbacksTable();
|
|
} else {
|
|
showToast(result.detail || `Failed to ${isEdit ? 'update' : 'create'} callback`, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving callback:', error);
|
|
showToast(`Error ${isEdit ? 'updating' : 'creating'} callback`, 'error');
|
|
} finally {
|
|
if (submitBtn) submitBtn.disabled = false;
|
|
}
|
|
}
|
|
|
|
async function deleteCallbackConfirm(callbackName) {
|
|
if (!await showConfirm(t('callbacks.confirm.delete').replace('{name}', callbackName))) {
|
|
return;
|
|
}
|
|
|
|
const token = localStorage.getItem('media_server_token');
|
|
|
|
try {
|
|
const response = await fetch(`/api/callbacks/delete/${callbackName}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok && result.success) {
|
|
showToast('Callback deleted successfully', 'success');
|
|
loadCallbacksTable();
|
|
} else {
|
|
showToast(result.detail || 'Failed to delete callback', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting callback:', error);
|
|
showToast('Error deleting callback', 'error');
|
|
}
|
|
}
|