Add user profile & dashboard insights
Introduce a user profile page and expand dashboard insights/UI. Added UserController::show and a new users/show view with user stats, domains, tags and groups; updated users index to include a "view profile" link and changed edit form action to /users/{id}/update. Enhanced DashboardController to compute registrar distribution, notification coverage, channel totals and dashboard tag usage; updated dashboard/index.php to show system status, expiring list, registrar/tag widgets and notification coverage panels. Minor controller hardening: DomainController now returns a permission message when a domain is inaccessible, and TagController enforces isolation-mode access checks. UI/JS improvements: add a Quick Actions dropdown in top-nav, refactor dropdown toggle/close logic in layout/base.php, and small notification markup tweak. Routes were adjusted to expose the new user profile endpoints.
This commit is contained in:
@@ -57,6 +57,37 @@ class DashboardController extends Controller
|
|||||||
$formattedRecentDomains = \App\Helpers\DomainHelper::formatMultiple($recentDomains);
|
$formattedRecentDomains = \App\Helpers\DomainHelper::formatMultiple($recentDomains);
|
||||||
$formattedExpiringDomains = \App\Helpers\DomainHelper::formatMultiple($expiringThisMonth);
|
$formattedExpiringDomains = \App\Helpers\DomainHelper::formatMultiple($expiringThisMonth);
|
||||||
|
|
||||||
|
// Get all domains for registrar distribution & notification coverage
|
||||||
|
if ($isolationMode === 'isolated') {
|
||||||
|
$allDomains = $this->domainModel->getAllWithGroups($userId);
|
||||||
|
} else {
|
||||||
|
$allDomains = $this->domainModel->getAllWithGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registrar distribution
|
||||||
|
$registrarCounts = [];
|
||||||
|
foreach ($allDomains as $d) {
|
||||||
|
$reg = !empty($d['registrar']) ? $d['registrar'] : 'Unknown';
|
||||||
|
$registrarCounts[$reg] = ($registrarCounts[$reg] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
arsort($registrarCounts);
|
||||||
|
|
||||||
|
// Notification coverage
|
||||||
|
$domainsWithGroup = count(array_filter($allDomains, fn($d) => !empty($d['group_name'])));
|
||||||
|
$totalDomainCount = count($allDomains);
|
||||||
|
|
||||||
|
// Total channels
|
||||||
|
$totalChannels = 0;
|
||||||
|
foreach ($groups as $g) { $totalChannels += ($g['channel_count'] ?? 0); }
|
||||||
|
|
||||||
|
// Get user's tags with usage
|
||||||
|
$tagModel = new \App\Models\Tag();
|
||||||
|
if ($isolationMode === 'isolated') {
|
||||||
|
$dashTags = $tagModel->getAllWithUsage($userId);
|
||||||
|
} else {
|
||||||
|
$dashTags = $tagModel->getAllWithUsage();
|
||||||
|
}
|
||||||
|
|
||||||
$this->view('dashboard/index', [
|
$this->view('dashboard/index', [
|
||||||
'recentDomains' => $formattedRecentDomains,
|
'recentDomains' => $formattedRecentDomains,
|
||||||
'expiringThisMonth' => $formattedExpiringDomains,
|
'expiringThisMonth' => $formattedExpiringDomains,
|
||||||
@@ -64,6 +95,11 @@ class DashboardController extends Controller
|
|||||||
'recentLogs' => $recentLogs,
|
'recentLogs' => $recentLogs,
|
||||||
'groups' => $groups,
|
'groups' => $groups,
|
||||||
'systemStatus' => $systemStatus,
|
'systemStatus' => $systemStatus,
|
||||||
|
'registrarCounts' => $registrarCounts,
|
||||||
|
'domainsWithGroup' => $domainsWithGroup,
|
||||||
|
'totalDomainCount' => $totalDomainCount,
|
||||||
|
'totalChannels' => $totalChannels,
|
||||||
|
'dashTags' => $dashTags,
|
||||||
'title' => 'Dashboard'
|
'title' => 'Dashboard'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -531,7 +531,7 @@ class DomainController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$domain) {
|
if (!$domain) {
|
||||||
$_SESSION['error'] = 'Domain not found';
|
$_SESSION['error'] = 'You do not have permission to view this domain.';
|
||||||
$this->redirect('/domains');
|
$this->redirect('/domains');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,6 +250,13 @@ class TagController extends Controller
|
|||||||
$userId = \Core\Auth::id();
|
$userId = \Core\Auth::id();
|
||||||
$settingModel = new \App\Models\Setting();
|
$settingModel = new \App\Models\Setting();
|
||||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||||
|
|
||||||
|
// Check if user can access this tag in isolation mode
|
||||||
|
if ($isolationMode === 'isolated' && !$this->tagModel->canUserAccessTag($id, $userId, true)) {
|
||||||
|
$_SESSION['error'] = 'You do not have permission to view this tag.';
|
||||||
|
$this->redirect('/tags');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get domains for this tag with proper formatting
|
// Get domains for this tag with proper formatting
|
||||||
$domainModel = new \App\Models\Domain();
|
$domainModel = new \App\Models\Domain();
|
||||||
|
|||||||
@@ -195,6 +195,62 @@ class UserController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show user profile view (admin)
|
||||||
|
*/
|
||||||
|
public function show($params = [])
|
||||||
|
{
|
||||||
|
$userId = $params['id'] ?? 0;
|
||||||
|
$user = $this->userModel->find($userId);
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
$_SESSION['error'] = 'User not found';
|
||||||
|
$this->redirect('/users');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's domains (formatted for display)
|
||||||
|
$domainModel = new \App\Models\Domain();
|
||||||
|
$domains = $domainModel->getAllWithGroups($userId);
|
||||||
|
$domains = \App\Helpers\DomainHelper::formatMultiple($domains);
|
||||||
|
$userDomainStats = $domainModel->getStatistics($userId);
|
||||||
|
|
||||||
|
// Get user's tags with domains per tag
|
||||||
|
$tagModel = new \App\Models\Tag();
|
||||||
|
$tags = $tagModel->getAllWithUsage($userId);
|
||||||
|
|
||||||
|
// Fetch domains for each tag (formatted for display)
|
||||||
|
foreach ($tags as &$tag) {
|
||||||
|
$tagDomains = $tagModel->getDomainsForTag($tag['id'], $userId);
|
||||||
|
$tag['domains'] = \App\Helpers\DomainHelper::formatMultiple($tagDomains);
|
||||||
|
}
|
||||||
|
unset($tag);
|
||||||
|
|
||||||
|
// Get user's notification groups with channels
|
||||||
|
$groupModel = new \App\Models\NotificationGroup();
|
||||||
|
$groups = $groupModel->getAllWithChannelCount($userId);
|
||||||
|
|
||||||
|
// Fetch channels for each group
|
||||||
|
$channelModel = new \App\Models\NotificationChannel();
|
||||||
|
foreach ($groups as &$group) {
|
||||||
|
$group['channels'] = $channelModel->getByGroupId($group['id']);
|
||||||
|
}
|
||||||
|
unset($group);
|
||||||
|
|
||||||
|
// Get 2FA status
|
||||||
|
$twoFactorStatus = $this->userModel->getTwoFactorStatus($userId);
|
||||||
|
|
||||||
|
$this->view('users/show', [
|
||||||
|
'title' => htmlspecialchars($user['full_name']) . ' - User Profile',
|
||||||
|
'user' => $user,
|
||||||
|
'domains' => $domains,
|
||||||
|
'userDomainStats' => $userDomainStats,
|
||||||
|
'tags' => $tags,
|
||||||
|
'groups' => $groups,
|
||||||
|
'twoFactorStatus' => $twoFactorStatus,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show edit user form
|
* Show edit user form
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,12 +9,62 @@ if (!isset($domainStats)) {
|
|||||||
$domainStats = \App\Helpers\LayoutHelper::getDomainStats();
|
$domainStats = \App\Helpers\LayoutHelper::getDomainStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare widget data
|
||||||
|
$topRegistrars = array_slice($registrarCounts ?? [], 0, 8, true);
|
||||||
|
$topTags = array_slice(array_filter($dashTags ?? [], fn($t) => ($t['usage_count'] ?? 0) > 0), 0, 8);
|
||||||
|
$domainsWithoutGroup = ($totalDomainCount ?? 0) - ($domainsWithGroup ?? 0);
|
||||||
|
$totalGroupCount = count($groups ?? []);
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<?php if (\Core\Auth::isAdmin()): ?>
|
||||||
|
<!-- System Status Bar (Admin) -->
|
||||||
|
<div class="bg-white rounded-lg border border-gray-200 px-5 py-3 mb-4">
|
||||||
|
<div class="flex items-center gap-6">
|
||||||
|
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wide flex items-center">
|
||||||
|
<i class="fas fa-server text-gray-400 mr-2"></i>
|
||||||
|
System Status
|
||||||
|
</span>
|
||||||
|
<?php
|
||||||
|
$statusColors = [
|
||||||
|
'green' => 'text-green-600',
|
||||||
|
'yellow' => 'text-yellow-600',
|
||||||
|
'red' => 'text-red-600',
|
||||||
|
'gray' => 'text-gray-600'
|
||||||
|
];
|
||||||
|
$statusDots = [
|
||||||
|
'green' => 'bg-green-500',
|
||||||
|
'yellow' => 'bg-yellow-500',
|
||||||
|
'red' => 'bg-red-500',
|
||||||
|
'gray' => 'bg-gray-400'
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
<div class="flex items-center gap-5 text-sm">
|
||||||
|
<span class="flex items-center gap-1.5">
|
||||||
|
<span class="w-2 h-2 rounded-full <?= $statusDots[$systemStatus['database']['color']] ?>"></span>
|
||||||
|
<span class="text-gray-500">Database</span>
|
||||||
|
<span class="<?= $statusColors[$systemStatus['database']['color']] ?> font-medium"><?= ucfirst($systemStatus['database']['status']) ?></span>
|
||||||
|
</span>
|
||||||
|
<span class="text-gray-200">|</span>
|
||||||
|
<span class="flex items-center gap-1.5">
|
||||||
|
<span class="w-2 h-2 rounded-full <?= $statusDots[$systemStatus['whois']['color']] ?>"></span>
|
||||||
|
<span class="text-gray-500">TLD Registry</span>
|
||||||
|
<span class="<?= $statusColors[$systemStatus['whois']['color']] ?> font-medium"><?= ucfirst($systemStatus['whois']['status']) ?></span>
|
||||||
|
</span>
|
||||||
|
<span class="text-gray-200">|</span>
|
||||||
|
<span class="flex items-center gap-1.5">
|
||||||
|
<span class="w-2 h-2 rounded-full <?= $statusDots[$systemStatus['notifications']['color']] ?>"></span>
|
||||||
|
<span class="text-gray-500">Notifications</span>
|
||||||
|
<span class="<?= $statusColors[$systemStatus['notifications']['color']] ?> font-medium"><?= ucfirst($systemStatus['notifications']['status']) ?></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Statistics Cards -->
|
<!-- Statistics Cards -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
<!-- Total Domains Card -->
|
|
||||||
<div class="bg-white rounded-lg border border-gray-200 p-5 hover:shadow-md transition-shadow duration-200">
|
<div class="bg-white rounded-lg border border-gray-200 p-5 hover:shadow-md transition-shadow duration-200">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -26,8 +76,6 @@ ob_start();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Active Domains Card -->
|
|
||||||
<div class="bg-white rounded-lg border border-gray-200 p-5 hover:shadow-md transition-shadow duration-200">
|
<div class="bg-white rounded-lg border border-gray-200 p-5 hover:shadow-md transition-shadow duration-200">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -39,8 +87,6 @@ ob_start();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Expiring Soon Card -->
|
|
||||||
<div class="bg-white rounded-lg border border-gray-200 p-5 hover:shadow-md transition-shadow duration-200">
|
<div class="bg-white rounded-lg border border-gray-200 p-5 hover:shadow-md transition-shadow duration-200">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -53,8 +99,6 @@ ob_start();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Inactive Domains Card -->
|
|
||||||
<div class="bg-white rounded-lg border border-gray-200 p-5 hover:shadow-md transition-shadow duration-200">
|
<div class="bg-white rounded-lg border border-gray-200 p-5 hover:shadow-md transition-shadow duration-200">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -68,18 +112,22 @@ ob_start();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content Grid -->
|
<!-- Main Content: Recent Domains + Expiring Soon -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
|
||||||
<!-- Recent Domains -->
|
<!-- Recent Domains -->
|
||||||
<div class="lg:col-span-2">
|
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
<div class="px-5 py-3 border-b border-gray-200">
|
||||||
<div class="px-5 py-3 border-b border-gray-200">
|
<div class="flex items-center justify-between">
|
||||||
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
|
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
|
||||||
<i class="fas fa-clock text-gray-400 mr-2 text-xs"></i>
|
<i class="fas fa-clock text-gray-400 mr-2 text-xs"></i>
|
||||||
Recent Domains
|
Recent Domains
|
||||||
</h2>
|
</h2>
|
||||||
|
<a href="/domains" class="text-xs text-primary hover:text-primary-dark font-medium">
|
||||||
|
View all <i class="fas fa-arrow-right ml-1"></i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4">
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
<?php if (!empty($recentDomains)): ?>
|
<?php if (!empty($recentDomains)): ?>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<?php foreach ($recentDomains as $domain): ?>
|
<?php foreach ($recentDomains as $domain): ?>
|
||||||
@@ -93,11 +141,7 @@ ob_start();
|
|||||||
<div class="flex items-center space-x-3 text-xs text-gray-500 mt-0.5">
|
<div class="flex items-center space-x-3 text-xs text-gray-500 mt-0.5">
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<i class="far fa-calendar mr-1"></i>
|
<i class="far fa-calendar mr-1"></i>
|
||||||
<?php if ($domain['expiration_date']): ?>
|
<?= $domain['expiration_date'] ? date('M d, Y', strtotime($domain['expiration_date'])) : 'Not set' ?>
|
||||||
<?= date('M d, Y', strtotime($domain['expiration_date'])) ?>
|
|
||||||
<?php else: ?>
|
|
||||||
Not set
|
|
||||||
<?php endif; ?>
|
|
||||||
</span>
|
</span>
|
||||||
<?php if ($domain['registrar']): ?>
|
<?php if ($domain['registrar']): ?>
|
||||||
<span class="flex items-center truncate">
|
<span class="flex items-center truncate">
|
||||||
@@ -109,13 +153,8 @@ ob_start();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2 flex-shrink-0">
|
<div class="flex items-center space-x-2 flex-shrink-0">
|
||||||
<?php
|
<span class="px-2 py-1 rounded text-xs font-medium <?= $domain['statusClass'] ?>">
|
||||||
// Display data prepared by DomainHelper in controller
|
<?= $domain['statusText'] ?>
|
||||||
$statusClass = $domain['statusClass'];
|
|
||||||
$statusText = $domain['statusText'];
|
|
||||||
?>
|
|
||||||
<span class="px-2 py-1 rounded text-xs font-medium <?= $statusClass ?>">
|
|
||||||
<?= $statusText ?>
|
|
||||||
</span>
|
</span>
|
||||||
<a href="/domains/<?= $domain['id'] ?>" class="text-gray-400 hover:text-primary">
|
<a href="/domains/<?= $domain['id'] ?>" class="text-gray-400 hover:text-primary">
|
||||||
<i class="fas fa-chevron-right text-sm"></i>
|
<i class="fas fa-chevron-right text-sm"></i>
|
||||||
@@ -124,12 +163,6 @@ ob_start();
|
|||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 pt-3 border-t border-gray-100 text-center">
|
|
||||||
<a href="/domains" class="text-sm text-primary hover:text-primary-dark font-medium inline-flex items-center">
|
|
||||||
View All Domains
|
|
||||||
<i class="fas fa-arrow-right ml-2 text-xs"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="text-center py-8">
|
<div class="text-center py-8">
|
||||||
<i class="fas fa-globe text-gray-300 text-4xl mb-3"></i>
|
<i class="fas fa-globe text-gray-300 text-4xl mb-3"></i>
|
||||||
@@ -140,128 +173,168 @@ ob_start();
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sidebar: Quick Actions & Stats -->
|
<!-- Expiring Soon -->
|
||||||
<div class="space-y-4">
|
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
<!-- Quick Actions -->
|
<div class="px-5 py-3 border-b border-gray-200">
|
||||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
<div class="flex items-center justify-between">
|
||||||
<div class="px-5 py-3 border-b border-gray-200">
|
|
||||||
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
|
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
|
||||||
<i class="fas fa-bolt text-gray-400 mr-2 text-xs"></i>
|
<i class="fas fa-exclamation-triangle text-orange-500 mr-2 text-xs"></i>
|
||||||
Quick Actions
|
Expiring Soon
|
||||||
</h2>
|
</h2>
|
||||||
|
<?php if (($expiringCount ?? 0) > 5): ?>
|
||||||
|
<a href="/domains?status=expiring_soon" class="text-xs text-primary hover:text-primary-dark font-medium">
|
||||||
|
View all <?= $expiringCount ?>
|
||||||
|
<i class="fas fa-arrow-right ml-1"></i>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php if (!empty($expiringThisMonth)): ?>
|
||||||
<div class="p-4 space-y-2">
|
<div class="p-4 space-y-2">
|
||||||
<a href="/domains/create" class="flex items-center p-3 border border-gray-200 hover:border-primary hover:bg-blue-50 rounded-lg transition-all duration-200 group">
|
<?php foreach ($expiringThisMonth as $domain): ?>
|
||||||
<div class="w-9 h-9 bg-blue-50 group-hover:bg-primary rounded-lg flex items-center justify-center group-hover:text-white text-blue-600 transition-colors duration-200">
|
<?php
|
||||||
<i class="fas fa-plus text-sm"></i>
|
$daysLeft = $domain['daysLeft'];
|
||||||
</div>
|
$urgencyClass = $daysLeft <= 7 ? 'text-red-600' : ($daysLeft <= 30 ? 'text-orange-600' : 'text-yellow-600');
|
||||||
<span class="ml-3 text-sm font-medium text-gray-700 group-hover:text-primary">Add New Domain</span>
|
?>
|
||||||
</a>
|
<div class="flex items-center justify-between p-3 border border-gray-100 rounded-lg hover:border-gray-300 hover:shadow-sm transition-all duration-200">
|
||||||
<a href="/groups/create" class="flex items-center p-3 border border-gray-200 hover:border-green-500 hover:bg-green-50 rounded-lg transition-all duration-200 group">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="w-9 h-9 bg-green-50 group-hover:bg-green-500 rounded-lg flex items-center justify-center group-hover:text-white text-green-600 transition-colors duration-200">
|
<p class="text-sm font-medium text-gray-900 truncate"><?= htmlspecialchars($domain['domain_name']) ?></p>
|
||||||
<i class="fas fa-bell text-sm"></i>
|
<p class="text-xs text-gray-500 mt-0.5">
|
||||||
</div>
|
<?= $domain['expiration_date'] ? date('M d, Y', strtotime($domain['expiration_date'])) : 'Unknown' ?>
|
||||||
<span class="ml-3 text-sm font-medium text-gray-700 group-hover:text-green-700">Create Group</span>
|
<span class="<?= $urgencyClass ?> font-semibold ml-2">
|
||||||
</a>
|
<?= $daysLeft ?> days
|
||||||
<a href="/debug/whois" class="flex items-center p-3 border border-gray-200 hover:border-indigo-500 hover:bg-indigo-50 rounded-lg transition-all duration-200 group">
|
</span>
|
||||||
<div class="w-9 h-9 bg-indigo-50 group-hover:bg-indigo-500 rounded-lg flex items-center justify-center group-hover:text-white text-indigo-600 transition-colors duration-200">
|
</p>
|
||||||
<i class="fas fa-search text-sm"></i>
|
</div>
|
||||||
</div>
|
<a href="/domains/<?= $domain['id'] ?>" class="text-gray-400 hover:text-primary">
|
||||||
<span class="ml-3 text-sm font-medium text-gray-700 group-hover:text-indigo-700">WHOIS Lookup</span>
|
<i class="fas fa-chevron-right text-sm"></i>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- System Status -->
|
|
||||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
|
||||||
<div class="px-5 py-3 border-b border-gray-200">
|
|
||||||
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
|
|
||||||
<i class="fas fa-server text-gray-400 mr-2 text-xs"></i>
|
|
||||||
System Status
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="p-4 space-y-3">
|
|
||||||
<?php
|
|
||||||
$statusColors = [
|
|
||||||
'green' => 'text-green-600',
|
|
||||||
'yellow' => 'text-yellow-600',
|
|
||||||
'red' => 'text-red-600',
|
|
||||||
'gray' => 'text-gray-600'
|
|
||||||
];
|
|
||||||
?>
|
|
||||||
<div class="flex items-center justify-between text-sm">
|
|
||||||
<span class="text-gray-600">Database</span>
|
|
||||||
<span class="flex items-center <?= $statusColors[$systemStatus['database']['color']] ?> font-medium">
|
|
||||||
<i class="fas fa-circle text-xs mr-1.5"></i>
|
|
||||||
<?= ucfirst($systemStatus['database']['status']) ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between text-sm">
|
|
||||||
<span class="text-gray-600">TLD Registry</span>
|
|
||||||
<span class="flex items-center <?= $statusColors[$systemStatus['whois']['color']] ?> font-medium">
|
|
||||||
<i class="fas fa-circle text-xs mr-1.5"></i>
|
|
||||||
<?= ucfirst($systemStatus['whois']['status']) ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between text-sm">
|
|
||||||
<span class="text-gray-600">Notifications</span>
|
|
||||||
<span class="flex items-center <?= $statusColors[$systemStatus['notifications']['color']] ?> font-medium">
|
|
||||||
<i class="fas fa-circle text-xs mr-1.5"></i>
|
|
||||||
<?= ucfirst($systemStatus['notifications']['status']) ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Expiring Soon -->
|
|
||||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
|
||||||
<div class="px-5 py-3 border-b border-gray-200">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
|
|
||||||
<i class="fas fa-exclamation-triangle text-orange-500 mr-2 text-xs"></i>
|
|
||||||
Expiring Soon
|
|
||||||
</h2>
|
|
||||||
<?php if (($expiringCount ?? 0) > 5): ?>
|
|
||||||
<a href="/domains?status=expiring_soon" class="text-xs text-primary hover:text-primary-dark font-medium">
|
|
||||||
View all <?= $expiringCount ?>
|
|
||||||
<i class="fas fa-arrow-right ml-1"></i>
|
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
</div>
|
||||||
</div>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php if (!empty($expiringThisMonth)): ?>
|
<?php else: ?>
|
||||||
<div class="p-4 space-y-2">
|
<div class="p-6 text-center">
|
||||||
<?php foreach ($expiringThisMonth as $domain): ?>
|
<i class="fas fa-check-circle text-green-500 text-3xl mb-2"></i>
|
||||||
<?php
|
<p class="text-sm text-gray-600">No domains expiring soon</p>
|
||||||
// Display data prepared by DomainHelper in controller
|
<p class="text-xs text-gray-400 mt-1">within <?= $domainStats['expiring_threshold'] ?? 30 ?> days</p>
|
||||||
$daysLeft = $domain['daysLeft'];
|
</div>
|
||||||
$urgencyClass = $daysLeft <= 7 ? 'text-red-600' : ($daysLeft <= 30 ? 'text-orange-600' : 'text-yellow-600');
|
<?php endif; ?>
|
||||||
?>
|
</div>
|
||||||
<div class="flex items-center justify-between p-3 border border-gray-100 rounded-lg hover:border-gray-300 hover:shadow-sm transition-all duration-200">
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<p class="text-sm font-medium text-gray-900 truncate"><?= htmlspecialchars($domain['domain_name']) ?></p>
|
<!-- Insights Row -->
|
||||||
<p class="text-xs text-gray-500 mt-0.5">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||||
<?= $domain['expiration_date'] ? date('M d, Y', strtotime($domain['expiration_date'])) : 'Unknown' ?>
|
<!-- Registrar Distribution -->
|
||||||
<span class="<?= $urgencyClass ?> font-semibold ml-2">
|
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
<?= $daysLeft ?> days
|
<div class="px-5 py-3 border-b border-gray-200 flex items-center justify-between">
|
||||||
</span>
|
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
|
||||||
</p>
|
<i class="fas fa-building text-gray-400 mr-2 text-xs"></i>
|
||||||
|
Registrar Distribution
|
||||||
|
</h2>
|
||||||
|
<span class="text-xs text-gray-500"><?= count($registrarCounts ?? []) ?> registrar<?= count($registrarCounts ?? []) != 1 ? 's' : '' ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="p-5">
|
||||||
|
<?php if (!empty($topRegistrars)): ?>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<?php foreach ($topRegistrars as $regName => $regCount): ?>
|
||||||
|
<?php $regPct = ($totalDomainCount ?? 0) > 0 ? round(($regCount / $totalDomainCount) * 100) : 0; ?>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between mb-1">
|
||||||
|
<span class="text-sm text-gray-700 font-medium truncate mr-3"><?= htmlspecialchars($regName) ?></span>
|
||||||
|
<span class="text-xs text-gray-500 whitespace-nowrap"><?= $regCount ?> (<?= $regPct ?>%)</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-gray-100 rounded-full h-1.5">
|
||||||
|
<div class="bg-blue-500 rounded-full h-1.5" style="width: <?= max(2, $regPct) ?>%"></div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/domains/<?= $domain['id'] ?>" class="text-gray-400 hover:text-primary">
|
|
||||||
<i class="fas fa-chevron-right text-sm"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="p-6 text-center">
|
<div class="text-center py-4">
|
||||||
<i class="fas fa-check-circle text-green-500 text-3xl mb-2"></i>
|
<i class="fas fa-building text-gray-300 text-2xl mb-2"></i>
|
||||||
<p class="text-sm text-gray-600">No domains expiring soon</p>
|
<p class="text-sm text-gray-500">No registrar data</p>
|
||||||
<p class="text-xs text-gray-400 mt-1">within <?= $domainStats['expiring_threshold'] ?? 30 ?> days</p>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tag Usage -->
|
||||||
|
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
|
<div class="px-5 py-3 border-b border-gray-200 flex items-center justify-between">
|
||||||
|
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
|
||||||
|
<i class="fas fa-tags text-gray-400 mr-2 text-xs"></i>
|
||||||
|
Tag Usage
|
||||||
|
</h2>
|
||||||
|
<span class="text-xs text-gray-500"><?= count($dashTags ?? []) ?> tag<?= count($dashTags ?? []) != 1 ? 's' : '' ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="p-5">
|
||||||
|
<?php if (!empty($topTags)): ?>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<?php foreach ($topTags as $tt): ?>
|
||||||
|
<?php $pct = ($totalDomainCount ?? 0) > 0 ? round(($tt['usage_count'] / $totalDomainCount) * 100) : 0; ?>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between mb-1">
|
||||||
|
<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium border <?= htmlspecialchars($tt['color'] ?? 'bg-gray-100 text-gray-700 border-gray-300') ?>">
|
||||||
|
<i class="fas fa-tag mr-1" style="font-size: 8px;"></i>
|
||||||
|
<?= htmlspecialchars($tt['name']) ?>
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-gray-500"><?= $tt['usage_count'] ?> domain<?= $tt['usage_count'] != 1 ? 's' : '' ?> (<?= $pct ?>%)</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-gray-100 rounded-full h-1.5">
|
||||||
|
<div class="bg-primary rounded-full h-1.5" style="width: <?= max(2, $pct) ?>%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<i class="fas fa-tags text-gray-300 text-2xl mb-2"></i>
|
||||||
|
<p class="text-sm text-gray-500">No tags in use</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notification Coverage -->
|
||||||
|
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
|
<div class="px-5 py-3 border-b border-gray-200 flex items-center justify-between">
|
||||||
|
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
|
||||||
|
<i class="fas fa-bell text-gray-400 mr-2 text-xs"></i>
|
||||||
|
Notification Coverage
|
||||||
|
</h2>
|
||||||
|
<span class="text-xs text-gray-500"><?= $totalGroupCount ?> group<?= $totalGroupCount != 1 ? 's' : '' ?>, <?= $totalChannels ?? 0 ?> channel<?= ($totalChannels ?? 0) != 1 ? 's' : '' ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="p-5">
|
||||||
|
<?php if (($totalDomainCount ?? 0) > 0): ?>
|
||||||
|
<?php $coveragePct = round((($domainsWithGroup ?? 0) / $totalDomainCount) * 100); ?>
|
||||||
|
<div class="flex items-center justify-center mb-4">
|
||||||
|
<div class="relative w-28 h-28">
|
||||||
|
<svg class="w-28 h-28 transform -rotate-90" viewBox="0 0 36 36">
|
||||||
|
<path class="text-gray-200" stroke="currentColor" stroke-width="3" fill="none" d="M18 2.0845a15.9155 15.9155 0 0 1 0 31.831 15.9155 15.9155 0 0 1 0-31.831"/>
|
||||||
|
<path class="text-primary" stroke="currentColor" stroke-width="3" fill="none" stroke-dasharray="<?= $coveragePct ?>, 100" stroke-linecap="round" d="M18 2.0845a15.9155 15.9155 0 0 1 0 31.831 15.9155 15.9155 0 0 1 0-31.831"/>
|
||||||
|
</svg>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center">
|
||||||
|
<span class="text-xl font-bold text-gray-900"><?= $coveragePct ?>%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-3 text-center">
|
||||||
|
<div class="bg-green-50 border border-green-200 rounded-lg p-3">
|
||||||
|
<p class="text-lg font-bold text-green-700"><?= $domainsWithGroup ?? 0 ?></p>
|
||||||
|
<p class="text-xs text-green-600">With Notifications</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3">
|
||||||
|
<p class="text-lg font-bold text-gray-700"><?= $domainsWithoutGroup ?></p>
|
||||||
|
<p class="text-xs text-gray-500">Without Notifications</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<i class="fas fa-bell-slash text-gray-300 text-2xl mb-2"></i>
|
||||||
|
<p class="text-sm text-gray-500">No domains to monitor</p>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -240,47 +240,49 @@ if (!isset($appName)) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Close all dropdowns except the one specified
|
||||||
|
function closeOtherDropdowns(exceptId) {
|
||||||
|
['userDropdown', 'notificationsDropdown', 'quickActionsDropdown'].forEach(id => {
|
||||||
|
if (id !== exceptId) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Toggle user dropdown
|
// Toggle user dropdown
|
||||||
function toggleDropdown() {
|
function toggleDropdown() {
|
||||||
const dropdown = document.getElementById('userDropdown');
|
closeOtherDropdowns('userDropdown');
|
||||||
const notifDropdown = document.getElementById('notificationsDropdown');
|
document.getElementById('userDropdown').classList.toggle('show');
|
||||||
|
|
||||||
// Close notifications dropdown if open
|
|
||||||
if (notifDropdown && notifDropdown.classList.contains('show')) {
|
|
||||||
notifDropdown.classList.remove('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
dropdown.classList.toggle('show');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle notifications dropdown
|
// Toggle notifications dropdown
|
||||||
function toggleNotifications() {
|
function toggleNotifications() {
|
||||||
const dropdown = document.getElementById('notificationsDropdown');
|
closeOtherDropdowns('notificationsDropdown');
|
||||||
const userDropdown = document.getElementById('userDropdown');
|
document.getElementById('notificationsDropdown').classList.toggle('show');
|
||||||
|
}
|
||||||
// Close user dropdown if open
|
|
||||||
if (userDropdown && userDropdown.classList.contains('show')) {
|
// Toggle quick actions dropdown
|
||||||
userDropdown.classList.remove('show');
|
function toggleQuickActions() {
|
||||||
}
|
closeOtherDropdowns('quickActionsDropdown');
|
||||||
|
document.getElementById('quickActionsDropdown').classList.toggle('show');
|
||||||
dropdown.classList.toggle('show');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close dropdowns when clicking outside
|
// Close dropdowns when clicking outside
|
||||||
document.addEventListener('click', function(event) {
|
document.addEventListener('click', function(event) {
|
||||||
const userDropdown = document.getElementById('userDropdown');
|
const dropdowns = [
|
||||||
const notifDropdown = document.getElementById('notificationsDropdown');
|
{ id: 'userDropdown', trigger: '[onclick="toggleDropdown()"]' },
|
||||||
|
{ id: 'notificationsDropdown', trigger: '[onclick="toggleNotifications()"]' },
|
||||||
|
{ id: 'quickActionsDropdown', trigger: '[onclick="toggleQuickActions()"]' }
|
||||||
|
];
|
||||||
|
|
||||||
const isUserDropdownClick = event.target.closest('[onclick="toggleDropdown()"]') || event.target.closest('#userDropdown');
|
dropdowns.forEach(({ id, trigger }) => {
|
||||||
const isNotifDropdownClick = event.target.closest('[onclick="toggleNotifications()"]') || event.target.closest('#notificationsDropdown');
|
const dd = document.getElementById(id);
|
||||||
|
if (dd && dd.classList.contains('show')) {
|
||||||
if (!isUserDropdownClick && userDropdown && userDropdown.classList.contains('show')) {
|
const isInside = event.target.closest(trigger) || event.target.closest('#' + id);
|
||||||
userDropdown.classList.remove('show');
|
if (!isInside) dd.classList.remove('show');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (!isNotifDropdownClick && notifDropdown && notifDropdown.classList.contains('show')) {
|
|
||||||
notifDropdown.classList.remove('show');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Live Search Functionality
|
// Live Search Functionality
|
||||||
|
|||||||
@@ -50,10 +50,41 @@
|
|||||||
|
|
||||||
<!-- Right: Actions & User -->
|
<!-- Right: Actions & User -->
|
||||||
<div class="flex items-center space-x-1 sm:space-x-2">
|
<div class="flex items-center space-x-1 sm:space-x-2">
|
||||||
<!-- Quick Add Domain -->
|
<!-- Quick Actions Dropdown -->
|
||||||
<a href="/domains/create" title="Add Domain" 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">
|
<div class="relative">
|
||||||
<i class="fas fa-plus"></i>
|
<button onclick="toggleQuickActions()" title="Quick Actions" 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">
|
||||||
</a>
|
<i class="fas fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
<div id="quickActionsDropdown" class="dropdown-menu absolute right-0 mt-2 w-52 bg-white rounded-lg shadow-xl border border-gray-200 overflow-hidden py-1">
|
||||||
|
<div class="px-3 py-2 border-b border-gray-100">
|
||||||
|
<p class="text-xs font-semibold text-gray-500 uppercase tracking-wide">Quick Actions</p>
|
||||||
|
</div>
|
||||||
|
<a href="/domains/create" class="flex items-center px-4 py-2.5 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary transition-colors">
|
||||||
|
<div class="w-7 h-7 bg-blue-50 rounded-md flex items-center justify-center mr-3">
|
||||||
|
<i class="fas fa-globe text-blue-600 text-xs"></i>
|
||||||
|
</div>
|
||||||
|
Add Domain
|
||||||
|
</a>
|
||||||
|
<a href="/groups/create" class="flex items-center px-4 py-2.5 text-sm text-gray-700 hover:bg-green-50 hover:text-green-700 transition-colors">
|
||||||
|
<div class="w-7 h-7 bg-green-50 rounded-md flex items-center justify-center mr-3">
|
||||||
|
<i class="fas fa-bell text-green-600 text-xs"></i>
|
||||||
|
</div>
|
||||||
|
Create Group
|
||||||
|
</a>
|
||||||
|
<a href="/tags" class="flex items-center px-4 py-2.5 text-sm text-gray-700 hover:bg-purple-50 hover:text-purple-700 transition-colors">
|
||||||
|
<div class="w-7 h-7 bg-purple-50 rounded-md flex items-center justify-center mr-3">
|
||||||
|
<i class="fas fa-tag text-purple-600 text-xs"></i>
|
||||||
|
</div>
|
||||||
|
Create Tag
|
||||||
|
</a>
|
||||||
|
<a href="/debug/whois" class="flex items-center px-4 py-2.5 text-sm text-gray-700 hover:bg-indigo-50 hover:text-indigo-700 transition-colors">
|
||||||
|
<div class="w-7 h-7 bg-indigo-50 rounded-md flex items-center justify-center mr-3">
|
||||||
|
<i class="fas fa-search text-indigo-600 text-xs"></i>
|
||||||
|
</div>
|
||||||
|
WHOIS Lookup
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -94,7 +125,7 @@
|
|||||||
<div class="flex items-start space-x-3">
|
<div class="flex items-start space-x-3">
|
||||||
<?php $loginData = $notif['login_data'] ?? null; ?>
|
<?php $loginData = $notif['login_data'] ?? null; ?>
|
||||||
<?php if ($loginData && $notif['type'] === 'session_failed'): ?>
|
<?php if ($loginData && $notif['type'] === 'session_failed'): ?>
|
||||||
<!-- Failed login notification (mirrors successful login layout) -->
|
<!-- Failed login notification -->
|
||||||
<a href="<?= $notifUrl ?>" class="w-8 h-8 bg-red-100 rounded-lg flex items-center justify-center flex-shrink-0 hover:opacity-80 transition-opacity">
|
<a href="<?= $notifUrl ?>" class="w-8 h-8 bg-red-100 rounded-lg flex items-center justify-center flex-shrink-0 hover:opacity-80 transition-opacity">
|
||||||
<?php if (($loginData['country_code'] ?? 'xx') !== 'xx'): ?>
|
<?php if (($loginData['country_code'] ?? 'xx') !== 'xx'): ?>
|
||||||
<span class="fi fi-<?= strtolower($loginData['country_code']) ?> text-base rounded-sm"></span>
|
<span class="fi fi-<?= strtolower($loginData['country_code']) ?> text-base rounded-sm"></span>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ ob_start();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<form method="POST" action="/users/update" class="space-y-5">
|
<form method="POST" action="/users/<?= $user['id'] ?>/update" class="space-y-5">
|
||||||
<?= csrf_field() ?>
|
<?= csrf_field() ?>
|
||||||
<input type="hidden" name="id" value="<?= $user['id'] ?>">
|
<input type="hidden" name="id" value="<?= $user['id'] ?>">
|
||||||
|
|
||||||
|
|||||||
@@ -280,6 +280,9 @@ $pagination = $pagination ?? [
|
|||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
<div class="flex items-center justify-end space-x-2">
|
<div class="flex items-center justify-end space-x-2">
|
||||||
|
<a href="/users/<?= $user['id'] ?>" class="text-gray-600 hover:text-primary" title="View Profile">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</a>
|
||||||
<a href="/users/<?= $user['id'] ?>/edit" class="text-blue-600 hover:text-blue-800" title="Edit">
|
<a href="/users/<?= $user['id'] ?>/edit" class="text-blue-600 hover:text-blue-800" title="Edit">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
1090
app/Views/users/show.php
Normal file
1090
app/Views/users/show.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -163,6 +163,7 @@ $router->get('/api/notifications/recent', [NotificationController::class, 'getRe
|
|||||||
$router->get('/users', [UserController::class, 'index']);
|
$router->get('/users', [UserController::class, 'index']);
|
||||||
$router->get('/users/create', [UserController::class, 'create']);
|
$router->get('/users/create', [UserController::class, 'create']);
|
||||||
$router->post('/users/store', [UserController::class, 'store']);
|
$router->post('/users/store', [UserController::class, 'store']);
|
||||||
|
$router->get('/users/{id}', [UserController::class, 'show']);
|
||||||
$router->get('/users/{id}/edit', [UserController::class, 'edit']);
|
$router->get('/users/{id}/edit', [UserController::class, 'edit']);
|
||||||
$router->post('/users/{id}/update', [UserController::class, 'update']);
|
$router->post('/users/{id}/update', [UserController::class, 'update']);
|
||||||
$router->post('/users/{id}/delete', [UserController::class, 'delete']);
|
$router->post('/users/{id}/delete', [UserController::class, 'delete']);
|
||||||
|
|||||||
Reference in New Issue
Block a user