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:
378
app/Views/dashboard/index.twig
Normal file
378
app/Views/dashboard/index.twig
Normal file
@@ -0,0 +1,378 @@
|
||||
{% extends 'layout/base.twig' %}
|
||||
|
||||
{% set title = 'Dashboard' %}
|
||||
{% set pageTitle = 'Dashboard Overview' %}
|
||||
{% set pageDescription = 'Monitor your domains and expiration dates' %}
|
||||
{% set pageIcon = 'fas fa-chart-line' %}
|
||||
|
||||
{% set topRegistrars = registrarCounts|default({})|slice(0, 8) %}
|
||||
{% set topTags = dashTags|default([])|filter(t => t.usage_count|default(0) > 0)|slice(0, 8) %}
|
||||
{% set domainsWithoutGroup = totalDomainCount|default(0) - domainsWithGroup|default(0) %}
|
||||
{% set totalGroupCount = groups|default([])|length %}
|
||||
|
||||
{% block content %}
|
||||
{# Welcome Banner #}
|
||||
<div class="mb-4 bg-gradient-to-r from-primary to-blue-600 rounded-lg p-4 text-white relative overflow-hidden">
|
||||
<div class="absolute right-0 top-0 w-48 h-48 opacity-10">
|
||||
<i class="fas fa-globe text-[150px] -rotate-12 -translate-y-6 translate-x-6"></i>
|
||||
</div>
|
||||
<div class="relative z-10">
|
||||
<h2 class="text-lg font-bold">Welcome back, {{ auth.fullName|default(auth.username)|default('User') }}!</h2>
|
||||
<p class="text-blue-100 mt-1 text-xs">
|
||||
{% if domainStats.expiring_soon|default(0) > 0 %}
|
||||
You have <span class="font-semibold text-white">{{ domainStats.expiring_soon }}</span> domain{{ domainStats.expiring_soon > 1 ? 's' : '' }} expiring within {{ domainStats.expiring_threshold|default(30) }} days.
|
||||
{% else %}
|
||||
All your domains are in good standing. No urgent actions needed.
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<a href="/domains/create" class="inline-flex items-center px-3 py-1.5 bg-white/20 hover:bg-white/30 backdrop-blur-sm rounded-lg text-xs font-medium transition-colors">
|
||||
<i class="fas fa-plus mr-1.5 text-xs"></i>Add Domain
|
||||
</a>
|
||||
<a href="/debug/whois" class="inline-flex items-center px-3 py-1.5 bg-white/10 hover:bg-white/20 backdrop-blur-sm rounded-lg text-xs font-medium transition-colors">
|
||||
<i class="fas fa-search mr-1.5 text-xs"></i>WHOIS Lookup
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if auth.isAdmin %}
|
||||
{# System Status Bar (Admin) #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 px-5 py-3 mb-4">
|
||||
<div class="flex flex-wrap items-center gap-4 sm:gap-6">
|
||||
<span class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide flex items-center">
|
||||
<i class="fas fa-server text-gray-400 dark:text-slate-500 mr-2"></i>
|
||||
System Status
|
||||
</span>
|
||||
<div class="flex flex-wrap items-center gap-3 sm:gap-5 text-sm">
|
||||
{% set statusColors = {'green': 'text-green-600 dark:text-green-400', 'yellow': 'text-yellow-600 dark:text-yellow-400', 'red': 'text-red-600 dark:text-red-400', 'gray': 'text-gray-600 dark:text-slate-400'} %}
|
||||
{% set statusDots = {'green': 'bg-green-500', 'yellow': 'bg-yellow-500', 'red': 'bg-red-500', 'gray': 'bg-gray-400'} %}
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="w-2 h-2 rounded-full {{ statusDots[systemStatus.database.color] }}"></span>
|
||||
<span class="text-gray-500 dark:text-slate-400">Database</span>
|
||||
<span class="{{ statusColors[systemStatus.database.color] }} font-medium">{{ systemStatus.database.status|capitalize }}</span>
|
||||
</span>
|
||||
<span class="text-gray-200 dark:text-slate-700 hidden sm:inline">|</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="w-2 h-2 rounded-full {{ statusDots[systemStatus.whois.color] }}"></span>
|
||||
<span class="text-gray-500 dark:text-slate-400">TLD Registry</span>
|
||||
<span class="{{ statusColors[systemStatus.whois.color] }} font-medium">{{ systemStatus.whois.status|capitalize }}</span>
|
||||
</span>
|
||||
<span class="text-gray-200 dark:text-slate-700 hidden sm:inline">|</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="w-2 h-2 rounded-full {{ statusDots[systemStatus.notifications.color] }}"></span>
|
||||
<span class="text-gray-500 dark:text-slate-400">Notifications</span>
|
||||
<span class="{{ statusColors[systemStatus.notifications.color] }} font-medium">{{ systemStatus.notifications.status|capitalize }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Statistics Cards #}
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||
{# Total Domains Card #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-4 hover:shadow-md dark:hover:shadow-slate-700/50 transition-all duration-200">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">Total Domains</p>
|
||||
<p class="text-2xl font-bold text-gray-900 dark:text-white mt-1.5">{{ domainStats.total|default(0) }}</p>
|
||||
</div>
|
||||
<div class="w-9 h-9 bg-blue-100 dark:bg-blue-500/20 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-globe text-blue-600 dark:text-blue-400 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2.5 pt-2.5 border-t border-gray-100 dark:border-slate-700">
|
||||
<a href="/domains" class="text-xs text-primary dark:text-blue-400 hover:underline font-medium">
|
||||
View all <i class="fas fa-arrow-right ml-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Active Domains Card #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-4 hover:shadow-md dark:hover:shadow-slate-700/50 transition-all duration-200">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">Active</p>
|
||||
<p class="text-2xl font-bold text-green-600 dark:text-green-400 mt-1.5">{{ domainStats.active|default(0) }}</p>
|
||||
</div>
|
||||
<div class="w-9 h-9 bg-green-100 dark:bg-green-500/20 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-check-circle text-green-600 dark:text-green-400 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2.5 pt-2.5 border-t border-gray-100 dark:border-slate-700">
|
||||
{% if domainStats.total|default(0) > 0 %}
|
||||
{% set activePercent = ((domainStats.active|default(0) / domainStats.total) * 100)|round %}
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400">{{ activePercent }}% of total</span>
|
||||
{% else %}
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400">No domains yet</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Expiring Soon Card #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-4 hover:shadow-md dark:hover:shadow-slate-700/50 transition-all duration-200 {{ domainStats.expiring_soon|default(0) > 0 ? 'ring-2 ring-orange-500/50' : '' }}">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">Expiring Soon</p>
|
||||
<p class="text-2xl font-bold {{ domainStats.expiring_soon|default(0) > 0 ? 'text-orange-600 dark:text-orange-400' : 'text-gray-900 dark:text-white' }} mt-1.5">{{ domainStats.expiring_soon|default(0) }}</p>
|
||||
</div>
|
||||
<div class="w-9 h-9 {{ domainStats.expiring_soon|default(0) > 0 ? 'bg-orange-100 dark:bg-orange-500/20' : 'bg-gray-100 dark:bg-slate-700' }} rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-clock {{ domainStats.expiring_soon|default(0) > 0 ? 'text-orange-600 dark:text-orange-400' : 'text-gray-400 dark:text-slate-500' }} text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2.5 pt-2.5 border-t border-gray-100 dark:border-slate-700">
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400">within {{ domainStats.expiring_threshold|default(30) }} days</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Inactive Domains Card #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-4 hover:shadow-md dark:hover:shadow-slate-700/50 transition-all duration-200">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">Inactive</p>
|
||||
<p class="text-2xl font-bold text-gray-400 dark:text-slate-500 mt-1.5">{{ domainStats.inactive|default(0) }}</p>
|
||||
</div>
|
||||
<div class="w-9 h-9 bg-gray-100 dark:bg-slate-700 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-pause-circle text-gray-400 dark:text-slate-500 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2.5 pt-2.5 border-t border-gray-100 dark:border-slate-700">
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400">monitoring paused</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Main Content: Recent Domains + Expiring Soon #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
|
||||
{# Recent Domains #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-200 dark:border-slate-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<i class="fas fa-clock text-gray-400 dark:text-slate-500 mr-2 text-xs"></i>
|
||||
Recent Domains
|
||||
</h2>
|
||||
<a href="/domains" class="text-xs text-primary hover:text-primary-dark font-medium">
|
||||
View all <i class="fas fa-arrow-right ml-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
{% if recentDomains is not empty %}
|
||||
<div class="space-y-2">
|
||||
{% for domain in recentDomains %}
|
||||
<div class="flex items-center justify-between p-3 border border-gray-100 dark:border-slate-700 rounded-lg hover:border-gray-300 dark:hover:border-slate-600 hover:shadow-sm transition-all duration-200">
|
||||
<div class="flex items-center space-x-3 flex-1 min-w-0">
|
||||
<div class="w-9 h-9 bg-gray-50 dark:bg-slate-700 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas fa-globe text-gray-400 dark:text-slate-500 text-sm"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white truncate">{{ domain.domain_name }}</h3>
|
||||
<div class="flex items-center space-x-3 text-xs text-gray-500 dark:text-slate-400 mt-0.5">
|
||||
<span class="flex items-center">
|
||||
<i class="far fa-calendar mr-1"></i>
|
||||
{{ domain.expiration_date ? domain.expiration_date|date('M d, Y') : 'Not set' }}
|
||||
</span>
|
||||
{% if domain.registrar %}
|
||||
<span class="flex items-center truncate">
|
||||
<i class="fas fa-building mr-1"></i>
|
||||
{{ domain.registrar }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 flex-shrink-0">
|
||||
<span class="px-2 py-1 rounded text-xs font-medium {{ domain.statusClass }}">
|
||||
{{ domain.statusText }}
|
||||
</span>
|
||||
<a href="/domains/{{ domain.id }}" class="text-gray-400 dark:text-slate-500 hover:text-primary">
|
||||
<i class="fas fa-chevron-right text-sm"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-8">
|
||||
<i class="fas fa-globe text-gray-300 dark:text-slate-600 text-4xl mb-3"></i>
|
||||
<p class="text-sm text-gray-600 dark:text-slate-400">No domains added yet</p>
|
||||
<a href="/domains/create" class="mt-3 inline-flex items-center px-4 py-2 bg-primary text-white text-sm rounded-lg hover:bg-primary-dark transition-colors duration-200">
|
||||
<i class="fas fa-plus mr-2"></i>
|
||||
Add Your First Domain
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Expiring Soon #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-200 dark:border-slate-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<i class="fas fa-exclamation-triangle text-orange-500 mr-2 text-xs"></i>
|
||||
Expiring Soon
|
||||
</h2>
|
||||
{% if expiringCount|default(0) > 5 %}
|
||||
<a href="/domains?status=expiring_soon" class="text-xs text-primary hover:text-primary-dark font-medium">
|
||||
View all {{ expiringCount }}
|
||||
<i class="fas fa-arrow-right ml-1"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if expiringThisMonth is not empty %}
|
||||
<div class="p-4 space-y-2">
|
||||
{% for domain in expiringThisMonth %}
|
||||
{% set daysLeft = domain.daysLeft %}
|
||||
{% if daysLeft <= 7 %}
|
||||
{% set urgencyClass = 'text-red-600 dark:text-red-400' %}
|
||||
{% elseif daysLeft <= 30 %}
|
||||
{% set urgencyClass = 'text-orange-600 dark:text-orange-400' %}
|
||||
{% else %}
|
||||
{% set urgencyClass = 'text-yellow-600 dark:text-yellow-400' %}
|
||||
{% endif %}
|
||||
<div class="flex items-center justify-between p-3 border border-gray-100 dark:border-slate-700 rounded-lg hover:border-gray-300 dark:hover:border-slate-600 hover:shadow-sm transition-all duration-200">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white truncate">{{ domain.domain_name }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">
|
||||
{{ domain.expiration_date ? domain.expiration_date|date('M d, Y') : 'Unknown' }}
|
||||
<span class="{{ urgencyClass }} font-semibold ml-2">
|
||||
{{ daysLeft }} days
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<a href="/domains/{{ domain.id }}" class="text-gray-400 dark:text-slate-500 hover:text-primary">
|
||||
<i class="fas fa-chevron-right text-sm"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-6 text-center">
|
||||
<i class="fas fa-check-circle text-green-500 text-3xl mb-2"></i>
|
||||
<p class="text-sm text-gray-600 dark:text-slate-400">No domains expiring soon</p>
|
||||
<p class="text-xs text-gray-400 dark:text-slate-500 mt-1">within {{ domainStats.expiring_threshold|default(30) }} days</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Insights Row #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
{# Registrar Distribution #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-200 dark:border-slate-700 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<i class="fas fa-building text-gray-400 dark:text-slate-500 mr-2 text-xs"></i>
|
||||
Registrar Distribution
|
||||
</h2>
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400">{{ registrarCounts|default({})|length }} registrar{{ registrarCounts|default({})|length != 1 ? 's' : '' }}</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
{% if topRegistrars is not empty %}
|
||||
<div class="space-y-3">
|
||||
{% for regName, regCount in topRegistrars %}
|
||||
{% set regPct = totalDomainCount|default(0) > 0 ? ((regCount / totalDomainCount) * 100)|round : 0 %}
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-sm text-gray-700 dark:text-slate-300 font-medium truncate mr-3">{{ regName }}</span>
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400 whitespace-nowrap">{{ regCount }} ({{ regPct }}%)</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-100 dark:bg-slate-700 rounded-full h-1.5">
|
||||
<div class="bg-blue-500 rounded-full h-1.5" style="width: {{ max(2, regPct) }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-building text-gray-300 dark:text-slate-600 text-2xl mb-2"></i>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">No registrar data</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Tag Usage #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-200 dark:border-slate-700 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<i class="fas fa-tags text-gray-400 dark:text-slate-500 mr-2 text-xs"></i>
|
||||
Tag Usage
|
||||
</h2>
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400">{{ dashTags|default([])|length }} tag{{ dashTags|default([])|length != 1 ? 's' : '' }}</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
{% if topTags is not empty %}
|
||||
<div class="space-y-3">
|
||||
{% for tt in topTags %}
|
||||
{% set pct = totalDomainCount|default(0) > 0 ? ((tt.usage_count / totalDomainCount) * 100)|round : 0 %}
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium border {{ tt.color|default('bg-gray-100 text-gray-700 border-gray-300') }}">
|
||||
<i class="fas fa-tag mr-1" style="font-size: 8px;"></i>
|
||||
{{ tt.name }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400">{{ tt.usage_count }} domain{{ tt.usage_count != 1 ? 's' : '' }} ({{ pct }}%)</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-100 dark:bg-slate-700 rounded-full h-1.5">
|
||||
<div class="bg-primary rounded-full h-1.5" style="width: {{ max(2, pct) }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-tags text-gray-300 dark:text-slate-600 text-2xl mb-2"></i>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">No tags in use</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Notification Coverage #}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-200 dark:border-slate-700 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<i class="fas fa-bell text-gray-400 dark:text-slate-500 mr-2 text-xs"></i>
|
||||
Notification Coverage
|
||||
</h2>
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400">{{ totalGroupCount }} group{{ totalGroupCount != 1 ? 's' : '' }}, {{ totalChannels|default(0) }} channel{{ totalChannels|default(0) != 1 ? 's' : '' }}</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
{% if totalDomainCount|default(0) > 0 %}
|
||||
{% set coveragePct = ((domainsWithGroup|default(0) / totalDomainCount) * 100)|round %}
|
||||
<div class="flex items-center justify-center mb-4">
|
||||
<div class="relative w-28 h-28">
|
||||
<svg class="w-28 h-28 transform -rotate-90" viewBox="0 0 36 36">
|
||||
<path class="text-gray-200 dark:text-slate-700" stroke="currentColor" stroke-width="3" fill="none" d="M18 2.0845a15.9155 15.9155 0 0 1 0 31.831 15.9155 15.9155 0 0 1 0-31.831"/>
|
||||
<path class="text-primary" stroke="currentColor" stroke-width="3" fill="none" stroke-dasharray="{{ coveragePct }}, 100" stroke-linecap="round" d="M18 2.0845a15.9155 15.9155 0 0 1 0 31.831 15.9155 15.9155 0 0 1 0-31.831"/>
|
||||
</svg>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<span class="text-xl font-bold text-gray-900 dark:text-white">{{ coveragePct }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3 text-center">
|
||||
<div class="bg-green-50 dark:bg-green-500/10 border border-green-200 dark:border-green-500/30 rounded-lg p-3">
|
||||
<p class="text-lg font-bold text-green-700 dark:text-green-400">{{ domainsWithGroup|default(0) }}</p>
|
||||
<p class="text-xs text-green-600 dark:text-green-500">With Notifications</p>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-slate-700 border border-gray-200 dark:border-slate-600 rounded-lg p-3">
|
||||
<p class="text-lg font-bold text-gray-700 dark:text-slate-300">{{ domainsWithoutGroup }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400">Without Notifications</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-bell-slash text-gray-300 dark:text-slate-600 text-2xl mb-2"></i>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">No domains to monitor</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user