// Admin Panel JavaScript // API Configuration const API_BASE = CONFIG ? CONFIG.API_BASE : 'http://localhost:5000/api'; // State let platforms = []; let editingPlatformKey = null; let formatCounter = 0; let platformPatterns = {}; let aspectRatioPatterns = {}; // Initialize document.addEventListener('DOMContentLoaded', () => { loadPlatforms(); loadNamingConventions(); setupEventListeners(); }); // Event Listeners function setupEventListeners() { document.getElementById('addPlatformBtn').addEventListener('click', () => openModal()); document.getElementById('exportBtn').addEventListener('click', exportSpecs); document.getElementById('importBtn').addEventListener('click', () => document.getElementById('importFile').click()); document.getElementById('importFile').addEventListener('change', importSpecs); document.getElementById('reloadBtn').addEventListener('click', loadPlatforms); document.getElementById('resetFactoryBtn').addEventListener('click', resetToFactory); document.getElementById('closeModal').addEventListener('click', closeModal); document.getElementById('cancelBtn').addEventListener('click', closeModal); document.getElementById('addFormatBtn').addEventListener('click', addFormatConfig); document.getElementById('platformForm').addEventListener('submit', savePlatform); // Naming conventions document.getElementById('addPlatformPatternBtn').addEventListener('click', addPlatformPattern); document.getElementById('addAspectRatioPatternBtn').addEventListener('click', addAspectRatioPattern); document.getElementById('saveNamingBtn').addEventListener('click', saveNamingConventions); document.getElementById('testNamingBtn').addEventListener('click', testNaming); } // Load Platforms async function loadPlatforms() { try { const response = await fetch(`${API_BASE}/platforms`); const data = await response.json(); platforms = data.platforms; updateMetrics(); renderPlatforms(); } catch (error) { console.error('Error loading platforms:', error); showMessage('Error loading platforms: ' + error.message, 'error'); } } // Update Metrics function updateMetrics() { const totalPlatforms = platforms.length; const totalFormats = platforms.reduce((sum, p) => sum + p.formats.length, 0); const codecs = new Set(platforms.map(p => p.codec)); const aspectRatios = new Set(); platforms.forEach(p => { p.formats.forEach(f => aspectRatios.add(f.ratio)); }); document.getElementById('totalPlatforms').textContent = totalPlatforms; document.getElementById('totalFormats').textContent = totalFormats; document.getElementById('totalCodecs').textContent = codecs.size; document.getElementById('totalAspectRatios').textContent = aspectRatios.size; } // Render Platforms function renderPlatforms() { const container = document.getElementById('platformsList'); if (platforms.length === 0) { container.innerHTML = `

No platforms configured

Click "Add New Platform" to get started

