Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>{% if title %}{{ title }}{% else %}Matax Express Admin Panel{% endif %}</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="preconnect" href="https://fonts.googleapis.com"> | |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code&display=swap" rel="stylesheet"> | |
<style> | |
body { | |
font-family: 'Inter', sans-serif; | |
background-color: #f8fafc; /* Lighter gray background */ | |
} | |
.link-card { | |
transition: transform 0.3s ease, box-shadow 0.3s ease; | |
background-color: white; | |
border: 1px solid #e2e8f0; | |
border-radius: 1rem; /* Slightly larger radius */ | |
box-shadow: 0 4px 12px rgba(0,0,0,0.05); | |
} | |
.link-card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 10px 25px rgba(0,0,0,0.08); | |
} | |
.code-block { | |
background: #1e293b; /* Dark slate background */ | |
border: 1px solid #334155; | |
border-radius: 0.75rem; | |
padding: 1rem 1.5rem; | |
font-family: 'Fira Code', monospace; | |
font-size: 0.9rem; | |
color: #cbd5e1; | |
overflow-x: auto; | |
box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
} | |
.code-block pre { | |
margin: 0; | |
white-space: pre-wrap; /* Allow wrapping */ | |
word-break: break-all; | |
} | |
.log-output { | |
background: #0f172a; /* Even darker for logs */ | |
color: #94a3b8; | |
font-family: 'Fira Code', monospace; | |
font-size: 0.8rem; | |
height: 200px; | |
overflow-y: auto; | |
border-radius: 0.5rem; | |
padding: 0.75rem; | |
border: 1px solid #334155; | |
} | |
.progress-bar-fill { | |
transition: width 0.4s ease-in-out; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 text-gray-800"> | |
<!-- Navbar --> | |
<nav class="bg-white shadow-sm sticky top-0 left-0 w-full z-10 border-b border-gray-200"> | |
<div class="max-w-7xl mx-auto px-6 py-4 flex justify-between items-center"> | |
<h1 class="text-2xl font-bold text-gray-900">Matax Express Admin Panel</h1> | |
<div class="space-x-6 text-gray-600"> | |
<a href="{{ url_for('zoho.index') }}" class="hover:text-blue-600 transition">Index</a> | |
</div> | |
</div> | |
</nav> | |
<!-- Content --> | |
<div class="max-w-7xl mx-auto px-6 pt-12 pb-10"> | |
<h2 class="text-3xl font-bold mb-8 text-gray-900">Quick Actions</h2> | |
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8"> | |
<!-- MODIFIED: Inventory Sync Card --> | |
<div class="link-card p-6 flex flex-col justify-between space-y-4 col-span-1 md:col-span-2"> | |
<div> | |
<h3 class="text-lg font-semibold text-gray-900 mb-2">Sync Inventory from Zoho</h3> | |
<p class="text-gray-500 text-sm">Update your website inventory directly from Zoho. This may take a few minutes.</p> | |
</div> | |
<!-- Sync Progress UI (Initially hidden) --> | |
<div id="sync-progress-container" class="hidden space-y-3 pt-2"> | |
<!-- Progress Bar --> | |
<div class="w-full bg-gray-200 rounded-full h-2.5"> | |
<div id="progress-bar" class="bg-blue-600 h-2.5 rounded-full progress-bar-fill" style="width: 0%"></div> | |
</div> | |
<!-- Status Message --> | |
<p id="sync-status-message" class="text-center text-sm font-medium text-gray-600">Initializing...</p> | |
<!-- Live Log --> | |
<div id="sync-log-output" class="log-output"></div> | |
</div> | |
<!-- Sync Button --> | |
<button id="start-sync-button" class="w-full mt-auto bg-blue-600 text-white font-semibold py-2.5 px-4 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-4 focus:ring-blue-300 transition-all duration-300 flex items-center justify-center space-x-2 disabled:bg-gray-400 disabled:cursor-not-allowed"> | |
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-8.707l-3-3a1 1 0 00-1.414 0l-3 3a1 1 0 001.414 1.414L9 9.414V13a1 1 0 102 0V9.414l1.293 1.293a1 1 0 001.414-1.414z" clip-rule="evenodd" /></svg> | |
<span>Sync Inventory</span> | |
</button> | |
</div> | |
<a href="/api/sync_xero_users" class="link-card p-6"> | |
<h3 class="text-lg font-semibold text-gray-900 mb-2">Sync Zoho Users</h3> | |
<p class="text-gray-500 text-sm">Keep your approved Zoho users up to date in the database.</p> | |
</a> | |
<a href="/api/sendmail" class="link-card p-6"> | |
<h3 class="text-lg font-semibold text-gray-900 mb-2">Send Reminder Emails</h3> | |
<p class="text-gray-500 text-sm">Notify users with pending items in their cart.</p> | |
</a> | |
<a href="/api/pages/update_ui" class="link-card p-6"> | |
<h3 class="text-lg font-semibold text-gray-900 mb-2">Update About UI</h3> | |
<p class="text-gray-500 text-sm">Revamp the look of the Contact and About Us pages.</p> | |
</a> | |
<a href="/api/pages/edit_homepage" class="link-card p-6"> | |
<h3 class="text-lg font-semibold text-gray-900 mb-2">Update Homepage UI</h3> | |
<p class="text-gray-500 text-sm">Edit the Faq and Why choose us page.This may take some time to take effect</p> | |
</a> | |
</div> | |
<!-- Output Block for final results --> | |
<div id="output-wrapper" class="mt-12 hidden"> | |
<h3 class="text-xl font-bold text-gray-800 mb-4">Sync Result Summary</h3> | |
<div class="code-block"> | |
<pre id="final-output-pre">{% block content %}{% endblock %}</pre> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
const startButton = document.getElementById('start-sync-button'); | |
const progressContainer = document.getElementById('sync-progress-container'); | |
const progressBar = document.getElementById('progress-bar'); | |
const statusMessage = document.getElementById('sync-status-message'); | |
const logOutput = document.getElementById('sync-log-output'); | |
const outputWrapper = document.getElementById('output-wrapper'); | |
const finalOutputPre = document.getElementById('final-output-pre'); | |
let eventSource; | |
startButton.addEventListener('click', function() { | |
// --- 1. Reset UI State --- | |
startButton.disabled = true; | |
startButton.querySelector('span').textContent = 'Syncing...'; | |
progressContainer.classList.remove('hidden'); | |
progressBar.style.width = '0%'; | |
progressBar.classList.remove('bg-green-500', 'bg-red-500'); | |
progressBar.classList.add('bg-blue-600'); | |
logOutput.innerHTML = ''; | |
statusMessage.textContent = 'Connecting to server...'; | |
outputWrapper.classList.add('hidden'); | |
finalOutputPre.textContent = ''; | |
addLogEntry('Initiating synchronization...', 'info'); | |
// --- 2. Start Server-Sent Events Connection --- | |
// IMPORTANT: Ensure this URL points to your actual streaming endpoint. | |
const eventSourceURL = "{{ url_for('zoho.fetch_inventory_stream') }}"; | |
eventSource = new EventSource(eventSourceURL); | |
eventSource.onopen = function() { | |
addLogEntry('Connection established. Starting sync process.', 'info'); | |
}; | |
eventSource.onmessage = function(event) { | |
const data = JSON.parse(event.data); | |
// Update Progress Bar | |
progressBar.style.width = data.progress + '%'; | |
// Update Status Message (show first line only for brevity) | |
statusMessage.textContent = data.message.split('\n')[0]; | |
// Add full message to log | |
addLogEntry(data.message, 'message'); | |
// Handle final states | |
if (data.status === 'complete' || data.status === 'error') { | |
eventSource.close(); | |
if (data.status === 'complete') { | |
handleSyncComplete(data); | |
} else { | |
handleSyncError(data); | |
} | |
} | |
}; | |
eventSource.onerror = function(err) { | |
console.error("EventSource failed:", err); | |
eventSource.close(); | |
const errorData = { message: 'Connection to the server was lost. Please check the console and try again.' }; | |
handleSyncError(errorData); | |
}; | |
}); | |
function handleSyncComplete(data) { | |
statusMessage.textContent = "Synchronization completed successfully!"; | |
progressBar.classList.remove('bg-blue-600'); | |
progressBar.classList.add('bg-green-500'); | |
addLogEntry('Sync complete!', 'success'); | |
if (data.final_code) { | |
finalOutputPre.textContent = data.final_code; | |
outputWrapper.classList.remove('hidden'); | |
} | |
resetButtonAfterDelay(); | |
} | |
function handleSyncError(data) { | |
statusMessage.textContent = "An error occurred during synchronization."; | |
progressBar.classList.remove('bg-blue-600'); | |
progressBar.classList.add('bg-red-500'); | |
addLogEntry(data.message, 'error'); | |
resetButtonAfterDelay(); | |
} | |
function addLogEntry(message, type) { | |
const timestamp = new Date().toLocaleTimeString(); | |
const entry = document.createElement('div'); | |
entry.classList.add('flex', 'items-start', 'space-x-2'); | |
let colorClass = 'text-gray-400'; | |
if (type === 'success') colorClass = 'text-green-400'; | |
if (type === 'error') colorClass = 'text-red-400'; | |
entry.innerHTML = ` | |
<span class="font-mono text-gray-500">[${timestamp}]</span> | |
<span class="flex-1 ${colorClass}">${message.replace(/\n/g, '<br>')}</span> | |
`; | |
logOutput.appendChild(entry); | |
logOutput.scrollTop = logOutput.scrollHeight; // Auto-scroll to bottom | |
} | |
function resetButtonAfterDelay() { | |
setTimeout(() => { | |
startButton.disabled = false; | |
startButton.querySelector('span').textContent = 'Sync Inventory'; | |
}, 2000); | |
} | |
}); | |
</script> | |
</body> | |
</html> |