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:
506
app/Views/tld-registry/import-logs.twig
Normal file
506
app/Views/tld-registry/import-logs.twig
Normal file
@@ -0,0 +1,506 @@
|
||||
{% extends 'layout/base.twig' %}
|
||||
|
||||
{% set title = 'TLD Import Logs' %}
|
||||
{% set pageTitle = 'TLD Import Logs' %}
|
||||
{% set pageDescription = 'History of TLD registry import operations' %}
|
||||
{% set pageIcon = 'fas fa-history' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{# Header with Actions #}
|
||||
<div class="mb-4 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Import Logs</h1>
|
||||
<p class="text-gray-600 dark:text-slate-400 mt-1">History of TLD registry import operations</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="/tld-registry" class="inline-flex items-center px-4 py-2.5 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 Registry
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Statistics Cards #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
{# Total Imports Card #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-5 hover:shadow-md transition-shadow duration-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">Total Imports</p>
|
||||
<p class="text-2xl font-semibold text-gray-900 dark:text-white mt-1">{{ importStats.total_imports|default(0) }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-50 dark:bg-blue-500/10 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-download text-blue-600 dark:text-blue-400 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Successful Imports Card #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-5 hover:shadow-md transition-shadow duration-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">Successful</p>
|
||||
<p class="text-2xl font-semibold text-gray-900 dark:text-white mt-1">{{ importStats.successful_imports|default(0) }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-green-50 dark:bg-green-500/10 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-check-circle text-green-600 dark:text-green-400 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Failed Imports Card #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-5 hover:shadow-md transition-shadow duration-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">Failed</p>
|
||||
<p class="text-2xl font-semibold text-gray-900 dark:text-white mt-1">{{ importStats.failed_imports|default(0) }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-red-50 dark:bg-red-500/10 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-times-circle text-red-600 dark:text-red-400 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Last Import Card #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-5 hover:shadow-md transition-shadow duration-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">Last Import</p>
|
||||
<p class="text-sm font-semibold text-gray-900 dark:text-white mt-1">
|
||||
{% if importStats.last_import is not empty %}
|
||||
{{ importStats.last_import|date('M j, H:i') }}
|
||||
{% else %}
|
||||
Never
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-indigo-50 dark:bg-indigo-500/10 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-clock text-indigo-600 dark:text-indigo-400 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Import Logs Table #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
{% if imports is not empty %}
|
||||
{# Table View (Desktop) #}
|
||||
<div class="hidden lg:block overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-slate-700">
|
||||
<thead class="bg-gray-50 dark:bg-slate-900">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase tracking-wider">Import Type</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase tracking-wider">Status</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase tracking-wider">Results</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase tracking-wider">Publication Date</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase tracking-wider">Started</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-slate-800 divide-y divide-gray-200 dark:divide-slate-700">
|
||||
{% set typeIcons = {
|
||||
tld_list: 'fa-list',
|
||||
rdap: 'fa-database',
|
||||
whois: 'fa-server',
|
||||
complete_workflow: 'fa-tasks',
|
||||
check_updates: 'fa-sync-alt',
|
||||
manual: 'fa-hand-pointer'
|
||||
} %}
|
||||
{% set typeLabels = {
|
||||
tld_list: 'TLD List',
|
||||
rdap: 'RDAP Servers',
|
||||
whois: 'WHOIS Data',
|
||||
complete_workflow: 'Complete Workflow',
|
||||
check_updates: 'Update Check',
|
||||
manual: 'Manual Import'
|
||||
} %}
|
||||
{% set typeDescriptions = {
|
||||
tld_list: 'IANA TLD list import',
|
||||
rdap: 'RDAP server bootstrap data',
|
||||
whois: 'WHOIS server & registry URLs',
|
||||
complete_workflow: 'Full import (TLD List → RDAP → WHOIS)',
|
||||
check_updates: 'IANA update verification',
|
||||
manual: 'Manual data import'
|
||||
} %}
|
||||
{% for import in imports %}
|
||||
{% set icon = typeIcons[import.import_type]|default('fa-file-import') %}
|
||||
{% set label = typeLabels[import.import_type]|default(import.import_type|capitalize) %}
|
||||
{% set description = typeDescriptions[import.import_type]|default('Import operation') %}
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors duration-150"
|
||||
data-import-id="{{ import.id }}"
|
||||
data-import-data="{{ import|json_encode|e('html_attr') }}">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 h-10 w-10 bg-primary bg-opacity-10 rounded-lg flex items-center justify-center">
|
||||
<i class="fas {{ icon }} text-primary"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="text-sm font-semibold text-gray-900 dark:text-white">{{ label }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-slate-400">{{ description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
{% if import.status == 'completed' %}
|
||||
{% set statusClass = 'bg-green-100 dark:bg-green-500/10 text-green-700 dark:text-green-400 border-green-200 dark:border-green-500/30' %}
|
||||
{% set statusIcon = 'fa-check-circle' %}
|
||||
{% set statusText = 'Completed' %}
|
||||
{% elseif import.status == 'failed' %}
|
||||
{% set statusClass = 'bg-red-100 dark:bg-red-500/10 text-red-700 dark:text-red-400 border-red-200 dark:border-red-500/30' %}
|
||||
{% set statusIcon = 'fa-times-circle' %}
|
||||
{% set statusText = 'Failed' %}
|
||||
{% else %}
|
||||
{% set statusClass = 'bg-yellow-100 dark:bg-yellow-500/10 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-500/30' %}
|
||||
{% set statusIcon = 'fa-clock' %}
|
||||
{% set statusText = 'In Progress' %}
|
||||
{% endif %}
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold border {{ statusClass }}">
|
||||
<i class="fas {{ statusIcon }} mr-1"></i>
|
||||
{{ statusText }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="text-sm text-gray-900 dark:text-white">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex items-center">
|
||||
<i class="fas fa-globe text-gray-400 dark:text-slate-500 mr-1"></i>
|
||||
{{ import.total_tlds }} total
|
||||
</span>
|
||||
<span class="flex items-center text-green-600 dark:text-green-400">
|
||||
<i class="fas fa-plus mr-1"></i>
|
||||
{{ import.new_tlds }} new
|
||||
</span>
|
||||
<span class="flex items-center text-blue-600 dark:text-blue-400">
|
||||
<i class="fas fa-sync mr-1"></i>
|
||||
{{ import.updated_tlds }} updated
|
||||
</span>
|
||||
{% if import.failed_tlds > 0 %}
|
||||
<span class="flex items-center text-red-600 dark:text-red-400">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>
|
||||
{{ import.failed_tlds }} failed
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-slate-400">
|
||||
{% if import.iana_publication_date %}
|
||||
<div class="flex items-center">
|
||||
<i class="far fa-calendar mr-2"></i>
|
||||
{{ import.iana_publication_date|date('M j, Y') }}
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-gray-400 dark:text-slate-500">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-slate-400">
|
||||
<div class="flex items-center">
|
||||
<i class="far fa-clock mr-2"></i>
|
||||
{{ import.started_at|date('M j, H:i') }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<button onclick="showImportDetails({{ import.id }})" class="text-primary hover:text-primary-dark">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{# Card View (Mobile) #}
|
||||
<div class="lg:hidden divide-y divide-gray-200 dark:divide-slate-700">
|
||||
{% for import in imports %}
|
||||
{% set icon = typeIcons[import.import_type]|default('fa-file-import') %}
|
||||
{% set label = typeLabels[import.import_type]|default(import.import_type|capitalize) %}
|
||||
<div class="p-6 hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors duration-150"
|
||||
data-import-id="{{ import.id }}"
|
||||
data-import-data="{{ import|json_encode|e('html_attr') }}">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center">
|
||||
<div class="w-10 h-10 bg-primary bg-opacity-10 rounded-lg flex items-center justify-center mr-3">
|
||||
<i class="fas {{ icon }} text-primary"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">{{ label }}</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">{{ import.started_at|date('M j, Y H:i') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% if import.status == 'completed' %}
|
||||
{% set mobileStatusClass = 'bg-green-100 dark:bg-green-500/10 text-green-700 dark:text-green-400' %}
|
||||
{% set mobileStatusIcon = 'fa-check-circle' %}
|
||||
{% set mobileStatusText = 'Completed' %}
|
||||
{% elseif import.status == 'failed' %}
|
||||
{% set mobileStatusClass = 'bg-red-100 dark:bg-red-500/10 text-red-700 dark:text-red-400' %}
|
||||
{% set mobileStatusIcon = 'fa-times-circle' %}
|
||||
{% set mobileStatusText = 'Failed' %}
|
||||
{% else %}
|
||||
{% set mobileStatusClass = 'bg-yellow-100 dark:bg-yellow-500/10 text-yellow-700 dark:text-yellow-400' %}
|
||||
{% set mobileStatusIcon = 'fa-clock' %}
|
||||
{% set mobileStatusText = 'In Progress' %}
|
||||
{% endif %}
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold {{ mobileStatusClass }}">
|
||||
<i class="fas {{ mobileStatusIcon }} mr-1"></i>
|
||||
{{ mobileStatusText }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-slate-400">Total TLDs:</span>
|
||||
<span class="font-semibold text-gray-900 dark:text-white">{{ import.total_tlds }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-slate-400">New:</span>
|
||||
<span class="font-semibold text-green-600 dark:text-green-400">{{ import.new_tlds }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-slate-400">Updated:</span>
|
||||
<span class="font-semibold text-blue-600 dark:text-blue-400">{{ import.updated_tlds }}</span>
|
||||
</div>
|
||||
{% if import.failed_tlds > 0 %}
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-slate-400">Failed:</span>
|
||||
<span class="font-semibold text-red-600 dark:text-red-400">{{ import.failed_tlds }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-2 mt-3">
|
||||
<button onclick="showImportDetails({{ import.id }})" class="flex-1 px-3 py-1.5 bg-blue-50 dark:bg-blue-500/10 text-blue-600 dark:text-blue-400 rounded text-center text-sm hover:bg-blue-100 dark:hover:bg-blue-500/20 transition-colors">
|
||||
<i class="fas fa-eye mr-1"></i> Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# Pagination #}
|
||||
{% if pagination.total_pages > 1 %}
|
||||
<div class="bg-white dark:bg-slate-800 px-4 py-3 flex items-center justify-between border-t border-gray-200 dark:border-slate-700 sm:px-6">
|
||||
<div class="flex-1 flex justify-between sm:hidden">
|
||||
{% if pagination.current_page > 1 %}
|
||||
<a href="?page={{ pagination.current_page - 1 }}"
|
||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-slate-600 text-sm font-medium rounded-md text-gray-700 dark:text-slate-300 bg-white dark:bg-slate-800 hover:bg-gray-50 dark:hover:bg-slate-700">
|
||||
Previous
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if pagination.current_page < pagination.total_pages %}
|
||||
<a href="?page={{ pagination.current_page + 1 }}"
|
||||
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-slate-600 text-sm font-medium rounded-md text-gray-700 dark:text-slate-300 bg-white dark:bg-slate-800 hover:bg-gray-50 dark:hover:bg-slate-700">
|
||||
Next
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-700 dark:text-slate-300">
|
||||
Showing <span class="font-medium">{{ pagination.showing_from }}</span> to
|
||||
<span class="font-medium">{{ pagination.showing_to }}</span> of
|
||||
<span class="font-medium">{{ pagination.total }}</span> results
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
|
||||
{% for i in 1..pagination.total_pages %}
|
||||
<a href="?page={{ i }}"
|
||||
class="relative inline-flex items-center px-4 py-2 border text-sm font-medium {{ i == pagination.current_page ? 'z-10 bg-primary border-primary text-white' : 'bg-white dark:bg-slate-800 border-gray-300 dark:border-slate-600 text-gray-500 dark:text-slate-400 hover:bg-gray-50 dark:hover:bg-slate-700' }} {{ i == 1 ? 'rounded-l-md' : '' }} {{ i == pagination.total_pages ? 'rounded-r-md' : '' }}">
|
||||
{{ i }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center py-12 px-6">
|
||||
<div class="mb-4">
|
||||
<i class="fas fa-history text-gray-300 dark:text-slate-600 text-6xl"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-slate-300 mb-1">No Import Logs</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400 mb-4">No TLD imports have been performed yet.</p>
|
||||
<a href="/tld-registry" class="inline-flex items-center px-5 py-2.5 bg-primary text-white text-sm rounded-lg hover:bg-primary-dark transition-colors font-medium">
|
||||
<i class="fas fa-arrow-left mr-2"></i>
|
||||
Back to Registry
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Import Details Modal #}
|
||||
<div id="importDetailsModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border border-gray-200 dark:border-slate-700 w-11/12 md:w-3/4 lg:w-1/2 shadow-lg rounded-md bg-white dark:bg-slate-800">
|
||||
<div class="mt-3">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Import Details</h3>
|
||||
<button onclick="closeImportDetails()" class="text-gray-400 dark:text-slate-500 hover:text-gray-600 dark:hover:text-slate-300">
|
||||
<i class="fas fa-times text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="importDetailsContent" class="text-sm text-gray-600 dark:text-slate-400">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showImportDetails(importId) {
|
||||
const importData = findImportData(importId);
|
||||
|
||||
if (!importData) {
|
||||
document.getElementById('importDetailsContent').innerHTML = `
|
||||
<div class="text-center text-gray-500 dark:text-slate-400">
|
||||
<p>Import details not found</p>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('importDetailsModal').classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const typeLabels = {
|
||||
'tld_list': 'TLD List',
|
||||
'rdap': 'RDAP Servers',
|
||||
'whois': 'WHOIS Data',
|
||||
'complete_workflow': 'Complete Workflow',
|
||||
'check_updates': 'Update Check',
|
||||
'manual': 'Manual Import'
|
||||
};
|
||||
|
||||
const typeDescriptions = {
|
||||
'tld_list': 'IANA TLD list import',
|
||||
'rdap': 'RDAP server bootstrap data',
|
||||
'whois': 'WHOIS server & registry URLs',
|
||||
'complete_workflow': 'Full import (TLD List → RDAP → WHOIS)',
|
||||
'check_updates': 'IANA update verification',
|
||||
'manual': 'Manual data import'
|
||||
};
|
||||
|
||||
const typeLabel = typeLabels[importData.import_type] || importData.import_type;
|
||||
const typeDescription = typeDescriptions[importData.import_type] || 'Import operation';
|
||||
|
||||
let duration = 'Unknown';
|
||||
if (importData.started_at && importData.completed_at) {
|
||||
const start = new Date(importData.started_at);
|
||||
const end = new Date(importData.completed_at);
|
||||
const diffMs = end - start;
|
||||
const minutes = Math.floor(diffMs / 60000);
|
||||
const seconds = Math.floor((diffMs % 60000) / 1000);
|
||||
|
||||
if (diffMs < 5000 && importData.import_type === 'complete_workflow') {
|
||||
const estimatedSeconds = Math.round((importData.total_tlds || 0) * 1.1);
|
||||
const estMinutes = Math.floor(estimatedSeconds / 60);
|
||||
const estSeconds = estimatedSeconds % 60;
|
||||
duration = `~${estMinutes} minutes ${estSeconds} seconds (estimated)`;
|
||||
} else if (minutes === 0 && seconds === 0) {
|
||||
duration = 'Less than 1 second';
|
||||
} else {
|
||||
duration = `${minutes} minutes ${seconds} seconds`;
|
||||
}
|
||||
}
|
||||
|
||||
let statusClass = 'bg-gray-100 dark:bg-slate-700 text-gray-800 dark:text-slate-200';
|
||||
let statusText = 'Unknown';
|
||||
if (importData.status === 'completed') {
|
||||
statusClass = 'bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400';
|
||||
statusText = 'Completed';
|
||||
} else if (importData.status === 'failed') {
|
||||
statusClass = 'bg-red-100 dark:bg-red-500/10 text-red-800 dark:text-red-400';
|
||||
statusText = 'Failed';
|
||||
} else if (importData.status === 'running') {
|
||||
statusClass = 'bg-yellow-100 dark:bg-yellow-500/10 text-yellow-800 dark:text-yellow-400';
|
||||
statusText = 'Running';
|
||||
}
|
||||
|
||||
document.getElementById('importDetailsContent').innerHTML = `
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between">
|
||||
<span class="font-medium text-gray-900 dark:text-white">Import ID:</span>
|
||||
<span class="text-gray-700 dark:text-slate-300">${importData.id}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="font-medium text-gray-900 dark:text-white">Type:</span>
|
||||
<span class="text-gray-700 dark:text-slate-300">${typeLabel}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="font-medium text-gray-900 dark:text-white">Description:</span>
|
||||
<span class="text-gray-600 dark:text-slate-400">${typeDescription}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="font-medium text-gray-900 dark:text-white">Status:</span>
|
||||
<span class="px-2 py-1 rounded text-xs ${statusClass}">${statusText}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="font-medium text-gray-900 dark:text-white">Duration:</span>
|
||||
<span class="text-gray-700 dark:text-slate-300">${duration}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="font-medium text-gray-900 dark:text-white">Started:</span>
|
||||
<span class="text-gray-700 dark:text-slate-300">${new Date(importData.started_at).toLocaleString()}</span>
|
||||
</div>
|
||||
${importData.completed_at ? `
|
||||
<div class="flex justify-between">
|
||||
<span class="font-medium text-gray-900 dark:text-white">Completed:</span>
|
||||
<span class="text-gray-700 dark:text-slate-300">${new Date(importData.completed_at).toLocaleString()}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
${importData.iana_publication_date ? `
|
||||
<div class="flex justify-between">
|
||||
<span class="font-medium text-gray-900 dark:text-white">IANA Publication:</span>
|
||||
<span class="text-gray-700 dark:text-slate-300">${importData.iana_publication_date}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="mt-4">
|
||||
<h4 class="font-medium text-gray-900 dark:text-white mb-2">Import Results:</h4>
|
||||
<div class="bg-gray-100 dark:bg-slate-900 p-3 rounded text-xs font-mono space-y-1 text-gray-800 dark:text-slate-300">
|
||||
<div>Total TLDs: ${importData.total_tlds || 0}</div>
|
||||
<div>New TLDs: ${importData.new_tlds || 0}</div>
|
||||
<div>Updated TLDs: ${importData.updated_tlds || 0}</div>
|
||||
<div>Failed TLDs: ${importData.failed_tlds || 0}</div>
|
||||
${importData.error_message ? `
|
||||
<div class="text-red-600 dark:text-red-400 mt-2">
|
||||
<strong>Error:</strong> ${importData.error_message}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('importDetailsModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function findImportData(importId) {
|
||||
const importRows = document.querySelectorAll('tr[data-import-id]');
|
||||
for (let row of importRows) {
|
||||
if (row.getAttribute('data-import-id') == importId) {
|
||||
return JSON.parse(row.getAttribute('data-import-data'));
|
||||
}
|
||||
}
|
||||
|
||||
const importCards = document.querySelectorAll('[data-import-id]');
|
||||
for (let card of importCards) {
|
||||
if (card.getAttribute('data-import-id') == importId) {
|
||||
return JSON.parse(card.getAttribute('data-import-data'));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function closeImportDetails() {
|
||||
document.getElementById('importDetailsModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('importDetailsModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeImportDetails();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user