#include "HtmlPages.h"
// ================================================================
// LOGIN PAGE
// ================================================================
const char home_login_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Water Pump Manager - Masuk</title>
<style>
body{
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
background:#f8fafc;
display:flex;
justify-content:center;
align-items:center;
height:100vh;
margin:0;
color:#1e293b;
}
.box{
background:#fff;
padding:40px 30px;
border-radius:16px;
width:100%;
max-width:360px;
box-shadow:0 10px 25px -5px rgba(0,0,0,0.08),0 8px 10px -6px rgba(0,0,0,0.06);
border:1px solid #e2e8f0;
box-sizing:border-box;
}
h2{
text-align:center;
margin:0 0 8px;
color:#0284c7;
font-size:24px;
font-weight:700;
}
.subtitle{
text-align:center;
margin:0 0 24px;
color:#64748b;
font-size:14px;
}
.input-group{
margin-bottom:16px;
}
.input-group label{
display:block;
margin-bottom:6px;
font-size:13px;
color:#64748b;
font-weight:500;
}
input{
width:100%;
padding:12px 14px;
border:1px solid #cbd5e1;
background:#fff;
color:#1e293b;
border-radius:8px;
font-size:15px;
box-sizing:border-box;
}
input:focus{
outline:none;
border-color:#0284c7;
}
button{
width:100%;
padding:12px;
background:#0284c7;
color:#fff;
border:none;
border-radius:8px;
font-size:15px;
font-weight:600;
cursor:pointer;
margin-top:8px;
}
.modal{
display:none;
position:fixed;
z-index:100;
left:0;
top:0;
width:100%;
height:100%;
background:rgba(15,23,42,.6);
justify-content:center;
align-items:center;
}
.modal-content{
background:#fff;
border:1px solid #e2e8f0;
padding:24px;
border-radius:12px;
width:85%;
max-width:320px;
text-align:center;
}
.modal-text{
font-size:15px;
color:#334155;
margin-bottom:20px;
}
.modal-btn{
background:#e2e8f0;
color:#1e293b;
border:none;
padding:10px 20px;
border-radius:6px;
cursor:pointer;
}
.modal-btn.err{
background:#ef4444;
color:#fff;
}
</style>
</head>
<body>
<div class="box">
<h2>Water Pump Control</h2>
<p class="subtitle">Masuk untuk mengelola sistem pompa</p>
<div class="input-group">
<label>Nama Pengguna</label>
<input id="u" placeholder="Contoh: admin" autocomplete="off">
</div>
<div class="input-group">
<label>Kata Sandi</label>
<input id="p" type="password" placeholder="Masukkan kata sandi">
</div>
<button onclick="login()">Masuk ke Panel</button>
</div>
<div id="popupModal" class="modal">
<div class="modal-content">
<div id="modalText" class="modal-text">Pesan Kesalahan</div>
<button id="modalBtn" class="modal-btn" onclick="closeModal()">Mengerti</button>
</div>
</div>
<script>
function tampilPesan(pesan, IsError=false){
document.getElementById('modalText').innerText = pesan;
const btn = document.getElementById('modalBtn');
btn.className = IsError ? "modal-btn err" : "modal-btn";
document.getElementById('popupModal').style.display = 'flex';
}
function closeModal(){
document.getElementById('popupModal').style.display = 'none';
}
function login(){
const u = document.getElementById('u').value;
const p = document.getElementById('p').value;
if(!u || !p){
tampilPesan('Nama pengguna dan kata sandi tidak boleh kosong.', true);
return;
}
const formData = new FormData();
formData.append('user', u);
formData.append('pass', p);
fetch('/login', { method:'POST', body:formData })
.then(r => {
if(r.ok){ location.href='/dashboard'; }
else if(r.status === 429){ tampilPesan('Terlalu banyak percobaan. Tunggu 30 detik.', true); }
else { tampilPesan('Nama pengguna atau kata sandi salah.', true); }
})
.catch(() => { tampilPesan('Gagal berkomunikasi dengan ESP32.', true); });
}
</script>
</body>
</html>
)rawliteral";
// ================================================================
// DASHBOARD PAGE (SUCTION & BOOSTER PUMPS + TELEMETRY)
// ================================================================
const char home_dashboard_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Dashboard Pompa Air</title>
<style>
:root{
--bg:#f8fafc;
--panel:#fff;
--border:#e2e8f0;
--text:#1e293b;
--muted:#64748b;
--accent:#0284c7;
--danger:#ef4444;
--success:#10b981;
}
body{
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
margin:0;
padding:16px;
background:var(--bg);
color:var(--text);
}
.container{
max-width:1000px;
margin:0 auto;
}
header{
display:flex;
justify-content:space-between;
align-items:center;
padding-bottom:16px;
border-bottom:1px solid var(--border);
margin-bottom:20px;
}
h1{
font-size:20px;
font-weight:700;
margin:0;
}
h1 span{
color:var(--accent);
}
.btn-logout{
padding:8px 16px;
background:rgba(239,68,68,.1);
color:var(--danger);
text-decoration:none;
border-radius:8px;
font-size:13px;
font-weight:600;
border:1px solid rgba(239,68,68,.2);
}
.status-bar{
background:var(--panel);
border:1px solid var(--border);
border-radius:12px;
padding:14px 20px;
margin-bottom:24px;
}
.status-flex{
display:flex;
flex-wrap:wrap;
gap:16px 30px;
}
.status-item{
display:flex;
align-items:center;
gap:6px;
color:var(--muted);
font-size:14px;
}
.indicator-online{
width:8px;
height:8px;
background:var(--success);
border-radius:50%;
box-shadow:0 0 8px var(--success);
}
.matrix-grid{
display:grid;
grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
gap:16px;
}
.node-card{
position:relative;
background:var(--panel);
border:1px solid var(--border);
border-radius:14px;
padding:18px;
display:flex;
flex-direction:column;
justify-content:space-between;
box-sizing:border-box;
min-height:160px;
}
.node-index{
font-size:11px;
font-weight:700;
color:var(--muted);
text-transform:uppercase;
letter-spacing:.5px;
display:block;
}
.node-title{
font-size:18px;
font-weight:600;
display:block;
margin:4px 0 12px 0;
}
.status-badge{
position:absolute;
top:20px;
right:20px;
width:10px;
height:10px;
border-radius:50%;
background:#cbd5e1;
}
.node-card.active-node{
border-color:rgba(16,185,129,.4);
}
.node-card.active-node .status-badge{
background:var(--success);
box-shadow:0 0 10px var(--success);
}
.pump-info{
font-size:13px;
color:var(--muted);
margin-bottom:12px;
}
.node-action-btn{
width:100%;
padding:10px 0;
font-size:13px;
font-weight:600;
border-radius:8px;
border:1px solid var(--border);
background:#f1f5f9;
color:var(--text);
cursor:pointer;
}
.node-card.active-node .node-action-btn{
background:var(--success);
color:#fff;
border:none;
}
@media(max-width:576px){
.matrix-grid{
grid-template-columns:1fr;
gap:12px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Dashboard <span>Pompa Air</span></h1>
<a href="/logout" class="btn-logout">Keluar Sesi</a>
</header>
<div class="status-bar" id="telemetry">
<div class="status-item">
<span class="indicator-online"></span>
Menghubungkan telemetri...
</div>
</div>
<div class="matrix-grid" id="matrix">
<!-- Suction Pump Card -->
<div class="node-card" id="card-suction">
<div class="status-badge"></div>
<div>
<span class="node-index">POMPA SUMUR / TANDON BAWAH</span>
<span class="node-title">Suction Pump</span>
<div class="pump-info" id="info-suction">Mode: Auto | Fault: None</div>
</div>
<button class="node-action-btn" onclick="togglePump('suction')">STATUS: MATI</button>
</div>
<!-- Booster Pump Card -->
<div class="node-card" id="card-booster">
<div class="status-badge"></div>
<div>
<span class="node-index">POMPA PENDORONG RUMAH</span>
<span class="node-title">Booster Pump</span>
<div class="pump-info" id="info-booster">Mode: Auto | Fault: None</div>
</div>
<button class="node-action-btn" onclick="togglePump('booster')">STATUS: MATI</button>
</div>
</div>
</div>
<script>
let ws, isBusy = false;
function init(){
ws = new WebSocket(`ws://${location.hostname}/ws`);
ws.onopen = () => console.log("WebSocket Terhubung");
ws.onmessage = onMsg;
ws.onclose = () => setTimeout(init, 2000);
}
function onMsg(e){
try{
const d = JSON.parse(e.data);
// Update Telemetri Bar
document.getElementById('telemetry').innerHTML = `
<div class="status-flex">
<div class="status-item"><span class="indicator-online"></span>Sistem: <strong>Normal</strong></div>
<div class="status-item">Uptime: <strong>${d.uptime||'—'}</strong></div>
<div class="status-item">Level Tandon: <strong>${d.sensors ? d.sensors.tankLevel.toFixed(1) : 0}%</strong></div>
<div class="status-item">Tekanan: <strong>${d.sensors ? d.sensors.pressure.toFixed(2) : 0} Bar</strong></div>
<div class="status-item">WiFi: <strong>${d.rssi||0}%</strong></div>
</div>
`;
// Update Suction State
if(d.suction){
const sCard = document.getElementById('card-suction');
const sInfo = document.getElementById('info-suction');
const sBtn = sCard.querySelector('.node-action-btn');
sCard.classList.toggle('active-node', d.suction.running);
sInfo.innerText = `Mode: ${d.suction.mode === 1 ? 'Manual' : 'Auto'} | Fault: ${d.suction.fault}`;
sBtn.innerText = d.suction.running ? 'MATIKAN' : 'NYALAKAN';
}
// Update Booster State
if(d.booster){
const bCard = document.getElementById('card-booster');
const bInfo = document.getElementById('info-booster');
const bBtn = bCard.querySelector('.node-action-btn');
bCard.classList.toggle('active-node', d.booster.running);
bInfo.innerText = `Mode: ${d.booster.mode === 1 ? 'Manual' : 'Auto'} | Fault: ${d.booster.fault}`;
bBtn.innerText = d.booster.running ? 'MATIKAN' : 'NYALAKAN';
}
}catch(err){}
}
function togglePump(pumpName){
if(isBusy || ws.readyState !== 1) return;
isBusy = true;
ws.send("pump:" + pumpName + ":toggle");
setTimeout(() => { isBusy = false; }, 400);
}
window.onload = init;
</script>
</body>
</html>
)rawliteral";
// ================================================================
// PROVISIONING PAGE (AP MODE CONFIGURATION)
// ================================================================
const char home_provisioning_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Pengaturan Awal Water Pump Manager</title>
<style>
body{
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
margin:0;
padding:20px;
background:#f8fafc;
color:#1e293b;
display:flex;
justify-content:center;
align-items:center;
min-height:100vh;
}
.container{
max-width:420px;
width:100%;
background:#fff;
padding:30px;
border-radius:16px;
border:1px solid #e2e8f0;
box-shadow:0 10px 25px -5px rgba(0,0,0,.08);
box-sizing:border-box;
}
h2{
text-align:center;
color:#0284c7;
margin:0 0 8px;
font-size:22px;
font-weight:700;
}
.desc{
text-align:center;
font-size:13px;
color:#64748b;
margin:-4px 0 24px;
line-height:1.4;
}
h3{
font-size:14px;
color:#334155;
border-bottom:1px solid #e2e8f0;
padding-bottom:6px;
margin:20px 0 12px;
text-transform:uppercase;
letter-spacing:.5px;
}
label{
display:block;
margin-top:10px;
font-size:13px;
color:#64748b;
font-weight:500;
}
input{
width:100%;
padding:11px 14px;
margin:6px 0 12px;
border:1px solid #cbd5e1;
background:#fff;
color:#1e293b;
border-radius:8px;
box-sizing:border-box;
font-size:14px;
}
button{
width:100%;
padding:14px;
background:#0284c7;
color:#fff;
border:none;
border-radius:8px;
font-size:15px;
font-weight:600;
cursor:pointer;
margin-top:16px;
}
</style>
</head>
<body>
<div class="container">
<h2>Konfigurasi Sistem Pompa</h2>
<p class="desc">Sistem mendeteksi instalasi baru atau kehilangan koneksi WiFi. Silakan atur jaringan Anda.</p>
<form action="/save-home-config" method="POST">
<h3>1. Sambungan WiFi</h3>
<label>Nama Jaringan WiFi (SSID)</label>
<input type="text" name="ssid" placeholder="Masukkan SSID WiFi" required autocomplete="off">
<label>Kata Sandi WiFi</label>
<input type="password" name="password" placeholder="Kata sandi WiFi">
<h3>2. Keamanan Akses Panel</h3>
<label>Kata Sandi Admin Baru</label>
<input type="password" name="admin_pass" placeholder="Minimal 6 karakter" required minlength="6">
<button type="submit">Simpan & Jalankan Sistem</button>
</form>
</div>
</body>
</html>
)rawliteral";