Created
June 6, 2025 14:20
-
-
Save chrisastley/61d78dba0bc0cfd06649683dd0ff12d2 to your computer and use it in GitHub Desktop.
Magento Console save script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Separate Progress Window Version | |
// Opens progress stats in a separate window you can move to another monitor | |
(function() { | |
'use strict'; | |
const CONFIG = { | |
linkClass: 'edit-link', | |
processDelay: 7000, // Time to wait for each tab to process | |
maxRetries: 3, | |
enableFirst: true | |
}; | |
console.log('🔄 Auto-save script loaded on:', window.location.href); | |
// Check if we're on the report page | |
const isReportPage = document.querySelector('.' + CONFIG.linkClass) !== null; | |
if (isReportPage) { | |
console.log('📋 Detected report page - setting up injection-based processing...'); | |
setupInjectionBasedProcessing(); | |
} | |
function setupInjectionBasedProcessing() { | |
const links = document.querySelectorAll('.' + CONFIG.linkClass); | |
if (links.length === 0) { | |
alert('No links with class "' + CONFIG.linkClass + '" found.'); | |
return; | |
} | |
const urls = Array.from(links) | |
.map(link => link.href) | |
.filter(url => url && (url.includes('/catalog/product/edit/') || url.includes('/admin'))) | |
.filter((url, index, arr) => arr.indexOf(url) === index); | |
if (urls.length === 0) { | |
alert('No valid product edit URLs found.'); | |
return; | |
} | |
console.log('Found ' + urls.length + ' product URLs'); | |
const proceed = confirm('Found ' + urls.length + ' products to process.\n\nThis will open a separate progress window you can move to another monitor.\n\nStart processing?'); | |
if (!proceed) return; | |
// Initialize stats and reset stop flag | |
const stats = { | |
total: urls.length, | |
processed: 0, | |
enabled: 0, | |
saved: 0, | |
errors: 0, | |
startTime: Date.now(), | |
lastProductTime: Date.now(), | |
totalProcessingTime: 0 | |
}; | |
window.magentoAutoSaveStop = false; | |
window.magentoAutoSavePaused = false; | |
// Open separate progress window | |
openProgressWindow(stats); | |
// Start processing | |
processUrlsWithInjection(urls, 0, stats); | |
} | |
function openProgressWindow(stats) { | |
// Calculate window position (offset from main window) | |
const left = window.screenX + 50; | |
const top = window.screenY + 50; | |
// Open new window for progress | |
window.progressWindow = window.open( | |
'about:blank', | |
'magentoProgress', | |
`width=620,height=640,left=${left},top=${top},resizable=yes,scrollbars=no,status=no,menubar=no,toolbar=no` | |
); | |
if (!window.progressWindow) { | |
alert('Could not open progress window. Please allow pop-ups and try again.'); | |
return; | |
} | |
// Set up the progress window HTML | |
window.progressWindow.document.write(` | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Magento Auto-Processing Progress</title> | |
<style> | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; | |
margin: 0; | |
padding: 20px; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
color: white; | |
min-height: 100vh; | |
box-sizing: border-box; | |
} | |
.header { | |
text-align: center; | |
margin-bottom: 30px; | |
padding: 20px; | |
background: rgba(255,255,255,0.1); | |
border-radius: 15px; | |
backdrop-filter: blur(10px); | |
} | |
.title { | |
font-size: 24px; | |
font-weight: bold; | |
margin-bottom: 10px; | |
} | |
.controls { | |
display: flex; | |
gap: 10px; | |
justify-content: center; | |
margin-top: 15px; | |
} | |
.btn { | |
padding: 10px 20px; | |
border: none; | |
border-radius: 8px; | |
font-weight: bold; | |
cursor: pointer; | |
font-size: 14px; | |
transition: all 0.2s; | |
} | |
.btn:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 12px rgba(0,0,0,0.3); | |
} | |
.btn-pause { background: #ffc107; color: #000; } | |
.btn-stop { background: #dc3545; color: white; } | |
.btn-close { background: #6c757d; color: white; } | |
.stats-container { | |
background: rgba(255,255,255,0.1); | |
padding: 25px; | |
border-radius: 15px; | |
backdrop-filter: blur(10px); | |
margin-bottom: 20px; | |
} | |
.current-product { | |
font-size: 20px; | |
text-align: center; | |
margin-bottom: 20px; | |
font-weight: bold; | |
} | |
.progress-bar-container { | |
background: rgba(255,255,255,0.2); | |
height: 20px; | |
border-radius: 10px; | |
margin: 20px 0; | |
overflow: hidden; | |
} | |
.progress-bar { | |
height: 100%; | |
background: linear-gradient(90deg, #4CAF50, #45a049); | |
width: 0%; | |
transition: width 0.5s ease; | |
border-radius: 10px; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.2); | |
} | |
.stats-grid { | |
display: grid; | |
grid-template-columns: 1fr 1fr; | |
gap: 15px; | |
margin: 20px 0; | |
} | |
.stat-item { | |
text-align: center; | |
padding: 15px; | |
background: rgba(255,255,255,0.1); | |
border-radius: 10px; | |
} | |
.stat-value { | |
font-size: 24px; | |
font-weight: bold; | |
display: block; | |
} | |
.stat-label { | |
font-size: 12px; | |
opacity: 0.8; | |
margin-top: 5px; | |
} | |
.timing-info { | |
text-align: center; | |
font-size: 16px; | |
margin-top: 20px; | |
padding: 15px; | |
background: rgba(255,255,255,0.1); | |
border-radius: 10px; | |
} | |
.status-message { | |
text-align: center; | |
margin-top: 20px; | |
font-size: 14px; | |
opacity: 0.9; | |
font-style: italic; | |
} | |
.flash { | |
animation: flash 0.5s ease-in-out; | |
} | |
@keyframes flash { | |
0% { background: rgba(255,255,255,0.1); } | |
50% { background: rgba(255,255,0,0.3); } | |
100% { background: rgba(255,255,255,0.1); } | |
} | |
</style> | |
</head> | |
<body> | |
<div class="header"> | |
<div class="title">🔄 Magento Auto-Processing</div> | |
<div class="controls"> | |
<button id="pauseBtn" class="btn btn-pause">PAUSE</button> | |
<button id="stopBtn" class="btn btn-stop">STOP</button> | |
<button id="closeBtn" class="btn btn-close">CLOSE</button> | |
</div> | |
</div> | |
<div class="stats-container"> | |
<div id="currentProduct" class="current-product">Starting...</div> | |
<div class="progress-bar-container"> | |
<div id="progressBar" class="progress-bar"></div> | |
</div> | |
<div class="stats-grid"> | |
<div class="stat-item"> | |
<span id="processedCount" class="stat-value">0</span> | |
<div class="stat-label">PROCESSED</div> | |
</div> | |
<div class="stat-item"> | |
<span id="savedCount" class="stat-value">0</span> | |
<div class="stat-label">SAVED</div> | |
</div> | |
<div class="stat-item"> | |
<span id="enabledCount" class="stat-value">0</span> | |
<div class="stat-label">ENABLED</div> | |
</div> | |
<div class="stat-item"> | |
<span id="errorCount" class="stat-value">0</span> | |
<div class="stat-label">ERRORS</div> | |
</div> | |
</div> | |
<div id="timingInfo" class="timing-info"> | |
Rate: 0.0 products/min | ETA: calculating... | |
</div> | |
</div> | |
<div id="statusMessage" class="status-message"> | |
Processing products in background tabs... | |
</div> | |
</body> | |
</html> | |
`); | |
window.progressWindow.document.close(); | |
// Set up event handlers in the progress window | |
const progressDoc = window.progressWindow.document; | |
progressDoc.getElementById('pauseBtn').addEventListener('click', () => { | |
const btn = progressDoc.getElementById('pauseBtn'); | |
if (window.magentoAutoSavePaused) { | |
window.magentoAutoSavePaused = false; | |
btn.textContent = 'PAUSE'; | |
btn.className = 'btn btn-pause'; | |
progressDoc.getElementById('statusMessage').textContent = 'Processing products in background tabs...'; | |
console.log('▶️ Processing resumed by user'); | |
} else { | |
window.magentoAutoSavePaused = true; | |
btn.textContent = 'RESUME'; | |
btn.className = 'btn btn-stop'; | |
progressDoc.getElementById('statusMessage').textContent = 'Processing paused - click RESUME to continue'; | |
console.log('⏸️ Processing paused by user'); | |
} | |
}); | |
progressDoc.getElementById('stopBtn').addEventListener('click', () => { | |
if (confirm('Stop processing? This will halt the automation.')) { | |
window.magentoAutoSaveStop = true; | |
progressDoc.getElementById('statusMessage').textContent = 'Processing stopped by user'; | |
console.log('🛑 Processing stopped by user'); | |
} | |
}); | |
progressDoc.getElementById('closeBtn').addEventListener('click', () => { | |
window.progressWindow.close(); | |
}); | |
// Keep reference for updates | |
window.progressDoc = progressDoc; | |
console.log('✅ Progress window opened - you can now move it to another monitor!'); | |
} | |
function updateProgressWindow(current, stats) { | |
if (!window.progressWindow || window.progressWindow.closed) { | |
console.log('⚠️ Progress window was closed'); | |
return; | |
} | |
const doc = window.progressDoc; | |
if (!doc) return; | |
try { | |
// Update current product | |
doc.getElementById('currentProduct').textContent = `Product ${current} / ${stats.total}`; | |
// Update progress bar | |
const percentage = Math.round((current / stats.total) * 100); | |
doc.getElementById('progressBar').style.width = percentage + '%'; | |
// Update stats | |
doc.getElementById('processedCount').textContent = stats.processed; | |
doc.getElementById('savedCount').textContent = stats.saved; | |
doc.getElementById('enabledCount').textContent = stats.enabled; | |
doc.getElementById('errorCount').textContent = stats.errors; | |
// Update timing | |
if (stats.processed > 0) { | |
const elapsedTime = (Date.now() - stats.startTime) / 1000; | |
const rate = stats.processed / (elapsedTime / 60); | |
const remaining = stats.total - stats.processed; | |
const eta = remaining / rate; | |
doc.getElementById('timingInfo').textContent = `Rate: ${rate.toFixed(1)} products/min | ETA: ${Math.round(eta)}min`; | |
} | |
// Flash effect when product completes | |
const container = doc.querySelector('.stats-container'); | |
container.classList.add('flash'); | |
setTimeout(() => { | |
container.classList.remove('flash'); | |
}, 500); | |
} catch (e) { | |
console.log('Error updating progress window:', e); | |
} | |
} | |
function processUrlsWithInjection(urls, index, stats) { | |
// Check if user clicked stop | |
if (window.magentoAutoSaveStop) { | |
console.log('🛑 Processing stopped by user at product ' + (index + 1)); | |
if (window.progressWindow && !window.progressWindow.closed) { | |
window.progressDoc.getElementById('statusMessage').textContent = `Processing stopped. Processed ${stats.processed} out of ${stats.total} products.`; | |
} | |
setTimeout(() => { | |
alert('Processing stopped. Processed ' + stats.processed + ' out of ' + stats.total + ' products.'); | |
}, 1000); | |
return; | |
} | |
// Check if user clicked pause | |
if (window.magentoAutoSavePaused) { | |
console.log('⏸️ Processing paused at product ' + (index + 1)); | |
setTimeout(() => { | |
processUrlsWithInjection(urls, index, stats); | |
}, 1000); | |
return; | |
} | |
if (index >= urls.length) { | |
// All done! | |
const totalTime = (Date.now() - stats.startTime) / 1000; | |
const avgPerProduct = totalTime / stats.processed; | |
const productsPerMinute = stats.processed / (totalTime / 60); | |
const summary = `All products processed!\n\n📊 Summary:\n• Total: ${stats.total}\n• Processed: ${stats.processed}\n• Enabled: ${stats.enabled}\n• Saved: ${stats.saved}\n• Errors: ${stats.errors}\n\n⏱️ Performance:\n• Total time: ${Math.round(totalTime/60)}min ${Math.round(totalTime%60)}s\n• Average per product: ${avgPerProduct.toFixed(1)}s\n• Processing rate: ${productsPerMinute.toFixed(1)} products/minute`; | |
if (window.progressWindow && !window.progressWindow.closed) { | |
window.progressDoc.getElementById('statusMessage').textContent = 'All products completed! Check main window for summary.'; | |
} | |
alert(summary); | |
console.log('✅ Processing complete!', stats); | |
return; | |
} | |
const url = urls[index]; | |
console.log('🆕 Processing product ' + (index + 1) + '/' + urls.length); | |
// Update progress window | |
updateProgressWindow(index + 1, stats); | |
// Open tab normally | |
const newTab = window.open(url, '_blank'); | |
if (!newTab) { | |
alert('Pop-up blocked! Please allow pop-ups and try again.'); | |
return; | |
} | |
// Wait for tab to load completely, then inject script | |
setTimeout(() => { | |
if (window.magentoAutoSaveStop) { | |
try { newTab.close(); } catch (e) {} | |
return; | |
} | |
waitForTabToLoad(newTab, () => { | |
injectProcessingScript(newTab, index + 1, urls.length, (result) => { | |
// Calculate timing | |
const now = Date.now(); | |
const productTime = now - stats.lastProductTime; | |
stats.totalProcessingTime += productTime; | |
stats.lastProductTime = now; | |
// Handle result | |
if (result.completed) { | |
if (result.enabled) stats.enabled++; | |
if (result.saved) stats.saved++; | |
console.log('✅ Product ' + (index + 1) + ' processed successfully in ' + (productTime/1000).toFixed(1) + 's'); | |
} else { | |
stats.errors++; | |
console.log('❌ Product ' + (index + 1) + ' processing failed'); | |
} | |
stats.processed++; | |
// Update progress window | |
updateProgressWindow(index + 1, stats); | |
// Close tab and move to next | |
setTimeout(() => { | |
try { newTab.close(); } catch (e) {} | |
setTimeout(() => { | |
processUrlsWithInjection(urls, index + 1, stats); | |
}, 500); | |
}, 1000); | |
}); | |
}); | |
}, index === 0 ? 3000 : 2000); | |
} | |
function waitForTabToLoad(tabWindow, callback) { | |
let checkCount = 0; | |
const maxChecks = 50; | |
function checkTabReady() { | |
checkCount++; | |
try { | |
const isLoaded = tabWindow.document && | |
tabWindow.document.readyState === 'complete' && | |
tabWindow.document.querySelector('body'); | |
if (isLoaded) { | |
console.log('✅ Tab fully loaded, ready for injection'); | |
callback(); | |
} else if (checkCount >= maxChecks) { | |
console.log('⚠️ Tab load timeout, proceeding anyway'); | |
callback(); | |
} else { | |
setTimeout(checkTabReady, 200); | |
} | |
} catch (e) { | |
if (checkCount >= maxChecks) { | |
console.log('⚠️ Cannot check tab state, proceeding anyway'); | |
callback(); | |
} else { | |
setTimeout(checkTabReady, 200); | |
} | |
} | |
} | |
checkTabReady(); | |
} | |
function injectProcessingScript(tabWindow, current, total, callback) { | |
try { | |
const processingScript = ` | |
(function() { | |
console.log('🚀 Injected script running on product ${current}/${total}'); | |
let enabled = false; | |
let saved = false; | |
let completed = false; | |
function waitAndProcess() { | |
const isDocumentReady = document.readyState === 'complete'; | |
const hasBody = !!document.body; | |
const hasSaveButton = !!document.querySelector('#save-button'); | |
if (isDocumentReady && hasBody && hasSaveButton) { | |
console.log('✅ Page is fully ready, starting processing...'); | |
setTimeout(attemptProcessing, 500); | |
} else { | |
setTimeout(waitAndProcess, 300); | |
} | |
} | |
function attemptProcessing() { | |
try { | |
// Enable product | |
const enableResult = checkAndEnableProduct(); | |
if (enableResult.needed && enableResult.success) { | |
enabled = true; | |
console.log('✅ Product enabled'); | |
} | |
// Save product | |
const saveResult = attemptSave(); | |
if (saveResult.success) { | |
saved = true; | |
console.log('✅ Product saved'); | |
} | |
completed = true; | |
} catch (error) { | |
console.error('❌ Error during processing:', error); | |
completed = false; | |
} | |
window.processingResult = { | |
completed: completed, | |
enabled: enabled, | |
saved: saved, | |
timestamp: Date.now() | |
}; | |
console.log('🏁 Final processing result:', window.processingResult); | |
} | |
function checkAndEnableProduct() { | |
const toggleSelectors = [ | |
'input.admin__actions-switch-checkbox[name="product[status]"]', | |
'.admin__actions-switch input[name="product[status]"]' | |
]; | |
for (const selector of toggleSelectors) { | |
const toggleField = document.querySelector(selector); | |
if (toggleField) { | |
if (!toggleField.checked) { | |
toggleField.checked = true; | |
toggleField.dispatchEvent(new Event('change', { bubbles: true })); | |
toggleField.dispatchEvent(new Event('click', { bubbles: true })); | |
return { needed: true, success: true }; | |
} else { | |
return { needed: false, success: true }; | |
} | |
} | |
} | |
return { needed: false, success: true }; | |
} | |
function attemptSave() { | |
const selectors = ['#save-button', 'button[data-ui-id="save-button"]', 'button[title="Save"]']; | |
for (const selector of selectors) { | |
const button = document.querySelector(selector); | |
if (button && !button.disabled && button.offsetParent !== null) { | |
const text = button.textContent.trim().toLowerCase(); | |
if (text === 'save') { | |
button.click(); | |
return { attempted: true, success: true }; | |
} | |
} | |
} | |
return { attempted: false, success: false }; | |
} | |
waitAndProcess(); | |
})(); | |
`; | |
setTimeout(() => { | |
try { | |
tabWindow.eval(processingScript); | |
console.log('✅ Script injected into fully loaded product ' + current); | |
// Poll for results | |
let pollCount = 0; | |
const maxPolls = Math.floor(CONFIG.processDelay / 300); | |
function pollForResults() { | |
pollCount++; | |
try { | |
const result = tabWindow.processingResult; | |
if (result && result.completed !== undefined) { | |
console.log('📤 Retrieved result for product ' + current + ' after ' + (pollCount * 300) + 'ms:', result); | |
callback(result); | |
} else if (pollCount >= maxPolls) { | |
console.log('⏰ Timeout waiting for result from product ' + current); | |
callback({ completed: false, enabled: false, saved: false }); | |
} else { | |
setTimeout(pollForResults, 300); | |
} | |
} catch (e) { | |
if (pollCount >= maxPolls) { | |
callback({ completed: false, enabled: false, saved: false }); | |
} else { | |
setTimeout(pollForResults, 300); | |
} | |
} | |
} | |
setTimeout(pollForResults, 1000); | |
} catch (e) { | |
console.log('❌ Could not inject script into tab for product ' + current + ':', e); | |
callback({ completed: false, enabled: false, saved: false }); | |
} | |
}, 200); | |
} catch (e) { | |
console.log('❌ Error setting up injection:', e); | |
callback({ completed: false, enabled: false, saved: false }); | |
} | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment