From 4818172bc614054d5351ae04c85c5079d568ddc8 Mon Sep 17 00:00:00 2001 From: Hosteroid Date: Tue, 3 Mar 2026 18:21:32 +0200 Subject: [PATCH] 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. --- app/Controllers/ProfileController.php | 23 +- app/Controllers/SettingsController.php | 73 ++ app/Controllers/UserController.php | 18 + app/Services/ErrorHandler.php | 24 +- app/Services/TwoFactorService.php | 22 +- app/Views/2fa/backup-codes.php | 209 ---- app/Views/2fa/backup-codes.twig | 124 +++ app/Views/2fa/setup.php | 162 ---- app/Views/2fa/setup.twig | 116 +++ app/Views/2fa/verify.php | 206 ---- app/Views/2fa/verify.twig | 124 +++ ...aptcha-widget.php => _captcha-widget.twig} | 70 +- app/Views/auth/base-auth.php | 56 -- app/Views/auth/base-auth.twig | 209 ++++ app/Views/auth/forgot-password.php | 83 -- app/Views/auth/forgot-password.twig | 82 ++ app/Views/auth/{login.php => login.twig} | 90 +- .../auth/{register.php => register.twig} | 112 +-- ...reset-password.php => reset-password.twig} | 96 +- .../{verify-email.php => verify-email.twig} | 67 +- app/Views/dashboard/index.php | 347 ------- app/Views/dashboard/index.twig | 378 ++++++++ app/Views/debug/{whois.php => whois.twig} | 184 ++-- .../domains/{bulk-add.php => bulk-add.twig} | 169 ++-- app/Views/domains/{create.php => create.twig} | 102 +- app/Views/domains/{edit.php => edit.twig} | 169 ++-- app/Views/domains/{index.php => index.twig} | 771 +++++++-------- app/Views/domains/view.php | 446 --------- app/Views/domains/view.twig | 431 +++++++++ app/Views/errors/{404.php => 404.twig} | 10 +- app/Views/errors/{500.php => 500.twig} | 31 +- app/Views/errors/admin-detail.php | 572 ----------- app/Views/errors/admin-detail.twig | 482 ++++++++++ app/Views/errors/admin-index.php | 605 ------------ app/Views/errors/admin-index.twig | 481 ++++++++++ app/Views/errors/{debug.php => debug.twig} | 197 ++-- app/Views/groups/{create.php => create.twig} | 76 +- app/Views/groups/{edit.php => edit.twig} | 553 +++++------ app/Views/groups/{index.php => index.twig} | 378 ++++---- .../installer/{complete.php => complete.twig} | 14 +- .../installer/{update.php => update.twig} | 21 +- .../installer/{welcome.php => welcome.twig} | 14 +- app/Views/layout/{base.php => base.twig} | 292 +++--- app/Views/layout/messages.php | 119 --- app/Views/layout/messages.twig | 115 +++ app/Views/layout/sidebar.php | 136 --- app/Views/layout/sidebar.twig | 124 +++ app/Views/layout/top-nav.php | 396 -------- app/Views/layout/top-nav.twig | 373 +++++++ app/Views/notifications/index.php | 447 --------- app/Views/notifications/index.twig | 422 ++++++++ app/Views/profile/{index.php => index.twig} | 542 +++++------ app/Views/search/results.php | 300 ------ app/Views/search/results.twig | 279 ++++++ app/Views/settings/{index.php => index.twig} | 908 ++++++++---------- app/Views/tags/{index.php => index.twig} | 631 ++++++------ app/Views/tags/view.php | 417 -------- app/Views/tags/view.twig | 351 +++++++ app/Views/tld-registry/import-logs.php | 562 ----------- app/Views/tld-registry/import-logs.twig | 506 ++++++++++ ...port-progress.php => import-progress.twig} | 212 ++-- app/Views/tld-registry/index.php | 894 ----------------- app/Views/tld-registry/index.twig | 842 ++++++++++++++++ app/Views/tld-registry/view.php | 427 -------- app/Views/tld-registry/view.twig | 420 ++++++++ app/Views/users/{create.php => create.twig} | 147 ++- app/Views/users/{edit.php => edit.twig} | 177 ++-- app/Views/users/index.php | 559 ----------- app/Views/users/index.twig | 504 ++++++++++ app/Views/users/{show.php => show.twig} | 864 ++++++++--------- core/Controller.php | 11 +- core/Router.php | 7 +- core/TwigService.php | 253 +++++ 73 files changed, 9948 insertions(+), 10686 deletions(-) delete mode 100644 app/Views/2fa/backup-codes.php create mode 100644 app/Views/2fa/backup-codes.twig delete mode 100644 app/Views/2fa/setup.php create mode 100644 app/Views/2fa/setup.twig delete mode 100644 app/Views/2fa/verify.php create mode 100644 app/Views/2fa/verify.twig rename app/Views/auth/{captcha-widget.php => _captcha-widget.twig} (54%) delete mode 100644 app/Views/auth/base-auth.php create mode 100644 app/Views/auth/base-auth.twig delete mode 100644 app/Views/auth/forgot-password.php create mode 100644 app/Views/auth/forgot-password.twig rename app/Views/auth/{login.php => login.twig} (58%) rename app/Views/auth/{register.php => register.twig} (64%) rename app/Views/auth/{reset-password.php => reset-password.twig} (60%) rename app/Views/auth/{verify-email.php => verify-email.twig} (50%) delete mode 100644 app/Views/dashboard/index.php create mode 100644 app/Views/dashboard/index.twig rename app/Views/debug/{whois.php => whois.twig} (60%) rename app/Views/domains/{bulk-add.php => bulk-add.twig} (68%) rename app/Views/domains/{create.php => create.twig} (71%) rename app/Views/domains/{edit.php => edit.twig} (56%) rename app/Views/domains/{index.php => index.twig} (56%) delete mode 100644 app/Views/domains/view.php create mode 100644 app/Views/domains/view.twig rename app/Views/errors/{404.php => 404.twig} (93%) rename app/Views/errors/{500.php => 500.twig} (89%) delete mode 100644 app/Views/errors/admin-detail.php create mode 100644 app/Views/errors/admin-detail.twig delete mode 100644 app/Views/errors/admin-index.php create mode 100644 app/Views/errors/admin-index.twig rename app/Views/errors/{debug.php => debug.twig} (77%) rename app/Views/groups/{create.php => create.twig} (58%) rename app/Views/groups/{edit.php => edit.twig} (68%) rename app/Views/groups/{index.php => index.twig} (58%) rename app/Views/installer/{complete.php => complete.twig} (86%) rename app/Views/installer/{update.php => update.twig} (80%) rename app/Views/installer/{welcome.php => welcome.twig} (92%) rename app/Views/layout/{base.php => base.twig} (59%) delete mode 100644 app/Views/layout/messages.php create mode 100644 app/Views/layout/messages.twig delete mode 100644 app/Views/layout/sidebar.php create mode 100644 app/Views/layout/sidebar.twig delete mode 100644 app/Views/layout/top-nav.php create mode 100644 app/Views/layout/top-nav.twig delete mode 100644 app/Views/notifications/index.php create mode 100644 app/Views/notifications/index.twig rename app/Views/profile/{index.php => index.twig} (60%) delete mode 100644 app/Views/search/results.php create mode 100644 app/Views/search/results.twig rename app/Views/settings/{index.php => index.twig} (60%) rename app/Views/tags/{index.php => index.twig} (56%) delete mode 100644 app/Views/tags/view.php create mode 100644 app/Views/tags/view.twig delete mode 100644 app/Views/tld-registry/import-logs.php create mode 100644 app/Views/tld-registry/import-logs.twig rename app/Views/tld-registry/{import-progress.php => import-progress.twig} (60%) delete mode 100644 app/Views/tld-registry/index.php create mode 100644 app/Views/tld-registry/index.twig delete mode 100644 app/Views/tld-registry/view.php create mode 100644 app/Views/tld-registry/view.twig rename app/Views/users/{create.php => create.twig} (64%) rename app/Views/users/{edit.php => edit.twig} (59%) delete mode 100644 app/Views/users/index.php create mode 100644 app/Views/users/index.twig rename app/Views/users/{show.php => show.twig} (56%) create mode 100644 core/TwigService.php diff --git a/app/Controllers/ProfileController.php b/app/Controllers/ProfileController.php index e0750d1..6ba8f58 100644 --- a/app/Controllers/ProfileController.php +++ b/app/Controllers/ProfileController.php @@ -8,6 +8,7 @@ use App\Models\User; use App\Models\SessionManager; use App\Models\RememberToken; use App\Services\Logger; +use App\Services\TwoFactorService; use App\Helpers\AvatarHelper; class ProfileController extends Controller @@ -71,10 +72,30 @@ class ProfileController extends Controller // Format sessions for display (adds deviceIcon, browserInfo, timeAgo, sessionAge) $formattedSessions = \App\Helpers\SessionHelper::formatForDisplay($sessions); + // Avatar data + $avatar = AvatarHelper::getAvatar($user, 80); + + // 2FA status + $twoFactorService = new TwoFactorService(); + $twoFactorPolicy = $twoFactorService->getTwoFactorPolicy(); + + $backupCodes = !empty($user['two_factor_backup_codes']) + ? json_decode($user['two_factor_backup_codes'], true) + : []; + + $twoFactorStatus = [ + 'enabled' => !empty($user['two_factor_enabled']), + 'setup_at' => $user['two_factor_setup_at'] ?? null, + 'backup_codes_count' => is_array($backupCodes) ? count($backupCodes) : 0, + 'required' => $twoFactorService->isTwoFactorRequired($userId), + ]; + $this->view('profile/index', [ 'user' => $user, 'sessions' => $formattedSessions, - 'userModel' => $this->userModel, + 'avatar' => $avatar, + 'twoFactorStatus' => $twoFactorStatus, + 'twoFactorPolicy' => $twoFactorPolicy, 'title' => 'My Profile' ]); } diff --git a/app/Controllers/SettingsController.php b/app/Controllers/SettingsController.php index b6d5351..23be609 100644 --- a/app/Controllers/SettingsController.php +++ b/app/Controllers/SettingsController.php @@ -70,6 +70,72 @@ class SettingsController extends Controller // Status notification triggers $statusTriggers = $this->settingModel->getNotificationStatusTriggers(); + // Timezone lists for the Application tab + $popularTimezones = [ + 'UTC' => 'UTC', + 'America/New_York' => 'Eastern Time (US)', + 'America/Chicago' => 'Central Time (US)', + 'America/Denver' => 'Mountain Time (US)', + 'America/Los_Angeles' => 'Pacific Time (US)', + 'Europe/London' => 'London', + 'Europe/Paris' => 'Paris', + 'Asia/Tokyo' => 'Tokyo', + 'Australia/Sydney' => 'Sydney' + ]; + $allTimezones = timezone_identifiers_list(); + + // Determine which notification preset is selected + $currentNotificationDays = $settings['notification_days_before'] ?? '30,15,7,3,1'; + $selectedPreset = 'custom'; + foreach ($notificationPresets as $key => $preset) { + if ($preset['value'] === $currentNotificationDays) { + $selectedPreset = $key; + break; + } + } + + // Cron path for System tab + $cronPath = realpath(defined('PATH_ROOT') ? PATH_ROOT . 'cron/check_domains.php' : __DIR__ . '/../../cron/check_domains.php') ?: 'cron/check_domains.php'; + + // Cached update state for Updates tab + $cachedUpdateAvailable = false; + $cachedUpdateData = null; + $currentVer = $appSettings['app_version'] ?? '0'; + $latestVer = $updateSettings['latest_available_version'] ?? null; + $updateChannel = $updateSettings['update_channel'] ?? 'stable'; + $commitsBehind = (int)($updateSettings['commits_behind_count'] ?? 0); + $installedSha = $updateSettings['installed_commit_sha'] ?? ''; + $remoteSha = $updateSettings['latest_remote_sha'] ?? ''; + if ($installedSha !== '' && $remoteSha !== '' && str_starts_with($installedSha, $remoteSha)) { + $commitsBehind = 0; + } + if ($latestVer && version_compare($latestVer, $currentVer, '>')) { + $cachedUpdateAvailable = true; + $cachedUpdateData = [ + 'available' => true, + 'type' => 'release', + 'current_version' => $currentVer, + 'latest_version' => $latestVer, + 'release_notes' => $updateSettings['latest_release_notes'] ?? '', + 'release_url' => $updateSettings['latest_release_url'] ?? '', + 'published_at' => $updateSettings['latest_release_published_at'] ?? null, + 'channel' => $updateChannel, + ]; + } elseif ($updateChannel === 'latest' && $commitsBehind > 0) { + $cachedUpdateAvailable = true; + $cachedUpdateData = [ + 'available' => true, + 'type' => 'hotfix', + 'current_version' => $currentVer, + 'commits_behind' => $commitsBehind, + 'commit_messages' => [], + 'channel' => $updateChannel, + ]; + } + + // Rollback availability + $rollbackAvailable = !empty($updateSettings['update_backup_path']) && file_exists($updateSettings['update_backup_path']); + $this->view('settings/index', [ 'settings' => $settings, 'appSettings' => $appSettings, @@ -81,6 +147,13 @@ class SettingsController extends Controller 'notificationPresets' => $notificationPresets, 'checkIntervalPresets' => $checkIntervalPresets, 'statusTriggers' => $statusTriggers, + 'popularTimezones' => $popularTimezones, + 'allTimezones' => $allTimezones, + 'selectedPreset' => $selectedPreset, + 'cronPath' => $cronPath, + 'cachedUpdateAvailable' => $cachedUpdateAvailable, + 'cachedUpdateData' => $cachedUpdateData, + 'rollbackAvailable' => $rollbackAvailable, 'title' => 'Settings' ]); } diff --git a/app/Controllers/UserController.php b/app/Controllers/UserController.php index a3a3f31..3be9227 100644 --- a/app/Controllers/UserController.php +++ b/app/Controllers/UserController.php @@ -49,6 +49,11 @@ class UserController extends Controller // Get filtered users $users = $this->userModel->getFiltered($filters, $sort, strtoupper($order), $perPage, $offset); + + foreach ($users as &$u) { + $u['avatar'] = \App\Helpers\AvatarHelper::getAvatar($u, 40); + } + unset($u); $this->view('users/index', [ 'users' => $users, @@ -240,6 +245,17 @@ class UserController extends Controller // Get 2FA status $twoFactorStatus = $this->userModel->getTwoFactorStatus($userId); + // Avatar for profile header + $userAvatar = \App\Helpers\AvatarHelper::getAvatar($user, 64); + + // Registrar distribution + $registrarCounts = []; + foreach ($domains as $d) { + $reg = !empty($d['registrar']) ? $d['registrar'] : 'Unknown'; + $registrarCounts[$reg] = ($registrarCounts[$reg] ?? 0) + 1; + } + arsort($registrarCounts); + $this->view('users/show', [ 'title' => htmlspecialchars($user['full_name']) . ' - User Profile', 'user' => $user, @@ -248,6 +264,8 @@ class UserController extends Controller 'tags' => $tags, 'groups' => $groups, 'twoFactorStatus' => $twoFactorStatus, + 'userAvatar' => $userAvatar, + 'registrarCounts' => $registrarCounts, ]); } diff --git a/app/Services/ErrorHandler.php b/app/Services/ErrorHandler.php index a204812..2b57662 100644 --- a/app/Services/ErrorHandler.php +++ b/app/Services/ErrorHandler.php @@ -297,7 +297,29 @@ class ErrorHandler $session_data = json_decode($errorData['session_data'], true); // Display debug page in development, clean 500 in production - if ($this->isDevelopment) { + $twigTemplate = $this->isDevelopment ? 'errors/debug.twig' : 'errors/500.twig'; + $twigFile = __DIR__ . '/../Views/' . $twigTemplate; + + if (file_exists($twigFile)) { + try { + $memory_usage_mb = round(($memory_usage ?? 0) / 1024 / 1024, 2); + $peak_memory_mb = round(memory_get_peak_usage(true) / 1024 / 1024, 2); + $errorContext = compact( + 'error_id', 'error_type', 'error_message', 'error_file', 'error_line', + 'stack_trace', 'request_method', 'request_uri', 'user_agent', + 'ip_address', 'php_version', 'memory_usage', 'memory_usage_mb', + 'peak_memory_mb', 'occurred_at', 'user_info', 'request_data', 'session_data' + ); + echo \Core\TwigService::getInstance()->render($twigTemplate, $errorContext); + } catch (\Throwable $e) { + // Twig itself failed — fall back to raw PHP view + if ($this->isDevelopment) { + require __DIR__ . '/../Views/errors/debug.php'; + } else { + require __DIR__ . '/../Views/errors/500.php'; + } + } + } elseif ($this->isDevelopment) { require __DIR__ . '/../Views/errors/debug.php'; } else { require __DIR__ . '/../Views/errors/500.php'; diff --git a/app/Services/TwoFactorService.php b/app/Services/TwoFactorService.php index 2254934..2fca54d 100644 --- a/app/Services/TwoFactorService.php +++ b/app/Services/TwoFactorService.php @@ -38,14 +38,20 @@ class TwoFactorService */ public function generateQrCodeDataUri(string $email, string $secret, string $appName = 'Domain Monitor'): string { - $qrCode = new QrCode($this->google2fa->getQRCodeUrl($appName, $email, $secret)); - $qrCode->setSize(200); - $qrCode->setMargin(10); - - $writer = new PngWriter(); - $result = $writer->write($qrCode); - - return 'data:image/png;base64,' . base64_encode($result->getString()); + $previousLevel = error_reporting(error_reporting() & ~E_DEPRECATED); + + try { + $qrCode = new QrCode($this->google2fa->getQRCodeUrl($appName, $email, $secret)); + $qrCode->setSize(200); + $qrCode->setMargin(10); + + $writer = new PngWriter(); + $result = $writer->write($qrCode); + + return 'data:image/png;base64,' . base64_encode($result->getString()); + } finally { + error_reporting($previousLevel); + } } /** diff --git a/app/Views/2fa/backup-codes.php b/app/Views/2fa/backup-codes.php deleted file mode 100644 index 4075457..0000000 --- a/app/Views/2fa/backup-codes.php +++ /dev/null @@ -1,209 +0,0 @@ - - -
-
-
-

