213 lines
8.7 KiB
HTML
213 lines
8.7 KiB
HTML
<!-- data/index.html -->
|
|
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
<title>Bel Sekolah</title>
|
|
<style>
|
|
/* Inline Tailwind CSS for offline compatibility */
|
|
.bg-gradient-to-b { background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); }
|
|
.from-slate-50 { --tw-gradient-from: #f8fafc; --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(248, 250, 252, 0)); }
|
|
.to-white { --tw-gradient-to: #ffffff; }
|
|
.min-h-screen { min-height: 100vh; }
|
|
.font-sans { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; }
|
|
.text-slate-800 { --tw-text-opacity: 1; color: rgb(30 41 59 / var(--tw-text-opacity)); }
|
|
.max-w-2xl { max-width: 42rem; }
|
|
.mx-auto { margin-left: auto; margin-right: auto; }
|
|
.p-6 { padding: 1.5rem; }
|
|
.text-center { text-align: center; }
|
|
.mb-6 { margin-bottom: 1.5rem; }
|
|
.text-5xl { font-size: 3rem; line-height: 1; }
|
|
.text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
|
|
.font-bold { font-weight: 700; }
|
|
.text-blue-600 { --tw-text-opacity: 1; color: rgb(37 99 235 / var(--tw-text-opacity)); }
|
|
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
|
|
.text-slate-600 { --tw-text-opacity: 1; color: rgb(71 85 105 / var(--tw-text-opacity)); }
|
|
.mt-1 { margin-top: 0.25rem; }
|
|
.bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); }
|
|
.shadow { --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-width 0px), var(--tw-ring-shadow), var(--tw-shadow); }
|
|
.rounded-xl { border-radius: 0.75rem; }
|
|
.mb-4 { margin-bottom: 1rem; }
|
|
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
|
|
.mb-1 { margin-bottom: 0.25rem; }
|
|
.text-5xl { font-size: 3rem; line-height: 1; }
|
|
.font-extrabold { font-weight: 800; }
|
|
.tracking-wider { letter-spacing: 0.05em; }
|
|
.text-slate-500 { --tw-text-opacity: 1; color: rgb(100 116 139 / var(--tw-text-opacity)); }
|
|
.mt-2 { margin-top: 0.5rem; }
|
|
.flex { display: flex; }
|
|
.items-center { align-items: center; }
|
|
.justify-center { justify-content: center; }
|
|
.gap-4 { gap: 1rem; }
|
|
.my-3 { margin-top: 0.75rem; margin-bottom: 0.75rem; }
|
|
.w-3 { width: 0.75rem; }
|
|
.h-3 { height: 0.75rem; }
|
|
.rounded-full { border-radius: 9999px; }
|
|
.bg-gray-300 { --tw-bg-opacity: 1; background-color: rgb(209 213 219 / var(--tw-bg-opacity)); }
|
|
.text-slate-700 { --tw-text-opacity: 1; color: rgb(51 65 85 / var(--tw-text-opacity)); }
|
|
.grid { display: grid; }
|
|
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
.sm\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
.gap-3 { gap: 0.75rem; }
|
|
.mt-6 { margin-top: 1.5rem; }
|
|
.max-w-md { max-width: 28rem; }
|
|
.bg-slate-50 { --tw-bg-opacity: 1; background-color: rgb(248 250 252 / var(--tw-bg-opacity)); }
|
|
.p-3 { padding: 0.75rem; }
|
|
.rounded { border-radius: 0.25rem; }
|
|
.h-40 { height: 10rem; }
|
|
.overflow-auto { overflow: auto; }
|
|
.text-xs { font-size: 0.75rem; line-height: 1rem; }
|
|
.text-slate-500 { --tw-text-opacity: 1; color: rgb(100 116 139 / var(--tw-text-opacity)); }
|
|
.mt-6 { margin-top: 1.5rem; }
|
|
/* Button styles */
|
|
.btn {
|
|
display: inline-block;
|
|
padding: 0.5rem 0.9rem;
|
|
border-radius: 8px;
|
|
color: white;
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
|
}
|
|
.btn.small { padding: 0.25rem 0.5rem; font-size: 0.9rem; }
|
|
.btn.btn-blue { background: #2563EB; }
|
|
.btn.btn-green { background: #059669; }
|
|
.btn.btn-red { background: #DC2626; }
|
|
.btn.btn-gray { background: #6B7280; }
|
|
.btn.btn-purple { background: #7C3AED; }
|
|
.btn:hover { filter: brightness(0.95); }
|
|
/* Responsive */
|
|
@media (max-width: 640px) {
|
|
.btn { padding: 0.45rem 0.7rem; font-size: 0.9rem; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-gradient-to-b from-slate-50 to-white min-h-screen font-sans text-slate-800">
|
|
<div class="max-w-2xl mx-auto p-6">
|
|
<header class="text-center mb-6">
|
|
<div class="text-5xl">🔔</div>
|
|
<h1 class="text-3xl font-bold text-blue-600">Bel Sekolah</h1>
|
|
<div id="schoolName" class="text-sm text-slate-600 mt-1">SMA Negeri</div>
|
|
</header>
|
|
|
|
<main class="bg-white shadow rounded-xl p-6">
|
|
<div class="text-center mb-4">
|
|
<div id="hari" class="text-lg text-slate-600 mb-1"></div>
|
|
<div id="clock" class="text-5xl font-extrabold tracking-wider">--.--.--</div>
|
|
<div id="tanggal" class="text-slate-500 mt-2"></div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-center gap-4 my-3">
|
|
<div id="statusDot" class="w-3 h-3 rounded-full bg-gray-300"></div>
|
|
<div id="statusText" class="text-slate-700">Memuat...</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 mt-6 max-w-md mx-auto">
|
|
<button id="btnTest" class="btn btn-green">Tes Bel</button>
|
|
<button id="btnStop" class="btn btn-red">Stop</button>
|
|
<a href="/setting.html" class="btn btn-blue">Pengaturan</a>
|
|
</div>
|
|
|
|
<div id="log" class="mt-6 bg-slate-50 p-3 rounded h-40 overflow-auto text-sm text-slate-700"></div>
|
|
</main>
|
|
|
|
<footer class="text-center text-slate-500 text-xs mt-6">
|
|
© Wartana 2025 — Bel Sekolah Otomatis
|
|
</footer>
|
|
</div>
|
|
|
|
<script>
|
|
// WebSocket connection (uses same host)
|
|
const protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
|
const ws = new WebSocket(protocol + location.host + '/ws');
|
|
|
|
const hariEl = document.getElementById('hari');
|
|
const clockEl = document.getElementById('clock');
|
|
const tanggalEl = document.getElementById('tanggal');
|
|
const statusDot = document.getElementById('statusDot');
|
|
const statusText = document.getElementById('statusText');
|
|
const schoolEl = document.getElementById('schoolName');
|
|
const logEl = document.getElementById('log');
|
|
|
|
function appendLog(s) {
|
|
const t = document.createElement('div');
|
|
t.textContent = (new Date()).toLocaleTimeString() + " — " + s;
|
|
logEl.prepend(t);
|
|
}
|
|
|
|
ws.onopen = () => {
|
|
appendLog("🔌 WebSocket tersambung.");
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
appendLog("⚠️ WebSocket terputus.");
|
|
statusDot.style.background = 'gray';
|
|
statusText.textContent = 'Terputus';
|
|
};
|
|
|
|
ws.onmessage = (ev) => {
|
|
try {
|
|
const msg = JSON.parse(ev.data);
|
|
if (msg.type === 'time') {
|
|
const h = String(msg.jam).padStart(2,'0');
|
|
const m = String(msg.menit).padStart(2,'0');
|
|
const s = String(msg.detik).padStart(2,'0');
|
|
clockEl.textContent = `${h}.${m}.${s}`;
|
|
const weekday = ["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"];
|
|
hariEl.textContent = weekday[msg.weekday || new Date(msg.epoch*1000).getDay()];
|
|
const monthNames = ["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember"];
|
|
tanggalEl.textContent = `${msg.hari} ${monthNames[(msg.bulan||(new Date(msg.epoch*1000)).getMonth())-1]} ${msg.tahun}`;
|
|
} else if (msg.type === 'status') {
|
|
if (msg.playing) {
|
|
statusDot.style.background = 'green';
|
|
statusText.textContent = `Bel berbunyi (Track ${msg.track})`;
|
|
} else {
|
|
statusDot.style.background = 'gray';
|
|
statusText.textContent = 'Bel siaga';
|
|
}
|
|
if (msg.schoolName) schoolEl.textContent = msg.schoolName;
|
|
|
|
// Update clients info
|
|
const clientsInfoEl = document.getElementById('clientsInfo');
|
|
if (msg.clientList && msg.clientList.length > 0) {
|
|
let info = '';
|
|
msg.clientList.forEach((client, index) => {
|
|
if (index > 0) info += ', ';
|
|
info += `${client.mac}: ${client.percentage}%`;
|
|
});
|
|
clientsInfoEl.textContent = info;
|
|
} else {
|
|
clientsInfoEl.textContent = '';
|
|
}
|
|
} else if (msg.type === 'log') {
|
|
appendLog(msg.msg);
|
|
}
|
|
} catch(e) {
|
|
console.error("ws onmessage parse err", e);
|
|
}
|
|
};
|
|
|
|
// Buttons
|
|
document.getElementById('btnTest').addEventListener('click', async () => {
|
|
// call protected API -> will prompt basic auth
|
|
// test uses track 1000 (same as button click)
|
|
try {
|
|
await fetch('/api/play?track=1000');
|
|
} catch(e) { console.error(e); }
|
|
});
|
|
|
|
document.getElementById('btnStop').addEventListener('click', async () => {
|
|
try {
|
|
await fetch('/api/stop');
|
|
} catch(e) { console.error(e); }
|
|
});
|
|
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|