Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
<?php
require_once '../init.php';
requireLogin();
header('Content-Type: application/json');
$action = $_POST['action'] ?? $_GET['action'] ?? '';
try {
switch ($action) {
case 'add':
$name = trim($_POST['name'] ?? '');
$url = trim($_POST['url'] ?? '');
$display_order = (int)($_POST['display_order'] ?? 0);
$status = $_POST['status'] ?? 'normal';
$logo_url = trim($_POST['logo_url'] ?? '');
if (!$name || !$url) {
throw new Exception("ชื่อและลิงค์เว็บไซต์เป็นข้อมูลจำเป็น");
}
$stmt = $pdo->prepare("INSERT INTO websites (name, url, status, logo_url, display_order) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$name, $url, $status, $logo_url, $display_order]);
logAction($pdo, 'Add Website', "Added website: $name ($url)");
echo json_encode(['success' => true]);
break;
case 'edit':
$id = (int)($_POST['id'] ?? 0);
$name = trim($_POST['name'] ?? '');
$url = trim($_POST['url'] ?? '');
$display_order = (int)($_POST['display_order'] ?? 0);
$status = $_POST['status'] ?? 'normal';
$logo_url = trim($_POST['logo_url'] ?? '');
if (!$id || !$name || !$url) {
throw new Exception("ข้อมูลไม่ครบถ้วน");
}
$stmt = $pdo->prepare("UPDATE websites SET name = ?, url = ?, status = ?, logo_url = ?, display_order = ? WHERE id = ?");
$stmt->execute([$name, $url, $status, $logo_url, $display_order, $id]);
logAction($pdo, 'Edit Website', "Edited website ID $id to: $name");
echo json_encode(['success' => true]);
break;
case 'delete':
$id = (int)($_POST['id'] ?? 0);
if (!$id) throw new Exception("ไม่พบ ID");
$stmt = $pdo->prepare("SELECT name FROM websites WHERE id = ?");
$stmt->execute([$id]);
$web = $stmt->fetch();
if ($web) {
$stmt = $pdo->prepare("DELETE FROM websites WHERE id = ?");
$stmt->execute([$id]);
logAction($pdo, 'Delete Website', "Deleted website: {$web['name']}");
}
echo json_encode(['success' => true]);
break;
case 'reset_clicks':
$id = (int)($_POST['id'] ?? 0);
if (!$id) throw new Exception("ไม่พบ ID");
$stmt = $pdo->prepare("SELECT name FROM websites WHERE id = ?");
$stmt->execute([$id]);
$web = $stmt->fetch();
if ($web) {
$stmt = $pdo->prepare("UPDATE websites SET click_count = 0 WHERE id = ?");
$stmt->execute([$id]);
logAction($pdo, 'Reset Clicks', "Reset click count for website: {$web['name']}");
}
echo json_encode(['success' => true]);
break;
case 'save_settings':
$site_name = trim($_POST['site_name'] ?? '');
$site_subtitle = trim($_POST['site_subtitle'] ?? '');
if (!$site_name) throw new Exception("ชื่อระบบเป็นข้อมูลจำเป็น");
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
$stmt->execute(['site_name', $site_name]);
$stmt->execute(['site_subtitle', $site_subtitle]);
logAction($pdo, 'Update Settings', "Updated site settings");
echo json_encode(['success' => true]);
break;
default:
throw new Exception("Invalid action");
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
+300
View File
@@ -0,0 +1,300 @@
<?php
require_once '../init.php';
requireLogin();
$stmt = $pdo->query("SELECT * FROM websites ORDER BY display_order ASC");
$websites = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>จัดการเว็บไซต์ - <?= htmlspecialchars($globalSettings['site_name'] ?? 'ศูนย์รวมเว็บหน่วยงาน') ?></title>
<link rel="icon" type="image/png" href="../assets/images/logo.png">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="../assets/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Datatables -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="antialiased min-h-screen bg-gray-50 py-10 px-4">
<div class="w-full max-w-6xl mx-auto glass-container">
<div class="flex flex-wrap justify-between items-center mb-8 border-b border-gray-200 pb-4 gap-4">
<h1 class="text-2xl font-bold text-gray-800"><i class="fa-solid fa-list mr-2"></i> จัดการเว็บไซต์</h1>
<div class="flex flex-wrap gap-2">
<a href="settings.php" class="magic-btn-outline"><i class="fa-solid fa-gear mr-1"></i> ตั้งค่าระบบ</a>
<a href="logs.php" class="magic-btn-outline"><i class="fa-solid fa-history mr-1"></i> ประวัติการทำงาน</a>
<a href="../index.php" class="magic-btn-outline"><i class="fa-solid fa-home mr-1"></i> หน้าหลัก</a>
<a href="logout.php" class="bg-red-500 hover:bg-red-600 text-white py-2 px-4 rounded-xl transition"><i class="fa-solid fa-right-from-bracket mr-1"></i> ออกจากระบบ</a>
</div>
</div>
<div class="mb-6 flex flex-wrap gap-2">
<button onclick="openModal()" class="magic-btn"><i class="fa-solid fa-plus mr-1"></i> เพิ่มเว็บไซต์ใหม่</button>
<button onclick="addMockData()" class="magic-btn-outline"><i class="fa-solid fa-vial mr-1"></i> เพิ่มข้อมูลตัวอย่าง 10 รายการ</button>
<button onclick="clearMockData()" class="bg-orange-500 hover:bg-orange-600 text-white py-2 px-4 rounded-xl transition shadow-sm"><i class="fa-solid fa-trash-can mr-1"></i> ล้างข้อมูลตัวอย่าง</button>
</div>
<div class="bg-white rounded-xl shadow-sm p-4 border border-gray-100 overflow-x-auto">
<table id="websitesTable" class="w-full border-collapse">
<thead>
<tr class="bg-gray-50">
<th class="p-3 border-b text-center whitespace-nowrap">ลำดับ</th>
<th class="p-3 border-b text-center whitespace-nowrap">โลโก้</th>
<th class="p-3 border-b text-center whitespace-nowrap">ชื่อเว็บ</th>
<th class="p-3 border-b text-center whitespace-nowrap">ลิงก์</th>
<th class="p-3 border-b text-center whitespace-nowrap">สถานะ</th>
<th class="p-3 border-b text-center whitespace-nowrap">สถิติ (คลิก)</th>
<th class="p-3 border-b text-center whitespace-nowrap">จัดการ</th>
</tr>
</thead>
<tbody>
<?php foreach($websites as $web): ?>
<tr>
<td class="p-3 border-b text-center whitespace-nowrap"><?= htmlspecialchars($web['display_order']) ?></td>
<td class="p-3 border-b text-center whitespace-nowrap">
<?php $logo = !empty($web['logo_url']) ? htmlspecialchars($web['logo_url']) : '../assets/images/logo.png'; ?>
<img src="<?= $logo ?>" alt="logo" class="w-10 h-10 object-contain rounded bg-gray-50 border border-gray-100 mx-auto" onerror="this.src='../assets/images/logo.png'">
</td>
<td class="p-3 border-b font-medium"><?= htmlspecialchars($web['name']) ?> <?php if($web['is_mock']) echo '<span class="text-xs bg-gray-200 text-gray-600 px-2 py-1 rounded ml-2">ตัวอย่าง</span>'; ?></td>
<td class="p-3 border-b text-center">
<a href="<?= htmlspecialchars($web['url']) ?>" target="_blank" class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-blue-50 text-blue-500 hover:bg-blue-100 hover:text-blue-700 transition" title="<?= htmlspecialchars($web['url']) ?>">
<i class="fa-solid fa-link"></i>
</a>
</td>
<td class="p-3 border-b text-center whitespace-nowrap">
<?php
if($web['status'] == 'normal') echo '<span class="px-2 py-1 bg-green-100 text-green-800 rounded-full text-xs">ปกติ</span>';
elseif($web['status'] == 'cancelled') echo '<span class="px-2 py-1 bg-red-100 text-red-800 rounded-full text-xs">ยกเลิก</span>';
elseif($web['status'] == 'maintenance') echo '<span class="px-2 py-1 bg-orange-100 text-orange-800 rounded-full text-xs">ปรับปรุง</span>';
?>
</td>
<td class="p-3 border-b text-center font-bold text-gray-700 whitespace-nowrap"><?= number_format($web['click_count']) ?></td>
<td class="p-3 border-b text-center whitespace-nowrap">
<button onclick="editWeb(<?= $web['id'] ?>, '<?= htmlspecialchars(addslashes($web['name'])) ?>', '<?= htmlspecialchars(addslashes($web['url'])) ?>', <?= $web['display_order'] ?>, '<?= htmlspecialchars(addslashes($web['logo_url'] ?? '')) ?>', '<?= $web['status'] ?>')" class="text-blue-500 hover:text-blue-700 mr-2" title="แก้ไข"><i class="fa-solid fa-pen-to-square text-lg"></i></button>
<button onclick="resetClicks(<?= $web['id'] ?>, '<?= htmlspecialchars(addslashes($web['name'])) ?>')" class="text-orange-500 hover:text-orange-700 mr-2" title="เคลียร์สถิติการเข้าชม"><i class="fa-solid fa-eraser text-lg"></i></button>
<button onclick="deleteWeb(<?= $web['id'] ?>)" class="text-red-500 hover:text-red-700" title="ลบ"><i class="fa-solid fa-trash text-lg"></i></button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal Form -->
<div id="formModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50 px-4">
<div class="bg-white rounded-2xl p-6 w-full max-w-md shadow-2xl transform transition-all">
<h2 id="modalTitle" class="text-xl font-bold mb-4">เพิ่มเว็บไซต์ใหม่</h2>
<form id="webForm" onsubmit="saveWeb(event)">
<input type="hidden" id="webId" name="id" value="">
<input type="hidden" id="actionType" name="action" value="add">
<div class="mb-4">
<label class="block text-gray-700 mb-2 font-medium">ชื่อเว็บไซต์</label>
<input type="text" id="webName" name="name" class="magic-input border border-gray-300" required>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2 font-medium">ลิงค์ (URL)</label>
<input type="url" id="webUrl" name="url" class="magic-input border border-gray-300" placeholder="https://" required>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2 font-medium">ลิงก์โลโก้ (ปล่อยว่างเพื่อใช้โลโก้เริ่มต้น)</label>
<input type="text" id="webLogo" name="logo_url" class="magic-input border border-gray-300" placeholder="https://.../logo.png">
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2 font-medium">สถานะ</label>
<select id="webStatus" name="status" class="magic-input border border-gray-300">
<option value="normal">ใช้งานปกติ</option>
<option value="cancelled">ยกเลิกใช้งาน</option>
<option value="maintenance">ปรับปรุง</option>
</select>
</div>
<div class="mb-6">
<label class="block text-gray-700 mb-2 font-medium">ลำดับการแสดงผล (ตัวเลข)</label>
<input type="number" id="webOrder" name="display_order" class="magic-input border border-gray-300" value="0" required>
</div>
<div class="flex justify-end gap-2">
<button type="button" onclick="closeModal()" class="px-4 py-2 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-xl transition font-medium">ยกเลิก</button>
<button type="submit" class="magic-btn">บันทึก</button>
</div>
</form>
</div>
</div>
<script>
$(document).ready(function() {
$('#websitesTable').DataTable({
"language": {
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
},
"order": [[ 0, "asc" ]]
});
});
function openModal() {
document.getElementById('formModal').classList.remove('hidden');
document.getElementById('formModal').classList.add('flex');
document.getElementById('modalTitle').innerText = 'เพิ่มเว็บไซต์ใหม่';
document.getElementById('webForm').reset();
document.getElementById('webId').value = '';
document.getElementById('actionType').value = 'add';
document.getElementById('webStatus').value = 'normal';
}
function closeModal() {
document.getElementById('formModal').classList.add('hidden');
document.getElementById('formModal').classList.remove('flex');
}
function editWeb(id, name, url, order, logo_url, status) {
openModal();
document.getElementById('modalTitle').innerText = 'แก้ไขเว็บไซต์';
document.getElementById('webId').value = id;
document.getElementById('webName').value = name;
document.getElementById('webUrl').value = url;
document.getElementById('webOrder').value = order;
document.getElementById('webLogo').value = logo_url;
document.getElementById('webStatus').value = status;
document.getElementById('actionType').value = 'edit';
}
function saveWeb(e) {
e.preventDefault();
const formData = new FormData(e.target);
fetch('api.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if(data.success) {
Swal.fire('สำเร็จ', 'บันทึกข้อมูลเรียบร้อยแล้ว', 'success').then(() => {
location.reload();
});
} else {
Swal.fire('ข้อผิดพลาด', data.message, 'error');
}
})
.catch(error => {
Swal.fire('ข้อผิดพลาด', 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้', 'error');
});
}
function deleteWeb(id) {
Swal.fire({
title: 'ยืนยันการลบ?',
text: "คุณต้องการลบเว็บไซต์นี้ใช่หรือไม่!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'ใช่, ลบเลย!',
cancelButtonText: 'ยกเลิก'
}).then((result) => {
if (result.isConfirmed) {
const formData = new FormData();
formData.append('action', 'delete');
formData.append('id', id);
fetch('api.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if(data.success) {
Swal.fire('ลบแล้ว!', 'ลบข้อมูลเรียบร้อยแล้ว.', 'success').then(() => {
location.reload();
});
} else {
Swal.fire('ข้อผิดพลาด', data.message, 'error');
}
});
}
})
}
function resetClicks(id, name) {
Swal.fire({
title: 'ยืนยันการเคลียร์สถิติ?',
text: `คุณต้องการรีเซ็ตยอดผู้เข้าชมของเว็บ ${name} ให้กลับเป็น 0 ใช่หรือไม่?`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#f97316',
cancelButtonColor: '#3085d6',
confirmButtonText: 'ใช่, เคลียร์เลย!',
cancelButtonText: 'ยกเลิก'
}).then((result) => {
if (result.isConfirmed) {
const formData = new FormData();
formData.append('action', 'reset_clicks');
formData.append('id', id);
fetch('api.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if(data.success) {
Swal.fire('สำเร็จ!', 'เคลียร์สถิติเรียบร้อยแล้ว.', 'success').then(() => {
location.reload();
});
} else {
Swal.fire('ข้อผิดพลาด', data.message, 'error');
}
});
}
});
}
function addMockData() {
fetch('mock_data.php?action=add')
.then(response => response.json())
.then(data => {
if(data.success) {
Swal.fire('สำเร็จ', 'เพิ่มข้อมูลตัวอย่าง 10 รายการแล้ว', 'success').then(() => {
location.reload();
});
}
});
}
function clearMockData() {
Swal.fire({
title: 'ยืนยันการล้างข้อมูล?',
text: "ลบเฉพาะข้อมูลที่ถูกสร้างเป็นตัวอย่างเท่านั้น",
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'ล้างข้อมูล',
cancelButtonText: 'ยกเลิก'
}).then((result) => {
if (result.isConfirmed) {
fetch('mock_data.php?action=clear')
.then(response => response.json())
.then(data => {
if(data.success) {
Swal.fire('สำเร็จ', 'ล้างข้อมูลตัวอย่างแล้ว', 'success').then(() => {
location.reload();
});
}
});
}
});
}
</script>
</body>
</html>
+61
View File
@@ -0,0 +1,61 @@
<?php
require_once '../init.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$password = $_POST['password'] ?? '';
if ($password === ADMIN_PASSWORD) {
$_SESSION['admin_logged_in'] = true;
logAction($pdo, 'Login', 'Admin logged in successfully');
header("Location: index.php");
exit;
} else {
$error = 'รหัสผ่านไม่ถูกต้อง';
logAction($pdo, 'Login Failed', 'Failed login attempt with incorrect password');
}
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>เข้าสู่ระบบจัดการ - <?= htmlspecialchars($globalSettings['site_name'] ?? 'ศูนย์รวมเว็บหน่วยงาน') ?></title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="../assets/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body class="antialiased min-h-screen flex items-center justify-center p-4">
<div class="w-full max-w-md glass-container">
<div class="text-center mb-8">
<h1 class="text-2xl font-bold text-gray-800 mb-2">ระบบจัดการหลังบ้าน</h1>
<p class="text-gray-600">กรุณาระบุรหัสผ่านเพื่อเข้าใช้งาน</p>
</div>
<?php if($error): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4 text-center">
<?= $error ?>
</div>
<?php endif; ?>
<form method="POST" action="">
<div class="mb-6">
<input type="password" name="password" class="magic-input" placeholder="รหัสผ่าน" required autofocus>
</div>
<button type="submit" class="magic-btn w-full">
<i class="fa-solid fa-right-to-bracket mr-2"></i> เข้าสู่ระบบ
</button>
</form>
<div class="mt-6 text-center">
<a href="../index.php" class="text-emerald-600 hover:text-emerald-800 text-sm">
<i class="fa-solid fa-arrow-left mr-1"></i> กลับไปหน้าหลัก
</a>
</div>
</div>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
<?php
session_start();
session_destroy();
header("Location: login.php");
exit;
+80
View File
@@ -0,0 +1,80 @@
<?php
require_once '../init.php';
requireLogin();
$stmt = $pdo->query("SELECT * FROM activity_logs ORDER BY created_at DESC");
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ประวัติการทำงาน - <?= htmlspecialchars($globalSettings['site_name'] ?? 'ศูนย์รวมเว็บหน่วยงาน') ?></title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="../assets/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Datatables -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
</head>
<body class="antialiased min-h-screen bg-gray-50 py-10 px-4">
<div class="w-full max-w-6xl mx-auto glass-container">
<div class="flex flex-wrap justify-between items-center mb-8 border-b border-gray-200 pb-4 gap-4">
<h1 class="text-2xl font-bold text-gray-800"><i class="fa-solid fa-history mr-2"></i> ประวัติการทำงาน</h1>
<div class="flex gap-2">
<a href="index.php" class="magic-btn-outline"><i class="fa-solid fa-arrow-left mr-1"></i> กลับไปจัดการเว็บไซต์</a>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-4 border border-gray-100 overflow-x-auto">
<table id="logsTable" class="w-full border-collapse">
<thead>
<tr class="bg-gray-50">
<th class="p-3 border-b text-center whitespace-nowrap">วัน-เวลา</th>
<th class="p-3 border-b text-center whitespace-nowrap">การกระทำ</th>
<th class="p-3 border-b text-center whitespace-nowrap">รายละเอียด</th>
<th class="p-3 border-b text-center whitespace-nowrap">IP</th>
</tr>
</thead>
<tbody>
<?php foreach($logs as $log): ?>
<tr>
<td class="p-3 border-b text-sm text-gray-600 text-center whitespace-nowrap"><?= htmlspecialchars($log['created_at']) ?></td>
<td class="p-3 border-b font-medium text-center whitespace-nowrap">
<?php
$badgeColor = 'bg-gray-100 text-gray-800';
if(strpos($log['action'], 'Add') !== false) $badgeColor = 'bg-green-100 text-green-800';
else if(strpos($log['action'], 'Edit') !== false) $badgeColor = 'bg-blue-100 text-blue-800';
else if(strpos($log['action'], 'Delete') !== false) $badgeColor = 'bg-red-100 text-red-800';
else if(strpos($log['action'], 'Login') !== false) $badgeColor = 'bg-purple-100 text-purple-800';
else if(strpos($log['action'], 'Mock') !== false) $badgeColor = 'bg-orange-100 text-orange-800';
?>
<span class="px-2 py-1 rounded text-xs <?= $badgeColor ?>"><?= htmlspecialchars($log['action']) ?></span>
</td>
<td class="p-3 border-b text-gray-700 text-center whitespace-nowrap"><?= htmlspecialchars($log['details']) ?></td>
<td class="p-3 border-b text-sm text-gray-500 text-center whitespace-nowrap"><?= htmlspecialchars($log['ip_address']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<script>
$(document).ready(function() {
$('#logsTable').DataTable({
"language": {
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
},
"order": [[ 0, "desc" ]]
});
});
</script>
</body>
</html>
+53
View File
@@ -0,0 +1,53 @@
<?php
require_once '../init.php';
requireLogin();
header('Content-Type: application/json');
$action = $_GET['action'] ?? '';
try {
if ($action === 'add') {
$mockData = [
['กระทรวงสาธารณสุข', 'https://www.moph.go.th/', 1, 'normal', ''],
['กรมควบคุมโรค', 'https://ddc.moph.go.th/', 2, 'normal', ''],
['กรมอนามัย', 'https://anamai.moph.go.th/', 3, 'normal', ''],
['กรมการแพทย์', 'https://www.dms.go.th/', 4, 'maintenance', ''],
['สำนักงานคณะกรรมการอาหารและยา', 'https://www.fda.moph.go.th/', 5, 'normal', ''],
['สถาบันวัคซีนแห่งชาติ', 'https://nvi.go.th/', 6, 'normal', ''],
['องค์การเภสัชกรรม', 'https://www.gpo.or.th/', 7, 'normal', ''],
['กรมสุขภาพจิต', 'https://dmh.go.th/', 8, 'normal', ''],
['สำนักงานหลักประกันสุขภาพแห่งชาติ', 'https://www.nhso.go.th/', 9, 'normal', ''],
['แพทยสภา (ทดสอบยกเลิก)', 'https://tmc.or.th/', 10, 'cancelled', ''],
['กระทรวงศึกษาธิการ', 'https://www.moe.go.th/', 11, 'normal', ''],
['กระทรวงมหาดไทย', 'https://www.moi.go.th/', 12, 'normal', ''],
['กระทรวงการคลัง', 'https://www.mof.go.th/', 13, 'normal', ''],
['กระทรวงพาณิชย์', 'https://www.moc.go.th/', 14, 'normal', ''],
['กระทรวงเกษตรและสหกรณ์', 'https://www.moac.go.th/', 15, 'normal', ''],
['กระทรวงคมนาคม', 'https://www.mot.go.th/', 16, 'normal', ''],
['กระทรวงดิจิทัลเพื่อเศรษฐกิจและสังคม', 'https://www.mdes.go.th/', 17, 'normal', ''],
['กระทรวงแรงงาน', 'https://www.mol.go.th/', 18, 'normal', ''],
['กระทรวงอุตสาหกรรม', 'https://www.industry.go.th/', 19, 'normal', ''],
['กระทรวงการต่างประเทศ', 'https://www.mfa.go.th/', 20, 'normal', '']
];
$stmt = $pdo->prepare("INSERT INTO websites (name, url, display_order, status, logo_url, click_count, is_mock) VALUES (?, ?, ?, ?, ?, 0, 1)");
foreach ($mockData as $data) {
$stmt->execute($data);
}
logAction($pdo, 'Mock Data Add', 'Added 20 mock websites');
echo json_encode(['success' => true]);
} elseif ($action === 'clear') {
$pdo->exec("DELETE FROM websites WHERE is_mock = 1");
logAction($pdo, 'Mock Data Clear', 'Cleared all mock websites');
echo json_encode(['success' => true]);
} else {
throw new Exception("Invalid action");
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
+75
View File
@@ -0,0 +1,75 @@
<?php
require_once '../init.php';
requireLogin();
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ตั้งค่าระบบ - <?= htmlspecialchars($globalSettings['site_name'] ?? 'ศูนย์รวมเว็บหน่วยงาน') ?></title>
<link rel="icon" type="image/png" href="../assets/images/logo.png">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="../assets/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="antialiased min-h-screen bg-gray-50 py-10 px-4">
<div class="w-full max-w-4xl mx-auto glass-container">
<div class="flex flex-wrap justify-between items-center mb-8 border-b border-gray-200 pb-4 gap-4">
<h1 class="text-2xl font-bold text-gray-800"><i class="fa-solid fa-gear mr-2"></i> ตั้งค่าระบบ</h1>
<div class="flex flex-wrap gap-2">
<a href="index.php" class="magic-btn-outline"><i class="fa-solid fa-list mr-1"></i> จัดการเว็บไซต์</a>
<a href="../index.php" class="magic-btn-outline"><i class="fa-solid fa-home mr-1"></i> หน้าหลัก</a>
<a href="logout.php" class="bg-red-500 hover:bg-red-600 text-white py-2 px-4 rounded-xl transition"><i class="fa-solid fa-right-from-bracket mr-1"></i> ออกจากระบบ</a>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
<form id="settingsForm" onsubmit="saveSettings(event)">
<input type="hidden" name="action" value="save_settings">
<div class="mb-4">
<label class="block text-gray-700 mb-2 font-medium">ชื่อระบบ (Site Name)</label>
<input type="text" name="site_name" class="magic-input border border-gray-300" value="<?= htmlspecialchars($globalSettings['site_name'] ?? '') ?>" required>
</div>
<div class="mb-6">
<label class="block text-gray-700 mb-2 font-medium">คำอธิบายระบบ (Site Subtitle)</label>
<input type="text" name="site_subtitle" class="magic-input border border-gray-300" value="<?= htmlspecialchars($globalSettings['site_subtitle'] ?? '') ?>">
</div>
<button type="submit" class="magic-btn w-full md:w-auto"><i class="fa-solid fa-save mr-1"></i> บันทึกการตั้งค่า</button>
</form>
</div>
</div>
<script>
function saveSettings(e) {
e.preventDefault();
const formData = new FormData(e.target);
fetch('api.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if(data.success) {
Swal.fire('สำเร็จ', 'บันทึกการตั้งค่าเรียบร้อยแล้ว', 'success').then(() => {
location.reload();
});
} else {
Swal.fire('ข้อผิดพลาด', data.message, 'error');
}
})
.catch(error => {
Swal.fire('ข้อผิดพลาด', 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้', 'error');
});
}
</script>
</body>
</html>
+197
View File
@@ -0,0 +1,197 @@
@import url('https://fonts.googleapis.css2?family=Sarabun:wght@300;400;500;600;700&display=swap');
:root {
--primary-color: #047857; /* Emerald 700 - Professional green */
--primary-hover: #059669;
--bg-color: #f0fdf4; /* Very light green background */
--card-bg: rgba(255, 255, 255, 0.7);
--text-color: #1f2937;
--border-color: rgba(255, 255, 255, 0.5);
--glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.05);
}
body {
font-family: 'Sarabun', sans-serif;
background-color: #f8fafc;
background-image:
radial-gradient(at 40% 20%, hsla(160,100%,74%,0.15) 0px, transparent 50%),
radial-gradient(at 80% 0%, hsla(189,100%,56%,0.15) 0px, transparent 50%),
radial-gradient(at 0% 50%, hsla(355,100%,93%,0.1) 0px, transparent 50%);
background-attachment: fixed;
color: var(--text-color);
min-height: 100vh;
margin: 0;
}
.glass-container {
background: var(--card-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--border-color);
box-shadow: var(--glass-shadow);
border-radius: 24px;
padding: 2rem;
}
.magic-card {
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.8);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.02);
border-radius: 20px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
position: relative;
text-decoration: none;
display: block;
height: 100%;
}
.magic-card::before {
content: "";
position: absolute;
inset: 0;
border-radius: 20px;
padding: 2px;
background: linear-gradient(120deg, rgba(255,255,255,0) 30%, rgba(255,255,255,0.8), rgba(255,255,255,0) 70%);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0;
transition: opacity 0.5s ease;
}
.magic-card:hover {
transform: translateY(-6px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
background: rgba(255, 255, 255, 0.8);
}
.magic-card:hover::before {
opacity: 1;
}
.magic-input {
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
padding: 12px 20px;
width: 100%;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-family: 'Sarabun', sans-serif;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
}
.magic-input:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.04);
border-color: rgba(0, 0, 0, 0.15);
}
.magic-input:focus {
outline: none;
background: #ffffff;
box-shadow: 0 0 0 4px rgba(4, 120, 87, 0.15), 0 10px 15px -3px rgba(0, 0, 0, 0.05);
border-color: var(--primary-color);
}
.magic-btn {
background: var(--primary-color);
color: white;
border: none;
border-radius: 12px;
padding: 10px 24px;
font-family: 'Sarabun', sans-serif;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.magic-btn:hover {
background: var(--primary-hover);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(4, 120, 87, 0.3);
}
.magic-btn-outline {
background: transparent;
color: var(--primary-color);
border: 2px solid var(--primary-color);
border-radius: 12px;
padding: 8px 22px;
font-family: 'Sarabun', sans-serif;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.magic-btn-outline:hover {
background: var(--primary-color);
color: white;
}
.logo-img {
height: 90px;
object-fit: contain;
filter: drop-shadow(0 4px 6px rgba(0,0,0,0.1));
}
/* Custom Datatable styling to match magic UI */
table.dataTable.no-footer {
border-bottom: 1px solid rgba(0,0,0,0.1) !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current,
.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
background: var(--primary-color) !important;
color: white !important;
border: none !important;
border-radius: 8px !important;
box-shadow: 0 2px 4px rgba(4, 120, 87, 0.2);
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
border-radius: 8px !important;
border: 1px solid transparent !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
background: rgba(255,255,255,0.5) !important;
border: 1px solid rgba(0,0,0,0.1) !important;
color: var(--text-color) !important;
}
.dataTables_wrapper .dataTables_length select,
.dataTables_wrapper .dataTables_filter input {
border-radius: 8px;
border: 1px solid rgba(0,0,0,0.2);
padding: 6px 10px;
outline: none;
font-family: 'Sarabun', sans-serif;
}
.dataTables_wrapper .dataTables_filter input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(4, 120, 87, 0.1);
}
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter {
margin-bottom: 1.25rem;
}
table.dataTable thead th,
table.dataTable thead td {
text-align: center !important;
}
table.dataTable tbody td {
vertical-align: middle;
}
/* Custom Scrollbar */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(0,0,0,0.02);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(4, 120, 87, 0.2);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(4, 120, 87, 0.4);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

+9
View File
@@ -0,0 +1,9 @@
<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '@Samui@10742');
define('DB_NAME', 'ksh_web_portal');
// Admin Password
define('ADMIN_PASSWORD', 'ijawa2467');
+24
View File
@@ -0,0 +1,24 @@
CREATE DATABASE IF NOT EXISTS `one_stop_web_service` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `one_stop_web_service`;
CREATE TABLE IF NOT EXISTS `websites` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`url` text NOT NULL,
`status` enum('normal','cancelled','maintenance') NOT NULL DEFAULT 'normal',
`logo_url` text NULL,
`click_count` int(11) NOT NULL DEFAULT 0,
`display_order` int(11) NOT NULL DEFAULT 0,
`is_mock` tinyint(1) NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `activity_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`action` varchar(255) NOT NULL,
`details` text NOT NULL,
`ip_address` varchar(45) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+26
View File
@@ -0,0 +1,26 @@
<?php
require_once 'init.php';
header('Content-Type: application/json');
try {
$stmt = $pdo->query("SELECT * FROM websites ORDER BY display_order ASC");
$websites = $stmt->fetchAll(PDO::FETCH_ASSOC);
$summary = [
'total' => count($websites),
'normal' => 0,
'maintenance' => 0,
'cancelled' => 0
];
foreach ($websites as $row) {
if(isset($summary[$row['status']])) {
$summary[$row['status']]++;
}
}
echo json_encode(['success' => true, 'websites' => $websites, 'summary' => $summary]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
+29
View File
@@ -0,0 +1,29 @@
<?php
require_once 'init.php';
$id = (int)($_GET['id'] ?? 0);
if ($id > 0) {
$stmt = $pdo->prepare("SELECT url, status FROM websites WHERE id = ?");
$stmt->execute([$id]);
$web = $stmt->fetch();
if ($web && $web['status'] === 'normal') {
// Increment click count
$update = $pdo->prepare("UPDATE websites SET click_count = click_count + 1 WHERE id = ?");
$update->execute([$id]);
// Redirect
header("Location: " . $web['url']);
exit;
} else if ($web) {
// If not normal, shouldn't be called directly since JS should catch it,
// but just redirect back to index if it happens
header("Location: index.php");
exit;
}
}
// Fallback
header("Location: index.php");
exit;
+267
View File
@@ -0,0 +1,267 @@
<?php
require_once 'init.php';
// Fetch websites
$stmt = $pdo->query("SELECT * FROM websites ORDER BY display_order ASC, name ASC");
$websites = $stmt->fetchAll(PDO::FETCH_ASSOC);
$summary = ['total' => count($websites), 'normal' => 0, 'maintenance' => 0, 'cancelled' => 0];
foreach ($websites as $web) {
if(isset($summary[$web['status']])) {
$summary[$web['status']]++;
}
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($globalSettings['site_name'] ?? 'ศูนย์รวมเว็บหน่วยงาน') ?></title>
<link rel="icon" type="image/png" href="assets/images/logo.png">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="assets/css/style.css">
<!-- FontAwesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="antialiased min-h-screen flex flex-col items-center py-10 px-4">
<div class="w-full max-w-6xl glass-container">
<div class="text-center mb-10">
<!-- Logo -->
<img src="assets/images/logo.png" alt="Logo" class="logo-img mx-auto mb-4" onerror="this.src='https://via.placeholder.com/150x150?text=LOGO'">
<h1 class="text-3xl md:text-4xl font-bold text-gray-800 mb-2"><?= htmlspecialchars($globalSettings['site_name'] ?? 'ศูนย์รวมเว็บหน่วยงาน') ?></h1>
<p class="text-gray-600 text-lg"><?= htmlspecialchars($globalSettings['site_subtitle'] ?? 'One Stop Web Service') ?></p>
</div>
<!-- Status Summary Cards -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8" id="statusSummary">
<div id="filter-card-all" onclick="setStatusFilter('all')" class="cursor-pointer bg-white rounded-xl shadow-sm border border-gray-100 p-4 text-center transform hover:-translate-y-1 transition duration-300 ring-2 ring-emerald-500 opacity-100">
<div class="text-gray-500 text-sm font-medium mb-1">ทั้งหมด</div>
<div class="text-2xl font-bold text-gray-800"><span id="sum-total"><?= $summary['total'] ?></span></div>
</div>
<div id="filter-card-normal" onclick="setStatusFilter('normal')" class="cursor-pointer bg-green-50 rounded-xl shadow-sm border border-green-100 p-4 text-center transform hover:-translate-y-1 transition duration-300 opacity-60 hover:opacity-100">
<div class="text-green-600 text-sm font-medium mb-1"><i class="fa-solid fa-circle-check mr-1"></i>ปกติ</div>
<div class="text-2xl font-bold text-green-700"><span id="sum-normal"><?= $summary['normal'] ?></span></div>
</div>
<div id="filter-card-maintenance" onclick="setStatusFilter('maintenance')" class="cursor-pointer bg-orange-50 rounded-xl shadow-sm border border-orange-100 p-4 text-center transform hover:-translate-y-1 transition duration-300 opacity-60 hover:opacity-100">
<div class="text-orange-600 text-sm font-medium mb-1"><i class="fa-solid fa-person-digging mr-1"></i>ปรับปรุง</div>
<div class="text-2xl font-bold text-orange-700"><span id="sum-maintenance"><?= $summary['maintenance'] ?></span></div>
</div>
<div id="filter-card-cancelled" onclick="setStatusFilter('cancelled')" class="cursor-pointer bg-red-50 rounded-xl shadow-sm border border-red-100 p-4 text-center transform hover:-translate-y-1 transition duration-300 opacity-60 hover:opacity-100">
<div class="text-red-600 text-sm font-medium mb-1"><i class="fa-solid fa-circle-xmark mr-1"></i>ยกเลิก</div>
<div class="text-2xl font-bold text-red-700"><span id="sum-cancelled"><?= $summary['cancelled'] ?></span></div>
</div>
</div>
<div class="flex justify-between items-center mb-8 flex-wrap gap-4">
<div class="w-full md:w-1/2 lg:w-1/3 relative">
<i class="fa-solid fa-magnifying-glass absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
<input type="text" id="searchInput" class="magic-input pl-10" placeholder="ค้นหาเว็บไซต์...">
</div>
<div class="flex gap-2 items-center w-full md:w-auto mt-4 md:mt-0">
<a href="admin/login.php" class="magic-btn-outline text-sm">
<i class="fa-solid fa-gear mr-2"></i> ระบบจัดการ
</a>
</div>
</div>
<div class="max-h-[500px] overflow-y-auto pr-2 custom-scrollbar">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4" id="websitesContainer">
<?php if(count($websites) > 0): ?>
<?php foreach($websites as $web): ?>
<?php
$targetUrl = ($web['status'] == 'normal') ? 'go.php?id='.$web['id'] : '#';
$logoUrl = !empty($web['logo_url']) ? htmlspecialchars($web['logo_url']) : 'assets/images/logo.png';
?>
<a href="<?= $targetUrl ?>" <?= ($web['status'] == 'normal') ? 'target="_blank"' : '' ?> data-status="<?= $web['status'] ?>" data-name="<?= htmlspecialchars($web['name']) ?>" class="magic-card p-4 flex flex-row items-center text-left website-item relative group">
<?php if($web['status'] == 'cancelled'): ?>
<div class="absolute top-3 right-3"><span class="flex h-3 w-3 relative"><span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span><span class="relative inline-flex rounded-full h-3 w-3 bg-red-500" title="ยกเลิกใช้งาน"></span></span></div>
<?php elseif($web['status'] == 'maintenance'): ?>
<div class="absolute top-3 right-3"><span class="flex h-3 w-3 relative"><span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-400 opacity-75"></span><span class="relative inline-flex rounded-full h-3 w-3 bg-orange-500" title="ปรับปรุง"></span></span></div>
<?php endif; ?>
<div class="w-12 h-12 flex-shrink-0 rounded-full bg-white flex items-center justify-center mr-4 shadow-sm border border-gray-100 overflow-hidden logo-container">
<img src="<?= $logoUrl ?>" alt="logo" class="w-full h-full object-cover" onerror="this.src='assets/images/logo.png'">
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-base text-gray-800 web-name truncate"><?= htmlspecialchars($web['name']) ?></h3>
<span class="hidden web-url"><?= htmlspecialchars($web['url']) ?></span>
</div>
<div class="ml-2 flex-shrink-0 text-xs px-2 py-1 bg-gray-100 text-gray-500 rounded-lg">
<i class="fa-solid fa-mouse-pointer mr-1"></i> เปิดแล้ว <span id="click-count-<?= $web['id'] ?>"><?= number_format($web['click_count']) ?></span> ครั้ง
</div>
</a>
<?php endforeach; ?>
<?php else: ?>
<div class="col-span-full text-center py-10 text-gray-500">
<i class="fa-solid fa-folder-open text-4xl mb-3 text-gray-300"></i>
<p>ยังไม่มีข้อมูลเว็บไซต์</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
<script>
function bindClickEvents() {
document.querySelectorAll('.website-item').forEach(item => {
item.addEventListener('click', function(e) {
const status = this.getAttribute('data-status');
const name = this.getAttribute('data-name');
if (status === 'cancelled') {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'ยกเลิกการใช้งาน',
text: `เว็บไซต์ ${name} ถูกยกเลิกการใช้งานแล้ว`,
confirmButtonColor: '#047857'
});
} else if (status === 'maintenance') {
e.preventDefault();
Swal.fire({
icon: 'warning',
title: 'ปิดปรับปรุง',
text: `เว็บไซต์ ${name} กำลังอยู่ในช่วงปิดปรับปรุงระบบชั่วคราว`,
confirmButtonColor: '#047857'
});
}
});
});
}
bindClickEvents();
let currentStatusFilter = 'all';
function setStatusFilter(status) {
currentStatusFilter = status;
const allStatuses = ['all', 'normal', 'maintenance', 'cancelled'];
allStatuses.forEach(s => {
const card = document.getElementById('filter-card-' + s);
if(s === status) {
card.classList.remove('opacity-60', 'hover:opacity-100');
card.classList.add('opacity-100', 'ring-2', 'ring-emerald-500');
} else {
card.classList.add('opacity-60', 'hover:opacity-100');
card.classList.remove('opacity-100', 'ring-2', 'ring-emerald-500');
}
});
filterItems();
}
function filterItems() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const items = document.querySelectorAll('.website-item');
items.forEach(item => {
const name = item.querySelector('.web-name').innerText.toLowerCase();
const url = item.querySelector('.web-url').innerText.toLowerCase();
const status = item.getAttribute('data-status');
const matchesSearch = name.includes(searchTerm) || url.includes(searchTerm);
const matchesStatus = (currentStatusFilter === 'all' || status === currentStatusFilter);
if(matchesSearch && matchesStatus) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
}
// Simple client-side search filtering
document.getElementById('searchInput').addEventListener('input', filterItems);
function escapeHtml(unsafe) {
return (unsafe || '').toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
let lastDataString = '';
// Real-time list and stats polling (every 3 seconds)
setInterval(() => {
fetch('get_stats.php')
.then(res => res.json())
.then(data => {
if (data.success) {
const currentDataString = JSON.stringify(data.websites);
if (currentDataString !== lastDataString) {
lastDataString = currentDataString;
// Update Summary
document.getElementById('sum-total').innerText = data.summary.total;
document.getElementById('sum-normal').innerText = data.summary.normal;
document.getElementById('sum-maintenance').innerText = data.summary.maintenance;
document.getElementById('sum-cancelled').innerText = data.summary.cancelled;
// Re-render websites
const container = document.getElementById('websitesContainer');
if (data.websites.length === 0) {
container.innerHTML = `
<div class="col-span-full text-center py-10 text-gray-500">
<i class="fa-solid fa-folder-open text-4xl mb-3 text-gray-300"></i>
<p>ยังไม่มีข้อมูลเว็บไซต์</p>
</div>
`;
} else {
let html = '';
data.websites.forEach(web => {
const targetUrl = (web.status === 'normal') ? 'go.php?id=' + web.id : '#';
const logoUrl = web.logo_url ? escapeHtml(web.logo_url) : 'assets/images/logo.png';
const clickCount = new Intl.NumberFormat('en-US').format(web.click_count);
let badgeHtml = '';
if(web.status === 'cancelled') {
badgeHtml = `<div class="absolute top-3 right-3"><span class="flex h-3 w-3 relative"><span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span><span class="relative inline-flex rounded-full h-3 w-3 bg-red-500" title="ยกเลิกใช้งาน"></span></span></div>`;
} else if(web.status === 'maintenance') {
badgeHtml = `<div class="absolute top-3 right-3"><span class="flex h-3 w-3 relative"><span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-400 opacity-75"></span><span class="relative inline-flex rounded-full h-3 w-3 bg-orange-500" title="ปรับปรุง"></span></span></div>`;
}
const targetAttr = (web.status === 'normal') ? 'target="_blank"' : '';
html += `
<a href="${targetUrl}" ${targetAttr} data-status="${web.status}" data-name="${escapeHtml(web.name)}" class="magic-card p-4 flex flex-row items-center text-left website-item relative group">
${badgeHtml}
<div class="w-12 h-12 flex-shrink-0 rounded-full bg-white flex items-center justify-center mr-4 shadow-sm border border-gray-100 overflow-hidden logo-container">
<img src="${logoUrl}" alt="logo" class="w-full h-full object-cover" onerror="this.src='assets/images/logo.png'">
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-base text-gray-800 web-name truncate">${escapeHtml(web.name)}</h3>
<span class="hidden web-url">${escapeHtml(web.url)}</span>
</div>
<div class="ml-2 flex-shrink-0 text-xs px-2 py-1 bg-gray-100 text-gray-500 rounded-lg">
<i class="fa-solid fa-mouse-pointer mr-1"></i> เปิดแล้ว <span id="click-count-${web.id}">${clickCount}</span> ครั้ง
</div>
</a>
`;
});
container.innerHTML = html;
}
// Re-bind click handlers and re-apply search filter
bindClickEvents();
filterItems();
}
}
})
.catch(err => console.error('Error fetching data:', err));
}, 3000);
</script>
</body>
</html>
+85
View File
@@ -0,0 +1,85 @@
<?php
session_start();
require_once __DIR__ . '/config.php';
try {
$pdo = new PDO("mysql:host=" . DB_HOST, DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Create DB if not exists
$pdo->exec("CREATE DATABASE IF NOT EXISTS `" . DB_NAME . "` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$pdo->exec("USE `" . DB_NAME . "`");
// Create tables
$pdo->exec("
CREATE TABLE IF NOT EXISTS `websites` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`url` text NOT NULL,
`status` enum('normal','cancelled','maintenance') NOT NULL DEFAULT 'normal',
`logo_url` text NULL,
`click_count` int(11) NOT NULL DEFAULT 0,
`display_order` int(11) NOT NULL DEFAULT 0,
`is_mock` tinyint(1) NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
// Add new columns to existing table for V2 upgrade backward compatibility
try {
$pdo->exec("ALTER TABLE `websites` ADD COLUMN `status` ENUM('normal', 'cancelled', 'maintenance') NOT NULL DEFAULT 'normal' AFTER `url`");
} catch (PDOException $e) {}
try {
$pdo->exec("ALTER TABLE `websites` ADD COLUMN `logo_url` TEXT NULL AFTER `status`");
} catch (PDOException $e) {}
try {
$pdo->exec("ALTER TABLE `websites` ADD COLUMN `click_count` INT(11) NOT NULL DEFAULT 0 AFTER `logo_url`");
} catch (PDOException $e) {}
$pdo->exec("
CREATE TABLE IF NOT EXISTS `activity_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`action` varchar(255) NOT NULL,
`details` text NOT NULL,
`ip_address` varchar(45) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
// Create Settings table
$pdo->exec("
CREATE TABLE IF NOT EXISTS `settings` (
`setting_key` varchar(50) NOT NULL,
`setting_value` text NOT NULL,
PRIMARY KEY (`setting_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
// Insert default settings
$stmt = $pdo->prepare("INSERT IGNORE INTO `settings` (`setting_key`, `setting_value`) VALUES ('site_name', 'ศูนย์รวมเว็บหน่วยงาน'), ('site_subtitle', 'One Stop Web Service')");
$stmt->execute();
// Fetch settings globally
$stmt = $pdo->query("SELECT setting_key, setting_value FROM settings");
$globalSettings = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$globalSettings[$row['setting_key']] = $row['setting_value'];
}
} catch (PDOException $e) {
die("Database Connection failed. Please check config.php: " . $e->getMessage());
}
function logAction($pdo, $action, $details) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
$stmt = $pdo->prepare("INSERT INTO activity_logs (action, details, ip_address) VALUES (?, ?, ?)");
$stmt->execute([$action, $details, $ip]);
}
function requireLogin() {
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header("Location: login.php");
exit;
}
}