implement seeder
This commit is contained in:
parent
0efd443a53
commit
3af09ea069
@ -6,6 +6,7 @@
|
|||||||
use App\Models\App;
|
use App\Models\App;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Models\UserAppOverride;
|
use App\Models\UserAppOverride;
|
||||||
|
use App\Models\Setting;
|
||||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
@ -19,8 +20,8 @@ class DatabaseSeeder extends Seeder
|
|||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// 1. Seed default administrator
|
// 1. Seed default administrator (Preserve if already exists)
|
||||||
$admin = User::updateOrCreate(
|
$admin = User::firstOrCreate(
|
||||||
['email' => 'admin@company.com'],
|
['email' => 'admin@company.com'],
|
||||||
[
|
[
|
||||||
'name' => 'System Administrator',
|
'name' => 'System Administrator',
|
||||||
@ -29,30 +30,105 @@ public function run(): void
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Seed default apps
|
// 2. Seed default roles (Preserve description if modified)
|
||||||
$outlook = App::updateOrCreate(
|
$pmRole = Role::firstOrCreate(
|
||||||
['name' => 'Outlook'],
|
['name' => 'Project Manager'],
|
||||||
|
['description' => 'Oversees projects, budgets, and milestones.']
|
||||||
|
);
|
||||||
|
|
||||||
|
$leadRole = Role::firstOrCreate(
|
||||||
|
['name' => 'Team Lead'],
|
||||||
|
['description' => 'Manages dev teams and assigns work items.']
|
||||||
|
);
|
||||||
|
|
||||||
|
$salesRole = Role::firstOrCreate(
|
||||||
|
['name' => 'Sales Team'],
|
||||||
|
['description' => 'Drives client acquisition and manages CRM leads.']
|
||||||
|
);
|
||||||
|
|
||||||
|
$devRole = Role::firstOrCreate(
|
||||||
|
['name' => 'Developer'],
|
||||||
|
['description' => 'Writes code and builds features.']
|
||||||
|
);
|
||||||
|
|
||||||
|
$internRole = Role::firstOrCreate(
|
||||||
|
['name' => 'Intern'],
|
||||||
|
['description' => 'Learning and assisting on project modules.']
|
||||||
|
);
|
||||||
|
|
||||||
|
$hrRole = Role::firstOrCreate(
|
||||||
|
['name' => 'HR Manager'],
|
||||||
|
['description' => 'Handles recruitment, payroll, and employee relations.']
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Seed default apps/services (Preserve if already exists)
|
||||||
|
$outlookCalendar = App::firstOrCreate(
|
||||||
|
['name' => 'Outlook Calendar'],
|
||||||
[
|
[
|
||||||
'url' => 'https://outlook.office.com/mail/',
|
'url' => 'https://outlook.cloud.microsoft/calendar/view/workweek?deeplink=mail%2F',
|
||||||
'icon_svg' => 'outlook',
|
'icon_svg' => 'outlook',
|
||||||
'color' => '#0078d4',
|
'color' => '#0078d4',
|
||||||
'desc' => 'Access your corporate mail, calendar, and contacts instantly.',
|
'desc' => 'Access your workweek calendar and scheduled meetings.',
|
||||||
'tag' => 'Microsoft 365'
|
'tag' => 'Microsoft 365'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$teams = App::updateOrCreate(
|
$convexCrm = App::firstOrCreate(
|
||||||
['name' => 'Microsoft Teams'],
|
['name' => 'Convex CRM'],
|
||||||
[
|
[
|
||||||
'url' => 'https://teams.microsoft.com/v2/',
|
'url' => 'https://convexsol.convexcrm.com/',
|
||||||
'icon_svg' => 'teams',
|
'icon_svg' => 'keka',
|
||||||
'color' => '#6264a7',
|
'color' => '#4f46e5',
|
||||||
'desc' => 'Chat, meet, call, and collaborate with your team.',
|
'desc' => 'Customer Relationship Management and lead tracking portal.',
|
||||||
'tag' => 'Microsoft 365'
|
'tag' => 'CRM'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$keka = App::updateOrCreate(
|
$copilot = App::firstOrCreate(
|
||||||
|
['name' => 'Microsoft Copilot'],
|
||||||
|
[
|
||||||
|
'url' => 'https://outlook.cloud.microsoft/host/b5abf2ae-c16b-4310-8f8a-d3bcdb52f162/entity1-d870f6cd-4aa5-4d42-9626-ab690c041429',
|
||||||
|
'icon_svg' => 'teams',
|
||||||
|
'color' => '#0078d4',
|
||||||
|
'desc' => 'AI-powered productivity and search assistant.',
|
||||||
|
'tag' => 'AI Assistant'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$demoCrm = App::firstOrCreate(
|
||||||
|
['name' => 'Convex Demo CRM'],
|
||||||
|
[
|
||||||
|
'url' => 'https://demo-convexcrm.convexsol.co/',
|
||||||
|
'icon_svg' => 'keka',
|
||||||
|
'color' => '#06b6d4',
|
||||||
|
'desc' => 'Demonstration environment for Convex CRM features.',
|
||||||
|
'tag' => 'CRM'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$git = App::firstOrCreate(
|
||||||
|
['name' => 'SentientGeeks Git'],
|
||||||
|
[
|
||||||
|
'url' => 'https://git.sentientgeeks.us/',
|
||||||
|
'icon_svg' => 'outlook',
|
||||||
|
'color' => '#f1502f',
|
||||||
|
'desc' => 'Self-hosted repository hosting and code collaboration tool.',
|
||||||
|
'tag' => 'Dev Tools'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$internalCrm = App::firstOrCreate(
|
||||||
|
['name' => 'Internal CRM'],
|
||||||
|
[
|
||||||
|
'url' => 'https://internal.convexcrm.com/',
|
||||||
|
'icon_svg' => 'keka',
|
||||||
|
'color' => '#10b981',
|
||||||
|
'desc' => 'Internal client relations and account directory panel.',
|
||||||
|
'tag' => 'CRM'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$keka = App::firstOrCreate(
|
||||||
['name' => 'Keka HR'],
|
['name' => 'Keka HR'],
|
||||||
[
|
[
|
||||||
'url' => 'https://sentientgeeks.keka.com/',
|
'url' => 'https://sentientgeeks.keka.com/',
|
||||||
@ -63,59 +139,77 @@ public function run(): void
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Seed default roles
|
$teams = App::firstOrCreate(
|
||||||
$pmRole = Role::updateOrCreate(
|
['name' => 'Microsoft Teams'],
|
||||||
['name' => 'Project Manager'],
|
[
|
||||||
['description' => 'Oversees projects, budgets, and milestones.']
|
'url' => 'https://teams.microsoft.com/v2/',
|
||||||
|
'icon_svg' => 'teams',
|
||||||
|
'color' => '#6264a7',
|
||||||
|
'desc' => 'Chat, meet, call, and collaborate with your team.',
|
||||||
|
'tag' => 'Microsoft 365'
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$leadRole = Role::updateOrCreate(
|
$outlookMail = App::firstOrCreate(
|
||||||
['name' => 'Team Lead'],
|
['name' => 'Outlook Mail'],
|
||||||
['description' => 'Manages dev teams and assigns work items.']
|
[
|
||||||
|
'url' => 'https://outlook.cloud.microsoft/host/377c982d-9686-450e-9a7c-22aeaf1bc162/7211f19f-262a-42eb-a02e-289956491741',
|
||||||
|
'icon_svg' => 'outlook',
|
||||||
|
'color' => '#0078d4',
|
||||||
|
'desc' => 'Corporate email and messaging inbox.',
|
||||||
|
'tag' => 'Microsoft 365'
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$salesRole = Role::updateOrCreate(
|
$outlookMailSecondary = App::firstOrCreate(
|
||||||
['name' => 'Sales Team'],
|
['name' => 'Outlook Mail (Secondary)'],
|
||||||
['description' => 'Drives client acquisition and manages CRM leads.']
|
[
|
||||||
|
'url' => 'https://outlook.cloud.microsoft/host/1f8c20f5-d70f-4f8e-93e1-31d8fce0c8c9/096f3341-6ebc-45ac-b97f-e28aecd40b66',
|
||||||
|
'icon_svg' => 'outlook',
|
||||||
|
'color' => '#0369a1',
|
||||||
|
'desc' => 'Secondary business communications gateway.',
|
||||||
|
'tag' => 'Microsoft 365'
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$devRole = Role::updateOrCreate(
|
$todo = App::firstOrCreate(
|
||||||
['name' => 'Developer'],
|
['name' => 'Microsoft To-Do'],
|
||||||
['description' => 'Writes code and builds features.']
|
[
|
||||||
|
'url' => 'https://outlook.cloud.microsoft/host/0d5c91ee-5be2-4b79-81ed-23e6c4580427/ToDoId',
|
||||||
|
'icon_svg' => 'outlook',
|
||||||
|
'color' => '#2563eb',
|
||||||
|
'desc' => 'Organize, track, and complete daily lists and tasks.',
|
||||||
|
'tag' => 'Microsoft 365'
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$internRole = Role::updateOrCreate(
|
// 4. Assign default apps/services to specific roles ONLY IF role was recently created
|
||||||
['name' => 'Intern'],
|
if ($hrRole->wasRecentlyCreated) {
|
||||||
['description' => 'Learning and assisting on project modules.']
|
$hrRole->apps()->sync([$outlookMail->id, $outlookCalendar->id, $teams->id, $keka->id, $todo->id]);
|
||||||
);
|
}
|
||||||
|
|
||||||
$hrRole = Role::updateOrCreate(
|
if ($devRole->wasRecentlyCreated) {
|
||||||
['name' => 'HR Manager'],
|
$devRole->apps()->sync([$outlookMail->id, $outlookCalendar->id, $teams->id, $git->id, $todo->id]);
|
||||||
['description' => 'Handles recruitment, payroll, and employee relations.']
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// 4. Assign apps/services to specific roles
|
if ($leadRole->wasRecentlyCreated) {
|
||||||
// HR has access to Outlook, Teams, and Keka
|
$leadRole->apps()->sync([$outlookMail->id, $outlookCalendar->id, $teams->id, $git->id, $todo->id]);
|
||||||
$hrRole->apps()->sync([$outlook->id, $teams->id, $keka->id]);
|
}
|
||||||
|
|
||||||
// Developers have access to Outlook and Teams
|
if ($pmRole->wasRecentlyCreated) {
|
||||||
$devRole->apps()->sync([$outlook->id, $teams->id]);
|
$pmRole->apps()->sync([$outlookMail->id, $outlookCalendar->id, $teams->id, $todo->id]);
|
||||||
|
}
|
||||||
|
|
||||||
// Team Leads have access to Outlook and Teams
|
if ($salesRole->wasRecentlyCreated) {
|
||||||
$leadRole->apps()->sync([$outlook->id, $teams->id]);
|
$salesRole->apps()->sync([$teams->id, $convexCrm->id, $demoCrm->id]);
|
||||||
|
}
|
||||||
|
|
||||||
// Project Managers have access to Outlook and Teams
|
if ($internRole->wasRecentlyCreated) {
|
||||||
$pmRole->apps()->sync([$outlook->id, $teams->id]);
|
$internRole->apps()->sync([$teams->id]);
|
||||||
|
}
|
||||||
|
|
||||||
// Sales Team has access to Teams
|
// 5. Seed default standard users and assign roles (Preserve if already exists)
|
||||||
$salesRole->apps()->sync([$teams->id]);
|
$user1 = User::firstOrCreate(
|
||||||
|
|
||||||
// Interns have access to Teams (no Outlook, no Keka)
|
|
||||||
$internRole->apps()->sync([$teams->id]);
|
|
||||||
|
|
||||||
// 5. Seed default standard users and assign roles
|
|
||||||
// John Doe (Developer & Team Lead) - Test multiple roles
|
|
||||||
$user1 = User::updateOrCreate(
|
|
||||||
['email' => 'user@company.com'],
|
['email' => 'user@company.com'],
|
||||||
[
|
[
|
||||||
'name' => 'John Doe User',
|
'name' => 'John Doe User',
|
||||||
@ -123,10 +217,11 @@ public function run(): void
|
|||||||
'microsoft_id' => 'mock-microsoft-id-123',
|
'microsoft_id' => 'mock-microsoft-id-123',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$user1->roles()->sync([$devRole->id, $leadRole->id]);
|
if ($user1->wasRecentlyCreated) {
|
||||||
|
$user1->roles()->sync([$devRole->id, $leadRole->id]);
|
||||||
|
}
|
||||||
|
|
||||||
// Jane Smith (HR Manager)
|
$user2 = User::firstOrCreate(
|
||||||
$user2 = User::updateOrCreate(
|
|
||||||
['email' => 'hr@company.com'],
|
['email' => 'hr@company.com'],
|
||||||
[
|
[
|
||||||
'name' => 'Jane Smith HR',
|
'name' => 'Jane Smith HR',
|
||||||
@ -134,24 +229,25 @@ public function run(): void
|
|||||||
'microsoft_id' => 'mock-microsoft-id-456',
|
'microsoft_id' => 'mock-microsoft-id-456',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$user2->roles()->sync([$hrRole->id]);
|
if ($user2->wasRecentlyCreated) {
|
||||||
|
$user2->roles()->sync([$hrRole->id]);
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Seed user-specific overrides for testing
|
// 6. Seed user-specific overrides ONLY IF override is recently created
|
||||||
// John Doe (Developer & Team Lead - has Outlook and Teams via roles)
|
if ($user1->wasRecentlyCreated) {
|
||||||
// Scenario A: Revoke Outlook for John Doe (even though Developer role grants it)
|
UserAppOverride::firstOrCreate(
|
||||||
UserAppOverride::updateOrCreate(
|
['user_id' => $user1->id, 'app_id' => $outlookMail->id],
|
||||||
['user_id' => $user1->id, 'app_id' => $outlook->id],
|
['type' => 'deny']
|
||||||
['type' => 'deny']
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// Scenario B: Add Keka HR as an extra service for John Doe (irrespective of Developer role)
|
UserAppOverride::firstOrCreate(
|
||||||
UserAppOverride::updateOrCreate(
|
['user_id' => $user1->id, 'app_id' => $keka->id],
|
||||||
['user_id' => $user1->id, 'app_id' => $keka->id],
|
['type' => 'allow']
|
||||||
['type' => 'allow']
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
// Seed default role setting
|
// Seed default role setting
|
||||||
\App\Models\Setting::updateOrCreate(
|
Setting::firstOrCreate(
|
||||||
['key' => 'default_role'],
|
['key' => 'default_role'],
|
||||||
['value' => 'Developer']
|
['value' => 'Developer']
|
||||||
);
|
);
|
||||||
|
|||||||
BIN
public/Screenshot_11.png
Normal file
BIN
public/Screenshot_11.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
@ -12,11 +12,18 @@
|
|||||||
<!-- SweetAlert2 -->
|
<!-- SweetAlert2 -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
<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>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg-color: #0b0f19;
|
--bg-color: #0b0f19;
|
||||||
--card-bg: rgba(17, 24, 39, 0.7);
|
--card-bg: rgba(15, 23, 42, 0.45); /* Darker translucent glass base */
|
||||||
--card-border: rgba(255, 255, 255, 0.08);
|
--card-border: rgba(255, 255, 255, 0.18); /* High gloss border */
|
||||||
--accent-primary: #6366f1;
|
--accent-primary: #6366f1;
|
||||||
--accent-secondary: #0078d4;
|
--accent-secondary: #0078d4;
|
||||||
--text-main: #f3f4f6;
|
--text-main: #f3f4f6;
|
||||||
@ -26,6 +33,88 @@
|
|||||||
--info-color: #06b6d4;
|
--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;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -164,20 +253,45 @@
|
|||||||
.stats-grid {
|
.stats-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
gap: 20px;
|
gap: 24px;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
background-color: var(--card-bg);
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.01) 100%), var(--card-bg);
|
||||||
border: 1px solid var(--card-border);
|
border: 1.5px solid var(--card-border); /* Thicker border */
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(25px) saturate(130%);
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
-webkit-backdrop-filter: blur(25px) saturate(130%);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.25), inset 0 1px 2px rgba(255, 255, 255, 0.15), inset 0 -1px 1px rgba(255, 255, 255, 0.05);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
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.28);
|
||||||
|
box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.35), inset 0 1px 2px rgba(255, 255, 255, 0.25), inset 0 -1px 1px rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-card {
|
||||||
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.01) 100%), var(--card-bg);
|
||||||
|
border: 1.5px solid var(--card-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
backdrop-filter: blur(25px) saturate(130%);
|
||||||
|
-webkit-backdrop-filter: blur(25px) saturate(130%);
|
||||||
|
box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.3), inset 0 1px 2px rgba(255, 255, 255, 0.15), inset 0 -1px 1px rgba(255, 255, 255, 0.05);
|
||||||
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-card:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.28);
|
||||||
|
box-shadow: 0 16px 48px 0 rgba(0, 0, 0, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.25), inset 0 -1px 1px rgba(255, 255, 255, 0.08);
|
||||||
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
@ -202,12 +316,53 @@
|
|||||||
|
|
||||||
/* Users Management Card */
|
/* Users Management Card */
|
||||||
.users-card {
|
.users-card {
|
||||||
background-color: var(--card-bg);
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.01) 100%), var(--card-bg);
|
||||||
border: 1px solid var(--card-border);
|
border: 1.5px solid var(--card-border);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
|
backdrop-filter: blur(25px) saturate(130%);
|
||||||
|
-webkit-backdrop-filter: blur(25px) saturate(130%);
|
||||||
|
box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.3), inset 0 1px 2px rgba(255, 255, 255, 0.15), inset 0 -1px 1px rgba(255, 255, 255, 0.05);
|
||||||
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-card:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.28);
|
||||||
|
box-shadow: 0 16px 48px 0 rgba(0, 0, 0, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.25), inset 0 -1px 1px rgba(255, 255, 255, 0.08);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert Styling */
|
||||||
|
.alert {
|
||||||
|
padding: 14px 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(16px);
|
||||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
-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 {
|
.card-header {
|
||||||
@ -579,309 +734,14 @@
|
|||||||
|
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
<div class="alert alert-success" style="margin-bottom: 24px;">
|
<div class="alert alert-success" style="margin-bottom: 24px;">
|
||||||
<span class="alert-icon">✓</span>
|
|
||||||
<div>{{ session('success') }}</div>
|
<div>{{ session('success') }}</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@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>
|
|
||||||
|
|
||||||
<!-- Default Role Configuration Form -->
|
|
||||||
<form action="{{ route('admin.settings.default-role') }}" method="POST" style="margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid rgba(255, 255, 255, 0.1);">
|
|
||||||
@csrf
|
|
||||||
<div style="margin-bottom: 12px;">
|
|
||||||
<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:10px; background:rgba(255,255,255,0.03); border:1px solid var(--card-border); border-radius:8px; color:white; outline: none; font-size: 0.85rem;">
|
|
||||||
@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.85rem; border-radius:8px; padding:10px 16px; border:none; cursor:pointer; font-weight:600; white-space: nowrap;">Save Default</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- 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 -->
|
<!-- Users Table Card -->
|
||||||
<section class="users-card">
|
<section class="users-card" style="margin-bottom: 50px;">
|
||||||
<div class="card-header">
|
<div class="card-header" style="margin-bottom: 20px;">
|
||||||
<div class="card-title">User Accounts Directory</div>
|
<div class="card-title" style="font-family: 'Outfit', sans-serif; font-size: 1.25rem; font-weight: 600; color: #a5b4fc;">User Accounts Directory</div>
|
||||||
<div style="font-size: 0.8rem; color: var(--text-muted);">
|
<div style="font-size: 0.8rem; color: var(--text-muted);">
|
||||||
Showing registered system entities and block controls
|
Showing registered system entities and block controls
|
||||||
</div>
|
</div>
|
||||||
@ -932,6 +792,9 @@
|
|||||||
@if($usr->roles->isNotEmpty())
|
@if($usr->roles->isNotEmpty())
|
||||||
<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:6px;">
|
<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:6px;">
|
||||||
@foreach($usr->roles as $role)
|
@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>
|
<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
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@ -1002,8 +865,342 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- App Management, Roles Management, & User Overrides Grid -->
|
||||||
|
<section class="management-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 30px; margin-bottom: 50px;">
|
||||||
|
|
||||||
|
<!-- 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: 600; color: #a5b4fc; 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: 350px; 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">
|
||||||
|
<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: 600; color: #a5b4fc; 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: 280px; 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">
|
||||||
|
<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: 600; color: #a5b4fc; margin: 0;">User Service Overrides</h3>
|
||||||
|
<button onclick="openModal('create-override-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 Override</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Overrides List -->
|
||||||
|
<div class="table-responsive" style="max-height: 350px; 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>
|
||||||
|
|
||||||
</main>
|
</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')">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="{{ route('admin.apps.store') }}" 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" 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: 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)</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: 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')">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="{{ route('admin.roles.store') }}" 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" 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: 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>
|
||||||
|
<div class="checkbox-grid" style="background:rgba(0,0,0,0.2); border:1px solid var(--card-border); border-radius:8px; padding:12px; max-height:150px; overflow-y:auto; display:flex; flex-direction:column; gap:8px;">
|
||||||
|
@foreach($apps as $app)
|
||||||
|
<label class="checkbox-item">
|
||||||
|
<input type="checkbox" name="apps[]" value="{{ $app->id }}">
|
||||||
|
{{ $app->name }}
|
||||||
|
</label>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</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')">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="{{ route('admin.overrides.store') }}" 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 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: 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</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>
|
||||||
|
<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 -->
|
<!-- Edit Role Modal -->
|
||||||
<div id="edit-role-modal" class="admin-modal">
|
<div id="edit-role-modal" class="admin-modal">
|
||||||
<div class="admin-modal-content">
|
<div class="admin-modal-content">
|
||||||
@ -1101,22 +1298,14 @@
|
|||||||
@csrf
|
@csrf
|
||||||
<div style="margin-bottom: 16px;">
|
<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>
|
<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;">
|
<select name="roles[]" id="assign-roles-select" multiple="multiple" class="form-input select2-multiple" style="width: 100%;">
|
||||||
@foreach($roles as $role)
|
@foreach($roles as $role)
|
||||||
@if(strtolower($role->name) === 'admin')
|
@if(strtolower($role->name) === 'admin')
|
||||||
@continue
|
@continue
|
||||||
@endif
|
@endif
|
||||||
<label class="checkbox-item">
|
<option value="{{ $role->id }}">{{ $role->name }}</option>
|
||||||
<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
|
@endforeach
|
||||||
</div>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn-cancel" onclick="closeModal('assign-roles-modal')">Cancel</button>
|
<button type="button" class="btn-cancel" onclick="closeModal('assign-roles-modal')">Cancel</button>
|
||||||
@ -1156,14 +1345,21 @@ function openAssignRolesModal(userId, name, roleIds) {
|
|||||||
|
|
||||||
document.getElementById('assign-user-name').innerText = name;
|
document.getElementById('assign-user-name').innerText = name;
|
||||||
|
|
||||||
// Reset and check user's roles
|
// Set select values and trigger Select2 update
|
||||||
document.querySelectorAll('.assign-user-role-checkbox').forEach(cb => {
|
$('#assign-roles-select').val(roleIds).trigger('change');
|
||||||
cb.checked = roleIds.includes(parseInt(cb.value));
|
|
||||||
});
|
|
||||||
|
|
||||||
openModal('assign-roles-modal');
|
openModal('assign-roles-modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#assign-roles-select').select2({
|
||||||
|
placeholder: "Select roles...",
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
dropdownParent: $('#assign-roles-modal')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function openEditAppModal(id, name, url, tag, iconSvg, color, desc, logoPath) {
|
function openEditAppModal(id, name, url, tag, iconSvg, color, desc, logoPath) {
|
||||||
const form = document.getElementById('edit-app-form');
|
const form = document.getElementById('edit-app-form');
|
||||||
form.action = "{{ route('admin.apps.update', ':id') }}".replace(':id', id);
|
form.action = "{{ route('admin.apps.update', ':id') }}".replace(':id', id);
|
||||||
|
|||||||
@ -9,17 +9,63 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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">
|
<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">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Apply theme immediately before rendering to prevent flashing
|
||||||
|
(function () {
|
||||||
|
const savedTheme = localStorage.getItem('theme') || 'dark';
|
||||||
|
if (savedTheme === 'light') {
|
||||||
|
document.documentElement.classList.add('light-mode');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('light-mode');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg-color: #0b0f19;
|
--bg-color: #0b0f19;
|
||||||
--card-bg: rgba(17, 24, 39, 0.7);
|
--card-bg: rgba(17, 24, 39, 0.45); /* Darker translucent glass base */
|
||||||
--card-border: rgba(255, 255, 255, 0.08);
|
--card-border: rgba(255, 255, 255, 0.18); /* High gloss border */
|
||||||
--accent-primary: #4f46e5;
|
--accent-primary: #4f46e5;
|
||||||
--accent-secondary: #0078d4;
|
--accent-secondary: #0078d4;
|
||||||
--text-main: #f3f4f6;
|
--text-main: #f3f4f6;
|
||||||
--text-muted: #9ca3af;
|
--text-muted: #9ca3af;
|
||||||
--success-color: #10b981;
|
--success-color: #10b981;
|
||||||
--info-color: #06b6d4;
|
--info-color: #06b6d4;
|
||||||
|
|
||||||
|
--bg-gradient: radial-gradient(circle at 85% 15%, rgba(99, 102, 241, 0.15) 0%, transparent 45%),
|
||||||
|
radial-gradient(circle at 15% 85%, rgba(6, 182, 212, 0.12) 0%, transparent 45%),
|
||||||
|
radial-gradient(circle at 50% 50%, rgba(99, 102, 241, 0.07) 0%, transparent 50%);
|
||||||
|
--navbar-bg: rgba(10, 15, 26, 0.45);
|
||||||
|
--modal-bg: rgba(25, 30, 45, 0.65);
|
||||||
|
--modal-overlay: rgba(11, 15, 25, 0.85);
|
||||||
|
--logo-bg: rgba(15, 23, 42, 0.55);
|
||||||
|
--card-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||||
|
--input-bg: rgba(255, 255, 255, 0.03);
|
||||||
|
--theme-toggle-hover: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.light-mode {
|
||||||
|
--bg-color: #f3f4f6;
|
||||||
|
--card-bg: rgba(255, 255, 255, 0.75);
|
||||||
|
--card-border: rgba(0, 0, 0, 0.08);
|
||||||
|
--accent-primary: #4f46e5;
|
||||||
|
--accent-secondary: #0284c7;
|
||||||
|
--text-main: #1f2937;
|
||||||
|
--text-muted: #4b5563;
|
||||||
|
--success-color: #059669;
|
||||||
|
--info-color: #0891b2;
|
||||||
|
|
||||||
|
--bg-gradient: radial-gradient(circle at 85% 15%, rgba(99, 102, 241, 0.08) 0%, transparent 45%),
|
||||||
|
radial-gradient(circle at 15% 85%, rgba(6, 182, 212, 0.06) 0%, transparent 45%),
|
||||||
|
radial-gradient(circle at 50% 50%, rgba(99, 102, 241, 0.04) 0%, transparent 50%);
|
||||||
|
--navbar-bg: rgba(255, 255, 255, 0.75);
|
||||||
|
--modal-bg: rgba(255, 255, 255, 0.95);
|
||||||
|
--modal-overlay: rgba(243, 244, 246, 0.8);
|
||||||
|
--logo-bg: rgba(255, 255, 255, 0.9);
|
||||||
|
--card-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
--input-bg: rgba(0, 0, 0, 0.03);
|
||||||
|
--theme-toggle-hover: rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.personal-app-card:hover .personal-app-actions {
|
.personal-app-card:hover .personal-app-actions {
|
||||||
@ -34,7 +80,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(11, 15, 25, 0.85);
|
background-color: var(--modal-overlay);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
-webkit-backdrop-filter: blur(20px);
|
-webkit-backdrop-filter: blur(20px);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -52,17 +98,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.personal-modal-content {
|
.personal-modal-content {
|
||||||
background-color: rgba(25, 30, 45, 0.9);
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.01) 100%), var(--modal-bg);
|
||||||
border: 1px solid var(--card-border);
|
border: 1.5px solid var(--card-border);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.15), inset 0 -1px 1px rgba(255, 255, 255, 0.05);
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), background-color 0.3s ease, border-color 0.3s ease;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
backdrop-filter: blur(25px) saturate(130%);
|
||||||
|
-webkit-backdrop-filter: blur(25px) saturate(130%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.personal-modal.active .personal-modal-content {
|
.personal-modal.active .personal-modal-content {
|
||||||
@ -115,10 +163,10 @@
|
|||||||
.form-input {
|
.form-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
background: rgba(255, 255, 255, 0.03);
|
background: var(--input-bg);
|
||||||
border: 1px solid var(--card-border);
|
border: 1px solid var(--card-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: white;
|
color: var(--text-main);
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
@ -126,7 +174,8 @@
|
|||||||
|
|
||||||
.form-input:focus {
|
.form-input:focus {
|
||||||
border-color: var(--accent-primary);
|
border-color: var(--accent-primary);
|
||||||
background: rgba(255, 255, 255, 0.06);
|
background: var(--input-bg);
|
||||||
|
filter: brightness(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
@ -163,10 +212,7 @@
|
|||||||
body {
|
body {
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
background-image:
|
background-image: var(--bg-gradient);
|
||||||
radial-gradient(circle at 85% 15%, rgba(99, 102, 241, 0.15) 0%, transparent 45%),
|
|
||||||
radial-gradient(circle at 15% 85%, rgba(6, 182, 212, 0.12) 0%, transparent 45%),
|
|
||||||
radial-gradient(circle at 50% 50%, rgba(99, 102, 241, 0.07) 0%, transparent 50%);
|
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@ -174,6 +220,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Top Profile-Integrated Navigation Bar */
|
/* Top Profile-Integrated Navigation Bar */
|
||||||
@ -182,14 +229,22 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 12px 5%;
|
padding: 12px 5%;
|
||||||
background: rgba(10, 15, 26, 0.35); /* Translucent glass dark */
|
background: var(--navbar-bg); /* Translucent glass */
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05); /* Very fine glass border */
|
border-bottom: 1px solid var(--card-border); /* Very fine glass border */
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
-webkit-backdrop-filter: blur(20px);
|
-webkit-backdrop-filter: blur(20px);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.25);
|
||||||
|
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme toggle button specific styling */
|
||||||
|
.theme-toggle-btn:hover {
|
||||||
|
background: var(--theme-toggle-hover) !important;
|
||||||
|
transform: scale(1.05);
|
||||||
|
border-color: var(--accent-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-profile {
|
.nav-profile {
|
||||||
@ -230,8 +285,9 @@
|
|||||||
font-family: 'Outfit', sans-serif;
|
font-family: 'Outfit', sans-serif;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: white;
|
color: var(--text-main);
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
|
transition: color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-profile-meta {
|
.nav-profile-meta {
|
||||||
@ -244,7 +300,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.meta-dot {
|
.meta-dot {
|
||||||
color: rgba(255, 255, 255, 0.15);
|
color: var(--card-border);
|
||||||
|
transition: color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-badge {
|
.nav-badge {
|
||||||
@ -322,7 +379,6 @@
|
|||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 32px 5%;
|
padding: 32px 5%;
|
||||||
max-width: 960px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
@ -331,7 +387,6 @@
|
|||||||
/* Services Section - Centered & Grid Layout */
|
/* Services Section - Centered & Grid Layout */
|
||||||
.services-container {
|
.services-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 960px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
margin-top: 10px; /* Reduced space between profile card and services */
|
margin-top: 10px; /* Reduced space between profile card and services */
|
||||||
}
|
}
|
||||||
@ -382,15 +437,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: rgba(15, 23, 42, 0.45); /* Dark semitransparent background */
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.01) 100%), var(--logo-bg);
|
||||||
border: 1.5px solid rgba(255, 255, 255, 0.08); /* Distinct visible border */
|
border: 1.5px solid var(--card-border); /* Distinct visible border */
|
||||||
backdrop-filter: blur(12px); /* Frosted glass effect */
|
backdrop-filter: blur(20px) saturate(130%); /* Frosted glass effect */
|
||||||
-webkit-backdrop-filter: blur(12px);
|
-webkit-backdrop-filter: blur(20px) saturate(130%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 4px 12px rgba(0, 0, 0, 0.25),
|
var(--card-shadow),
|
||||||
inset 0 1px 1px rgba(255, 255, 255, 0.05);
|
inset 0 1px 2px rgba(255, 255, 255, 0.15);
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1), background-color 0.3s ease, border-color 0.3s ease;
|
||||||
color: var(--service-color);
|
color: var(--service-color);
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -405,12 +460,13 @@
|
|||||||
|
|
||||||
.service-card-glass:hover .service-logo-wrapper {
|
.service-card-glass:hover .service-logo-wrapper {
|
||||||
transform: translateY(-4px); /* Moves only the box slightly up */
|
transform: translateY(-4px); /* Moves only the box slightly up */
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.04) 100%), var(--logo-bg);
|
||||||
border-color: var(--service-color);
|
border-color: var(--service-color);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 8px 24px rgba(0, 0, 0, 0.3),
|
0 12px 30px rgba(0, 0, 0, 0.35),
|
||||||
0 0 20px color-mix(in srgb, var(--service-color) 45%, transparent);
|
0 0 20px color-mix(in srgb, var(--service-color) 45%, transparent),
|
||||||
|
inset 0 1px 2px rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-card-glass:hover .service-logo-wrapper svg {
|
.service-card-glass:hover .service-logo-wrapper svg {
|
||||||
@ -440,7 +496,7 @@
|
|||||||
font-family: 'Outfit', sans-serif;
|
font-family: 'Outfit', sans-serif;
|
||||||
font-size: 0.72rem; /* Clean text scale */
|
font-size: 0.72rem; /* Clean text scale */
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: rgba(255, 255, 255, 0.65); /* Faint gray */
|
color: var(--text-muted); /* Adaptive color */
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
@ -450,7 +506,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.service-card-glass:hover .service-title {
|
.service-card-glass:hover .service-title {
|
||||||
color: #ffffff;
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-subtitle {
|
.service-subtitle {
|
||||||
@ -585,7 +641,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(11, 15, 25, 0.85);
|
background-color: var(--modal-overlay);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
-webkit-backdrop-filter: blur(20px);
|
-webkit-backdrop-filter: blur(20px);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -603,16 +659,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sso-modal-card {
|
.sso-modal-card {
|
||||||
background-color: rgba(25, 30, 45, 0.7);
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.01) 100%), var(--modal-bg);
|
||||||
border: 1px solid var(--card-border);
|
border: 1.5px solid var(--card-border);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.15), inset 0 -1px 1px rgba(255, 255, 255, 0.05);
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
transition: transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
transition: transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275), background-color 0.3s ease, border-color 0.3s ease;
|
||||||
|
backdrop-filter: blur(25px) saturate(130%);
|
||||||
|
-webkit-backdrop-filter: blur(25px) saturate(130%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sso-modal.active .sso-modal-card {
|
.sso-modal.active .sso-modal-card {
|
||||||
@ -738,10 +796,6 @@
|
|||||||
{{ \App\Models\Setting::get('default_role', 'Developer') }}
|
{{ \App\Models\Setting::get('default_role', 'Developer') }}
|
||||||
@endif
|
@endif
|
||||||
</span>
|
</span>
|
||||||
@if($user->microsoft_id)
|
|
||||||
<span class="meta-dot">•</span>
|
|
||||||
<span class="nav-badge sso">Microsoft Linked</span>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -749,6 +803,26 @@
|
|||||||
|
|
||||||
<!-- Right: Actions -->
|
<!-- Right: Actions -->
|
||||||
<div class="nav-actions">
|
<div class="nav-actions">
|
||||||
|
<!-- Theme Toggle Button -->
|
||||||
|
<button id="theme-toggle" class="theme-toggle-btn" title="Toggle Theme" aria-label="Toggle Theme" style="background: var(--input-bg); border: 1.5px solid var(--card-border); color: var(--text-main); width: 38px; height: 38px; border-radius: 8px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.25s ease;">
|
||||||
|
<!-- Moon Icon (Dark Mode) -->
|
||||||
|
<svg class="theme-icon-dark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 18px; height: 18px; display: block; color: #f59e0b;">
|
||||||
|
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||||
|
</svg>
|
||||||
|
<!-- Sun Icon (Light Mode) -->
|
||||||
|
<svg class="theme-icon-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 18px; height: 18px; display: none; color: #eab308;">
|
||||||
|
<circle cx="12" cy="12" r="5"></circle>
|
||||||
|
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||||
|
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||||
|
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||||
|
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||||
|
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||||
|
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||||
|
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||||
|
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
@if($user->role === 'admin')
|
@if($user->role === 'admin')
|
||||||
<a href="{{ route('admin.dashboard') }}" class="nav-action-btn admin" title="Admin Panel">
|
<a href="{{ route('admin.dashboard') }}" class="nav-action-btn admin" title="Admin Panel">
|
||||||
Go to Admin Dashboard
|
Go to Admin Dashboard
|
||||||
@ -1040,6 +1114,33 @@ function openEditPersonalAppModal(id, name, url, color, tag, desc, logo) {
|
|||||||
function closePersonalModal(modalId) {
|
function closePersonalModal(modalId) {
|
||||||
document.getElementById(modalId).classList.remove('active');
|
document.getElementById(modalId).classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Theme Switcher Logic
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const themeToggleBtn = document.getElementById('theme-toggle');
|
||||||
|
const sunIcon = themeToggleBtn.querySelector('.theme-icon-light');
|
||||||
|
const moonIcon = themeToggleBtn.querySelector('.theme-icon-dark');
|
||||||
|
|
||||||
|
function updateIcons() {
|
||||||
|
const isLight = document.documentElement.classList.contains('light-mode');
|
||||||
|
if (isLight) {
|
||||||
|
sunIcon.style.display = 'block';
|
||||||
|
moonIcon.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
sunIcon.style.display = 'none';
|
||||||
|
moonIcon.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateIcons();
|
||||||
|
|
||||||
|
themeToggleBtn.addEventListener('click', () => {
|
||||||
|
document.documentElement.classList.toggle('light-mode');
|
||||||
|
const newTheme = document.documentElement.classList.contains('light-mode') ? 'light' : 'dark';
|
||||||
|
localStorage.setItem('theme', newTheme);
|
||||||
|
updateIcons();
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user