Skip to content

Instantly share code, notes, and snippets.

@chrisastley
Created June 6, 2025 14:20
Show Gist options
  • Save chrisastley/61d78dba0bc0cfd06649683dd0ff12d2 to your computer and use it in GitHub Desktop.
Save chrisastley/61d78dba0bc0cfd06649683dd0ff12d2 to your computer and use it in GitHub Desktop.
Magento Console save script
// 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