loreal-video-optimizer/frontend/admin.js
DJP 300a67d247 Add Reset to Factory Defaults button with double confirmation
Features:
- Renamed "Reload from Server" to "Refresh Display" for clarity
- Added "Reset to Factory Defaults" button with warning icon
- Red danger button styling (matches warning theme)
- Double confirmation dialog to prevent accidental resets
- Backend endpoint to restore original 8 platforms (21 configurations)
- Deletes custom platform_specs.json file
- Restores L'Oréal documentation specifications

Functionality:
- "Refresh Display" - Reloads current specs from backend (no changes)
- "Reset to Factory Defaults" - Deletes ALL custom platforms and restores original 21 configs
- Custom platforms are saved until factory reset is triggered
- Factory defaults stored at server startup for restoration

Safety features:
- Two confirmation dialogs
- Clear warning messages
- Success feedback showing platform count restored

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 18:40:59 -04:00

445 lines
16 KiB
JavaScript

// 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;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
loadPlatforms();
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);
}
// 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 = `
<div class="empty-state">
<h3>No platforms configured</h3>
<p>Click "Add New Platform" to get started</p>
</div>
`;
return;
}
container.innerHTML = platforms.map(platform => `
<div class="platform-card">
<div class="platform-header">
<div>
<div class="platform-title">${platform.name}</div>
<div class="platform-key">Key: ${platform.key}</div>
</div>
<div class="platform-actions">
<button class="btn-icon" onclick="editPlatform('${platform.key}')">Edit</button>
<button class="btn-icon danger" onclick="deletePlatform('${platform.key}')">Delete</button>
</div>
</div>
<div class="platform-info">
<div class="info-badge">
<div class="info-badge-label">Codec</div>
<div class="info-badge-value">${platform.codec}</div>
</div>
<div class="info-badge">
<div class="info-badge-label">Formats</div>
<div class="info-badge-value">${platform.formats.length} configurations</div>
</div>
</div>
<table class="formats-table">
<thead>
<tr>
<th>Aspect Ratio</th>
<th>Resolution</th>
<th>Bitrate</th>
<th>Bitrate Range</th>
<th>Audio</th>
</tr>
</thead>
<tbody>
${platform.formats.map(format => `
<tr>
<td><strong>${format.ratio}</strong></td>
<td>${format.size}</td>
<td>${format.bitrate}</td>
<td>${format.bitrate_min} - ${format.bitrate_max}</td>
<td>${format.audio}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
`).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 = `
<div class="format-config-header">
<div class="format-config-title">Format Configuration ${formatCounter}</div>
<button type="button" class="btn-remove" onclick="removeFormat('${formatId}')">&times;</button>
</div>
<div class="format-fields">
<div class="form-group">
<label>Aspect Ratio *</label>
<input type="text" class="text-input ratio-input" placeholder="e.g., 16:9" value="${formatData?.ratio || ''}" required>
</div>
<div class="form-group">
<label>Resolution *</label>
<input type="text" class="text-input size-input" placeholder="e.g., 1920x1080" value="${formatData?.size || ''}" required>
</div>
<div class="form-group">
<label>Bitrate *</label>
<input type="text" class="text-input bitrate-input" placeholder="e.g., 1500k" value="${formatData?.bitrate || ''}" required>
</div>
<div class="form-group">
<label>Min Bitrate *</label>
<input type="text" class="text-input bitrate-min-input" placeholder="e.g., 1300k" value="${formatData?.bitrate_min || ''}" required>
</div>
<div class="form-group">
<label>Max Bitrate *</label>
<input type="text" class="text-input bitrate-max-input" placeholder="e.g., 1690k" value="${formatData?.bitrate_max || ''}" required>
</div>
<div class="form-group">
<label>Audio Bitrate *</label>
<input type="text" class="text-input audio-input" placeholder="e.g., 128k" value="${formatData?.audio || ''}" required>
</div>
<div class="form-group">
<label>Audio Codec</label>
<input type="text" class="text-input audio-codec-input" placeholder="e.g., aac" value="${formatData?.audio_codec || ''}">
</div>
<div class="form-group">
<label>Note (optional)</label>
<input type="text" class="text-input note-input" placeholder="e.g., Not tested" value="${formatData?.note || ''}">
</div>
</div>
`;
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);
}