refactor: Refactored mobile UI
This commit is contained in:
268
Backend/public/mobile-sim-activity.html
Normal file
268
Backend/public/mobile-sim-activity.html
Normal file
@@ -0,0 +1,268 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="black">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SecureCam Web Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.6.0/dist/full.min.css" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(15, 15, 20, 0.7);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(25, 25, 30, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.btn-premium {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-premium:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: #10b981;
|
||||
box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #ef4444;
|
||||
box-shadow: 0 0 8px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-enter {
|
||||
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body data-page="activity" class="h-screen bg-[#0a0a0c] text-gray-200 overflow-hidden flex">
|
||||
|
||||
<!-- Toast Container -->
|
||||
<div id="toast-container" class="toast toast-top toast-end z-50"></div>
|
||||
|
||||
<!-- MAIN APP SHELL -->
|
||||
<div class="flex h-full w-full">
|
||||
|
||||
<!-- SIDEBAR NAVIGATION -->
|
||||
<aside id="bottomNav"
|
||||
class="w-20 lg:w-64 glass-panel border-r border-white/5 flex-col justify-between hidden h-full">
|
||||
<!-- Logo Area -->
|
||||
<div class="p-6 flex items-center justify-center lg:justify-start gap-3 border-b border-white/5">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Nav Links -->
|
||||
<nav class="flex-1 py-6 px-3 space-y-2">
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="home">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Dashboard</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all relative data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="activity">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Activity Feed</span>
|
||||
<span id="notificationDot"
|
||||
class="absolute lg:relative lg:top-auto lg:right-auto top-3 right-3 lg:ml-auto w-2 h-2 bg-red-500 rounded-full hidden"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Settings</span>
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- CONTENT AREA -->
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
||||
|
||||
<!-- Top Bar -->
|
||||
<header
|
||||
class="h-16 shrink-0 border-b border-white/5 flex items-center justify-end px-6 relative z-10 glass-panel">
|
||||
<div class="flex items-center gap-6">
|
||||
<div id="connectionStatus" class="flex items-center gap-2">
|
||||
<span class="status-dot status-offline transition-colors duration-300"></span>
|
||||
<span class="text-xs text-gray-400 font-medium tracking-wide uppercase">OFFLINE</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-px bg-white/10"></div>
|
||||
|
||||
<div id="authStatusBadge" class="flex items-center gap-2 text-sm text-gray-400">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-gray-800 flex items-center justify-center text-xs font-bold border border-white/10">
|
||||
?</div>
|
||||
<span class="hidden sm:inline">Signed Out</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Pages -->
|
||||
<div class="flex-1 overflow-y-auto p-4 md:p-8 lg:p-10 relative">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- ACTIVITY SCREEN -->
|
||||
<section id="screen-activity" class="hidden flex-col gap-6 max-w-4xl mx-auto py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-2xl font-bold text-white tracking-tight">Activity History</h2>
|
||||
<button id="clearActivityBtn"
|
||||
class="btn btn-ghost text-gray-400 hover:bg-white/5 hover:text-white rounded-xl border border-transparent">Clear
|
||||
Read</button>
|
||||
</div>
|
||||
|
||||
<div class="glass-card rounded-3xl border border-white/5 p-2 overflow-hidden">
|
||||
<div id="activityFeedList" class="divide-y divide-white/5">
|
||||
<!-- Empty State -->
|
||||
<div class="text-center py-16 opacity-50">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4 text-gray-600" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<p class="text-sm font-medium text-gray-400">All quiet. No notifications yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div> <!-- End Main App Shell -->
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/sim/mobile-sim.js" defer></script>
|
||||
|
||||
<!-- Recording Modal -->
|
||||
<div id="recordingModal"
|
||||
class="fixed inset-0 bg-[#0a0a0c]/90 backdrop-blur z-[100] hidden items-center justify-center p-4 lg:p-10">
|
||||
<div
|
||||
class="w-full max-w-4xl glass-card rounded-3xl p-6 space-y-4 shadow-2xl border border-white/10 flex flex-col max-h-[90vh]">
|
||||
<div class="flex items-center justify-between shrink-0">
|
||||
<h3 id="recordingModalTitle" class="text-lg font-semibold text-white tracking-wide">Recording Playback</h3>
|
||||
<button id="recordingModalCloseBtn"
|
||||
class="btn btn-square btn-ghost text-gray-400 hover:text-white rounded-xl hover:bg-white/10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 bg-black rounded-2xl overflow-hidden relative border border-white/5 shadow-inner">
|
||||
<video id="recordingModalVideo" class="w-full h-full object-contain" controls playsinline></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
286
Backend/public/mobile-sim-auth.html
Normal file
286
Backend/public/mobile-sim-auth.html
Normal file
@@ -0,0 +1,286 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="black">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SecureCam Web Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.6.0/dist/full.min.css" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(15, 15, 20, 0.7);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(25, 25, 30, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.btn-premium {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-premium:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: #10b981;
|
||||
box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #ef4444;
|
||||
box-shadow: 0 0 8px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-enter {
|
||||
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body data-page="auth" class="h-screen bg-[#0a0a0c] text-gray-200 overflow-hidden flex">
|
||||
|
||||
<!-- Toast Container -->
|
||||
<div id="toast-container" class="toast toast-top toast-end z-50"></div>
|
||||
|
||||
<!-- MAIN APP SHELL -->
|
||||
<div class="flex h-full w-full">
|
||||
|
||||
<!-- SIDEBAR NAVIGATION -->
|
||||
<aside id="bottomNav"
|
||||
class="w-20 lg:w-64 glass-panel border-r border-white/5 flex-col justify-between hidden h-full">
|
||||
<!-- Logo Area -->
|
||||
<div class="p-6 flex items-center justify-center lg:justify-start gap-3 border-b border-white/5">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Nav Links -->
|
||||
<nav class="flex-1 py-6 px-3 space-y-2">
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="home">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Dashboard</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all relative data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="activity">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Activity Feed</span>
|
||||
<span id="notificationDot"
|
||||
class="absolute lg:relative lg:top-auto lg:right-auto top-3 right-3 lg:ml-auto w-2 h-2 bg-red-500 rounded-full hidden"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Settings</span>
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- CONTENT AREA -->
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
||||
|
||||
<!-- Top Bar -->
|
||||
<header
|
||||
class="h-16 shrink-0 border-b border-white/5 flex items-center justify-end px-6 relative z-10 glass-panel">
|
||||
<div class="flex items-center gap-6">
|
||||
<div id="connectionStatus" class="flex items-center gap-2">
|
||||
<span class="status-dot status-offline transition-colors duration-300"></span>
|
||||
<span class="text-xs text-gray-400 font-medium tracking-wide uppercase">OFFLINE</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-px bg-white/10"></div>
|
||||
|
||||
<div id="authStatusBadge" class="flex items-center gap-2 text-sm text-gray-400">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-gray-800 flex items-center justify-center text-xs font-bold border border-white/10">
|
||||
?</div>
|
||||
<span class="hidden sm:inline">Signed Out</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Pages -->
|
||||
<div class="flex-1 overflow-y-auto p-4 md:p-8 lg:p-10 relative">
|
||||
|
||||
<!-- AUTH SCREEN -->
|
||||
<section id="screen-auth"
|
||||
class="flex flex-col items-center justify-center min-h-[70vh] animate-fade-in max-w-sm mx-auto">
|
||||
<div class="text-center space-y-3 mb-8">
|
||||
<div
|
||||
class="w-20 h-20 bg-gradient-to-tr from-blue-600 to-indigo-600 rounded-3xl mx-auto flex items-center justify-center shadow-lg shadow-blue-900/20">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-3xl font-bold text-white tracking-tight">SecureCam Web</h2>
|
||||
<p class="text-gray-400 text-sm">Sign in to manage visual security from your browser.</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full glass-card p-6 md:p-8 rounded-3xl space-y-4 shadow-2xl">
|
||||
<div class="form-control">
|
||||
<label class="label hidden"><span class="label-text text-gray-400">Email</span></label>
|
||||
<input id="authEmail" type="email" placeholder="Email address"
|
||||
class="input bg-black/40 border-white/10 text-sm focus:border-blue-500 focus:outline-none transition-colors w-full h-12 rounded-xl" />
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<input id="authPassword" type="password" placeholder="Password"
|
||||
class="input bg-black/40 border-white/10 text-sm focus:border-blue-500 focus:outline-none transition-colors w-full h-12 rounded-xl" />
|
||||
</div>
|
||||
<div id="authNameField" class="form-control hidden">
|
||||
<input id="authName" type="text" placeholder="Your Name"
|
||||
class="input bg-black/40 border-white/10 text-sm focus:border-blue-500 focus:outline-none transition-colors w-full h-12 rounded-xl" />
|
||||
</div>
|
||||
|
||||
<div class="pt-4 space-y-4">
|
||||
<button id="signInBtn"
|
||||
class="btn btn-premium w-full h-12 rounded-xl shadow-lg shadow-blue-900/20 text-base">Sign In</button>
|
||||
<div class="divider text-xs text-gray-600">OR</div>
|
||||
<button id="toggleAuthModeBtn"
|
||||
class="btn btn-ghost w-full text-gray-400 hover:text-white hover:bg-white/5 rounded-xl border border-white/5">Create
|
||||
an account</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div> <!-- End Main App Shell -->
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/sim/mobile-sim.js" defer></script>
|
||||
|
||||
<!-- Recording Modal -->
|
||||
<div id="recordingModal"
|
||||
class="fixed inset-0 bg-[#0a0a0c]/90 backdrop-blur z-[100] hidden items-center justify-center p-4 lg:p-10">
|
||||
<div
|
||||
class="w-full max-w-4xl glass-card rounded-3xl p-6 space-y-4 shadow-2xl border border-white/10 flex flex-col max-h-[90vh]">
|
||||
<div class="flex items-center justify-between shrink-0">
|
||||
<h3 id="recordingModalTitle" class="text-lg font-semibold text-white tracking-wide">Recording Playback</h3>
|
||||
<button id="recordingModalCloseBtn"
|
||||
class="btn btn-square btn-ghost text-gray-400 hover:text-white rounded-xl hover:bg-white/10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 bg-black rounded-2xl overflow-hidden relative border border-white/5 shadow-inner">
|
||||
<video id="recordingModalVideo" class="w-full h-full object-contain" controls playsinline></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
320
Backend/public/mobile-sim-camera.html
Normal file
320
Backend/public/mobile-sim-camera.html
Normal file
@@ -0,0 +1,320 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="black">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SecureCam Web Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.6.0/dist/full.min.css" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(15, 15, 20, 0.7);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(25, 25, 30, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.btn-premium {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-premium:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: #10b981;
|
||||
box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #ef4444;
|
||||
box-shadow: 0 0 8px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-enter {
|
||||
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body data-page="camera" class="h-screen bg-[#0a0a0c] text-gray-200 overflow-hidden flex">
|
||||
|
||||
<!-- Toast Container -->
|
||||
<div id="toast-container" class="toast toast-top toast-end z-50"></div>
|
||||
|
||||
<!-- MAIN APP SHELL -->
|
||||
<div class="flex h-full w-full">
|
||||
|
||||
<!-- SIDEBAR NAVIGATION -->
|
||||
<aside id="bottomNav"
|
||||
class="w-20 lg:w-64 glass-panel border-r border-white/5 flex-col justify-between hidden h-full">
|
||||
<!-- Logo Area -->
|
||||
<div class="p-6 flex items-center justify-center lg:justify-start gap-3 border-b border-white/5">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Nav Links -->
|
||||
<nav class="flex-1 py-6 px-3 space-y-2">
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="home">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Dashboard</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all relative data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="activity">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Activity Feed</span>
|
||||
<span id="notificationDot"
|
||||
class="absolute lg:relative lg:top-auto lg:right-auto top-3 right-3 lg:ml-auto w-2 h-2 bg-red-500 rounded-full hidden"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Settings</span>
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- CONTENT AREA -->
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
||||
|
||||
<!-- Top Bar -->
|
||||
<header
|
||||
class="h-16 shrink-0 border-b border-white/5 flex items-center justify-end px-6 relative z-10 glass-panel">
|
||||
<div class="flex items-center gap-6">
|
||||
<div id="connectionStatus" class="flex items-center gap-2">
|
||||
<span class="status-dot status-offline transition-colors duration-300"></span>
|
||||
<span class="text-xs text-gray-400 font-medium tracking-wide uppercase">OFFLINE</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-px bg-white/10"></div>
|
||||
|
||||
<div id="authStatusBadge" class="flex items-center gap-2 text-sm text-gray-400">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-gray-800 flex items-center justify-center text-xs font-bold border border-white/10">
|
||||
?</div>
|
||||
<span class="hidden sm:inline">Signed Out</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Pages -->
|
||||
<div class="flex-1 overflow-y-auto p-4 md:p-8 lg:p-10 relative">
|
||||
|
||||
|
||||
|
||||
<!-- CAMERA DASHBOARD -->
|
||||
<section id="screen-home-camera" class="hidden flex-col gap-10 max-w-7xl mx-auto h-full">
|
||||
<div class="flex justify-between items-center shrink-0 mb-4">
|
||||
<h2 class="text-2xl font-bold text-white tracking-tight">Camera Feed (Broadcasting)</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-col lg:flex-row gap-8 min-h-0">
|
||||
<!-- Main Player -->
|
||||
<div class="flex-1 glass-card rounded-3xl overflow-hidden relative flex flex-col border border-white/10">
|
||||
<div id="cameraPreview" class="flex-1 bg-black relative flex items-center justify-center">
|
||||
<video id="cameraVideo" class="absolute inset-0 w-full h-full object-cover hidden" autoplay playsinline
|
||||
muted></video>
|
||||
<!-- Recording indicator -->
|
||||
<div
|
||||
class="absolute top-4 left-4 z-20 flex items-center gap-2 px-3 py-1.5 rounded-full bg-black/50 backdrop-blur border border-white/10">
|
||||
<span
|
||||
class="w-2.5 h-2.5 bg-red-500 rounded-full shadow-[0_0_8px_rgba(239,68,68,0.8)] animate-pulse"></span>
|
||||
<span class="text-xs text-white font-medium tracking-wide">REC</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cameraOfflineOverlay"
|
||||
class="absolute inset-0 bg-[#0a0a0c]/80 backdrop-blur-sm z-10 flex flex-col items-center justify-center gap-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-gray-600" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
</svg>
|
||||
<p class="text-gray-400 font-medium tracking-wide">Camera Offline</p>
|
||||
<button id="cameraGoOnlineBtn"
|
||||
class="btn btn-outline btn-success rounded-xl border-green-500/50 text-green-400 hover:bg-green-500/10 hover:border-green-400">
|
||||
Go Online
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls Sidebar -->
|
||||
<div class="lg:w-80 shrink-0 flex flex-col gap-6">
|
||||
<div class="glass-card p-6 rounded-3xl border border-white/5 space-y-4">
|
||||
<h3 class="text-xs font-bold text-gray-500 uppercase tracking-wider">Manual Controls</h3>
|
||||
<div class="space-y-3">
|
||||
<button id="startMotionBtn"
|
||||
class="btn w-full justify-start h-14 rounded-xl bg-white/5 border-white/5 hover:bg-red-500/10 hover:border-red-500/30 hover:text-red-400 transition-all font-medium group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 group-hover:text-red-400 mr-2"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
Simulate Motion Event
|
||||
</button>
|
||||
<button id="endMotionBtn"
|
||||
class="btn w-full justify-start h-14 rounded-xl bg-white/5 border-white/5 hover:bg-white/10 font-medium disabled:opacity-30"
|
||||
disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 mr-2" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
|
||||
</svg>
|
||||
Stop Recording
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-card p-6 rounded-3xl border border-white/5 h-80 flex flex-col min-h-0 overflow-hidden">
|
||||
<h3 class="text-xs font-bold text-gray-500 uppercase tracking-wider mb-4 shrink-0">System Logs</h3>
|
||||
<div id="cameraLogs"
|
||||
class="flex-1 min-h-0 bg-black/40 rounded-xl p-4 text-xs font-mono text-gray-400 overflow-y-auto border border-white/5">
|
||||
<div class="text-gray-600 italic">Awaiting connection...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div> <!-- End Main App Shell -->
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/sim/mobile-sim.js" defer></script>
|
||||
|
||||
<!-- Recording Modal -->
|
||||
<div id="recordingModal"
|
||||
class="fixed inset-0 bg-[#0a0a0c]/90 backdrop-blur z-[100] hidden items-center justify-center p-4 lg:p-10">
|
||||
<div
|
||||
class="w-full max-w-4xl glass-card rounded-3xl p-6 space-y-4 shadow-2xl border border-white/10 flex flex-col max-h-[90vh]">
|
||||
<div class="flex items-center justify-between shrink-0">
|
||||
<h3 id="recordingModalTitle" class="text-lg font-semibold text-white tracking-wide">Recording Playback</h3>
|
||||
<button id="recordingModalCloseBtn"
|
||||
class="btn btn-square btn-ghost text-gray-400 hover:text-white rounded-xl hover:bg-white/10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 bg-black rounded-2xl overflow-hidden relative border border-white/5 shadow-inner">
|
||||
<video id="recordingModalVideo" class="w-full h-full object-contain" controls playsinline></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
335
Backend/public/mobile-sim-client.html
Normal file
335
Backend/public/mobile-sim-client.html
Normal file
@@ -0,0 +1,335 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="black">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SecureCam Web Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.6.0/dist/full.min.css" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(15, 15, 20, 0.7);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(25, 25, 30, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.btn-premium {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-premium:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: #10b981;
|
||||
box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #ef4444;
|
||||
box-shadow: 0 0 8px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-enter {
|
||||
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body data-page="client" class="h-screen bg-[#0a0a0c] text-gray-200 overflow-hidden flex">
|
||||
|
||||
<!-- Toast Container -->
|
||||
<div id="toast-container" class="toast toast-top toast-end z-50"></div>
|
||||
|
||||
<!-- MAIN APP SHELL -->
|
||||
<div class="flex h-full w-full">
|
||||
|
||||
<!-- SIDEBAR NAVIGATION -->
|
||||
<aside id="bottomNav"
|
||||
class="w-20 lg:w-64 glass-panel border-r border-white/5 flex-col justify-between hidden h-full">
|
||||
<!-- Logo Area -->
|
||||
<div class="p-6 flex items-center justify-center lg:justify-start gap-3 border-b border-white/5">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Nav Links -->
|
||||
<nav class="flex-1 py-6 px-3 space-y-2">
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="home">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Dashboard</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all relative data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="activity">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Activity Feed</span>
|
||||
<span id="notificationDot"
|
||||
class="absolute lg:relative lg:top-auto lg:right-auto top-3 right-3 lg:ml-auto w-2 h-2 bg-red-500 rounded-full hidden"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Settings</span>
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- CONTENT AREA -->
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
||||
|
||||
<!-- Top Bar -->
|
||||
<header
|
||||
class="h-16 shrink-0 border-b border-white/5 flex items-center justify-end px-6 relative z-10 glass-panel">
|
||||
<div class="flex items-center gap-6">
|
||||
<div id="connectionStatus" class="flex items-center gap-2">
|
||||
<span class="status-dot status-offline transition-colors duration-300"></span>
|
||||
<span class="text-xs text-gray-400 font-medium tracking-wide uppercase">OFFLINE</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-px bg-white/10"></div>
|
||||
|
||||
<div id="authStatusBadge" class="flex items-center gap-2 text-sm text-gray-400">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-gray-800 flex items-center justify-center text-xs font-bold border border-white/10">
|
||||
?</div>
|
||||
<span class="hidden sm:inline">Signed Out</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Pages -->
|
||||
<div class="flex-1 overflow-y-auto p-4 md:p-8 lg:p-10 relative">
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- CLIENT DASHBOARD -->
|
||||
<section id="screen-home-client" class="hidden flex-col gap-12 max-w-7xl mx-auto h-full">
|
||||
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-bold text-white tracking-tight">Client Dashboard</h2>
|
||||
<div class="flex gap-3">
|
||||
<button id="linkCameraBtn"
|
||||
class="btn btn-outline border-white/10 text-gray-300 hover:text-white hover:bg-white/5 rounded-xl gap-2 shadow-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Link Camera
|
||||
</button>
|
||||
<button id="refreshClientBtn"
|
||||
class="btn btn-ghost btn-square rounded-xl bg-white/5 border border-white/5 hover:bg-white/10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cameras -> Horizontal List -->
|
||||
<div class="glass-card rounded-3xl border border-white/5 p-5 shrink-0 mb-4">
|
||||
<h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4">Your Cameras</h3>
|
||||
<div id="linkedCamerasList" class="flex overflow-x-auto gap-4 pb-2 snap-x">
|
||||
<!-- Populated by JS -->
|
||||
<div class="w-full text-center py-6 bg-black/20 rounded-2xl border border-dashed border-white/10">
|
||||
<p class="text-gray-500 text-sm">No cameras linked yet</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Theater Player Layout -->
|
||||
<div class="flex flex-col xl:flex-row gap-8 flex-1 min-h-0">
|
||||
|
||||
<!-- Stream Viewer -->
|
||||
<div id="clientStreamViewerWrapper"
|
||||
class="flex-1 glass-card rounded-3xl overflow-hidden border border-white/10 flex flex-col shadow-xl min-h-[400px] hidden">
|
||||
<div class="px-5 py-4 border-b border-white/5 bg-black/20 flex justify-between items-center">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-2 h-2 rounded-full bg-red-500 animate-[pulse_2s_ease-in-out_infinite] hidden"
|
||||
id="clientLiveDot"></span>
|
||||
<h3 class="font-medium text-white tracking-wide" id="clientStreamViewerTitle">Live Feed Viewer</h3>
|
||||
</div>
|
||||
<button id="closeStreamViewerBtn"
|
||||
class="btn btn-ghost btn-sm btn-circle text-gray-400 hover:text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="clientStreamContainer" class="flex-1 bg-black relative flex items-center justify-center">
|
||||
<!-- This video element receives the WebRTC stream -->
|
||||
<video id="clientStreamVideo" class="absolute inset-0 w-full h-full object-contain hidden"
|
||||
playsinline></video>
|
||||
<img id="clientStreamImage" class="absolute inset-0 w-full h-full object-contain hidden" />
|
||||
|
||||
<div id="clientStreamPlaceholder" class="flex flex-col items-center gap-4 animate-pulse">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-700" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<p class="text-sm font-medium text-gray-500 tracking-wide uppercase">Select a camera to view</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Sidebar (Recordings) -->
|
||||
<div class="xl:w-96 shrink-0 flex flex-col gap-6 overflow-y-auto pr-2">
|
||||
|
||||
<!-- Recordings -->
|
||||
<div class="glass-card rounded-3xl border border-white/5 p-5 flex-1">
|
||||
<h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4">Recent Recordings</h3>
|
||||
<div id="recordingsList" class="space-y-3">
|
||||
<!-- Populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div> <!-- End Main App Shell -->
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/sim/mobile-sim.js" defer></script>
|
||||
|
||||
<!-- Recording Modal -->
|
||||
<div id="recordingModal"
|
||||
class="fixed inset-0 bg-[#0a0a0c]/90 backdrop-blur z-[100] hidden items-center justify-center p-4 lg:p-10">
|
||||
<div
|
||||
class="w-full max-w-4xl glass-card rounded-3xl p-6 space-y-4 shadow-2xl border border-white/10 flex flex-col max-h-[90vh]">
|
||||
<div class="flex items-center justify-between shrink-0">
|
||||
<h3 id="recordingModalTitle" class="text-lg font-semibold text-white tracking-wide">Recording Playback</h3>
|
||||
<button id="recordingModalCloseBtn"
|
||||
class="btn btn-square btn-ghost text-gray-400 hover:text-white rounded-xl hover:bg-white/10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 bg-black rounded-2xl overflow-hidden relative border border-white/5 shadow-inner">
|
||||
<video id="recordingModalVideo" class="w-full h-full object-contain" controls playsinline></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
290
Backend/public/mobile-sim-onboarding.html
Normal file
290
Backend/public/mobile-sim-onboarding.html
Normal file
@@ -0,0 +1,290 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="black">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SecureCam Web Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.6.0/dist/full.min.css" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(15, 15, 20, 0.7);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(25, 25, 30, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.btn-premium {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-premium:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: #10b981;
|
||||
box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #ef4444;
|
||||
box-shadow: 0 0 8px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-enter {
|
||||
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body data-page="onboarding" class="h-screen bg-[#0a0a0c] text-gray-200 overflow-hidden flex">
|
||||
|
||||
<!-- Toast Container -->
|
||||
<div id="toast-container" class="toast toast-top toast-end z-50"></div>
|
||||
|
||||
<!-- MAIN APP SHELL -->
|
||||
<div class="flex h-full w-full">
|
||||
|
||||
<!-- SIDEBAR NAVIGATION -->
|
||||
<aside id="bottomNav"
|
||||
class="w-20 lg:w-64 glass-panel border-r border-white/5 flex-col justify-between hidden h-full">
|
||||
<!-- Logo Area -->
|
||||
<div class="p-6 flex items-center justify-center lg:justify-start gap-3 border-b border-white/5">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Nav Links -->
|
||||
<nav class="flex-1 py-6 px-3 space-y-2">
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="home">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Dashboard</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all relative data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="activity">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Activity Feed</span>
|
||||
<span id="notificationDot"
|
||||
class="absolute lg:relative lg:top-auto lg:right-auto top-3 right-3 lg:ml-auto w-2 h-2 bg-red-500 rounded-full hidden"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Settings</span>
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- CONTENT AREA -->
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
||||
|
||||
<!-- Top Bar -->
|
||||
<header
|
||||
class="h-16 shrink-0 border-b border-white/5 flex items-center justify-end px-6 relative z-10 glass-panel">
|
||||
<div class="flex items-center gap-6">
|
||||
<div id="connectionStatus" class="flex items-center gap-2">
|
||||
<span class="status-dot status-offline transition-colors duration-300"></span>
|
||||
<span class="text-xs text-gray-400 font-medium tracking-wide uppercase">OFFLINE</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-px bg-white/10"></div>
|
||||
|
||||
<div id="authStatusBadge" class="flex items-center gap-2 text-sm text-gray-400">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-gray-800 flex items-center justify-center text-xs font-bold border border-white/10">
|
||||
?</div>
|
||||
<span class="hidden sm:inline">Signed Out</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Pages -->
|
||||
<div class="flex-1 overflow-y-auto p-4 md:p-8 lg:p-10 relative">
|
||||
|
||||
|
||||
<!-- ONBOARDING SCREEN -->
|
||||
<section id="screen-onboarding"
|
||||
class="hidden flex-col items-center justify-center min-h-[70vh] max-w-lg mx-auto">
|
||||
<div class="text-center space-y-2 mb-8">
|
||||
<h2 class="text-3xl font-bold text-white tracking-tight">Configure Device</h2>
|
||||
<p class="text-sm text-gray-400">Set up this browser simulator's role</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full glass-card p-6 md:p-8 rounded-3xl space-y-6 shadow-2xl">
|
||||
<div class="form-control w-full">
|
||||
<label class="label"><span class="label-text text-xs font-semibold text-gray-400 tracking-wider">DEVICE
|
||||
NAME</span></label>
|
||||
<input id="deviceName" type="text" placeholder="e.g. Living Room Cam"
|
||||
class="input input-bordered h-12 rounded-xl bg-black/40 border-white/10 focus:border-blue-500" />
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label"><span
|
||||
class="label-text text-xs font-semibold text-gray-400 tracking-wider">ROLE</span></label>
|
||||
<div class="grid grid-cols-2 gap-2 p-1.5 bg-black/40 rounded-xl border border-white/5">
|
||||
<button
|
||||
class="btn btn-ghost normal-case text-gray-400 data-[active=true]:bg-blue-600 data-[active=true]:text-white rounded-lg h-10 min-h-0"
|
||||
id="btn-role-camera" data-role="camera" data-active="false">Camera</button>
|
||||
<button
|
||||
class="btn btn-ghost normal-case text-gray-400 data-[active=true]:bg-blue-600 data-[active=true]:text-white rounded-lg h-10 min-h-0"
|
||||
id="btn-role-client" data-role="client" data-active="true">Client</button>
|
||||
</div>
|
||||
<input type="hidden" id="role" value="client">
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label"><span class="label-text text-xs font-semibold text-gray-400 tracking-wider">PUSH
|
||||
TOKEN (OPTIONAL)</span></label>
|
||||
<input id="pushToken" type="text" placeholder="simulated_token_123"
|
||||
class="input input-bordered h-12 rounded-xl bg-black/40 border-white/10 focus:border-blue-500" />
|
||||
</div>
|
||||
|
||||
<div class="pt-6">
|
||||
<button id="registerBtn" class="btn btn-premium w-full h-12 rounded-xl text-base">Complete Setup</button>
|
||||
<button id="loadSavedBtn"
|
||||
class="btn btn-ghost w-full mt-3 text-gray-500 hover:text-white hover:bg-white/5">Load previously saved
|
||||
device</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div> <!-- End Main App Shell -->
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/sim/mobile-sim.js" defer></script>
|
||||
|
||||
<!-- Recording Modal -->
|
||||
<div id="recordingModal"
|
||||
class="fixed inset-0 bg-[#0a0a0c]/90 backdrop-blur z-[100] hidden items-center justify-center p-4 lg:p-10">
|
||||
<div
|
||||
class="w-full max-w-4xl glass-card rounded-3xl p-6 space-y-4 shadow-2xl border border-white/10 flex flex-col max-h-[90vh]">
|
||||
<div class="flex items-center justify-between shrink-0">
|
||||
<h3 id="recordingModalTitle" class="text-lg font-semibold text-white tracking-wide">Recording Playback</h3>
|
||||
<button id="recordingModalCloseBtn"
|
||||
class="btn btn-square btn-ghost text-gray-400 hover:text-white rounded-xl hover:bg-white/10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 bg-black rounded-2xl overflow-hidden relative border border-white/5 shadow-inner">
|
||||
<video id="recordingModalVideo" class="w-full h-full object-contain" controls playsinline></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
310
Backend/public/mobile-sim-settings.html
Normal file
310
Backend/public/mobile-sim-settings.html
Normal file
@@ -0,0 +1,310 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="black">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SecureCam Web Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.6.0/dist/full.min.css" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(15, 15, 20, 0.7);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(25, 25, 30, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.btn-premium {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-premium:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: #10b981;
|
||||
box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #ef4444;
|
||||
box-shadow: 0 0 8px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-enter {
|
||||
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body data-page="settings" class="h-screen bg-[#0a0a0c] text-gray-200 overflow-hidden flex">
|
||||
|
||||
<!-- Toast Container -->
|
||||
<div id="toast-container" class="toast toast-top toast-end z-50"></div>
|
||||
|
||||
<!-- MAIN APP SHELL -->
|
||||
<div class="flex h-full w-full">
|
||||
|
||||
<!-- SIDEBAR NAVIGATION -->
|
||||
<aside id="bottomNav"
|
||||
class="w-20 lg:w-64 glass-panel border-r border-white/5 flex-col justify-between hidden h-full">
|
||||
<!-- Logo Area -->
|
||||
<div class="p-6 flex items-center justify-center lg:justify-start gap-3 border-b border-white/5">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Nav Links -->
|
||||
<nav class="flex-1 py-6 px-3 space-y-2">
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="home">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Dashboard</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all relative data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="activity">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Activity Feed</span>
|
||||
<span id="notificationDot"
|
||||
class="absolute lg:relative lg:top-auto lg:right-auto top-3 right-3 lg:ml-auto w-2 h-2 bg-red-500 rounded-full hidden"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn w-full flex items-center gap-3 p-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all data-[active=true]:text-white data-[active=true]:bg-blue-600/10 data-[active=true]:border data-[active=true]:border-blue-500/20 group"
|
||||
data-target="settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span class="font-medium hidden lg:block text-sm">Settings</span>
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- CONTENT AREA -->
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
||||
|
||||
<!-- Top Bar -->
|
||||
<header
|
||||
class="h-16 shrink-0 border-b border-white/5 flex items-center justify-end px-6 relative z-10 glass-panel">
|
||||
<div class="flex items-center gap-6">
|
||||
<div id="connectionStatus" class="flex items-center gap-2">
|
||||
<span class="status-dot status-offline transition-colors duration-300"></span>
|
||||
<span class="text-xs text-gray-400 font-medium tracking-wide uppercase">OFFLINE</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-px bg-white/10"></div>
|
||||
|
||||
<div id="authStatusBadge" class="flex items-center gap-2 text-sm text-gray-400">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-gray-800 flex items-center justify-center text-xs font-bold border border-white/10">
|
||||
?</div>
|
||||
<span class="hidden sm:inline">Signed Out</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Pages -->
|
||||
<div class="flex-1 overflow-y-auto p-4 md:p-8 lg:p-10 relative">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- SETTINGS SCREEN -->
|
||||
<section id="screen-settings" class="hidden flex-col gap-6 max-w-2xl mx-auto py-8">
|
||||
<h2 class="text-2xl font-bold text-white tracking-tight px-2">Settings</h2>
|
||||
|
||||
<div class="glass-card rounded-3xl border border-white/5 p-8 flex items-center gap-6">
|
||||
<div
|
||||
class="w-20 h-20 bg-blue-600/20 text-blue-500 rounded-full flex items-center justify-center leading-none text-2xl font-bold border border-blue-500/30"
|
||||
id="profileInitials">U</div>
|
||||
<div>
|
||||
<h3 class="text-xl text-white font-semibold tracking-wide" id="profileName">User</h3>
|
||||
<p class="text-gray-400 mt-1" id="profileEmail">user@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-card rounded-3xl border border-white/5 overflow-hidden">
|
||||
<button id="checkOpsBtn"
|
||||
class="w-full flex items-center justify-between p-5 text-left text-gray-300 hover:bg-white/5 transition-colors border-b border-white/5 group">
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
class="p-2 rounded-lg bg-white/5 group-hover:bg-blue-500/20 group-hover:text-blue-400 transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="font-medium">Run Diagnostics</span>
|
||||
</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-600" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="w-full flex items-center justify-between p-5 text-left text-gray-300 hover:bg-white/5 transition-colors group">
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
class="p-2 rounded-lg bg-white/5 group-hover:bg-blue-500/20 group-hover:text-blue-400 transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="font-medium">Device Information</span>
|
||||
</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-600" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button id="signOutBtn"
|
||||
class="btn btn-error btn-outline w-full rounded-2xl h-14 text-base font-medium gap-3 border-red-500/50 hover:bg-red-500/10 hover:border-red-500 mt-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
Sign Out
|
||||
</button>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div> <!-- End Main App Shell -->
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/sim/mobile-sim.js" defer></script>
|
||||
|
||||
<!-- Recording Modal -->
|
||||
<div id="recordingModal"
|
||||
class="fixed inset-0 bg-[#0a0a0c]/90 backdrop-blur z-[100] hidden items-center justify-center p-4 lg:p-10">
|
||||
<div
|
||||
class="w-full max-w-4xl glass-card rounded-3xl p-6 space-y-4 shadow-2xl border border-white/10 flex flex-col max-h-[90vh]">
|
||||
<div class="flex items-center justify-between shrink-0">
|
||||
<h3 id="recordingModalTitle" class="text-lg font-semibold text-white tracking-wide">Recording Playback</h3>
|
||||
<button id="recordingModalCloseBtn"
|
||||
class="btn btn-square btn-ghost text-gray-400 hover:text-white rounded-xl hover:bg-white/10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 bg-black rounded-2xl overflow-hidden relative border border-white/5 shadow-inner">
|
||||
<video id="recordingModalVideo" class="w-full h-full object-contain" controls playsinline></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -47,6 +47,52 @@ const store = new Store({
|
||||
loading: false, // global loading spinner state if needed
|
||||
});
|
||||
|
||||
const PAGE_PATHS = {
|
||||
auth: '/sim/mobile-sim-auth.html',
|
||||
onboarding: '/sim/mobile-sim-onboarding.html',
|
||||
camera: '/sim/mobile-sim-camera.html',
|
||||
client: '/sim/mobile-sim-client.html',
|
||||
activity: '/sim/mobile-sim-activity.html',
|
||||
settings: '/sim/mobile-sim-settings.html',
|
||||
};
|
||||
|
||||
const currentPageKey = document.body?.dataset?.page || '';
|
||||
const multiPageMode = Boolean(currentPageKey);
|
||||
|
||||
const getHomePageKeyForRole = (role) => (role === 'camera' ? 'camera' : 'client');
|
||||
|
||||
const getPathForScreen = (screen, role) => {
|
||||
if (screen === 'home') {
|
||||
return PAGE_PATHS[getHomePageKeyForRole(role)];
|
||||
}
|
||||
return PAGE_PATHS[screen] || null;
|
||||
};
|
||||
|
||||
const navigateToScreen = (screen, options = {}) => {
|
||||
const { replace = false, role = store.get().device?.role } = options;
|
||||
const targetPath = getPathForScreen(screen, role);
|
||||
|
||||
if (multiPageMode && targetPath && window.location.pathname !== targetPath) {
|
||||
if (replace) {
|
||||
window.location.replace(targetPath);
|
||||
} else {
|
||||
window.location.assign(targetPath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
store.update({ screen });
|
||||
return false;
|
||||
};
|
||||
|
||||
const getScreenForCurrentPage = () => {
|
||||
if (currentPageKey === 'activity') return 'activity';
|
||||
if (currentPageKey === 'settings') return 'settings';
|
||||
if (currentPageKey === 'onboarding') return 'onboarding';
|
||||
if (currentPageKey === 'camera' || currentPageKey === 'client') return 'home';
|
||||
return 'auth';
|
||||
};
|
||||
|
||||
// --- 2. UI Utilities ---
|
||||
const $ = (selector) => {
|
||||
// If it looks like a simple ID (no spaces, dots, hash), use getElementById
|
||||
@@ -209,27 +255,68 @@ const init = async () => {
|
||||
if (session && session.session) {
|
||||
store.update({ session });
|
||||
if (store.get().deviceToken) {
|
||||
// If we have a token, skip onboarding
|
||||
navigateBasedOnRole();
|
||||
const role = store.get().device?.role;
|
||||
if (multiPageMode && (currentPageKey === 'auth' || currentPageKey === 'onboarding')) {
|
||||
if (navigateToScreen('home', { replace: true, role })) return;
|
||||
}
|
||||
if (multiPageMode && (currentPageKey === 'camera' || currentPageKey === 'client')) {
|
||||
const expectedHome = getHomePageKeyForRole(role);
|
||||
if (expectedHome !== currentPageKey) {
|
||||
if (navigateToScreen('home', { replace: true, role })) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (multiPageMode) {
|
||||
store.update({ screen: getScreenForCurrentPage() });
|
||||
} else {
|
||||
navigateBasedOnRole();
|
||||
}
|
||||
connectSocket();
|
||||
startPolling();
|
||||
} else {
|
||||
store.update({ screen: 'onboarding' });
|
||||
if (multiPageMode) {
|
||||
if (currentPageKey !== 'onboarding') {
|
||||
if (navigateToScreen('onboarding', { replace: true })) return;
|
||||
} else {
|
||||
store.update({ screen: 'onboarding' });
|
||||
}
|
||||
} else {
|
||||
store.update({ screen: 'onboarding' });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (multiPageMode) {
|
||||
if (currentPageKey !== 'auth') {
|
||||
if (navigateToScreen('auth', { replace: true })) return;
|
||||
} else {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
} else {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if (multiPageMode) {
|
||||
if (currentPageKey !== 'auth') {
|
||||
if (navigateToScreen('auth', { replace: true })) return;
|
||||
} else {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
} else {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
} catch {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
};
|
||||
|
||||
const navigateBasedOnRole = () => {
|
||||
const { device } = store.get();
|
||||
if (!device) return store.update({ screen: 'onboarding' });
|
||||
if (!device) {
|
||||
navigateToScreen('onboarding');
|
||||
return;
|
||||
}
|
||||
|
||||
// Default home screen based on role
|
||||
store.update({ screen: 'home' });
|
||||
navigateToScreen('home', { role: device.role });
|
||||
};
|
||||
|
||||
const startCameraPreview = async () => {
|
||||
@@ -1258,11 +1345,16 @@ const Actions = {
|
||||
|
||||
// Proceed
|
||||
if (store.get().deviceToken) {
|
||||
navigateBasedOnRole();
|
||||
const role = store.get().device?.role;
|
||||
if (multiPageMode && currentPageKey === 'auth') {
|
||||
if (navigateToScreen('home', { replace: true, role })) return;
|
||||
} else {
|
||||
navigateBasedOnRole();
|
||||
}
|
||||
connectSocket();
|
||||
startPolling();
|
||||
} else {
|
||||
store.update({ screen: 'onboarding' });
|
||||
if (navigateToScreen('onboarding')) return;
|
||||
}
|
||||
} catch (e) {
|
||||
// handled by API wrapper toast
|
||||
@@ -1325,6 +1417,7 @@ const Actions = {
|
||||
teardownPeerConnection();
|
||||
stopCameraPreview();
|
||||
localStorage.removeItem('mobileSimDevice');
|
||||
if (navigateToScreen('auth', { replace: true })) return;
|
||||
Toast.show('Signed Out', 'info');
|
||||
},
|
||||
|
||||
@@ -1470,7 +1563,7 @@ const Actions = {
|
||||
return;
|
||||
}
|
||||
|
||||
store.update({ screen: 'home' });
|
||||
if (navigateToScreen('home')) return;
|
||||
await Actions.requestStream(cameraDeviceId);
|
||||
},
|
||||
};
|
||||
@@ -1481,31 +1574,45 @@ const render = (state) => {
|
||||
// 1. Screen Visibility
|
||||
$$('section[id^="screen-"]').forEach(el => el.classList.add('hidden'));
|
||||
|
||||
const showSectionById = (id) => {
|
||||
const element = $(id);
|
||||
if (!element) return false;
|
||||
element.classList.remove('hidden');
|
||||
return true;
|
||||
};
|
||||
|
||||
if (state.screen === 'home') {
|
||||
const homeId = state.device?.role === 'camera' ? 'screen-home-camera' : 'screen-home-client';
|
||||
$(homeId).classList.remove('hidden');
|
||||
const preferredHomeId = state.device?.role === 'camera' ? 'screen-home-camera' : 'screen-home-client';
|
||||
if (!showSectionById(preferredHomeId)) {
|
||||
const fallbackHomeId = preferredHomeId === 'screen-home-camera' ? 'screen-home-client' : 'screen-home-camera';
|
||||
showSectionById(fallbackHomeId);
|
||||
}
|
||||
} else {
|
||||
$(`screen-${state.screen}`).classList.remove('hidden');
|
||||
showSectionById(`screen-${state.screen}`);
|
||||
}
|
||||
|
||||
// 2. Top Bar Status
|
||||
const statusDot = $('#connectionStatus .status-dot');
|
||||
const statusText = $('#connectionStatus span:last-child');
|
||||
if (state.socketConnected) {
|
||||
statusDot.className = 'status-dot status-online transition-colors duration-300';
|
||||
statusText.textContent = 'ONLINE';
|
||||
} else {
|
||||
statusDot.className = 'status-dot status-offline transition-colors duration-300';
|
||||
statusText.textContent = 'OFFLINE';
|
||||
if (statusDot && statusText) {
|
||||
if (state.socketConnected) {
|
||||
statusDot.className = 'status-dot status-online transition-colors duration-300';
|
||||
statusText.textContent = 'ONLINE';
|
||||
} else {
|
||||
statusDot.className = 'status-dot status-offline transition-colors duration-300';
|
||||
statusText.textContent = 'OFFLINE';
|
||||
}
|
||||
}
|
||||
|
||||
const authBadge = $('authStatusBadge');
|
||||
if (state.session?.user) {
|
||||
authBadge.textContent = state.session.user.email;
|
||||
authBadge.classList.add('text-blue-400');
|
||||
} else {
|
||||
authBadge.textContent = 'Signed Out';
|
||||
authBadge.classList.remove('text-blue-400');
|
||||
if (authBadge) {
|
||||
if (state.session?.user) {
|
||||
authBadge.textContent = state.session.user.email;
|
||||
authBadge.classList.add('text-blue-400');
|
||||
} else {
|
||||
authBadge.textContent = 'Signed Out';
|
||||
authBadge.classList.remove('text-blue-400');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Bottom Nav Visibility & State
|
||||
@@ -1513,36 +1620,41 @@ const render = (state) => {
|
||||
const unreadNotifications = state.motionNotifications.filter((notification) => !notification.isRead).length;
|
||||
updateNotificationDot(unreadNotifications > 0);
|
||||
|
||||
if (state.session && state.device) {
|
||||
nav.classList.remove('hidden');
|
||||
$$('.nav-btn').forEach(btn => {
|
||||
const target = btn.dataset.target;
|
||||
const isActive = target === state.screen || (target === 'home' && (state.screen === 'home-camera' || state.screen === 'home-client'));
|
||||
btn.setAttribute('data-active', isActive);
|
||||
// Optional: Force styles for Web/Desktop mode if needed, though data-active is handled by Tailwind variants
|
||||
});
|
||||
} else {
|
||||
nav.classList.add('hidden');
|
||||
if (nav) {
|
||||
if (state.session && state.device) {
|
||||
nav.classList.remove('hidden');
|
||||
$$('.nav-btn').forEach(btn => {
|
||||
const target = btn.dataset.target;
|
||||
const isActive = target === state.screen || (target === 'home' && (state.screen === 'home-camera' || state.screen === 'home-client'));
|
||||
btn.setAttribute('data-active', isActive);
|
||||
});
|
||||
} else {
|
||||
nav.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Camera Mode specifics
|
||||
if (state.device?.role === 'camera') {
|
||||
if (state.device?.role === 'camera' && state.screen === 'home') {
|
||||
const preview = $('cameraPreview');
|
||||
const offlineOverlay = $('cameraOfflineOverlay');
|
||||
const startMotionBtn = $('startMotionBtn');
|
||||
const endMotionBtn = $('endMotionBtn');
|
||||
|
||||
if (!preview || !offlineOverlay || !startMotionBtn || !endMotionBtn) return;
|
||||
|
||||
if (state.socketConnected) {
|
||||
offlineOverlay.classList.add('hidden');
|
||||
if (state.isMotionActive) {
|
||||
preview.classList.remove('bg-black/50');
|
||||
preview.classList.add('bg-red-900/20');
|
||||
$('startMotionBtn').classList.add('hidden');
|
||||
$('endMotionBtn').classList.remove('hidden');
|
||||
$('endMotionBtn').disabled = false;
|
||||
startMotionBtn.classList.add('hidden');
|
||||
endMotionBtn.classList.remove('hidden');
|
||||
endMotionBtn.disabled = false;
|
||||
} else {
|
||||
preview.classList.add('bg-black/50');
|
||||
preview.classList.remove('bg-red-900/20');
|
||||
$('startMotionBtn').classList.remove('hidden');
|
||||
$('endMotionBtn').classList.add('hidden');
|
||||
startMotionBtn.classList.remove('hidden');
|
||||
endMotionBtn.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
offlineOverlay.classList.remove('hidden');
|
||||
@@ -1552,10 +1664,11 @@ const render = (state) => {
|
||||
// 5. Client Mode Lists
|
||||
if (state.device?.role === 'client' && state.screen === 'home') {
|
||||
const list = $('linkedCamerasList');
|
||||
if (state.linkedCameras.length === 0) {
|
||||
list.innerHTML = `<div class="min-w-full text-center py-8 bg-gray-900/30 rounded-xl border border-dashed border-gray-800"><p class="text-gray-600 text-xs">No cameras linked yet</p></div>`;
|
||||
} else {
|
||||
list.innerHTML = state.linkedCameras.map(link => {
|
||||
if (list) {
|
||||
if (state.linkedCameras.length === 0) {
|
||||
list.innerHTML = `<div class="min-w-full text-center py-8 bg-gray-900/30 rounded-xl border border-dashed border-gray-800"><p class="text-gray-600 text-xs">No cameras linked yet</p></div>`;
|
||||
} else {
|
||||
list.innerHTML = state.linkedCameras.map(link => {
|
||||
const cameraName = getCameraLabel(link.cameraDeviceId, link.cameraName);
|
||||
const escapedCameraName = escapeHtml(cameraName);
|
||||
const cameraStatus = (link.cameraStatus || '').toLowerCase() === 'online' ? 'Online' : 'Offline';
|
||||
@@ -1632,65 +1745,67 @@ const render = (state) => {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}).join('');
|
||||
|
||||
// Show/hide main wrapper
|
||||
const viewerWrapper = $('clientStreamViewerWrapper');
|
||||
if (viewerWrapper) {
|
||||
if (state.activeCameraDeviceId) {
|
||||
viewerWrapper.classList.remove('hidden');
|
||||
const title = $('clientStreamViewerTitle');
|
||||
if (title) title.textContent = `Live Feed: ${getCameraLabel(state.activeCameraDeviceId)}`;
|
||||
} else {
|
||||
viewerWrapper.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide main wrapper
|
||||
const viewerWrapper = $('clientStreamViewerWrapper');
|
||||
if (viewerWrapper) {
|
||||
if (state.activeCameraDeviceId) {
|
||||
viewerWrapper.classList.remove('hidden');
|
||||
const title = $('clientStreamViewerTitle');
|
||||
if (title) title.textContent = `Live Feed: ${getCameraLabel(state.activeCameraDeviceId)}`;
|
||||
} else {
|
||||
viewerWrapper.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
// Find session ID for active camera if known
|
||||
let foundSessionId = state.activeStreamSessionId;
|
||||
const sessions = state.cameraSessions || {};
|
||||
if (!foundSessionId && sessions[state.activeCameraDeviceId]) {
|
||||
foundSessionId = sessions[state.activeCameraDeviceId];
|
||||
}
|
||||
|
||||
if (state.activeCameraDeviceId) {
|
||||
// Find session ID for active camera if known
|
||||
let foundSessionId = state.activeStreamSessionId;
|
||||
const sessions = state.cameraSessions || {};
|
||||
if (!foundSessionId && sessions[state.activeCameraDeviceId]) {
|
||||
foundSessionId = sessions[state.activeCameraDeviceId];
|
||||
}
|
||||
|
||||
const currentStream = foundSessionId ? remoteStreams.get(foundSessionId) : null;
|
||||
if (currentStream) {
|
||||
const videoEl = $('clientStreamVideo');
|
||||
if (videoEl && videoEl.srcObject !== currentStream) {
|
||||
videoEl.srcObject = currentStream;
|
||||
setClientStreamMode('video');
|
||||
$('clientLiveDot')?.classList.remove('hidden');
|
||||
// Only play if it's not already playing to prevent interruptions
|
||||
if (videoEl.paused) {
|
||||
void videoEl.play().catch(() => { });
|
||||
const currentStream = foundSessionId ? remoteStreams.get(foundSessionId) : null;
|
||||
if (currentStream) {
|
||||
const videoEl = $('clientStreamVideo');
|
||||
if (videoEl && videoEl.srcObject !== currentStream) {
|
||||
videoEl.srcObject = currentStream;
|
||||
setClientStreamMode('video');
|
||||
$('clientLiveDot')?.classList.remove('hidden');
|
||||
// Only play if it's not already playing to prevent interruptions
|
||||
if (videoEl.paused) {
|
||||
void videoEl.play().catch(() => { });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$('clientLiveDot')?.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
$('clientLiveDot')?.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
$('clientLiveDot')?.classList.add('hidden');
|
||||
}
|
||||
|
||||
const imageEl = $('clientStreamImage');
|
||||
if (imageEl && !imageEl.dataset.errorBound) {
|
||||
imageEl.dataset.errorBound = '1';
|
||||
imageEl.addEventListener('error', () => {
|
||||
const videoEl = $('clientStreamVideo');
|
||||
if (videoEl) {
|
||||
videoEl.classList.add('hidden');
|
||||
}
|
||||
setClientStreamMode('unavailable');
|
||||
});
|
||||
const imageEl = $('clientStreamImage');
|
||||
if (imageEl && !imageEl.dataset.errorBound) {
|
||||
imageEl.dataset.errorBound = '1';
|
||||
imageEl.addEventListener('error', () => {
|
||||
const videoEl = $('clientStreamVideo');
|
||||
if (videoEl) {
|
||||
videoEl.classList.add('hidden');
|
||||
}
|
||||
setClientStreamMode('unavailable');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const recList = $('recordingsList');
|
||||
if (state.recordings.length === 0) {
|
||||
recList.innerHTML = `<div class="text-center py-4 bg-gray-900/30 rounded-xl"><p class="text-gray-600 text-xs text-center">No recordings found</p></div>`;
|
||||
} else {
|
||||
recList.innerHTML = state.recordings.slice(0, 5).map(rec => `
|
||||
if (recList) {
|
||||
if (state.recordings.length === 0) {
|
||||
recList.innerHTML = `<div class="text-center py-4 bg-gray-900/30 rounded-xl"><p class="text-gray-600 text-xs text-center">No recordings found</p></div>`;
|
||||
} else {
|
||||
recList.innerHTML = state.recordings.slice(0, 5).map(rec => `
|
||||
<div class="flex items-center justify-between p-3 bg-gray-900/40 rounded-lg border border-white/5 hover:bg-gray-800 transition-colors">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-medium text-gray-300">${new Date(rec.createdAt).toLocaleString()}</span>
|
||||
@@ -1701,13 +1816,15 @@ const render = (state) => {
|
||||
</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.screen === 'activity') {
|
||||
const activityFeed = $('activityFeedList');
|
||||
if (state.motionNotifications.length === 0) {
|
||||
activityFeed.innerHTML = `
|
||||
if (activityFeed) {
|
||||
if (state.motionNotifications.length === 0) {
|
||||
activityFeed.innerHTML = `
|
||||
<div class="text-center py-10 opacity-50">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 mx-auto mb-2 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
@@ -1715,36 +1832,42 @@ const render = (state) => {
|
||||
<p class="text-sm text-gray-500">No notifications yet</p>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
activityFeed.innerHTML = state.motionNotifications.map((notification) => `
|
||||
} else {
|
||||
activityFeed.innerHTML = state.motionNotifications.map((notification) => `
|
||||
<button class="w-full text-left p-3 rounded-lg border border-white/5 ${notification.isRead ? 'bg-gray-900/30' : 'bg-blue-900/20'} motion-notification-btn" data-notification-id="${notification.id}" data-camera-device-id="${notification.cameraDeviceId}">
|
||||
<p class="text-xs font-medium text-gray-200">${notification.message}</p>
|
||||
<p class="text-[10px] text-gray-500 mt-1">${new Date(notification.createdAt).toLocaleString()}</p>
|
||||
</button>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Settings Screen
|
||||
if (state.session?.user && state.screen === 'settings') {
|
||||
$('profileName').textContent = state.session.user.name;
|
||||
$('profileEmail').textContent = state.session.user.email;
|
||||
$('profileInitials').textContent = state.session.user.name.charAt(0).toUpperCase();
|
||||
const profileName = $('profileName');
|
||||
const profileEmail = $('profileEmail');
|
||||
const profileInitials = $('profileInitials');
|
||||
if (profileName) profileName.textContent = state.session.user.name;
|
||||
if (profileEmail) profileEmail.textContent = state.session.user.email;
|
||||
if (profileInitials) profileInitials.textContent = state.session.user.name.charAt(0).toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
const addActivity = (type, msg) => {
|
||||
const list = $('activityFeedList');
|
||||
const item = document.createElement('div');
|
||||
item.className = 'p-3 rounded-lg bg-gray-900/40 border border-white/5 flex flex-col gap-1';
|
||||
item.innerHTML = `
|
||||
if (list) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'p-3 rounded-lg bg-gray-900/40 border border-white/5 flex flex-col gap-1';
|
||||
item.innerHTML = `
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase tracking-wider">${type}</span>
|
||||
<span class="text-[10px] text-gray-600">${new Date().toLocaleTimeString()}</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-300">${msg}</p>
|
||||
`;
|
||||
list.prepend(item);
|
||||
list.prepend(item);
|
||||
}
|
||||
|
||||
// Also update camera logs if applicable
|
||||
if ($('cameraLogs')) {
|
||||
@@ -1756,27 +1879,34 @@ const addActivity = (type, msg) => {
|
||||
|
||||
const updateNotificationDot = (show) => {
|
||||
const dot = $('notificationDot');
|
||||
if (!dot) return;
|
||||
if (show) dot.classList.remove('hidden');
|
||||
else dot.classList.add('hidden');
|
||||
};
|
||||
|
||||
// --- 6. Event Listeners ---
|
||||
|
||||
$('toggleAuthModeBtn').addEventListener('click', Actions.toggleAuthMode);
|
||||
$('signInBtn').addEventListener('click', Actions.submitAuth);
|
||||
$('registerBtn').addEventListener('click', Actions.registerDevice);
|
||||
$('loadSavedBtn').addEventListener('click', () => { /* Handle legacy loading if needed */ });
|
||||
const bind = (id, eventName, handler) => {
|
||||
const element = $(id);
|
||||
if (!element) return;
|
||||
element.addEventListener(eventName, handler);
|
||||
};
|
||||
|
||||
bind('toggleAuthModeBtn', 'click', Actions.toggleAuthMode);
|
||||
bind('signInBtn', 'click', Actions.submitAuth);
|
||||
bind('registerBtn', 'click', Actions.registerDevice);
|
||||
bind('loadSavedBtn', 'click', () => { /* Handle legacy loading if needed */ });
|
||||
$$('#screen-onboarding [data-role]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => Actions.selectRole(btn.dataset.role));
|
||||
});
|
||||
$('recordingsList').addEventListener('click', (event) => {
|
||||
bind('recordingsList', 'click', (event) => {
|
||||
const target = event.target.closest('.download-recording-btn');
|
||||
if (!target || target.disabled) return;
|
||||
const recordingId = target.dataset.recordingId;
|
||||
if (!recordingId) return;
|
||||
Actions.openRecording(recordingId);
|
||||
});
|
||||
$('activityFeedList').addEventListener('click', (event) => {
|
||||
bind('activityFeedList', 'click', (event) => {
|
||||
const target = event.target.closest('.motion-notification-btn');
|
||||
if (!target) return;
|
||||
const notificationId = target.dataset.notificationId;
|
||||
@@ -1857,31 +1987,31 @@ $$('.nav-btn').forEach(btn => {
|
||||
if (btn.dataset.target === 'activity') {
|
||||
markAllNotificationsRead();
|
||||
}
|
||||
store.update({ screen: btn.dataset.target });
|
||||
if (navigateToScreen(btn.dataset.target)) return;
|
||||
});
|
||||
});
|
||||
|
||||
// Camera Controls
|
||||
$('cameraGoOnlineBtn').addEventListener('click', async () => {
|
||||
bind('cameraGoOnlineBtn', 'click', async () => {
|
||||
if (store.get().device?.role === 'camera') {
|
||||
await startCameraPreview();
|
||||
}
|
||||
connectSocket();
|
||||
});
|
||||
$('startMotionBtn').addEventListener('click', Actions.startMotion);
|
||||
$('endMotionBtn').addEventListener('click', Actions.endMotion);
|
||||
bind('startMotionBtn', 'click', Actions.startMotion);
|
||||
bind('endMotionBtn', 'click', Actions.endMotion);
|
||||
|
||||
// Client Controls
|
||||
$('linkCameraBtn').addEventListener('click', Actions.linkCamera);
|
||||
$('refreshClientBtn').addEventListener('click', startPolling);
|
||||
bind('linkCameraBtn', 'click', Actions.linkCamera);
|
||||
bind('refreshClientBtn', 'click', startPolling);
|
||||
|
||||
// Settings
|
||||
$('signOutBtn').addEventListener('click', Actions.signOut);
|
||||
$('clearActivityBtn').addEventListener('click', () => {
|
||||
bind('signOutBtn', 'click', Actions.signOut);
|
||||
bind('clearActivityBtn', 'click', () => {
|
||||
store.update({ motionNotifications: [] });
|
||||
});
|
||||
$('recordingModalCloseBtn').addEventListener('click', Actions.closeRecordingModal);
|
||||
$('recordingModal').addEventListener('click', (event) => {
|
||||
bind('recordingModalCloseBtn', 'click', Actions.closeRecordingModal);
|
||||
bind('recordingModal', 'click', (event) => {
|
||||
if (event.target === $('recordingModal')) {
|
||||
Actions.closeRecordingModal();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user