2FA Backup Codes

-

Save these codes in a safe place - they can be used to access your account if you lose your authenticator device

-
- -
- -
-
-
- -
-
-

Important Security Notice

-

- These backup codes are shown only once. Each code can only be used once. Store them securely and never share them with anyone. -

-
-
-
- - -
-
-

Your Backup Codes

- -
- -
- $code): ?> -
- - -
- -
-
- - -
-

How to use backup codes:

-
    -
  • • When logging in, enter a backup code instead of your 2FA code
  • -
  • • Each backup code can only be used once
  • -
  • • After using a code, it will be automatically removed from your account
  • -
  • • If you run out of backup codes, you'll need to disable and re-enable 2FA
  • -
-
- - - -
-
-
- - - - diff --git a/app/Views/2fa/backup-codes.twig b/app/Views/2fa/backup-codes.twig new file mode 100644 index 0000000..b7eaf62 --- /dev/null +++ b/app/Views/2fa/backup-codes.twig @@ -0,0 +1,124 @@ +{% extends 'layout/base.twig' %} + +{% set title = '2FA Backup Codes' %} +{% set pageTitle = '2FA Backup Codes' %} +{% set pageDescription = 'Save these backup codes in a safe place' %} +{% set pageIcon = 'fas fa-key' %} + +{% block content %} +
+
+
+