`; return; } container.innerHTML = platforms.map(platform => `
${platform.name}
Key: ${platform.key}
Codec
${platform.codec}
Formats
${platform.formats.length} configurations
${platform.formats.map(format => ` `).join('')}
Aspect Ratio Resolution Bitrate Bitrate Range Audio
${format.ratio} ${format.size} ${format.bitrate} ${format.bitrate_min} - ${format.bitrate_max} ${format.audio}
`).join(''); } // Modal Functions function openModal(platform = null) { editingPlatformKey = platform ? platform.key : null; const modal = document.getElementById('platformModal'); const title = document.getElementById('modalTitle'); if (platform) { title.textContent = `Edit Platform: ${platform.name}`; populateForm(platform); } else { title.textContent = 'Add New Platform'; resetForm(); } modal.style.display = 'block'; } function closeModal() { document.getElementById('platformModal').style.display = 'none'; resetForm(); } function resetForm() { document.getElementById('platformForm').reset(); document.getElementById('formatsContainer').innerHTML = ''; editingPlatformKey = null; formatCounter = 0; // Add one default format config addFormatConfig(); } function populateForm(platform) { document.getElementById('platformKey').value = platform.key; document.getElementById('platformKey').disabled = true; // Don't allow key changes document.getElementById('platformName').value = platform.name; document.getElementById('platformCodec').value = platform.codec; document.getElementById('platformContainer').value = platform.container || 'mp4'; // Clear and populate formats document.getElementById('formatsContainer').innerHTML = ''; platform.formats.forEach(format => addFormatConfig(format)); } // Format Configuration Management function addFormatConfig(formatData = null) { const container = document.getElementById('formatsContainer'); const formatId = `format-${formatCounter++}`; const formatDiv = document.createElement('div'); formatDiv.className = 'format-config'; formatDiv.id = formatId; formatDiv.innerHTML = `
Format Configuration ${formatCounter}
`; container.appendChild(formatDiv); } function removeFormat(formatId) { const formatDiv = document.getElementById(formatId); if (formatDiv) { formatDiv.remove(); } } // Save Platform async function savePlatform(e) { e.preventDefault(); const platformKey = document.getElementById('platformKey').value.trim().toLowerCase(); const platformName = document.getElementById('platformName').value.trim(); const codec = document.getElementById('platformCodec').value; const container = document.getElementById('platformContainer').value; // Collect all format configurations const formatConfigs = []; const formatDivs = document.querySelectorAll('.format-config'); formatDivs.forEach(div => { const format = { ratio: div.querySelector('.ratio-input').value.trim(), size: div.querySelector('.size-input').value.trim(), bitrate: div.querySelector('.bitrate-input').value.trim(), bitrate_min: div.querySelector('.bitrate-min-input').value.trim(), bitrate_max: div.querySelector('.bitrate-max-input').value.trim(), audio: div.querySelector('.audio-input').value.trim() }; const audioCodec = div.querySelector('.audio-codec-input').value.trim(); const note = div.querySelector('.note-input').value.trim(); if (audioCodec) format.audio_codec = audioCodec; if (note) format.note = note; formatConfigs.push(format); }); if (formatConfigs.length === 0) { showMessage('Please add at least one format configuration', 'error'); return; } const platformData = { key: platformKey, name: platformName, codec: codec, container: container, formats: formatConfigs }; try { const url = editingPlatformKey ? `${API_BASE}/admin/platforms/${editingPlatformKey}` : `${API_BASE}/admin/platforms`; const method = editingPlatformKey ? 'PUT' : 'POST'; const response = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(platformData) }); const result = await response.json(); if (result.success) { showMessage(`Platform ${editingPlatformKey ? 'updated' : 'added'} successfully!`, 'success'); closeModal(); loadPlatforms(); } else { showMessage('Error: ' + result.error, 'error'); } } catch (error) { showMessage('Error saving platform: ' + error.message, 'error'); } } // Edit Platform function editPlatform(platformKey) { const platform = platforms.find(p => p.key === platformKey); if (platform) { openModal(platform); } } // Delete Platform async function deletePlatform(platformKey) { if (!confirm(`Are you sure you want to delete platform "${platformKey}"?`)) { return; } try { const response = await fetch(`${API_BASE}/admin/platforms/${platformKey}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showMessage('Platform deleted successfully!', 'success'); loadPlatforms(); } else { showMessage('Error: ' + result.error, 'error'); } } catch (error) { showMessage('Error deleting platform: ' + error.message, 'error'); } } // Export/Import Functions async function exportSpecs() { try { const response = await fetch(`${API_BASE}/admin/export`); const data = await response.json(); if (data.success) { const blob = new Blob([JSON.stringify(data.specs, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `platform_specs_${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); showMessage('Specifications exported successfully!', 'success'); } else { showMessage('Error exporting: ' + data.error, 'error'); } } catch (error) { showMessage('Error exporting specs: ' + error.message, 'error'); } } async function importSpecs(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = async (event) => { try { const specs = JSON.parse(event.target.result); const response = await fetch(`${API_BASE}/admin/import`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ specs: specs }) }); const result = await response.json(); if (result.success) { showMessage('Specifications imported successfully!', 'success'); loadPlatforms(); } else { showMessage('Error importing: ' + result.error, 'error'); } } catch (error) { showMessage('Error importing specs: ' + error.message, 'error'); } }; reader.readAsText(file); e.target.value = ''; // Reset file input } // Reset to Factory Defaults async function resetToFactory() { const confirmed = confirm( '⚠️ WARNING: This will DELETE all custom platforms and restore the original 8 platforms (21 configurations) from the L\'Oréal documentation.\n\n' + 'This action cannot be undone unless you have exported your current specifications.\n\n' + 'Do you want to continue?' ); if (!confirmed) return; // Double confirmation for safety const doubleConfirm = confirm( 'Are you ABSOLUTELY SURE?\n\n' + 'All custom platforms will be permanently deleted.\n\n' + 'Click OK to reset to factory defaults.' ); if (!doubleConfirm) return; try { const response = await fetch(`${API_BASE}/admin/reset-factory`, { method: 'POST' }); const result = await response.json(); if (result.success) { showMessage(`✅ Reset successful! Restored ${result.platforms_count} original platforms.`, 'success'); loadPlatforms(); } else { showMessage('Error resetting: ' + result.error, 'error'); } } catch (error) { showMessage('Error resetting to factory: ' + error.message, 'error'); } } // UI Helpers function showMessage(message, type = 'success') { const existingMessage = document.querySelector('.message'); if (existingMessage) { existingMessage.remove(); } const messageDiv = document.createElement('div'); messageDiv.className = `message ${type}`; messageDiv.textContent = message; const container = document.querySelector('.container'); container.insertBefore(messageDiv, container.firstChild.nextSibling); setTimeout(() => { messageDiv.remove(); }, 5000); } // ============================================================================ // NAMING CONVENTIONS // ============================================================================ // Load Naming Conventions async function loadNamingConventions() { try { const response = await fetch(`${API_BASE}/admin/naming-conventions`); const data = await response.json(); if (data.success) { platformPatterns = data.platform_patterns || {}; aspectRatioPatterns = data.aspect_ratio_patterns || {}; renderPlatformPatterns(); renderAspectRatioPatterns(); } } catch (error) { console.error('Error loading naming conventions:', error); } } // Render Platform Patterns function renderPlatformPatterns() { const container = document.getElementById('platformPatternsList'); container.innerHTML = ''; Object.keys(platformPatterns).forEach(platformKey => { const patterns = platformPatterns[platformKey]; patterns.forEach((pattern, index) => { addPlatformPatternRow(platformKey, pattern, index); }); }); // Add empty row for new pattern if (platforms.length > 0 && Object.keys(platformPatterns).length === 0) { addPlatformPatternRow(); } } // Render Aspect Ratio Patterns function renderAspectRatioPatterns() { const container = document.getElementById('aspectRatioPatternsList'); container.innerHTML = ''; Object.keys(aspectRatioPatterns).forEach(ratio => { const patterns = aspectRatioPatterns[ratio]; patterns.forEach((pattern, index) => { addAspectRatioPatternRow(ratio, pattern, index); }); }); // Add empty row for new pattern if (Object.keys(aspectRatioPatterns).length === 0) { addAspectRatioPatternRow(); } } // Add Platform Pattern Row function addPlatformPatternRow(platformKey = '', pattern = '', index = 0) { const container = document.getElementById('platformPatternsList'); const itemId = `platform-pattern-${Date.now()}-${index}`; const itemDiv = document.createElement('div'); itemDiv.className = 'pattern-item'; itemDiv.id = itemId; itemDiv.innerHTML = ` `; container.appendChild(itemDiv); } // Add Aspect Ratio Pattern Row function addAspectRatioPatternRow(ratio = '', pattern = '', index = 0) { const container = document.getElementById('aspectRatioPatternsList'); const itemId = `ratio-pattern-${Date.now()}-${index}`; const itemDiv = document.createElement('div'); itemDiv.className = 'pattern-item'; itemDiv.id = itemId; itemDiv.innerHTML = ` `; container.appendChild(itemDiv); } // Add Pattern Button Handlers function addPlatformPattern() { addPlatformPatternRow(); } function addAspectRatioPattern() { addAspectRatioPatternRow(); } // Remove Pattern Item function removePatternItem(itemId) { const item = document.getElementById(itemId); if (item) { item.remove(); } } // Save Naming Conventions async function saveNamingConventions() { try { // Collect platform patterns const newPlatformPatterns = {}; const platformItems = document.querySelectorAll('#platformPatternsList .pattern-item'); platformItems.forEach(item => { const platformKey = item.querySelector('.platform-select-pattern').value; const pattern = item.querySelector('.platform-pattern-value').value.trim(); if (platformKey && pattern) { if (!newPlatformPatterns[platformKey]) { newPlatformPatterns[platformKey] = []; } newPlatformPatterns[platformKey].push(pattern); } }); // Collect aspect ratio patterns const newAspectRatioPatterns = {}; const ratioItems = document.querySelectorAll('#aspectRatioPatternsList .pattern-item'); ratioItems.forEach(item => { const ratio = item.querySelector('.aspect-ratio-select').value.trim(); const pattern = item.querySelector('.aspect-ratio-pattern-value').value.trim(); if (ratio && pattern) { if (!newAspectRatioPatterns[ratio]) { newAspectRatioPatterns[ratio] = []; } newAspectRatioPatterns[ratio].push(pattern); } }); // Save to backend const response = await fetch(`${API_BASE}/admin/naming-conventions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ platform_patterns: newPlatformPatterns, aspect_ratio_patterns: newAspectRatioPatterns }) }); const result = await response.json(); if (result.success) { platformPatterns = newPlatformPatterns; aspectRatioPatterns = newAspectRatioPatterns; showMessage('Naming conventions saved successfully!', 'success'); } else { showMessage('Error saving: ' + result.error, 'error'); } } catch (error) { showMessage('Error saving naming conventions: ' + error.message, 'error'); } } // Test Naming Patterns function testNaming() { const filename = prompt('Enter a filename to test pattern detection:'); if (!filename) return; let detectedPlatform = null; let detectedRatio = null; // Test platform patterns Object.keys(platformPatterns).forEach(platformKey => { platformPatterns[platformKey].forEach(pattern => { if (filename.toLowerCase().includes(pattern.toLowerCase())) { detectedPlatform = platformKey; } }); }); // Test aspect ratio patterns Object.keys(aspectRatioPatterns).forEach(ratio => { aspectRatioPatterns[ratio].forEach(pattern => { if (filename.toLowerCase().includes(pattern.toLowerCase())) { detectedRatio = ratio; } }); }); const message = `Filename: "${filename}"\n\n` + `Detected Platform: ${detectedPlatform || 'None'}\n` + `Detected Aspect Ratio: ${detectedRatio || 'None'}`; alert(message); }