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:
@@ -1,60 +1,61 @@
|
||||
<?php
|
||||
$title = 'Bulk Add Domains';
|
||||
$pageTitle = 'Bulk Add Domains';
|
||||
$pageDescription = 'Add multiple domains at once';
|
||||
$pageIcon = 'fas fa-layer-group';
|
||||
ob_start();
|
||||
?>
|
||||
{% 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 %}
|
||||
|
||||
<!-- Main Container -->
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<!-- Tabs -->
|
||||
<div class="flex border-b border-gray-200 bg-gray-50">
|
||||
<button onclick="switchTab('paste')" id="tab-paste" class="px-6 py-3 text-sm font-medium border-b-2 border-primary text-primary bg-white transition-colors">
|
||||
<div class="flex border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
|
||||
<button onclick="switchTab('paste')" id="tab-paste" class="px-6 py-3 text-sm font-medium border-b-2 border-primary text-primary bg-white dark:bg-slate-800 transition-colors">
|
||||
<i class="fas fa-keyboard mr-2"></i>Paste Domains
|
||||
</button>
|
||||
<button onclick="switchTab('import')" id="tab-import" class="px-6 py-3 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 transition-colors">
|
||||
<button onclick="switchTab('import')" id="tab-import" class="px-6 py-3 text-sm font-medium border-b-2 border-transparent text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-300 hover:border-gray-300 dark:hover:border-slate-600 transition-colors">
|
||||
<i class="fas fa-file-upload mr-2"></i>Import from File
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab 1: Paste Domains (existing) -->
|
||||
<!-- Tab 1: Paste Domains -->
|
||||
<div id="panel-paste" class="p-6">
|
||||
<form method="POST" action="/domains/bulk-add" class="space-y-5">
|
||||
<?= csrf_field() ?>
|
||||
{{ csrf_field() }}
|
||||
<!-- Domains Textarea -->
|
||||
<div>
|
||||
<label for="domains" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="domains" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Domain Names *
|
||||
</label>
|
||||
<textarea
|
||||
id="domains"
|
||||
name="domains"
|
||||
rows="10"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm font-mono"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm font-mono"
|
||||
placeholder="example.com google.com github.com ..."
|
||||
required
|
||||
autofocus></textarea>
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
Enter one domain per line. Domains without http:// or www.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
<div>
|
||||
<label for="tags-input" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="tags-input" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Tags
|
||||
<span class="text-gray-400 font-normal">(Optional)</span>
|
||||
<span class="text-gray-400 dark:text-slate-500 font-normal">(Optional)</span>
|
||||
</label>
|
||||
|
||||
<div id="tags-display" class="min-h-[40px] p-2 border border-gray-300 rounded-lg mb-2 flex flex-wrap gap-1.5 bg-gray-50"></div>
|
||||
<div id="tags-display" class="min-h-[40px] p-2 border border-gray-300 dark:border-slate-600 rounded-lg mb-2 flex flex-wrap gap-1.5 bg-gray-50 dark:bg-slate-900"></div>
|
||||
|
||||
<div class="relative">
|
||||
<i class="fas fa-tag absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-sm"></i>
|
||||
<i class="fas fa-tag absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-slate-500 text-sm"></i>
|
||||
<input type="text"
|
||||
id="tags-input"
|
||||
class="w-full pl-10 pr-20 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
|
||||
class="w-full pl-10 pr-20 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
|
||||
placeholder="Type any tag and press Enter or comma..."
|
||||
onkeydown="handleTagInput(event)">
|
||||
<button type="button" onclick="addTagFromInput()" class="absolute right-2 top-1/2 transform -translate-y-1/2 px-3 py-1 bg-primary text-white text-xs rounded hover:bg-primary-dark">
|
||||
@@ -64,39 +65,39 @@ ob_start();
|
||||
|
||||
<input type="hidden" id="tags" name="tags" value="">
|
||||
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
All imported domains will be tagged with these tags.
|
||||
</p>
|
||||
|
||||
<div class="mt-2">
|
||||
<p class="text-xs text-gray-600 mb-1.5">Available Tags:</p>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 mb-1.5">Available Tags:</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<?php foreach ($availableTags as $tag): ?>
|
||||
<button type="button" onclick="addTag('<?= htmlspecialchars($tag['name']) ?>')"
|
||||
class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium border <?= htmlspecialchars($tag['color']) ?> hover:opacity-80 transition-colors">
|
||||
{% for tag in availableTags %}
|
||||
<button type="button" onclick="addTag('{{ tag.name }}')"
|
||||
class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium border {{ tag.color }} hover:opacity-80 transition-colors">
|
||||
<i class="fas fa-plus mr-1" style="font-size: 8px;"></i>
|
||||
<?= htmlspecialchars($tag['name']) ?>
|
||||
{{ tag.name }}
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Group -->
|
||||
<div>
|
||||
<label for="notification_group_id" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="notification_group_id" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Notification Group (Optional)
|
||||
</label>
|
||||
<select id="notification_group_id"
|
||||
name="notification_group_id"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
class="w-full px-3 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
<option value="">-- No Group (No notifications) --</option>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
{% for group in groups %}
|
||||
<option value="{{ group.id }}">{{ group.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
Assign all domains to this notification group
|
||||
</p>
|
||||
</div>
|
||||
@@ -109,7 +110,7 @@ ob_start();
|
||||
Add All Domains
|
||||
</button>
|
||||
<a href="/domains"
|
||||
class="inline-flex items-center justify-center px-5 py-2.5 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors text-sm">
|
||||
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">
|
||||
<i class="fas fa-times mr-2"></i>
|
||||
Cancel
|
||||
</a>
|
||||
@@ -120,30 +121,30 @@ ob_start();
|
||||
<!-- Tab 2: Import from File -->
|
||||
<div id="panel-import" class="hidden p-6">
|
||||
<form method="POST" action="/domains/import" enctype="multipart/form-data" class="space-y-5" id="domainImportForm">
|
||||
<?= csrf_field() ?>
|
||||
{{ csrf_field() }}
|
||||
|
||||
<!-- Drag & Drop Zone -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Select File *
|
||||
</label>
|
||||
<div id="domainDropzone" class="relative border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer transition-all hover:border-primary hover:bg-gray-50">
|
||||
<div id="domainDropzone" class="relative border-2 border-dashed border-gray-300 dark:border-slate-600 rounded-lg p-8 text-center cursor-pointer transition-all hover:border-primary hover:bg-gray-50 dark:hover:bg-slate-700">
|
||||
<input type="file" name="import_file" accept=".csv,.json" required id="domainFileInput"
|
||||
class="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10">
|
||||
<div id="domainDropzoneContent">
|
||||
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i>
|
||||
<p class="text-sm text-gray-600 font-medium">Drag & drop your file here</p>
|
||||
<p class="text-xs text-gray-400 my-1.5">or</p>
|
||||
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 dark:text-slate-500 mb-3"></i>
|
||||
<p class="text-sm text-gray-600 dark:text-slate-400 font-medium">Drag & drop your file here</p>
|
||||
<p class="text-xs text-gray-400 dark:text-slate-500 my-1.5">or</p>
|
||||
<span class="inline-flex items-center px-4 py-2 bg-primary text-white text-xs rounded-lg font-medium">
|
||||
<i class="fas fa-folder-open mr-1.5"></i>Browse Files
|
||||
</span>
|
||||
<p class="mt-3 text-xs text-gray-400">CSV, JSON · Max <?= \App\Helpers\ViewHelper::getMaxUploadSize() ?></p>
|
||||
<p class="mt-3 text-xs text-gray-400 dark:text-slate-500">CSV, JSON · Max {{ max_upload_size() }}</p>
|
||||
</div>
|
||||
<div id="domainDropzoneFile" class="hidden">
|
||||
<i class="fas fa-file-alt text-2xl text-primary mb-1.5"></i>
|
||||
<p class="text-sm font-medium text-gray-700" id="domainFileName"></p>
|
||||
<p class="text-xs text-gray-400" id="domainFileSize"></p>
|
||||
<button type="button" id="domainFileRemove" class="mt-1.5 text-xs text-red-500 hover:text-red-700 font-medium">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-slate-300" id="domainFileName"></p>
|
||||
<p class="text-xs text-gray-400 dark:text-slate-500" id="domainFileSize"></p>
|
||||
<button type="button" id="domainFileRemove" class="mt-1.5 text-xs text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 font-medium">
|
||||
<i class="fas fa-trash-alt mr-1"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
@@ -151,31 +152,31 @@ ob_start();
|
||||
</div>
|
||||
|
||||
<!-- Expected Format Info -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<p class="text-sm font-medium text-gray-900 mb-2"><i class="fas fa-info-circle text-blue-500 mr-1.5"></i>Expected File Format</p>
|
||||
<p class="text-xs text-gray-600 mb-2">CSV columns or JSON fields:</p>
|
||||
<div class="bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white mb-2"><i class="fas fa-info-circle text-blue-500 dark:text-blue-400 mr-1.5"></i>Expected File Format</p>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 mb-2">CSV columns or JSON fields:</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<code class="px-2 py-0.5 bg-white rounded text-xs border border-blue-200 font-semibold text-blue-800">domain_name *</code>
|
||||
<code class="px-2 py-0.5 bg-white rounded text-xs border border-gray-200 text-gray-600">tags</code>
|
||||
<code class="px-2 py-0.5 bg-white rounded text-xs border border-gray-200 text-gray-600">notes</code>
|
||||
<code class="px-2 py-0.5 bg-white rounded text-xs border border-gray-200 text-gray-600">notification_group</code>
|
||||
<code class="px-2 py-0.5 bg-white dark:bg-slate-800 rounded text-xs border border-blue-200 dark:border-blue-800 font-semibold text-blue-800 dark:text-blue-400">domain_name *</code>
|
||||
<code class="px-2 py-0.5 bg-white dark:bg-slate-800 rounded text-xs border border-gray-200 dark:border-slate-700 text-gray-600 dark:text-slate-400">tags</code>
|
||||
<code class="px-2 py-0.5 bg-white dark:bg-slate-800 rounded text-xs border border-gray-200 dark:border-slate-700 text-gray-600 dark:text-slate-400">notes</code>
|
||||
<code class="px-2 py-0.5 bg-white dark:bg-slate-800 rounded text-xs border border-gray-200 dark:border-slate-700 text-gray-600 dark:text-slate-400">notification_group</code>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">Only <code class="bg-white px-1 rounded">domain_name</code> is required. Tags should be comma-separated. Notification group is matched by name.</p>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400 mt-2">Only <code class="bg-white dark:bg-slate-800 px-1 rounded">domain_name</code> is required. Tags should be comma-separated. Notification group is matched by name.</p>
|
||||
</div>
|
||||
|
||||
<!-- Fallback Notification Group -->
|
||||
<div>
|
||||
<label for="import_notification_group_id" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="import_notification_group_id" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Default Notification Group
|
||||
<span class="text-gray-400 font-normal">(for domains without a group in the file)</span>
|
||||
<span class="text-gray-400 dark:text-slate-500 font-normal">(for domains without a group in the file)</span>
|
||||
</label>
|
||||
<select id="import_notification_group_id"
|
||||
name="notification_group_id"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
class="w-full px-3 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
<option value="">-- No Group (No notifications) --</option>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
{% for group in groups %}
|
||||
<option value="{{ group.id }}">{{ group.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -187,7 +188,7 @@ ob_start();
|
||||
Import Domains
|
||||
</button>
|
||||
<a href="/domains"
|
||||
class="inline-flex items-center justify-center px-5 py-2.5 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors text-sm">
|
||||
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">
|
||||
<i class="fas fa-times mr-2"></i>
|
||||
Cancel
|
||||
</a>
|
||||
@@ -198,7 +199,7 @@ ob_start();
|
||||
|
||||
<!-- Info Cards -->
|
||||
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div class="bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center">
|
||||
@@ -206,8 +207,8 @@ ob_start();
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-1">How It Works</h3>
|
||||
<p class="text-xs text-gray-600 leading-relaxed">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">How It Works</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 leading-relaxed">
|
||||
Paste domain names or upload a CSV/JSON file. The system will fetch WHOIS information
|
||||
for each domain automatically. This may take a few moments depending on how many domains you're adding.
|
||||
</p>
|
||||
@@ -215,7 +216,7 @@ ob_start();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-orange-50 border border-orange-200 rounded-lg p-4">
|
||||
<div class="bg-orange-50 dark:bg-orange-500/10 border border-orange-200 dark:border-orange-800 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-10 h-10 bg-orange-500 rounded-lg flex items-center justify-center">
|
||||
@@ -223,8 +224,8 @@ ob_start();
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-1">Important Notes</h3>
|
||||
<ul class="text-xs text-gray-600 space-y-1">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">Important Notes</h3>
|
||||
<ul class="text-xs text-gray-600 dark:text-slate-400 space-y-1">
|
||||
<li class="flex items-start">
|
||||
<i class="fas fa-circle text-orange-500 mt-1 mr-2" style="font-size: 6px;"></i>
|
||||
<span>Duplicate domains will be skipped</span>
|
||||
@@ -244,8 +245,10 @@ ob_start();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Tab switching
|
||||
function switchTab(tab) {
|
||||
document.getElementById('panel-paste').classList.toggle('hidden', tab !== 'paste');
|
||||
document.getElementById('panel-import').classList.toggle('hidden', tab !== 'import');
|
||||
@@ -254,18 +257,17 @@ function switchTab(tab) {
|
||||
const importTab = document.getElementById('tab-import');
|
||||
|
||||
[pasteTab, importTab].forEach(btn => {
|
||||
btn.classList.remove('border-primary', 'text-primary', 'bg-white', 'border-transparent', 'text-gray-500');
|
||||
btn.classList.remove('border-primary', 'text-primary', 'bg-white', 'dark:bg-slate-800', 'border-transparent', 'text-gray-500', 'dark:text-slate-400');
|
||||
});
|
||||
const active = tab === 'paste' ? pasteTab : importTab;
|
||||
const inactive = tab === 'paste' ? importTab : pasteTab;
|
||||
active.classList.add('border-primary', 'text-primary', 'bg-white');
|
||||
inactive.classList.add('border-transparent', 'text-gray-500');
|
||||
active.classList.add('border-primary', 'text-primary', 'bg-white', 'dark:bg-slate-800');
|
||||
inactive.classList.add('border-transparent', 'text-gray-500', 'dark:text-slate-400');
|
||||
}
|
||||
|
||||
let tags = [];
|
||||
|
||||
// Available tags with their colors from the database
|
||||
const availableTags = <?= json_encode($availableTags) ?>;
|
||||
const availableTags = {{ availableTags|json_encode|raw }};
|
||||
const tagColors = {};
|
||||
availableTags.forEach(tag => {
|
||||
tagColors[tag.name] = tag.color;
|
||||
@@ -274,12 +276,10 @@ availableTags.forEach(tag => {
|
||||
function addTag(tagName) {
|
||||
tagName = tagName.trim().toLowerCase();
|
||||
|
||||
// Validate tag (alphanumeric and hyphens only)
|
||||
if (!/^[a-z0-9-]+$/.test(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if tag already exists
|
||||
if (tags.includes(tagName)) {
|
||||
return;
|
||||
}
|
||||
@@ -288,7 +288,6 @@ function addTag(tagName) {
|
||||
updateTagsDisplay();
|
||||
updateHiddenInput();
|
||||
|
||||
// Clear input
|
||||
document.getElementById('tags-input').value = '';
|
||||
}
|
||||
|
||||
@@ -303,7 +302,7 @@ function updateTagsDisplay() {
|
||||
display.innerHTML = '';
|
||||
|
||||
if (tags.length === 0) {
|
||||
display.innerHTML = '<span class="text-xs text-gray-400 italic">No tags added yet</span>';
|
||||
display.innerHTML = '<span class="text-xs text-gray-400 dark:text-slate-500 italic">No tags added yet</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -338,17 +337,14 @@ function addTagFromInput() {
|
||||
const value = input.value.trim();
|
||||
|
||||
if (value) {
|
||||
// Handle multiple tags separated by commas
|
||||
const newTags = value.split(',').map(t => t.trim().toLowerCase()).filter(t => t);
|
||||
newTags.forEach(tag => addTag(tag));
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize display
|
||||
updateTagsDisplay();
|
||||
|
||||
// --- Domain Import drag-and-drop & loading ---
|
||||
(function() {
|
||||
const dropzone = document.getElementById('domainDropzone');
|
||||
const fileInput = document.getElementById('domainFileInput');
|
||||
@@ -371,7 +367,7 @@ updateTagsDisplay();
|
||||
fileSize.textContent = formatSize(file.size);
|
||||
content.classList.add('hidden');
|
||||
fileInfo.classList.remove('hidden');
|
||||
dropzone.classList.remove('border-gray-300');
|
||||
dropzone.classList.remove('border-gray-300', 'dark:border-slate-600');
|
||||
dropzone.classList.add('border-primary', 'bg-primary/5');
|
||||
}
|
||||
|
||||
@@ -379,7 +375,7 @@ updateTagsDisplay();
|
||||
fileInput.value = '';
|
||||
content.classList.remove('hidden');
|
||||
fileInfo.classList.add('hidden');
|
||||
dropzone.classList.add('border-gray-300');
|
||||
dropzone.classList.add('border-gray-300', 'dark:border-slate-600');
|
||||
dropzone.classList.remove('border-primary', 'bg-primary/5');
|
||||
}
|
||||
|
||||
@@ -397,7 +393,7 @@ updateTagsDisplay();
|
||||
dropzone.addEventListener(evt, function(e) {
|
||||
e.preventDefault();
|
||||
dropzone.classList.add('border-primary', 'bg-primary/5');
|
||||
dropzone.classList.remove('border-gray-300');
|
||||
dropzone.classList.remove('border-gray-300', 'dark:border-slate-600');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -406,7 +402,7 @@ updateTagsDisplay();
|
||||
e.preventDefault();
|
||||
if (!fileInput.files.length) {
|
||||
dropzone.classList.remove('border-primary', 'bg-primary/5');
|
||||
dropzone.classList.add('border-gray-300');
|
||||
dropzone.classList.add('border-gray-300', 'dark:border-slate-600');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -426,9 +422,4 @@ updateTagsDisplay();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include __DIR__ . '/../layout/base.php';
|
||||
?>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,57 +1,58 @@
|
||||
<?php
|
||||
$title = 'Add New Domain';
|
||||
$pageTitle = 'Add New Domain';
|
||||
$pageDescription = 'Start monitoring a new domain';
|
||||
$pageIcon = 'fas fa-plus-circle';
|
||||
ob_start();
|
||||
?>
|
||||
{% extends 'layout/base.twig' %}
|
||||
|
||||
{% set title = 'Add New Domain' %}
|
||||
{% set pageTitle = 'Add New Domain' %}
|
||||
{% set pageDescription = 'Start monitoring a new domain' %}
|
||||
{% set pageIcon = 'fas fa-plus-circle' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Main Form -->
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold text-gray-900 flex items-center">
|
||||
<i class="fas fa-globe text-gray-400 mr-2 text-sm"></i>
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-slate-700">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<i class="fas fa-globe text-gray-400 dark:text-slate-500 mr-2 text-sm"></i>
|
||||
Domain Information
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<form method="POST" action="/domains/store" class="space-y-5">
|
||||
<?= csrf_field() ?>
|
||||
{{ csrf_field() }}
|
||||
<!-- Domain Name -->
|
||||
<div>
|
||||
<label for="domain_name" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="domain_name" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Domain Name *
|
||||
</label>
|
||||
<input type="text"
|
||||
id="domain_name"
|
||||
name="domain_name"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
|
||||
placeholder="example.com"
|
||||
required
|
||||
autofocus>
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
Enter the domain name without http:// or https://
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
<div>
|
||||
<label for="tags-input" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="tags-input" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Tags
|
||||
<span class="text-gray-400 font-normal">(Optional)</span>
|
||||
<span class="text-gray-400 dark:text-slate-500 font-normal">(Optional)</span>
|
||||
</label>
|
||||
|
||||
<!-- Tag Display Area -->
|
||||
<div id="tags-display" class="min-h-[40px] p-2 border border-gray-300 rounded-lg mb-2 flex flex-wrap gap-1.5 bg-gray-50"></div>
|
||||
<div id="tags-display" class="min-h-[40px] p-2 border border-gray-300 dark:border-slate-600 rounded-lg mb-2 flex flex-wrap gap-1.5 bg-gray-50 dark:bg-slate-900"></div>
|
||||
|
||||
<!-- Tag Input -->
|
||||
<div class="relative">
|
||||
<i class="fas fa-tag absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-sm"></i>
|
||||
<i class="fas fa-tag absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-slate-500 text-sm"></i>
|
||||
<input type="text"
|
||||
id="tags-input"
|
||||
class="w-full pl-10 pr-20 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
|
||||
class="w-full pl-10 pr-20 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
|
||||
placeholder="Type any tag and press Enter or comma..."
|
||||
onkeydown="handleTagInput(event)">
|
||||
<button type="button" onclick="addTagFromInput()" class="absolute right-2 top-1/2 transform -translate-y-1/2 px-3 py-1 bg-primary text-white text-xs rounded hover:bg-primary-dark">
|
||||
@@ -62,40 +63,40 @@ ob_start();
|
||||
<!-- Hidden input to store tags for form submission -->
|
||||
<input type="hidden" id="tags" name="tags" value="">
|
||||
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
Type any custom tag (letters, numbers, hyphens). Press <kbd class="px-1 py-0.5 bg-gray-200 rounded text-xs">Enter</kbd> or <kbd class="px-1 py-0.5 bg-gray-200 rounded text-xs">,</kbd> to add.
|
||||
Type any custom tag (letters, numbers, hyphens). Press <kbd class="px-1 py-0.5 bg-gray-200 dark:bg-slate-600 rounded text-xs">Enter</kbd> or <kbd class="px-1 py-0.5 bg-gray-200 dark:bg-slate-600 rounded text-xs">,</kbd> to add.
|
||||
</p>
|
||||
|
||||
<!-- Available Tags -->
|
||||
<div class="mt-2">
|
||||
<p class="text-xs text-gray-600 mb-1.5">💡 Available Tags:</p>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 mb-1.5">💡 Available Tags:</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<?php foreach ($availableTags as $tag): ?>
|
||||
<button type="button" onclick="addTag('<?= htmlspecialchars($tag['name']) ?>')"
|
||||
class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium border <?= htmlspecialchars($tag['color']) ?> hover:opacity-80 transition-colors">
|
||||
{% for tag in availableTags %}
|
||||
<button type="button" onclick="addTag('{{ tag.name }}')"
|
||||
class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium border {{ tag.color }} hover:opacity-80 transition-colors">
|
||||
<i class="fas fa-plus mr-1" style="font-size: 8px;"></i>
|
||||
<?= htmlspecialchars($tag['name']) ?>
|
||||
{{ tag.name }}
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Group -->
|
||||
<div>
|
||||
<label for="notification_group_id" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="notification_group_id" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Notification Group
|
||||
</label>
|
||||
<select id="notification_group_id"
|
||||
name="notification_group_id"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
class="w-full px-3 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
<option value="">-- No Group (No notifications) --</option>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
{% for group in groups %}
|
||||
<option value="{{ group.id }}">{{ group.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
Optional: Assign to a notification group to receive expiry alerts
|
||||
</p>
|
||||
</div>
|
||||
@@ -108,7 +109,7 @@ ob_start();
|
||||
Add Domain
|
||||
</button>
|
||||
<a href="/domains"
|
||||
class="inline-flex items-center justify-center px-5 py-2.5 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors text-sm">
|
||||
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">
|
||||
<i class="fas fa-times mr-2"></i>
|
||||
Cancel
|
||||
</a>
|
||||
@@ -120,7 +121,7 @@ ob_start();
|
||||
<!-- Info Cards -->
|
||||
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- How it works -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div class="bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center">
|
||||
@@ -128,8 +129,8 @@ ob_start();
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-1">How It Works</h3>
|
||||
<p class="text-xs text-gray-600 leading-relaxed">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">How It Works</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 leading-relaxed">
|
||||
When you add a domain, we automatically fetch its WHOIS information including
|
||||
expiration date, registrar, nameservers, and other important details. This may take a few seconds.
|
||||
</p>
|
||||
@@ -138,7 +139,7 @@ ob_start();
|
||||
</div>
|
||||
|
||||
<!-- What we track -->
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<div class="bg-green-50 dark:bg-green-500/10 border border-green-200 dark:border-green-800 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-10 h-10 bg-green-500 rounded-lg flex items-center justify-center">
|
||||
@@ -146,8 +147,8 @@ ob_start();
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-1">What We Track</h3>
|
||||
<ul class="text-xs text-gray-600 space-y-1">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">What We Track</h3>
|
||||
<ul class="text-xs text-gray-600 dark:text-slate-400 space-y-1">
|
||||
<li class="flex items-center">
|
||||
<i class="fas fa-circle text-green-500" style="font-size: 6px;"></i>
|
||||
<span class="ml-2">Domain expiration date</span>
|
||||
@@ -171,11 +172,13 @@ ob_start();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
let tags = [];
|
||||
|
||||
// Available tags with their colors from the database
|
||||
const availableTags = <?= json_encode($availableTags) ?>;
|
||||
const availableTags = {{ availableTags|json_encode|raw }};
|
||||
const tagColors = {};
|
||||
availableTags.forEach(tag => {
|
||||
tagColors[tag.name] = tag.color;
|
||||
@@ -184,12 +187,10 @@ availableTags.forEach(tag => {
|
||||
function addTag(tagName) {
|
||||
tagName = tagName.trim().toLowerCase();
|
||||
|
||||
// Validate tag (alphanumeric and hyphens only)
|
||||
if (!/^[a-z0-9-]+$/.test(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if tag already exists
|
||||
if (tags.includes(tagName)) {
|
||||
return;
|
||||
}
|
||||
@@ -198,7 +199,6 @@ function addTag(tagName) {
|
||||
updateTagsDisplay();
|
||||
updateHiddenInput();
|
||||
|
||||
// Clear input
|
||||
document.getElementById('tags-input').value = '';
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ function updateTagsDisplay() {
|
||||
display.innerHTML = '';
|
||||
|
||||
if (tags.length === 0) {
|
||||
display.innerHTML = '<span class="text-xs text-gray-400 italic">No tags added yet</span>';
|
||||
display.innerHTML = '<span class="text-xs text-gray-400 dark:text-slate-500 italic">No tags added yet</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,18 +248,12 @@ function addTagFromInput() {
|
||||
const value = input.value.trim();
|
||||
|
||||
if (value) {
|
||||
// Handle multiple tags separated by commas
|
||||
const newTags = value.split(',').map(t => t.trim().toLowerCase()).filter(t => t);
|
||||
newTags.forEach(tag => addTag(tag));
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize display
|
||||
updateTagsDisplay();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include __DIR__ . '/../layout/base.php';
|
||||
?>
|
||||
{% endblock %}
|
||||
@@ -1,60 +1,61 @@
|
||||
<?php
|
||||
$title = 'Edit Domain';
|
||||
$pageTitle = 'Edit Domain';
|
||||
$pageDescription = htmlspecialchars($domain['domain_name']);
|
||||
$pageIcon = 'fas fa-edit';
|
||||
ob_start();
|
||||
?>
|
||||
{% extends 'layout/base.twig' %}
|
||||
|
||||
{% set title = 'Edit Domain' %}
|
||||
{% set pageTitle = 'Edit Domain' %}
|
||||
{% set pageDescription = domain.domain_name %}
|
||||
{% set pageIcon = 'fas fa-edit' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Main Form -->
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold text-gray-900 flex items-center">
|
||||
<i class="fas fa-cog text-gray-400 mr-2 text-sm"></i>
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-slate-700">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<i class="fas fa-cog text-gray-400 dark:text-slate-500 mr-2 text-sm"></i>
|
||||
Domain Settings
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<form method="POST" action="/domains/<?= $domain['id'] ?>/update" class="space-y-5">
|
||||
<?= csrf_field() ?>
|
||||
<form method="POST" action="/domains/{{ domain.id }}/update" class="space-y-5">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<!-- Domain Name (Read-only) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Domain Name
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input type="text"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-600 cursor-not-allowed text-sm"
|
||||
value="<?= htmlspecialchars($domain['domain_name']) ?>"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-gray-50 dark:bg-slate-900 text-gray-600 dark:text-slate-400 cursor-not-allowed text-sm"
|
||||
value="{{ domain.domain_name }}"
|
||||
disabled>
|
||||
<div class="absolute right-3 top-1/2 transform -translate-y-1/2">
|
||||
<i class="fas fa-lock text-gray-400 text-xs"></i>
|
||||
<i class="fas fa-lock text-gray-400 dark:text-slate-500 text-xs"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
Domain name cannot be changed after creation
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
<div>
|
||||
<label for="tags-input" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="tags-input" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Tags
|
||||
<span class="text-gray-400 font-normal">(Optional)</span>
|
||||
<span class="text-gray-400 dark:text-slate-500 font-normal">(Optional)</span>
|
||||
</label>
|
||||
|
||||
<!-- Tag Display Area -->
|
||||
<div id="tags-display" class="min-h-[40px] p-2 border border-gray-300 rounded-lg mb-2 flex flex-wrap gap-1.5 bg-gray-50"></div>
|
||||
<div id="tags-display" class="min-h-[40px] p-2 border border-gray-300 dark:border-slate-600 rounded-lg mb-2 flex flex-wrap gap-1.5 bg-gray-50 dark:bg-slate-900"></div>
|
||||
|
||||
<!-- Tag Input -->
|
||||
<div class="relative">
|
||||
<i class="fas fa-tag absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-sm"></i>
|
||||
<i class="fas fa-tag absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-slate-500 text-sm"></i>
|
||||
<input type="text"
|
||||
id="tags-input"
|
||||
class="w-full pl-10 pr-20 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
|
||||
class="w-full pl-10 pr-20 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
|
||||
placeholder="Type any tag and press Enter or comma..."
|
||||
onkeydown="handleTagInput(event)">
|
||||
<button type="button" onclick="addTagFromInput()" class="absolute right-2 top-1/2 transform -translate-y-1/2 px-3 py-1 bg-primary text-white text-xs rounded hover:bg-primary-dark">
|
||||
@@ -63,91 +64,91 @@ ob_start();
|
||||
</div>
|
||||
|
||||
<!-- Hidden input to store tags for form submission -->
|
||||
<input type="hidden" id="tags" name="tags" value="<?= htmlspecialchars($domain['tags'] ?? '') ?>">
|
||||
<input type="hidden" id="tags" name="tags" value="{{ domain.tags|default('') }}">
|
||||
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
Type any custom tag (letters, numbers, hyphens). Press <kbd class="px-1 py-0.5 bg-gray-200 rounded text-xs">Enter</kbd> or <kbd class="px-1 py-0.5 bg-gray-200 rounded text-xs">,</kbd> to add.
|
||||
Type any custom tag (letters, numbers, hyphens). Press <kbd class="px-1 py-0.5 bg-gray-200 dark:bg-slate-600 rounded text-xs">Enter</kbd> or <kbd class="px-1 py-0.5 bg-gray-200 dark:bg-slate-600 rounded text-xs">,</kbd> to add.
|
||||
</p>
|
||||
|
||||
<!-- Available Tags -->
|
||||
<div class="mt-2">
|
||||
<p class="text-xs text-gray-600 mb-1.5">💡 Available Tags:</p>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 mb-1.5">💡 Available Tags:</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<?php foreach ($availableTags as $tag): ?>
|
||||
<button type="button" onclick="addTag('<?= htmlspecialchars($tag['name']) ?>')"
|
||||
class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium border <?= htmlspecialchars($tag['color']) ?> hover:opacity-80 transition-colors">
|
||||
{% for tag in availableTags %}
|
||||
<button type="button" onclick="addTag('{{ tag.name }}')"
|
||||
class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium border {{ tag.color }} hover:opacity-80 transition-colors">
|
||||
<i class="fas fa-plus mr-1" style="font-size: 8px;"></i>
|
||||
<?= htmlspecialchars($tag['name']) ?>
|
||||
{{ tag.name }}
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Group -->
|
||||
<div>
|
||||
<label for="notification_group_id" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="notification_group_id" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Notification Group
|
||||
</label>
|
||||
<select id="notification_group_id"
|
||||
name="notification_group_id"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
class="w-full px-3 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
<option value="">-- No Group (No notifications) --</option>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>"
|
||||
<?= $domain['notification_group_id'] == $group['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($group['name']) ?>
|
||||
{% for group in groups %}
|
||||
<option value="{{ group.id }}"
|
||||
{{ domain.notification_group_id == group.id ? 'selected' : '' }}>
|
||||
{{ group.name }}
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
Change the notification group or remove it to stop receiving alerts
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Manual Expiration Date -->
|
||||
<div>
|
||||
<label for="manual_expiration_date" class="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
<label for="manual_expiration_date" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
|
||||
Manual Expiration Date
|
||||
<span class="text-gray-400 font-normal">(Optional)</span>
|
||||
<span class="text-gray-400 dark:text-slate-500 font-normal">(Optional)</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<i class="fas fa-calendar-alt absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-sm"></i>
|
||||
<i class="fas fa-calendar-alt absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-slate-500 text-sm"></i>
|
||||
<input type="date"
|
||||
id="manual_expiration_date"
|
||||
name="manual_expiration_date"
|
||||
value="<?= $domain['expiration_date'] ? date('Y-m-d', strtotime($domain['expiration_date'])) : '' ?>"
|
||||
class="w-full pl-10 pr-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
value="{{ domain.expiration_date ? domain.expiration_date|date('Y-m-d') : '' }}"
|
||||
class="w-full pl-10 pr-3 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-gray-500">
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
Set a manual expiration date if WHOIS/RDAP doesn't provide one (e.g., for .nl domains).
|
||||
This will be used for expiration notifications and status calculations.
|
||||
</p>
|
||||
<?php if ($domain['expiration_date']): ?>
|
||||
<p class="mt-1 text-xs text-green-600">
|
||||
{% if domain.expiration_date %}
|
||||
<p class="mt-1 text-xs text-green-600 dark:text-green-400">
|
||||
<i class="fas fa-check-circle mr-1"></i>
|
||||
Current expiration date: <?= $domain['expiration_date'] ? date('M j, Y', strtotime($domain['expiration_date'])) : 'Unknown' ?>
|
||||
Current expiration date: {{ domain.expiration_date|date('M j, Y') }}
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<p class="mt-1 text-xs text-amber-600">
|
||||
{% else %}
|
||||
<p class="mt-1 text-xs text-amber-600 dark:text-amber-400">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>
|
||||
No expiration date available from WHOIS/RDAP. Consider setting a manual date.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Active Monitoring -->
|
||||
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
||||
<div class="bg-gray-50 dark:bg-slate-900 rounded-lg p-4 border border-gray-200 dark:border-slate-700">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="checkbox"
|
||||
name="is_active"
|
||||
<?= $domain['is_active'] ? 'checked' : '' ?>
|
||||
class="w-4 h-4 text-primary border-gray-300 rounded focus:ring-primary cursor-pointer">
|
||||
{{ domain.is_active ? 'checked' : '' }}
|
||||
class="w-4 h-4 text-primary border-gray-300 dark:border-slate-600 rounded focus:ring-primary cursor-pointer">
|
||||
<div class="ml-3">
|
||||
<span class="text-sm font-medium text-gray-900">Enable Active Monitoring</span>
|
||||
<p class="text-xs text-gray-600 mt-0.5">When enabled, this domain will be checked regularly and notifications will be sent</p>
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-white">Enable Active Monitoring</span>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 mt-0.5">When enabled, this domain will be checked regularly and notifications will be sent</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -159,8 +160,8 @@ ob_start();
|
||||
<i class="fas fa-save mr-2"></i>
|
||||
Update Domain
|
||||
</button>
|
||||
<a href="<?= htmlspecialchars($referrer ?? '/domains/' . $domain['id']) ?>"
|
||||
class="inline-flex items-center justify-center px-5 py-2.5 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors text-sm">
|
||||
<a href="{{ referrer|default('/domains/' ~ domain.id) }}"
|
||||
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">
|
||||
<i class="fas fa-times mr-2"></i>
|
||||
Cancel
|
||||
</a>
|
||||
@@ -171,37 +172,38 @@ ob_start();
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<a href="/domains/<?= $domain['id'] ?>"
|
||||
class="flex items-center justify-center p-3 bg-white border border-gray-200 rounded-lg hover:border-blue-300 hover:bg-blue-50 transition-colors group">
|
||||
<i class="fas fa-eye text-blue-600 mr-2 text-sm"></i>
|
||||
<span class="text-sm font-medium text-gray-700">View Details</span>
|
||||
<a href="/domains/{{ domain.id }}"
|
||||
class="flex items-center justify-center p-3 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg hover:border-blue-300 dark:hover:border-blue-700 hover:bg-blue-50 dark:hover:bg-blue-500/10 transition-colors group">
|
||||
<i class="fas fa-eye text-blue-600 dark:text-blue-400 mr-2 text-sm"></i>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-slate-300">View Details</span>
|
||||
</a>
|
||||
<form method="POST" action="/domains/<?= $domain['id'] ?>/refresh" class="m-0">
|
||||
<?= csrf_field() ?>
|
||||
<form method="POST" action="/domains/{{ domain.id }}/refresh" class="m-0">
|
||||
{{ csrf_field() }}
|
||||
<button type="submit"
|
||||
class="w-full flex items-center justify-center p-3 bg-white border border-gray-200 rounded-lg hover:border-green-300 hover:bg-green-50 transition-colors group">
|
||||
<i class="fas fa-sync-alt text-green-600 mr-2 text-sm"></i>
|
||||
<span class="text-sm font-medium text-gray-700">Refresh WHOIS</span>
|
||||
class="w-full flex items-center justify-center p-3 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg hover:border-green-300 dark:hover:border-green-700 hover:bg-green-50 dark:hover:bg-green-500/10 transition-colors group">
|
||||
<i class="fas fa-sync-alt text-green-600 dark:text-green-400 mr-2 text-sm"></i>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-slate-300">Refresh WHOIS</span>
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="/domains/<?= $domain['id'] ?>/delete" onsubmit="return confirm('Delete this domain permanently?')" class="m-0">
|
||||
<?= csrf_field() ?>
|
||||
<form method="POST" action="/domains/{{ domain.id }}/delete" onsubmit="return confirm('Delete this domain permanently?')" class="m-0">
|
||||
{{ csrf_field() }}
|
||||
<button type="submit"
|
||||
class="w-full flex items-center justify-center p-3 bg-white border border-gray-200 rounded-lg hover:border-red-300 hover:bg-red-50 transition-colors group">
|
||||
<i class="fas fa-trash text-red-600 mr-2 text-sm"></i>
|
||||
<span class="text-sm font-medium text-gray-700">Delete Domain</span>
|
||||
class="w-full flex items-center justify-center p-3 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg hover:border-red-300 dark:hover:border-red-700 hover:bg-red-50 dark:hover:bg-red-500/10 transition-colors group">
|
||||
<i class="fas fa-trash text-red-600 dark:text-red-400 mr-2 text-sm"></i>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-slate-300">Delete Domain</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Initialize tags from existing domain data
|
||||
const existingTags = '<?= htmlspecialchars($domain['tags'] ?? '') ?>';
|
||||
const existingTags = {{ domain.tags|default('')|json_encode|raw }};
|
||||
let tags = existingTags ? existingTags.split(',').map(t => t.trim().toLowerCase()).filter(t => t) : [];
|
||||
|
||||
// Available tags with their colors from the database
|
||||
const availableTags = <?= json_encode($availableTags) ?>;
|
||||
const availableTags = {{ availableTags|json_encode|raw }};
|
||||
const tagColors = {};
|
||||
availableTags.forEach(tag => {
|
||||
tagColors[tag.name] = tag.color;
|
||||
@@ -210,12 +212,10 @@ availableTags.forEach(tag => {
|
||||
function addTag(tagName) {
|
||||
tagName = tagName.trim().toLowerCase();
|
||||
|
||||
// Validate tag (alphanumeric and hyphens only)
|
||||
if (!/^[a-z0-9-]+$/.test(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if tag already exists
|
||||
if (tags.includes(tagName)) {
|
||||
return;
|
||||
}
|
||||
@@ -224,7 +224,6 @@ function addTag(tagName) {
|
||||
updateTagsDisplay();
|
||||
updateHiddenInput();
|
||||
|
||||
// Clear input
|
||||
document.getElementById('tags-input').value = '';
|
||||
}
|
||||
|
||||
@@ -239,7 +238,7 @@ function updateTagsDisplay() {
|
||||
display.innerHTML = '';
|
||||
|
||||
if (tags.length === 0) {
|
||||
display.innerHTML = '<span class="text-xs text-gray-400 italic">No tags added yet</span>';
|
||||
display.innerHTML = '<span class="text-xs text-gray-400 dark:text-slate-500 italic">No tags added yet</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -274,18 +273,12 @@ function addTagFromInput() {
|
||||
const value = input.value.trim();
|
||||
|
||||
if (value) {
|
||||
// Handle multiple tags separated by commas
|
||||
const newTags = value.split(',').map(t => t.trim().toLowerCase()).filter(t => t);
|
||||
newTags.forEach(tag => addTag(tag));
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize display
|
||||
updateTagsDisplay();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include __DIR__ . '/../layout/base.php';
|
||||
?>
|
||||
{% endblock %}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,446 +0,0 @@
|
||||
<?php
|
||||
$title = 'Domain Details';
|
||||
$pageTitle = htmlspecialchars($domain['domain_name']);
|
||||
$pageDescription = 'Domain information and monitoring status';
|
||||
$pageIcon = 'fas fa-globe';
|
||||
|
||||
// Data already formatted by controller via DomainHelper
|
||||
$whoisData = json_decode($domain['whois_data'] ?? '{}', true);
|
||||
$daysLeft = $domain['daysLeft'];
|
||||
$domainStatus = $domain['displayStatus'];
|
||||
$expiryColor = $domain['expiryColor'];
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<!-- Top Action Bar -->
|
||||
<div class="mb-3 flex flex-wrap gap-2 justify-between items-center">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<?php
|
||||
// Status badge data prepared by DomainHelper in controller
|
||||
$statusClass = $domain['statusClass'];
|
||||
$statusText = $domain['statusText'];
|
||||
$statusIcon = $domain['statusIcon'];
|
||||
?>
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold <?= $statusClass ?>">
|
||||
<i class="fas <?= $statusIcon ?> mr-1.5"></i>
|
||||
<?= $statusText ?>
|
||||
</span>
|
||||
<?php if ($domainStatus !== 'available'): ?>
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-<?= $expiryColor ?>-100 text-<?= $expiryColor ?>-800 border border-<?= $expiryColor ?>-200">
|
||||
<i class="fas fa-calendar-alt mr-1.5"></i>
|
||||
<?= $daysLeft !== null ? $daysLeft . ' days left' : 'No expiry date' ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-100 text-indigo-800 border border-indigo-200">
|
||||
<i class="fas fa-<?= $domain['is_active'] ? 'check-circle' : 'pause-circle' ?> mr-1.5"></i>
|
||||
<?= $domain['is_active'] ? 'Monitoring Active' : 'Monitoring Paused' ?>
|
||||
</span>
|
||||
|
||||
<!-- Tags Display -->
|
||||
<?php
|
||||
$tags = !empty($domain['tags']) ? explode(',', $domain['tags']) : [];
|
||||
$tagColors = !empty($domain['tag_colors']) ? explode('|', $domain['tag_colors']) : [];
|
||||
|
||||
// Create a mapping of tag names to their colors
|
||||
$tagColorMap = [];
|
||||
foreach ($availableTags as $availableTag) {
|
||||
$tagColorMap[$availableTag['name']] = $availableTag['color'];
|
||||
}
|
||||
|
||||
foreach ($tags as $index => $tag):
|
||||
$tag = trim($tag);
|
||||
// Use the color from the database if available, otherwise use the stored color, otherwise default
|
||||
$colorClass = $tagColorMap[$tag] ?? (isset($tagColors[$index]) ? $tagColors[$index] : 'bg-gray-100 text-gray-700 border-gray-200');
|
||||
?>
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold border <?= $colorClass ?>">
|
||||
<i class="fas fa-tag mr-1.5" style="font-size: 10px;"></i>
|
||||
<?= htmlspecialchars(ucfirst($tag)) ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<form method="POST" action="/domains/<?= $domain['id'] ?>/refresh" class="inline">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="inline-flex items-center justify-center px-3 py-2 bg-green-600 text-white text-xs rounded-lg hover:bg-green-700 transition-colors font-medium min-w-[80px] h-[32px]">
|
||||
<i class="fas fa-sync-alt mr-1.5"></i>
|
||||
Refresh
|
||||
</button>
|
||||
</form>
|
||||
<a href="/domains/<?= $domain['id'] ?>/edit?from=/domains/<?= $domain['id'] ?>" class="inline-flex items-center justify-center px-3 py-2 bg-blue-600 text-white text-xs rounded-lg hover:bg-blue-700 transition-colors font-medium min-w-[80px] h-[32px]">
|
||||
<i class="fas fa-edit mr-1.5"></i>
|
||||
Edit
|
||||
</a>
|
||||
<form method="POST" action="/domains/<?= $domain['id'] ?>/delete" onsubmit="return confirm('Delete this domain?')" class="inline">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="inline-flex items-center justify-center px-3 py-2 bg-red-600 text-white text-xs rounded-lg hover:bg-red-700 transition-colors font-medium min-w-[80px] h-[32px]">
|
||||
<i class="fas fa-trash mr-1.5"></i>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
<a href="/domains" class="inline-flex items-center justify-center px-3 py-2 border border-gray-300 text-gray-700 text-xs rounded-lg hover:bg-gray-50 transition-colors font-medium min-w-[80px] h-[32px]">
|
||||
<i class="fas fa-arrow-left mr-1.5"></i>
|
||||
Back
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main 2-Column Layout -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
||||
|
||||
<!-- LEFT COLUMN -->
|
||||
<div class="space-y-3">
|
||||
|
||||
<!-- Registration Details -->
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
|
||||
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-building text-gray-400 mr-2" style="font-size: 10px;"></i>
|
||||
Registration Details
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-xs">
|
||||
<div>
|
||||
<label class="text-gray-500 font-medium block mb-0.5">Registrar</label>
|
||||
<p class="text-gray-900 font-semibold"><?= htmlspecialchars($domain['registrar'] ?? 'Unknown') ?></p>
|
||||
</div>
|
||||
<?php if (!empty($domain['registrar_url'])): ?>
|
||||
<div>
|
||||
<label class="text-gray-500 font-medium block mb-0.5">Registrar URL</label>
|
||||
<a href="<?= htmlspecialchars($domain['registrar_url']) ?>" target="_blank" class="text-blue-600 hover:text-blue-800 flex items-center">
|
||||
<i class="fas fa-external-link-alt mr-1" style="font-size: 9px;"></i>
|
||||
Visit
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($domain['abuse_email'])): ?>
|
||||
<div>
|
||||
<label class="text-gray-500 font-medium block mb-0.5">Abuse Contact</label>
|
||||
<a href="mailto:<?= htmlspecialchars($domain['abuse_email']) ?>" class="text-blue-600 hover:text-blue-800">
|
||||
<?= htmlspecialchars($domain['abuse_email']) ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($whoisData['whois_server'])): ?>
|
||||
<div>
|
||||
<label class="text-gray-500 font-medium block mb-0.5">WHOIS Server</label>
|
||||
<p class="text-gray-900 font-mono"><?= htmlspecialchars($whoisData['whois_server']) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($whoisData['owner'])): ?>
|
||||
<div class="col-span-2">
|
||||
<label class="text-gray-500 font-medium block mb-0.5">Owner</label>
|
||||
<p class="text-gray-900"><?= htmlspecialchars($whoisData['owner']) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Important Dates -->
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
|
||||
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-calendar text-gray-400 mr-2" style="font-size: 10px;"></i>
|
||||
Important Dates
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="space-y-2">
|
||||
<?php if (!empty($domain['expiration_date'])): ?>
|
||||
<div class="flex items-center justify-between p-2 bg-<?= $expiryColor ?>-50 rounded border border-<?= $expiryColor ?>-200">
|
||||
<div class="flex items-center">
|
||||
<div class="w-7 h-7 bg-<?= $expiryColor ?>-500 rounded flex items-center justify-center mr-2">
|
||||
<i class="fas fa-exclamation-triangle text-white text-xs"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-600 font-medium">
|
||||
Expiration
|
||||
<?php if ($domain['isManualExpiration']): ?>
|
||||
<span class="ml-1 inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800">
|
||||
<i class="fas fa-edit mr-1" style="font-size: 8px;"></i>
|
||||
Manual
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<p class="text-xs font-semibold text-gray-900"><?= $domain['expiration_date'] ? date('M j, Y', strtotime($domain['expiration_date'])) : 'Unknown' ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="px-2 py-1 bg-<?= $expiryColor ?>-100 text-<?= $expiryColor ?>-800 rounded text-xs font-bold">
|
||||
<?= $daysLeft ?> days
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($domain['updated_date'])): ?>
|
||||
<div class="flex items-center p-2 bg-blue-50 rounded border border-blue-200">
|
||||
<div class="w-7 h-7 bg-blue-500 rounded flex items-center justify-center mr-2">
|
||||
<i class="fas fa-clock text-white text-xs"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-600 font-medium">Last Updated</p>
|
||||
<p class="text-xs font-semibold text-gray-900"><?= date('M j, Y', strtotime($domain['updated_date'])) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($whoisData['creation_date'])): ?>
|
||||
<div class="flex items-center p-2 bg-green-50 rounded border border-green-200">
|
||||
<div class="w-7 h-7 bg-green-500 rounded flex items-center justify-center mr-2">
|
||||
<i class="fas fa-calendar-plus text-white text-xs"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-600 font-medium">Created</p>
|
||||
<p class="text-xs font-semibold text-gray-900"><?= date('M j, Y', strtotime($whoisData['creation_date'])) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="flex items-center p-2 bg-indigo-50 rounded border border-indigo-200">
|
||||
<div class="w-7 h-7 bg-indigo-500 rounded flex items-center justify-center mr-2">
|
||||
<i class="fas fa-sync text-white text-xs"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-600 font-medium">Last Checked</p>
|
||||
<p class="text-xs font-semibold text-gray-900"><?= date('M j, Y H:i', strtotime($domain['last_checked'])) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nameservers -->
|
||||
<?php if (!empty($whoisData['nameservers'])): ?>
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
|
||||
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-server text-gray-400 mr-2" style="font-size: 10px;"></i>
|
||||
Nameservers (<?= count($whoisData['nameservers']) ?>)
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="space-y-1.5">
|
||||
<?php foreach ($whoisData['nameservers'] as $index => $ns): ?>
|
||||
<div class="flex items-center p-2 bg-gray-50 rounded hover:bg-gray-100 transition-colors">
|
||||
<div class="w-6 h-6 bg-teal-500 rounded flex items-center justify-center text-white font-bold text-xs mr-2">
|
||||
<?= $index + 1 ?>
|
||||
</div>
|
||||
<p class="font-mono text-xs text-gray-800"><?= htmlspecialchars($ns) ?></p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Domain Status -->
|
||||
<?php if (!empty($domain['parsedStatuses'])): ?>
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
|
||||
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-info-circle text-gray-400 mr-2" style="font-size: 10px;"></i>
|
||||
Domain Status (<?= count($domain['parsedStatuses']) ?>)
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<?php foreach ($domain['parsedStatuses'] as $cleanStatus): ?>
|
||||
<?php
|
||||
// Format status text using helper
|
||||
$readableStatus = \App\Helpers\DomainHelper::formatStatusText($cleanStatus);
|
||||
?>
|
||||
<span class="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs font-medium" title="<?= htmlspecialchars($cleanStatus) ?>">
|
||||
<?= htmlspecialchars($readableStatus) ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- RIGHT COLUMN -->
|
||||
<div class="space-y-3">
|
||||
|
||||
<!-- Notification Group -->
|
||||
<?php if (!empty($domain['group_name'])): ?>
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
|
||||
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-bell text-gray-400 mr-2" style="font-size: 10px;"></i>
|
||||
Notification Group
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center mr-3">
|
||||
<i class="fas fa-users text-green-600"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-sm text-gray-900"><?= htmlspecialchars($domain['group_name']) ?></p>
|
||||
<?php if (!empty($domain['channels'])): ?>
|
||||
<p class="text-xs text-gray-600">
|
||||
<?= $domain['activeChannelCount'] ?? 0 ?> / <?= count($domain['channels']) ?> channels active
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (!empty($domain['channels'])): ?>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<?php foreach ($domain['channels'] as $channel): ?>
|
||||
<div class="flex items-center p-2 rounded <?= $channel['is_active'] ? 'bg-green-50 border border-green-200' : 'bg-gray-50 border border-gray-200' ?>">
|
||||
<i class="fas fa-<?= $channel['is_active'] ? 'check-circle text-green-600' : 'times-circle text-gray-400' ?> mr-2 text-xs"></i>
|
||||
<span class="text-xs font-medium text-gray-700"><?= ucfirst($channel['channel_type']) ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="bg-orange-50 rounded-lg border border-orange-200 p-4">
|
||||
<div class="flex items-start mb-2">
|
||||
<i class="fas fa-exclamation-triangle text-orange-500 mr-2 mt-0.5"></i>
|
||||
<div>
|
||||
<h3 class="text-xs font-semibold text-gray-900">No Group Assigned</h3>
|
||||
<p class="text-xs text-gray-600 mt-0.5">Won't receive notifications</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/domains/<?= $domain['id'] ?>/edit?from=/domains/<?= $domain['id'] ?>" class="block w-full text-center px-3 py-1.5 bg-orange-500 text-white text-xs rounded-lg hover:bg-orange-600 transition-colors font-medium">
|
||||
<i class="fas fa-plus mr-1"></i>
|
||||
Assign Group
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Notes Section -->
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
|
||||
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-sticky-note text-gray-400 mr-2" style="font-size: 10px;"></i>
|
||||
Notes
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<form method="POST" action="/domains/<?= $domain['id'] ?>/update-notes" id="notes-form">
|
||||
<?= csrf_field() ?>
|
||||
<textarea
|
||||
name="notes"
|
||||
id="notes-textarea"
|
||||
rows="6"
|
||||
class="w-full px-3 py-2 text-xs border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||||
placeholder="Add notes about this domain..."><?= htmlspecialchars($domain['notes'] ?? '') ?></textarea>
|
||||
|
||||
<div class="flex gap-2 mt-3">
|
||||
<button
|
||||
type="submit"
|
||||
class="flex-1 inline-flex items-center justify-center px-3 py-2 bg-blue-600 text-white text-xs rounded-lg hover:bg-blue-700 transition-colors font-medium">
|
||||
<i class="fas fa-save mr-1.5"></i>
|
||||
Update Notes
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick="resetNotes()"
|
||||
class="flex-1 inline-flex items-center justify-center px-3 py-2 border border-gray-300 text-gray-700 text-xs rounded-lg hover:bg-gray-50 transition-colors font-medium">
|
||||
<i class="fas fa-times mr-1.5"></i>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification History -->
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
|
||||
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-history text-gray-400 mr-2" style="font-size: 10px;"></i>
|
||||
Notification History (<?= count($logs) ?>)
|
||||
</h3>
|
||||
</div>
|
||||
<div class="overflow-hidden">
|
||||
<?php if (empty($logs)): ?>
|
||||
<div class="p-8 text-center">
|
||||
<i class="fas fa-bell-slash text-gray-300 text-3xl mb-2"></i>
|
||||
<p class="text-xs text-gray-500">No notifications sent yet</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="max-h-96 overflow-y-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-xs">
|
||||
<thead class="bg-gray-50 sticky top-0">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Channel</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Status</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Date</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<?php foreach ($logs as $log): ?>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-3 py-2 whitespace-nowrap">
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<?= ucfirst($log['channel_type']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap">
|
||||
<?php $statusClass = $log['status'] === 'sent' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'; ?>
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium <?= $statusClass ?>">
|
||||
<?= ucfirst($log['status']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap text-gray-600"><?= date('M j, H:i', strtotime($log['sent_at'])) ?></td>
|
||||
<td class="px-3 py-2 text-gray-700 max-w-xs truncate" title="<?= htmlspecialchars($log['message']) ?>">
|
||||
<?= htmlspecialchars($log['message']) ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Raw WHOIS Data (Collapsible) -->
|
||||
<?php if (!empty($domain['whois_data'])): ?>
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<button onclick="toggleWhoisData()" class="w-full px-4 py-2 border-b border-gray-200 bg-gray-50 text-left hover:bg-gray-100 transition-colors">
|
||||
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center justify-between">
|
||||
<span class="flex items-center">
|
||||
<i class="fas fa-code text-gray-400 mr-2" style="font-size: 10px;"></i>
|
||||
Raw WHOIS Data
|
||||
</span>
|
||||
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform" id="whois-chevron"></i>
|
||||
</h3>
|
||||
</button>
|
||||
<div id="whois-data" class="hidden p-4 bg-gray-900 max-h-64 overflow-y-auto">
|
||||
<pre class="text-xs text-green-400 font-mono"><?= htmlspecialchars(json_encode($whoisData, JSON_PRETTY_PRINT)) ?></pre>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleWhoisData() {
|
||||
const dataDiv = document.getElementById('whois-data');
|
||||
const chevron = document.getElementById('whois-chevron');
|
||||
dataDiv.classList.toggle('hidden');
|
||||
chevron.classList.toggle('rotate-180');
|
||||
}
|
||||
|
||||
function resetNotes() {
|
||||
const originalNotes = <?= json_encode($domain['notes'] ?? '') ?>;
|
||||
document.getElementById('notes-textarea').value = originalNotes;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include __DIR__ . '/../layout/base.php';
|
||||
?>
|
||||
431
app/Views/domains/view.twig
Normal file
431
app/Views/domains/view.twig
Normal file
@@ -0,0 +1,431 @@
|
||||
{% extends 'layout/base.twig' %}
|
||||
|
||||
{% set title = 'Domain Details' %}
|
||||
{% set pageTitle = domain.domain_name %}
|
||||
{% set pageDescription = 'Domain information and monitoring status' %}
|
||||
{% set pageIcon = 'fas fa-globe' %}
|
||||
|
||||
{% set daysLeft = domain.daysLeft %}
|
||||
{% set domainStatus = domain.displayStatus %}
|
||||
{% set expiryColor = domain.expiryColor %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Top Action Bar -->
|
||||
<div class="mb-3 flex flex-wrap gap-2 justify-between items-center">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold {{ domain.statusClass }}">
|
||||
<i class="fas {{ domain.statusIcon }} mr-1.5"></i>
|
||||
{{ domain.statusText }}
|
||||
</span>
|
||||
{% if domainStatus != 'available' %}
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-{{ expiryColor }}-100 text-{{ expiryColor }}-800 dark:bg-{{ expiryColor }}-500/10 dark:text-{{ expiryColor }}-400 border border-{{ expiryColor }}-200 dark:border-{{ expiryColor }}-800">
|
||||
<i class="fas fa-calendar-alt mr-1.5"></i>
|
||||
{{ daysLeft is not null ? daysLeft ~ ' days left' : 'No expiry date' }}
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-100 dark:bg-indigo-500/10 text-indigo-800 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-800">
|
||||
<i class="fas fa-{{ domain.is_active ? 'check-circle' : 'pause-circle' }} mr-1.5"></i>
|
||||
{{ domain.is_active ? 'Monitoring Active' : 'Monitoring Paused' }}
|
||||
</span>
|
||||
|
||||
<!-- Tags Display -->
|
||||
{% set domainTags = domain.tags ? domain.tags|split(',') : [] %}
|
||||
{% set tagColorList = domain.tag_colors ? domain.tag_colors|split('|') : [] %}
|
||||
|
||||
{% for tag in domainTags %}
|
||||
{% set tagName = tag|trim %}
|
||||
{% set matchedTags = availableTags|filter(t => t.name == tagName) %}
|
||||
{% if matchedTags|length > 0 %}
|
||||
{% set colorClass = matchedTags|first.color %}
|
||||
{% elseif tagColorList[loop.index0] is defined %}
|
||||
{% set colorClass = tagColorList[loop.index0] %}
|
||||
{% else %}
|
||||
{% set colorClass = 'bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-slate-300 border-gray-200 dark:border-slate-700' %}
|
||||
{% endif %}
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold border {{ colorClass }}">
|
||||
<i class="fas fa-tag mr-1.5" style="font-size: 10px;"></i>
|
||||
{{ tagName|capitalize }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<form method="POST" action="/domains/{{ domain.id }}/refresh" class="inline">
|
||||
{{ csrf_field() }}
|
||||
<button type="submit" class="inline-flex items-center justify-center px-3 py-2 bg-green-600 text-white text-xs rounded-lg hover:bg-green-700 transition-colors font-medium min-w-[80px] h-[32px]">
|
||||
<i class="fas fa-sync-alt mr-1.5"></i>
|
||||
Refresh
|
||||
</button>
|
||||
</form>
|
||||
<a href="/domains/{{ domain.id }}/edit?from=/domains/{{ domain.id }}" class="inline-flex items-center justify-center px-3 py-2 bg-blue-600 text-white text-xs rounded-lg hover:bg-blue-700 transition-colors font-medium min-w-[80px] h-[32px]">
|
||||
<i class="fas fa-edit mr-1.5"></i>
|
||||
Edit
|
||||
</a>
|
||||
<form method="POST" action="/domains/{{ domain.id }}/delete" onsubmit="return confirm('Delete this domain?')" class="inline">
|
||||
{{ csrf_field() }}
|
||||
<button type="submit" class="inline-flex items-center justify-center px-3 py-2 bg-red-600 text-white text-xs rounded-lg hover:bg-red-700 transition-colors font-medium min-w-[80px] h-[32px]">
|
||||
<i class="fas fa-trash mr-1.5"></i>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
<a href="/domains" class="inline-flex items-center justify-center px-3 py-2 border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-slate-300 text-xs rounded-lg hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors font-medium min-w-[80px] h-[32px]">
|
||||
<i class="fas fa-arrow-left mr-1.5"></i>
|
||||
Back
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main 2-Column Layout -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
||||
|
||||
<!-- LEFT COLUMN -->
|
||||
<div class="space-y-3">
|
||||
|
||||
<!-- Registration Details -->
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
|
||||
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-building text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
|
||||
Registration Details
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-xs">
|
||||
<div>
|
||||
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Registrar</label>
|
||||
<p class="text-gray-900 dark:text-white font-semibold">{{ domain.registrar|default('Unknown') }}</p>
|
||||
</div>
|
||||
{% if domain.registrar_url is not empty %}
|
||||
<div>
|
||||
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Registrar URL</label>
|
||||
<a href="{{ domain.registrar_url }}" target="_blank" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 flex items-center">
|
||||
<i class="fas fa-external-link-alt mr-1" style="font-size: 9px;"></i>
|
||||
Visit
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if domain.abuse_email is not empty %}
|
||||
<div>
|
||||
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Abuse Contact</label>
|
||||
<a href="mailto:{{ domain.abuse_email }}" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300">
|
||||
{{ domain.abuse_email }}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if whoisData.whois_server is defined %}
|
||||
<div>
|
||||
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">WHOIS Server</label>
|
||||
<p class="text-gray-900 dark:text-white font-mono">{{ whoisData.whois_server }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if whoisData.owner is defined %}
|
||||
<div class="col-span-2">
|
||||
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Owner</label>
|
||||
<p class="text-gray-900 dark:text-white">{{ whoisData.owner }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Important Dates -->
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
|
||||
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-calendar text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
|
||||
Important Dates
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="space-y-2">
|
||||
{% if domain.expiration_date is not empty %}
|
||||
<div class="flex items-center justify-between p-2 bg-{{ expiryColor }}-50 dark:bg-{{ expiryColor }}-500/10 rounded border border-{{ expiryColor }}-200 dark:border-{{ expiryColor }}-800">
|
||||
<div class="flex items-center">
|
||||
<div class="w-7 h-7 bg-{{ expiryColor }}-500 rounded flex items-center justify-center mr-2">
|
||||
<i class="fas fa-exclamation-triangle text-white text-xs"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 font-medium">
|
||||
Expiration
|
||||
{% if domain.isManualExpiration %}
|
||||
<span class="ml-1 inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-amber-100 dark:bg-amber-500/10 text-amber-800 dark:text-amber-400">
|
||||
<i class="fas fa-edit mr-1" style="font-size: 8px;"></i>
|
||||
Manual
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p class="text-xs font-semibold text-gray-900 dark:text-white">{{ domain.expiration_date ? domain.expiration_date|date('M j, Y') : 'Unknown' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="px-2 py-1 bg-{{ expiryColor }}-100 dark:bg-{{ expiryColor }}-500/20 text-{{ expiryColor }}-800 dark:text-{{ expiryColor }}-400 rounded text-xs font-bold">
|
||||
{{ daysLeft }} days
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if domain.updated_date is not empty %}
|
||||
<div class="flex items-center p-2 bg-blue-50 dark:bg-blue-500/10 rounded border border-blue-200 dark:border-blue-800">
|
||||
<div class="w-7 h-7 bg-blue-500 rounded flex items-center justify-center mr-2">
|
||||
<i class="fas fa-clock text-white text-xs"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 font-medium">Last Updated</p>
|
||||
<p class="text-xs font-semibold text-gray-900 dark:text-white">{{ domain.updated_date|date('M j, Y') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if whoisData.creation_date is defined %}
|
||||
<div class="flex items-center p-2 bg-green-50 dark:bg-green-500/10 rounded border border-green-200 dark:border-green-800">
|
||||
<div class="w-7 h-7 bg-green-500 rounded flex items-center justify-center mr-2">
|
||||
<i class="fas fa-calendar-plus text-white text-xs"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 font-medium">Created</p>
|
||||
<p class="text-xs font-semibold text-gray-900 dark:text-white">{{ whoisData.creation_date|date('M j, Y') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex items-center p-2 bg-indigo-50 dark:bg-indigo-500/10 rounded border border-indigo-200 dark:border-indigo-800">
|
||||
<div class="w-7 h-7 bg-indigo-500 rounded flex items-center justify-center mr-2">
|
||||
<i class="fas fa-sync text-white text-xs"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 font-medium">Last Checked</p>
|
||||
<p class="text-xs font-semibold text-gray-900 dark:text-white">{{ domain.last_checked|date('M j, Y H:i') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nameservers -->
|
||||
{% if whoisData.nameservers is not empty %}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
|
||||
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-server text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
|
||||
Nameservers ({{ whoisData.nameservers|length }})
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="space-y-1.5">
|
||||
{% for ns in whoisData.nameservers %}
|
||||
<div class="flex items-center p-2 bg-gray-50 dark:bg-slate-700 rounded hover:bg-gray-100 dark:hover:bg-slate-600 transition-colors">
|
||||
<div class="w-6 h-6 bg-teal-500 rounded flex items-center justify-center text-white font-bold text-xs mr-2">
|
||||
{{ loop.index }}
|
||||
</div>
|
||||
<p class="font-mono text-xs text-gray-800 dark:text-slate-200">{{ ns }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Domain Status -->
|
||||
{% if domain.parsedStatuses is not empty %}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
|
||||
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-info-circle text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
|
||||
Domain Status ({{ domain.parsedStatuses|length }})
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{% for status in domain.parsedStatuses %}
|
||||
<span class="px-2 py-1 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 rounded text-xs font-medium" title="{{ status }}">
|
||||
{{ status }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- RIGHT COLUMN -->
|
||||
<div class="space-y-3">
|
||||
|
||||
<!-- Notification Group -->
|
||||
{% if domain.group_name is not empty %}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
|
||||
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-bell text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
|
||||
Notification Group
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 bg-green-100 dark:bg-green-500/10 rounded-lg flex items-center justify-center mr-3">
|
||||
<i class="fas fa-users text-green-600 dark:text-green-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-sm text-gray-900 dark:text-white">{{ domain.group_name }}</p>
|
||||
{% if domain.channels is not empty %}
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400">
|
||||
{{ domain.activeChannelCount|default(0) }} / {{ domain.channels|length }} channels active
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if domain.channels is not empty %}
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
{% for channel in domain.channels %}
|
||||
<div class="flex items-center p-2 rounded {{ channel.is_active ? 'bg-green-50 dark:bg-green-500/10 border border-green-200 dark:border-green-800' : 'bg-gray-50 dark:bg-slate-700 border border-gray-200 dark:border-slate-700' }}">
|
||||
<i class="fas fa-{{ channel.is_active ? 'check-circle text-green-600 dark:text-green-400' : 'times-circle text-gray-400 dark:text-slate-500' }} mr-2 text-xs"></i>
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-slate-300">{{ channel.channel_type|capitalize }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="bg-orange-50 dark:bg-orange-500/10 rounded-lg border border-orange-200 dark:border-orange-800 p-4">
|
||||
<div class="flex items-start mb-2">
|
||||
<i class="fas fa-exclamation-triangle text-orange-500 dark:text-orange-400 mr-2 mt-0.5"></i>
|
||||
<div>
|
||||
<h3 class="text-xs font-semibold text-gray-900 dark:text-white">No Group Assigned</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-slate-400 mt-0.5">Won't receive notifications</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/domains/{{ domain.id }}/edit?from=/domains/{{ domain.id }}" class="block w-full text-center px-3 py-1.5 bg-orange-500 text-white text-xs rounded-lg hover:bg-orange-600 transition-colors font-medium">
|
||||
<i class="fas fa-plus mr-1"></i>
|
||||
Assign Group
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Notes Section -->
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
|
||||
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-sticky-note text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
|
||||
Notes
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<form method="POST" action="/domains/{{ domain.id }}/update-notes" id="notes-form">
|
||||
{{ csrf_field() }}
|
||||
<textarea
|
||||
name="notes"
|
||||
id="notes-textarea"
|
||||
rows="6"
|
||||
class="w-full px-3 py-2 text-xs border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||||
placeholder="Add notes about this domain...">{{ domain.notes|default('') }}</textarea>
|
||||
|
||||
<div class="flex gap-2 mt-3">
|
||||
<button
|
||||
type="submit"
|
||||
class="flex-1 inline-flex items-center justify-center px-3 py-2 bg-blue-600 text-white text-xs rounded-lg hover:bg-blue-700 transition-colors font-medium">
|
||||
<i class="fas fa-save mr-1.5"></i>
|
||||
Update Notes
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick="resetNotes()"
|
||||
class="flex-1 inline-flex items-center justify-center px-3 py-2 border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-slate-300 text-xs rounded-lg hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors font-medium">
|
||||
<i class="fas fa-times mr-1.5"></i>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification History -->
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
|
||||
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-history text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
|
||||
Notification History ({{ logs|length }})
|
||||
</h3>
|
||||
</div>
|
||||
<div class="overflow-hidden">
|
||||
{% if logs is empty %}
|
||||
<div class="p-8 text-center">
|
||||
<i class="fas fa-bell-slash text-gray-300 dark:text-slate-600 text-3xl mb-2"></i>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400">No notifications sent yet</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="max-h-96 overflow-y-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-slate-700 text-xs">
|
||||
<thead class="bg-gray-50 dark:bg-slate-900 sticky top-0">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Channel</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Status</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Date</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-slate-800 divide-y divide-gray-200 dark:divide-slate-700">
|
||||
{% for log in logs %}
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
|
||||
<td class="px-3 py-2 whitespace-nowrap">
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400">
|
||||
{{ log.channel_type|capitalize }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap">
|
||||
{% set logStatusClass = log.status == 'sent' ? 'bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400' : 'bg-red-100 dark:bg-red-500/10 text-red-800 dark:text-red-400' %}
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium {{ logStatusClass }}">
|
||||
{{ log.status|capitalize }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap text-gray-600 dark:text-slate-400">{{ log.sent_at|date('M j, H:i') }}</td>
|
||||
<td class="px-3 py-2 text-gray-700 dark:text-slate-300 max-w-xs truncate" title="{{ log.message }}">
|
||||
{{ log.message }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Raw WHOIS Data (Collapsible) -->
|
||||
{% if domain.whois_data is not empty %}
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
|
||||
<button onclick="toggleWhoisData()" class="w-full px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900 text-left hover:bg-gray-100 dark:hover:bg-slate-700 transition-colors">
|
||||
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center justify-between">
|
||||
<span class="flex items-center">
|
||||
<i class="fas fa-code text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
|
||||
Raw WHOIS Data
|
||||
</span>
|
||||
<i class="fas fa-chevron-down text-gray-400 dark:text-slate-500 text-xs transition-transform" id="whois-chevron"></i>
|
||||
</h3>
|
||||
</button>
|
||||
<div id="whois-data" class="hidden p-4 bg-gray-900 max-h-64 overflow-y-auto">
|
||||
<pre class="text-xs text-green-400 font-mono">{{ whoisData|json_encode(constant('JSON_PRETTY_PRINT')) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function toggleWhoisData() {
|
||||
const dataDiv = document.getElementById('whois-data');
|
||||
const chevron = document.getElementById('whois-chevron');
|
||||
dataDiv.classList.toggle('hidden');
|
||||
chevron.classList.toggle('rotate-180');
|
||||
}
|
||||
|
||||
function resetNotes() {
|
||||
const originalNotes = {{ domain.notes|default('')|json_encode|raw }};
|
||||
document.getElementById('notes-textarea').value = originalNotes;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user