Initial Commit
This commit is contained in:
310
app/Views/layout/base.php
Normal file
310
app/Views/layout/base.php
Normal file
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
/**
|
||||
* Base Layout Template
|
||||
* Contains: HTML structure, meta tags, CSS/JS includes, global stats
|
||||
*/
|
||||
|
||||
// Fetch global stats for sidebar (available on all pages)
|
||||
if (!isset($globalStats)) {
|
||||
try {
|
||||
$pdo = \Core\Database::getConnection();
|
||||
|
||||
// Get total domains
|
||||
$totalStmt = $pdo->query("SELECT COUNT(*) as count FROM domains");
|
||||
$totalResult = $totalStmt->fetch(\PDO::FETCH_ASSOC);
|
||||
$total = $totalResult['count'] ?? 0;
|
||||
|
||||
// Get active domains
|
||||
$activeStmt = $pdo->query("SELECT COUNT(*) as count FROM domains WHERE is_active = 1");
|
||||
$activeResult = $activeStmt->fetch(\PDO::FETCH_ASSOC);
|
||||
$active = $activeResult['count'] ?? 0;
|
||||
|
||||
// Get expiring soon (within 30 days)
|
||||
$expiringSoonStmt = $pdo->query("SELECT COUNT(*) as count FROM domains WHERE expiration_date IS NOT NULL AND expiration_date <= DATE_ADD(NOW(), INTERVAL 30 DAY) AND expiration_date >= NOW()");
|
||||
$expiringSoonResult = $expiringSoonStmt->fetch(\PDO::FETCH_ASSOC);
|
||||
$expiringSoon = $expiringSoonResult['count'] ?? 0;
|
||||
|
||||
$globalStats = [
|
||||
'total' => $total,
|
||||
'active' => $active,
|
||||
'expiring_soon' => $expiringSoon
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
$globalStats = [
|
||||
'total' => 0,
|
||||
'active' => 0,
|
||||
'expiring_soon' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="description" content="Domain Monitor - Track and monitor your domain expiration dates">
|
||||
<meta name="author" content="Domain Monitor">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<!-- Title -->
|
||||
<title><?= $title ?? 'Domain Monitor' ?> - <?= $_ENV['APP_NAME'] ?? 'Domain Monitor' ?></title>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Flag Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flag-icons/7.5.0/css/flag-icons.min.css" referrerpolicy="no-referrer" />
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<!-- Tailwind Configuration -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
DEFAULT: '#4A90E2',
|
||||
dark: '#357ABD',
|
||||
light: '#6BA3E8',
|
||||
},
|
||||
sidebar: {
|
||||
DEFAULT: '#1F2937',
|
||||
light: '#374151',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
|
||||
<!-- Custom Page Styles (optional) -->
|
||||
<?php if (isset($customStyles)): ?>
|
||||
<style><?= $customStyles ?></style>
|
||||
<?php endif; ?>
|
||||
|
||||
<style>
|
||||
/* Sidebar full height */
|
||||
.sidebar {
|
||||
height: 100vh;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
.sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dropdown animation */
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
.dropdown-menu.show {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Active sidebar link */
|
||||
.sidebar-link.active {
|
||||
background: #374151;
|
||||
border-left: 4px solid #4A90E2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
|
||||
<?php include __DIR__ . '/top-nav.php'; ?>
|
||||
|
||||
<?php include __DIR__ . '/sidebar.php'; ?>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="md:ml-64 pt-16 min-h-screen bg-gray-50">
|
||||
<div class="p-6">
|
||||
<!-- Flash Messages -->
|
||||
<?php include __DIR__ . '/messages.php'; ?>
|
||||
|
||||
<!-- Page Content -->
|
||||
<?php if (isset($content)): ?>
|
||||
<?= $content ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Global Scripts -->
|
||||
<script>
|
||||
// Toggle sidebar on mobile
|
||||
function toggleSidebar() {
|
||||
document.getElementById('sidebar').classList.toggle('open');
|
||||
}
|
||||
|
||||
// Toggle user dropdown
|
||||
function toggleDropdown() {
|
||||
document.getElementById('userDropdown').classList.toggle('show');
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function(event) {
|
||||
const dropdown = document.getElementById('userDropdown');
|
||||
const isClickInside = event.target.closest('[onclick="toggleDropdown()"]') || event.target.closest('#userDropdown');
|
||||
|
||||
if (!isClickInside && dropdown && dropdown.classList.contains('show')) {
|
||||
dropdown.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Live Search Functionality
|
||||
let searchTimeout;
|
||||
const searchInput = document.getElementById('globalSearchInput');
|
||||
const searchDropdown = document.getElementById('searchDropdown');
|
||||
const searchResults = document.getElementById('searchResults');
|
||||
const searchLoading = document.getElementById('searchLoading');
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function(e) {
|
||||
const query = e.target.value.trim();
|
||||
|
||||
clearTimeout(searchTimeout);
|
||||
|
||||
if (query.length < 2) {
|
||||
searchDropdown.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading
|
||||
searchDropdown.classList.remove('hidden');
|
||||
searchLoading.classList.remove('hidden');
|
||||
searchResults.innerHTML = '';
|
||||
|
||||
// Debounce search
|
||||
searchTimeout = setTimeout(() => {
|
||||
fetch(`/api/search/suggest?q=${encodeURIComponent(query)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
searchLoading.classList.add('hidden');
|
||||
renderSearchResults(data);
|
||||
})
|
||||
.catch(error => {
|
||||
searchLoading.classList.add('hidden');
|
||||
searchResults.innerHTML = '<div class="p-4 text-red-600 text-sm">Error loading results</div>';
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Handle form submission
|
||||
const searchForm = document.getElementById('globalSearchForm');
|
||||
if (searchForm) {
|
||||
searchForm.addEventListener('submit', function(e) {
|
||||
searchDropdown.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function(event) {
|
||||
if (searchDropdown && !searchDropdown.contains(event.target) && event.target !== searchInput) {
|
||||
searchDropdown.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderSearchResults(data) {
|
||||
let html = '';
|
||||
|
||||
if (data.domains && data.domains.length > 0) {
|
||||
html += '<div class="p-2">';
|
||||
html += '<p class="px-3 py-2 text-xs font-semibold text-gray-500 uppercase">Your Domains</p>';
|
||||
|
||||
data.domains.forEach(domain => {
|
||||
const statusColors = {
|
||||
'red': 'text-red-600',
|
||||
'orange': 'text-orange-600',
|
||||
'yellow': 'text-yellow-600',
|
||||
'green': 'text-green-600',
|
||||
'gray': 'text-gray-400'
|
||||
};
|
||||
const colorClass = statusColors[domain.status_color] || 'text-gray-600';
|
||||
|
||||
html += `
|
||||
<a href="/domains/${domain.id}" class="block px-3 py-2 hover:bg-gray-50 rounded-lg transition-colors">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold text-gray-900 truncate">${escapeHtml(domain.domain_name)}</p>
|
||||
<p class="text-xs text-gray-500">${escapeHtml(domain.registrar || 'Unknown registrar')}</p>
|
||||
</div>
|
||||
${domain.days_left !== null ? `
|
||||
<div class="ml-3 text-right">
|
||||
<p class="text-xs font-semibold ${colorClass}">${domain.days_left} days</p>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Show WHOIS lookup option if no results and looks like a domain
|
||||
if (data.domains.length === 0 && data.isDomainLike) {
|
||||
html += `
|
||||
<div class="p-4 border-t border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">Domain not in portfolio</p>
|
||||
<p class="text-xs text-gray-500 mt-0.5">Perform WHOIS lookup for ${escapeHtml(data.query)}</p>
|
||||
</div>
|
||||
<button onclick="window.location.href='/search?q=${encodeURIComponent(data.query)}'" class="px-3 py-1.5 bg-primary text-white text-xs rounded-lg hover:bg-primary-dark">
|
||||
Lookup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (data.domains.length === 0) {
|
||||
html += '<div class="p-4 text-center text-sm text-gray-500">No results found</div>';
|
||||
}
|
||||
|
||||
// Add "View all results" link if there are results
|
||||
if (data.domains.length > 0) {
|
||||
html += `
|
||||
<div class="border-t border-gray-200 p-2">
|
||||
<a href="/search?q=${encodeURIComponent(data.query)}" class="block px-3 py-2 text-center text-sm font-medium text-primary hover:bg-gray-50 rounded-lg">
|
||||
View all results →
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
searchResults.innerHTML = html;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Custom Page Scripts (optional) -->
|
||||
<?php if (isset($customScripts)): ?>
|
||||
<script><?= $customScripts ?></script>
|
||||
<?php endif; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
119
app/Views/layout/messages.php
Normal file
119
app/Views/layout/messages.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<!-- Toast Notifications Container -->
|
||||
<div id="toast-container" class="fixed bottom-4 right-4 z-50 space-y-3 max-w-sm">
|
||||
|
||||
<!-- Success Toast -->
|
||||
<?php if (isset($_SESSION['success'])): ?>
|
||||
<div class="toast bg-white border-l-4 border-green-500 rounded-lg shadow-lg p-4 flex items-start animate-slide-in">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<i class="fas fa-check text-green-600 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3 flex-1">
|
||||
<p class="text-sm font-medium text-gray-900">Success</p>
|
||||
<p class="text-sm text-gray-600 mt-0.5"><?= htmlspecialchars($_SESSION['success']) ?></p>
|
||||
</div>
|
||||
<button onclick="this.parentElement.remove()" class="ml-3 flex-shrink-0 text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<i class="fas fa-times text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php unset($_SESSION['success']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Error Toast -->
|
||||
<?php if (isset($_SESSION['error'])): ?>
|
||||
<div class="toast bg-white border-l-4 border-red-500 rounded-lg shadow-lg p-4 flex items-start animate-slide-in">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-8 h-8 bg-red-100 rounded-full flex items-center justify-center">
|
||||
<i class="fas fa-times text-red-600 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3 flex-1">
|
||||
<p class="text-sm font-medium text-gray-900">Error</p>
|
||||
<p class="text-sm text-gray-600 mt-0.5"><?= htmlspecialchars($_SESSION['error']) ?></p>
|
||||
</div>
|
||||
<button onclick="this.parentElement.remove()" class="ml-3 flex-shrink-0 text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<i class="fas fa-times text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php unset($_SESSION['error']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Warning Toast -->
|
||||
<?php if (isset($_SESSION['warning'])): ?>
|
||||
<div class="toast bg-white border-l-4 border-orange-500 rounded-lg shadow-lg p-4 flex items-start animate-slide-in">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-8 h-8 bg-orange-100 rounded-full flex items-center justify-center">
|
||||
<i class="fas fa-exclamation-triangle text-orange-600 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3 flex-1">
|
||||
<p class="text-sm font-medium text-gray-900">Warning</p>
|
||||
<p class="text-sm text-gray-600 mt-0.5"><?= htmlspecialchars($_SESSION['warning']) ?></p>
|
||||
</div>
|
||||
<button onclick="this.parentElement.remove()" class="ml-3 flex-shrink-0 text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<i class="fas fa-times text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php unset($_SESSION['warning']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Info Toast -->
|
||||
<?php if (isset($_SESSION['info'])): ?>
|
||||
<div class="toast bg-white border-l-4 border-blue-500 rounded-lg shadow-lg p-4 flex items-start animate-slide-in">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<i class="fas fa-info text-blue-600 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3 flex-1">
|
||||
<p class="text-sm font-medium text-gray-900">Info</p>
|
||||
<p class="text-sm text-gray-600 mt-0.5"><?= htmlspecialchars($_SESSION['info']) ?></p>
|
||||
</div>
|
||||
<button onclick="this.parentElement.remove()" class="ml-3 flex-shrink-0 text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<i class="fas fa-times text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php unset($_SESSION['info']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Toast Auto-Dismiss Script -->
|
||||
<script>
|
||||
// Auto-dismiss toasts after 5 seconds
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const toasts = document.querySelectorAll('.toast');
|
||||
|
||||
toasts.forEach(toast => {
|
||||
// Add fade-out animation after 5 seconds
|
||||
setTimeout(() => {
|
||||
toast.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
|
||||
toast.style.opacity = '0';
|
||||
toast.style.transform = 'translateX(100%)';
|
||||
|
||||
// Remove from DOM after animation
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 300);
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-slide-in {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
</style>
|
||||
102
app/Views/layout/sidebar.php
Normal file
102
app/Views/layout/sidebar.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside id="sidebar" class="sidebar fixed left-0 top-0 w-64 bg-gray-900 text-white z-30">
|
||||
<div class="h-full overflow-y-auto flex flex-col">
|
||||
|
||||
<!-- Logo Section -->
|
||||
<div class="h-16 px-5 border-b border-gray-800 flex items-center">
|
||||
<div class="flex items-center">
|
||||
<div class="w-9 h-9 bg-primary rounded-lg flex items-center justify-center mr-3">
|
||||
<i class="fas fa-globe text-white text-sm"></i>
|
||||
</div>
|
||||
<h1 class="text-sm font-semibold text-white"><?= $_ENV['APP_NAME'] ?? 'Domain Monitor' ?></h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<nav class="px-4 py-3">
|
||||
<div class="space-y-0.5">
|
||||
<a href="/" class="sidebar-link flex items-center px-3 py-2 rounded-md text-gray-400 hover:bg-gray-800 hover:text-white transition-colors duration-150 <?= $_SERVER['REQUEST_URI'] === '/' ? 'bg-primary text-white' : '' ?>">
|
||||
<i class="fas fa-chart-line text-xs mr-3 w-4"></i>
|
||||
<span class="text-sm">Dashboard</span>
|
||||
</a>
|
||||
|
||||
<a href="/domains" class="sidebar-link flex items-center px-3 py-2 rounded-md text-gray-400 hover:bg-gray-800 hover:text-white transition-colors duration-150 <?= strpos($_SERVER['REQUEST_URI'], '/domains') !== false ? 'bg-primary text-white' : '' ?>">
|
||||
<i class="fas fa-globe text-xs mr-3 w-4"></i>
|
||||
<span class="text-sm">Domains</span>
|
||||
</a>
|
||||
|
||||
<a href="/groups" class="sidebar-link flex items-center px-3 py-2 rounded-md text-gray-400 hover:bg-gray-800 hover:text-white transition-colors duration-150 <?= strpos($_SERVER['REQUEST_URI'], '/groups') !== false ? 'bg-primary text-white' : '' ?>">
|
||||
<i class="fas fa-bell text-xs mr-3 w-4"></i>
|
||||
<span class="text-sm">Notification Groups</span>
|
||||
</a>
|
||||
|
||||
<a href="/tld-registry" class="sidebar-link flex items-center px-3 py-2 rounded-md text-gray-400 hover:bg-gray-800 hover:text-white transition-colors duration-150 <?= strpos($_SERVER['REQUEST_URI'], '/tld-registry') !== false ? 'bg-primary text-white' : '' ?>">
|
||||
<i class="fas fa-database text-xs mr-3 w-4"></i>
|
||||
<span class="text-sm">TLD Registry</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tools Section -->
|
||||
<div class="mt-4 pt-3 border-t border-gray-800">
|
||||
<p class="text-xs font-semibold text-gray-500 uppercase tracking-wider px-3 mb-1">Tools</p>
|
||||
<div class="space-y-0.5">
|
||||
<a href="/debug/whois" class="sidebar-link flex items-center px-3 py-2 rounded-md text-gray-400 hover:bg-gray-800 hover:text-white transition-colors duration-150">
|
||||
<i class="fas fa-search text-xs mr-3 w-4"></i>
|
||||
<span class="text-sm">WHOIS Lookup</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Quick Stats Cards - Pinned to Bottom -->
|
||||
<div class="mt-auto px-4 pb-3 border-t border-gray-800 pt-3">
|
||||
<div class="text-xs font-semibold text-gray-500 uppercase tracking-wider px-3 mb-2">Quick Stats</div>
|
||||
<div class="space-y-1.5">
|
||||
<div class="bg-gray-800 rounded-md p-2.5 border border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="w-7 h-7 bg-blue-500/20 rounded flex items-center justify-center mr-2.5">
|
||||
<i class="fas fa-globe text-blue-400 text-xs"></i>
|
||||
</div>
|
||||
<span class="text-gray-400 text-xs">Total</span>
|
||||
</div>
|
||||
<span class="text-white font-semibold text-sm"><?= $globalStats['total'] ?? 0 ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800 rounded-md p-2.5 border border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="w-7 h-7 bg-orange-500/20 rounded flex items-center justify-center mr-2.5">
|
||||
<i class="fas fa-exclamation-triangle text-orange-400 text-xs"></i>
|
||||
</div>
|
||||
<span class="text-gray-400 text-xs">Expiring</span>
|
||||
</div>
|
||||
<span class="text-orange-400 font-semibold text-sm"><?= $globalStats['expiring_soon'] ?? 0 ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800 rounded-md p-2.5 border border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="w-7 h-7 bg-green-500/20 rounded flex items-center justify-center mr-2.5">
|
||||
<i class="fas fa-check-circle text-green-400 text-xs"></i>
|
||||
</div>
|
||||
<span class="text-gray-400 text-xs">Active</span>
|
||||
</div>
|
||||
<span class="text-green-400 font-semibold text-sm"><?= $globalStats['active'] ?? 0 ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="px-4 py-3 border-t border-gray-800">
|
||||
<div class="text-center">
|
||||
<p class="text-xs text-gray-500">© <?= date('Y') ?> Domain Monitor</p>
|
||||
<p class="text-xs text-gray-600 mt-0.5">v1.0.0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
133
app/Views/layout/top-nav.php
Normal file
133
app/Views/layout/top-nav.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<!-- Top Navigation Bar -->
|
||||
<nav class="bg-white border-b border-gray-200 fixed top-0 left-0 md:left-64 right-0 z-20">
|
||||
<div class="px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- Left: Menu button and Page Header -->
|
||||
<div class="flex items-center min-w-0">
|
||||
<button onclick="toggleSidebar()" class="text-gray-500 hover:text-gray-700 focus:outline-none focus:text-gray-700 md:hidden mr-4">
|
||||
<i class="fas fa-bars text-xl"></i>
|
||||
</button>
|
||||
|
||||
<!-- Page Title & Description -->
|
||||
<div class="hidden md:block">
|
||||
<h2 class="text-xl font-bold text-gray-800 flex items-center">
|
||||
<?php if (isset($pageIcon)): ?>
|
||||
<i class="<?= $pageIcon ?> text-primary mr-2"></i>
|
||||
<?php endif; ?>
|
||||
<?= $pageTitle ?? $title ?? 'Dashboard' ?>
|
||||
</h2>
|
||||
<?php if (isset($pageDescription)): ?>
|
||||
<p class="text-sm text-gray-600 mt-0.5"><?= $pageDescription ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Center: Search Bar -->
|
||||
<div class="flex-1 max-w-2xl mx-8">
|
||||
<form action="/search" method="GET" class="relative hidden md:block" id="globalSearchForm">
|
||||
<input type="text"
|
||||
name="q"
|
||||
placeholder="Search domains or lookup WHOIS..."
|
||||
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-sm"
|
||||
id="globalSearchInput"
|
||||
autocomplete="off">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
|
||||
|
||||
<!-- Search Results Dropdown -->
|
||||
<div id="searchDropdown" class="hidden absolute top-full left-0 right-0 mt-2 bg-white rounded-lg shadow-xl border border-gray-200 max-h-96 overflow-y-auto z-50">
|
||||
<!-- Loading state -->
|
||||
<div id="searchLoading" class="hidden p-4 text-center">
|
||||
<i class="fas fa-spinner fa-spin text-primary"></i>
|
||||
<p class="text-sm text-gray-600 mt-2">Searching...</p>
|
||||
</div>
|
||||
|
||||
<!-- Results will be inserted here -->
|
||||
<div id="searchResults"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Right: Actions & User -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- Quick Add Domain -->
|
||||
<a href="/domains/create" title="Add Domain" class="flex items-center justify-center w-9 h-9 bg-primary hover:bg-primary-dark text-white rounded-lg transition-colors duration-150">
|
||||
<i class="fas fa-plus"></i>
|
||||
</a>
|
||||
|
||||
<!-- Notifications -->
|
||||
<button title="Notifications" class="relative flex items-center justify-center w-9 h-9 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors duration-150">
|
||||
<i class="fas fa-bell"></i>
|
||||
<?php if (($globalStats['expiring_soon'] ?? 0) > 0): ?>
|
||||
<span class="absolute top-1 right-1 flex h-2 w-2">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
|
||||
<!-- Settings -->
|
||||
<button title="Settings" class="flex items-center justify-center w-9 h-9 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors duration-150">
|
||||
<i class="fas fa-cog"></i>
|
||||
</button>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="hidden md:block h-8 w-px bg-gray-300"></div>
|
||||
|
||||
<!-- User Dropdown -->
|
||||
<div class="relative">
|
||||
<button onclick="toggleDropdown()" class="flex items-center space-x-3 p-2 hover:bg-gray-100 rounded-lg transition-colors duration-150 focus:outline-none">
|
||||
<div class="w-9 h-9 rounded-full bg-primary flex items-center justify-center text-white font-semibold">
|
||||
<?= strtoupper(substr($_SESSION['username'] ?? 'A', 0, 1)) ?>
|
||||
</div>
|
||||
<div class="hidden lg:block text-left">
|
||||
<p class="text-sm font-medium text-gray-700"><?= htmlspecialchars($_SESSION['username'] ?? 'User') ?></p>
|
||||
<p class="text-xs text-gray-500">Administrator</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down text-gray-400 text-xs hidden md:block"></i>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<div id="userDropdown" class="dropdown-menu absolute right-0 mt-2 w-56 bg-white rounded-lg shadow-lg py-2 border border-gray-200">
|
||||
<div class="px-4 py-3 border-b border-gray-200">
|
||||
<p class="text-sm font-medium text-gray-900"><?= htmlspecialchars($_SESSION['full_name'] ?? $_SESSION['username'] ?? 'User') ?></p>
|
||||
<p class="text-xs text-gray-500 mt-1"><?= htmlspecialchars($_SESSION['email'] ?? 'admin@example.com') ?></p>
|
||||
<span class="inline-block mt-2 px-2 py-1 bg-green-100 text-green-800 text-xs font-semibold rounded">
|
||||
<i class="fas fa-circle text-xs mr-1"></i>Online
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a href="#" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors duration-150">
|
||||
<i class="fas fa-user-circle w-5 text-gray-400 mr-3"></i>
|
||||
My Profile
|
||||
</a>
|
||||
|
||||
<a href="#" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors duration-150">
|
||||
<i class="fas fa-cog w-5 text-gray-400 mr-3"></i>
|
||||
Account Settings
|
||||
</a>
|
||||
|
||||
<a href="#" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors duration-150">
|
||||
<i class="fas fa-bell w-5 text-gray-400 mr-3"></i>
|
||||
Notifications
|
||||
</a>
|
||||
|
||||
<div class="border-t border-gray-200 my-1"></div>
|
||||
|
||||
<a href="#" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors duration-150">
|
||||
<i class="fas fa-question-circle w-5 text-gray-400 mr-3"></i>
|
||||
Help & Support
|
||||
</a>
|
||||
|
||||
<div class="border-t border-gray-200 my-1"></div>
|
||||
|
||||
<a href="/logout" class="flex items-center px-4 py-2 text-sm text-red-600 hover:bg-red-50 transition-colors duration-150">
|
||||
<i class="fas fa-sign-out-alt w-5 mr-3"></i>
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
Reference in New Issue
Block a user