783 lines
28 KiB
JavaScript
783 lines
28 KiB
JavaScript
// Admin Panel JavaScript
|
|
// API Configuration is loaded from config.js (API_BASE is available globally)
|
|
|
|
// State
|
|
let platforms = [];
|
|
let editingPlatformKey = null;
|
|
let formatCounter = 0;
|
|
let platformPatterns = {};
|
|
let aspectRatioPatterns = {};
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadPlatforms();
|
|
loadNamingConventions();
|
|
setupEventListeners();
|
|
loadBoxStatus();
|
|
loadBoxHistory();
|
|
});
|
|
|
|
// 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);
|
|
|
|
// Box automation history
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
document.getElementById('boxHistoryDate').value = today;
|
|
document.getElementById('boxRefreshBtn').addEventListener('click', () => {
|
|
loadBoxStatus();
|
|
loadBoxHistory();
|
|
});
|
|
document.getElementById('boxLast7Btn').addEventListener('click', () => loadBoxHistory(7));
|
|
document.getElementById('boxHistoryDate').addEventListener('change', () => loadBoxHistory());
|
|
}
|
|
|
|
// 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}')">×</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);
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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 = `
|
|
<select class="select-input pattern-select platform-select-pattern">
|
|
<option value="">Select Platform...</option>
|
|
${platforms.map(p => `<option value="${p.key}" ${p.key === platformKey ? 'selected' : ''}>${p.name}</option>`).join('')}
|
|
</select>
|
|
<input type="text" class="text-input pattern-input platform-pattern-value" placeholder="e.g., _tiktok_, _tt_" value="${pattern}">
|
|
<button type="button" class="pattern-remove" onclick="removePatternItem('${itemId}')">×</button>
|
|
`;
|
|
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 = `
|
|
<input type="text" class="text-input pattern-select aspect-ratio-select" placeholder="e.g., 16:9" value="${ratio}">
|
|
<input type="text" class="text-input pattern-input aspect-ratio-pattern-value" placeholder="e.g., _16x9_, _landscape_" value="${pattern}">
|
|
<button type="button" class="pattern-remove" onclick="removePatternItem('${itemId}')">×</button>
|
|
`;
|
|
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);
|
|
}
|
|
|
|
// ============================================================
|
|
// BOX AUTOMATION HISTORY
|
|
// ============================================================
|
|
|
|
async function loadBoxStatus() {
|
|
const indicator = document.getElementById('boxStatusIndicator');
|
|
const title = document.getElementById('boxStatusTitle');
|
|
const sub = document.getElementById('boxStatusSub');
|
|
const inEl = document.getElementById('boxFolderIn');
|
|
const successEl = document.getElementById('boxFolderSuccess');
|
|
const failedEl = document.getElementById('boxFolderFailed');
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/box/health`);
|
|
const data = await res.json();
|
|
|
|
if (!data.box_available) {
|
|
indicator.className = 'box-status-indicator offline';
|
|
title.textContent = 'Box SDK not installed';
|
|
sub.textContent = 'Run: pip install boxsdk[jwt]';
|
|
return;
|
|
}
|
|
|
|
if (!data.box_initialised) {
|
|
indicator.className = 'box-status-indicator warning';
|
|
title.textContent = 'Box not initialised';
|
|
sub.textContent = 'Check BOX_VIDEO_OPTIMIZER_FOLDER_ID in .env';
|
|
return;
|
|
}
|
|
|
|
indicator.className = 'box-status-indicator online';
|
|
const mode = data.polling_enabled
|
|
? `Polling every ${data.polling_interval_seconds}s`
|
|
: 'Webhook mode';
|
|
title.textContent = `Connected — ${mode}`;
|
|
sub.textContent = `Folders configured: ${data.folders_configured ? '✓' : '✗'}`;
|
|
|
|
const folders = data.folders || {};
|
|
inEl.textContent = folders['IN'] ? `ID: ${folders['IN']}` : '—';
|
|
successEl.textContent = folders['OUT_SUCCESS'] ? `ID: ${folders['OUT_SUCCESS']}` : '—';
|
|
failedEl.textContent = folders['OUT_FAILED'] ? `ID: ${folders['OUT_FAILED']}` : '—';
|
|
|
|
} catch (err) {
|
|
indicator.className = 'box-status-indicator offline';
|
|
title.textContent = 'Cannot reach backend';
|
|
sub.textContent = err.message;
|
|
}
|
|
}
|
|
|
|
async function loadBoxHistory(days = null) {
|
|
const tbody = document.getElementById('boxHistoryBody');
|
|
const summaryRow = document.getElementById('boxSummaryRow');
|
|
const summaryText = document.getElementById('boxSummaryText');
|
|
tbody.innerHTML = '<tr><td colspan="8" class="box-empty">Loading...</td></tr>';
|
|
summaryRow.style.display = 'none';
|
|
|
|
try {
|
|
let url;
|
|
if (days) {
|
|
url = `${API_BASE}/admin/box-history?days=${days}`;
|
|
} else {
|
|
const date = document.getElementById('boxHistoryDate').value;
|
|
url = `${API_BASE}/admin/box-history?date=${date}`;
|
|
}
|
|
|
|
const res = await fetch(url);
|
|
const data = await res.json();
|
|
const jobs = data.jobs || [];
|
|
|
|
if (jobs.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="8" class="box-empty">No Box jobs found for this period.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
// Summary
|
|
const successful = jobs.filter(j => j.status === 'success').length;
|
|
const failed = jobs.filter(j => j.status !== 'success').length;
|
|
const label = days ? `Last ${data.days} days` : data.date;
|
|
summaryText.textContent = `${label} — ${jobs.length} job${jobs.length !== 1 ? 's' : ''}: ${successful} succeeded, ${failed} failed`;
|
|
summaryRow.style.display = 'block';
|
|
|
|
// Rows
|
|
tbody.innerHTML = jobs.map(job => {
|
|
const time = new Date(job.timestamp).toLocaleString('en-GB', {
|
|
day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit'
|
|
});
|
|
const inputMb = job.input_file_size ? (job.input_file_size / 1048576).toFixed(1) + ' MB' : '—';
|
|
const outputMb = job.output_file_size ? (job.output_file_size / 1048576).toFixed(1) + ' MB' : '—';
|
|
const reduction = job.size_reduction_percent != null ? job.size_reduction_percent.toFixed(1) + '%' : '—';
|
|
const duration = job.conversion_duration_seconds != null ? job.conversion_duration_seconds.toFixed(0) + 's' : '—';
|
|
const platform = (job.platform || '—').toUpperCase();
|
|
const ratio = job.aspect_ratio || '—';
|
|
|
|
const badgeClass = job.status === 'success' ? 'success'
|
|
: job.status === 'skipped' ? 'skipped'
|
|
: 'failure';
|
|
|
|
return `<tr>
|
|
<td>${time}</td>
|
|
<td>${platform}</td>
|
|
<td>${ratio}</td>
|
|
<td>${inputMb}</td>
|
|
<td>${outputMb}</td>
|
|
<td>${reduction}</td>
|
|
<td>${duration}</td>
|
|
<td><span class="box-badge ${badgeClass}">${job.status}</span></td>
|
|
</tr>`;
|
|
}).join('');
|
|
|
|
} catch (err) {
|
|
tbody.innerHTML = `<tr><td colspan="8" class="box-empty">Error loading history: ${err.message}</td></tr>`;
|
|
}
|
|
}
|