fix report and add filter

This commit is contained in:
Vckynando12 2025-03-23 17:42:37 +07:00
parent 3381eb11b5
commit beb4d15d11
4 changed files with 617 additions and 259 deletions

View File

@ -56,6 +56,23 @@ void setup() {
Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);
// Konfigurasi NTP dan tunggu sinkronisasi
configTime(7 * 3600, 0, "pool.ntp.org"); // GMT+7
Serial.println("Menunggu sinkronisasi waktu");
while (time(nullptr) < 1000000000) {
Serial.print(".");
delay(100);
}
Serial.println("\nWaktu tersinkronisasi!");
// Setelah waktu tersinkronisasi, baru kirim timestamp pertama
unsigned long epochTime = time(nullptr);
if (Firebase.setInt(firebaseData, "/device/lastActiveWemos", epochTime)) {
Serial.println("Initial timestamp sent: " + String(epochTime));
} else {
Serial.println("Failed to send initial timestamp");
}
// Inisialisasi RFID
SPI.begin();
mfrc522.PCD_Init();
@ -84,13 +101,6 @@ void setup() {
// Update path untuk restart control
Firebase.setBool(firebaseData, "/control/restartWemos", false);
// Konfigurasi NTP
configTime(7 * 3600, 0, "pool.ntp.org"); // GMT+7
// Kirim timestamp pertama kali
unsigned long epochTime = time(nullptr);
Firebase.setInt(firebaseData, "/device/lastActiveWemos", epochTime);
// Update status device saat startup
Firebase.setString(firebaseData, "/logs/systemWemos", "Device Online");
}

View File