2FA Backup Codes

+

Save these codes in a safe place - they can be used to access your account if you lose your authenticator device

+
+ +
+ {# Warning #} +
+
+
+ +
+
+

Important Security Notice

+

+ These backup codes are shown only once. Each code can only be used once. Store them securely and never share them with anyone. +

+
+
+
+ + {# Backup Codes #} +
+
+

Your Backup Codes

+ +
+ +
+ {% for code in backupCodes %} +
+ {{ code }} + +
+ {% endfor %} +
+
+ + {# Instructions #} +
+

How to use backup codes:

+
    +
  • • When logging in, enter a backup code instead of your 2FA code
  • +
  • • Each backup code can only be used once
  • +
  • • After using a code, it will be automatically removed from your account
  • +
  • • If you run out of backup codes, you'll need to disable and re-enable 2FA
  • +
+
+ + {# Actions #} + +
+
+
+ + +{% endblock %} diff --git a/app/Views/2fa/setup.php b/app/Views/2fa/setup.php deleted file mode 100644 index 7233a62..0000000 --- a/app/Views/2fa/setup.php +++ /dev/null @@ -1,162 +0,0 @@ - - -
-
-
-

- - Setup Two-Factor Authentication -

-
- -
- -
-

Step 1: Install an Authenticator App

-

Download one of these apps on your mobile device:

- -
-
- -

Google Authenticator

-

iOS & Android

-
-
- -

Authy

-

iOS & Android

-
-
- -

Microsoft Authenticator

-

iOS & Android

-
-
-
- - -
-

Step 2: Scan QR Code

-

Open your authenticator app and scan this QR code:

- -
-
- -

- Note: This QR code will remain the same even if you refresh the page. - Once you scan it, you can enter the verification code below. -

-
-
- -
-
- QR Code for 2FA setup -
- -
-

Can't scan? Enter this code manually:

-
- -
-
-
-
- - -
-

Step 3: Verify Setup

-

Enter the 6-digit code from your authenticator app:

- -
- - -
- -

Enter 6-digit code

-
- -
- - - - Cancel - -
-
-
- - -
-
- -
-

Important Security Notice

-

- Once 2FA is enabled, you'll need your authenticator app to log in. - Make sure to save your backup codes in a secure location. -

-
-
-
-
-
-
- - - - diff --git a/app/Views/2fa/setup.twig b/app/Views/2fa/setup.twig new file mode 100644 index 0000000..e26d265 --- /dev/null +++ b/app/Views/2fa/setup.twig @@ -0,0 +1,116 @@ +{% extends 'layout/base.twig' %} + +{% set title = 'Setup Two-Factor Authentication' %} +{% set pageTitle = 'Setup 2FA' %} +{% set pageDescription = 'Configure two-factor authentication for your account' %} +{% set pageIcon = 'fas fa-shield-alt' %} + +{% block content %} +
+
+
+

+ + Setup Two-Factor Authentication +

+
+ +
+ {# Step 1: Download Authenticator App #} +
+

Step 1: Install an Authenticator App

+

Download one of these apps on your mobile device:

+ +
+
+ +

Google Authenticator

+

iOS & Android

+
+
+ +

Authy

+

iOS & Android

+
+
+ +

Microsoft Authenticator

+

iOS & Android

+
+
+
+ + {# Step 2: Scan QR Code #} +
+

Step 2: Scan QR Code

+

Open your authenticator app and scan this QR code:

+ +
+
+ QR Code for 2FA setup +
+ +
+

Can't scan? Enter this code manually:

+
+ {{ secret }} +
+
+
+
+ + {# Step 3: Verify Code #} +
+

Step 3: Verify Setup

+

Enter the 6-digit code from your authenticator app:

+ +
+ {{ csrf_field() }} + +
+ +

Enter 6-digit code

+
+ +
+ + + Cancel + +
+
+
+ + {# Security Notice #} +
+
+ +
+

Important Security Notice

+

+ Once 2FA is enabled, you'll need your authenticator app to log in. + Make sure to save your backup codes in a secure location. +

+
+
+
+
+
+
+ + +{% endblock %} diff --git a/app/Views/2fa/verify.php b/app/Views/2fa/verify.php deleted file mode 100644 index cf97b4f..0000000 --- a/app/Views/2fa/verify.php +++ /dev/null @@ -1,206 +0,0 @@ -checkRateLimit($_SERVER['REMOTE_ADDR'] ?? '', $user['id']); -?> - -
-
- -
-

- 2FA Verification -

-

- Hello, !
- Please enter your 2FA code to complete login. -

-
- - -
-
- - -
-
- - - - -
-
- - -
-
- - - -
- - - -
-
- - Security verification completed during login -
-
- -
- - -

Enter 6-digit code from your authenticator app, email code, or 8-character backup code

-
- - - -
- - - - - - Email code unavailable - - - - - Sign out instead - -
- -
-
-

- Having trouble? You can also use a backup code or contact your administrator for help. -

-
-
-
- - - - diff --git a/app/Views/2fa/verify.twig b/app/Views/2fa/verify.twig new file mode 100644 index 0000000..20af8d5 --- /dev/null +++ b/app/Views/2fa/verify.twig @@ -0,0 +1,124 @@ +{% extends 'auth/base-auth.twig' %} + +{% set title = '2FA Verification' %} + +{% block content %} +
+
+ +
+

2FA Verification

+

+ Hello, {{ user.full_name|default(user.username) }}!
+ Please enter your 2FA code to complete login. +

+
+ +{% if flash.success is defined %} +
+
+ + {{ flash.success }} +
+
+{% endif %} + +{% if flash.error is defined %} +
+
+ + {{ flash.error }} +
+
+{% endif %} + +
+ {{ csrf_field() }} + +
+
+ + Security verification completed during login +
+
+ +
+ + +

Enter 6-digit code from your authenticator app, email code, or 8-character backup code

+
+ + + +
+ {% if canSendEmailCode %} + + {% else %} + Email code unavailable + {% endif %} + + Sign out instead + +
+ +
+

Having trouble? You can also use a backup code or contact your administrator.

+
+
+ + +{% endblock %} diff --git a/app/Views/auth/captcha-widget.php b/app/Views/auth/_captcha-widget.twig similarity index 54% rename from app/Views/auth/captcha-widget.php rename to app/Views/auth/_captcha-widget.twig index 4be7b22..cf87b58 100644 --- a/app/Views/auth/captcha-widget.php +++ b/app/Views/auth/_captcha-widget.twig @@ -1,42 +1,36 @@ - - - +{% if provider != 'disabled' and siteKey is not empty %} +{# CAPTCHA Widget #}
- - -
+ {% if provider == 'recaptcha_v2' %} + {# reCAPTCHA v2 #} +
- - + {% elseif provider == 'recaptcha_v3' %} + {# reCAPTCHA v3 (Invisible) #} - - - - -
- + - + {% elseif provider == 'turnstile' %} + {# Cloudflare Turnstile #} +
+ + {% endif %}
- - +{% if provider == 'recaptcha_v3' %} +{# reCAPTCHA v3 Form Submission Handler #} - - +{% elseif provider == 'recaptcha_v2' or provider == 'turnstile' %} +{# reCAPTCHA v2 / Turnstile Response Handler #} - - +{% endif %} +{% endif %} diff --git a/app/Views/auth/base-auth.php b/app/Views/auth/base-auth.php deleted file mode 100644 index 01e65d8..0000000 --- a/app/Views/auth/base-auth.php +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - <?= $title ?? 'Authentication' ?> - Domain Monitor - - - - - - - - - - - - -
- -
- -
- - -
-

- © Domain Monitor. All rights reserved. -

-
-
- - - - - - - diff --git a/app/Views/auth/base-auth.twig b/app/Views/auth/base-auth.twig new file mode 100644 index 0000000..a2cacf1 --- /dev/null +++ b/app/Views/auth/base-auth.twig @@ -0,0 +1,209 @@ +{# + # Auth Layout Template + # Used for: login, register, forgot-password, verify-email, etc. + #} + + + + + + {{ title|default('Authentication') }} - {{ appSettings.app_name|default('Domain Monitor') }} + + {# Tailwind CSS #} + + + {# Font Awesome #} + + + + + {# Theme initialization (prevent flash) #} + + + + + {% block head %}{% endblock %} + + + {# Monitoring Graph Waves Background #} +
+ {# Light Mode SVG #} + + + + + + + + + + + + + + + + + {# Top accent line #} + + + {# Wave 1 - Back #} + + + + {# Wave 2 - Middle #} + + + + {# Wave 3 - Front #} + + + + {# Bottom accent line #} + + + + {# Dark Mode SVG #} + + + + + + + + + + + + + + + + + {# Top accent line #} + + + {# Wave 1 - Back #} + + + + {# Wave 2 - Middle #} + + + + {# Wave 3 - Front #} + + + + {# Bottom accent line #} + + +
+ + {# Logo - Top Left #} +
+ {{ appSettings.app_name|default('Domain Monitor') }} +
+ {{ appSettings.app_name|default('Domain Monitor') }} + Track your domains +
+
+ + {# Theme Toggle Button #} + + +
+ + {# Auth Card #} +
+ {% block content %}{% endblock %} +
+ + {# Footer #} +
+

+ © {{ "now"|date("Y") }} {{ appSettings.app_name|default('Domain Monitor') }}. All rights reserved. +

+
+
+ + + + {% block scripts %}{% endblock %} + + diff --git a/app/Views/auth/forgot-password.php b/app/Views/auth/forgot-password.php deleted file mode 100644 index dc563d5..0000000 --- a/app/Views/auth/forgot-password.php +++ /dev/null @@ -1,83 +0,0 @@ - - - -
-
- -
-

Forgot Password?

-

No worries, we'll send you reset instructions

-
- - - -
-
- - -
-
- - - - -
-
- - -
-
- - - - -
- - -
- -
-
- -
- -
-

Enter the email associated with your account

-
- - - - - - -
- - -
- - - Back to Login - -
- - diff --git a/app/Views/auth/forgot-password.twig b/app/Views/auth/forgot-password.twig new file mode 100644 index 0000000..49fb961 --- /dev/null +++ b/app/Views/auth/forgot-password.twig @@ -0,0 +1,82 @@ +{# + # Forgot Password Page + #} +{% extends 'auth/base-auth.twig' %} + +{% set title = 'Forgot Password' %} + +{% block content %} +{# Logo and Title #} +
+
+ +
+

Forgot Password?

+

No worries, we'll send you reset instructions

+
+ +{# Error Alert #} +{% if flash.error is defined %} +
+
+ + {{ flash.error }} +
+
+{% endif %} + +{# Success Alert #} +{% if flash.success is defined %} +
+
+ + {{ flash.success }} +
+
+{% endif %} + +{# Forgot Password Form #} +
+ {{ csrf_field() }} + + {# Email Field #} +
+ +
+
+ +
+ +
+

Enter the email associated with your account

+
+ + {# CAPTCHA Widget #} + {% include 'auth/_captcha-widget.twig' %} + + {# Submit Button #} + +
+ +{# Back to Login Link #} +
+ + + Back to Login + +
+{% endblock %} diff --git a/app/Views/auth/login.php b/app/Views/auth/login.twig similarity index 58% rename from app/Views/auth/login.php rename to app/Views/auth/login.twig index 174a709..ffab1ff 100644 --- a/app/Views/auth/login.php +++ b/app/Views/auth/login.twig @@ -1,34 +1,47 @@ - +{# + # Login Page + #} +{% extends 'auth/base-auth.twig' %} - +{% set title = 'Login' %} + +{% block content %} +{# Logo and Title #}
-

Welcome Back

-

Sign in to access your account

+

Welcome Back

+

Sign in to access your account

- - -
+{# Success Alert #} +{% if flash.success is defined %} +
- - + + {{ flash.success }}
- - +{% endif %} - +{# Error Alert #} +{% if flash.error is defined %} +
+
+ + {{ flash.error }} +
+
+{% endif %} + +{# Login Form #}
- - + {{ csrf_field() }} + + {# Username Field #}
-
- + {# Password Field #}
-
- + {# Remember Me #}
Forgot password?
- - + {# CAPTCHA Widget #} + {% include 'auth/_captcha-widget.twig' %} - + {# Submit Button #}
- - -
-

+{% if registrationEnabled|default(false) %} +{# Sign Up Link #} +

+

Don't have an account? Create Account

- +{% endif %} +{% endblock %} - function togglePassword() { const passwordInput = document.getElementById('password'); @@ -129,6 +141,4 @@ $scripts = <<<'SCRIPT' } } -SCRIPT; -require __DIR__ . '/base-auth.php'; -?> +{% endblock %} diff --git a/app/Views/auth/register.php b/app/Views/auth/register.twig similarity index 64% rename from app/Views/auth/register.php rename to app/Views/auth/register.twig index 8c6fd04..134accd 100644 --- a/app/Views/auth/register.php +++ b/app/Views/auth/register.twig @@ -1,44 +1,47 @@ - +{# + # Registration Page + #} +{% extends 'auth/base-auth.twig' %} - +{% set title = 'Register' %} + +{% block content %} +{# Logo and Title #}
-

Create Account

-

Join Domain Monitor today

+

Create Account

+

Join Domain Monitor today

- - -
+{# Error Alert #} +{% if flash.error is defined %} +
- - + + {{ flash.error }}
- - +{% endif %} - -
+{# Success Alert #} +{% if flash.success is defined %} +
- - + + {{ flash.success }}
- - +{% endif %} - +{# Registration Form #}
- - + {{ csrf_field() }} + + {# Full Name Field #}
-
- + {# Username Field #}
-
- + {# Email Field #}
-
- + {# Password Field #}
-
- + {# Confirm Password Field #}
-
- + {# Terms Checkbox #}
+ class="w-4 h-4 text-primary border-gray-300 dark:border-gray-600 dark:bg-gray-700 rounded focus:ring-primary">
-
- - + {# CAPTCHA Widget #} + {% include 'auth/_captcha-widget.twig' %} - + {# Submit Button #}
- -
-

+{# Sign In Link #} +

+

Already have an account? Sign In

+{% endblock %} - function togglePassword(fieldId) { const passwordInput = document.getElementById(fieldId); @@ -216,6 +218,4 @@ $scripts = <<<'SCRIPT' } }); -SCRIPT; -require __DIR__ . '/base-auth.php'; -?> +{% endblock %} diff --git a/app/Views/auth/reset-password.php b/app/Views/auth/reset-password.twig similarity index 60% rename from app/Views/auth/reset-password.php rename to app/Views/auth/reset-password.twig index b58e923..2fdeb68 100644 --- a/app/Views/auth/reset-password.php +++ b/app/Views/auth/reset-password.twig @@ -1,37 +1,50 @@ - +{# + # Reset Password Page + #} +{% extends 'auth/base-auth.twig' %} - +{% set title = 'Reset Password' %} + +{% block content %} +{# Logo and Title #}
-

Reset Password

-

Enter your new password below

+

Reset Password

+

Enter your new password below

- - -
+{# Success Alert #} +{% if flash.success is defined %} +
- - + + {{ flash.success }}
- - +{% endif %} - +{# Error Alert #} +{% if flash.error is defined %} +
+
+ + {{ flash.error }} +
+
+{% endif %} + +{# Reset Password Form #}
- - - + {{ csrf_field() }} + + {# Hidden token field #} + - + {# Password Field #}
-
- + {# Confirm Password Field #}
-
- -
-

+ {# Password Strength Indicator #} +

+

Password Requirements:

-
    +
    • At least 8 characters long
    • Mix of uppercase and lowercase letters recommended
    • Include numbers and special characters for extra security
- - + {# CAPTCHA Widget #} + {% include 'auth/_captcha-widget.twig' %} - + {# Submit Button #} - -
- +{# Back to Login Link #} + +{% endblock %} - function togglePassword(fieldId) { const passwordInput = document.getElementById(fieldId); @@ -146,6 +158,4 @@ $scripts = <<<'SCRIPT' } }); -SCRIPT; -require __DIR__ . '/base-auth.php'; -?> +{% endblock %} diff --git a/app/Views/auth/verify-email.php b/app/Views/auth/verify-email.twig similarity index 50% rename from app/Views/auth/verify-email.php rename to app/Views/auth/verify-email.twig index fc64874..466f107 100644 --- a/app/Views/auth/verify-email.php +++ b/app/Views/auth/verify-email.twig @@ -1,60 +1,63 @@ - +{# + # Verify Email Page + #} +{% extends 'auth/base-auth.twig' %} - - +{% set title = 'Verify Email' %} + +{% block content %} +{% if verified|default(false) %} + {# Success State #}
-
- +
+
-

Email Verified!

-

Your email address has been successfully verified.

+

Email Verified!

+

Your email address has been successfully verified.

Sign In to Your Account
- - +{% elseif error|default(false) %} + {# Error State #}
-
- +
+
-

Verification Failed

-

+

Verification Failed

+

{{ errorMessage|default('Invalid or expired verification link.') }}

- - +{% else %} + {# Pending State #}
-
- +
+
-

Check Your Email

-

- We've sent a verification link to . +

Check Your Email

+

+ We've sent a verification link to {{ email|default('your email') }}. Please check your inbox and click the link to verify your account.

-
-

+

+

Didn't receive the email?

-
    +
    • Check your spam or junk folder
    • Make sure you entered the correct email address
    • Wait a few minutes for the email to arrive
    • @@ -66,14 +69,10 @@ ob_start(); Resend Verification Email - + Back to Login
- - - +{% endif %} +{% endblock %} diff --git a/app/Views/dashboard/index.php b/app/Views/dashboard/index.php deleted file mode 100644 index 4af6826..0000000 --- a/app/Views/dashboard/index.php +++ /dev/null @@ -1,347 +0,0 @@ - ($t['usage_count'] ?? 0) > 0), 0, 8); -$domainsWithoutGroup = ($totalDomainCount ?? 0) - ($domainsWithGroup ?? 0); -$totalGroupCount = count($groups ?? []); - -ob_start(); -?> - - - -
-
- - - System Status - - 'text-green-600', - 'yellow' => 'text-yellow-600', - 'red' => 'text-red-600', - 'gray' => 'text-gray-600' - ]; - $statusDots = [ - 'green' => 'bg-green-500', - 'yellow' => 'bg-yellow-500', - 'red' => 'bg-red-500', - 'gray' => 'bg-gray-400' - ]; - ?> -
- - - Database - - - | - - - TLD Registry - - - | - - - Notifications - - -
-
-
- - - -
-
-
-
-

Total Domains

-

-
-
- -
-
-
-
-
-
-

Active

-

-
-
- -
-
-
-
-
-
-

Expiring Soon

-

-

within days

-
-
- -
-
-
-
-
-
-

Inactive

-

-
-
- -
-
-
-
- - -
- -
-
-
-

- - Recent Domains -

- - View all - -
-
-
- -
- -
-
-
- -
-
-

-
- - - - - - - - - - -
-
-
-
- - - - - - -
-
- -
- -
- -

No domains added yet

- - - Add Your First Domain - -
- -
-
- - -
-
-
-

- - Expiring Soon -

- 5): ?> - - View all - - - -
-
- -
- - -
-
-

-

- - - days - -

-
- - - -
- -
- -
- -

No domains expiring soon

-

within days

-
- -
-
- - -
- -
-
-

- - Registrar Distribution -

- registrar -
-
- -
- $regCount): ?> - 0 ? round(($regCount / $totalDomainCount) * 100) : 0; ?> -
-
- - (%) -
-
-
-
-
- -
- -
- -

No registrar data

-
- -
-
- - -
-
-

- - Tag Usage -

- tag -
-
- -
- - 0 ? round(($tt['usage_count'] / $totalDomainCount) * 100) : 0; ?> -
-
- - - - - domain (%) -
-
-
-
-
- -
- -
- -

No tags in use

-
- -
-
- - -
-
-

- - Notification Coverage -

- group, channel -
-
- 0): ?> - -
-
- - - - -
- % -
-
-
-
-
-

-

With Notifications

-
-
-

-

Without Notifications

-
-
- -
- -

No domains to monitor

-
- -
-
-
- - diff --git a/app/Views/dashboard/index.twig b/app/Views/dashboard/index.twig new file mode 100644 index 0000000..2c05d40 --- /dev/null +++ b/app/Views/dashboard/index.twig @@ -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 #} +
+
+ +
+
+

Welcome back, {{ auth.fullName|default(auth.username)|default('User') }}!

+

+ {% if domainStats.expiring_soon|default(0) > 0 %} + You have {{ domainStats.expiring_soon }} 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 %} +

+ +
+
+ +{% if auth.isAdmin %} +{# System Status Bar (Admin) #} +
+
+ + + System Status + +
+ {% 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'} %} + + + Database + {{ systemStatus.database.status|capitalize }} + + + + + TLD Registry + {{ systemStatus.whois.status|capitalize }} + + + + + Notifications + {{ systemStatus.notifications.status|capitalize }} + +
+
+
+{% endif %} + +{# Statistics Cards #} +
+ {# Total Domains Card #} +
+
+
+

Total Domains

+

{{ domainStats.total|default(0) }}

+
+
+ +
+
+ +
+ + {# Active Domains Card #} +
+
+
+

Active

+

{{ domainStats.active|default(0) }}

+
+
+ +
+
+
+ {% if domainStats.total|default(0) > 0 %} + {% set activePercent = ((domainStats.active|default(0) / domainStats.total) * 100)|round %} + {{ activePercent }}% of total + {% else %} + No domains yet + {% endif %} +
+
+ + {# Expiring Soon Card #} +
+
+
+

Expiring Soon

+

{{ domainStats.expiring_soon|default(0) }}

+
+
+ +
+
+
+ within {{ domainStats.expiring_threshold|default(30) }} days +
+
+ + {# Inactive Domains Card #} +
+
+
+

Inactive

+

{{ domainStats.inactive|default(0) }}

+
+
+ +
+
+
+ monitoring paused +
+
+
+ +{# Main Content: Recent Domains + Expiring Soon #} +
+ {# Recent Domains #} +
+
+
+

+ + Recent Domains +

+ + View all + +
+
+
+ {% if recentDomains is not empty %} +
+ {% for domain in recentDomains %} +
+
+
+ +
+
+

{{ domain.domain_name }}

+
+ + + {{ domain.expiration_date ? domain.expiration_date|date('M d, Y') : 'Not set' }} + + {% if domain.registrar %} + + + {{ domain.registrar }} + + {% endif %} +
+
+
+
+ + {{ domain.statusText }} + + + + +
+
+ {% endfor %} +
+ {% else %} +
+ +

No domains added yet

+ + + Add Your First Domain + +
+ {% endif %} +
+
+ + {# Expiring Soon #} +
+
+
+

+ + Expiring Soon +

+ {% if expiringCount|default(0) > 5 %} + + View all {{ expiringCount }} + + + {% endif %} +
+
+ {% if expiringThisMonth is not empty %} +
+ {% 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 %} +
+
+

{{ domain.domain_name }}

+

+ {{ domain.expiration_date ? domain.expiration_date|date('M d, Y') : 'Unknown' }} + + {{ daysLeft }} days + +

+
+ + + +
+ {% endfor %} +
+ {% else %} +
+ +

No domains expiring soon

+

within {{ domainStats.expiring_threshold|default(30) }} days

+
+ {% endif %} +
+
+ +{# Insights Row #} +
+ {# Registrar Distribution #} +
+
+

+ + Registrar Distribution +

+ {{ registrarCounts|default({})|length }} registrar{{ registrarCounts|default({})|length != 1 ? 's' : '' }} +
+
+ {% if topRegistrars is not empty %} +
+ {% for regName, regCount in topRegistrars %} + {% set regPct = totalDomainCount|default(0) > 0 ? ((regCount / totalDomainCount) * 100)|round : 0 %} +
+
+ {{ regName }} + {{ regCount }} ({{ regPct }}%) +
+
+
+
+
+ {% endfor %} +
+ {% else %} +
+ +

No registrar data

+
+ {% endif %} +
+
+ + {# Tag Usage #} +
+
+

+ + Tag Usage +

+ {{ dashTags|default([])|length }} tag{{ dashTags|default([])|length != 1 ? 's' : '' }} +
+
+ {% if topTags is not empty %} +
+ {% for tt in topTags %} + {% set pct = totalDomainCount|default(0) > 0 ? ((tt.usage_count / totalDomainCount) * 100)|round : 0 %} +
+
+ + + {{ tt.name }} + + {{ tt.usage_count }} domain{{ tt.usage_count != 1 ? 's' : '' }} ({{ pct }}%) +
+
+
+
+
+ {% endfor %} +
+ {% else %} +
+ +

No tags in use

+
+ {% endif %} +
+
+ + {# Notification Coverage #} +
+
+

+ + Notification Coverage +

+ {{ totalGroupCount }} group{{ totalGroupCount != 1 ? 's' : '' }}, {{ totalChannels|default(0) }} channel{{ totalChannels|default(0) != 1 ? 's' : '' }} +
+
+ {% if totalDomainCount|default(0) > 0 %} + {% set coveragePct = ((domainsWithGroup|default(0) / totalDomainCount) * 100)|round %} +
+
+ + + + +
+ {{ coveragePct }}% +
+
+
+
+
+

{{ domainsWithGroup|default(0) }}

+

With Notifications

+
+
+

{{ domainsWithoutGroup }}

+

Without Notifications

+
+
+ {% else %} +
+ +

No domains to monitor

+
+ {% endif %} +
+
+
+{% endblock %} diff --git a/app/Views/debug/whois.php b/app/Views/debug/whois.twig similarity index 60% rename from app/Views/debug/whois.php rename to app/Views/debug/whois.twig index 507fb98..b7d4bc0 100644 --- a/app/Views/debug/whois.php +++ b/app/Views/debug/whois.twig @@ -1,28 +1,29 @@ - +{% extends "layout/base.twig" %} - +{% set title = 'WHOIS Debug Tool' %} +{% set pageTitle = 'WHOIS Debug Tool' %} +{% set pageDescription = 'Test and debug WHOIS data extraction' %} +{% set pageIcon = 'fas fa-search' %} + +{% block content %} + +{% if domain is empty %}
-
+
-
@@ -36,14 +37,14 @@ ob_start();
-
+
-

What is this tool?

-

+

What is this tool?

+

This debug tool shows you the raw WHOIS data for any domain and how our system parses it. Use it to troubleshoot issues with domain information extraction.

@@ -52,10 +53,10 @@ ob_start();
- +{% else %}
- + Check Another Domain @@ -66,19 +67,19 @@ ob_start();
-
+
-

Domain

-

+

Domain

+

{{ domain }}

-

WHOIS Server

-

+

WHOIS Server

+

{{ server }}

-

TLD

-

+

TLD

+

{{ tld }}

@@ -86,53 +87,53 @@ ob_start();
-
-
-

- +
+
+

+ Extracted Data (What We Save)

-
- Domain - +
+ Domain + {{ info.domain ?? 'N/A' }}
-
- Registrar - +
+ Registrar + {{ info.registrar ?? 'N/A' }}
-
- Expiration Date - +
+ Expiration Date + {{ info.expiration_date ?? 'N/A' }}
-
- Creation Date - +
+ Creation Date + {{ info.creation_date ?? 'N/A' }}
-
- Updated Date - +
+ Updated Date + {{ info.updated_date ?? 'N/A' }}
-
- Registrar URL - +
+ Registrar URL + {{ info.registrar_url ?? 'N/A' }}
-
- Abuse Email - +
+ Abuse Email + {{ info.abuse_email ?? 'N/A' }}
- Nameservers + Nameservers
- - -
- - - N/A - + {% if info.nameservers is not empty %} + {% for ns in info.nameservers %} +
{{ ns }}
+ {% endfor %} + {% else %} + N/A + {% endif %}
@@ -140,30 +141,30 @@ ob_start();
-
-
-

- +
+
+

+ All Key-Value Pairs

- - +
+ - - + + - - - - - - + + {% for item in parsedData %} + {% if item.value is not empty %} + + + - - + {% endif %} + {% endfor %}
KeyValueKeyValue
{{ item.key }}{{ item.value }}
@@ -171,27 +172,27 @@ ob_start();
-
-
-

- +
+
+

+ Raw WHOIS Response

-
+
{{ response }}
@@ -231,33 +232,26 @@ ob_start(); report += data.rawResponse; report += `\n\n=== END OF REPORT ===`; - // Copy to clipboard with fallback copyToClipboard(report, button); } - // Robust clipboard copy function with fallback function copyToClipboard(text, button) { - // Try modern clipboard API first if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(() => { showCopySuccess(button); }).catch(err => { console.error('Modern clipboard API failed:', err); - // Fallback to legacy method fallbackCopyTextToClipboard(text, button); }); } else { - // Use fallback for non-HTTPS or older browsers fallbackCopyTextToClipboard(text, button); } } function fallbackCopyTextToClipboard(text, button) { - // Create a temporary textarea const textArea = document.createElement('textarea'); textArea.value = text; - // Make it invisible but accessible textArea.style.position = 'fixed'; textArea.style.top = '0'; textArea.style.left = '0'; @@ -308,10 +302,6 @@ ob_start(); } - - - +{% endif %} +{% endblock %} diff --git a/app/Views/domains/bulk-add.php b/app/Views/domains/bulk-add.twig similarity index 68% rename from app/Views/domains/bulk-add.php rename to app/Views/domains/bulk-add.twig index 3898c48..afc5def 100644 --- a/app/Views/domains/bulk-add.php +++ b/app/Views/domains/bulk-add.twig @@ -1,60 +1,61 @@ - +{% extends 'layout/base.twig' %} + +{% set title = 'Bulk Add Domains' %} +{% set pageTitle = 'Bulk Add Domains' %} +{% set pageDescription = 'Add multiple domains at once' %} +{% set pageIcon = 'fas fa-layer-group' %} + +{% block content %}
-
+
-
- -
- +
- + {{ csrf_field() }}
-
-
-
@@ -109,7 +110,7 @@ ob_start(); Add All Domains + class="inline-flex items-center justify-center px-5 py-2.5 border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-slate-300 rounded-lg font-medium hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors text-sm"> Cancel @@ -120,30 +121,30 @@ ob_start();