1659 lines
137 KiB
PHP
1659 lines
137 KiB
PHP
<?php
|
|
// views/admin_settings.php
|
|
require_once("../core/auth.php");
|
|
require_once("../config/db.php");
|
|
require_once("../core/utils.php");
|
|
require_once("../core/settings.php");
|
|
require_once("../inc/rfc6238.php");
|
|
|
|
// ตรวจสอบสิทธิ์ผู้ดูแลระบบ
|
|
$current_page = basename($_SERVER['PHP_SELF']);
|
|
$current_user = $_SESSION['account'] ?? 'Unknown';
|
|
if (!function_exists('is_admin') || !is_admin($current_user)) {
|
|
die("<div style='padding:2rem;text-align:center;font-family:sans-serif;'><h2>Access Denied</h2><p>คุณไม่มีสิทธิ์เข้าถึงหน้านี้</p><a href='dashboard.php'>กลับหน้าหลัก</a></div>");
|
|
}
|
|
|
|
$tab = $_GET['tab'] ?? 'general';
|
|
$success_msg = '';
|
|
$show_2fa_modal = false;
|
|
$modal_data = [];
|
|
|
|
// จัดการ POST Action ต่างๆ
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|
if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
|
|
die("<div style='padding:2rem;text-align:center;font-family:sans-serif;'><h2>CSRF Token Validation Failed</h2><p>การยืนยันตัวตนล้มเหลว กรุณาโหลดหน้าเว็บใหม่</p><a href='admin_settings.php'>โหลดใหม่</a></div>");
|
|
}
|
|
|
|
$action = $_POST['action'];
|
|
$target_user = $_POST['target_user'] ?? '';
|
|
|
|
if (!empty($target_user)) {
|
|
if ($action === 'reset_2fa') {
|
|
$stmt = $conn2->prepare("DELETE FROM sys_user_2fa WHERE loginname = ?");
|
|
if ($stmt) {
|
|
$stmt->bind_param("s", $target_user);
|
|
if ($stmt->execute()) {
|
|
system_log($conn2, $current_user, 'RESET_2FA', ['target_user' => $target_user]);
|
|
$success_msg = "รีเซ็ต 2FA สำหรับผู้ใช้ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว ระบบจะบังคับให้แสกน QR Code ใหม่เมื่อล็อกอินครั้งถัดไป";
|
|
}
|
|
$stmt->close();
|
|
}
|
|
} elseif ($action === 'generate_2fa') {
|
|
$target_name = $_POST['target_name'] ?? $target_user;
|
|
$new_secretkey = TokenAuth6238::generateRandomClue();
|
|
$qr_url = TokenAuth6238::getBarCodeUrl($target_name, '', $new_secretkey, 'HOSxP-WebService');
|
|
|
|
$show_2fa_modal = true;
|
|
$modal_data = [
|
|
'username' => $target_user,
|
|
'name' => $target_name,
|
|
'secret' => $new_secretkey,
|
|
'qr' => $qr_url
|
|
];
|
|
} elseif ($action === 'save_2fa') {
|
|
$secretkey = $_POST['secretkey'] ?? '';
|
|
if (!empty($secretkey)) {
|
|
$stmt = $conn2->prepare("INSERT INTO sys_user_2fa (loginname, secret_key) VALUES (?, ?) ON DUPLICATE KEY UPDATE secret_key = ?");
|
|
if ($stmt) {
|
|
$stmt->bind_param("sss", $target_user, $secretkey, $secretkey);
|
|
if ($stmt->execute()) {
|
|
system_log($conn2, $current_user, 'SETUP_2FA', ['target_user' => $target_user]);
|
|
$success_msg = "ตั้งค่า 2FA สำหรับ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
|
|
}
|
|
$stmt->close();
|
|
}
|
|
}
|
|
} elseif ($action === 'grant_admin') {
|
|
$stmt = $conn2->prepare("INSERT IGNORE INTO sys_admins (username, granted_by) VALUES (?, ?)");
|
|
if ($stmt) {
|
|
$stmt->bind_param("ss", $target_user, $current_user);
|
|
if ($stmt->execute()) {
|
|
system_log($conn2, $current_user, 'GRANT_ADMIN', ['target_user' => $target_user]);
|
|
$success_msg = "มอบสิทธิ์ผู้ดูแลระบบให้ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
|
|
}
|
|
$stmt->close();
|
|
}
|
|
} elseif ($action === 'revoke_admin') {
|
|
$stmt = $conn2->prepare("DELETE FROM sys_admins WHERE username = ?");
|
|
if ($stmt) {
|
|
$stmt->bind_param("s", $target_user);
|
|
if ($stmt->execute()) {
|
|
system_log($conn2, $current_user, 'REVOKE_ADMIN', ['target_user' => $target_user]);
|
|
$success_msg = "ปลดสิทธิ์ผู้ดูแลระบบของ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
|
|
}
|
|
$stmt->close();
|
|
}
|
|
} elseif ($action === 'grant_search_name') {
|
|
$stmt = $conn2->prepare("INSERT INTO sys_user_permissions (username, can_search_name, updated_by) VALUES (?, 1, ?) ON DUPLICATE KEY UPDATE can_search_name = 1, updated_by = ?");
|
|
if ($stmt) {
|
|
$stmt->bind_param("sss", $target_user, $current_user, $current_user);
|
|
if ($stmt->execute()) {
|
|
system_log($conn2, $current_user, 'GRANT_SEARCH_NAME', ['target_user' => $target_user]);
|
|
$success_msg = "เปิดสิทธิ์ค้นหาด้วยชื่อ-สกุล ให้ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
|
|
}
|
|
$stmt->close();
|
|
}
|
|
} elseif ($action === 'revoke_search_name') {
|
|
$stmt = $conn2->prepare("UPDATE sys_user_permissions SET can_search_name = 0, updated_by = ? WHERE username = ?");
|
|
if ($stmt) {
|
|
$stmt->bind_param("ss", $current_user, $target_user);
|
|
if ($stmt->execute()) {
|
|
system_log($conn2, $current_user, 'REVOKE_SEARCH_NAME', ['target_user' => $target_user]);
|
|
$success_msg = "ปิดสิทธิ์ค้นหาด้วยชื่อ-สกุล ของ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
|
|
}
|
|
$stmt->close();
|
|
}
|
|
} elseif ($action === 'unblock_ip') {
|
|
$target_ip = $target_user; // Repurposing target_user input for IP
|
|
if (!empty($target_ip)) {
|
|
require_once("../core/security.php");
|
|
clear_failed_logins($conn2, $target_ip);
|
|
system_log($conn2, $current_user, 'UNBLOCK_IP', ['target_ip' => $target_ip]);
|
|
$success_msg = "ปลดบล็อก IP <strong>" . htmlspecialchars($target_ip) . "</strong> เรียบร้อยแล้ว";
|
|
}
|
|
}
|
|
} elseif ($action === 'save_general') {
|
|
if (isset($_POST['settings']) && is_array($_POST['settings'])) {
|
|
// Decode base64 SQL fields to bypass WAF/Cloudflare
|
|
$sql_keys = [
|
|
'vaccine_sql', 'ncd_sql',
|
|
'dashboard_dm_all', 'dashboard_dm_hba1c',
|
|
'dashboard_dm_1', 'dashboard_dm_2', 'dashboard_dm_3', 'dashboard_dm_4', 'dashboard_dm_5', 'dashboard_dm_eye',
|
|
'dashboard_ht_all', 'dashboard_ht_1', 'dashboard_ht_2',
|
|
'dashboard_dlp_all',
|
|
'dashboard_ckd_all', 'dashboard_ckd_1'
|
|
];
|
|
foreach ($sql_keys as $k) {
|
|
if (isset($_POST['settings'][$k.'_base64'])) {
|
|
$_POST['settings'][$k] = base64_decode(strrev($_POST['settings'][$k.'_base64']));
|
|
unset($_POST['settings'][$k.'_base64']);
|
|
}
|
|
}
|
|
|
|
save_app_settings($_POST['settings']);
|
|
|
|
$updated_keys = array_keys($_POST['settings']);
|
|
$details_msg = "อัปเดตการตั้งค่าจำนวน " . count($updated_keys) . " รายการ (" . implode(', ', $updated_keys) . ")";
|
|
|
|
system_log($conn2, $current_user, 'UPDATE_SETTINGS', ['status' => 'success', 'details' => $details_msg]);
|
|
$success_msg = "บันทึกการตั้งค่าเรียบร้อยแล้ว";
|
|
$tab = $_POST['current_tab'] ?? 'general';
|
|
}
|
|
} elseif ($action === 'add_changelog') {
|
|
$r_date = $_POST['release_date'] ?? date('Y-m-d');
|
|
$r_ver = $_POST['version'] ?? '';
|
|
$r_desc = $_POST['description'] ?? '';
|
|
if (!empty($r_ver) && !empty($r_desc)) {
|
|
if (add_changelog($r_date, $r_ver, $r_desc)) {
|
|
$success_msg = "เพิ่มประวัติเวอร์ชั่น <strong>" . htmlspecialchars($r_ver) . "</strong> เรียบร้อยแล้ว";
|
|
}
|
|
}
|
|
$tab = $_POST['current_tab'] ?? $_GET['tab'] ?? 'general';
|
|
} elseif ($action === 'delete_changelog') {
|
|
$log_id = intval($_POST['log_id'] ?? 0);
|
|
if ($log_id > 0) {
|
|
if (delete_changelog($log_id)) {
|
|
$success_msg = "ลบประวัติการอัปเดตเรียบร้อยแล้ว";
|
|
}
|
|
}
|
|
$tab = $_POST['current_tab'] ?? $_GET['tab'] ?? 'general';
|
|
} elseif (strpos($action, 'test_notification_') === 0) {
|
|
require_once("../core/security.php");
|
|
$target = str_replace('test_notification_', '', $action);
|
|
$results = send_test_notification($target);
|
|
|
|
$msg_parts = [];
|
|
if (isset($results['line'])) {
|
|
$msg_parts[] = "Line Notify: " . ($results['line'] ? "✅ สำเร็จ" : "❌ ล้มเหลว");
|
|
}
|
|
if (isset($results['telegram'])) {
|
|
$msg_parts[] = "Telegram: " . ($results['telegram'] ? "✅ สำเร็จ" : "❌ ล้มเหลว");
|
|
}
|
|
|
|
if (empty($msg_parts)) {
|
|
$success_msg = "ยังไม่ได้เปิดใช้งานการแจ้งเตือนนี้ หรือตั้งค่าไม่ครบ โปรดตั้งค่าแล้วบันทึกก่อนทดสอบ";
|
|
} else {
|
|
$success_msg = "ผลการทดสอบแจ้งเตือน: " . implode(" | ", $msg_parts);
|
|
}
|
|
$tab = $_POST['current_tab'] ?? $_GET['tab'] ?? 'general';
|
|
}
|
|
}
|
|
|
|
// โหลดข้อมูลเจ้าหน้าที่ (Tab 1)
|
|
$users_list = [];
|
|
$admin_usernames = [];
|
|
$search_name_usernames = [];
|
|
if ($tab === '2fa') {
|
|
// โหลดรายชื่อแอดมินและสิทธิ์ค้นหาทั้งหมดมาก่อน
|
|
$res_admins = $conn2->query("SELECT username FROM sys_admins");
|
|
if ($res_admins) {
|
|
while ($r = $res_admins->fetch_assoc()) {
|
|
$admin_usernames[] = $r['username'];
|
|
}
|
|
}
|
|
|
|
$res_search = $conn2->query("SELECT username FROM sys_user_permissions WHERE can_search_name = 1");
|
|
if ($res_search) {
|
|
while ($r = $res_search->fetch_assoc()) {
|
|
$search_name_usernames[] = $r['username'];
|
|
}
|
|
}
|
|
|
|
// 1. ตรวจสอบ/สร้างตาราง sys_user_2fa ใน conn2
|
|
$conn2->query("CREATE TABLE IF NOT EXISTS sys_user_2fa (
|
|
loginname VARCHAR(255) PRIMARY KEY,
|
|
secret_key VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
|
|
|
|
// 2. ดึงข้อมูล 2FA จาก conn2
|
|
$user_2fa_keys = [];
|
|
$res_2fa = $conn2->query("SELECT loginname, secret_key FROM sys_user_2fa");
|
|
if ($res_2fa) {
|
|
while ($r = $res_2fa->fetch_assoc()) {
|
|
$user_2fa_keys[$r['loginname']] = $r['secret_key'];
|
|
}
|
|
}
|
|
|
|
// 3. (Optional Migration) ตรวจสอบและย้ายข้อมูลจาก opduser -> sys_user_2fa ถ้ายังว่างอยู่
|
|
if (empty($user_2fa_keys)) {
|
|
try {
|
|
$check_col = $conn1->query("SHOW COLUMNS FROM opduser LIKE 'two_fa_secret_key_samui'");
|
|
if ($check_col && $check_col->num_rows > 0) {
|
|
$mig = $conn1->query("SELECT loginname, two_fa_secret_key_samui FROM opduser WHERE two_fa_secret_key_samui IS NOT NULL AND two_fa_secret_key_samui != ''");
|
|
if ($mig && $mig->num_rows > 0) {
|
|
$stmt_ins = $conn2->prepare("INSERT IGNORE INTO sys_user_2fa (loginname, secret_key) VALUES (?, ?)");
|
|
while ($m_row = $mig->fetch_assoc()) {
|
|
$stmt_ins->bind_param("ss", $m_row['loginname'], $m_row['two_fa_secret_key_samui']);
|
|
$stmt_ins->execute();
|
|
$user_2fa_keys[$m_row['loginname']] = $m_row['two_fa_secret_key_samui']; // update local array
|
|
}
|
|
$stmt_ins->close();
|
|
}
|
|
}
|
|
} catch (Exception $e) { }
|
|
}
|
|
|
|
// กำหนดจำนวนข้อมูลต่อหน้า หรือดึงมาหมดถ้าไม่เยอะ (ที่นี่ดึงมาทั้งหมดก่อน)
|
|
$res = $conn1->query("SELECT loginname, name, account_disable FROM opduser ORDER BY name ASC");
|
|
if ($res) {
|
|
while ($row = $res->fetch_assoc()) {
|
|
$row['is_admin'] = in_array($row['loginname'], $admin_usernames);
|
|
$row['can_search_name'] = in_array($row['loginname'], $search_name_usernames);
|
|
$row['two_fa_secret_key_samui'] = $user_2fa_keys[$row['loginname']] ?? '';
|
|
$users_list[] = $row;
|
|
}
|
|
}
|
|
}
|
|
|
|
// โหลดข้อมูล Banned IPs (Tab 3)
|
|
$banned_ips = [];
|
|
if ($tab === 'security') {
|
|
$conn2->query("DELETE FROM sys_login_attempts WHERE attempt_time < (NOW() - INTERVAL 15 MINUTE)");
|
|
$res = $conn2->query("
|
|
SELECT ip_address, COUNT(*) as fail_count, MAX(attempt_time) as last_attempt, GROUP_CONCAT(DISTINCT username) as target_users
|
|
FROM sys_login_attempts
|
|
GROUP BY ip_address
|
|
HAVING fail_count >= 5
|
|
ORDER BY last_attempt DESC
|
|
");
|
|
if ($res) {
|
|
while ($row = $res->fetch_assoc()) {
|
|
$banned_ips[] = $row;
|
|
}
|
|
}
|
|
}
|
|
|
|
// โหลดข้อมูล Logs (Tab 2)
|
|
$logs_list = [];
|
|
if ($tab === 'logs') {
|
|
$filter_search = $_GET['search'] ?? '';
|
|
$filter_action = $_GET['action'] ?? '';
|
|
$filter_start = $_GET['start_date'] ?? date('Y-m-d');
|
|
$filter_end = $_GET['end_date'] ?? date('Y-m-d');
|
|
|
|
$where_clauses = ["DATE(created_at) BETWEEN ? AND ?"];
|
|
$params = [$filter_start, $filter_end];
|
|
$types = "ss";
|
|
|
|
if (!empty($filter_search)) {
|
|
$where_clauses[] = "(username LIKE ? OR details LIKE ?)";
|
|
$search_param = "%{$filter_search}%";
|
|
$params[] = $search_param;
|
|
$params[] = $search_param;
|
|
$types .= "ss";
|
|
}
|
|
|
|
if (!empty($filter_action)) {
|
|
$where_clauses[] = "action = ?";
|
|
$params[] = $filter_action;
|
|
$types .= "s";
|
|
}
|
|
|
|
$where_sql = implode(' AND ', $where_clauses);
|
|
$sql_logs = "SELECT * FROM sys_audit_logs WHERE $where_sql ORDER BY created_at DESC LIMIT 500";
|
|
|
|
$stmt = $conn2->prepare($sql_logs);
|
|
if ($stmt) {
|
|
$stmt->bind_param($types, ...$params);
|
|
$stmt->execute();
|
|
$res = $stmt->get_result();
|
|
while ($row = $res->fetch_assoc()) {
|
|
$logs_list[] = $row;
|
|
}
|
|
$stmt->close();
|
|
}
|
|
}
|
|
|
|
// ดึงรายการ Action ทั้งหมดที่มีในระบบเพื่อมาทำ Filter Dropdown
|
|
$actions_list = [];
|
|
$res_acts = $conn2->query("SELECT DISTINCT action FROM sys_audit_logs ORDER BY action ASC");
|
|
if ($res_acts) {
|
|
while($row = $res_acts->fetch_assoc()) {
|
|
$actions_list[] = $row['action'];
|
|
}
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="th">
|
|
<head>
|
|
<?php require_once("../components/head.php"); ?>
|
|
<!-- jQuery & DataTables -->
|
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
|
<!-- SQL Formatter -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql-formatter/12.2.3/sql-formatter.min.js"></script>
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">
|
|
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
|
|
|
|
<!-- DataTables Buttons for Excel Export -->
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.1/css/buttons.dataTables.min.css">
|
|
<script src="https://cdn.datatables.net/buttons/2.4.1/js/dataTables.buttons.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
|
<script src="https://cdn.datatables.net/buttons/2.4.1/js/buttons.html5.min.js"></script>
|
|
|
|
<style>
|
|
/* Custom CSS to make DataTables look good with Tailwind */
|
|
.dataTables_wrapper .dataTables_length select, .dataTables_wrapper .dataTables_filter input {
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 0.5rem;
|
|
padding: 0.35rem 0.75rem;
|
|
outline: none;
|
|
margin-bottom: 1rem;
|
|
font-size: 0.875rem;
|
|
}
|
|
.dataTables_wrapper .dataTables_length select:focus, .dataTables_wrapper .dataTables_filter input:focus {
|
|
border-color: #10b981;
|
|
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
|
|
}
|
|
|
|
/* Excel Export Button Styling */
|
|
.dt-buttons .dt-button.btn-export-excel {
|
|
background: #10b981 !important;
|
|
color: white !important;
|
|
border: none !important;
|
|
border-radius: 0.5rem !important;
|
|
padding: 0.4rem 1rem !important;
|
|
font-size: 0.875rem !important;
|
|
font-family: 'Sarabun', sans-serif !important;
|
|
transition: all 0.2s !important;
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
gap: 0.5rem !important;
|
|
}
|
|
.dt-buttons .dt-button.btn-export-excel:hover {
|
|
background: #059669 !important;
|
|
}
|
|
.dataTables_wrapper .dt-buttons {
|
|
margin-bottom: 0 !important;
|
|
}
|
|
|
|
.dataTables_wrapper .dataTables_paginate .paginate_button {
|
|
border-radius: 0.5rem !important;
|
|
padding: 0.25rem 0.75rem !important;
|
|
margin: 0 0.125rem !important;
|
|
border: 1px solid transparent !important;
|
|
}
|
|
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
|
|
background: #10b981 !important;
|
|
color: white !important;
|
|
border: none !important;
|
|
}
|
|
.dataTables_wrapper .dataTables_paginate .paginate_button:hover:not(.current) {
|
|
background: #f1f5f9 !important;
|
|
color: #334155 !important;
|
|
}
|
|
table.dataTable.no-footer {
|
|
border-bottom: 1px solid #f1f5f9;
|
|
}
|
|
table.dataTable thead th {
|
|
border-bottom: 1px solid #f1f5f9;
|
|
}
|
|
.dt-center {
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
<script>
|
|
function generate2FA(username, name) {
|
|
document.getElementById('action_input').value = 'generate_2fa';
|
|
document.getElementById('target_user_input').value = username;
|
|
|
|
let nameInput = document.createElement('input');
|
|
nameInput.type = 'hidden';
|
|
nameInput.name = 'target_name';
|
|
nameInput.value = name;
|
|
document.getElementById('admin_action_form').appendChild(nameInput);
|
|
|
|
document.getElementById('admin_action_form').submit();
|
|
}
|
|
|
|
function confirmReset2FA(username, name) {
|
|
if (confirm(`คุณต้องการรีเซ็ต 2FA ของ ${name} (${username}) ใช่หรือไม่?\nการกระทำนี้จะลบ Secret Key เดิมทิ้งทันที`)) {
|
|
document.getElementById('action_input').value = 'reset_2fa';
|
|
document.getElementById('target_user_input').value = username;
|
|
document.getElementById('admin_action_form').submit();
|
|
}
|
|
}
|
|
|
|
function confirmGrantAdmin(username, name) {
|
|
if (confirm(`คุณต้องการมอบสิทธิ์ผู้ดูแลระบบให้ ${name} (${username}) ใช่หรือไม่?`)) {
|
|
document.getElementById('action_input').value = 'grant_admin';
|
|
document.getElementById('target_user_input').value = username;
|
|
document.getElementById('admin_action_form').submit();
|
|
}
|
|
}
|
|
|
|
function confirmRevokeAdmin(username, name) {
|
|
if (confirm(`คุณต้องการปลดสิทธิ์ผู้ดูแลระบบของ ${name} (${username}) ใช่หรือไม่?`)) {
|
|
document.getElementById('action_input').value = 'revoke_admin';
|
|
document.getElementById('target_user_input').value = username;
|
|
document.getElementById('admin_action_form').submit();
|
|
}
|
|
}
|
|
|
|
function confirmGrantSearchName(username, name) {
|
|
if (confirm(`คุณต้องการเปิดสิทธิ์ค้นหาด้วยชื่อ-สกุลให้ ${name} (${username}) ใช่หรือไม่?`)) {
|
|
document.getElementById('action_input').value = 'grant_search_name';
|
|
document.getElementById('target_user_input').value = username;
|
|
document.getElementById('admin_action_form').submit();
|
|
}
|
|
}
|
|
|
|
function confirmRevokeSearchName(username, name) {
|
|
if (confirm(`คุณต้องการปิดสิทธิ์ค้นหาด้วยชื่อ-สกุลของ ${name} (${username}) ใช่หรือไม่?`)) {
|
|
document.getElementById('action_input').value = 'revoke_search_name';
|
|
document.getElementById('target_user_input').value = username;
|
|
document.getElementById('admin_action_form').submit();
|
|
}
|
|
}
|
|
|
|
function confirmUnblockIP(ip) {
|
|
if (confirm(`คุณต้องการปลดบล็อก IP: ${ip} ใช่หรือไม่?`)) {
|
|
document.getElementById('action_input').value = 'unblock_ip';
|
|
document.getElementById('target_user_input').value = ip;
|
|
document.getElementById('admin_action_form').submit();
|
|
}
|
|
}
|
|
</script>
|
|
</head>
|
|
<body class="bg-blue-50 text-slate-800 font-sans antialiased min-h-screen flex">
|
|
|
|
<?php require_once("../components/layout_sidebar.php"); ?>
|
|
|
|
<main class="flex-1 flex flex-col min-w-0 h-screen overflow-hidden">
|
|
<?php require_once("../components/layout_topbar.php"); ?>
|
|
|
|
<div class="p-4 md:p-8 lg:p-10 flex-1 overflow-y-auto">
|
|
<div class="max-w-screen-2xl mx-auto animate-enter">
|
|
|
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-slate-800 tracking-tight">ตั้งค่าระบบ (Admin Settings)</h1>
|
|
<p class="text-slate-500 mt-1">จัดการผู้ใช้งาน, ระบบยืนยันตัวตน และตรวจสอบประวัติการใช้งาน</p>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if(!empty($success_msg)): ?>
|
|
<div class="bg-blue-100 text-blue-800 px-6 py-4 rounded-xl mb-6 flex items-center gap-3 shadow-sm border border-blue-200">
|
|
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
<span><?php echo $success_msg; ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Tabs Navigation -->
|
|
<div class="flex space-x-2 border-b border-slate-200 mb-6 overflow-x-auto">
|
|
<a href="?tab=general" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'general' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
|
|
การตั้งค่าทั่วไป (General Settings)
|
|
</a>
|
|
<a href="?tab=database" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'database' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
|
|
ตั้งค่าการแสดงผลเพิ่มเติม
|
|
</a>
|
|
<a href="?tab=ncd" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'ncd' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
|
|
ทะเบียนผู้ป่วย (NCD)
|
|
</a>
|
|
<a href="?tab=dashboard_ncd" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'dashboard_ncd' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
|
|
Dashboard NCD
|
|
</a>
|
|
<a href="?tab=2fa" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === '2fa' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
|
|
จัดการผู้ใช้งาน (Users & Roles)
|
|
</a>
|
|
<a href="?tab=security" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'security' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
|
|
รายการบล็อก (IP Bans)
|
|
</a>
|
|
<a href="?tab=logs" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'logs' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
|
|
ประวัติการใช้งาน (System Logs)
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Hidden form for admin actions -->
|
|
<form id="admin_action_form" method="POST" class="hidden">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
<input type="hidden" name="action" id="action_input" value="">
|
|
<input type="hidden" name="target_user" id="target_user_input" value="">
|
|
</form>
|
|
|
|
<?php if ($tab === 'general'): ?>
|
|
<!-- Tab: General Settings -->
|
|
<div class="glass-card overflow-hidden animate-enter" style="animation-delay: 0.1s;">
|
|
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
|
|
<h3 class="text-lg font-bold text-slate-800">การตั้งค่าทั่วไป</h3>
|
|
<p class="text-sm text-slate-500">ปรับแต่งข้อความแสดงผลบนหน้าล็อกอินและแถบเมนู</p>
|
|
</div>
|
|
<div class="p-4 sm:p-6">
|
|
<?php $app_settings = get_app_settings(); ?>
|
|
<form method="POST" class="max-w-2xl space-y-6">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
<input type="hidden" name="action" value="save_general">
|
|
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
|
|
|
|
<div class="space-y-4">
|
|
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">หน้าล็อกอิน (Login Page)</h4>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">หัวข้อหลัก (Main Header)</label>
|
|
<input type="text" name="settings[login_header_main]" value="<?php echo htmlspecialchars($app_settings['login_header_main']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">หัวข้อรอง (Sub Header)</label>
|
|
<input type="text" name="settings[login_header_sub]" value="<?php echo htmlspecialchars($app_settings['login_header_sub']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4 pt-2">
|
|
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">แถบเมนู (Sidebar)</h4>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">หัวข้อหลัก (Main Header)</label>
|
|
<input type="text" name="settings[sidebar_header_main]" value="<?php echo htmlspecialchars($app_settings['sidebar_header_main']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">หัวข้อรอง (Sub Header)</label>
|
|
<input type="text" name="settings[sidebar_header_sub]" value="<?php echo htmlspecialchars($app_settings['sidebar_header_sub']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4 pt-2">
|
|
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">แถบด้านล่าง (Footer)</h4>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">ข้อความฝั่งซ้าย (Left Text)</label>
|
|
<input type="text" name="settings[footer_text]" value="<?php echo htmlspecialchars($app_settings['footer_text']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
|
|
<p class="text-[11px] text-slate-500 mt-1">สามารถใส่ <code>{YEAR}</code> แทนปีปัจจุบันอัตโนมัติ และรองรับ HTML เบื้องต้นเช่น <code>&copy;</code> (ข้อความ Powered By จะถูกบังคับแสดงขวามือเสมอ)</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4 pt-4 mt-4 border-t border-slate-100">
|
|
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">เงื่อนไขการค้นหาประวัติการรับบริการ</h4>
|
|
<div>
|
|
<label class="flex items-center gap-3 cursor-pointer p-3 border border-slate-200 rounded-lg bg-slate-50 hover:bg-slate-100 transition-colors">
|
|
<div class="relative">
|
|
<input type="hidden" name="settings[strict_search_enable]" value="0">
|
|
<input type="checkbox" name="settings[strict_search_enable]" value="1" class="sr-only peer" <?php echo ($app_settings['strict_search_enable'] == '1') ? 'checked' : ''; ?>>
|
|
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div>
|
|
</div>
|
|
<div>
|
|
<span class="block text-sm font-medium text-slate-700">บังคับค้นหาแบบตรงตัว (Strict Search)</span>
|
|
<span class="block text-[11px] text-slate-500 mt-0.5">เมื่อเปิดใช้งาน การค้นหาประวัติการรับบริการด้วย HN, CID, หรือหมายเลขโทรศัพท์ จะต้องกรอกข้อมูลให้ตรงทุกตัวอักษรเท่านั้น (ยกเว้นการค้นหาด้วยชื่อ-สกุล)</span>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4 pt-4 mt-4 border-t border-slate-100">
|
|
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">ระบบรักษาความปลอดภัย (Security)</h4>
|
|
|
|
<div>
|
|
<label class="flex items-center gap-3 cursor-pointer p-3 border border-slate-200 rounded-lg bg-slate-50 hover:bg-slate-100 transition-colors">
|
|
<div class="relative">
|
|
<input type="hidden" name="settings[auto_logout_enable]" value="0">
|
|
<input type="checkbox" id="auto_logout_toggle" name="settings[auto_logout_enable]" value="1" class="sr-only peer" <?php echo ($app_settings['auto_logout_enable'] == '1') ? 'checked' : ''; ?> onchange="document.getElementById('auto_logout_time_container').style.display = this.checked ? 'block' : 'none';">
|
|
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div>
|
|
</div>
|
|
<div>
|
|
<span class="block text-sm font-medium text-slate-700">ออกจากระบบอัตโนมัติเมื่อไม่มีการใช้งาน (Auto Logout)</span>
|
|
<span class="block text-[11px] text-slate-500 mt-0.5">ป้องกันการถูกผู้อื่นใช้งานเครื่อง หากผู้ใช้ลืมออกจากระบบ</span>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
<div id="auto_logout_time_container" style="display: <?php echo ($app_settings['auto_logout_enable'] == '1') ? 'block' : 'none'; ?>;">
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">ระยะเวลาไม่มีการใช้งาน (นาที)</label>
|
|
<input type="number" name="settings[auto_logout_minutes]" value="<?php echo htmlspecialchars($app_settings['auto_logout_minutes']); ?>" min="1" max="1440" class="w-full form-input py-2 px-3 text-sm">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pt-4 mt-4 border-t border-slate-100">
|
|
<button type="submit" class="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm">
|
|
บันทึกการตั้งค่า
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab: Notifications Settings -->
|
|
<div class="glass-card overflow-hidden animate-enter mt-6" style="animation-delay: 0.15s;">
|
|
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
|
<div>
|
|
<h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
|
|
<svg class="w-5 h-5 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path></svg>
|
|
การแจ้งเตือนเมื่อมีการแบน IP (Notifications)
|
|
</h3>
|
|
<p class="text-sm text-slate-500">ตั้งค่าการแจ้งเตือนเมื่อระบบระงับ IP เนื่องจากพยายามล็อกอินผิดพลาด</p>
|
|
</div>
|
|
</div>
|
|
<div class="p-4 sm:p-6">
|
|
<form method="POST" class="max-w-2xl space-y-6">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
<input type="hidden" name="action" value="save_general">
|
|
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
|
|
|
|
<div class="space-y-4">
|
|
|
|
<div class="p-5 bg-white rounded-xl border border-slate-200 shadow-sm">
|
|
<div class="flex items-center justify-between mb-4 pb-3 border-b border-slate-100">
|
|
<label for="line_notify_enable" class="flex items-center gap-3 cursor-pointer">
|
|
<input type="hidden" name="settings[line_notify_enable]" value="0">
|
|
<input type="checkbox" id="line_notify_enable" name="settings[line_notify_enable]" value="1" <?php echo ($app_settings['line_notify_enable'] == '1') ? 'checked' : ''; ?> class="w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer">
|
|
<span class="font-bold text-slate-800 flex items-center gap-2">
|
|
<svg class="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 24 24"><path d="M19.365 9.863c.349.0.63.285.63.631.0.345-.281.63-.63.63H17.61v1.138h1.755c.349.0.63.283.63.63.0.344-.281.629-.63.629h-2.386c-.345.0-.627-.285-.627-.629V8.142c0-.344.282-.629.627-.629h2.386c.349.0.63.285.63.629.0.344-.281.63-.63.63H17.61v1.091h1.755zM14.958 11.521c0 .344-.282.629-.628.629h-2.386c-.345.0-.627-.285-.627-.629V8.142c0-.344.282-.629.627-.629h2.386c.346.0.628.285.628.629.0.344-.282.63-.628.63H12.58v1.091h1.751c.345.0.628.285.628.63.0.345-.283.63-.628.63H12.58v1.138h1.751c.346.0.628.283.628.63zM10.153 12.15h-2.181V8.142c0-.344-.281-.629-.628-.629-.346.0-.629.285-.629.629v4.636c0 .344.283.629.629.629h2.809c.346.0.628-.285.628-.629.0-.345-.282-.628-.628-.628zM5.539 12.15H4.283V8.142c0-.344-.282-.629-.628-.629-.345.0-.628.285-.628.629v4.008H1.77c-.345.0-.628.285-.628.628.0.345.283.629.628.629h3.769c.346.0.628-.284.628-.629.0-.344-.282-.628-.628-.628z" transform="translate(1 5)"/></svg>
|
|
Line Notify
|
|
</span>
|
|
</label>
|
|
<button type="submit" name="action" value="test_notification_line" class="px-4 py-1.5 bg-green-50 text-green-700 text-sm font-bold rounded-lg hover:bg-green-100 transition-colors border border-green-200" title="ทดสอบส่งผ่าน Line Notify (กรุณาบันทึกการตั้งค่าก่อน)">
|
|
ทดสอบ Line
|
|
</button>
|
|
</div>
|
|
<div class="ml-8">
|
|
<label class="block text-sm font-medium text-slate-600 mb-1">Line Notify Token</label>
|
|
<input type="text" name="settings[line_notify_token]" value="<?php echo htmlspecialchars($app_settings['line_notify_token']); ?>" class="w-full form-input py-2 px-3 text-sm" placeholder="ใส่ Token ของ Line Notify">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-5 bg-white rounded-xl border border-slate-200 shadow-sm mt-4">
|
|
<div class="flex items-center justify-between mb-4 pb-3 border-b border-slate-100">
|
|
<label for="telegram_notify_enable" class="flex items-center gap-3 cursor-pointer">
|
|
<input type="hidden" name="settings[telegram_notify_enable]" value="0">
|
|
<input type="checkbox" id="telegram_notify_enable" name="settings[telegram_notify_enable]" value="1" <?php echo ($app_settings['telegram_notify_enable'] == '1') ? 'checked' : ''; ?> class="w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer">
|
|
<span class="font-bold text-slate-800 flex items-center gap-2">
|
|
<svg class="w-5 h-5 text-sky-500" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69.01-.03.01-.14-.07-.18-.08-.05-.19-.02-.27.01-.12.04-1.96 1.25-5.54 3.69-.52.36-1 .53-1.42.52-.47-.01-1.37-.26-2.03-.48-.82-.27-1.47-.42-1.42-.88.03-.24.29-.48.79-.74 3.08-1.34 5.15-2.23 6.19-2.66 2.95-1.23 3.56-1.44 3.96-1.45.09.0.28.02.4.11.1.08.13.19.14.28-.01.07.01.21-.01.32z"/></svg>
|
|
Telegram
|
|
</span>
|
|
</label>
|
|
<button type="submit" name="action" value="test_notification_telegram" class="px-4 py-1.5 bg-sky-50 text-sky-700 text-sm font-bold rounded-lg hover:bg-sky-100 transition-colors border border-sky-200" title="ทดสอบส่งผ่าน Telegram (กรุณาบันทึกการตั้งค่าก่อน)">
|
|
ทดสอบ Telegram
|
|
</button>
|
|
</div>
|
|
<div class="ml-8 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-600 mb-1">Bot Token</label>
|
|
<input type="text" name="settings[telegram_bot_token]" value="<?php echo htmlspecialchars($app_settings['telegram_bot_token']); ?>" class="w-full form-input py-2 px-3 text-sm" placeholder="ใส่ Bot Token">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-600 mb-1">Chat ID</label>
|
|
<input type="text" name="settings[telegram_chat_id]" value="<?php echo htmlspecialchars($app_settings['telegram_chat_id']); ?>" class="w-full form-input py-2 px-3 text-sm" placeholder="ใส่ Chat ID ปลายทาง">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="pt-4 flex flex-wrap gap-3">
|
|
<button type="submit" class="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm">
|
|
บันทึกการตั้งค่าการแจ้งเตือน
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="glass-card overflow-hidden animate-enter mt-6" style="animation-delay: 0.2s;">
|
|
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
|
<div>
|
|
<h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
|
|
<svg class="w-5 h-5 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"></path></svg>
|
|
ประกาศข่าวสาร (Version History)
|
|
</h3>
|
|
<p class="text-sm text-slate-500">บันทึกประวัติการอัปเดตและแจ้งเตือนเวอร์ชั่นใหม่</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-4 sm:p-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<!-- Add Form -->
|
|
<div class="lg:col-span-1 bg-slate-50/50 p-5 rounded-xl border border-slate-100 h-fit">
|
|
<h4 class="font-semibold text-slate-700 mb-4 text-sm">เพิ่มประวัติใหม่</h4>
|
|
<form method="POST" class="space-y-4">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
<input type="hidden" name="action" value="add_changelog">
|
|
|
|
<div>
|
|
<label class="block text-xs font-medium text-slate-600 mb-1">วันที่ประกาศ</label>
|
|
<input type="date" name="release_date" value="<?php echo date('Y-m-d'); ?>" class="w-full form-input py-2 px-3 text-sm" required>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-slate-600 mb-1">เวอร์ชั่น (เช่น v2.9)</label>
|
|
<input type="text" name="version" class="w-full form-input py-2 px-3 text-sm" placeholder="HOSxP Web Service v..." required>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-slate-600 mb-1">รายละเอียด (Badge / Note)</label>
|
|
<input type="text" name="description" class="w-full form-input py-2 px-3 text-sm" placeholder="เพิ่มฟีเจอร์..." required>
|
|
</div>
|
|
<button type="submit" class="w-full py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm">
|
|
บันทึกประวัติ
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- List view -->
|
|
<div class="lg:col-span-2">
|
|
<div class="space-y-4">
|
|
<?php
|
|
$logs = get_changelogs();
|
|
if (empty($logs)):
|
|
?>
|
|
<div class="text-center py-8 text-slate-400">
|
|
<svg class="w-12 h-12 mx-auto mb-3 text-slate-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
|
|
<p>ยังไม่มีประวัติการอัปเดต</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<?php foreach ($logs as $log):
|
|
$d = new DateTime($log['release_date']);
|
|
$thai_year = $d->format('Y') + 543;
|
|
$display_date = $d->format('d-m-') . $thai_year;
|
|
?>
|
|
<div class="relative bg-white border border-slate-100 rounded-2xl p-5 shadow-sm hover:shadow-md transition-all flex items-center justify-between group overflow-hidden">
|
|
<div class="absolute left-0 top-0 bottom-0 w-1.5 bg-amber-400 rounded-l-2xl"></div>
|
|
<div class="pl-2">
|
|
<div class="text-[11px] font-bold text-amber-500 mb-1 tracking-wide"><?php echo $display_date; ?></div>
|
|
<div class="flex items-center gap-3 flex-wrap">
|
|
<h4 class="text-base font-semibold text-slate-800"><?php echo htmlspecialchars($log['version']); ?></h4>
|
|
<?php if(!empty($log['description'])): ?>
|
|
<span class="px-3 py-1 bg-blue-50 text-blue-700 text-xs font-medium rounded-full border border-blue-100/50">
|
|
<?php echo htmlspecialchars($log['description']); ?>
|
|
</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<form method="POST" onsubmit="return confirm('ยืนยันการลบประวัตินี้?');">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
<input type="hidden" name="action" value="delete_changelog">
|
|
<input type="hidden" name="log_id" value="<?php echo $log['id']; ?>">
|
|
<button type="submit" class="p-2 text-slate-300 hover:text-rose-500 hover:bg-rose-50 rounded-lg transition-colors opacity-0 group-hover:opacity-100" title="ลบข้อมูล">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($tab === 'database'): ?>
|
|
<!-- Tab: Database Settings -->
|
|
<div class="glass-card overflow-hidden animate-enter" style="animation-delay: 0.1s;">
|
|
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
|
|
<h3 class="text-lg font-bold text-slate-800">ตั้งค่าการแสดงผลเพิ่มเติม</h3>
|
|
<p class="text-sm text-slate-500">ปรับแต่งคำสั่ง SQL และการเชื่อมต่อฐานข้อมูลสำหรับดึงข้อมูลมาแสดงผลในส่วนต่างๆ</p>
|
|
</div>
|
|
<div class="p-4 sm:p-6">
|
|
<?php $app_settings = get_app_settings(); ?>
|
|
<form method="POST" class="max-w-4xl space-y-6" onsubmit="return encodeSQL();">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
<input type="hidden" name="action" value="save_general">
|
|
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
|
|
|
|
<div class="space-y-6">
|
|
<!-- Vaccine System Section -->
|
|
<div class="bg-white border border-slate-200 rounded-xl p-5 sm:p-6 shadow-sm">
|
|
<div class="flex items-center gap-3 mb-4 border-b border-slate-100 pb-3">
|
|
<div class="w-10 h-10 rounded-lg bg-blue-50 text-blue-600 flex items-center justify-center">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 2 4 4"/><path d="m17 7 3-3"/><path d="M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5"/><path d="m9 11 4 4"/><path d="m5 19-3 3"/><path d="m14 4 6 6"/></svg>
|
|
</div>
|
|
<div>
|
|
<h4 class="font-bold text-slate-800 text-lg">ระบบประวัติวัคซีน (Vaccination System)</h4>
|
|
<p class="text-xs text-slate-500">ดึงข้อมูลประวัติการรับวัคซีนจากฐานข้อมูล HIS</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-2">
|
|
<label class="block text-sm font-medium text-slate-700">ชุดคำสั่ง SQL (SQL Query)</label>
|
|
<button type="button" onclick="formatSqlInput('vaccine_sql_input')" class="text-xs bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-3 py-1.5 rounded-md transition-colors border border-slate-200 hover:border-blue-300 flex items-center gap-1">
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7"></path></svg>
|
|
จัดรูปแบบ SQL
|
|
</button>
|
|
</div>
|
|
<div class="bg-blue-50 text-blue-800 p-4 rounded-lg mb-4 text-[13px] border border-blue-100">
|
|
<div class="font-bold mb-2 flex items-center gap-1 text-blue-900">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
คำแนะนำและข้อบังคับในการเขียน SQL
|
|
</div>
|
|
<ul class="list-disc pl-5 space-y-1.5">
|
|
<li>กรุณาใช้คำว่า <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">'ระบุ_HN'</code> (มี Single Quote) แทนรหัส HN ของผู้ป่วยในเงื่อนไข WHERE เสมอ</li>
|
|
<li>ผลลัพธ์จะต้องตั้งชื่อคอลัมน์ (AS) ให้ตรงกับตัวแปรเหล่านี้:</li>
|
|
<ul class="list-circle pl-5 mt-2 space-y-1 text-blue-700">
|
|
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_group</code> = ชื่อกลุ่มของวัคซีน (ระบบใช้คอลัมน์นี้ในการแบ่งกลุ่มและแยกสี)</li>
|
|
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_name</code> = ชื่อวัคซีนที่ฉีด</li>
|
|
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_date</code> = วันที่รับวัคซีน (รูปแบบ YYYY-MM-DD)</li>
|
|
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_code</code> = รหัสวัคซีน (แสดงเป็นตัวเล็กๆ ข้างชื่อ)</li>
|
|
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_lot_no</code> = หมายเลข Lot (ไม่บังคับ)</li>
|
|
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_place_name</code> = สถานที่รับวัคซีน (ไม่บังคับ)</li>
|
|
</ul>
|
|
</ul>
|
|
</div>
|
|
<input type="hidden" name="settings[vaccine_sql_base64]" id="vaccine_sql_base64">
|
|
<textarea id="vaccine_sql_input" rows="14" class="w-full form-input py-3 px-4 text-sm font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 shadow-inner" spellcheck="false"><?php echo htmlspecialchars($app_settings['vaccine_sql'] ?? ''); ?></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Placeholder for future modules -->
|
|
<!--
|
|
<div class="bg-white border border-slate-200 rounded-xl p-5 sm:p-6 shadow-sm border-dashed opacity-60">
|
|
<div class="flex items-center justify-center py-8 flex-col text-slate-400">
|
|
<svg class="w-8 h-8 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
|
<span class="text-sm font-medium">เพิ่มชุดคำสั่งใหม่ในอนาคต</span>
|
|
</div>
|
|
</div>
|
|
-->
|
|
</div>
|
|
|
|
<div class="pt-2">
|
|
<button type="submit" class="px-6 py-2.5 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm inline-flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg>
|
|
บันทึกการตั้งค่า
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<script>
|
|
function encodeSQL() {
|
|
var sqlInput = document.getElementById('vaccine_sql_input');
|
|
var b64Hidden = document.getElementById('vaccine_sql_base64');
|
|
if (sqlInput && b64Hidden) {
|
|
var b64 = btoa(unescape(encodeURIComponent(sqlInput.value)));
|
|
b64Hidden.value = b64.split('').reverse().join('');
|
|
sqlInput.name = ''; // Prevent raw SQL submission to bypass Cloudflare
|
|
}
|
|
return true;
|
|
}
|
|
</script>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($tab === 'ncd'): ?>
|
|
<!-- Tab: NCD Settings -->
|
|
<div class="glass-card overflow-hidden animate-enter" style="animation-delay: 0.1s;">
|
|
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
|
|
<h3 class="text-lg font-bold text-slate-800">ทะเบียนผู้ป่วย (NCD Registry)</h3>
|
|
<p class="text-sm text-slate-500">ปรับแต่งคำสั่ง SQL สำหรับดึงข้อมูลในระบบทะเบียนผู้ป่วย NCD</p>
|
|
</div>
|
|
<div class="p-4 sm:p-6">
|
|
<?php $app_settings = get_app_settings(); ?>
|
|
<form method="POST" class="max-w-4xl space-y-6" onsubmit="return encodeNCDSQL();">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
<input type="hidden" name="action" value="save_general">
|
|
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
|
|
|
|
<div class="space-y-6">
|
|
<div class="bg-white border border-slate-200 rounded-xl p-5 sm:p-6 shadow-sm">
|
|
<div class="flex items-center justify-between mb-4 border-b border-slate-100 pb-3">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-lg bg-emerald-50 text-emerald-600 flex items-center justify-center">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
|
|
</div>
|
|
<div>
|
|
<h4 class="font-bold text-slate-800 text-lg">ชุดคำสั่ง SQL (NCD Registry SQL)</h4>
|
|
<p class="text-xs text-slate-500">ดึงข้อมูลทะเบียนผู้ป่วยเรื้อรังจาก HOSxP</p>
|
|
</div>
|
|
</div>
|
|
<button type="button" onclick="formatSqlInput('ncd_sql_input')" class="text-xs bg-slate-100 hover:bg-emerald-100 text-slate-600 hover:text-emerald-700 px-3 py-1.5 rounded-md transition-colors border border-slate-200 hover:border-emerald-300 flex items-center gap-1">
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7"></path></svg>
|
|
จัดรูปแบบ SQL
|
|
</button>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="bg-blue-50 text-blue-800 p-4 rounded-lg mb-4 text-[13px] border border-blue-100">
|
|
<div class="font-bold mb-2 flex items-center gap-1 text-blue-900">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
คำแนะนำและข้อบังคับในการเขียน SQL
|
|
</div>
|
|
<ul class="list-disc pl-5 space-y-1.5">
|
|
<li>ต้องมีตัวแทน (Placeholder) ชื่อ <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">{clinic_cond}</code> เพื่อให้ระบบนำเงื่อนไขคลินิกไปแทรก (เช่น `AND cl.clinic = '001'`)</li>
|
|
<li>ต้องมี `?` 2 ตำแหน่งสำหรับ bind param ค่าวันที่ (`vstdate between ? and ?`) ใน Prepared Statement</li>
|
|
<li>ต้องทำการตั้งชื่อ (AS) ให้ตรงกับที่ระบบนำไปแสดงผล (เช่น <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">pt_name</code>, <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">age_y</code>, <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">tmb_name</code>)</li>
|
|
</ul>
|
|
</div>
|
|
<input type="hidden" name="settings[ncd_sql_base64]" id="ncd_sql_base64">
|
|
<textarea id="ncd_sql_input" rows="18" class="w-full form-input py-3 px-4 text-sm font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 shadow-inner" spellcheck="false"><?php echo htmlspecialchars($app_settings['ncd_sql'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div class="mt-8 pt-6 border-t border-slate-100">
|
|
<h4 class="font-bold text-slate-800 text-md mb-4 flex items-center gap-2">
|
|
<svg class="w-5 h-5 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path></svg>
|
|
ตั้งค่ารหัสคลินิก (Clinic Codes Mapping)
|
|
</h4>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">รหัสคลินิกเบาหวาน (DM)</label>
|
|
<input type="text" name="settings[ncd_clinic_dm_code]" value="<?php echo htmlspecialchars($app_settings['ncd_clinic_dm_code'] ?? '001'); ?>" class="w-full form-input py-2 px-3 text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">รหัสคลินิกความดันฯ (HT)</label>
|
|
<input type="text" name="settings[ncd_clinic_ht_code]" value="<?php echo htmlspecialchars($app_settings['ncd_clinic_ht_code'] ?? '002'); ?>" class="w-full form-input py-2 px-3 text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">รหัสคลินิกไขมันฯ (DLP)</label>
|
|
<input type="text" name="settings[ncd_clinic_dlp_code]" value="<?php echo htmlspecialchars($app_settings['ncd_clinic_dlp_code'] ?? '039'); ?>" class="w-full form-input py-2 px-3 text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1">รหัสคลินิกโรคไต (CKD)</label>
|
|
<input type="text" name="settings[ncd_clinic_ckd_code]" value="<?php echo htmlspecialchars($app_settings['ncd_clinic_ckd_code'] ?? '051'); ?>" class="w-full form-input py-2 px-3 text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pt-2">
|
|
<button type="submit" class="px-6 py-2.5 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm inline-flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg>
|
|
บันทึกการตั้งค่า
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<script>
|
|
function encodeNCDSQL() {
|
|
var sqlInput = document.getElementById('ncd_sql_input');
|
|
var b64Hidden = document.getElementById('ncd_sql_base64');
|
|
if (sqlInput && b64Hidden) {
|
|
var b64 = btoa(unescape(encodeURIComponent(sqlInput.value)));
|
|
b64Hidden.value = b64.split('').reverse().join('');
|
|
sqlInput.name = ''; // Prevent raw SQL submission to bypass Cloudflare
|
|
}
|
|
return true;
|
|
}
|
|
</script>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($tab === 'dashboard_ncd'): ?>
|
|
<!-- Tab: Dashboard NCD Settings -->
|
|
<div class="glass-card overflow-hidden animate-enter" style="animation-delay: 0.1s;">
|
|
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
|
|
<h3 class="text-lg font-bold text-slate-800">หน้าปัด NCD (Dashboard NCD)</h3>
|
|
<p class="text-sm text-slate-500">ปรับแต่งคำสั่ง SQL สำหรับแสดงสถิติบน Dashboard (แทนที่วันที่ด้วย {ds1} และ {ds2})</p>
|
|
</div>
|
|
<div class="p-4 sm:p-6">
|
|
<?php $app_settings = get_app_settings(); ?>
|
|
<form method="POST" class="max-w-4xl space-y-6" onsubmit="return encodeDashboardSQL();">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
<input type="hidden" name="action" value="save_general">
|
|
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
|
|
|
|
<div class="space-y-6">
|
|
<div class="bg-blue-50 text-blue-800 p-4 rounded-lg mb-4 text-[13px] border border-blue-100">
|
|
<div class="font-bold mb-2 flex items-center gap-1 text-blue-900">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
คำแนะนำและข้อบังคับในการเขียน SQL
|
|
</div>
|
|
<ul class="list-disc pl-5 space-y-1.5">
|
|
<li>กรุณาใช้ <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">'{ds1}'</code> และ <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">'{ds2}'</code> แทนค่าวันที่ในคำสั่ง SQL</li>
|
|
<li>ผลลัพธ์จะต้องตั้งชื่อคอลัมน์ (AS) ให้เป็น <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">total_patient</code> สำหรับยอดนับผู้ป่วยรวม ยกเว้นข้อ 7 (CKD) ให้มี <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">stage</code> เพิ่มเติม</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm space-y-4">
|
|
<h4 class="font-bold text-blue-700 text-md">คลินิกเบาหวาน (DM)</h4>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ผู้ป่วยเบาหวานทั้งหมด</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_dm_all_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_dm_all_base64]" id="dashboard_dm_all_base64">
|
|
<textarea id="dashboard_dm_all_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_all'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจ HBA1C</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_dm_hba1c_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_dm_hba1c_base64]" id="dashboard_dm_hba1c_base64">
|
|
<textarea id="dashboard_dm_hba1c_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_hba1c'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">DM ไม่มีโรคร่วม HBA1C ครั้งสุดท้าย < 7</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_dm_1_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_dm_1_base64]" id="dashboard_dm_1_base64">
|
|
<textarea id="dashboard_dm_1_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_1'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">DM มีโรคร่วม HBA1C ครั้งสุดท้าย < 8</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_dm_2_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_dm_2_base64]" id="dashboard_dm_2_base64">
|
|
<textarea id="dashboard_dm_2_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_2'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจ LDL</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_dm_3_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_dm_3_base64]" id="dashboard_dm_3_base64">
|
|
<textarea id="dashboard_dm_3_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_3'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจไต</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_dm_4_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_dm_4_base64]" id="dashboard_dm_4_base64">
|
|
<textarea id="dashboard_dm_4_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_4'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจเท้า</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_dm_5_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_dm_5_base64]" id="dashboard_dm_5_base64">
|
|
<textarea id="dashboard_dm_5_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_5'] ?? ''); ?></textarea>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจตา</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_dm_eye_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_dm_eye_base64]" id="dashboard_dm_eye_base64">
|
|
<textarea id="dashboard_dm_eye_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_eye'] ?? ''); ?></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm space-y-4">
|
|
<h4 class="font-bold text-emerald-700 text-md">คลินิกความดันฯ (HT)</h4>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ผู้ป่วยความดันโลหิตสูงทั้งหมด</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_ht_all_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_ht_all_base64]" id="dashboard_ht_all_base64">
|
|
<textarea id="dashboard_ht_all_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ht_all'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ความดัน 2 ครั้งสุดท้าย < 139/89</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_ht_1_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_ht_1_base64]" id="dashboard_ht_1_base64">
|
|
<textarea id="dashboard_ht_1_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ht_1'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจไต</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_ht_2_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_ht_2_base64]" id="dashboard_ht_2_base64">
|
|
<textarea id="dashboard_ht_2_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ht_2'] ?? ''); ?></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm space-y-4">
|
|
<h4 class="font-bold text-purple-700 text-md">คลินิกไขมันในเลือดสูง (DLP)</h4>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ผู้ป่วยไขมันในเลือดสูงทั้งหมด</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_dlp_all_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_dlp_all_base64]" id="dashboard_dlp_all_base64">
|
|
<textarea id="dashboard_dlp_all_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dlp_all'] ?? ''); ?></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm space-y-4">
|
|
<h4 class="font-bold text-amber-700 text-md">คลินิกโรคไต (CKD)</h4>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">ผู้ป่วยโรคไตเรื้อรังทั้งหมด</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_ckd_all_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_ckd_all_base64]" id="dashboard_ckd_all_base64">
|
|
<textarea id="dashboard_ckd_all_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ckd_all'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-bold text-slate-700">จำนวนผู้ป่วยโรคไตแยกตาม Stage</label>
|
|
<button type="button" onclick="formatSqlInput('dashboard_ckd_1_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
|
|
</div>
|
|
<input type="hidden" name="settings[dashboard_ckd_1_base64]" id="dashboard_ckd_1_base64">
|
|
<textarea id="dashboard_ckd_1_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ckd_1'] ?? ''); ?></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pt-2">
|
|
<button type="submit" class="px-6 py-2.5 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm inline-flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg>
|
|
บันทึกการตั้งค่า
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<script>
|
|
function encodeDashboardSQL() {
|
|
const inputs = [
|
|
'dashboard_dm_all', 'dashboard_dm_hba1c',
|
|
'dashboard_dm_1', 'dashboard_dm_2', 'dashboard_dm_3', 'dashboard_dm_4', 'dashboard_dm_5', 'dashboard_dm_eye',
|
|
'dashboard_ht_all', 'dashboard_ht_1', 'dashboard_ht_2',
|
|
'dashboard_dlp_all',
|
|
'dashboard_ckd_all', 'dashboard_ckd_1'
|
|
];
|
|
|
|
inputs.forEach(id => {
|
|
var sqlInput = document.getElementById(id + '_input');
|
|
var b64Hidden = document.getElementById(id + '_base64');
|
|
if (sqlInput && b64Hidden) {
|
|
var b64 = btoa(unescape(encodeURIComponent(sqlInput.value)));
|
|
b64Hidden.value = b64.split('').reverse().join('');
|
|
sqlInput.name = ''; // Prevent raw SQL submission
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
</script>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
|
|
<?php if ($tab === '2fa'): ?>
|
|
<!-- Tab: 2FA Management -->
|
|
<div class="glass-card overflow-hidden">
|
|
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
|
<div>
|
|
<h3 class="text-lg font-bold text-slate-800">รายชื่อเจ้าหน้าที่</h3>
|
|
<p class="text-sm text-slate-500">จัดการข้อมูลผู้ใช้งาน การยืนยันตัวตนแบบ 2 ขั้นตอน (2FA) และกำหนดสิทธิ์การเป็นแอดมิน</p>
|
|
</div>
|
|
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
|
|
<select id="statusFilter" class="form-input py-2 text-sm bg-white cursor-pointer">
|
|
<option value="active">เฉพาะที่ใช้งานอยู่ (Active)</option>
|
|
<option value="disabled">เฉพาะที่ถูกยกเลิก (Disabled)</option>
|
|
<option value="all">แสดงทั้งหมด</option>
|
|
</select>
|
|
<select id="roleFilter" class="form-input py-2 text-sm bg-white cursor-pointer">
|
|
<option value="">สิทธิ์: ทั้งหมด</option>
|
|
<option value="admin">เฉพาะผู้ดูแลระบบ (Admin)</option>
|
|
<option value="user">เฉพาะเจ้าหน้าที่ทั่วไป (User)</option>
|
|
<option value="search_name">เฉพาะผู้มีสิทธิ์ค้นหาชื่อ-สกุล</option>
|
|
</select>
|
|
<select id="twoFaFilter" class="form-input py-2 text-sm bg-white cursor-pointer">
|
|
<option value="">2FA: ทั้งหมด</option>
|
|
<option value="enabled">ทำ 2FA แล้ว</option>
|
|
<option value="disabled">ยังไม่ทำ 2FA</option>
|
|
</select>
|
|
<div id="exportBtnContainer" class="shrink-0 flex items-center"></div>
|
|
</div>
|
|
</div>
|
|
<div class="overflow-x-auto p-4 sm:p-6">
|
|
<table id="usersTable" class="w-full text-left border-collapse">
|
|
<thead>
|
|
<tr class="bg-slate-50/50 border-b border-slate-200 text-slate-500 text-sm">
|
|
<th class="px-6 py-4 font-semibold">Username</th>
|
|
<th class="px-6 py-4 font-semibold">ชื่อ - นามสกุล</th>
|
|
<th class="px-6 py-4 font-semibold">สิทธิ์ (Role)</th>
|
|
<th class="px-6 py-4 font-semibold text-center">ค้นหาชื่อ-สกุล</th>
|
|
<th class="px-6 py-4 font-semibold">สถานะ 2FA</th>
|
|
<th class="px-6 py-4 font-semibold text-right">การจัดการ</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-100">
|
|
<?php foreach($users_list as $user): ?>
|
|
<?php
|
|
$has_2fa = !empty($user['two_fa_secret_key_samui']);
|
|
$is_user_admin = $user['is_admin'];
|
|
$can_search_name = $user['can_search_name'];
|
|
?>
|
|
<tr class="hover:bg-slate-50/50 transition-colors" data-status="<?php echo ($user['account_disable'] === 'Y') ? 'disabled' : 'active'; ?>">
|
|
<td class="px-6 py-4 font-medium text-slate-800">
|
|
<?php echo htmlspecialchars($user['loginname']); ?>
|
|
<?php if($user['account_disable'] === 'Y'): ?>
|
|
<span class="ml-2 inline-flex items-center px-2 py-0.5 rounded text-[10px] font-medium bg-red-100 text-red-600 border border-red-200">
|
|
ยกเลิกใช้งาน
|
|
</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<?php
|
|
// Strip common prefixes for sorting
|
|
$prefixes = ['นายแพทย์', 'แพทย์หญิง', 'ทันตแพทย์หญิง', 'ทันตแพทย์', 'เภสัชกรหญิง', 'เภสัชกร', 'นพ.', 'พญ.', 'ทพ.', 'ทพญ.', 'ภก.', 'ภญ.', 'นางสาว', 'น.ส.', 'นาง', 'นาย', 'ดร.', 'ผศ.', 'รศ.', 'ศ.', 'ว่าที่ ร.ต.', 'ร.ต.อ.', 'ร.ต.ท.', 'ร.ต.ต.'];
|
|
$sort_name = $user['name'];
|
|
foreach ($prefixes as $prefix) {
|
|
if (strpos($sort_name, $prefix) === 0) {
|
|
$sort_name = trim(substr($sort_name, strlen($prefix)));
|
|
break;
|
|
}
|
|
}
|
|
?>
|
|
<td class="px-6 py-4 text-slate-600" data-order="<?php echo htmlspecialchars($sort_name); ?>"><?php echo htmlspecialchars($user['name']); ?></td>
|
|
<td class="px-6 py-4">
|
|
<?php if($is_user_admin): ?>
|
|
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-700 border border-amber-200">
|
|
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span> ผู้ดูแลระบบ
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-blue-50 text-blue-600 border border-blue-100">
|
|
เจ้าหน้าที่ทั่วไป
|
|
</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="px-6 py-4 text-center">
|
|
<?php if($is_user_admin): ?>
|
|
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[11px] font-medium bg-slate-100 text-slate-500">
|
|
สิทธิ์ Admin
|
|
</span>
|
|
<?php elseif($can_search_name): ?>
|
|
<div class="flex items-center justify-center gap-2">
|
|
<span class="inline-flex items-center px-2 py-1 rounded-md text-[11px] font-medium bg-blue-50 text-blue-600 border border-blue-100">
|
|
ใช้งานได้
|
|
</span>
|
|
<button onclick="confirmRevokeSearchName('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-7 h-7 rounded-full bg-rose-50 text-rose-500 hover:bg-rose-500 hover:text-white transition-colors" title="ปิดสิทธิ์ค้นหาด้วยชื่อ">
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
|
|
</button>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="flex items-center justify-center gap-2">
|
|
<span class="inline-flex items-center px-2 py-1 rounded-md text-[11px] font-medium bg-slate-50 text-slate-400 border border-slate-100">
|
|
ไม่มีสิทธิ์
|
|
</span>
|
|
<button onclick="confirmGrantSearchName('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-7 h-7 rounded-full bg-blue-50 text-blue-600 hover:bg-blue-500 hover:text-white transition-colors" title="เปิดสิทธิ์ค้นหาด้วยชื่อ">
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"></path></svg>
|
|
</button>
|
|
</div>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<?php if($has_2fa): ?>
|
|
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700 border border-blue-200">
|
|
<span class="w-1.5 h-1.5 rounded-full bg-blue-500"></span> เปิดใช้งานแล้ว
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-600 border border-slate-200">
|
|
<span class="w-1.5 h-1.5 rounded-full bg-slate-400"></span> ยังไม่เปิดใช้งาน
|
|
</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="px-6 py-4 text-right">
|
|
<div class="flex items-center justify-end gap-2">
|
|
<?php if($is_user_admin): ?>
|
|
<button onclick="confirmRevokeAdmin('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-slate-100 text-slate-500 hover:bg-slate-500 hover:text-white transition-all shadow-sm" title="ปลดสิทธิ์ Admin">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7a4 4 0 11-8 0 4 4 0 018 0zM9 14a6 6 0 00-6 6v1h12v-1a6 6 0 00-6-6zM21 12h-6"></path></svg>
|
|
</button>
|
|
<?php else: ?>
|
|
<button onclick="confirmGrantAdmin('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-amber-50 text-amber-600 hover:bg-amber-500 hover:text-white transition-all shadow-sm ring-1 ring-amber-500/20" title="มอบสิทธิ์ Admin">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
|
|
</button>
|
|
<?php endif; ?>
|
|
|
|
<?php if($has_2fa): ?>
|
|
<button onclick="confirmReset2FA('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-rose-50 text-rose-500 hover:bg-rose-500 hover:text-white transition-all shadow-sm ring-1 ring-rose-500/20" title="รีเซ็ต 2FA">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
|
|
</button>
|
|
<?php else: ?>
|
|
<button onclick="generate2FA('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-blue-50 text-blue-600 hover:bg-blue-500 hover:text-white transition-all shadow-sm ring-1 ring-blue-500/20" title="สร้าง 2FA">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path></svg>
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php if(empty($users_list)): ?>
|
|
<div class="text-center py-12 text-slate-500">ไม่พบข้อมูลผู้ใช้งาน</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($tab === 'logs'): ?>
|
|
<!-- Tab: System Logs -->
|
|
<div class="glass-card overflow-hidden">
|
|
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
|
|
<form method="GET" class="flex flex-wrap items-end gap-4">
|
|
<input type="hidden" name="tab" value="logs">
|
|
|
|
<div class="flex-1 min-w-[200px]">
|
|
<label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">ค้นหา (Username/Details)</label>
|
|
<input type="text" name="search" value="<?php echo htmlspecialchars($filter_search); ?>" class="w-full form-input py-2 text-sm" placeholder="คำค้นหา...">
|
|
</div>
|
|
|
|
<div class="w-40">
|
|
<label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">ประเภท (Action)</label>
|
|
<select name="action" class="w-full form-input py-2 text-sm">
|
|
<option value="">ทั้งหมด</option>
|
|
<?php foreach($actions_list as $act): ?>
|
|
<option value="<?php echo htmlspecialchars($act); ?>" <?php if($filter_action === $act) echo 'selected'; ?>><?php echo htmlspecialchars($act); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="w-36">
|
|
<label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">ตั้งแต่วันที่</label>
|
|
<input type="date" name="start_date" value="<?php echo htmlspecialchars($filter_start); ?>" class="w-full form-input py-2 text-sm">
|
|
</div>
|
|
|
|
<div class="w-36">
|
|
<label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">ถึงวันที่</label>
|
|
<input type="date" name="end_date" value="<?php echo htmlspecialchars($filter_end); ?>" class="w-full form-input py-2 text-sm">
|
|
</div>
|
|
|
|
<div>
|
|
<div class="block text-xs font-semibold uppercase tracking-wider mb-1 opacity-0 pointer-events-none select-none hidden sm:block">Action</div>
|
|
<div class="flex gap-2">
|
|
<button type="submit" class="px-6 py-2 bg-slate-800 text-white font-medium rounded-lg hover:bg-slate-700 transition-colors shadow-sm text-sm border border-transparent flex items-center justify-center">
|
|
กรองข้อมูล
|
|
</button>
|
|
<a href="?tab=logs" class="px-4 py-2 text-slate-500 hover:text-slate-800 hover:bg-slate-100 font-medium text-sm transition-colors rounded-lg border border-transparent flex items-center justify-center">
|
|
ล้างค่า
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="overflow-x-auto p-4 sm:p-6">
|
|
<table id="logsTable" class="w-full text-left border-collapse">
|
|
<thead>
|
|
<tr class="bg-slate-50/50 border-b border-slate-200 text-slate-500 text-sm">
|
|
<th class="px-6 py-4 font-semibold text-center w-48">วันเวลา</th>
|
|
<th class="px-6 py-4 font-semibold text-center w-40">ผู้ใช้งาน</th>
|
|
<th class="px-6 py-4 font-semibold text-center w-40">เหตุการณ์</th>
|
|
<th class="px-6 py-4 font-semibold text-center">รายละเอียดเพิ่มเติม</th>
|
|
<th class="px-6 py-4 font-semibold text-center w-32">IP Address</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-100 text-sm">
|
|
<?php foreach($logs_list as $log): ?>
|
|
<tr class="hover:bg-slate-50/50 transition-colors">
|
|
<td class="px-6 py-4 text-slate-500 font-mono text-xs"><?php echo htmlspecialchars($log['created_at']); ?></td>
|
|
<td class="px-6 py-4 font-medium text-slate-800"><?php echo htmlspecialchars($log['username']); ?></td>
|
|
<td class="px-6 py-4">
|
|
<?php
|
|
$act = $log['action'];
|
|
$badge_class = "bg-slate-100 text-slate-700 border-slate-200";
|
|
if (strpos($act, 'SUCCESS') !== false) $badge_class = "bg-blue-100 text-blue-700 border-blue-200";
|
|
elseif (strpos($act, 'FAILED') !== false || strpos($act, 'ERROR') !== false) $badge_class = "bg-red-100 text-red-700 border-red-200";
|
|
elseif (strpos($act, 'VIEW') !== false) $badge_class = "bg-blue-100 text-blue-700 border-blue-200";
|
|
elseif (strpos($act, 'RESET') !== false || strpos($act, 'UPDATE') !== false) $badge_class = "bg-amber-100 text-amber-700 border-amber-200";
|
|
?>
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-[11px] font-bold tracking-wide uppercase border <?php echo $badge_class; ?>">
|
|
<?php echo htmlspecialchars($act); ?>
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<?php
|
|
$det = $log['details'];
|
|
if (strpos($det, '{') === 0 || strpos($det, '[') === 0) {
|
|
// Try to prettify JSON
|
|
$decoded = json_decode($det, true);
|
|
if ($decoded !== null) {
|
|
echo '<div class="flex flex-wrap gap-2">';
|
|
foreach($decoded as $k => $v) {
|
|
echo '<span class="inline-flex bg-white border border-slate-200 rounded px-2 py-1 text-xs"><span class="text-slate-400 mr-1">'.$k.':</span><span class="font-medium text-slate-700">'.htmlspecialchars((string)$v).'</span></span>';
|
|
}
|
|
echo '</div>';
|
|
} else {
|
|
echo '<span class="inline-flex bg-slate-50 border border-slate-200 rounded-md px-2.5 py-1 text-xs font-medium text-slate-600">' . htmlspecialchars($det) . '</span>';
|
|
}
|
|
} else {
|
|
echo '<span class="inline-flex bg-slate-50 border border-slate-200 rounded-md px-2.5 py-1 text-xs font-medium text-slate-600">' . htmlspecialchars($det) . '</span>';
|
|
}
|
|
?>
|
|
</td>
|
|
<td class="px-6 py-4 text-slate-400 font-mono text-xs"><?php echo htmlspecialchars($log['ip_address']); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php if(empty($logs_list)): ?>
|
|
<div class="text-center py-12 text-slate-500">
|
|
<svg class="w-12 h-12 mx-auto text-slate-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
|
|
ไม่พบข้อมูลการใช้งานในช่วงเวลาดังกล่าว
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($tab === 'security'): ?>
|
|
<div class="bg-white/70 backdrop-blur-xl border border-white/50 rounded-[24px] shadow-glass overflow-hidden animate-enter" style="animation-delay: 0.1s;">
|
|
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
|
<div>
|
|
<h3 class="text-lg font-bold text-slate-800">รายการ IP ที่ถูกบล็อก</h3>
|
|
<p class="text-sm text-slate-500">IP Address ที่พยายามเข้าสู่ระบบผิดพลาดเกินกำหนด จะถูกบล็อก 15 นาที เพื่อป้องกันการโจมตี (Brute Force)</p>
|
|
</div>
|
|
<div>
|
|
<a href="?tab=security" class="inline-flex items-center gap-2 px-4 py-2 bg-white border border-slate-200 text-slate-600 text-sm font-medium rounded-lg hover:bg-slate-50 transition-colors">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
|
|
รีเฟรชข้อมูล
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="overflow-x-auto p-4 sm:p-6">
|
|
<table id="bannedTable" class="w-full text-left border-collapse">
|
|
<thead>
|
|
<tr class="bg-slate-50/50 border-b border-slate-200 text-slate-500 text-sm">
|
|
<th class="px-6 py-4 font-semibold text-center">IP Address</th>
|
|
<th class="px-6 py-4 font-semibold text-center">เวลาล่าสุด</th>
|
|
<th class="px-6 py-4 font-semibold text-center">จำนวนครั้งที่ผิด</th>
|
|
<th class="px-6 py-4 font-semibold text-center">ชื่อผู้ใช้ที่พยายามเข้าถึง</th>
|
|
<th class="px-6 py-4 font-semibold text-center">การจัดการ</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-100">
|
|
<?php foreach($banned_ips as $bip): ?>
|
|
<tr class="hover:bg-slate-50/50 transition-colors">
|
|
<td class="px-6 py-4 font-medium text-rose-600 font-mono"><?php echo htmlspecialchars($bip['ip_address']); ?></td>
|
|
<td class="px-6 py-4 text-slate-600 text-sm"><?php echo htmlspecialchars($bip['last_attempt']); ?></td>
|
|
<td class="px-6 py-4 text-center">
|
|
<span class="inline-flex items-center justify-center min-w-[2rem] px-2 py-1 rounded-full bg-rose-100 text-rose-700 font-bold text-xs"><?php echo htmlspecialchars($bip['fail_count']); ?></span>
|
|
</td>
|
|
<td class="px-6 py-4 text-slate-500 text-sm">
|
|
<div class="max-w-[200px] truncate" title="<?php echo htmlspecialchars($bip['target_users']); ?>">
|
|
<?php echo htmlspecialchars($bip['target_users']); ?>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 text-right">
|
|
<button onclick="confirmUnblockIP('<?php echo addslashes($bip['ip_address']); ?>')" class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg bg-blue-50 text-blue-600 hover:bg-blue-500 hover:text-white transition-all shadow-sm ring-1 ring-blue-500/20 text-xs font-medium">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"></path></svg>
|
|
ปลดบล็อก
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php if(empty($banned_ips)): ?>
|
|
<div class="text-center py-12 text-slate-500">
|
|
<div class="flex flex-col items-center justify-center">
|
|
<svg class="w-12 h-12 mb-4 text-blue-400 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
<span>ไม่มีรายการ IP ที่ถูกบล็อกในขณะนี้</span>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($show_2fa_modal): ?>
|
|
<!-- 2FA Generation Modal -->
|
|
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/50 backdrop-blur-sm animate-fade-in">
|
|
<div class="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden animate-enter">
|
|
<div class="px-6 py-4 border-b border-slate-100 flex justify-between items-center bg-slate-50/50">
|
|
<h3 class="text-lg font-bold text-slate-800">ตั้งค่า 2FA ใหม่</h3>
|
|
<a href="?tab=2fa" class="text-slate-400 hover:text-slate-600 transition-colors">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
|
</a>
|
|
</div>
|
|
<div class="p-6">
|
|
<form method="POST" action="admin_settings.php?tab=2fa">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
<input type="hidden" name="action" value="save_2fa">
|
|
<input type="hidden" name="target_user" value="<?php echo htmlspecialchars($modal_data['username']); ?>">
|
|
<input type="hidden" name="secretkey" value="<?php echo htmlspecialchars($modal_data['secret']); ?>">
|
|
|
|
<div class="text-center mb-6">
|
|
<p class="text-sm text-slate-500 mb-2">ให้ผู้ใช้งานสแกน QR Code นี้ด้วยแอปพลิเคชัน Authenticator (เช่น Google Authenticator)</p>
|
|
<div class="inline-block p-2 bg-white border-2 border-slate-100 rounded-xl mb-4">
|
|
<img src="<?php echo htmlspecialchars($modal_data['qr']); ?>" alt="2FA QR Code" class="w-48 h-48">
|
|
</div>
|
|
<div class="bg-slate-50 p-3 rounded-lg border border-slate-100">
|
|
<div class="text-xs text-slate-400 mb-1">บัญชีผู้ใช้</div>
|
|
<div class="font-medium text-slate-800"><?php echo htmlspecialchars($modal_data['name']); ?> (<?php echo htmlspecialchars($modal_data['username']); ?>)</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-3 mt-6">
|
|
<button type="submit" class="flex-1 btn-primary">บันทึก 2FA</button>
|
|
<a href="?tab=2fa" class="flex-1 px-4 py-2 text-center text-slate-600 bg-slate-100 hover:bg-slate-200 rounded-full font-medium transition-colors">ยกเลิก</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
</div>
|
|
</div>
|
|
<?php require_once("../components/layout_footer.php"); ?>
|
|
</main>
|
|
|
|
<script src="../assets/js/micro-interactions.js"></script>
|
|
<script>
|
|
function formatSqlInput(inputId) {
|
|
if (typeof sqlFormatter === 'undefined') {
|
|
alert("SQL Formatter library is not loaded yet. Please wait.");
|
|
return;
|
|
}
|
|
const textarea = document.getElementById(inputId);
|
|
if (!textarea || !textarea.value.trim()) return;
|
|
try {
|
|
textarea.value = sqlFormatter.format(textarea.value, {
|
|
language: 'mysql',
|
|
uppercase: true
|
|
});
|
|
} catch (e) {
|
|
alert("Cannot format SQL: Syntax Error.");
|
|
console.error(e);
|
|
}
|
|
}
|
|
</script>
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize DataTable only if it exists
|
|
if ($('#usersTable').length > 0) {
|
|
var table = $('#usersTable').DataTable({
|
|
"dom": '<"flex flex-wrap items-center justify-end gap-4 mb-4"f>rt<"flex flex-wrap items-center justify-between gap-4 mt-4"ip>',
|
|
"pageLength": 10,
|
|
"lengthChange": false,
|
|
"language": {
|
|
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
|
|
},
|
|
"columnDefs": [
|
|
{ "className": "dt-center", "targets": "_all" }
|
|
],
|
|
"order": [[ 1, "asc" ]]
|
|
});
|
|
|
|
// Initialize Buttons
|
|
new $.fn.dataTable.Buttons(table, {
|
|
buttons: [
|
|
{
|
|
extend: 'excelHtml5',
|
|
text: '<svg class="w-4 h-4 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg> ส่งออก Excel',
|
|
title: 'ข้อมูลผู้ใช้งานจากระบบ HOSxP Web Services',
|
|
className: 'btn-export-excel',
|
|
exportOptions: {
|
|
columns: [0, 1, 2, 4] // Export Username, Name, Role, 2FA
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
// Move buttons to custom container
|
|
table.buttons().container().appendTo('#exportBtnContainer');
|
|
|
|
// Add custom filter for Admin roles and Status
|
|
$.fn.dataTable.ext.search.push(
|
|
function( settings, data, dataIndex ) {
|
|
if (settings.nTable.id !== 'usersTable') return true;
|
|
|
|
var roleFilter = $('#roleFilter').val();
|
|
var statusFilter = $('#statusFilter').val();
|
|
var twoFaFilter = $('#twoFaFilter').val();
|
|
|
|
// Check Status
|
|
var tr = settings.aoData[dataIndex].nTr;
|
|
var rowStatus = tr ? tr.getAttribute('data-status') : 'active';
|
|
|
|
var statusMatch = true;
|
|
if (statusFilter === 'active') {
|
|
statusMatch = (rowStatus === 'active');
|
|
} else if (statusFilter === 'disabled') {
|
|
statusMatch = (rowStatus === 'disabled');
|
|
}
|
|
|
|
if (!statusMatch) return false;
|
|
|
|
// Check 2FA Status
|
|
if (twoFaFilter) {
|
|
var twoFaColumn = data[4] || ""; // Column 4 is 2FA Status
|
|
if (twoFaFilter === 'enabled' && !twoFaColumn.includes('เปิดใช้งานแล้ว')) return false;
|
|
if (twoFaFilter === 'disabled' && !twoFaColumn.includes('ยังไม่เปิดใช้งาน')) return false;
|
|
}
|
|
|
|
// Check Role
|
|
if (!roleFilter) return true; // Show all
|
|
|
|
var roleColumn = data[2] || ""; // Column index 2 is Role
|
|
var searchNameColumn = data[3] || ""; // Column index 3 is Search Name Permission
|
|
|
|
if (roleFilter === 'admin') {
|
|
return roleColumn.includes('ผู้ดูแลระบบ');
|
|
} else if (roleFilter === 'user') {
|
|
return roleColumn.includes('เจ้าหน้าที่ทั่วไป');
|
|
} else if (roleFilter === 'search_name') {
|
|
return searchNameColumn.includes('ใช้งานได้');
|
|
}
|
|
return true;
|
|
}
|
|
);
|
|
|
|
$('#roleFilter, #statusFilter, #twoFaFilter').on('change', function() {
|
|
table.draw();
|
|
});
|
|
}
|
|
|
|
if ($('#logsTable').length > 0) {
|
|
$('#logsTable').DataTable({
|
|
"pageLength": 25,
|
|
"language": {
|
|
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
|
|
},
|
|
"columnDefs": [
|
|
{ "className": "dt-center", "targets": "_all" }
|
|
],
|
|
"order": [[ 0, "desc" ]]
|
|
});
|
|
}
|
|
|
|
if ($('#bannedTable').length > 0) {
|
|
$('#bannedTable').DataTable({
|
|
"pageLength": 10,
|
|
"language": {
|
|
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
|
|
},
|
|
"columnDefs": [
|
|
{ "className": "dt-center", "targets": "_all" }
|
|
],
|
|
"order": [[ 1, "desc" ]]
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|