@ -43,59 +43,135 @@ public function handle()
// Ambil data terakhir jika ada
$lastEntry = !empty($historyData) ? end($historyData) : null;
// Cek apakah ada perubahan pada security, smartcab, control, atau logs
// Cek perubahan pada setiap kategori data
$securityChanged = false;
$smartcabChanged = false;
$controlChanged = false;
$logsChanged = false;
$dht11Changed = false;
$deviceChanged = false;
$hasChanges = false;
if ($lastEntry === null) {
$hasChanges = true;
// Jika belum ada data, anggap semua kategori berubah (kecuali yang dikecualikan)
$securityChanged = !empty($securityData);
$smartcabChanged = !empty($smartcabData);
$controlChanged = !empty($controlData);
$logsChanged = !empty($logsData);
// Untuk DHT11 dan Device, kita hanya anggap berubah jika ada data selain yang dikecualikan
$dht11Changed = !empty($dht11Data) && $this->hasNonTrivialDHT11Data($dht11Data);
$deviceChanged = !empty($deviceData) && $this->hasNonTrivialDeviceData($deviceData);
$hasChanges = $securityChanged || $smartcabChanged || $controlChanged ||
$logsChanged || $dht11Changed || $deviceChanged;
} else {
// Cek perubahan pada setiap kategori
$securityChanged = $this->hasDataChanged($lastEntry['security'] ?? [], $securityData);
$smartcabChanged = $this->hasDataChanged($lastEntry['smartcab'] ?? [], $smartcabData);
$controlChanged = $this->hasDataChanged($lastEntry['control'] ?? [], $controlData);
$logsChanged = $this->hasDataChanged($lastEntry['logs'] ?? [], $logsData);
$hasChanges = $securityChanged || $smartcabChanged || $controlChanged || $logsChanged;
// Untuk dht11 dan device kita periksa secara khusus, mengabaikan field yang sering berubah
$dht11Changed = $this->hasNonTrivialDHT11Changes($lastEntry['dht11'] ?? [], $dht11Data);
$deviceChanged = $this->hasNonTrivialDeviceChanges($lastEntry['device'] ?? [], $deviceData);
$hasChanges = $securityChanged || $smartcabChanged || $controlChanged ||
$logsChanged || $dht11Changed || $deviceChanged;
}
// Hanya simpan jika ada perubahan
if ($hasChanges) {
$newData = [
'id' => Str::uuid()->toString(), // Generate ID unik
'timestamp' => now()->toIso8601String(),
// Proses setiap perubahan secara terpisah
$changesMade = false;
// Buat template data lengkap
$fullData = [
'security' => $securityData,
'smartcab' => $smartcabData,
'control' => $controlData,
'logs' => $logsData
];
if (!empty($dht11Data)) {
$newData['dht11'] = $dht11Data;
}
'logs' => $logsData,
'dht11' => $dht11Data,
'device' => $deviceData
];
// Simpan setiap perubahan secara terpisah dengan data lengkap
if ($securityChanged && !empty($securityData)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'security'; // Tambahkan informasi apa yang berubah
if (!empty($deviceData)) {
$newData['device'] = $deviceData;
}
$historyData[] = $newData;
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'security'));
$this->info('Perubahan terdeteksi pada security, ID: ' . $newEntry['id']);
$changesMade = true;
}
if ($smartcabChanged && !empty($smartcabData)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'smartcab';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'smartcab'));
$this->info('Perubahan terdeteksi pada smartcab, ID: ' . $newEntry['id']);
$changesMade = true;
}
if ($controlChanged && !empty($controlData)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'control';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'control'));
$this->info('Perubahan terdeteksi pada control, ID: ' . $newEntry['id']);
$changesMade = true;
}
if ($logsChanged && !empty($logsData)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'logs';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'logs'));
$this->info('Perubahan terdeteksi pada logs, ID: ' . $newEntry['id']);
$changesMade = true;
}
if ($dht11Changed && !empty($dht11Data)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'dht11';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'dht11'));
$this->info('Perubahan signifikan terdeteksi pada dht11, ID: ' . $newEntry['id']);
$changesMade = true;
}
if ($deviceChanged && !empty($deviceData)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'device';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'device'));
$this->info('Perubahan signifikan terdeteksi pada device, ID: ' . $newEntry['id']);
$changesMade = true;
}
// Jika ada perubahan yang disimpan, update file
if ($changesMade) {
Storage::put('reports.json', json_encode($historyData, JSON_PRETTY_PRINT));
$this->info('Data baru tersimpan dengan ID: ' . $newData['id']);
// Broadcast event untuk realtime update
event(new ReportUpdated($newData));
if (isset($securityChanged) && $securityChanged) {
$this->info('Perubahan terdeteksi pada security');
}
if (isset($smartcabChanged) && $smartcabChanged) {
$this->info('Perubahan terdeteksi pada smartcab');
}
if (isset($controlChanged) && $controlChanged) {
$this->info('Perubahan terdeteksi pada control');
}
if (isset($logsChanged) && $logsChanged) {
$this->info('Perubahan terdeteksi pada logs');
}
$this->info('Semua perubahan berhasil disimpan.');
} else {
$this->info('Tidak ada perubahan pada data, data tidak disimpan');
$this->info('Tidak ada perubahan signifikan pada data, data tidak disimpan');
}
} catch (\Exception $e) {
@ -110,4 +186,96 @@ private function hasDataChanged($oldData, $newData)
return $oldJson !== $newJson;
}
/**
* Memeriksa perubahan pada data dht11 selain humidity dan temperature
*/
private function hasNonTrivialDHT11Changes($oldData, $newData)
{
// Buat salinan data untuk perbandingan
$oldDataCompare = is_array($oldData) ? $oldData : [];
$newDataCompare = is_array($newData) ? $newData : [];
// Hapus field yang sering berubah
if (isset($oldDataCompare['humidity'])) {
unset($oldDataCompare['humidity']);
}
if (isset($oldDataCompare['temperature'])) {
unset($oldDataCompare['temperature']);
}
if (isset($newDataCompare['humidity'])) {
unset($newDataCompare['humidity']);
}
if (isset($newDataCompare['temperature'])) {
unset($newDataCompare['temperature']);
}
// Bandingkan data yang tersisa
return json_encode($oldDataCompare) !== json_encode($newDataCompare);
}
/**
* Memeriksa apakah DHT11 data memiliki field selain yang dikecualikan
*/
private function hasNonTrivialDHT11Data($data)
{
$dataCopy = is_array($data) ? $data : [];
// Hapus field yang sering berubah
if (isset($dataCopy['humidity'])) {
unset($dataCopy['humidity']);
}
if (isset($dataCopy['temperature'])) {
unset($dataCopy['temperature']);
}
// Periksa apakah masih ada data lain
return !empty($dataCopy);
}
/**
* Memeriksa perubahan pada data device selain lastActive dan lastActiveWemos
*/
private function hasNonTrivialDeviceChanges($oldData, $newData)
{
// Buat salinan data untuk perbandingan
$oldDataCompare = is_array($oldData) ? $oldData : [];
$newDataCompare = is_array($newData) ? $newData : [];
// Hapus field yang sering berubah
if (isset($oldDataCompare['lastActive'])) {
unset($oldDataCompare['lastActive']);
}
if (isset($oldDataCompare['lastActiveWemos'])) {
unset($oldDataCompare['lastActiveWemos']);
}
if (isset($newDataCompare['lastActive'])) {
unset($newDataCompare['lastActive']);
}
if (isset($newDataCompare['lastActiveWemos'])) {
unset($newDataCompare['lastActiveWemos']);
}
// Bandingkan data yang tersisa
return json_encode($oldDataCompare) !== json_encode($newDataCompare);
}
/**
* Memeriksa apakah Device data memiliki field selain yang dikecualikan
*/
private function hasNonTrivialDeviceData($data)
{
$dataCopy = is_array($data) ? $data : [];
// Hapus field yang sering berubah
if (isset($dataCopy['lastActive'])) {
unset($dataCopy['lastActive']);
}
if (isset($dataCopy['lastActiveWemos'])) {
unset($dataCopy['lastActiveWemos']);
}
// Periksa apakah masih ada data lain
return !empty($dataCopy);
}
}

