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

1158 lines
59 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;">
<div style="display: inline-flex; gap: 6px; justify-content: flex-end; align-items: center;">
<button class="action-btn" 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) }})" style="color:var(--info-color); border-color:rgba(6,182,212,0.2); padding:6px; display:flex; align-items:center; justify-content:center; border-radius:6px;" 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" style="color:#ef4444; border-color:rgba(239,68,68,0.2); padding:6px; display:flex; align-items:center; justify-content:center; border-radius:6px;" 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" 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')">&times;</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>
<!-- 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">
@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</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>
<div>
<label style="display:block; font-size:0.75rem; color:var(--text-muted); margin-bottom:6px; font-weight:500;">Service URL</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>
</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 - overrides 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 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 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')">&times;</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 = "{{ route('admin.roles.update', ':id') }}".replace(':id', id);
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 = "{{ route('admin.users.roles.update', ':id') }}".replace(':id', userId);
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');
}
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');
}
</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>