202 lines
7.3 KiB
PHP
202 lines
7.3 KiB
PHP
<?php
|
|
require_once 'functions/auth.php';
|
|
|
|
$currentPage = basename($_SERVER['PHP_SELF']);
|
|
|
|
if(!isLoggedIn()) { header("Location: login.php"); exit; }
|
|
require_once 'partials/header.php';
|
|
?>
|
|
|
|
<style>
|
|
.sensor-bar-wrap {
|
|
width: 100%;
|
|
height: 5px;
|
|
background: rgba(255,255,255,.15);
|
|
border-radius: 99px;
|
|
margin-top: 8px;
|
|
overflow: hidden;
|
|
}
|
|
.sensor-bar-fill {
|
|
height: 100%;
|
|
border-radius: 99px;
|
|
background: #22c55e;
|
|
width: 0%;
|
|
transition: width .6s ease, background .3s;
|
|
}
|
|
#conn-status {
|
|
display: inline-block;
|
|
width: 9px; height: 9px;
|
|
border-radius: 50%;
|
|
background: #6b7280;
|
|
margin-right: 6px;
|
|
transition: background .3s;
|
|
}
|
|
#conn-status.online { background: #22c55e; box-shadow: 0 0 6px #22c55e; }
|
|
#conn-status.offline { background: #ef4444; box-shadow: 0 0 6px #ef4444; }
|
|
#last-update { font-size: 11px; color: rgba(255,255,255,.55); margin-left: 6px; }
|
|
.device-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
padding: 3px 10px;
|
|
border-radius: 99px;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
background: rgba(255,255,255,.1);
|
|
color: rgba(255,255,255,.5);
|
|
transition: background .3s, color .3s;
|
|
}
|
|
.device-badge.on { background: rgba(34,197,94,.2); color: #22c55e; }
|
|
.device-badge.off { background: rgba(239,68,68,.15); color: #ef4444; }
|
|
</style>
|
|
|
|
<div class="dashboard-layout">
|
|
<?php require_once 'partials/sidebar.php'; ?>
|
|
|
|
<div class="main-content page-enter">
|
|
|
|
<!-- TOPBAR -->
|
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; flex-wrap:wrap; gap:10px;">
|
|
<div style="display:flex; align-items:center; gap:10px; flex-wrap:wrap;">
|
|
<div style="display:flex; align-items:center; color:rgba(255,255,255,.7); font-size:13px;">
|
|
<span id="conn-status"></span>
|
|
<span id="conn-label">Menghubungkan…</span>
|
|
<span id="last-update"></span>
|
|
</div>
|
|
<span class="device-badge" id="badge-relay">
|
|
|
|
</span>
|
|
<span class="device-badge" id="badge-lampu">
|
|
|
|
</span>
|
|
</div>
|
|
<div class="topbar-icons">
|
|
<div class="notif" id="notifBtn">
|
|
<i class="fas fa-bell"></i>
|
|
<span class="notif-badge">0</span>
|
|
<div class="notif-dropdown" id="notifDropdown">
|
|
<p><b>Notifikasi</b></p>
|
|
<ul><li>Memuat data…</li></ul>
|
|
</div>
|
|
</div>
|
|
<div class="user" id="userBtn">
|
|
<i class="fas fa-user"></i>
|
|
<div class="user-dropdown" id="userDropdown">
|
|
<p><?= htmlspecialchars($_SESSION['email'] ?? 'User') ?></p>
|
|
<a href="logout.php">Logout</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- KARTU ATAS: Amonia & Kekeruhan -->
|
|
<div class="mon-top-grid">
|
|
<div class="mon-circle-card">
|
|
<div class="circle-val" id="val-amonia">— mg/L<mg>
|
|
<L></L></div>
|
|
<div class="title">Amonia</div>
|
|
<div class="sensor-bar-wrap">
|
|
<div class="sensor-bar-fill" id="bar-amonia"></div>
|
|
</div>
|
|
</div>
|
|
<div class="mon-circle-card">
|
|
<div class="circle-val" id="val-distance">— cm</div>
|
|
<div class="title">Distance</div>
|
|
<div class="sensor-bar-wrap">
|
|
<div class="sensor-bar-fill" id="bar-distance"></div>
|
|
</div>
|
|
</div>
|
|
<div class="mon-circle-card">
|
|
<!-- kekeruhan = turbidity dari topic aquamonn/kekeruhan -->
|
|
<div class="circle-val" id="val-kekeruhan">— NTU</div>
|
|
<div class="title">Kekeruhan</div>
|
|
<div class="sensor-bar-wrap">
|
|
<div class="sensor-bar-fill" id="bar-kekeruhan"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- KARTU BAWAH: Suhu & pH -->
|
|
<div class="mon-bot-grid">
|
|
<!-- Suhu → field "suhu" -->
|
|
<div class="mon-pill-card">
|
|
<span style="font-size:1.5rem;">☁️</span>
|
|
<div style="text-align:right; line-height:1; flex:1;">
|
|
<span id="val-suhu" style="font-size:1.8rem; font-weight:800;">—</span><br>
|
|
<span style="font-size:0.8rem; font-weight:normal;">Celcius</span>
|
|
<div class="sensor-bar-wrap" style="margin-top:6px;">
|
|
<div class="sensor-bar-fill" id="bar-suhu"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- pH → field "ph" (Node-RED) / "pH" (ESP32) keduanya ditangani -->
|
|
<div class="mon-pill-card">
|
|
<div style="flex:1; text-align:left;">
|
|
<span id="val-ph" style="font-size:1.5rem;">— pH</span>
|
|
<div class="sensor-bar-wrap" style="margin-top:6px;">
|
|
<div class="sensor-bar-fill" id="bar-ph"></div>
|
|
</div>
|
|
</div>
|
|
<span style="font-size:1.5rem;">💧</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<?php require_once 'partials/footer.php'; ?>
|
|
|
|
<script>
|
|
/**
|
|
* NODERED_URL = URL server Node-RED (misal http://192.168.1.x:1880)
|
|
* node-red.js akan panggil NODERED_URL + "/get-sensor"
|
|
*
|
|
* FIREBASE_DB_URL dipakai sebagai fallback jika NODERED_URL tidak diset.
|
|
*
|
|
* Ganti IP sesuai server Node-RED kamu.
|
|
*/
|
|
|
|
window.NODERED_URL = "https://node-red.aquamonn.online";
|
|
window.FIREBASE_DB_URL = "<?= rtrim(FIREBASE_DB_URL, '/') ?>";
|
|
|
|
// Jika NODERED_URL tidak dikonfigurasi di PHP, kosongkan agar
|
|
// node-red.js fallback ke Firebase langsung.
|
|
if (!window.NODERED_URL) {
|
|
// Firebase path sesuai flow:
|
|
// https://aquascape-67707-default-rtdb.firebaseio.com/sensor.json
|
|
window.FIREBASE_DB_URL = window.FIREBASE_DB_URL || "https://aquascape-67707-default-rtdb.firebaseio.com";
|
|
}
|
|
|
|
window.onSensorUpdate = function(ok) {
|
|
const dot = document.getElementById("conn-status");
|
|
const label = document.getElementById("conn-label");
|
|
const ts = document.getElementById("last-update");
|
|
if (!dot) return;
|
|
dot.className = ok ? "online" : "offline";
|
|
label.textContent = ok ? "Live" : "Offline";
|
|
if (ok) ts.textContent = " · " + new Date().toLocaleTimeString("id-ID");
|
|
};
|
|
</script>
|
|
|
|
<script src="assets/js/node-red.js"></script>
|
|
|
|
<script>
|
|
const notifBtn = document.getElementById("notifBtn");
|
|
const notifDropdown = document.getElementById("notifDropdown");
|
|
const userBtn = document.getElementById("userBtn");
|
|
const userDropdown = document.getElementById("userDropdown");
|
|
|
|
notifBtn.onclick = e => {
|
|
e.stopPropagation();
|
|
notifDropdown.classList.toggle("show");
|
|
userDropdown.classList.remove("show");
|
|
};
|
|
userBtn.onclick = e => {
|
|
e.stopPropagation();
|
|
userDropdown.classList.toggle("show");
|
|
notifDropdown.classList.remove("show");
|
|
};
|
|
document.onclick = () => {
|
|
notifDropdown.classList.remove("show");
|
|
userDropdown.classList.remove("show");
|
|
};
|
|
</script>
|