IceKhoffi commited on
Commit
9bd9d34
·
verified ·
1 Parent(s): 4592459

Update static/js/app.js

Browse files
Files changed (1) hide show
  1. static/js/app.js +36 -45
static/js/app.js CHANGED
@@ -84,7 +84,7 @@ DOMElements.contexts = DOMElements.canvases.map(canvas =>
84
  function updateDateTime() {
85
  if (!DOMElements.datetime) return;
86
 
87
- DOMElements.datetime.textContent = new Date().toLocaleString('id-ID', {
88
  dateStyle: 'full',
89
  timeStyle: 'short'
90
  });
@@ -93,7 +93,7 @@ function updateDateTime() {
93
  function addLog(message, type = 'info') {
94
  if (!DOMElements.systemLog) return;
95
 
96
- const timestamp = new Date().toLocaleTimeString('id-ID', { hour12: false });
97
  const typeClass = {
98
  info: 'text-slate-500',
99
  warning: 'text-amber-600',
@@ -160,12 +160,12 @@ function displayAudioResults(data) {
160
  }
161
 
162
  if (data.status === "analyzing") {
163
- DOMElements.vocalizationContent.innerHTML = `<p class="text-sm text-amber-600">Analisis vokalisasi sedang berlangsung...</p>`;
164
  return;
165
  }
166
 
167
  if (data.status === "no_data" || data.prediction === null) {
168
- DOMElements.vocalizationContent.innerHTML = `<p class="text-sm text-slate-500">Menunggu analisis vokalisasi berlangsung...</p>`;
169
  return;
170
  }
171
 
@@ -176,9 +176,9 @@ function displayAudioResults(data) {
176
 
177
  const probabilities = data.probabilities || {};
178
  const statusMap = {
179
- 'Healthy': { text: 'Sehat', color: 'green' },
180
- 'Unhealthy': { text: 'Tidak Sehat', color: 'red' },
181
- 'Noise': { text: 'Bising', color: 'amber' },
182
  };
183
 
184
  const dominantStatus = statusMap[data.prediction] || {
@@ -186,14 +186,10 @@ function displayAudioResults(data) {
186
  color: 'slate'
187
  };
188
 
189
- if(
190
- (data.prediction === "Unhealthy" || dominantStatus.text === "Tidak Sehat") &&
191
- state.lastVocalization !== "Unhealthy"
192
- ){
193
- addLog("Status Vokal: <strong>Tidak Sehat Terdeteksi</strong>", "danger");
194
  state.lastVocalization = "Unhealthy";
195
- }
196
- else if (data.prediction === "Healthy"){
197
  state.lastVocalization = "Healthy";
198
  }
199
 
@@ -217,13 +213,11 @@ function displayAudioResults(data) {
217
 
218
  DOMElements.vocalizationContent.innerHTML = `
219
  <div class="flex items-center justify-between mb-3">
220
- <span class="text-slate-500 text-xs">Status Dominan:</span>
221
  <span class="font-bold text-base text-${dominantStatus.color}-600">${dominantStatus.text}</span>
222
  </div>
223
  <div class="space-y-2">${barsHtml}</div>
224
  `;
225
-
226
-
227
  }
228
 
229
  // --- Modal function ---
@@ -265,7 +259,7 @@ function loadSettingsFromStorage() {
265
  if (savedAudioUrl) {
266
  state.audioUrl = savedAudioUrl;
267
  }
268
- addLog('Settings dimuat dari penyimpanan browser', 'info');
269
  }
270
 
271
  // --- Websocket Streaming functions ---
@@ -286,7 +280,7 @@ function connectWebSocket(cameraIndex) {
286
  state.cameraWebSockets[cameraIndex] = ws;
287
 
288
  ws.onopen = () => {
289
- addLog(`Menghubungkan ke Kamera ${cameraIndex + 1}`, 'info');
290
 
291
  ws.send(JSON.stringify({
292
  type: 'start_stream',
@@ -310,7 +304,7 @@ function connectWebSocket(cameraIndex) {
310
  };
311
 
312
  ws.onclose = () => {
313
- addLog(`Kamera ${cameraIndex + 1} terputus.`, 'warning');
314
 
315
  const ctx = DOMElements.contexts[cameraIndex];
316
  if (ctx) ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
@@ -321,17 +315,17 @@ function connectWebSocket(cameraIndex) {
321
 
322
  state.isStreaming = state.cameraWebSockets.some(ws => ws !== null);
323
  if (!state.isStreaming) {
324
- addLog("Semua streams terputus.", 'danger');
325
  }
326
  };
327
 
328
  ws.onerror = (error) => {
329
  console.error(`WebSocket Error for Camera ${cameraIndex + 1}:`, error);
330
- addLog(`Koneksi gagal untuk Kamera ${cameraIndex + 1}.`, 'danger');
331
  };
332
  } catch (error) {
333
  console.error(`Failed to create WebSocket for Camera ${cameraIndex + 1}:`, error);
334
- addLog(`Gagal terhubung ke Kamera ${cameraIndex + 1}.`, 'danger');
335
  }
336
  }
337
 
@@ -380,8 +374,7 @@ function handleTextMessage(data, cameraIndex) {
380
  const hasAlerted = state.lastInactiveAlert[cameraId];
381
 
382
  if (isAboveThreshold && !hasAlerted){
383
- const message = `Camera ${cameraId}: <strong> Persentase ayam tidak aktif cukup tinggi (${percent}%) </strong>`;
384
-
385
  addLog(message, "danger");
386
  state.lastInactiveAlert[cameraId] = true;
387
  }
@@ -403,7 +396,7 @@ function handleTextMessage(data, cameraIndex) {
403
  }
404
 
405
  function startAllStreams() {
406
- addLog("Mencoba memulai semua stream yang terkonfigurasi...", 'info');
407
 
408
  for (let i = 0; i < 4; i++) {
409
  setTimeout(() => connectWebSocket(i), i * 100);
@@ -419,7 +412,7 @@ function startAllStreams() {
419
  }
420
 
421
  function stopAllStreams() {
422
- addLog("Menghentikan semua stream...", 'warning');
423
 
424
  state.cameraWebSockets.forEach((ws, index) => {
425
  if (ws) {
@@ -459,20 +452,19 @@ async function fetchLatestAudioResult() {
459
  state.lastAudioResultKey = resultKey;
460
 
461
  if (wasAnalyzing){
462
- addLog("Hasil vokalisasi diperbarui", "info");
463
  state.isAnalyzingAudio = false;
464
  state.lastAnalysisTimestamp = new Date();
465
  }else{
466
- addLog("Hasil vokalisasi tersedia", "info");
467
  }
468
 
469
-
470
  displayAudioResults(audioData);
471
  } catch (error) {
472
  console.error("Error fetching audio result:", error);
473
 
474
  if (state.isAnalyzingAudio){
475
- addLog("Analisis audio gagal", "danger");
476
  state.isAnalyzingAudio = false;
477
  }
478
  }
@@ -484,7 +476,7 @@ async function downloadCSV() {
484
  const cameraId = DOMElements.cameraSelect.value;
485
 
486
  if (!start || !end) {
487
- addLog("Silakan pilih tanggal mulai dan tanggal selesai untuk ekspor.", 'warning');
488
  return;
489
  }
490
 
@@ -536,13 +528,14 @@ function handleToggleControlClick(e) {
536
  updateToggleButtons();
537
 
538
  const labelMap = {
539
- show_detected: 'Deteksi',
540
- show_density: 'Kepadatan',
541
- show_inactive: 'Inaktivitas'
542
  };
543
 
544
  const displayName = labelMap[control] || control;
545
- addLog(`Tampilan diperbarui: ${displayName} ${state[control] ? 'dinyalakan' : 'dimatikan'}`, 'info');
 
546
 
547
  const payload = JSON.stringify({
548
  type: 'display_settings_update',
@@ -559,7 +552,7 @@ function handleToggleControlClick(e) {
559
  }
560
 
561
  function handleSettingsButtonClick() {
562
- addLog('Tip: Double-click pada ikon gear untuk membuka setting kamera.', 'info');
563
  }
564
 
565
  function handleSettingsButtonDoubleClick() {
@@ -577,13 +570,13 @@ function handleSaveSettingsClick() {
577
  try {
578
  localStorage.setItem('chickSenseCameraUrls', JSON.stringify(state.cameraUrls));
579
  localStorage.setItem('chickSenseAudioUrl', state.audioUrl);
580
- addLog('Pengaturan disimpan ke penyimpanan peramban.', 'info');
581
  } catch (e) {
582
  console.error("Failed to save settings to local storage:", e);
583
- addLog('Tidak dapat menyimpan pengaturan.', 'danger');
584
  }
585
 
586
- addLog('Pengaturan disimpan. Memulai ulang aliran...', 'info');
587
  closeModal(DOMElements.settingsModal);
588
 
589
  stopAllStreams();
@@ -596,7 +589,7 @@ function handleStopStreamsClick() {
596
  }
597
 
598
  function handleExportButtonClick() {
599
- addLog('Tip: Double-click pada "Export CSV" untuk mengekspor metric ke CSV.', 'info');
600
  }
601
 
602
  function handleExportButtonDoubleClick() {
@@ -669,7 +662,7 @@ function initialize() {
669
 
670
  setInterval(updateDateTime, 30000);
671
 
672
- addLog('Sistem diinisialisasi. Selamat datang! IKGC', 'info');
673
  startAllStreams();
674
  }
675
 
@@ -677,6 +670,4 @@ if (document.readyState === 'loading') {
677
  document.addEventListener('DOMContentLoaded', initialize);
678
  } else {
679
  initialize();
680
- }
681
-
682
-
 
84
  function updateDateTime() {
85
  if (!DOMElements.datetime) return;
86
 
87
+ DOMElements.datetime.textContent = new Date().toLocaleString('en-US', {
88
  dateStyle: 'full',
89
  timeStyle: 'short'
90
  });
 
93
  function addLog(message, type = 'info') {
94
  if (!DOMElements.systemLog) return;
95
 
96
+ const timestamp = new Date().toLocaleTimeString('en-US', { hour12: false });
97
  const typeClass = {
98
  info: 'text-slate-500',
99
  warning: 'text-amber-600',
 
160
  }
161
 
162
  if (data.status === "analyzing") {
163
+ DOMElements.vocalizationContent.innerHTML = `<p class="text-sm text-amber-600">Vocalization analysis in progress...</p>`;
164
  return;
165
  }
166
 
167
  if (data.status === "no_data" || data.prediction === null) {
168
+ DOMElements.vocalizationContent.innerHTML = `<p class="text-sm text-slate-500">Waiting for vocalization analysis to run...</p>`;
169
  return;
170
  }
171
 
 
176
 
177
  const probabilities = data.probabilities || {};
178
  const statusMap = {
179
+ 'Healthy': { text: 'Healthy', color: 'green' },
180
+ 'Unhealthy': { text: 'Unhealthy', color: 'red' },
181
+ 'Noise': { text: 'Noise', color: 'amber' },
182
  };
183
 
184
  const dominantStatus = statusMap[data.prediction] || {
 
186
  color: 'slate'
187
  };
188
 
189
+ if (data.prediction === "Unhealthy" && state.lastVocalization !== "Unhealthy") {
190
+ addLog("Vocalization Status: <strong>Unhealthy Detected</strong>", "danger");
 
 
 
191
  state.lastVocalization = "Unhealthy";
192
+ } else if (data.prediction === "Healthy") {
 
193
  state.lastVocalization = "Healthy";
194
  }
195
 
 
213
 
214
  DOMElements.vocalizationContent.innerHTML = `
215
  <div class="flex items-center justify-between mb-3">
216
+ <span class="text-slate-500 text-xs">Dominant Status:</span>
217
  <span class="font-bold text-base text-${dominantStatus.color}-600">${dominantStatus.text}</span>
218
  </div>
219
  <div class="space-y-2">${barsHtml}</div>
220
  `;
 
 
221
  }
222
 
223
  // --- Modal function ---
 
259
  if (savedAudioUrl) {
260
  state.audioUrl = savedAudioUrl;
261
  }
262
+ addLog('Settings loaded from browser storage', 'info');
263
  }
264
 
265
  // --- Websocket Streaming functions ---
 
280
  state.cameraWebSockets[cameraIndex] = ws;
281
 
282
  ws.onopen = () => {
283
+ addLog(`Connecting to Camera ${cameraIndex + 1}`, 'info');
284
 
285
  ws.send(JSON.stringify({
286
  type: 'start_stream',
 
304
  };
305
 
306
  ws.onclose = () => {
307
+ addLog(`Camera ${cameraIndex + 1} disconnected.`, 'warning');
308
 
309
  const ctx = DOMElements.contexts[cameraIndex];
310
  if (ctx) ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
 
315
 
316
  state.isStreaming = state.cameraWebSockets.some(ws => ws !== null);
317
  if (!state.isStreaming) {
318
+ addLog("All streams disconnected.", 'danger');
319
  }
320
  };
321
 
322
  ws.onerror = (error) => {
323
  console.error(`WebSocket Error for Camera ${cameraIndex + 1}:`, error);
324
+ addLog(`Connection failed for Camera ${cameraIndex + 1}.`, 'danger');
325
  };
326
  } catch (error) {
327
  console.error(`Failed to create WebSocket for Camera ${cameraIndex + 1}:`, error);
328
+ addLog(`Failed to connect to Camera ${cameraIndex + 1}.`, 'danger');
329
  }
330
  }
331
 
 
374
  const hasAlerted = state.lastInactiveAlert[cameraId];
375
 
376
  if (isAboveThreshold && !hasAlerted){
377
+ const message = `Camera ${cameraId}: <strong> Inactive chicken percentage is high (${percent}%) </strong>`;
 
378
  addLog(message, "danger");
379
  state.lastInactiveAlert[cameraId] = true;
380
  }
 
396
  }
397
 
398
  function startAllStreams() {
399
+ addLog("Attempting to start all configured streams...", 'info');
400
 
401
  for (let i = 0; i < 4; i++) {
402
  setTimeout(() => connectWebSocket(i), i * 100);
 
412
  }
413
 
414
  function stopAllStreams() {
415
+ addLog("Stopping all streams...", 'warning');
416
 
417
  state.cameraWebSockets.forEach((ws, index) => {
418
  if (ws) {
 
452
  state.lastAudioResultKey = resultKey;
453
 
454
  if (wasAnalyzing){
455
+ addLog("Vocalization results updated", "info");
456
  state.isAnalyzingAudio = false;
457
  state.lastAnalysisTimestamp = new Date();
458
  }else{
459
+ addLog("Vocalization results available", "info");
460
  }
461
 
 
462
  displayAudioResults(audioData);
463
  } catch (error) {
464
  console.error("Error fetching audio result:", error);
465
 
466
  if (state.isAnalyzingAudio){
467
+ addLog("Audio analysis failed", "danger");
468
  state.isAnalyzingAudio = false;
469
  }
470
  }
 
476
  const cameraId = DOMElements.cameraSelect.value;
477
 
478
  if (!start || !end) {
479
+ addLog("Please select a start and end date for export.", 'warning');
480
  return;
481
  }
482
 
 
528
  updateToggleButtons();
529
 
530
  const labelMap = {
531
+ show_detected: 'Detection',
532
+ show_density: 'Density',
533
+ show_inactive: 'Inactivity'
534
  };
535
 
536
  const displayName = labelMap[control] || control;
537
+ const status = state[control] ? 'enabled' : 'disabled';
538
+ addLog(`View updated: ${displayName} ${status}`, 'info');
539
 
540
  const payload = JSON.stringify({
541
  type: 'display_settings_update',
 
552
  }
553
 
554
  function handleSettingsButtonClick() {
555
+ addLog('Tip: Double-click the gear icon to open camera settings.', 'info');
556
  }
557
 
558
  function handleSettingsButtonDoubleClick() {
 
570
  try {
571
  localStorage.setItem('chickSenseCameraUrls', JSON.stringify(state.cameraUrls));
572
  localStorage.setItem('chickSenseAudioUrl', state.audioUrl);
573
+ addLog('Settings saved to browser storage.', 'info');
574
  } catch (e) {
575
  console.error("Failed to save settings to local storage:", e);
576
+ addLog('Could not save settings.', 'danger');
577
  }
578
 
579
+ addLog('Settings saved. Restarting streams...', 'info');
580
  closeModal(DOMElements.settingsModal);
581
 
582
  stopAllStreams();
 
589
  }
590
 
591
  function handleExportButtonClick() {
592
+ addLog('Tip: Double-click "Export CSV" to export metrics to a CSV file.', 'info');
593
  }
594
 
595
  function handleExportButtonDoubleClick() {
 
662
 
663
  setInterval(updateDateTime, 30000);
664
 
665
+ addLog('System initialized. Welcome! IKGC', 'info');
666
  startAllStreams();
667
  }
668
 
 
670
  document.addEventListener('DOMContentLoaded', initialize);
671
  } else {
672
  initialize();
673
+ }