Switch PHP views to Twig and add 2FA/UI enhancements
Migrate many view templates from raw PHP to Twig and modernize UI/UX for 2FA and settings. Controllers updated to provide avatar data and two-factor info (ProfileController, UserController) and SettingsController now includes timezone lists, notification preset selection, cron path, cached update state and rollback availability. ErrorHandler now attempts to render error pages via a new Core\TwigService with a safe fallback to raw PHP views. TwoFactorService generation silences deprecated warnings during QR code creation. Numerous .php view files were removed and replaced with .twig equivalents (2fa setup/verify/backup-codes and many auth, dashboard, domains, errors, layout, users, tags, tld-registry, etc.), and core/TwigService was added. These changes move the app toward a Twig-based templating system, improve 2FA flows, surface avatar images in lists/profiles, and make error rendering more robust.
This commit is contained in:
302
app/Views/tld-registry/import-progress.twig
Normal file
302
app/Views/tld-registry/import-progress.twig
Normal file
@@ -0,0 +1,302 @@
|
||||
{% extends 'layout/base.twig' %}
|
||||
|
||||
{% set title = title|default('Import Progress') %}
|
||||
{% set pageTitle = title %}
|
||||
{% set pageDescription = 'Progressive data import with real-time progress' %}
|
||||
{% set pageIcon = 'fas fa-tasks' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="max-w-4xl mx-auto">
|
||||
{# Header #}
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{ title }}</h1>
|
||||
<p class="text-gray-600 dark:text-slate-400 mt-1">
|
||||
{% set descriptions = {
|
||||
tld_list: 'Importing complete TLD list from IANA',
|
||||
rdap: 'Importing RDAP servers for existing TLDs',
|
||||
whois: 'Importing WHOIS & Registry data via RDAP API (with HTML fallback)',
|
||||
check_updates: 'Checking for IANA updates',
|
||||
complete_workflow: 'Complete TLD import workflow (TLD List → RDAP → WHOIS & Registry Data)'
|
||||
} %}
|
||||
{{ descriptions[import_type]|default('Processing import') }}
|
||||
</p>
|
||||
</div>
|
||||
<a href="/tld-registry" class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-slate-300 text-sm rounded-lg hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors font-medium">
|
||||
<i class="fas fa-arrow-left mr-2"></i>
|
||||
Back to TLD Registry
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Progress Card #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Import Status</h2>
|
||||
<div id="status-badge" class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 dark:bg-yellow-500/10 text-yellow-800 dark:text-yellow-400">
|
||||
<i class="fas fa-clock mr-2"></i>
|
||||
<span id="status-text">Starting...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Progress Bar #}
|
||||
<div class="mb-4">
|
||||
<div class="flex justify-between text-sm text-gray-600 dark:text-slate-400 mb-2">
|
||||
<span id="progress-text">0 of 0 items processed</span>
|
||||
<span id="percentage-text">0%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 dark:bg-slate-700 rounded-full h-3">
|
||||
<div id="progress-bar" class="bg-blue-600 h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Step Progress (for complete workflow) #}
|
||||
<div id="step-progress" class="mb-4" style="display: none;">
|
||||
<div class="text-sm text-gray-600 dark:text-slate-400 mb-2">Workflow Steps:</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="step-item bg-gray-100 dark:bg-slate-700 rounded-lg p-2 text-center">
|
||||
<div class="text-xs font-medium text-gray-600 dark:text-slate-400">Step 1</div>
|
||||
<div class="text-xs text-gray-500 dark:text-slate-400">TLD List</div>
|
||||
<div id="step-1-status" class="text-xs text-gray-400 dark:text-slate-500">Pending</div>
|
||||
</div>
|
||||
<div class="step-item bg-gray-100 dark:bg-slate-700 rounded-lg p-2 text-center">
|
||||
<div class="text-xs font-medium text-gray-600 dark:text-slate-400">Step 2</div>
|
||||
<div class="text-xs text-gray-500 dark:text-slate-400">RDAP</div>
|
||||
<div id="step-2-status" class="text-xs text-gray-400 dark:text-slate-500">Pending</div>
|
||||
</div>
|
||||
<div class="step-item bg-gray-100 dark:bg-slate-700 rounded-lg p-2 text-center">
|
||||
<div class="text-xs font-medium text-gray-600 dark:text-slate-400">Step 3</div>
|
||||
<div class="text-xs text-gray-500 dark:text-slate-400">WHOIS & Registry</div>
|
||||
<div id="step-3-status" class="text-xs text-gray-400 dark:text-slate-500">Pending</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Statistics #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div class="bg-gray-50 dark:bg-slate-900 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-10 h-10 bg-blue-100 dark:bg-blue-500/10 rounded-lg flex items-center justify-center mr-3">
|
||||
<i class="fas fa-list text-blue-600 dark:text-blue-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">Total</p>
|
||||
<p id="total-count" class="text-xl font-semibold text-gray-900 dark:text-white">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 dark:bg-slate-900 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-10 h-10 bg-green-100 dark:bg-green-500/10 rounded-lg flex items-center justify-center mr-3">
|
||||
<i class="fas fa-check text-green-600 dark:text-green-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">Processed</p>
|
||||
<p id="processed-count" class="text-xl font-semibold text-gray-900 dark:text-white">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 dark:bg-slate-900 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-10 h-10 bg-red-100 dark:bg-red-500/10 rounded-lg flex items-center justify-center mr-3">
|
||||
<i class="fas fa-times text-red-600 dark:text-red-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">Failed</p>
|
||||
<p id="failed-count" class="text-xl font-semibold text-gray-900 dark:text-white">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 dark:bg-slate-900 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-10 h-10 bg-orange-100 dark:bg-orange-500/10 rounded-lg flex items-center justify-center mr-3">
|
||||
<i class="fas fa-hourglass-half text-orange-600 dark:text-orange-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">Remaining</p>
|
||||
<p id="remaining-count" class="text-xl font-semibold text-gray-900 dark:text-white">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Log Output #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Import Log</h3>
|
||||
<div id="log-output" class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm h-64 overflow-y-auto">
|
||||
<div class="text-gray-500">Initializing import process...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let logId = {{ log_id|json_encode|raw }};
|
||||
let importType = {{ import_type|json_encode|raw }};
|
||||
let isComplete = false;
|
||||
let totalProcessed = 0;
|
||||
let totalFailed = 0;
|
||||
|
||||
if (importType === 'complete_workflow') {
|
||||
document.getElementById('step-progress').style.display = 'block';
|
||||
}
|
||||
|
||||
function addLogMessage(message, type = 'info') {
|
||||
const logOutput = document.getElementById('log-output');
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const colorClass = type === 'error' ? 'text-red-400' : type === 'success' ? 'text-green-400' : 'text-blue-400';
|
||||
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.className = colorClass;
|
||||
logEntry.innerHTML = `[${timestamp}] ${message}`;
|
||||
|
||||
logOutput.appendChild(logEntry);
|
||||
logOutput.scrollTop = logOutput.scrollHeight;
|
||||
}
|
||||
|
||||
function updateProgress(data) {
|
||||
const total = data.total || 0;
|
||||
const processed = data.processed || 0;
|
||||
const failed = data.failed || 0;
|
||||
const remaining = data.remaining || 0;
|
||||
|
||||
document.getElementById('total-count').textContent = total;
|
||||
document.getElementById('processed-count').textContent = processed;
|
||||
document.getElementById('failed-count').textContent = failed;
|
||||
document.getElementById('remaining-count').textContent = remaining;
|
||||
|
||||
const totalToProcess = processed + remaining;
|
||||
const percentage = totalToProcess > 0 ? Math.round((processed / totalToProcess) * 100) : 0;
|
||||
|
||||
document.getElementById('progress-bar').style.width = percentage + '%';
|
||||
document.getElementById('progress-text').textContent = `${processed} of ${totalToProcess} items processed`;
|
||||
document.getElementById('percentage-text').textContent = percentage + '%';
|
||||
|
||||
if (importType === 'complete_workflow' && data.message) {
|
||||
updateStepProgress(data.message, processed, total);
|
||||
}
|
||||
|
||||
const statusBadge = document.getElementById('status-badge');
|
||||
const statusText = document.getElementById('status-text');
|
||||
|
||||
if (data.status === 'complete') {
|
||||
statusBadge.className = 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400';
|
||||
statusText.innerHTML = '<i class="fas fa-check mr-2"></i>Complete';
|
||||
isComplete = true;
|
||||
|
||||
const completionMessage = data.message || 'Import completed successfully!';
|
||||
addLogMessage(completionMessage, 'success');
|
||||
|
||||
if (importType === 'complete_workflow') {
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
updateStepStatus(i, 'completed');
|
||||
}
|
||||
}
|
||||
} else if (data.status === 'in_progress') {
|
||||
statusBadge.className = 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400';
|
||||
statusText.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>In Progress';
|
||||
addLogMessage(data.message || 'Processing batch...', 'info');
|
||||
} else if (data.status === 'error') {
|
||||
statusBadge.className = 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 dark:bg-red-500/10 text-red-800 dark:text-red-400';
|
||||
statusText.innerHTML = '<i class="fas fa-exclamation-triangle mr-2"></i>Error';
|
||||
addLogMessage(data.message || 'An error occurred', 'error');
|
||||
isComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
function checkProgress() {
|
||||
if (isComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/tld-registry/api/import-progress?log_id=${logId}`)
|
||||
.then(response => {
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (!contentType || !contentType.includes('application/json')) {
|
||||
return response.text().then(text => {
|
||||
addLogMessage('Server returned non-JSON response. This might be a PHP error or session issue.', 'error');
|
||||
addLogMessage('Response preview: ' + text.substring(0, 200) + '...', 'error');
|
||||
isComplete = true;
|
||||
throw new Error('Non-JSON response received');
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
addLogMessage('Error: ' + data.error, 'error');
|
||||
isComplete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
updateProgress(data);
|
||||
|
||||
if (data.status !== 'complete' && data.status !== 'error') {
|
||||
setTimeout(checkProgress, 2000);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.message.includes('Gateway Timeout') || error.message.includes('timeout')) {
|
||||
addLogMessage('Gateway timeout detected. Retrying in 5 seconds...', 'warning');
|
||||
setTimeout(checkProgress, 5000);
|
||||
} else {
|
||||
addLogMessage('Network error: ' + error.message, 'error');
|
||||
isComplete = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateStepProgress(message, currentStep, totalSteps) {
|
||||
const stepMatch = message.match(/Step (\d+)\/(\d+)/);
|
||||
if (stepMatch) {
|
||||
const stepNumber = parseInt(stepMatch[1]);
|
||||
const totalSteps = parseInt(stepMatch[2]);
|
||||
|
||||
const isCompleted = message.toLowerCase().includes('completed');
|
||||
|
||||
if (isCompleted) {
|
||||
for (let i = 1; i <= stepNumber; i++) {
|
||||
updateStepStatus(i, 'completed');
|
||||
}
|
||||
|
||||
if (stepNumber < totalSteps) {
|
||||
updateStepStatus(stepNumber + 1, 'in_progress');
|
||||
}
|
||||
} else {
|
||||
for (let i = 1; i < stepNumber; i++) {
|
||||
updateStepStatus(i, 'completed');
|
||||
}
|
||||
|
||||
updateStepStatus(stepNumber, 'in_progress');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateStepStatus(stepNumber, status) {
|
||||
const stepElement = document.getElementById(`step-${stepNumber}-status`);
|
||||
const stepItem = stepElement.closest('.step-item');
|
||||
|
||||
if (status === 'completed') {
|
||||
stepElement.textContent = 'Completed';
|
||||
stepElement.className = 'text-xs text-green-600 dark:text-green-400';
|
||||
stepItem.className = 'step-item bg-green-100 dark:bg-green-500/10 rounded-lg p-2 text-center';
|
||||
} else if (status === 'in_progress') {
|
||||
stepElement.textContent = 'In Progress';
|
||||
stepElement.className = 'text-xs text-blue-600 dark:text-blue-400';
|
||||
stepItem.className = 'step-item bg-blue-100 dark:bg-blue-500/10 rounded-lg p-2 text-center';
|
||||
} else if (status === 'failed') {
|
||||
stepElement.textContent = 'Failed';
|
||||
stepElement.className = 'text-xs text-red-600 dark:text-red-400';
|
||||
stepItem.className = 'step-item bg-red-100 dark:bg-red-500/10 rounded-lg p-2 text-center';
|
||||
}
|
||||
}
|
||||
|
||||
checkProgress();
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user