1074 lines
51 KiB
PHP
1074 lines
51 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Control Panel</title>
|
|
<!-- Google Fonts: Outfit & Inter -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;600;700;800&display=swap" rel="stylesheet">
|
|
|
|
<style>
|
|
:root {
|
|
--bg-color: #0b0f19;
|
|
--card-bg: rgba(17, 24, 39, 0.7);
|
|
--card-border: rgba(255, 255, 255, 0.08);
|
|
--accent-primary: #6366f1;
|
|
--accent-secondary: #0078d4;
|
|
--text-main: #f3f4f6;
|
|
--text-muted: #9ca3af;
|
|
--success-color: #10b981;
|
|
--error-color: #ef4444;
|
|
--info-color: #06b6d4;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-main);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow-x: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
/* Glowing background circles */
|
|
body::before {
|
|
content: '';
|
|
position: absolute;
|
|
width: 500px;
|
|
height: 500px;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle, rgba(99, 102, 241, 0.12) 0%, rgba(0, 0, 0, 0) 70%);
|
|
top: -200px;
|
|
left: -100px;
|
|
z-index: -1;
|
|
}
|
|
|
|
body::after {
|
|
content: '';
|
|
position: absolute;
|
|
width: 400px;
|
|
height: 400px;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle, rgba(0, 120, 212, 0.1) 0%, rgba(0, 0, 0, 0) 70%);
|
|
bottom: -100px;
|
|
right: -100px;
|
|
z-index: -1;
|
|
}
|
|
|
|
/* Navigation Bar */
|
|
.navbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 20px 5%;
|
|
background: rgba(17, 24, 39, 0.4);
|
|
border-bottom: 1px solid var(--card-border);
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
z-index: 100;
|
|
}
|
|
|
|
.nav-logo {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.5rem;
|
|
font-weight: 800;
|
|
background: linear-gradient(135deg, #a5b4fc 0%, #38bdf8 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.nav-logo svg {
|
|
width: 24px;
|
|
height: 24px;
|
|
fill: #a5b4fc;
|
|
}
|
|
|
|
.nav-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.btn-nav {
|
|
color: var(--text-muted);
|
|
text-decoration: none;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
padding: 8px 16px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--card-border);
|
|
transition: all 0.25s ease;
|
|
}
|
|
|
|
.btn-nav:hover {
|
|
color: var(--text-main);
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
background-color: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.btn-logout {
|
|
color: #ef4444;
|
|
border-color: rgba(239, 68, 68, 0.2);
|
|
}
|
|
|
|
.btn-logout:hover {
|
|
color: white;
|
|
background-color: var(--error-color);
|
|
border-color: transparent;
|
|
}
|
|
|
|
/* Dashboard Wrapper */
|
|
.dashboard-container {
|
|
flex: 1;
|
|
padding: 40px 5%;
|
|
max-width: 1200px;
|
|
width: 100%;
|
|
margin: 0 auto;
|
|
z-index: 10;
|
|
}
|
|
|
|
/* Welcome Section */
|
|
.admin-header {
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.admin-header h1 {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.admin-header p {
|
|
color: var(--text-muted);
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
/* Statistics Cards Grid */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.stat-card {
|
|
background-color: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
backdrop-filter: blur(8px);
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 6px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.stat-value {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.8rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.stat-icon {
|
|
font-size: 2rem;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
/* Users Management Card */
|
|
.users-card {
|
|
background-color: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 20px;
|
|
padding: 30px;
|
|
backdrop-filter: blur(16px);
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24px;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
}
|
|
|
|
.card-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.3rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Users Table styling */
|
|
.table-responsive {
|
|
overflow-x: auto;
|
|
width: 100%;
|
|
}
|
|
|
|
.users-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
text-align: left;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.users-table th {
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--card-border);
|
|
color: var(--text-muted);
|
|
font-weight: 600;
|
|
font-size: 0.8rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.users-table td {
|
|
padding: 16px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.users-table tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.user-cell {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.user-avatar {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
border: 1px solid var(--card-border);
|
|
}
|
|
|
|
.user-avatar-fallback {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 600;
|
|
color: white;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.user-name {
|
|
font-weight: 500;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.user-email {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Badges */
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 4px 8px;
|
|
border-radius: 9999px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.badge-admin {
|
|
background-color: rgba(99, 102, 241, 0.15);
|
|
color: #818cf8;
|
|
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
}
|
|
|
|
.badge-user {
|
|
background-color: rgba(6, 182, 212, 0.15);
|
|
color: var(--info-color);
|
|
border: 1px solid rgba(6, 182, 212, 0.3);
|
|
}
|
|
|
|
.badge-microsoft {
|
|
background-color: rgba(16, 185, 129, 0.12);
|
|
color: var(--success-color);
|
|
border: 1px solid rgba(16, 185, 129, 0.25);
|
|
gap: 4px;
|
|
}
|
|
|
|
.badge-password {
|
|
background-color: rgba(245, 158, 11, 0.12);
|
|
color: #fbbf24;
|
|
border: 1px solid rgba(245, 158, 11, 0.25);
|
|
}
|
|
|
|
.action-btn {
|
|
background-color: transparent;
|
|
border: 1px solid var(--card-border);
|
|
color: var(--text-muted);
|
|
padding: 6px 12px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 0.8rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.action-btn:hover {
|
|
color: var(--text-main);
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
background-color: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.microsoft-icon-tiny {
|
|
display: inline-grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 1px;
|
|
width: 10px;
|
|
height: 10px;
|
|
}
|
|
|
|
.tiny-rect {
|
|
width: 4px;
|
|
height: 4px;
|
|
}
|
|
.tr1 { background-color: #f25022; }
|
|
.tr2 { background-color: #7fba00; }
|
|
.tr3 { background-color: #00a4ef; }
|
|
.tr4 { background-color: #ffb900; }
|
|
|
|
@media (max-width: 768px) {
|
|
.users-card {
|
|
padding: 20px 15px;
|
|
}
|
|
}
|
|
|
|
/* Modal Backdrop */
|
|
.admin-modal {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(11, 15, 25, 0.85);
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
.admin-modal.active {
|
|
opacity: 1;
|
|
pointer-events: all;
|
|
}
|
|
.admin-modal-content {
|
|
background-color: rgba(25, 30, 45, 0.95);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 20px;
|
|
padding: 30px;
|
|
width: 90%;
|
|
max-width: 500px;
|
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
|
|
transform: scale(0.9);
|
|
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.1);
|
|
}
|
|
.admin-modal.active .admin-modal-content {
|
|
transform: scale(1);
|
|
}
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
border-bottom: 1px solid var(--card-border);
|
|
padding-bottom: 12px;
|
|
}
|
|
.modal-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
color: #a5b4fc;
|
|
}
|
|
.modal-close {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--text-muted);
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
line-height: 1;
|
|
}
|
|
.modal-close:hover {
|
|
color: white;
|
|
}
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 12px;
|
|
margin-top: 24px;
|
|
border-top: 1px solid var(--card-border);
|
|
padding-top: 16px;
|
|
}
|
|
.btn-cancel {
|
|
background: transparent;
|
|
border: 1px solid var(--card-border);
|
|
color: var(--text-muted);
|
|
padding: 8px 16px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-size: 0.85rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.btn-cancel:hover {
|
|
color: white;
|
|
border-color: rgba(255,255,255,0.2);
|
|
}
|
|
.checkbox-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
|
gap: 10px;
|
|
max-height: 150px;
|
|
overflow-y: auto;
|
|
background: rgba(0,0,0,0.2);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
margin-top: 6px;
|
|
}
|
|
.checkbox-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
font-size: 0.85rem;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
.checkbox-item input {
|
|
margin-top: 3px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Nav -->
|
|
<header class="navbar">
|
|
<div class="nav-logo">
|
|
<svg viewBox="0 0 24 24" style="margin-right: 8px;">
|
|
<path d="M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,13C14.67,13 20,14.33 20,17V18H4V17C4,14.33 9.33,13 12,13Z" />
|
|
</svg>
|
|
Control Panel
|
|
</div>
|
|
<div class="nav-actions">
|
|
<!-- Allow Admin to view the User portal dashboard -->
|
|
<a href="{{ route('dashboard') }}" class="btn-nav">
|
|
User Dashboard
|
|
</a>
|
|
<form action="{{ route('logout') }}" method="POST" id="logout-form" style="display: none;">
|
|
@csrf
|
|
</form>
|
|
<a href="#" onclick="event.preventDefault(); document.getElementById('logout-form').submit();" class="btn-nav btn-logout">
|
|
Sign Out
|
|
</a>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Container -->
|
|
<main class="dashboard-container">
|
|
|
|
<!-- Header -->
|
|
<section class="admin-header">
|
|
<h1>System Overview</h1>
|
|
<p>Welcome, <strong>{{ $admin->name }}</strong>. Monitoring active directories, credentials, and SSO logins.</p>
|
|
</section>
|
|
|
|
<!-- Stats Grid -->
|
|
<section class="stats-grid">
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Total Registrations</div>
|
|
<div class="stat-value">{{ $stats['total_users'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: var(--accent-primary);">👥</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Administrators</div>
|
|
<div class="stat-value">{{ $stats['admins_count'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: #818cf8;">🛡️</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Blocked Accounts</div>
|
|
<div class="stat-value">{{ $stats['blocked_count'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: var(--error-color);">🚫</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">Registered Apps</div>
|
|
<div class="stat-value">{{ $stats['apps_count'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: #fbbf24;">📦</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div>
|
|
<div class="stat-label">System Roles</div>
|
|
<div class="stat-value">{{ $stats['roles_count'] }}</div>
|
|
</div>
|
|
<div class="stat-icon" style="color: #a5b4fc;">🏷️</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Alert messages in dashboard -->
|
|
@if($errors->any())
|
|
<div class="alert alert-danger" style="margin-bottom: 24px;">
|
|
<span class="alert-icon">⚠️</span>
|
|
<div>
|
|
<ul style="margin: 0; padding-left: 16px; font-size: 0.85rem; line-height: 1.5; list-style-type: disc;">
|
|
@foreach($errors->all() as $error)
|
|
<li>{{ $error }}</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if(session('error'))
|
|
<div class="alert alert-danger" style="margin-bottom: 24px;">
|
|
<span class="alert-icon">⚠️</span>
|
|
<div>{{ session('error') }}</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if(session('success'))
|
|
<div class="alert alert-success" style="margin-bottom: 24px;">
|
|
<span class="alert-icon">✓</span>
|
|
<div>{{ session('success') }}</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- App Management, Roles Management, & User Overrides Grid -->
|
|
<section class="management-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 20px; margin-bottom: 40px;">
|
|
|
|
<!-- App Management Card -->
|
|
<div class="admin-card" style="background-color: var(--card-bg); border: 1px solid var(--card-border); border-radius: 20px; padding: 30px; backdrop-filter: blur(16px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);">
|
|
<h3 style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 600; margin-bottom: 16px; color: #a5b4fc;">Manage System Services</h3>
|
|
|
|
<!-- Add App Form -->
|
|
<form action="{{ route('admin.apps.store') }}" method="POST" enctype="multipart/form-data" style="margin-bottom: 30px;">
|
|
@csrf
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px;">
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service Name</label>
|
|
<input type="text" name="name" required placeholder="e.g. Keka HR" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">SSO URL</label>
|
|
<input type="url" name="url" required placeholder="https://..." class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
</div>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px;">
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Theme Color</label>
|
|
<input type="text" name="color" placeholder="#f27059" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Portal Tag</label>
|
|
<input type="text" name="tag" placeholder="HR Portal" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
</div>
|
|
<div style="margin-bottom: 12px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Icon SVG Code or Preset Name (outlook/teams/keka)</label>
|
|
<textarea name="icon_svg" rows="2" placeholder="e.g. keka, outlook, teams, or raw <svg>...</svg>" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white; font-family:monospace; font-size:0.8rem;"></textarea>
|
|
</div>
|
|
<div style="margin-bottom: 12px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Upload Service Logo (Optional - Image will override preset SVG)</label>
|
|
<input type="file" name="logo" accept=".jpg,.jpeg,.png,.webp,.svg" class="form-input" style="width:100%; padding:8px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service Description</label>
|
|
<input type="text" name="desc" placeholder="Brief description of the service..." class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<button type="submit" class="btn" style="background-color: var(--accent-primary); color: white; width:100%; font-size:0.85rem; border-radius:8px; padding:10px; border:none; cursor:pointer; font-weight:600;">+ Register Service App</button>
|
|
</form>
|
|
|
|
<!-- Registered Apps List -->
|
|
<h4 style="font-size:0.9rem; font-weight:600; margin-bottom:12px; border-top:1px solid var(--card-border); padding-top:20px; color:#9ca3af;">Registered Service Apps</h4>
|
|
<div class="table-responsive" style="max-height: 250px; overflow-y: auto;">
|
|
<table class="users-table" style="font-size:0.8rem;">
|
|
<thead>
|
|
<tr>
|
|
<th>Service</th>
|
|
<th>Tag</th>
|
|
<th style="text-align: right;">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($apps as $app)
|
|
<tr>
|
|
<td>
|
|
<div style="display:flex; align-items:center; gap:8px;">
|
|
@if($app->logo)
|
|
<img src="{{ asset($app->logo) }}" alt="{{ $app->name }}" style="width:20px; height:20px; object-fit:contain; border-radius:4px;">
|
|
@else
|
|
<div style="width:20px; height:20px; border-radius:4px; background-color:{{ $app->color }}; display:flex; align-items:center; justify-content:center; color:white; font-size:10px; font-weight:bold;">
|
|
{{ strtoupper(substr($app->name, 0, 1)) }}
|
|
</div>
|
|
@endif
|
|
<strong>{{ $app->name }}</strong>
|
|
</div>
|
|
</td>
|
|
<td><span class="badge" style="background-color:rgba(255,255,255,0.05); color:#cbd5e1; border:1px solid rgba(255,255,255,0.1);">{{ $app->tag }}</span></td>
|
|
<td style="text-align: right;">
|
|
<form action="{{ route('admin.apps.destroy', $app->id) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this service?')" style="display:inline;">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="action-btn" style="color:#ef4444; border-color:rgba(239,68,68,0.2); font-size:0.75rem; padding:4px 8px;">Delete</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="3" style="text-align: center; color: var(--text-muted); padding: 20px;">No registered service apps.</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Role Management Card -->
|
|
<div class="admin-card" style="background-color: var(--card-bg); border: 1px solid var(--card-border); border-radius: 20px; padding: 30px; backdrop-filter: blur(16px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);">
|
|
<h3 style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 600; margin-bottom: 16px; color: #a5b4fc;">Manage Roles & Services</h3>
|
|
|
|
<!-- Add Role Form -->
|
|
<form action="{{ route('admin.roles.store') }}" method="POST" style="margin-bottom: 30px;">
|
|
@csrf
|
|
<div style="margin-bottom: 12px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Role Name</label>
|
|
<input type="text" name="name" required placeholder="e.g. Developer, HR" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div style="margin-bottom: 12px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Role Description</label>
|
|
<input type="text" name="description" placeholder="Description of this role..." class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Assign Services Access</label>
|
|
<div class="checkbox-grid">
|
|
@foreach($apps as $app)
|
|
<label class="checkbox-item">
|
|
<input type="checkbox" name="apps[]" value="{{ $app->id }}">
|
|
{{ $app->name }}
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn" style="background-color: var(--accent-primary); color: white; width:100%; font-size:0.85rem; border-radius:8px; padding:10px; border:none; cursor:pointer; font-weight:600;">+ Create Role</button>
|
|
</form>
|
|
|
|
<!-- Registered Roles List -->
|
|
<h4 style="font-size:0.9rem; font-weight:600; margin-bottom:12px; border-top:1px solid var(--card-border); padding-top:20px; color:#9ca3af;">System Application Roles</h4>
|
|
<div class="table-responsive" style="max-height: 250px; overflow-y: auto;">
|
|
<table class="users-table" style="font-size:0.8rem;">
|
|
<thead>
|
|
<tr>
|
|
<th>Role Name</th>
|
|
<th>Assigned Services</th>
|
|
<th style="text-align: right;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($roles as $role)
|
|
<tr>
|
|
<td>
|
|
<div>
|
|
<strong>{{ $role->name }}</strong>
|
|
@if($role->description)
|
|
<div style="font-size:0.7rem; color:var(--text-muted); margin-top:2px;">{{ $role->description }}</div>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div style="display:flex; flex-wrap:wrap; gap:4px;">
|
|
@forelse($role->apps as $app)
|
|
<span class="badge" style="background-color:{{ $app->color }}20; color:{{ $app->color }}; border:1px solid {{ $app->color }}40; font-size:0.65rem;">{{ $app->name }}</span>
|
|
@empty
|
|
<span style="color:var(--text-muted); font-size:0.7rem;">None</span>
|
|
@endforelse
|
|
</div>
|
|
</td>
|
|
<td style="text-align: right; white-space: nowrap;">
|
|
<button class="action-btn" onclick="openEditRoleModal({{ $role->id }}, '{{ addslashes($role->name) }}', '{{ addslashes($role->description) }}', {{ json_encode($role->apps->pluck('id')) }})" style="color:var(--info-color); border-color:rgba(6,182,212,0.2); font-size:0.75rem; padding:4px 8px; margin-right:4px;">Edit</button>
|
|
<form action="{{ route('admin.roles.destroy', $role->id) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this role?')" style="display:inline;">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="action-btn" style="color:#ef4444; border-color:rgba(239,68,68,0.2); font-size:0.75rem; padding:4px 8px;">Delete</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="3" style="text-align: center; color: var(--text-muted); padding: 20px;">No roles defined.</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User Service Overrides Card -->
|
|
<div class="admin-card" style="background-color: var(--card-bg); border: 1px solid var(--card-border); border-radius: 20px; padding: 30px; backdrop-filter: blur(16px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);">
|
|
<h3 style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 600; margin-bottom: 16px; color: #a5b4fc;">User Service Overrides</h3>
|
|
|
|
<!-- Add Override Form -->
|
|
<form action="{{ route('admin.overrides.store') }}" method="POST" style="margin-bottom: 30px;">
|
|
@csrf
|
|
<div style="margin-bottom: 12px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Select User Account</label>
|
|
<select name="user_id" required class="form-input" style="width:100%; padding:10px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<option value="">-- Choose User --</option>
|
|
@foreach($users as $usr)
|
|
@if(!$usr->isAdmin())
|
|
<option value="{{ $usr->id }}">{{ $usr->name }} ({{ $usr->email }})</option>
|
|
@endif
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px;">
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service App</label>
|
|
<select name="app_id" required class="form-input" style="width:100%; padding:10px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<option value="">-- Choose App --</option>
|
|
@foreach($apps as $app)
|
|
<option value="{{ $app->id }}">{{ $app->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Override Mode</label>
|
|
<select name="type" required class="form-input" style="width:100%; padding:10px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
<option value="allow">ALLOW (Add extra service)</option>
|
|
<option value="deny">DENY (Revoke service)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn" style="background-color: #fbbf24; color: #0b0f19; width:100%; font-size:0.85rem; border-radius:8px; padding:10px; font-weight:700; border:none; cursor:pointer;">Apply Override</button>
|
|
</form>
|
|
|
|
<!-- Active Overrides List -->
|
|
<h4 style="font-size:0.9rem; font-weight:600; margin-bottom:12px; border-top:1px solid var(--card-border); padding-top:20px; color:#9ca3af;">Active Service Overrides</h4>
|
|
<div class="table-responsive" style="max-height: 250px; overflow-y: auto;">
|
|
<table class="users-table" style="font-size:0.8rem;">
|
|
<thead>
|
|
<tr>
|
|
<th>User</th>
|
|
<th>App</th>
|
|
<th>Type</th>
|
|
<th style="text-align: right;">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($overrides as $override)
|
|
<tr>
|
|
<td>
|
|
<div>
|
|
<strong>{{ $override->user->name }}</strong>
|
|
<div style="font-size:0.7rem; color:var(--text-muted);">{{ $override->user->email }}</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
@if($override->app)
|
|
<span style="display:flex; align-items:center; gap:6px;">
|
|
<div style="width:8px; height:8px; border-radius:50%; background-color:{{ $override->app->color }};"></div>
|
|
{{ $override->app->name }}
|
|
</span>
|
|
@else
|
|
<span style="color:#ef4444;">App Deleted</span>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
@if($override->type === 'allow')
|
|
<span class="badge" style="background-color:rgba(16,185,129,0.12); color:#10b981; border:1px solid rgba(16,185,129,0.3); font-size:0.65rem; padding:2px 6px;">ADDITIONAL</span>
|
|
@else
|
|
<span class="badge" style="background-color:rgba(239,68,68,0.12); color:#ef4444; border:1px solid rgba(239,68,68,0.3); font-size:0.65rem; padding:2px 6px;">REVOKED</span>
|
|
@endif
|
|
</td>
|
|
<td style="text-align: right;">
|
|
<form action="{{ route('admin.overrides.destroy', $override->id) }}" method="POST" style="display:inline;">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="action-btn" style="color:#ef4444; border-color:rgba(239,68,68,0.2); font-size:0.75rem; padding:4px 8px;">Remove</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="4" style="text-align: center; color: var(--text-muted); padding: 20px;">No custom overrides active. Services resolved by roles.</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<!-- Users Table Card -->
|
|
<section class="users-card">
|
|
<div class="card-header">
|
|
<div class="card-title">User Accounts Directory</div>
|
|
<div style="font-size: 0.8rem; color: var(--text-muted);">
|
|
Showing registered system entities and block controls
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="users-table">
|
|
<thead>
|
|
<tr>
|
|
<th>User Account</th>
|
|
<th>Assigned Role</th>
|
|
<th>Authentication Type</th>
|
|
<th>Linked On</th>
|
|
<th style="text-align: right;">Operations</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($users as $usr)
|
|
<tr>
|
|
<td>
|
|
<div class="user-cell">
|
|
@if($usr->avatar)
|
|
<img src="{{ $usr->avatar }}" alt="{{ $usr->name }}" class="user-avatar">
|
|
@else
|
|
<div class="user-avatar-fallback">
|
|
{{ strtoupper(substr($usr->name, 0, 1)) }}
|
|
</div>
|
|
@endif
|
|
<div>
|
|
<div class="user-name" style="display:flex; align-items:center; gap:8px;">
|
|
{{ $usr->name }}
|
|
@if($usr->is_blocked)
|
|
<span class="badge" style="background-color:rgba(239,68,68,0.15); color:#fca5a5; border:1px solid rgba(239,68,68,0.3); font-size:0.65rem; padding:2px 6px;">Blocked</span>
|
|
@else
|
|
<span class="badge" style="background-color:rgba(16,185,129,0.12); color:#a7f3d0; border:1px solid rgba(16,185,129,0.25); font-size:0.65rem; padding:2px 6px;">Active</span>
|
|
@endif
|
|
</div>
|
|
<div class="user-email">{{ $usr->email }}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
@if($usr->role === 'admin')
|
|
<span class="badge badge-admin">Admin</span>
|
|
@else
|
|
<span class="badge badge-user">User Portal</span>
|
|
@endif
|
|
@if($usr->roles->isNotEmpty())
|
|
<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:6px;">
|
|
@foreach($usr->roles as $role)
|
|
<span class="badge" style="background-color:rgba(99,102,241,0.1); color:#818cf8; border:1px solid rgba(99,102,241,0.25); font-size:0.65rem;">{{ $role->name }}</span>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
@if($usr->microsoft_id)
|
|
<span class="badge badge-microsoft">
|
|
<div class="microsoft-icon-tiny">
|
|
<div class="tiny-rect tr1"></div>
|
|
<div class="tiny-rect tr2"></div>
|
|
<div class="tiny-rect tr3"></div>
|
|
<div class="tiny-rect tr4"></div>
|
|
</div>
|
|
Microsoft OAuth
|
|
</span>
|
|
@else
|
|
<span class="badge badge-password">Local Password</span>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
<span style="font-size: 0.85rem; color: var(--text-muted);">
|
|
{{ $usr->created_at ? $usr->created_at->format('Y-m-d H:i') : 'N/A' }}
|
|
</span>
|
|
</td>
|
|
<td style="text-align: right; white-space: nowrap;">
|
|
@if($usr->id !== $admin->id)
|
|
@if(!$usr->isAdmin())
|
|
<button class="action-btn" onclick="openAssignRolesModal({{ $usr->id }}, '{{ addslashes($usr->name) }}', {{ json_encode($usr->roles->pluck('id')) }})" style="color:var(--accent-primary); border-color:rgba(99,102,241,0.2); font-size:0.75rem; padding:6px 12px; margin-right:4px;">Assign Roles</button>
|
|
@endif
|
|
<form action="{{ route('admin.users.toggle-block', $usr->id) }}" method="POST" style="display:inline;">
|
|
@csrf
|
|
<button type="submit" class="action-btn"
|
|
style="font-size:0.75rem; padding:6px 12px; border-radius:6px; font-weight:600; cursor:pointer; transition:all 0.2s ease;
|
|
{{ $usr->is_blocked ? 'color:#10b981; border-color:rgba(16,185,129,0.2); background-color:rgba(16,185,129,0.05);' : 'color:#ef4444; border-color:rgba(239,68,68,0.2); background-color:rgba(239,68,68,0.05);' }}">
|
|
{{ $usr->is_blocked ? 'Unblock' : 'Block' }}
|
|
</button>
|
|
</form>
|
|
@else
|
|
<span style="font-size:0.75rem; color:var(--text-muted); font-style:italic;">You (Admin)</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" style="text-align: center; color: var(--text-muted); padding: 30px;">
|
|
No registered accounts found in the database.
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
|
|
<!-- Edit Role Modal -->
|
|
<div id="edit-role-modal" class="admin-modal">
|
|
<div class="admin-modal-content">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Edit Application Role</h3>
|
|
<button class="modal-close" onclick="closeModal('edit-role-modal')">×</button>
|
|
</div>
|
|
<form id="edit-role-form" method="POST">
|
|
@csrf
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Role Name</label>
|
|
<input type="text" id="edit-role-name" name="name" required class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Description</label>
|
|
<input type="text" id="edit-role-description" name="description" class="form-input" style="width:100%; padding:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Services Access</label>
|
|
<div class="checkbox-grid">
|
|
@foreach($apps as $app)
|
|
<label class="checkbox-item">
|
|
<input type="checkbox" name="apps[]" value="{{ $app->id }}" class="edit-role-app-checkbox" id="edit-role-app-{{ $app->id }}">
|
|
{{ $app->name }}
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn-cancel" onclick="closeModal('edit-role-modal')">Cancel</button>
|
|
<button type="submit" class="action-btn" style="background-color:var(--accent-primary); color:white; border-color:transparent; font-weight:600; padding:8px 16px; border-radius:8px; cursor:pointer;">Save Changes</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Assign User Roles Modal -->
|
|
<div id="assign-roles-modal" class="admin-modal">
|
|
<div class="admin-modal-content">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Assign Roles to <span id="assign-user-name">User</span></h3>
|
|
<button class="modal-close" onclick="closeModal('assign-roles-modal')">×</button>
|
|
</div>
|
|
<form id="assign-roles-form" method="POST">
|
|
@csrf
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Select Roles for User</label>
|
|
<div style="background:rgba(0,0,0,0.2); border:1px solid var(--card-border); border-radius:8px; padding:12px; max-height:200px; overflow-y:auto; display:flex; flex-direction:column; gap:8px;">
|
|
@foreach($roles as $role)
|
|
<label class="checkbox-item">
|
|
<input type="checkbox" name="roles[]" value="{{ $role->id }}" class="assign-user-role-checkbox" id="assign-user-role-{{ $role->id }}">
|
|
<div>
|
|
<strong>{{ $role->name }}</strong>
|
|
@if($role->description)
|
|
<div style="font-size:0.7rem; color:var(--text-muted);">{{ $role->description }}</div>
|
|
@endif
|
|
</div>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn-cancel" onclick="closeModal('assign-roles-modal')">Cancel</button>
|
|
<button type="submit" class="action-btn" style="background-color:var(--accent-primary); color:white; border-color:transparent; font-weight:600; padding:8px 16px; border-radius:8px; cursor:pointer;">Update Roles</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function openModal(modalId) {
|
|
document.getElementById(modalId).classList.add('active');
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
document.getElementById(modalId).classList.remove('active');
|
|
}
|
|
|
|
function openEditRoleModal(id, name, description, appIds) {
|
|
const form = document.getElementById('edit-role-form');
|
|
form.action = `/admin/roles/${id}/update`;
|
|
|
|
document.getElementById('edit-role-name').value = name;
|
|
document.getElementById('edit-role-description').value = description || '';
|
|
|
|
// Reset and check assigned apps
|
|
document.querySelectorAll('.edit-role-app-checkbox').forEach(cb => {
|
|
cb.checked = appIds.includes(parseInt(cb.value));
|
|
});
|
|
|
|
openModal('edit-role-modal');
|
|
}
|
|
|
|
function openAssignRolesModal(userId, name, roleIds) {
|
|
const form = document.getElementById('assign-roles-form');
|
|
form.action = `/admin/users/${userId}/roles`;
|
|
|
|
document.getElementById('assign-user-name').innerText = name;
|
|
|
|
// Reset and check user's roles
|
|
document.querySelectorAll('.assign-user-role-checkbox').forEach(cb => {
|
|
cb.checked = roleIds.includes(parseInt(cb.value));
|
|
});
|
|
|
|
openModal('assign-roles-modal');
|
|
}
|
|
</script>
|
|
|
|
<footer style="text-align: center; padding: 40px 0; font-size: 0.75rem; color: var(--text-muted); z-index: 10;">
|
|
© 2026 Systems. Authorized Access Only.
|
|
</footer>
|
|
</body>
|
|
</html>
|