| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Camera Access Permissions</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .permission-card { |
| transition: all 0.3s ease; |
| } |
| .permission-card:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05); |
| } |
| .tab-content { |
| display: none; |
| } |
| .tab-content.active { |
| display: block; |
| animation: fadeIn 0.3s ease; |
| } |
| @keyframes fadeIn { |
| from { opacity: 0; } |
| to { opacity: 1; } |
| } |
| .tree-node { |
| position: relative; |
| padding-left: 20px; |
| } |
| .tree-node:before { |
| content: ""; |
| position: absolute; |
| left: 0; |
| top: 10px; |
| width: 15px; |
| height: 2px; |
| background-color: #6b7280; |
| } |
| .tree-node:after { |
| content: ""; |
| position: absolute; |
| left: 0; |
| top: 0; |
| bottom: 0; |
| width: 2px; |
| background-color: #6b7280; |
| } |
| .tree-node:last-child:after { |
| height: 10px; |
| } |
| .time-slot { |
| transition: all 0.2s ease; |
| } |
| .time-slot:hover { |
| background-color: #e5e7eb; |
| } |
| .time-slot.active { |
| background-color: #3b82f6; |
| color: white; |
| } |
| .ip-chip { |
| transition: all 0.2s ease; |
| } |
| .ip-chip:hover { |
| transform: scale(1.02); |
| } |
| .group-header { |
| cursor: pointer; |
| transition: background-color 0.2s; |
| } |
| .group-header:hover { |
| background-color: #f3f4f6; |
| } |
| .camera-item { |
| transition: all 0.2s; |
| } |
| .camera-item:hover { |
| background-color: #f9fafb; |
| } |
| .draggable { |
| cursor: move; |
| } |
| .draggable.dragging { |
| opacity: 0.5; |
| } |
| .dropzone { |
| min-height: 20px; |
| transition: background-color 0.2s; |
| } |
| .dropzone.active { |
| background-color: #e0f2fe; |
| } |
| .conflict-warning { |
| animation: pulse 2s infinite; |
| } |
| @keyframes pulse { |
| 0% { box-shadow: 0 0 0 0 rgba(234, 88, 12, 0.4); } |
| 70% { box-shadow: 0 0 0 10px rgba(234, 88, 12, 0); } |
| 100% { box-shadow: 0 0 0 0 rgba(234, 88, 12, 0); } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50"> |
| <div class="container mx-auto px-4 py-8"> |
| |
| <div class="flex justify-between items-center mb-8"> |
| <div> |
| <h1 class="text-3xl font-bold text-gray-800">Camera Access Permissions</h1> |
| <p class="text-gray-600">Configure and manage camera access for users and groups</p> |
| </div> |
| <div class="flex space-x-2"> |
| <button id="saveBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-save mr-2"></i> Save Changes |
| </button> |
| <button id="bulkBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-users mr-2"></i> Bulk Assign |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white rounded-xl shadow-md p-6 mb-6"> |
| <h2 class="text-xl font-semibold mb-4 text-gray-800">Select User/Group</h2> |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Search User</label> |
| <div class="relative"> |
| <input type="text" id="userSearch" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Search by name or email"> |
| <div class="absolute right-3 top-2.5 text-gray-400"> |
| <i class="fas fa-search"></i> |
| </div> |
| </div> |
| <div id="userResults" class="mt-2 border border-gray-200 rounded-lg max-h-60 overflow-y-auto hidden"> |
| |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Search Group</label> |
| <div class="relative"> |
| <input type="text" id="groupSearch" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Search group name"> |
| <div class="absolute right-3 top-2.5 text-gray-400"> |
| <i class="fas fa-search"></i> |
| </div> |
| </div> |
| <div id="groupResults" class="mt-2 border border-gray-200 rounded-lg max-h-60 overflow-y-auto hidden"> |
| |
| </div> |
| </div> |
| </div> |
| <div id="selectedEntities" class="mt-4 flex flex-wrap gap-2 hidden"> |
| |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white rounded-xl shadow-md overflow-hidden"> |
| <div class="p-6"> |
| <h3 class="text-lg font-medium text-gray-800 mb-4">Camera Access Overview</h3> |
| <p class="text-gray-600 mb-4">Configure which cameras or camera groups the user/group can access and their permission levels.</p> |
| |
| |
| <div class="bg-gray-50 p-4 rounded-lg mb-6"> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Search Cameras</label> |
| <div class="relative"> |
| <input type="text" id="cameraSearch" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Search by camera name"> |
| <div class="absolute right-3 top-2.5 text-gray-400"> |
| <i class="fas fa-search"></i> |
| </div> |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Filter by Status</label> |
| <select class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
| <option>All Cameras</option> |
| <option>Assigned Only</option> |
| <option>Unassigned Only</option> |
| <option>With Conflicts</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Quick Actions</label> |
| <div class="flex space-x-2"> |
| <button class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-3 py-2 rounded-lg text-sm flex items-center"> |
| <i class="fas fa-expand mr-1"></i> Expand All |
| </button> |
| <button class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-3 py-2 rounded-lg text-sm flex items-center"> |
| <i class="fas fa-compress mr-1"></i> Collapse All |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| |
| <div class="lg:col-span-2"> |
| <div class="bg-white border border-gray-200 rounded-lg shadow-sm"> |
| <div class="p-4 border-b border-gray-200"> |
| <h4 class="font-medium text-gray-800 flex items-center"> |
| <i class="fas fa-sitemap text-blue-600 mr-2"></i> |
| Camera Groups Hierarchy |
| </h4> |
| </div> |
| |
| <div id="cameraGroups" class="p-4 space-y-2"> |
| |
| <div class="group-container border border-gray-200 rounded-lg overflow-hidden"> |
| <div class="group-header bg-gray-50 p-3 flex items-center justify-between" data-group="main-building"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 group-checkbox" data-group="main-building"> |
| <div class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-building text-blue-600"></i> |
| </div> |
| <span class="font-medium">Main Building</span> |
| <span class="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full">12 cameras</span> |
| </div> |
| <button class="text-gray-500 hover:text-gray-700 group-toggle" data-group="main-building"> |
| <i class="fas fa-chevron-down"></i> |
| </button> |
| </div> |
| |
| <div class="group-content p-2 space-y-2" data-group="main-building" style="display: block;"> |
| |
| <div class="group-container border border-gray-200 rounded-lg overflow-hidden ml-4"> |
| <div class="group-header bg-gray-50 p-3 flex items-center justify-between" data-group="floor-1"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 group-checkbox" data-group="floor-1"> |
| <div class="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-layer-group text-purple-600"></i> |
| </div> |
| <span class="font-medium">Floor 1</span> |
| <span class="ml-2 text-xs bg-purple-100 text-purple-800 px-2 py-0.5 rounded-full">5 cameras</span> |
| </div> |
| <button class="text-gray-500 hover:text-gray-700 group-toggle" data-group="floor-1"> |
| <i class="fas fa-chevron-down"></i> |
| </button> |
| </div> |
| |
| <div class="group-content p-2 space-y-2" data-group="floor-1" style="display: block;"> |
| |
| <div class="camera-item bg-white p-2 rounded border border-gray-200 flex items-center justify-between draggable" draggable="true" data-camera="lobby-cam"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 camera-checkbox" data-camera="lobby-cam"> |
| <div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-video text-green-600"></i> |
| </div> |
| <span>Lobby Camera</span> |
| </div> |
| <div class="flex items-center space-x-2"> |
| <span class="text-xs bg-yellow-100 text-yellow-800 px-2 py-0.5 rounded-full">Conflict</span> |
| <button class="text-gray-400 hover:text-gray-600"> |
| <i class="fas fa-ellipsis-v"></i> |
| </button> |
| </div> |
| </div> |
| |
| <div class="camera-item bg-white p-2 rounded border border-gray-200 flex items-center justify-between draggable" draggable="true" data-camera="elevator-cam"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 camera-checkbox" data-camera="elevator-cam"> |
| <div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-video text-green-600"></i> |
| </div> |
| <span>Elevator Camera</span> |
| </div> |
| <button class="text-gray-400 hover:text-gray-600"> |
| <i class="fas fa-ellipsis-v"></i> |
| </button> |
| </div> |
| |
| <div class="camera-item bg-white p-2 rounded border border-gray-200 flex items-center justify-between draggable" draggable="true" data-camera="hallway-cam"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 camera-checkbox" data-camera="hallway-cam"> |
| <div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-video text-green-600"></i> |
| </div> |
| <span>Hallway Camera</span> |
| </div> |
| <button class="text-gray-400 hover:text-gray-600"> |
| <i class="fas fa-ellipsis-v"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="group-container border border-gray-200 rounded-lg overflow-hidden ml-4"> |
| <div class="group-header bg-gray-50 p-3 flex items-center justify-between" data-group="floor-2"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 group-checkbox" data-group="floor-2"> |
| <div class="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-layer-group text-purple-600"></i> |
| </div> |
| <span class="font-medium">Floor 2</span> |
| <span class="ml-2 text-xs bg-purple-100 text-purple-800 px-2 py-0.5 rounded-full">4 cameras</span> |
| </div> |
| <button class="text-gray-500 hover:text-gray-700 group-toggle" data-group="floor-2"> |
| <i class="fas fa-chevron-down"></i> |
| </button> |
| </div> |
| |
| <div class="group-content p-2 space-y-2" data-group="floor-2" style="display: none;"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="group-container border border-gray-200 rounded-lg overflow-hidden"> |
| <div class="group-header bg-gray-50 p-3 flex items-center justify-between" data-group="parking-lot"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 group-checkbox" data-group="parking-lot"> |
| <div class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-parking text-blue-600"></i> |
| </div> |
| <span class="font-medium">Parking Lot</span> |
| <span class="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full">3 cameras</span> |
| </div> |
| <button class="text-gray-500 hover:text-gray-700 group-toggle" data-group="parking-lot"> |
| <i class="fas fa-chevron-down"></i> |
| </button> |
| </div> |
| |
| <div class="group-content p-2 space-y-2" data-group="parking-lot" style="display: none;"> |
| |
| </div> |
| </div> |
| |
| |
| <div class="group-container border border-gray-200 rounded-lg overflow-hidden"> |
| <div class="group-header bg-gray-50 p-3 flex items-center justify-between" data-group="warehouse"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 group-checkbox" data-group="warehouse"> |
| <div class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-warehouse text-blue-600"></i> |
| </div> |
| <span class="font-medium">Warehouse</span> |
| <span class="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full">6 cameras</span> |
| </div> |
| <button class="text-gray-500 hover:text-gray-700 group-toggle" data-group="warehouse"> |
| <i class="fas fa-chevron-down"></i> |
| </button> |
| </div> |
| |
| <div class="group-content p-2 space-y-2" data-group="warehouse" style="display: none;"> |
| |
| </div> |
| </div> |
| |
| |
| <div class="border border-gray-200 rounded-lg overflow-hidden"> |
| <div class="bg-gray-50 p-3 flex items-center justify-between"> |
| <div class="flex items-center"> |
| <div class="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-question text-gray-600"></i> |
| </div> |
| <span class="font-medium">Unassigned Cameras</span> |
| <span class="ml-2 text-xs bg-gray-100 text-gray-800 px-2 py-0.5 rounded-full">2 cameras</span> |
| </div> |
| </div> |
| |
| <div class="p-2 space-y-2"> |
| <div class="camera-item bg-white p-2 rounded border border-gray-200 flex items-center justify-between draggable" draggable="true" data-camera="gate-cam"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 camera-checkbox" data-camera="gate-cam"> |
| <div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-video text-green-600"></i> |
| </div> |
| <span>Gate Camera</span> |
| </div> |
| <button class="text-gray-400 hover:text-gray-600"> |
| <i class="fas fa-ellipsis-v"></i> |
| </button> |
| </div> |
| |
| <div class="camera-item bg-white p-2 rounded border border-gray-200 flex items-center justify-between draggable" draggable="true" data-camera="backup-cam"> |
| <div class="flex items-center"> |
| <input type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2 camera-checkbox" data-camera="backup-cam"> |
| <div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-video text-green-600"></i> |
| </div> |
| <span>Backup Camera</span> |
| </div> |
| <button class="text-gray-400 hover:text-gray-600"> |
| <i class="fas fa-ellipsis-v"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="lg:col-span-1"> |
| <div class="bg-white border border-gray-200 rounded-lg shadow-sm sticky top-4"> |
| <div class="p-4 border-b border-gray-200"> |
| <h4 class="font-medium text-gray-800 flex items-center"> |
| <i class="fas fa-key text-blue-600 mr-2"></i> |
| Permission Configuration |
| </h4> |
| </div> |
| |
| <div class="p-4 space-y-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-2">Selected Items</label> |
| <div id="selectedItems" class="bg-gray-50 p-3 rounded-lg min-h-20 max-h-40 overflow-y-auto dropzone"> |
| <p class="text-sm text-gray-500 italic">No items selected</p> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-2">Permission Level</label> |
| <select class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
| <option>No Access</option> |
| <option selected>View Only</option> |
| <option>View + Playback</option> |
| <option>Full Access</option> |
| <option>Custom...</option> |
| </select> |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-2">Custom Permissions</label> |
| <div class="space-y-2"> |
| <div class="flex items-center"> |
| <input id="live-view" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" checked> |
| <label for="live-view" class="ml-2 block text-sm text-gray-700">Live View</label> |
| </div> |
| <div class="flex items-center"> |
| <input id="playback" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" checked> |
| <label for="playback" class="ml-2 block text-sm text-gray-700">Playback Recordings</label> |
| </div> |
| <div class="flex items-center"> |
| <input id="export" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"> |
| <label for="export" class="ml-2 block text-sm text-gray-700">Export Recordings</label> |
| </div> |
| <div class="flex items-center"> |
| <input id="ptz-control" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"> |
| <label for="ptz-control" class="ml-2 block text-sm text-gray-700">PTZ Control</label> |
| </div> |
| <div class="flex items-center"> |
| <input id="configuration" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"> |
| <label for="configuration" class="ml-2 block text-sm text-gray-700">Configuration</label> |
| </div> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-2">Inheritance Settings</label> |
| <div class="flex items-center"> |
| <input id="inherit-permissions" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" checked> |
| <label for="inherit-permissions" class="ml-2 block text-sm text-gray-700">Inherit permissions from parent groups</label> |
| </div> |
| <p class="mt-1 text-xs text-gray-500">When enabled, permissions from parent groups will override individual camera settings</p> |
| </div> |
| |
| <div class="pt-2"> |
| <button class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg"> |
| Apply Permissions |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="conflictModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center hidden z-50"> |
| <div class="bg-white rounded-lg shadow-xl max-w-2xl w-full p-6"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-lg font-medium text-gray-800 flex items-center"> |
| <i class="fas fa-exclamation-triangle text-yellow-500 mr-2"></i> |
| Permission Conflicts Detected |
| </h3> |
| <button id="closeConflictModal" class="text-gray-400 hover:text-gray-500"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="space-y-4"> |
| <p class="text-gray-600"> |
| The following cameras have conflicting permissions from multiple groups. Please resolve the conflicts before proceeding. |
| </p> |
| |
| <div class="border border-gray-200 rounded-lg overflow-hidden"> |
| <table class="min-w-full divide-y divide-gray-200"> |
| <thead class="bg-gray-50"> |
| <tr> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Camera</th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Current Group</th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Conflicting Group</th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Action</th> |
| </tr> |
| </thead> |
| <tbody class="bg-white divide-y divide-gray-200"> |
| <tr> |
| <td class="px-6 py-4 whitespace-nowrap"> |
| <div class="flex items-center"> |
| <div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-video text-green-600"></i> |
| </div> |
| <span>Lobby Camera</span> |
| </div> |
| </td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
| <div class="flex items-center"> |
| <div class="w-6 h-6 rounded-full bg-purple-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-layer-group text-purple-600 text-xs"></i> |
| </div> |
| Floor 1 |
| </div> |
| </td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
| <div class="flex items-center"> |
| <div class="w-6 h-6 rounded-full bg-blue-100 flex items-center justify-center mr-2"> |
| <i class="fas fa-building text-blue-600 text-xs"></i> |
| </div> |
| Security Group |
| </div> |
| </td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> |
| <select class="text-xs border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500"> |
| <option>Keep current group</option> |
| <option>Use conflicting group</option> |
| <option>Custom settings...</option> |
| </select> |
| </td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| |
| <div class="flex justify-end space-x-3 pt-4"> |
| <button id="cancelConflict" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg text-sm"> |
| Cancel |
| </button> |
| <button id="resolveConflict" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm"> |
| Resolve Conflicts |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="successNotification" class="fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg flex items-center hidden z-50"> |
| <i class="fas fa-check-circle mr-2"></i> |
| <span>Permissions updated successfully!</span> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const groupToggles = document.querySelectorAll('.group-toggle'); |
| groupToggles.forEach(toggle => { |
| toggle.addEventListener('click', function() { |
| const groupId = this.getAttribute('data-group'); |
| const content = document.querySelector(`.group-content[data-group="${groupId}"]`); |
| const icon = this.querySelector('i'); |
| |
| if (content.style.display === 'none' || !content.style.display) { |
| content.style.display = 'block'; |
| icon.classList.remove('fa-chevron-down'); |
| icon.classList.add('fa-chevron-up'); |
| } else { |
| content.style.display = 'none'; |
| icon.classList.remove('fa-chevron-up'); |
| icon.classList.add('fa-chevron-down'); |
| } |
| }); |
| }); |
| |
| |
| const groupCheckboxes = document.querySelectorAll('.group-checkbox'); |
| groupCheckboxes.forEach(checkbox => { |
| checkbox.addEventListener('change', function() { |
| const groupId = this.getAttribute('data-group'); |
| const cameras = document.querySelectorAll(`.group-content[data-group="${groupId}"] .camera-checkbox`); |
| |
| cameras.forEach(camCheckbox => { |
| camCheckbox.checked = this.checked; |
| }); |
| |
| updateSelectedItems(); |
| }); |
| }); |
| |
| |
| const cameraCheckboxes = document.querySelectorAll('.camera-checkbox'); |
| cameraCheckboxes.forEach(checkbox => { |
| checkbox.addEventListener('change', function() { |
| updateSelectedItems(); |
| }); |
| }); |
| |
| |
| function updateSelectedItems() { |
| const selectedItemsContainer = document.getElementById('selectedItems'); |
| const selectedCameras = document.querySelectorAll('.camera-checkbox:checked'); |
| |
| if (selectedCameras.length === 0) { |
| selectedItemsContainer.innerHTML = '<p class="text-sm text-gray-500 italic">No items selected</p>'; |
| return; |
| } |
| |
| selectedItemsContainer.innerHTML = ''; |
| selectedCameras.forEach(checkbox => { |
| const cameraId = checkbox.getAttribute('data-camera'); |
| const cameraItem = checkbox.closest('.camera-item'); |
| const cameraName = cameraItem.querySelector('span').textContent; |
| const cameraIcon = cameraItem.querySelector('.fa-video').parentNode.cloneNode(true); |
| |
| const chip = document.createElement('div'); |
| chip.className = 'bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-sm flex items-center mb-1'; |
| chip.appendChild(cameraIcon); |
| chip.innerHTML += ` ${cameraName}`; |
| |
| selectedItemsContainer.appendChild(chip); |
| }); |
| } |
| |
| |
| const draggables = document.querySelectorAll('.draggable'); |
| const dropzones = document.querySelectorAll('.dropzone'); |
| |
| draggables.forEach(draggable => { |
| draggable.addEventListener('dragstart', function() { |
| this.classList.add('dragging'); |
| }); |
| |
| draggable.addEventListener('dragend', function() { |
| this.classList.remove('dragging'); |
| }); |
| }); |
| |
| dropzones.forEach(dropzone => { |
| dropzone.addEventListener('dragover', function(e) { |
| e.preventDefault(); |
| this.classList.add('active'); |
| }); |
| |
| dropzone.addEventListener('dragleave', function() { |
| this.classList.remove('active'); |
| }); |
| |
| dropzone.addEventListener('drop', function(e) { |
| e.preventDefault(); |
| this.classList.remove('active'); |
| |
| const dragging = document.querySelector('.dragging'); |
| if (dragging) { |
| this.appendChild(dragging); |
| |
| |
| checkForConflicts(); |
| } |
| }); |
| }); |
| |
| |
| function checkForConflicts() { |
| |
| |
| const lobbyCamCheckbox = document.querySelector('.camera-checkbox[data-camera="lobby-cam"]'); |
| |
| if (lobbyCamCheckbox && lobbyCamCheckbox.checked) { |
| const conflictModal = document.getElementById('conflictModal'); |
| conflictModal.classList.remove('hidden'); |
| } |
| } |
| |
| |
| const closeConflictModal = document.getElementById('closeConflictModal'); |
| const cancelConflict = document.getElementById('cancelConflict'); |
| const resolveConflict = document.getElementById('resolveConflict'); |
| |
| if (closeConflictModal) { |
| closeConflictModal.addEventListener('click', function() { |
| document.getElementById('conflictModal').classList.add('hidden'); |
| }); |
| } |
| |
| if (cancelConflict) { |
| cancelConflict.addEventListener('click', function() { |
| document.getElementById('conflictModal').classList.add('hidden'); |
| }); |
| } |
| |
| if (resolveConflict) { |
| resolveConflict.addEventListener('click', function() { |
| document.getElementById('conflictModal').classList.add('hidden'); |
| document.getElementById('successNotification').classList.remove('hidden'); |
| |
| setTimeout(() => { |
| document.getElementById('successNotification').classList.add('hidden'); |
| }, 3000); |
| }); |
| } |
| |
| |
| const saveBtn = document.getElementById('saveBtn'); |
| if (saveBtn) { |
| saveBtn.addEventListener('click', function() { |
| |
| checkForConflicts(); |
| |
| |
| const conflictModal = document.getElementById('conflictModal'); |
| if (conflictModal.classList.contains('hidden')) { |
| document.getElementById('successNotification').classList.remove('hidden'); |
| |
| setTimeout(() => { |
| document.getElementById('successNotification').classList.add('hidden'); |
| }, 3000); |
| } |
| }); |
| } |
| |
| |
| const bulkBtn = document.getElementById('bulkBtn'); |
| if (bulkBtn) { |
| bulkBtn.addEventListener('click', function() { |
| |
| alert('Bulk assign functionality would open here'); |
| }); |
| } |
| |
| |
| const cameraSearch = document.getElementById('cameraSearch'); |
| if (cameraSearch) { |
| cameraSearch.addEventListener('input', function() { |
| const searchTerm = this.value.toLowerCase(); |
| const cameras = document.querySelectorAll('.camera-item'); |
| |
| cameras.forEach(camera => { |
| const cameraName = camera.querySelector('span').textContent.toLowerCase(); |
| if (cameraName.includes(searchTerm)) { |
| camera.style.display = 'flex'; |
| |
| |
| let parentGroup = camera.closest('.group-content'); |
| while (parentGroup) { |
| parentGroup.style.display = 'block'; |
| const toggle = document.querySelector(`.group-toggle[data-group="${parentGroup.getAttribute('data-group')}"]`); |
| if (toggle) { |
| toggle.querySelector('i').classList.remove('fa-chevron-down'); |
| toggle.querySelector('i').classList.add('fa-chevron-up'); |
| } |
| parentGroup = parentGroup.closest('.group-content'); |
| } |
| } else { |
| camera.style.display = 'none'; |
| } |
| }); |
| }); |
| } |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=daoan1412/camera-access-permissions" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |