422 lines
9.8 KiB
HTML
422 lines
9.8 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 () => {
|
|
try {
|
|
// Fetch testTrack from config first
|
|
const configRes = await fetch('/api/config');
|
|
const config = await configRes.json();
|
|
const track = config.testTrack || 1000;
|
|
await fetch(`/api/play?track=${track}`);
|
|
} catch (e) { console.error(e); }
|
|
});
|
|
|
|
document.getElementById('btnStop').addEventListener('click', async () => {
|
|
try {
|
|
await fetch('/api/stop');
|
|
} catch (e) { console.error(e); }
|
|
});
|
|
|
|
|
|
</script>
|
|
</body>
|
|
|
|
</html> |