sls/resources/views/admin/dashboard.blade.php

1763 lines
90 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">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- jQuery (required for Select2) -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Select2 CSS -->
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<!-- Select2 JS -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<style>
:root {
--bg-color: #0b0f19;
--card-bg: rgba(15, 23, 42, 0.55); /* Darker translucent glass base */
--card-border: rgba(255, 255, 255, 0.22); /* Crisp gloss border */
--accent-primary: #6366f1;
--accent-secondary: #0078d4;
--text-main: #ffffff;
--text-muted: #d1d5db;
--success-color: #10b981;
--error-color: #ef4444;
--info-color: #06b6d4;
}
/* Select2 Premium Theme Overrides */
.select2-container--default .select2-selection--multiple {
background-color: rgba(255, 255, 255, 0.03) !important;
border: 1px solid var(--card-border) !important;
border-radius: 8px !important;
min-height: 42px !important;
padding: 2px 6px !important;
transition: all 0.2s ease !important;
}
.select2-container--default.select2-container--focus .select2-selection--multiple {
border-color: var(--accent-primary) !important;
background-color: rgba(255, 255, 255, 0.06) !important;
box-shadow: 0 0 10px rgba(99, 102, 241, 0.25) !important;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: var(--accent-primary) !important;
border: none !important;
color: white !important;
border-radius: 6px !important;
padding: 2px 10px !important;
font-size: 0.8rem !important;
font-weight: 600 !important;
font-family: 'Outfit', sans-serif !important;
margin-top: 6px !important;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: rgba(255, 255, 255, 0.8) !important;
margin-right: 6px !important;
border: none !important;
background: none !important;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
color: white !important;
background: none !important;
}
.select2-container--default .select2-search--inline .select2-search__field {
color: var(--text-main) !important;
font-family: 'Inter', sans-serif !important;
font-size: 0.85rem !important;
margin-top: 8px !important;
}
.select2-dropdown {
background-color: #151b2c !important;
border: 1px solid var(--card-border) !important;
border-radius: 8px !important;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5) !important;
backdrop-filter: blur(20px) saturate(120%) !important;
-webkit-backdrop-filter: blur(20px) saturate(120%) !important;
z-index: 9999 !important;
}
.select2-container--default .select2-results__option {
padding: 8px 12px !important;
font-size: 0.85rem !important;
color: var(--text-main) !important;
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: var(--accent-primary) !important;
color: white !important;
}
.select2-container--default .select2-results__option[aria-selected=true] {
background-color: rgba(99, 102, 241, 0.2) !important;
color: var(--text-main) !important;
}
.select2-container--default .select2-search--dropdown .select2-search__field {
background-color: rgba(0, 0, 0, 0.2) !important;
border: 1px solid var(--card-border) !important;
border-radius: 6px !important;
color: var(--text-main) !important;
padding: 6px 10px !important;
outline: none !important;
}
/* Modern Ultra-Thin White Custom Scrollbars */
html, body, *, ::-webkit-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.4) rgba(15, 23, 42, 0.35);
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.35);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.4);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.15);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.85);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
}
::-webkit-scrollbar-corner {
background: transparent;
}
.select2-results__options {
max-height: 220px !important;
overflow-y: auto !important;
scrollbar-width: thin !important;
scrollbar-color: rgba(255, 255, 255, 0.4) rgba(15, 23, 42, 0.35) !important;
}
.select2-results__options::-webkit-scrollbar {
width: 4px !important;
}
* {
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;
color: #ffffff;
display: flex;
align-items: center;
gap: 8px;
}
.nav-logo svg {
width: 24px;
height: 24px;
fill: #ffffff;
}
.nav-actions {
display: flex;
align-items: center;
gap: 12px;
}
.btn-nav {
color: #e5e7eb;
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.3);
background-color: rgba(255, 255, 255, 0.08);
}
.btn-logout {
color: #ef4444;
border-color: rgba(239, 68, 68, 0.25);
}
.btn-logout:hover {
color: white;
background-color: var(--error-color);
border-color: transparent;
}
/* Dashboard Wrapper */
.dashboard-container {
flex: 1;
padding: 30px 4%;
max-width: 1400px;
width: 100%;
margin: 0 auto;
z-index: 10;
box-sizing: border-box;
}
/* Management Grid - 2 cards per row on desktop, 3rd card shifts to lower row */
.management-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 30px;
margin-bottom: 50px;
width: 100%;
box-sizing: border-box;
}
@media (max-width: 900px) {
.management-grid {
grid-template-columns: 1fr;
}
}
/* Welcome Section */
.admin-header {
margin-bottom: 30px;
}
.admin-header h1 {
font-family: 'Outfit', sans-serif;
font-size: 2rem;
font-weight: 700;
color: #ffffff;
margin-bottom: 6px;
}
.admin-header p {
color: #e5e7eb;
font-size: 0.95rem;
}
/* Statistics Cards Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 22px;
margin-bottom: 40px;
width: 100%;
box-sizing: border-box;
}
.stat-card {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%), var(--card-bg);
border: 2px solid var(--card-border);
border-radius: 18px;
padding: 26px;
backdrop-filter: blur(25px) saturate(130%);
-webkit-backdrop-filter: blur(25px) saturate(130%);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3), inset 0 1px 2px rgba(255, 255, 255, 0.18), inset 0 -1px 1px rgba(255, 255, 255, 0.05);
display: flex;
align-items: center;
justify-content: space-between;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.stat-card:hover {
transform: translateY(-2px);
border-color: rgba(255, 255, 255, 0.35);
box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.28);
}
.admin-card {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%), var(--card-bg);
border: 2px solid var(--card-border);
border-radius: 24px;
padding: 34px;
backdrop-filter: blur(25px) saturate(130%);
-webkit-backdrop-filter: blur(25px) saturate(130%);
box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.35), inset 0 1px 2px rgba(255, 255, 255, 0.18);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
display: flex;
flex-direction: column;
justify-content: flex-start;
min-height: 450px;
}
.admin-card:hover {
border-color: rgba(255, 255, 255, 0.35);
box-shadow: 0 16px 48px 0 rgba(0, 0, 0, 0.45), inset 0 1px 2px rgba(255, 255, 255, 0.28);
transform: translateY(-2px);
}
.stat-label {
font-size: 0.8rem;
color: #d1d5db;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 6px;
font-weight: 600;
}
.stat-value {
font-family: 'Outfit', sans-serif;
font-size: 1.8rem;
font-weight: 800;
color: #ffffff;
}
.stat-icon {
font-size: 2rem;
opacity: 0.85;
}
/* Users Management Card */
.users-card {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%), var(--card-bg);
border: 2px solid var(--card-border);
border-radius: 24px;
padding: 34px;
backdrop-filter: blur(25px) saturate(130%);
-webkit-backdrop-filter: blur(25px) saturate(130%);
box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.35), inset 0 1px 2px rgba(255, 255, 255, 0.18);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.users-card:hover {
border-color: rgba(255, 255, 255, 0.35);
box-shadow: 0 16px 48px 0 rgba(0, 0, 0, 0.45), inset 0 1px 2px rgba(255, 255, 255, 0.28);
transform: translateY(-2px);
}
/* Users Table styling */
.table-responsive {
width: 100%;
overflow-x: auto;
scrollbar-width: thin;
}
/* Modern Industry-Standard Role Cell Hover & Quick Edit */
.role-cell-container {
position: relative;
padding: 8px 12px;
border-radius: 10px;
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
border: 1px solid transparent;
cursor: pointer;
display: flex;
flex-direction: column;
gap: 4px;
}
.role-cell-container:hover {
background-color: rgba(99, 102, 241, 0.08);
border-color: rgba(99, 102, 241, 0.3);
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.15);
}
.role-quick-edit-btn {
position: absolute;
top: 6px;
right: 6px;
opacity: 0;
transform: scale(0.85);
background: linear-gradient(135deg, #6366f1 0%, #38bdf8 100%);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
z-index: 5;
}
.role-cell-container:hover .role-quick-edit-btn {
opacity: 1;
transform: scale(1);
}
.role-quick-edit-btn:hover {
transform: scale(1.18) rotate(6deg);
box-shadow: 0 6px 18px rgba(99, 102, 241, 0.6);
}
/* Alert Styling */
.alert {
padding: 14px 20px;
border-radius: 12px;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 12px;
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.2);
}
.alert-danger {
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.25);
color: #f87171;
}
.alert-success {
background-color: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.25);
color: #34d399;
}
.alert-icon {
font-size: 1.1rem;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.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);
}
/* Universal Edit & Delete Buttons */
.action-btn-edit {
color: var(--info-color) !important;
border-color: rgba(6, 182, 212, 0.25) !important;
background-color: rgba(6, 182, 212, 0.05) !important;
padding: 6px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
border-radius: 6px !important;
cursor: pointer;
transition: all 0.2s ease !important;
}
.action-btn-edit:hover {
color: #38bdf8 !important;
border-color: rgba(56, 189, 248, 0.5) !important;
background-color: rgba(6, 182, 212, 0.2) !important;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(6, 182, 212, 0.3) !important;
}
.action-btn-delete {
color: #ef4444 !important;
border-color: rgba(239, 68, 68, 0.25) !important;
background-color: rgba(239, 68, 68, 0.05) !important;
padding: 6px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
border-radius: 6px !important;
cursor: pointer;
transition: all 0.2s ease !important;
}
.action-btn-delete:hover {
color: #f87171 !important;
border-color: rgba(239, 68, 68, 0.5) !important;
background-color: rgba(239, 68, 68, 0.2) !important;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3) !important;
}
.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.96);
border: 2px solid var(--card-border);
border-radius: 22px;
padding: 32px;
width: 90%;
max-width: 500px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
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: 700;
color: #ffffff;
}
.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;">
<div>{{ session('success') }}</div>
</div>
@endif
<!-- Users Table Card -->
<section class="users-card" style="margin-bottom: 50px;">
<div class="card-header" style="margin-bottom: 20px;">
<div class="card-title" style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 700; color: #ffffff;">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>
<div class="role-cell-container" onclick="openAssignRolesModal({{ $usr->id }}, '{{ addslashes($usr->name) }}', {{ json_encode($usr->roles->pluck('id')) }})" title="Click to modify roles for {{ $usr->name }}">
<!-- Quick Edit Floating Button on Hover -->
@if($usr->id !== $admin->id)
<button class="role-quick-edit-btn" onclick="event.stopPropagation(); openAssignRolesModal({{ $usr->id }}, '{{ addslashes($usr->name) }}', {{ json_encode($usr->roles->pluck('id')) }})" title="Edit User Roles">
<svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor">
<path d="M14.06,9L15,9.94L5.92,19H5V18.06L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z" />
</svg>
</button>
@endif
<div>
@if($usr->role === 'admin')
<span class="badge badge-admin">Admin</span>
@else
<span class="badge badge-user">User Portal</span>
@endif
</div>
@if($usr->roles->isNotEmpty())
<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:2px;">
@foreach($usr->roles as $role)
@if(strtolower($role->name) === 'admin')
@continue
@endif
<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>
@elseif($usr->role !== 'admin')
<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:2px;">
<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;">{{ $defaultRoleName }}</span>
</div>
@endif
@if($usr->overrides->isNotEmpty())
<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:4px;">
@foreach($usr->overrides as $ov)
@if($ov->type === 'allow')
<span class="badge" style="background-color:rgba(16,185,129,0.15); color:#34d399; border:1px solid rgba(16,185,129,0.3); font-size:0.65rem; display:inline-flex; align-items:center; gap:4px;" title="Explicitly Allowed Service">
+ {{ $ov->app->name ?? 'App' }}
<form action="{{ route('admin.overrides.destroy', $ov->id) }}" method="POST" onsubmit="return confirm('Remove override for {{ addslashes($ov->app->name ?? 'App') }}?')" style="display:inline; margin:0;" onclick="event.stopPropagation();">
@csrf
@method('DELETE')
<button type="submit" style="background:none; border:none; color:#34d399; cursor:pointer; padding:0 2px; line-height:1; font-weight:bold; font-size:0.75rem;" title="Remove Override">&times;</button>
</form>
</span>
@else
<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; display:inline-flex; align-items:center; gap:4px;" title="Explicitly Revoked Service">
&minus; {{ $ov->app->name ?? 'App' }}
<form action="{{ route('admin.overrides.destroy', $ov->id) }}" method="POST" onsubmit="return confirm('Remove revoked override for {{ addslashes($ov->app->name ?? 'App') }}?')" style="display:inline; margin:0;" onclick="event.stopPropagation();">
@csrf
@method('DELETE')
<button type="submit" style="background:none; border:none; color:#fca5a5; cursor:pointer; padding:0 2px; line-height:1; font-weight:bold; font-size:0.75rem;" title="Remove Revoked Override">&times;</button>
</form>
</span>
@endif
@endforeach
</div>
@endif
</div>
</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)
<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>
<button class="action-btn" onclick="openCreateOverrideModalForUser({{ $usr->id }})" style="color:#06b6d4; border-color:rgba(6,182,212,0.25); background-color:rgba(6,182,212,0.05); font-size:0.75rem; padding:6px 10px; margin-right:4px; font-weight:600;" title="Manage Service Overrides">
<svg viewBox="0 0 24 24" style="width: 13px; height: 13px; fill: currentColor; vertical-align: text-bottom; margin-right: 2px;">
<path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M11,7V11H7V13H11V17H13V13H17V11H13V7H11Z"/>
</svg>
Overrides
</button>
@if($usr->isAdmin())
<button class="action-btn" onclick="confirmAdminToggle({{ $usr->id }}, '{{ addslashes($usr->name) }}', 'revoke')"
style="color:#ef4444; border-color:rgba(239,68,68,0.3); background-color:rgba(239,68,68,0.05); font-size:0.75rem; padding:6px 12px; margin-right:4px; font-weight:600;">
Revoke Admin
</button>
@else
<button class="action-btn" onclick="confirmAdminToggle({{ $usr->id }}, '{{ addslashes($usr->name) }}', 'grant')"
style="color:#f59e0b; border-color:rgba(245,158,11,0.3); background-color:rgba(245,158,11,0.05); font-size:0.75rem; padding:6px 12px; margin-right:4px; font-weight:600; box-shadow: 0 0 10px rgba(245,158,11,0.15);">
Give Admin Privilege
</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>
<!-- App Management, Roles Management, & User Overrides Grid -->
<section class="management-grid">
<!-- App Management Card -->
<div class="admin-card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h3 style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 700; color: #ffffff; margin: 0;">Manage System Services</h3>
<button onclick="openModal('add-service-modal')" class="action-btn" style="color:var(--accent-primary); border-color:var(--accent-primary); font-size:0.75rem; padding:6px 12px; font-weight:600; cursor:pointer;">+ Add Service</button>
</div>
<!-- Registered Apps List -->
<div class="table-responsive" style="max-height: 480px; overflow-y: auto;">
<table class="users-table" style="font-size:0.8rem;">
<thead>
<tr>
<th style="width: 45%;">Service</th>
<th style="width: 35%;">Tag</th>
<th style="width: 20%; 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;">
<div style="display: inline-flex; gap: 6px; justify-content: flex-end; align-items: center;">
<button class="action-btn action-btn-edit" onclick="openEditAppModal({{ $app->id }}, {{ json_encode($app->name) }}, {{ json_encode($app->url) }}, {{ json_encode($app->tag) }}, {{ json_encode($app->icon_svg) }}, {{ json_encode($app->color) }}, {{ json_encode($app->desc) }}, {{ json_encode($app->logo) }})" title="Edit Service">
<svg viewBox="0 0 24 24" style="width: 14px; height: 14px; fill: currentColor;">
<path d="M14.06,9L15,9.94L5.92,19H5V18.06L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z" />
</svg>
</button>
<form action="{{ route('admin.apps.destroy', $app->id) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this service?')" style="margin: 0; display: inline;">
@csrf
@method('DELETE')
<button type="submit" class="action-btn action-btn-delete" title="Delete Service">
<svg viewBox="0 0 24 24" style="width: 14px; height: 14px; fill: currentColor;">
<path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
</svg>
</button>
</form>
</div>
</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">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h3 style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 700; color: #ffffff; margin: 0;">Manage Roles & Services</h3>
<button onclick="openModal('create-role-modal')" class="action-btn" style="color:var(--accent-primary); border-color:var(--accent-primary); font-size:0.75rem; padding:6px 12px; font-weight:600; cursor:pointer;">+ Add Role</button>
</div>
<!-- Default Role Configuration Form (compact inline) -->
<form action="{{ route('admin.settings.default-role') }}" method="POST" style="margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid rgba(255, 255, 255, 0.1);">
@csrf
<div style="margin-bottom: 0;">
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Default Role for New Users</label>
<div style="display: flex; gap: 8px;">
<select name="default_role" required class="form-input" style="flex: 1; padding:8px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white; outline: none; font-size: 0.8rem;">
@foreach($roles as $role)
<option value="{{ $role->name }}" {{ $defaultRoleName === $role->name ? 'selected' : '' }} style="background-color: #1e1e38; color: white;">
{{ $role->name }}
</option>
@endforeach
</select>
<button type="submit" class="btn" style="background-color: var(--accent-primary); color: white; font-size:0.75rem; border-radius:8px; padding:8px 12px; border:none; cursor:pointer; font-weight:600; white-space: nowrap;">Save Default</button>
</div>
</div>
</form>
<!-- Registered Roles List -->
<div class="table-responsive" style="max-height: 480px; overflow-y: auto;">
<table class="users-table" style="font-size:0.8rem;">
<thead>
<tr>
<th style="width: 35%;">Role Name</th>
<th style="width: 45%;">Assigned Services</th>
<th style="width: 20%; 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;">
<div style="display: inline-flex; gap: 6px; justify-content: flex-end; align-items: center;">
<button class="action-btn action-btn-edit" onclick="openEditRoleModal({{ $role->id }}, '{{ addslashes($role->name) }}', '{{ addslashes($role->description) }}', {{ json_encode($role->apps->pluck('id')) }})" title="Edit Role">
<svg viewBox="0 0 24 24" style="width: 14px; height: 14px; fill: currentColor;">
<path d="M14.06,9L15,9.94L5.92,19H5V18.06L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z" />
</svg>
</button>
<form action="{{ route('admin.roles.destroy', $role->id) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this role?')" style="margin: 0; display: inline;">
@csrf
@method('DELETE')
<button type="submit" class="action-btn action-btn-delete" title="Delete Role">
<svg viewBox="0 0 24 24" style="width: 14px; height: 14px; fill: currentColor;">
<path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
</svg>
</button>
</form>
</div>
</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>
</section>
</section>
</main>
<!-- Add Service Modal -->
<div id="add-service-modal" class="admin-modal">
<div class="admin-modal-content" style="max-height: 90vh; overflow-y: auto;">
<div class="modal-header">
<h3 class="modal-title">Register New Service App</h3>
<button class="modal-close" onclick="closeModal('add-service-modal')">&times;</button>
</div>
<form action="{{ route('admin.apps.store') }}" method="POST" enctype="multipart/form-data" class="validated-form">
@csrf
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
<div>
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service Name <span style="color: #ef4444; font-weight: bold;">*</span></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 class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">SSO URL <span style="color: #ef4444; font-weight: bold;">*</span></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 class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please enter a valid URL.</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
<div>
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Brand Accent Color (Hex)</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: 16px;">
<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: 16px;">
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Upload Service Logo (Optional - Max 5MB)</label>
<input type="file" name="logo" accept=".jpg,.jpeg,.png,.webp,.svg" class="form-input logo-upload-5mb" style="width:100%; padding:8px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
<div class="file-size-error" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">File size exceeds maximum limit of 5MB.</div>
</div>
<div style="margin-bottom: 20px;">
<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>
<div class="modal-footer">
<button type="button" class="btn-cancel" onclick="closeModal('add-service-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;">+ Register Service</button>
</div>
</form>
</div>
</div>
<!-- Create Role Modal -->
<div id="create-role-modal" class="admin-modal">
<div class="admin-modal-content" style="max-height: 90vh; overflow-y: auto;">
<div class="modal-header">
<h3 class="modal-title">Create Application Role</h3>
<button class="modal-close" onclick="closeModal('create-role-modal')">&times;</button>
</div>
<form action="{{ route('admin.roles.store') }}" method="POST" class="validated-form">
@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 <span style="color: #ef4444; font-weight: bold;">*</span></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 class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
</div>
<div style="margin-bottom: 16px;">
<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: 20px;">
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Assign Services Access</label>
<select name="apps[]" id="create-role-apps-select" multiple="multiple" class="form-input select2-multiple" style="width: 100%;">
@foreach($apps as $app)
<option value="{{ $app->id }}">{{ $app->name }}</option>
@endforeach
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn-cancel" onclick="closeModal('create-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;">+ Create Role</button>
</div>
</form>
</div>
</div>
<!-- Create Override Modal -->
<div id="create-override-modal" class="admin-modal">
<div class="admin-modal-content" style="max-height: 90vh; overflow-y: auto;">
<div class="modal-header">
<h3 class="modal-title">Create User Service Override</h3>
<button class="modal-close" onclick="closeModal('create-override-modal')">&times;</button>
</div>
<form action="{{ route('admin.overrides.store') }}" method="POST" class="validated-form">
@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 User Account <span style="color: #ef4444; font-weight: bold;">*</span></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 class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please select a user account.</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px;">
<div>
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service App <span style="color: #ef4444; font-weight: bold;">*</span></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 class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please select a service app.</div>
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Override Mode <span style="color: #ef4444; font-weight: bold;">*</span></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>
<div class="modal-footer">
<button type="button" class="btn-cancel" onclick="closeModal('create-override-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;">Apply Override</button>
</div>
</form>
</div>
</div>
<!-- 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')">&times;</button>
</div>
<form id="edit-role-form" method="POST" class="validated-form">
@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 <span style="color: #ef4444; font-weight: bold;">*</span></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 class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
</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: 20px;">
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Services Access</label>
<select name="apps[]" id="edit-role-apps-select" multiple="multiple" class="form-input select2-multiple" style="width: 100%;">
@foreach($apps as $app)
<option value="{{ $app->id }}">{{ $app->name }}</option>
@endforeach
</select>
</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>
<!-- Edit App Modal -->
<div id="edit-app-modal" class="admin-modal">
<div class="admin-modal-content" style="max-height: 90vh; overflow-y: auto;">
<div class="modal-header">
<h3 class="modal-title">Edit Service App</h3>
<button class="modal-close" onclick="closeModal('edit-app-modal')">&times;</button>
</div>
<form id="edit-app-form" method="POST" enctype="multipart/form-data" class="validated-form">
@csrf
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
<div>
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service Name <span style="color: #ef4444; font-weight: bold;">*</span></label>
<input type="text" id="edit-app-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 class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">This field is required.</div>
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service URL <span style="color: #ef4444; font-weight: bold;">*</span></label>
<input type="url" id="edit-app-url" name="url" 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 class="field-error-msg" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">Please enter a valid URL.</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
<div>
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Brand Accent Color (Hex)</label>
<input type="text" id="edit-app-color" name="color" placeholder="#4f46e5" 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" id="edit-app-tag" 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: 16px;">
<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 id="edit-app-icon_svg" 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: 16px;">
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Upload Service Logo (Optional - Max 5MB)</label>
<input type="file" name="logo" accept=".jpg,.jpeg,.png,.webp,.svg" class="form-input logo-upload-5mb" style="width:100%; padding:8px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white;">
<div class="file-size-error" style="color: #ef4444; font-size: 0.75rem; margin-top: 4px; font-weight: 500; display: none;">File size exceeds maximum limit of 5MB.</div>
<div style="font-size:0.7rem; color:var(--text-muted); margin-top:4px;">Currently: <span id="edit-app-logo-status" style="font-weight:600; color:#38bdf8;">None</span></div>
</div>
<div style="margin-bottom: 20px;">
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service Description</label>
<input type="text" id="edit-app-desc" 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>
<div class="modal-footer">
<button type="button" class="btn-cancel" onclick="closeModal('edit-app-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 & Service Overrides Modal -->
<div id="assign-roles-modal" class="admin-modal">
<div class="admin-modal-content" style="max-width: 580px; max-height: 90vh; overflow-y: auto;">
<div class="modal-header">
<h3 class="modal-title">Manage Access for <span id="assign-user-name" style="color:var(--accent-primary);">User</span></h3>
<button class="modal-close" onclick="closeModal('assign-roles-modal')">&times;</button>
</div>
<!-- Section 1: Assigned Application Roles -->
<form id="assign-roles-form" method="POST" style="margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid var(--card-border);">
@csrf
<div style="margin-bottom: 12px;">
<label style="display:block; font-size:0.85rem; color:#ffffff; font-weight:600; margin-bottom:8px;">
1. Application Roles (Group Access)
</label>
<select name="roles[]" id="assign-roles-select" multiple="multiple" class="form-input select2-multiple" style="width: 100%;">
@foreach($roles as $role)
@if(strtolower($role->name) === 'admin')
@continue
@endif
<option value="{{ $role->id }}">{{ $role->name }}</option>
@endforeach
</select>
</div>
<div style="display:flex; justify-content:flex-end;">
<button type="submit" class="action-btn" style="background-color:var(--accent-primary); color:white; border-color:transparent; font-weight:600; padding:6px 14px; border-radius:8px; cursor:pointer; font-size:0.8rem;">Update Roles</button>
</div>
</form>
<!-- Section 2: Specific Service Overrides (Grant or Revoke Specific Apps) -->
<div>
<label style="display:block; font-size:0.85rem; color:#ffffff; font-weight:600; margin-bottom:10px;">
2. Specific Service Overrides (Add / Revoke Individual Services)
</label>
<!-- Active Overrides List -->
<div id="modal-active-overrides-container" style="margin-bottom: 16px;">
<!-- Dynamically populated via JS -->
</div>
<!-- Add New Override Sub-Form -->
<form action="{{ route('admin.overrides.store') }}" method="POST" class="validated-form" style="background: rgba(255,255,255,0.03); border: 1px dashed var(--card-border); border-radius: 12px; padding: 14px;">
@csrf
<input type="hidden" name="user_id" id="override-form-user-id" value="">
<div style="font-size: 0.75rem; color: var(--text-muted); font-weight: 600; margin-bottom: 10px;">+ Add New Service Override for User</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px;">
<div>
<select name="app_id" required class="form-input" style="width:100%; padding:8px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white; font-size:0.75rem;">
<option value="">-- Select Service App --</option>
@foreach($apps as $app)
<option value="{{ $app->id }}">{{ $app->name }}</option>
@endforeach
</select>
</div>
<div>
<select name="type" required class="form-input" style="width:100%; padding:8px; background:rgba(11,15,25,0.9); border:1px solid var(--card-border); border-radius:8px; color:white; font-size:0.75rem;">
<option value="allow">ALLOW (Grant Service Access)</option>
<option value="deny">DENY (Revoke Service Access)</option>
</select>
</div>
</div>
<div style="display:flex; justify-content:flex-end;">
<button type="submit" class="action-btn" style="background-color:#06b6d4; color:white; border-color:transparent; font-weight:600; padding:6px 14px; border-radius:6px; cursor:pointer; font-size:0.75rem;">Apply Override</button>
</div>
</form>
</div>
<div class="modal-footer" style="margin-top: 20px;">
<button type="button" class="btn-cancel" onclick="closeModal('assign-roles-modal')">Close</button>
</div>
</div>
</div>
<script>
function openModal(modalId) {
if (modalId === 'create-role-modal') {
$('#create-role-apps-select').val([]).trigger('change');
}
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 = "{{ route('admin.roles.update', ':id') }}".replace(':id', id);
document.getElementById('edit-role-name').value = name;
document.getElementById('edit-role-description').value = description || '';
// Set select values and trigger Select2 update
$('#edit-role-apps-select').val(appIds).trigger('change');
openModal('edit-role-modal');
}
const allUserOverridesData = {
@foreach($users as $usr)
{{ $usr->id }}: [
@foreach($usr->overrides as $ov)
{
id: {{ $ov->id }},
app_name: "{{ addslashes($ov->app->name ?? 'App') }}",
type: "{{ $ov->type }}"
},
@endforeach
],
@endforeach
};
function openAssignRolesModal(userId, name, roleIds) {
const form = document.getElementById('assign-roles-form');
form.action = "{{ route('admin.users.roles.update', ':id') }}".replace(':id', userId);
document.getElementById('assign-user-name').innerText = name;
const userIdInput = document.getElementById('override-form-user-id');
if (userIdInput) userIdInput.value = userId;
// Set select values and trigger Select2 update
$('#assign-roles-select').val(roleIds).trigger('change');
// Populate active overrides list for this user inside the modal
const container = document.getElementById('modal-active-overrides-container');
const overrides = allUserOverridesData[userId] || [];
if (overrides.length === 0) {
container.innerHTML = `<div style="font-size:0.75rem; color:var(--text-muted); padding:6px 0; font-style:italic;">No custom service overrides active for this user.</div>`;
} else {
let html = `<div style="display:flex; flex-direction:column; gap:6px;">`;
overrides.forEach(ov => {
const isAllow = ov.type === 'allow';
const bg = isAllow ? 'rgba(16,185,129,0.12)' : 'rgba(239,68,68,0.12)';
const color = isAllow ? '#34d399' : '#fca5a5';
const border = isAllow ? 'rgba(16,185,129,0.3)' : 'rgba(239,68,68,0.3)';
const label = isAllow ? '+ ALLOWED (Granted Access)' : '&minus; REVOKED (Access Denied)';
const destroyUrl = "{{ route('admin.overrides.destroy', ':id') }}".replace(':id', ov.id);
html += `
<div style="display:flex; justify-content:space-between; align-items:center; background:${bg}; border:1px solid ${border}; padding:6px 12px; border-radius:8px; font-size:0.75rem;">
<span style="color:${color}; font-weight:600;">
<strong>${ov.app_name}</strong> &bull; <span style="font-size:0.7rem;">${label}</span>
</span>
<form action="${destroyUrl}" method="POST" onsubmit="return confirm('Remove override for ${ov.app_name}?')" style="margin:0;">
@csrf
@method('DELETE')
<button type="submit" class="action-btn action-btn-delete" style="padding:2px 8px; font-size:0.7rem;" title="Delete Override">
Remove
</button>
</form>
</div>
`;
});
html += `</div>`;
container.innerHTML = html;
}
openModal('assign-roles-modal');
}
function openCreateOverrideModalForUser(userId) {
const usr = {!! json_encode($users->map(fn($u) => ['id' => $u->id, 'name' => $u->name, 'roles' => $u->roles->pluck('id')])) !!}.find(u => u.id === userId);
if (usr) {
openAssignRolesModal(usr.id, usr.name, usr.roles);
}
}
$(document).ready(function() {
$('#assign-roles-select').select2({
placeholder: "Select roles...",
allowClear: true,
width: '100%',
dropdownParent: $('#assign-roles-modal')
});
$('#create-role-apps-select').select2({
placeholder: "Select services access...",
allowClear: true,
width: '100%',
dropdownParent: $('#create-role-modal')
});
$('#edit-role-apps-select').select2({
placeholder: "Select services access...",
allowClear: true,
width: '100%',
dropdownParent: $('#edit-role-modal')
});
// Client-side file format and size validation (JPG, PNG, WEBP, SVG, AVIF, GIF, BMP | 5MB max)
const allowedFormats = ['jpg', 'jpeg', 'png', 'webp', 'svg', 'avif', 'gif', 'bmp', 'ico'];
$('.logo-upload-5mb').on('change', function() {
const file = this.files[0];
const errorDiv = $(this).siblings('.file-size-error');
if (file) {
const ext = file.name.split('.').pop().toLowerCase();
if (!allowedFormats.includes(ext)) {
errorDiv.text('Invalid file format ".' + ext + '". Only PNG, JPG, WEBP, SVG, AVIF, GIF, and BMP image formats are allowed.').show();
this.value = '';
return;
}
if (file.size > 5 * 1024 * 1024) {
errorDiv.text('File size (' + (file.size / (1024*1024)).toFixed(2) + 'MB) exceeds maximum limit of 5MB. Please upload an image under 5MB.').show();
this.value = '';
return;
}
errorDiv.hide();
}
});
// Client-side required field validation
$('form.validated-form').on('submit', function(e) {
let isValid = true;
$(this).find('[required]').each(function() {
const input = $(this);
const val = input.val();
const errorDiv = input.siblings('.field-error-msg');
if (!val || (Array.isArray(val) && val.length === 0)) {
isValid = false;
input.css('border-color', '#ef4444');
if (errorDiv.length) errorDiv.show();
} else {
input.css('border-color', 'var(--card-border)');
if (errorDiv.length) errorDiv.hide();
}
});
if (!isValid) {
e.preventDefault();
}
});
$('form.validated-form [required]').on('input change', function() {
const input = $(this);
const val = input.val();
const errorDiv = input.siblings('.field-error-msg');
if (val && (!Array.isArray(val) || val.length > 0)) {
input.css('border-color', 'rgba(255, 255, 255, 0.18)');
if (errorDiv.length) errorDiv.hide();
}
});
});
function openEditAppModal(id, name, url, tag, iconSvg, color, desc, logoPath) {
const form = document.getElementById('edit-app-form');
form.action = "{{ route('admin.apps.update', ':id') }}".replace(':id', id);
document.getElementById('edit-app-name').value = name;
document.getElementById('edit-app-url').value = url;
document.getElementById('edit-app-color').value = color || '';
document.getElementById('edit-app-tag').value = tag || '';
document.getElementById('edit-app-icon_svg').value = iconSvg || '';
document.getElementById('edit-app-desc').value = desc || '';
const logoStatus = document.getElementById('edit-app-logo-status');
if (logoPath) {
logoStatus.innerText = 'Has custom logo uploaded';
logoStatus.style.color = '#10b981';
} else {
logoStatus.innerText = 'No custom logo uploaded (using preset SVG/initials)';
logoStatus.style.color = '#9ca3af';
}
openModal('edit-app-modal');
}
function confirmAdminToggle(userId, userName, actionType) {
const isGrant = actionType === 'grant';
const title = isGrant ? 'Grant Admin Privilege?' : 'Revoke Admin Privilege?';
const text = isGrant
? `Are you sure you want to grant admin privilege to ${userName}? They will be able to access the admin control panel.`
: `Are you sure you want to revoke admin privilege from ${userName}? They will no longer have administrative access.`;
const confirmButtonText = isGrant ? 'Yes, grant it!' : 'Yes, revoke it!';
const confirmButtonColor = isGrant ? '#f59e0b' : '#ef4444';
Swal.fire({
title: title,
text: text,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: confirmButtonColor,
cancelButtonColor: '#374151',
confirmButtonText: confirmButtonText,
background: '#1f2937',
color: '#f3f4f6'
}).then((result) => {
if (result.isConfirmed) {
const form = document.createElement('form');
form.method = 'POST';
form.action = "{{ route('admin.users.toggle-admin', ':id') }}".replace(':id', userId);
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = '_token';
csrfInput.value = '{{ csrf_token() }}';
form.appendChild(csrfInput);
document.body.appendChild(form);
form.submit();
}
});
}
</script>
<footer style="text-align: center; padding: 40px 0; font-size: 0.75rem; color: var(--text-muted); z-index: 10;">
&copy; 2026 Systems. Authorized Access Only.
</footer>
</body>
</html>