1288 lines
72 KiB
PHP
1288 lines
72 KiB
PHP
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dashboard Admin</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
<script src="{{ asset('js/notification-constants.js') }}"></script>
|
|
<script src="{{ asset('js/notifications.js') }}"></script>
|
|
<link href="{{ asset('css/notifications.css') }}" rel="stylesheet">
|
|
</head>
|
|
<body class="bg-gray-100 dark:bg-gray-900" x-data="{ isNotificationsPanelOpen: false }">
|
|
<!-- Navbar -->
|
|
<nav class="fixed top-0 left-0 right-0 z-30 bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
|
<div class="px-4 py-3 lg:px-6">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<img src="{{ asset('asset/logo.png') }}" alt="SmartCab Logo" class="h-8 w-8 lg:h-10 lg:w-10 mr-2">
|
|
<div class="flex flex-col">
|
|
<span class="text-base lg:text-xl font-semibold dark:text-white">SMARTCAB</span>
|
|
<span class="text-xs lg:text-sm font-medium dark:text-white">Smart Cabin Security & Monitoring</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Right Menu -->
|
|
<div class="flex items-center gap-2 lg:hidden">
|
|
<a href="{{route('ai.chat')}}" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
AI
|
|
</a>
|
|
<!-- Notification Button Mobile -->
|
|
<button
|
|
@click="isNotificationsPanelOpen = true"
|
|
class="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white"
|
|
>
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Mobile Menu Button -->
|
|
<button id="mobileMenuBtn" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Desktop Menu -->
|
|
<div class="hidden lg:flex items-center gap-4">
|
|
<!-- Menu Items -->
|
|
<a href="{{route('ai.chat')}}" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
AI
|
|
</a>
|
|
|
|
|
|
<!-- Notification Button -->
|
|
<button
|
|
@click="isNotificationsPanelOpen = true"
|
|
class="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white"
|
|
>
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Dark Mode Toggle -->
|
|
<button id="darkModeToggle" class="p-2 text-gray-500 dark:text-gray-400">
|
|
<svg class="w-6 h-6 dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
|
|
</svg>
|
|
<svg class="w-6 h-6 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Profile Dropdown -->
|
|
<div class="relative">
|
|
<button class="flex items-center text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
<img class="w-8 h-8 rounded-full" src="{{ asset('asset/foto.jpg') }}" alt="profile">
|
|
<span class="ml-2 hidden lg:block"></span>
|
|
</button>
|
|
<div class="absolute right-0 hidden mt-2 w-48 bg-white rounded-md shadow-lg dark:bg-gray-700" id="profileMenu">
|
|
<div class="px-4 py-3 border-b dark:border-gray-600">
|
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">Hi!Vicky</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">vickynando12@gmail.com</p>
|
|
</div>
|
|
<a href="{{route('profile')}}" class="block px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-600">Your Profile</a>
|
|
<a href="{{route('logout')}}" class="block px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-600">Logout</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Menu -->
|
|
<div id="mobileMenu" class="hidden lg:hidden mt-4 space-y-4">
|
|
<!-- Profile Section Mobile -->
|
|
<div class="border-b pb-4 dark:border-gray-700">
|
|
<div class="flex items-center space-x-3 px-4">
|
|
<img class="w-10 h-10 rounded-full" src="{{ asset('asset/foto.jpg') }}" alt="profile">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">Hi!Vicky</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">vickynando12@gmail.com</p>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4 space-y-2">
|
|
<a href="{{route('profile')}}" class="block px-4 py-2 text-gray-600 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">Your Profile</a>
|
|
<a href="{{route('logout')}}" class="block px-4 py-2 text-gray-600 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">Logout</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col space-y-4 px-4">
|
|
<a href="#" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">Dashboard</a>
|
|
{{-- <a href="#" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">Chat</a> --}}
|
|
<!-- Dark Mode Toggle Mobile -->
|
|
<button id="mobileDarkModeToggle" class="flex items-center justify-between w-full text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
<span>Dark Mode</span>
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Main Content -->
|
|
<main class="pt-20 p-4 dark:bg-gray-900">
|
|
<div class="max-w-7xl mx-auto transition-colors duration-200">
|
|
<!-- Info Cards -->
|
|
<div class="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
|
<!-- Temperature Card -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-2 sm:p-3 mr-3 sm:mr-4 bg-red-100 rounded-full">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Temperature</p>
|
|
<p class="text-base sm:text-lg font-semibold text-gray-700 dark:text-gray-200" id="temp-value">{{ $temperature }}°C</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Wemos D1 Mini Status -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-amber-100 rounded-full">
|
|
<img src="{{ asset('asset/wemos.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Wemos D1 Mini</p>
|
|
<p class="text-sm sm:text-lg font-semibold" id="wemos-status">
|
|
{{ $systemWemos == 'Device Online' ? 'Online' : 'Offline' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NodeMCU ESP8266 Status -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-amber-100 rounded-full">
|
|
<img src="{{ asset('asset/esp.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">NodeMCU ESP8266</p>
|
|
<p class="text-sm sm:text-lg font-semibold" id="esp-status">
|
|
{{ $systemESP == 'Device online' ? 'Online' : 'Offline' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Security Status -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-green-100 rounded-full">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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 00-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>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Security Status</p>
|
|
<p class="text-sm sm:text-lg font-semibold" id="security-status">
|
|
{{ $status }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile-specific order for remaining sections -->
|
|
<div class="md:hidden mt-4">
|
|
<!-- Door Status Section for Mobile -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6 mb-4">
|
|
<div class="flex items-center mb-6">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-green-100 rounded-full">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Door Status</p>
|
|
<p class="text-sm sm:text-lg font-semibold" id="door-status">
|
|
{{ $servo_status }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Akses Terakhir</p>
|
|
<p class="text-sm sm:text-lg font-semibold text-green-500" id="last-access">{{ $last_access }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Card ID terdeteksi</p>
|
|
<p class="text-sm sm:text-lg font-semibold text-green-500" id="status-device">{{ $status_device }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Indicators for Mobile - Dipindahkan setelah door status -->
|
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
|
<!-- Exhaust Fan Status -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-3 sm:p-4">
|
|
<div class="flex items-center">
|
|
<div class="p-2 sm:p-3 mr-3 sm:mr-4 bg-green-100 rounded-full w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 12c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.071 4.929a10 10 0 10-14.142 14.142 10 10 0 0014.142-14.142z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v8M8 12h8"/>
|
|
</svg>
|
|
</div>
|
|
<div class="flex-1">
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Exhaust fan</p>
|
|
<p class="text-sm sm:text-lg font-semibold" id="fan-status">
|
|
{{ $fan == 'ON' ? 'Aktif' : 'Mati' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alarm System Status -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-3 sm:p-4">
|
|
<div class="flex items-center">
|
|
<div class="p-2 sm:p-3 mr-3 sm:mr-4 bg-red-100 rounded-full w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="flex-1">
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Alarm System</p>
|
|
<p class="text-sm sm:text-lg font-semibold" id="motion-status">
|
|
{{ $motion == 'clear' ? 'Aman' : 'Terdeteksi' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Device and Sensor Information for Mobile -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6 mb-4">
|
|
<h3 class="text-base sm:text-lg font-semibold text-gray-800 dark:text-gray-200 mb-4">
|
|
<span class="inline-block mr-2">
|
|
<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
</span>
|
|
Informasi perangkat dan sensor
|
|
</h3>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<!-- DHT 11 -->
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-cyan-100 rounded-full flex items-center justify-center w-10 h-10 sm:w-14 sm:h-14">
|
|
<img src="{{ asset('asset/dht.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="DHT11">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">DHT 11</p>
|
|
<p class="text-sm sm:text-lg font-semibold {{ $dhtStatus == 'connected' ? 'text-green-500' : 'text-red-500' }}">{{ $dhtStatus == 'connected' ? 'Connected' : 'Disconnected' }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MPU 6050 -->
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-cyan-100 rounded-full flex items-center justify-center w-10 h-10 sm:w-14 sm:h-14">
|
|
<img src="{{ asset('asset/mpu.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="MPU6050">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">MPU 6050</p>
|
|
<p class="text-sm sm:text-lg font-semibold {{ $mpuStatus == 'connected' ? 'text-green-500' : 'text-red-500' }}">{{ $mpuStatus == 'connected' ? 'Connected' : 'Disconnected' }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Servo MG996r -->
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-cyan-100 rounded-full flex items-center justify-center w-10 h-10 sm:w-14 sm:h-14">
|
|
<img src="{{ asset('asset/servo.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="Servo">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Servo MG996r</p>
|
|
<p class="text-sm sm:text-lg font-semibold {{ $servoStatus == 'Connected' ? 'text-green-500' : 'text-red-500' }}">{{ $servoStatus }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RFID Reader -->
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-cyan-100 rounded-full flex items-center justify-center w-10 h-10 sm:w-14 sm:h-14">
|
|
<img src="{{ asset('asset/rfid.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="RFID">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">RFID Reader</p>
|
|
<p class="text-sm sm:text-lg font-semibold {{ $rfidStatus == 'Connected' ? 'text-green-500' : 'text-red-500' }}">{{ $rfidStatus }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Desktop layout (hidden on mobile) -->
|
|
<div class="hidden md:block">
|
|
<!-- Device Information Section -->
|
|
<div class="mt-6 sm:mt-8 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<!-- Device and Sensor Information -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6">
|
|
<h3 class="text-base sm:text-lg font-semibold text-gray-800 dark:text-gray-200 mb-4">
|
|
<span class="inline-block mr-2">
|
|
<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
</span>
|
|
Informasi perangkat dan sensor
|
|
</h3>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<!-- DHT 11 -->
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-cyan-100 rounded-full flex items-center justify-center w-10 h-10 sm:w-14 sm:h-14">
|
|
<img src="{{ asset('asset/dht.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="DHT11">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">DHT 11</p>
|
|
<p class="text-sm sm:text-lg font-semibold {{ $dhtStatus == 'connected' ? 'text-green-500' : 'text-red-500' }}">{{ $dhtStatus == 'connected' ? 'Connected' : 'Disconnected' }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MPU 6050 -->
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-cyan-100 rounded-full flex items-center justify-center w-10 h-10 sm:w-14 sm:h-14">
|
|
<img src="{{ asset('asset/mpu.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="MPU6050">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">MPU 6050</p>
|
|
<p class="text-sm sm:text-lg font-semibold {{ $mpuStatus == 'connected' ? 'text-green-500' : 'text-red-500' }}">{{ $mpuStatus == 'connected' ? 'Connected' : 'Disconnected' }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Servo MG996r -->
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-cyan-100 rounded-full flex items-center justify-center w-10 h-10 sm:w-14 sm:h-14">
|
|
<img src="{{ asset('asset/servo.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="Servo">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Servo MG996r</p>
|
|
<p class="text-sm sm:text-lg font-semibold {{ $servoStatus == 'Connected' ? 'text-green-500' : 'text-red-500' }}">{{ $servoStatus }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RFID Reader -->
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-cyan-100 rounded-full flex items-center justify-center w-10 h-10 sm:w-14 sm:h-14">
|
|
<img src="{{ asset('asset/rfid.png') }}" class="w-6 h-6 sm:w-10 sm:h-10" alt="RFID">
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">RFID Reader</p>
|
|
<p class="text-sm sm:text-lg font-semibold {{ $rfidStatus == 'Connected' ? 'text-green-500' : 'text-red-500' }}">{{ $rfidStatus }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Door Status Section -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6">
|
|
<div class="flex items-center mb-6">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-green-100 rounded-full">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v10a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Door Status</p>
|
|
<p class="text-sm sm:text-lg font-semibold" id="door-status">
|
|
{{ $servo_status }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Akses Terakhir</p>
|
|
<p class="text-sm sm:text-lg font-semibold text-green-500" id="last-access">{{ $last_access }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Card ID terdeteksi</p>
|
|
<p class="text-sm sm:text-lg font-semibold text-green-500" id="status-device">{{ $status_device }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bottom Section -->
|
|
<div class="mt-6 sm:mt-8 grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<!-- Information Box -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-yellow-100 rounded-full mr-4">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-base sm:text-lg font-semibold text-gray-800 dark:text-gray-200">Keterangan</h3>
|
|
</div>
|
|
<div class="mt-4 text-xs sm:text-sm text-gray-600 dark:text-gray-400">
|
|
<p><b>⚠︎ Data perangkat Wemos D1 Mini dan NodeMCU ESP8266 memiliki jeda 1 menit. Saat pengecekan, tunggu 1 menit untuk melihat perubahan data.</b></p>
|
|
<p>•Jika anda restart wemos d1 mini maka perangkat RFID dan servo akan ikut dimulai ulang / di restart</p>
|
|
<p>•Jika anda restart NodeMCU Esp8266 maka perangkat kipas,alarm,DHT 11,dan Mpu6050 akan ikut di mulai ulang</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Device Control and Status Section -->
|
|
<div class="col-span-1 md:col-span-2">
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<!-- Device Control Section -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 sm:p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center">
|
|
<div class="p-3 sm:p-4 mr-3 sm:mr-4 bg-blue-100 rounded-full">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
</div>
|
|
<h3 class="text-base sm:text-lg font-semibold text-gray-800 dark:text-gray-200">Mulai Ulang Perangkat</h3>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<!-- Wemos D1 mini -->
|
|
<button id="restartWemos" class="flex flex-col items-center justify-center p-3 sm:p-4 bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-lg transition-all duration-200 shadow-md hover:shadow-xl dark:shadow-gray-900">
|
|
<img src="{{ asset('asset/wemos.png') }}" class="w-12 h-12 sm:w-16 sm:h-16 mb-2 sm:mb-3" alt="Wemos D1 mini">
|
|
<p class="text-sm sm:text-base text-gray-700 dark:text-gray-200 text-center">Wemos D1 mini</p>
|
|
</button>
|
|
|
|
<!-- NodeMCU ESP8266 -->
|
|
<button id="restartESP" class="flex flex-col items-center justify-center p-3 sm:p-4 bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-lg transition-all duration-200 shadow-md hover:shadow-xl dark:shadow-gray-900">
|
|
<img src="{{ asset('asset/esp.png') }}" class="w-12 h-12 sm:w-16 sm:h-16 mb-2 sm:mb-3" alt="NodeMCU ESP8266">
|
|
<p class="text-sm sm:text-base text-gray-700 dark:text-gray-200 text-center">NodeMCU ESP8266</p>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Indicators - Hanya tampil di desktop -->
|
|
<div class="grid grid-rows-2 gap-4 hidden md:grid">
|
|
<!-- Exhaust Fan Status -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-3 sm:p-4">
|
|
<div class="flex items-center">
|
|
<div class="p-2 sm:p-3 mr-3 sm:mr-4 bg-green-100 rounded-full w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 12c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.071 4.929a10 10 0 10-14.142 14.142 10 10 0 0014.142-14.142z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v8M8 12h8"/>
|
|
</svg>
|
|
</div>
|
|
<div class="flex-1">
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Exhaust fan</p>
|
|
<p class="text-sm sm:text-lg font-semibold text-green-500" id="fan-status">Aktif</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alarm System Status -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-3 sm:p-4">
|
|
<div class="flex items-center">
|
|
<div class="p-2 sm:p-3 mr-3 sm:mr-4 bg-red-100 rounded-full w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center">
|
|
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="flex-1">
|
|
<p class="text-xs sm:text-sm font-medium text-gray-600 dark:text-gray-400">Alarm System</p>
|
|
<p class="text-sm sm:text-lg font-semibold text-gray-500" id="motion-status">Dinonaktifkan</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Include notification panel component -->
|
|
@include('components.notification-panel')
|
|
|
|
<!-- Floating Action Button -->
|
|
<div class="fixed bottom-0 right-0 z-50 mb-5 mr-5">
|
|
<button class="flex h-14 w-14 items-center justify-center rounded-full bg-blue-500 text-white shadow-lg hover:bg-blue-600" onclick="toggleModal()">
|
|
<svg class="w-6 h-6 animate-spin" style="animation-duration: 3s;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Modal -->
|
|
<div id="controlModal" class="fixed inset-0 z-50 hidden">
|
|
<div class="fixed inset-0 bg-black bg-opacity-50" onclick="toggleModal()"></div>
|
|
<div class="fixed bottom-24 right-5 w-72 rounded-lg bg-white p-4 shadow-xl dark:bg-gray-800">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">Security Controls</h3>
|
|
|
|
<!-- Security Toggle -->
|
|
<div class="flex items-center justify-between mb-4">
|
|
<span class="text-gray-700 dark:text-gray-200">Security System</span>
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" id="securityToggle" class="sr-only peer" onchange="toggleSecurity(this)">
|
|
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Firebase SDK v8 -->
|
|
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
|
|
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js"></script>
|
|
|
|
<script>
|
|
// Firebase configuration
|
|
const firebaseConfig = {
|
|
apiKey: "AIzaSyCr8xNQIsPpIUMGIR9wEGmG7hgMDKf2H5I",
|
|
authDomain: "smartcab-8bb42.firebaseapp.com",
|
|
databaseURL: "https://smartcab-8bb42-default-rtdb.firebaseio.com",
|
|
projectId: "smartcab-8bb42",
|
|
storageBucket: "smartcab-8bb42.firebasestorage.app",
|
|
messagingSenderId: "539751617121",
|
|
appId: "1:539751617121:web:3a899309fdb5e29efa9020",
|
|
measurementId: "G-BQPQLLCJTR"
|
|
};
|
|
|
|
// Inisialisasi Firebase
|
|
if (!firebase.apps.length) {
|
|
firebase.initializeApp(firebaseConfig);
|
|
}
|
|
|
|
const database = firebase.database();
|
|
|
|
// Tambahkan variabel untuk melacak status restart ESP
|
|
let espRestartRequested = false;
|
|
|
|
// Listener untuk ESP status
|
|
database.ref('logs/systemESP').on('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('ESP status received:', status);
|
|
|
|
let statusText;
|
|
let statusClass;
|
|
|
|
if (status === 'Device online') {
|
|
statusText = 'Online';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-green-500';
|
|
} else if (status === 'Device auto-restarting...' || status === 'Device restarting by command...') {
|
|
statusText = 'Restarting...';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
} else {
|
|
statusText = 'Offline';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-red-500';
|
|
}
|
|
|
|
document.querySelectorAll('#esp-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
}, (error) => {
|
|
console.error('Error getting ESP status:', error);
|
|
});
|
|
|
|
// Tambahkan listener khusus untuk restart ESP
|
|
database.ref('control/restartESP').on('value', (snapshot) => {
|
|
const restartValue = snapshot.val();
|
|
console.log('ESP restart value:', restartValue);
|
|
|
|
// Jika nilai restart adalah true, segera ubah status menjadi "Restarting..."
|
|
if (restartValue === true || restartValue === "true") {
|
|
document.querySelectorAll('#esp-status').forEach(el => {
|
|
el.textContent = 'Restarting...';
|
|
el.className = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
});
|
|
|
|
// Pertahankan status "Restarting..." selama 30 detik
|
|
setTimeout(() => {
|
|
// Cek status saat ini sebelum mengubahnya kembali
|
|
database.ref('logs/systemESP').once('value', (snapshot) => {
|
|
const currentStatus = snapshot.val();
|
|
if (currentStatus !== 'Device online') {
|
|
document.querySelectorAll('#esp-status').forEach(el => {
|
|
el.textContent = 'Restarting...';
|
|
el.className = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
});
|
|
}
|
|
});
|
|
}, 30000);
|
|
}
|
|
});
|
|
|
|
// Fungsi untuk memperbarui status ESP di UI
|
|
function updateESPStatus(status) {
|
|
let statusText;
|
|
let statusClass;
|
|
|
|
if (espRestartRequested) {
|
|
statusText = 'Restarting...';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
} else if (status === 'Device online') {
|
|
statusText = 'Online';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-green-500';
|
|
} else if (status === 'Device auto-restarting...' || status === 'Device restarting by command...') {
|
|
statusText = 'Restarting...';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
} else {
|
|
statusText = 'Offline';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-red-500';
|
|
}
|
|
|
|
document.querySelectorAll('#esp-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
}
|
|
|
|
// Fungsi untuk memaksa perubahan pada DOM
|
|
function forceUpdate(id, text, className = null) {
|
|
try {
|
|
// Cari elemen dengan ID
|
|
const element = document.getElementById(id);
|
|
if (!element) {
|
|
console.error(`Element with id ${id} not found`);
|
|
return;
|
|
}
|
|
|
|
// Buat elemen baru dengan konten yang sama
|
|
const newElement = element.cloneNode(false);
|
|
newElement.innerHTML = text;
|
|
if (className) {
|
|
newElement.className = className;
|
|
}
|
|
|
|
// Ganti elemen lama dengan yang baru
|
|
element.parentNode.replaceChild(newElement, element);
|
|
|
|
console.log(`Element ${id} forcefully updated with: ${text}`);
|
|
} catch (error) {
|
|
console.error(`Error updating element ${id}:`, error);
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk memperbarui semua data dari snapshot
|
|
function updateAllFromSnapshot(path, data) {
|
|
console.log(`Updating all from ${path}:`, data);
|
|
|
|
if (path === 'smartcab') {
|
|
if (data.servo_status) {
|
|
document.querySelectorAll('#door-status').forEach(el => {
|
|
el.textContent = data.servo_status;
|
|
el.className = `text-sm sm:text-lg font-semibold ${
|
|
data.servo_status === 'Terkunci' ? 'text-green-500' : 'text-red-500'
|
|
}`;
|
|
});
|
|
}
|
|
|
|
if (data.last_access) {
|
|
document.querySelectorAll('#last-access').forEach(el => {
|
|
el.textContent = data.last_access;
|
|
});
|
|
}
|
|
|
|
if (data.status_device) {
|
|
document.querySelectorAll('#status-device').forEach(el => {
|
|
el.textContent = data.status_device;
|
|
});
|
|
}
|
|
}
|
|
else if (path === 'dht11') {
|
|
if (data.temperature) {
|
|
document.querySelectorAll('#temp-value').forEach(el => {
|
|
el.textContent = `${data.temperature}°C`;
|
|
});
|
|
}
|
|
}
|
|
else if (path === 'logs') {
|
|
if (data.systemWemos) {
|
|
const isOnline = data.systemWemos === 'Device Online';
|
|
const statusText = isOnline ? 'Online' : 'Offline';
|
|
const statusClass = `text-sm sm:text-lg font-semibold ${
|
|
isOnline ? 'text-green-500' : 'text-red-500'
|
|
}`;
|
|
document.querySelectorAll('#wemos-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
}
|
|
|
|
if (data.systemESP) {
|
|
const isOnline = data.systemESP === 'Device online';
|
|
const statusText = isOnline ? 'Online' : 'Offline';
|
|
const statusClass = `text-sm sm:text-lg font-semibold ${
|
|
isOnline ? 'text-green-500' : 'text-red-500'
|
|
}`;
|
|
document.querySelectorAll('#esp-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
}
|
|
}
|
|
else if (path === 'security') {
|
|
if (data.status) {
|
|
document.querySelectorAll('#security-status').forEach(el => {
|
|
el.textContent = data.status;
|
|
});
|
|
}
|
|
|
|
if (data.fan) {
|
|
const statusText = data.fan === 'ON' ? 'Aktif' : 'Mati';
|
|
const statusClass = `text-sm sm:text-lg font-semibold ${
|
|
data.fan === 'ON' ? 'text-green-500' : 'text-red-500'
|
|
}`;
|
|
document.querySelectorAll('#fan-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
}
|
|
|
|
if (data.motion) {
|
|
const statusText = data.motion === 'clear' ? 'Aman' : 'Terdeteksi';
|
|
const statusClass = `text-sm sm:text-lg font-semibold ${
|
|
data.motion === 'clear' ? 'text-green-500' : 'text-red-500'
|
|
}`;
|
|
document.querySelectorAll('#motion-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk setup listeners dengan pendekatan baru
|
|
function setupListeners() {
|
|
console.log('Setting up all listeners with new approach...');
|
|
|
|
// Hapus semua listener yang ada
|
|
database.ref().off();
|
|
|
|
// Listener untuk smartcab dengan pendekatan langsung
|
|
database.ref('smartcab').on('value', (snapshot) => {
|
|
const data = snapshot.val();
|
|
console.log('Smartcab data received:', data);
|
|
|
|
if (data) {
|
|
// Update door status
|
|
if (data.servo_status) {
|
|
document.querySelectorAll('#door-status').forEach(el => {
|
|
el.textContent = data.servo_status;
|
|
el.className = `text-sm sm:text-lg font-semibold ${
|
|
data.servo_status === 'Terkunci' ? 'text-green-500' : 'text-red-500'
|
|
}`;
|
|
});
|
|
}
|
|
|
|
// Update last access
|
|
if (data.last_access) {
|
|
document.querySelectorAll('#last-access').forEach(el => {
|
|
el.textContent = data.last_access;
|
|
});
|
|
}
|
|
|
|
// Update card ID
|
|
if (data.status_device) {
|
|
document.querySelectorAll('#status-device').forEach(el => {
|
|
el.textContent = data.status_device;
|
|
});
|
|
}
|
|
}
|
|
}, (error) => {
|
|
console.error('Error getting smartcab data:', error);
|
|
});
|
|
|
|
// Listener untuk temperature
|
|
database.ref('dht11/temperature').on('value', (snapshot) => {
|
|
const temp = snapshot.val();
|
|
console.log('Temperature data received:', temp);
|
|
if (temp) {
|
|
document.querySelectorAll('#temp-value').forEach(el => {
|
|
el.textContent = `${temp}°C`;
|
|
});
|
|
}
|
|
}, (error) => {
|
|
console.error('Error getting temperature data:', error);
|
|
});
|
|
|
|
// Listener untuk Wemos status
|
|
database.ref('logs/systemWemos').on('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('Wemos status received:', status);
|
|
|
|
let statusText;
|
|
let statusClass;
|
|
|
|
if (status === 'Device Online') {
|
|
statusText = 'Online';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-green-500';
|
|
} else if (status === 'Device auto-restarting...') {
|
|
statusText = 'Restarting...';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
} else {
|
|
statusText = 'Offline';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-red-500';
|
|
}
|
|
|
|
document.querySelectorAll('#wemos-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
}, (error) => {
|
|
console.error('Error getting Wemos status:', error);
|
|
});
|
|
|
|
// Listener untuk ESP status
|
|
database.ref('logs/systemESP').on('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('ESP status received:', status);
|
|
|
|
let statusText;
|
|
let statusClass;
|
|
|
|
if (status === 'Device online') {
|
|
statusText = 'Online';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-green-500';
|
|
} else if (status === 'Device auto-restarting...' || status === 'Device restarting by command...') {
|
|
statusText = 'Restarting...';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
} else {
|
|
statusText = 'Offline';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-red-500';
|
|
}
|
|
|
|
document.querySelectorAll('#esp-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
}, (error) => {
|
|
console.error('Error getting ESP status:', error);
|
|
});
|
|
|
|
// Listener untuk security status
|
|
database.ref('security/status').on('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('Security status received:', status);
|
|
|
|
document.querySelectorAll('#security-status').forEach(el => {
|
|
el.textContent = status;
|
|
});
|
|
}, (error) => {
|
|
console.error('Error getting security status:', error);
|
|
});
|
|
|
|
// Listener untuk fan status
|
|
database.ref('security/fan').on('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('Fan status received:', status);
|
|
|
|
const statusText = status === 'ON' ? 'Aktif' : 'Mati';
|
|
|
|
document.querySelectorAll('#fan-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = `text-sm sm:text-lg font-semibold ${
|
|
status === 'ON' ? 'text-green-500' : 'text-red-500'
|
|
}`;
|
|
});
|
|
}, (error) => {
|
|
console.error('Error getting fan status:', error);
|
|
});
|
|
|
|
// Listener untuk motion status
|
|
database.ref('security/motion').on('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('Motion status received:', status);
|
|
|
|
let statusText, statusClass;
|
|
|
|
if (status === 'clear') {
|
|
statusText = 'Aman';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-green-500';
|
|
} else if (status === 'detected') {
|
|
statusText = 'Terdeteksi';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-red-500';
|
|
} else if (status === 'disabled') {
|
|
statusText = 'Dinonaktifkan';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-gray-500';
|
|
}
|
|
|
|
document.querySelectorAll('#motion-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
}, (error) => {
|
|
console.error('Error getting motion status:', error);
|
|
});
|
|
|
|
console.log('All listeners setup complete with new approach');
|
|
}
|
|
|
|
// Fungsi untuk toggle modal
|
|
function toggleModal() {
|
|
const modal = document.getElementById('controlModal');
|
|
if (modal) {
|
|
modal.classList.toggle('hidden');
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk restart perangkat
|
|
function setupRestartButtons() {
|
|
const restartWemos = document.getElementById('restartWemos');
|
|
if (restartWemos) {
|
|
restartWemos.addEventListener('click', function() {
|
|
if (confirm('Apakah Anda yakin ingin me-restart Wemos D1 Mini?')) {
|
|
// Perbarui status restart di UI terlebih dahulu
|
|
document.querySelectorAll('#wemos-status').forEach(el => {
|
|
el.textContent = 'Restarting...';
|
|
el.className = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
});
|
|
|
|
// Perbarui status di Firebase
|
|
database.ref('logs/systemWemos').set('Device auto-restarting...')
|
|
.then(() => {
|
|
console.log('Status Wemos diperbarui ke restarting');
|
|
|
|
// Kirim perintah restart
|
|
return database.ref('control/restartWemos').set(true);
|
|
})
|
|
.then(() => {
|
|
alert('Perintah restart Wemos D1 Mini berhasil dikirim');
|
|
|
|
// Reset perintah restart setelah 5 detik
|
|
setTimeout(() => {
|
|
database.ref('control/restartWemos').set(false);
|
|
}, 5000);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error restarting Wemos:', error);
|
|
alert('Gagal mengirim perintah restart');
|
|
|
|
// Kembalikan status jika gagal
|
|
database.ref('logs/systemWemos').once('value', (snapshot) => {
|
|
const previousStatus = snapshot.val();
|
|
if (previousStatus === 'Device auto-restarting...') {
|
|
database.ref('logs/systemWemos').set('Device Online');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
const restartESP = document.getElementById('restartESP');
|
|
if (restartESP) {
|
|
restartESP.addEventListener('click', function() {
|
|
if (confirm('Apakah Anda yakin ingin me-restart NodeMCU ESP8266?')) {
|
|
// Perbarui status restart di UI terlebih dahulu
|
|
document.querySelectorAll('#esp-status').forEach(el => {
|
|
el.textContent = 'Restarting...';
|
|
el.className = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
});
|
|
|
|
// Perbarui status di Firebase
|
|
database.ref('logs/systemESP').set('Device restarting by command...')
|
|
.then(() => {
|
|
console.log('Status ESP diperbarui ke restarting');
|
|
|
|
// Kirim perintah restart
|
|
return database.ref('control/restartESP').set(true);
|
|
})
|
|
.then(() => {
|
|
alert('Perintah restart NodeMCU ESP8266 berhasil dikirim');
|
|
|
|
// Reset perintah restart setelah 5 detik
|
|
setTimeout(() => {
|
|
database.ref('control/restartESP').set(false);
|
|
}, 5000);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error restarting ESP:', error);
|
|
alert('Gagal mengirim perintah restart');
|
|
|
|
// Kembalikan status jika gagal
|
|
database.ref('logs/systemESP').once('value', (snapshot) => {
|
|
const previousStatus = snapshot.val();
|
|
if (previousStatus === 'Device restarting by command...') {
|
|
database.ref('logs/systemESP').set('Device online');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk setup mobile menu
|
|
function setupMobileMenu() {
|
|
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
|
|
const mobileMenu = document.getElementById('mobileMenu');
|
|
|
|
if (mobileMenuBtn && mobileMenu) {
|
|
mobileMenuBtn.addEventListener('click', () => {
|
|
mobileMenu.classList.toggle('hidden');
|
|
});
|
|
}
|
|
|
|
const profileButton = document.querySelector('button img');
|
|
const profileMenu = document.getElementById('profileMenu');
|
|
|
|
if (profileButton && profileMenu) {
|
|
profileButton.addEventListener('click', () => {
|
|
profileMenu.classList.toggle('hidden');
|
|
});
|
|
|
|
document.addEventListener('click', (e) => {
|
|
if (!profileButton.contains(e.target) && !profileMenu.contains(e.target)) {
|
|
profileMenu.classList.add('hidden');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Tambahkan fungsi untuk memastikan UI diperbarui secara real-time
|
|
function forceRefreshDeviceStatus() {
|
|
// Update ESP8266 status dari Firebase
|
|
database.ref('logs/systemESP').once('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('Forcing refresh of ESP status:', status);
|
|
|
|
let statusText, statusClass;
|
|
|
|
if (status === 'Device online') {
|
|
statusText = 'Online';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-green-500';
|
|
} else if (status === 'Device auto-restarting...' || status === 'Device restarting by command...') {
|
|
statusText = 'Restarting...';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
} else {
|
|
statusText = 'Offline';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-red-500';
|
|
}
|
|
|
|
document.querySelectorAll('#esp-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
});
|
|
|
|
// Update Wemos status dari Firebase
|
|
database.ref('logs/systemWemos').once('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('Forcing refresh of Wemos status:', status);
|
|
|
|
let statusText, statusClass;
|
|
|
|
if (status === 'Device Online') {
|
|
statusText = 'Online';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-green-500';
|
|
} else if (status === 'Device auto-restarting...') {
|
|
statusText = 'Restarting...';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-yellow-500';
|
|
} else {
|
|
statusText = 'Offline';
|
|
statusClass = 'text-sm sm:text-lg font-semibold text-red-500';
|
|
}
|
|
|
|
document.querySelectorAll('#wemos-status').forEach(el => {
|
|
el.textContent = statusText;
|
|
el.className = statusClass;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Fungsi untuk memeriksa status online/offline perangkat
|
|
function checkDeviceStatus() {
|
|
const currentTime = Math.floor(Date.now() / 1000); // Konversi ke detik
|
|
|
|
// Tambahkan pengecekan lebih agresif untuk ESP
|
|
database.ref('device/lastActive').once('value', (snapshot) => {
|
|
const lastActiveESP = snapshot.val();
|
|
|
|
if (lastActiveESP) {
|
|
const timeDiff = currentTime - lastActiveESP;
|
|
|
|
console.log('ESP8266 last active: ' + lastActiveESP);
|
|
console.log('Current time: ' + currentTime);
|
|
console.log('Time difference: ' + timeDiff + ' seconds');
|
|
|
|
// Jika perbedaan waktu lebih dari 65 detik, set status offline
|
|
if (timeDiff > 60) {
|
|
console.log('ESP8266 terdeteksi offline - mengirim status ke Firebase...');
|
|
|
|
database.ref('logs/systemESP').set('Device offline')
|
|
.then(() => {
|
|
console.log('Status ESP diperbarui ke offline');
|
|
})
|
|
.catch(error => {
|
|
console.error('Error updating ESP status:', error);
|
|
});
|
|
}
|
|
} else {
|
|
// Jika lastActive tidak ada, set status offline
|
|
console.log('ESP8266 lastActive tidak ditemukan - set status offline');
|
|
database.ref('logs/systemESP').set('Device offline');
|
|
}
|
|
});
|
|
|
|
// Tambahkan pengecekan lebih agresif untuk Wemos
|
|
database.ref('device/lastActiveWemos').once('value', (snapshot) => {
|
|
const lastActiveWemos = snapshot.val();
|
|
|
|
if (lastActiveWemos) {
|
|
const timeDiff = currentTime - lastActiveWemos;
|
|
|
|
console.log('Wemos D1 Mini last active: ' + lastActiveWemos);
|
|
console.log('Current time: ' + currentTime);
|
|
console.log('Time difference: ' + timeDiff + ' seconds');
|
|
|
|
// Jika perbedaan waktu lebih dari 65 detik, set status offline
|
|
if (timeDiff > 60) {
|
|
console.log('Wemos D1 Mini terdeteksi offline - mengirim status ke Firebase...');
|
|
|
|
database.ref('logs/systemWemos').set('Device Offline')
|
|
.then(() => {
|
|
console.log('Status Wemos diperbarui ke offline');
|
|
})
|
|
.catch(error => {
|
|
console.error('Error updating Wemos status:', error);
|
|
});
|
|
}
|
|
} else {
|
|
// Jika lastActiveWemos tidak ada, set status offline
|
|
console.log('Wemos lastActive tidak ditemukan - set status offline');
|
|
database.ref('logs/systemWemos').set('Device Offline');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Function to toggle security system (updated with lowercase values)
|
|
function toggleSecurity(checkbox) {
|
|
// Get the current state (checked = on, unchecked = off)
|
|
const isEnabled = checkbox.checked;
|
|
const securityStatus = isEnabled ? 'on' : 'off';
|
|
|
|
console.log(`Toggling security system to: ${securityStatus}`);
|
|
|
|
// Update Firebase with the new status
|
|
database.ref('security/status').set(securityStatus)
|
|
.then(() => {
|
|
console.log(`Security status updated to: ${securityStatus}`);
|
|
|
|
// Also update motion to disabled when turned off
|
|
if (!isEnabled) {
|
|
return database.ref('security/motion').set('disabled');
|
|
}
|
|
})
|
|
.then(() => {
|
|
if (!isEnabled) {
|
|
console.log('Motion sensor disabled');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error updating security status:', error);
|
|
// Revert the checkbox state if there was an error
|
|
checkbox.checked = !isEnabled;
|
|
alert(`Failed to update security status: ${error.message}`);
|
|
});
|
|
}
|
|
|
|
// Add this code to initialize the toggle based on current state (inside window.load event)
|
|
function initializeSecurityToggle() {
|
|
const securityToggle = document.getElementById('securityToggle');
|
|
if (securityToggle) {
|
|
// Get the current security status from Firebase
|
|
database.ref('security/status').once('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('Current security status:', status);
|
|
|
|
// Set the toggle based on the status
|
|
securityToggle.checked = (status === 'on');
|
|
});
|
|
|
|
// Also set up a listener to keep the toggle in sync
|
|
database.ref('security/status').on('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
console.log('Security status changed:', status);
|
|
|
|
// Only update if the value doesn't match (to prevent loops)
|
|
if (securityToggle.checked !== (status === 'on')) {
|
|
securityToggle.checked = (status === 'on');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Inisialisasi semua fungsi saat window load
|
|
window.addEventListener('load', function() {
|
|
console.log('Window loaded, initializing...');
|
|
|
|
// Tunggu sedikit untuk memastikan DOM sudah siap
|
|
setTimeout(function() {
|
|
setupListeners();
|
|
setupRestartButtons();
|
|
setupMobileMenu();
|
|
initializeSecurityToggle();
|
|
|
|
// Jalankan pengecekan status perangkat segera dan lebih sering
|
|
checkDeviceStatus(); // Panggil saat awal
|
|
setInterval(checkDeviceStatus, 61000); // Periksa setiap 10 detik
|
|
|
|
// Refresh UI lebih sering
|
|
forceRefreshDeviceStatus(); // Panggil saat awal
|
|
setInterval(forceRefreshDeviceStatus, 3000); // Refresh UI setiap 3 detik
|
|
|
|
// Polling untuk listener
|
|
setInterval(function() {
|
|
console.log('Refreshing listeners...');
|
|
setupListeners();
|
|
}, 61000);
|
|
|
|
console.log('Initialization complete');
|
|
}, 1000);
|
|
});
|
|
|
|
// Tambahkan listener untuk debugging
|
|
console.log('Script loaded');
|
|
|
|
// Tambahkan interval untuk memaksa refresh DOM setiap 5 detik
|
|
setInterval(function() {
|
|
console.log('Forcing DOM refresh...');
|
|
|
|
// Ambil data terbaru dari Firebase dan perbarui UI
|
|
database.ref('smartcab').once('value', (snapshot) => {
|
|
const data = snapshot.val();
|
|
if (data) {
|
|
// Update door status
|
|
if (data.servo_status) {
|
|
document.querySelectorAll('#door-status').forEach(el => {
|
|
el.textContent = data.servo_status;
|
|
el.className = `text-sm sm:text-lg font-semibold ${
|
|
data.servo_status === 'Terkunci' ? 'text-green-500' : 'text-red-500'
|
|
}`;
|
|
});
|
|
}
|
|
|
|
// Update last access
|
|
if (data.last_access) {
|
|
document.querySelectorAll('#last-access').forEach(el => {
|
|
el.textContent = data.last_access;
|
|
});
|
|
}
|
|
|
|
// Update card ID
|
|
if (data.status_device) {
|
|
document.querySelectorAll('#status-device').forEach(el => {
|
|
el.textContent = data.status_device;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}, 5000);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
|
|
<html lang="en"> |