38554-vm/index.php
Flatlogic Bot 5ccb5169a6 vvv
2026-02-18 08:58:32 +00:00

362 lines
15 KiB
PHP

<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('Asia/Jakarta');
session_start();
require_once __DIR__ . '/db/config.php';
$pdo = db();
$pdo->exec(
'CREATE TABLE IF NOT EXISTS sahur_settings ('
. 'id INT PRIMARY KEY, '
. 'sahur_hour TINYINT NOT NULL, '
. 'sahur_minute TINYINT NOT NULL, '
. 'updated_at DATETIME NOT NULL'
. ')'
);
$pdo->exec(
'CREATE TABLE IF NOT EXISTS sahur_events ('
. 'id INT AUTO_INCREMENT PRIMARY KEY, '
. 'event_type VARCHAR(32) NOT NULL, '
. 'message VARCHAR(255) NOT NULL, '
. 'meta TEXT NULL, '
. 'created_at DATETIME NOT NULL'
. ')'
);
$settings = $pdo->query('SELECT id, sahur_hour, sahur_minute, updated_at FROM sahur_settings WHERE id = 1')->fetch();
if (!$settings) {
$stmt = $pdo->prepare('INSERT INTO sahur_settings (id, sahur_hour, sahur_minute, updated_at) VALUES (1, :hour, :minute, NOW())');
$stmt->execute([':hour' => 3, ':minute' => 0]);
$settings = ['id' => 1, 'sahur_hour' => 3, 'sahur_minute' => 0, 'updated_at' => date('Y-m-d H:i:s')];
}
$flash = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);
function redirect_with_flash(string $type, string $message): void {
$_SESSION['flash'] = ['type' => $type, 'message' => $message];
header('Location: /');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'upload') {
if (!isset($_FILES['sahur_file']) || $_FILES['sahur_file']['error'] !== UPLOAD_ERR_OK) {
redirect_with_flash('danger', 'Upload gagal. Pilih file MP3 yang valid.');
}
$file = $_FILES['sahur_file'];
if ($file['size'] > 15000000) {
redirect_with_flash('danger', 'Ukuran file terlalu besar. Maksimum 15MB.');
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = $finfo ? finfo_file($finfo, $file['tmp_name']) : '';
if ($finfo) {
finfo_close($finfo);
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($mime !== 'audio/mpeg' || $ext !== 'mp3') {
redirect_with_flash('danger', 'Format file harus MP3 (audio/mpeg).');
}
$uploadDir = __DIR__ . '/uploads';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0775, true);
}
$targetPath = $uploadDir . '/sahur.mp3';
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
redirect_with_flash('danger', 'Upload gagal saat menyimpan file.');
}
$meta = json_encode([
'original_name' => $file['name'],
'size' => $file['size'],
]);
$stmt = $pdo->prepare('INSERT INTO sahur_events (event_type, message, meta, created_at) VALUES (:type, :message, :meta, NOW())');
$stmt->execute([
':type' => 'upload',
':message' => 'File sahur.mp3 diperbarui',
':meta' => $meta,
]);
redirect_with_flash('success', 'File sahur.mp3 berhasil diunggah dan diganti.');
}
if ($action === 'set_schedule') {
$hour = filter_input(INPUT_POST, 'sahur_hour', FILTER_VALIDATE_INT);
$minute = filter_input(INPUT_POST, 'sahur_minute', FILTER_VALIDATE_INT);
if ($hour === false || $minute === false || $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59) {
redirect_with_flash('danger', 'Jam sahur tidak valid. Masukkan 0-23 untuk jam dan 0-59 untuk menit.');
}
$stmt = $pdo->prepare('UPDATE sahur_settings SET sahur_hour = :hour, sahur_minute = :minute, updated_at = NOW() WHERE id = 1');
$stmt->execute([':hour' => $hour, ':minute' => $minute]);
$meta = json_encode(['hour' => $hour, 'minute' => $minute]);
$stmt = $pdo->prepare('INSERT INTO sahur_events (event_type, message, meta, created_at) VALUES (:type, :message, :meta, NOW())');
$stmt->execute([
':type' => 'schedule',
':message' => 'Jadwal sahur diperbarui',
':meta' => $meta,
]);
redirect_with_flash('success', 'Jadwal sahur berhasil diperbarui.');
}
if ($action === 'test_now') {
$stmt = $pdo->prepare('INSERT INTO sahur_events (event_type, message, meta, created_at) VALUES (:type, :message, :meta, NOW())');
$stmt->execute([
':type' => 'test',
':message' => 'Tes manual dipicu dari dashboard',
':meta' => null,
]);
redirect_with_flash('success', 'Tes sahur dicatat. Integrasi bot akan memutar audio saat tersedia.');
}
}
$audioPath = __DIR__ . '/uploads/sahur.mp3';
$fileExists = is_file($audioPath);
$fileSize = $fileExists ? filesize($audioPath) : null;
$fileUpdated = $fileExists ? date('Y-m-d H:i', filemtime($audioPath)) : null;
$now = new DateTime('now');
$nextRun = (clone $now)->setTime((int)$settings['sahur_hour'], (int)$settings['sahur_minute'], 0);
if ($nextRun <= $now) {
$nextRun->modify('+1 day');
}
$events = $pdo->query('SELECT id, event_type, message, created_at FROM sahur_events ORDER BY id DESC LIMIT 8')->fetchAll();
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sahur Bot Control Center</title>
<?php if ($projectDescription): ?>
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<meta name="robots" content="noindex, nofollow" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/assets/css/custom.css?v=<?= htmlspecialchars((string)time()) ?>" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg bg-white border-bottom sticky-top">
<div class="container">
<a class="navbar-brand fw-semibold" href="/">Sahur Bot Control</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMain" aria-controls="navMain" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navMain">
<div class="navbar-nav ms-auto">
<a class="nav-link" href="#status">Status</a>
<a class="nav-link" href="#upload">Upload</a>
<a class="nav-link" href="#schedule">Jadwal</a>
<a class="nav-link" href="#log">Log</a>
</div>
</div>
</div>
</nav>
<header class="hero border-bottom">
<div class="container py-5">
<div class="row align-items-center g-4">
<div class="col-lg-7">
<p class="text-uppercase text-muted small mb-2">Dashboard Sahur</p>
<h1 class="display-6 fw-semibold mb-3">Kendalikan jadwal sahur dan audio bot dalam satu tempat.</h1>
<p class="text-muted mb-4">Upload MP3, ubah jam sahur, dan catat tes manual. Status file dan jadwal berikutnya selalu terlihat jelas.</p>
<div class="d-flex gap-3">
<a class="btn btn-dark btn-lg" href="#upload">Upload MP3</a>
<a class="btn btn-outline-secondary btn-lg" href="#schedule">Ubah Jadwal</a>
</div>
</div>
<div class="col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body">
<p class="text-muted small mb-1">Next run</p>
<h2 class="h4 fw-semibold mb-2"><?= htmlspecialchars($nextRun->format('d M Y, H:i')) ?> WIB</h2>
<div class="d-flex flex-wrap gap-2">
<span class="badge bg-light text-dark border"><?= htmlspecialchars(sprintf('%02d:%02d', $settings['sahur_hour'], $settings['sahur_minute'])) ?> (jadwal aktif)</span>
<span class="badge bg-light text-dark border"><?= $fileExists ? 'File MP3 tersedia' : 'File MP3 belum ada' ?></span>
</div>
<p class="text-muted small mt-3 mb-0">Pastikan bot Discord kamu membaca jadwal yang sama.</p>
</div>
</div>
</div>
</div>
</div>
</header>
<main class="container py-5">
<?php if ($flash): ?>
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> alert-dismissible fade show" role="alert">
<?= htmlspecialchars($flash['message']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<section id="status" class="mb-5">
<div class="section-head">
<h2 class="h5 fw-semibold mb-1">Status Saat Ini</h2>
<p class="text-muted small">Pantau kesiapan audio dan jadwal sahur berikutnya.</p>
</div>
<div class="row g-3">
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<p class="text-muted small mb-1">Audio sahur</p>
<h3 class="h6 fw-semibold mb-2"><?= $fileExists ? 'Tersedia' : 'Belum tersedia' ?></h3>
<p class="text-muted small mb-0"><?= $fileExists ? 'Ukuran: ' . number_format($fileSize / 1024, 1) . ' KB' : 'Upload file sahur.mp3 agar bot dapat memutar.' ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<p class="text-muted small mb-1">Update terakhir</p>
<h3 class="h6 fw-semibold mb-2"><?= $fileUpdated ? htmlspecialchars($fileUpdated . ' WIB') : '—' ?></h3>
<p class="text-muted small mb-0">Penggantian file terakhir.</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<p class="text-muted small mb-1">Next run</p>
<h3 class="h6 fw-semibold mb-2"><?= htmlspecialchars($nextRun->format('d M Y, H:i')) ?> WIB</h3>
<p class="text-muted small mb-0">Jadwal sahur berdasarkan setting saat ini.</p>
</div>
</div>
</div>
</div>
<?php if (!$fileExists): ?>
<div class="alert alert-warning mt-3 mb-0" role="alert">
File sahur.mp3 belum ada. Bot Discord harus menampilkan peringatan di channel teks saat file belum tersedia.
</div>
<?php endif; ?>
</section>
<section id="upload" class="mb-5">
<div class="section-head">
<h2 class="h5 fw-semibold mb-1">Upload MP3 Sahur</h2>
<p class="text-muted small">Drag &amp; drop atau klik untuk memilih file. File akan ditimpa menjadi sahur.mp3.</p>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body">
<form class="row g-3" action="/" method="post" enctype="multipart/form-data">
<input type="hidden" name="action" value="upload" />
<div class="col-12">
<div class="dropzone" data-dropzone>
<input class="form-control" type="file" name="sahur_file" accept=".mp3,audio/mpeg" required>
<div class="dropzone-placeholder">
<span class="fw-semibold">Tarik file ke sini</span>
<span class="text-muted small">atau klik untuk memilih</span>
<span class="text-muted small" data-file-name>Belum ada file dipilih</span>
</div>
</div>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-dark">Upload &amp; Ganti File</button>
</div>
</form>
</div>
</div>
</section>
<section id="schedule" class="mb-5">
<div class="section-head">
<h2 class="h5 fw-semibold mb-1">Jadwal Sahur</h2>
<p class="text-muted small">Atur jam sahur harian yang akan digunakan bot.</p>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body">
<form class="row g-3 align-items-end" action="/" method="post">
<input type="hidden" name="action" value="set_schedule" />
<div class="col-md-4">
<label class="form-label" for="sahur_hour">Jam</label>
<input class="form-control" type="number" id="sahur_hour" name="sahur_hour" min="0" max="23" value="<?= htmlspecialchars((string)$settings['sahur_hour']) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label" for="sahur_minute">Menit</label>
<input class="form-control" type="number" id="sahur_minute" name="sahur_minute" min="0" max="59" value="<?= htmlspecialchars((string)$settings['sahur_minute']) ?>" required>
</div>
<div class="col-md-4 d-flex gap-2">
<button type="submit" class="btn btn-dark flex-fill">Simpan Jadwal</button>
<button type="submit" class="btn btn-outline-secondary flex-fill" name="action" value="test_now">Tes Sekarang</button>
</div>
</form>
</div>
</div>
</section>
<section id="log" class="mb-5">
<div class="section-head">
<h2 class="h5 fw-semibold mb-1">Log Aktivitas</h2>
<p class="text-muted small">Riwayat upload, perubahan jadwal, dan tes manual.</p>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body">
<?php if (!$events): ?>
<p class="text-muted mb-0">Belum ada aktivitas. Mulai dengan upload file sahur pertama.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th scope="col">Waktu</th>
<th scope="col">Tipe</th>
<th scope="col">Pesan</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<?php foreach ($events as $event): ?>
<tr>
<td class="text-muted small"><?= htmlspecialchars($event['created_at']) ?></td>
<td><span class="badge bg-light text-dark border text-uppercase"><?= htmlspecialchars($event['event_type']) ?></span></td>
<td><?= htmlspecialchars($event['message']) ?></td>
<td class="text-end"><a class="link-dark" href="/event.php?id=<?= htmlspecialchars((string)$event['id']) ?>">Detail</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</section>
</main>
<footer class="border-top py-4">
<div class="container d-flex flex-column flex-md-row justify-content-between gap-2">
<span class="text-muted small">Sahur Bot Dashboard (MVP)</span>
<span class="text-muted small">Zona waktu: WIB (Asia/Jakarta)</span>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="/assets/js/main.js?v=<?= htmlspecialchars((string)time()) ?>" defer></script>
</body>
</html>