View File

@ -15,15 +15,19 @@ class ReportUpdated implements ShouldBroadcast
use Dispatchable, InteractsWithSockets, SerializesModels;
public $report;
public $changeType;
/**
* Create a new event instance.
*
* @param array $report Data report
* @param string|null $changeType Tipe perubahan (security, smartcab, dll)
* @return void
*/
public function __construct($report)
public function __construct($report, $changeType = null)
{
$this->report = $report;
$this->changeType = $changeType ?? ($report['change_type'] ?? null);
}
/**
@ -35,4 +39,18 @@ public function broadcastOn()
{
return new Channel('reports');
}
/**
* Get the data to broadcast.
*
* @return array
*/
public function broadcastWith()
{
return [
'report' => $this->report,
'change_type' => $this->changeType,
'timestamp' => now()->toIso8601String()
];
}
}

View File

@ -29,6 +29,27 @@
50% { background-color: #d1ecf1; }
100% { background-color: #fff; }
}
/* Styling untuk multiple select */
select[multiple] {
max-height: 200px;
overflow-y: auto;
}
select[multiple] optgroup {
font-weight: 600;
color: #374151;
padding: 0.25rem 0;
}
select[multiple] option {
padding: 0.25rem 0.5rem;
margin: 0.125rem 0;
}
select[multiple] option:checked {
background-color: #2563eb;
color: white;
}
</style>
</head>
<body class="bg-gray-50">
@ -57,43 +78,59 @@
<ul class="space-y-2 text-sm">
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-green-500 mr-2"></span>
<span><strong>Keamanan Normal:</strong> Sistem keamanan berjalan dengan baik</span>
<span><strong>Fan:</strong> Perubahan pada status kipas</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-red-500 mr-2"></span>
<span><strong>Status Bahaya:</strong> Sistem keamanan mendeteksi potensi bahaya</span>
<span><strong>Status:</strong> Perubahan status keamanan (aman/bahaya)</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-yellow-500 mr-2"></span>
<span><strong>Gerakan Terdeteksi:</strong> Sensor gerakan mendeteksi aktivitas</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-lime-500 mr-2"></span>
<span><strong>Tidak Ada Gerakan:</strong> Tidak ada gerakan yang terdeteksi</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-blue-500 mr-2"></span>
<span><strong>Servo Terkunci:</strong> Servo dalam keadaan terkunci</span>
<span><strong>Motion:</strong> Perubahan status gerakan</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-cyan-500 mr-2"></span>
<span><strong>Servo Terbuka:</strong> Servo dalam keadaan terbuka</span>
<span><strong>Servo Status:</strong> Perubahan status servo (terbuka/terkunci)</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-violet-400 mr-2"></span>
<span><strong>Akses Terakhir:</strong> Perubahan pada akses terakhir</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-purple-500 mr-2"></span>
<span><strong>Kontrol Diubah:</strong> Terjadi perubahan pada kontrol perangkat</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-pink-500 mr-2"></span>
<span><strong>Status Perangkat:</strong> Perubahan pada status perangkat</span>
<span><strong>Last Access:</strong> Perubahan pada akses terakhir</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-orange-500 mr-2"></span>
<span><strong>Error/Warning:</strong> Terdapat kesalahan atau peringatan dalam sistem</span>
<span><strong>Restart ESP:</strong> Perangkat ESP direstart</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-amber-500 mr-2"></span>
<span><strong>Restart Wemos:</strong> Perangkat Wemos direstart</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-indigo-500 mr-2"></span>
<span><strong>RFID:</strong> Perubahan pada status RFID</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-lime-500 mr-2"></span>
<span><strong>DHT:</strong> Perubahan pada sensor DHT</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-teal-500 mr-2"></span>
<span><strong>MPU:</strong> Perubahan pada sensor MPU</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-sky-500 mr-2"></span>
<span><strong>Servo Log:</strong> Perubahan pada log servo</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-rose-500 mr-2"></span>
<span><strong>System ESP:</strong> Perubahan status sistem ESP</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-pink-500 mr-2"></span>
<span><strong>System Wemos:</strong> Perubahan status sistem Wemos</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-gray-500 mr-2"></span>
<span><strong>Device Status:</strong> Perubahan status perangkat lainnya</span>
</li>
</ul>
</div>
@ -169,6 +206,38 @@
</div>
</div>
<!-- Tambahkan ini di dalam form filter, setelah Time Range Picker -->
<div class="md:col-span-2">
<label for="categories" class="block mb-2 text-sm font-medium text-gray-700">Filter Kategori</label>
<select id="categories" data-te-select-init multiple
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
<optgroup label="Keamanan">
<option value="motion">Motion</option>
<option value="status">Status Keamanan</option>
<option value="fan">Fan</option>
</optgroup>
<optgroup label="Perangkat">
<option value="servo-status">Servo Status</option>
<option value="last-access">Last Access</option>
<option value="device">Device Status</option>
</optgroup>
<optgroup label="Kontrol">
<option value="restart-esp">Restart ESP</option>
<option value="restart-wemos">Restart Wemos</option>
</optgroup>
<optgroup label="Sensor">
<option value="rfid">RFID</option>
<option value="dht">DHT</option>
<option value="mpu">MPU</option>
</optgroup>
<optgroup label="Log">
<option value="servo-log">Servo Log</option>
<option value="system-esp">System ESP</option>
<option value="system-wemos">System Wemos</option>
</optgroup>
</select>
</div>
<div class="md:col-span-2 flex justify-end space-x-2">
<button type="button" id="applyFilter" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5">
Terapkan Filter
@ -287,6 +356,7 @@
const pageSize = 10; // Jumlah item per halaman
let filteredReports = []; // Menyimpan hasil filter
let isFilterActive = false; // Flag untuk menandai apakah filter aktif
let selectedCategories = [];
// Variabel untuk menyimpan instance chart
let systemOverviewChart = null;
@ -367,89 +437,84 @@ function goToNextPage() {
function applyFilters() {
const filterDate = document.getElementById('filterDate').value;
const timeFilterEnabled = document.getElementById('filterTimeToggle').checked;
// Simpan halaman sebelumnya
const prevPage = currentPage;
const categories = Array.from(document.getElementById('categories').selectedOptions).map(opt => opt.value);
// Set flag filter aktif
isFilterActive = filterDate || timeFilterEnabled;
isFilterActive = filterDate || timeFilterEnabled || categories.length > 0;
selectedCategories = categories;
let startTime = null;
let endTime = null;
let filteredData = allReports;
if (timeFilterEnabled) {
startTime = document.getElementById('startTime').value;
endTime = document.getElementById('endTime').value;
// Convert to minutes for easier comparison
const [startHour, startMinute] = startTime.split(':').map(Number);
const [endHour, endMinute] = endTime.split(':').map(Number);
const startTotalMinutes = startHour * 60 + startMinute;
const endTotalMinutes = endHour * 60 + endMinute;
// Filter data berdasarkan kriteria
filteredReports = allReports.filter(report => {
// Filter berdasarkan tanggal dan waktu
if (filterDate || timeFilterEnabled) {
filteredData = filteredData.filter(report => {
const reportDate = new Date(report.timestamp);
// Filter berdasarkan tanggal
// Filter tanggal
if (filterDate) {
const dateStr = reportDate.toISOString().split('T')[0];
if (dateStr !== filterDate) return false;
}
// Filter berdasarkan rentang waktu
// Filter waktu
if (timeFilterEnabled) {
const startTime = document.getElementById('startTime').value;
const endTime = document.getElementById('endTime').value;
const [startHour, startMinute] = startTime.split(':').map(Number);
const [endHour, endMinute] = endTime.split(':').map(Number);
const startTotalMinutes = startHour * 60 + startMinute;
const endTotalMinutes = endHour * 60 + endMinute;
const hour = reportDate.getHours();
const minute = reportDate.getMinutes();
const totalMinutes = hour * 60 + minute;
// Handle case where time range crosses midnight
if (startTotalMinutes <= endTotalMinutes) {
// Normal case (e.g., 08:00 to 17:00)
if (totalMinutes < startTotalMinutes || totalMinutes > endTotalMinutes) return false;
} else {
// Overnight case (e.g., 22:00 to 06:00)
if (totalMinutes < startTotalMinutes && totalMinutes > endTotalMinutes) return false;
}
}
return true;
});
} else {
// Jika filter waktu tidak aktif, hanya filter berdasarkan tanggal
filteredReports = allReports.filter(report => {
const reportDate = new Date(report.timestamp);
// Filter berdasarkan tanggal
if (filterDate) {
const dateStr = reportDate.toISOString().split('T')[0];
if (dateStr !== filterDate) return false;
}
return true;
}
// Filter berdasarkan kategori
if (categories.length > 0) {
filteredData = filteredData.filter(report => {
// Deteksi perubahan untuk report ini
const changes = detectChanges(report, allReports, allReports.indexOf(report));
// Cek apakah ada perubahan yang masuk dalam kategori yang dipilih
return changes.some(change => categories.includes(change.badge));
});
}
// Reset halaman ke 1 HANYA ketika menerapkan filter baru (karena ini eksplisit pengguna memfilter)
// Update data terfilter
filteredReports = filteredData;
// Reset ke halaman pertama
currentPage = 1;
// PENTING: Update chart dengan data yang sudah difilter
// Update chart
renderSystemOverviewChart(filteredReports);
// Render data yang telah difilter
// Render data
renderPaginatedReports(true);
// Tampilkan notifikasi hasil filter
const filterCount = filteredReports.length;
const totalCount = allReports.length;
if (filterCount === 0) {
showFilterNotification(`Tidak ada data yang cocok dengan filter`);
} else {
showFilterNotification(`Ditemukan ${filterCount} dari ${totalCount} data`);
let filterMessage = `Ditemukan ${filterCount} dari ${totalCount} data`;
if (categories.length > 0) {
filterMessage += ` dengan kategori: ${categories.join(', ')}`;
}
// Tambahkan badge untuk menampilkan status filter pada chart
showFilterNotification(filterCount === 0 ? 'Tidak ada data yang cocok dengan filter' : filterMessage);
// Update status filter pada chart
updateChartFilterStatus();
}
@ -481,6 +546,8 @@ function resetFilters() {
document.getElementById('filterTimeToggle').checked = false;
document.getElementById('timeRangePicker').classList.add('hidden');
document.getElementById('timeToggleStatus').textContent = 'Semua Waktu';
document.getElementById('categories').selectedIndex = -1; // Reset multiple select
selectedCategories = [];
// Reset flag filter
isFilterActive = false;
@ -489,7 +556,7 @@ function resetFilters() {
filteredReports = allReports;
currentPage = 1;
// PENTING: Update chart dengan semua data
// Update chart dengan semua data
renderSystemOverviewChart(allReports);
// Update chart filter status
@ -745,15 +812,25 @@ function renderReports(reports) {
const changes = detectChanges(report, allReports, allReports.indexOf(report));
changes.forEach(change => {
const badge = document.createElement('span');
// Flowbite badge styles
// Flowbite badge styles dengan warna khusus untuk setiap jenis
const badgeClasses = {
'primary': 'bg-blue-100 text-blue-800',
'secondary': 'bg-gray-100 text-gray-800',
'danger': 'bg-red-100 text-red-800',
'warning': 'bg-yellow-100 text-yellow-800',
'info': 'bg-indigo-100 text-indigo-800',
'light': 'bg-gray-100 text-gray-800',
'dark': 'bg-gray-700 text-gray-300'
'motion': 'bg-yellow-100 text-yellow-800',
'status': 'bg-red-100 text-red-800',
'fan': 'bg-green-100 text-green-800',
'last-access': 'bg-violet-100 text-violet-800',
'servo-status': 'bg-cyan-100 text-cyan-800',
'restart-esp': 'bg-orange-100 text-orange-800',
'restart-wemos': 'bg-amber-100 text-amber-800',
'rfid': 'bg-indigo-100 text-indigo-800',
'dht': 'bg-lime-100 text-lime-800',
'mpu': 'bg-teal-100 text-teal-800',
'servo-log': 'bg-sky-100 text-sky-800',
'system-esp': 'bg-rose-100 text-rose-800',
'system-wemos': 'bg-pink-100 text-pink-800',
'device': 'bg-gray-100 text-gray-800',
'light': 'bg-gray-100 text-gray-400'
};
badge.className = `px-2 py-0.5 rounded text-xs font-medium me-2 ${badgeClasses[change.badge] || badgeClasses['light']}`;
@ -810,41 +887,81 @@ function detectChanges(report, reports, index) {
// Security changes
if (report.security && prevReport.security) {
if (report.security.motion !== prevReport.security.motion) {
changes.push({ type: 'Gerakan', badge: 'danger' });
changes.push({ type: 'Motion', badge: 'motion' });
}
if (report.security.status !== prevReport.security.status) {
changes.push({ type: 'Status Keamanan', badge: 'danger' });
changes.push({ type: 'Status', badge: 'status' });
}
if (report.security.fan !== prevReport.security.fan) {
changes.push({ type: 'Fan', badge: 'fan' });
}
}
// Smartcab changes
if (report.smartcab && prevReport.smartcab) {
if (report.smartcab.last_access !== prevReport.smartcab.last_access) {
changes.push({ type: 'Akses Terakhir', badge: 'info' });
changes.push({ type: 'Last Access', badge: 'last-access' });
}
if (report.smartcab.servo_status !== prevReport.smartcab.servo_status) {
changes.push({ type: 'Status Servo', badge: 'warning' });
changes.push({ type: 'Servo Status', badge: 'servo-status' });
}
}
// Control changes
if (report.control && prevReport.control &&
JSON.stringify(report.control) !== JSON.stringify(prevReport.control)) {
changes.push({ type: 'Kontrol Perangkat', badge: 'secondary' });
}
// Device changes
if (report.device && prevReport.device &&
JSON.stringify(report.device) !== JSON.stringify(prevReport.device)) {
changes.push({ type: 'Status Perangkat', badge: 'secondary' });
if (report.control && prevReport.control) {
if (report.control.restartESP !== prevReport.control.restartESP) {
changes.push({ type: 'Restart ESP', badge: 'restart-esp' });
}
if (report.control.restartWemos !== prevReport.control.restartWemos) {
changes.push({ type: 'Restart Wemos', badge: 'restart-wemos' });
}
}
// Logs changes
if (report.logs && prevReport.logs &&
JSON.stringify(report.logs) !== JSON.stringify(prevReport.logs)) {
changes.push({ type: 'Log Sistem', badge: 'dark' });
if (report.logs && prevReport.logs) {
// RFID logs
if (report.logs.RFID && prevReport.logs.RFID &&
JSON.stringify(report.logs.RFID) !== JSON.stringify(prevReport.logs.RFID)) {
changes.push({ type: 'RFID', badge: 'rfid' });
}
// DHT logs
if (report.logs.dht && prevReport.logs.dht &&
JSON.stringify(report.logs.dht) !== JSON.stringify(prevReport.logs.dht)) {
changes.push({ type: 'DHT', badge: 'dht' });
}
// MPU logs
if (report.logs.mpu && prevReport.logs.mpu &&
JSON.stringify(report.logs.mpu) !== JSON.stringify(prevReport.logs.mpu)) {
changes.push({ type: 'MPU', badge: 'mpu' });
}
// Servo logs
if (report.logs.servo && prevReport.logs.servo &&
JSON.stringify(report.logs.servo) !== JSON.stringify(prevReport.logs.servo)) {
changes.push({ type: 'Servo Log', badge: 'servo-log' });
}
// System ESP logs
if (report.logs.systemESP !== prevReport.logs.systemESP) {
changes.push({ type: 'System ESP', badge: 'system-esp' });
}
// System Wemos logs
if (report.logs.systemWemos !== prevReport.logs.systemWemos) {
changes.push({ type: 'System Wemos', badge: 'system-wemos' });
}
}
// Device changes - general fallback if needed
if (report.device && prevReport.device &&
JSON.stringify(report.device) !== JSON.stringify(prevReport.device)) {
changes.push({ type: 'Device Status', badge: 'device' });
}
if (changes.length === 0) {
@ -1039,145 +1156,186 @@ function showDetail(report) {
function analyzeSystemOverview(reports) {
// Siapkan variabel untuk menyimpan hitungan status
const statusCounts = {
'securityNormal': 0, // Status keamanan normal
'securityDanger': 0, // Status keamanan bahaya
'motionDetected': 0, // Gerakan terdeteksi
'motionNotDetected': 0, // Gerakan tidak terdeteksi
'servoLocked': 0, // Servo terkunci
'servoUnlocked': 0, // Servo terbuka
'accessChanged': 0, // Perubahan akses terakhir
'controlChanged': 0, // Perubahan kontrol perangkat
'deviceChanged': 0, // Perubahan status perangkat
'logError': 0 // Log error
'motion': 0, // Motion status
'status': 0, // Security status
'fan': 0, // Fan status
'servoStatus': 0, // Servo status
'lastAccess': 0, // Last access
'restartEsp': 0, // Restart ESP
'restartWemos': 0, // Restart Wemos
'rfid': 0, // RFID
'dht': 0, // DHT sensor
'mpu': 0, // MPU sensor
'servoLog': 0, // Servo log
'systemEsp': 0, // System ESP
'systemWemos': 0, // System Wemos
'deviceStatus': 0 // Device status
};
// Total untuk persentase dan statusCounts
// Total untuk persentase
let totalCounts = 0;
// Fungsi untuk menghitung perubahan antar report
function countChanges(report, index) {
if (index === 0 || index >= reports.length - 1) return [];
const changes = [];
const prevReport = reports[index + 1]; // Data baris sebelumnya (karena data sorted terbaru dulu)
// Security changes
if (report.security && prevReport.security) {
if (report.security.motion !== prevReport.security.motion) {
changes.push('motion');
}
if (report.security.status !== prevReport.security.status) {
changes.push('status');
}
if (report.security.fan !== prevReport.security.fan) {
changes.push('fan');
}
}
// Smartcab changes
if (report.smartcab && prevReport.smartcab) {
if (report.smartcab.last_access !== prevReport.smartcab.last_access) {
changes.push('lastAccess');
}
if (report.smartcab.servo_status !== prevReport.smartcab.servo_status) {
changes.push('servoStatus');
}
}
// Control changes
if (report.control && prevReport.control) {
if (report.control.restartESP !== prevReport.control.restartESP) {
changes.push('restartEsp');
}
if (report.control.restartWemos !== prevReport.control.restartWemos) {
changes.push('restartWemos');
}
}
// Logs changes
if (report.logs && prevReport.logs) {
// RFID logs
if (report.logs.RFID && prevReport.logs.RFID &&
JSON.stringify(report.logs.RFID) !== JSON.stringify(prevReport.logs.RFID)) {
changes.push('rfid');
}
// DHT logs
if (report.logs.dht && prevReport.logs.dht &&
JSON.stringify(report.logs.dht) !== JSON.stringify(prevReport.logs.dht)) {
changes.push('dht');
}
// MPU logs
if (report.logs.mpu && prevReport.logs.mpu &&
JSON.stringify(report.logs.mpu) !== JSON.stringify(prevReport.logs.mpu)) {
changes.push('mpu');
}
// Servo logs
if (report.logs.servo && prevReport.logs.servo &&
JSON.stringify(report.logs.servo) !== JSON.stringify(prevReport.logs.servo)) {
changes.push('servoLog');
}
// System ESP logs
if (report.logs.systemESP !== prevReport.logs.systemESP) {
changes.push('systemEsp');
}
// System Wemos logs
if (report.logs.systemWemos !== prevReport.logs.systemWemos) {
changes.push('systemWemos');
}
}
// Device changes - general fallback if needed
if (report.device && prevReport.device &&
JSON.stringify(report.device) !== JSON.stringify(prevReport.device)) {
changes.push('deviceStatus');
}
return changes;
}
// Periksa setiap laporan untuk mengisi kategori
reports.forEach(report => {
let categoriesAdded = 0;
reports.forEach((report, index) => {
const changes = countChanges(report, index);
// Cek status keamanan
if (report.security) {
if (report.security.status) {
if (report.security.status.toLowerCase() === 'bahaya') {
statusCounts['securityDanger']++;
categoriesAdded++;
} else if (report.security.status.toLowerCase() === 'aman') {
statusCounts['securityNormal']++;
categoriesAdded++;
}
}
if (report.security.motion) {
if (report.security.motion.toLowerCase() === 'detected') {
statusCounts['motionDetected']++;
categoriesAdded++;
} else if (report.security.motion.toLowerCase() === 'not detected') {
statusCounts['motionNotDetected']++;
categoriesAdded++;
}
}
changes.forEach(change => {
statusCounts[change]++;
totalCounts++;
});
// Jika tidak ada perubahan, tambahkan satu ke total untuk report ini
if (changes.length === 0 && index > 0 && index < reports.length - 1) {
totalCounts++;
}
// Cek status smartcab
if (report.smartcab) {
if (report.smartcab.servo_status) {
if (report.smartcab.servo_status.toLowerCase() === 'locked') {
statusCounts['servoLocked']++;
categoriesAdded++;
} else if (report.smartcab.servo_status.toLowerCase() === 'unlocked') {
statusCounts['servoUnlocked']++;
categoriesAdded++;
}
}
if (report.smartcab.last_access) {
statusCounts['accessChanged']++;
categoriesAdded++;
}
}
// Cek perubahan kontrol
if (report.control && Object.keys(report.control).length > 0) {
statusCounts['controlChanged']++;
categoriesAdded++;
}
// Cek perubahan device
if (report.device && Object.keys(report.device).length > 0) {
statusCounts['deviceChanged']++;
categoriesAdded++;
}
// Cek logs untuk error
if (report.logs) {
let hasError = false;
for (const logKey in report.logs) {
if (report.logs[logKey] && report.logs[logKey].status) {
const status = report.logs[logKey].status.toLowerCase();
if (status.includes('error') || status.includes('warning')) {
hasError = true;
break;
}
}
}
if (hasError) {
statusCounts['logError']++;
categoriesAdded++;
}
}
// Tambahkan jumlah kategori ke total
totalCounts += categoriesAdded > 0 ? categoriesAdded : 1;
});
// Update counter
document.getElementById('system-chart-count').textContent =
`Total: ${reports.length} laporan dengan ${totalCounts} status`;
`Total: ${reports.length} laporan dengan ${totalCounts} perubahan status`;
// Kembalikan array untuk chart dengan hanya nilai yang bukan nol
const resultData = [
statusCounts['securityNormal'],
statusCounts['securityDanger'],
statusCounts['motionDetected'],
statusCounts['motionNotDetected'],
statusCounts['servoLocked'],
statusCounts['servoUnlocked'],
statusCounts['accessChanged'],
statusCounts['controlChanged'],
statusCounts['deviceChanged'],
statusCounts['logError']
statusCounts['fan'],
statusCounts['status'],
statusCounts['motion'],
statusCounts['servoStatus'],
statusCounts['lastAccess'],
statusCounts['restartEsp'],
statusCounts['restartWemos'],
statusCounts['rfid'],
statusCounts['dht'],
statusCounts['mpu'],
statusCounts['servoLog'],
statusCounts['systemEsp'],
statusCounts['systemWemos'],
statusCounts['deviceStatus']
];
// Label yang sesuai dengan resultData
const resultLabels = [
'Keamanan Normal',
'Status Bahaya',
'Gerakan Terdeteksi',
'Tidak Ada Gerakan',
'Servo Terkunci',
'Servo Terbuka',
'Akses Terakhir',
'Kontrol Diubah',
'Status Perangkat',
'Error/Warning'
'Fan',
'Status Keamanan',
'Motion',
'Servo Status',
'Last Access',
'Restart ESP',
'Restart Wemos',
'RFID',
'DHT',
'MPU',
'Servo Log',
'System ESP',
'System Wemos',
'Device Status'
];
// Warna yang sesuai dengan resultData
// Warna yang sesuai dengan resultData (sesuai dengan badge colors)
const resultColors = [
'#22c55e', // Keamanan Normal (Hijau)
'#ef4444', // Status Bahaya (Merah)
'#eab308', // Gerakan Terdeteksi (Kuning)
'#84cc16', // Tidak Ada Gerakan (Hijau Muda)
'#3b82f6', // Servo Terkunci (Biru)
'#06b6d4', // Servo Terbuka (Cyan)
'#8b5cf6', // Akses Terakhir (Ungu Muda)
'#a855f7', // Kontrol Diubah (Ungu)
'#ec4899', // Status Perangkat (Pink)
'#f97316' // Error/Warning (Oranye)
'#22c55e', // Fan (Hijau)
'#ef4444', // Status (Merah)
'#eab308', // Motion (Kuning)
'#06b6d4', // Servo Status (Cyan)
'#8b5cf6', // Last Access (Ungu Muda)
'#f97316', // Restart ESP (Oranye)
'#f59e0b', // Restart Wemos (Amber)
'#6366f1', // RFID (Indigo)
'#84cc16', // DHT (Lime)
'#14b8a6', // MPU (Teal)
'#0ea5e9', // Servo Log (Sky)
'#e11d48', // System ESP (Rose)
'#ec4899', // System Wemos (Pink)
'#6b7280' // Device Status (Gray)
];
// Filter untuk menghilangkan kategori dengan nilai nol
@ -1298,32 +1456,36 @@ function updateChartFilterStatus() {
const filterDate = document.getElementById('filterDate').value;
const timeFilterEnabled = document.getElementById('filterTimeToggle').checked;
let filterInfo = '';
let filterInfo = [];
if (filterDate) {
const formattedDate = new Date(filterDate).toLocaleDateString('id-ID', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
filterInfo += `Tanggal: ${formattedDate}`;
filterInfo.push(`Tanggal: ${formattedDate}`);
}
if (timeFilterEnabled) {
const startTime = document.getElementById('startTime').value;
const endTime = document.getElementById('endTime').value;
if (filterInfo) filterInfo += ' | ';
filterInfo += `Waktu: ${startTime} - ${endTime}`;
filterInfo.push(`Waktu: ${startTime} - ${endTime}`);
}
if (selectedCategories.length > 0) {
filterInfo.push(`Kategori: ${selectedCategories.join(', ')}`);
}
// Update text pada chart count
if (filterInfo) {
if (filterInfo.length > 0) {
chartCountElement.innerHTML = `
<span class="font-medium">Data Terfilter:</span> ${filteredReports.length} dari ${allReports.length}
`;
// Tampilkan badge filter
chartFilterBadge.classList.remove('hidden');
chartFilterText.textContent = filterInfo;
chartFilterText.textContent = filterInfo.join(' | ');
} else {
chartCountElement.textContent = `Total: ${allReports.length} laporan dengan ${analyzeSystemOverview(allReports).series.reduce((a, b) => a + b, 0)} status`;