Access Denied

คุณไม่มีสิทธิ์เข้าถึงหน้านี้

กลับหน้าหลัก"); } $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("

CSRF Token Validation Failed

การยืนยันตัวตนล้มเหลว กรุณาโหลดหน้าเว็บใหม่

โหลดใหม่
"); } $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 สำหรับผู้ใช้ " . htmlspecialchars($target_user) . " เรียบร้อยแล้ว ระบบจะบังคับให้แสกน 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 สำหรับ " . htmlspecialchars($target_user) . " เรียบร้อยแล้ว"; } $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 = "มอบสิทธิ์ผู้ดูแลระบบให้ " . htmlspecialchars($target_user) . " เรียบร้อยแล้ว"; } $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 = "ปลดสิทธิ์ผู้ดูแลระบบของ " . htmlspecialchars($target_user) . " เรียบร้อยแล้ว"; } $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 = "เปิดสิทธิ์ค้นหาด้วยชื่อ-สกุล ให้ " . htmlspecialchars($target_user) . " เรียบร้อยแล้ว"; } $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 = "ปิดสิทธิ์ค้นหาด้วยชื่อ-สกุล ของ " . htmlspecialchars($target_user) . " เรียบร้อยแล้ว"; } $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 " . htmlspecialchars($target_ip) . " เรียบร้อยแล้ว"; } } } 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 = "เพิ่มประวัติเวอร์ชั่น " . htmlspecialchars($r_ver) . " เรียบร้อยแล้ว"; } } $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']; } } ?>

ตั้งค่าระบบ (Admin Settings)

จัดการผู้ใช้งาน, ระบบยืนยันตัวตน และตรวจสอบประวัติการใช้งาน

การตั้งค่าทั่วไป

ปรับแต่งข้อความแสดงผลบนหน้าล็อกอินและแถบเมนู

หน้าล็อกอิน (Login Page)

แถบเมนู (Sidebar)

แถบด้านล่าง (Footer)

สามารถใส่ {YEAR} แทนปีปัจจุบันอัตโนมัติ และรองรับ HTML เบื้องต้นเช่น &copy; (ข้อความ Powered By จะถูกบังคับแสดงขวามือเสมอ)

เงื่อนไขการค้นหาประวัติการรับบริการ

ระบบรักษาความปลอดภัย (Security)

การแจ้งเตือนเมื่อมีการแบน IP (Notifications)

ตั้งค่าการแจ้งเตือนเมื่อระบบระงับ IP เนื่องจากพยายามล็อกอินผิดพลาด

ประกาศข่าวสาร (Version History)

บันทึกประวัติการอัปเดตและแจ้งเตือนเวอร์ชั่นใหม่

เพิ่มประวัติใหม่

ยังไม่มีประวัติการอัปเดต

format('Y') + 543; $display_date = $d->format('d-m-') . $thai_year; ?>

ตั้งค่าการแสดงผลเพิ่มเติม

ปรับแต่งคำสั่ง SQL และการเชื่อมต่อฐานข้อมูลสำหรับดึงข้อมูลมาแสดงผลในส่วนต่างๆ

ระบบประวัติวัคซีน (Vaccination System)

ดึงข้อมูลประวัติการรับวัคซีนจากฐานข้อมูล HIS

คำแนะนำและข้อบังคับในการเขียน SQL
  • กรุณาใช้คำว่า 'ระบุ_HN' (มี Single Quote) แทนรหัส HN ของผู้ป่วยในเงื่อนไข WHERE เสมอ
  • ผลลัพธ์จะต้องตั้งชื่อคอลัมน์ (AS) ให้ตรงกับตัวแปรเหล่านี้:
    • vaccine_group = ชื่อกลุ่มของวัคซีน (ระบบใช้คอลัมน์นี้ในการแบ่งกลุ่มและแยกสี)
    • vaccine_name = ชื่อวัคซีนที่ฉีด
    • vaccine_date = วันที่รับวัคซีน (รูปแบบ YYYY-MM-DD)
    • vaccine_code = รหัสวัคซีน (แสดงเป็นตัวเล็กๆ ข้างชื่อ)
    • vaccine_lot_no = หมายเลข Lot (ไม่บังคับ)
    • vaccine_place_name = สถานที่รับวัคซีน (ไม่บังคับ)

ทะเบียนผู้ป่วย (NCD Registry)

ปรับแต่งคำสั่ง SQL สำหรับดึงข้อมูลในระบบทะเบียนผู้ป่วย NCD

ชุดคำสั่ง SQL (NCD Registry SQL)

ดึงข้อมูลทะเบียนผู้ป่วยเรื้อรังจาก HOSxP

คำแนะนำและข้อบังคับในการเขียน SQL
  • ต้องมีตัวแทน (Placeholder) ชื่อ {clinic_cond} เพื่อให้ระบบนำเงื่อนไขคลินิกไปแทรก (เช่น `AND cl.clinic = '001'`)
  • ต้องมี `?` 2 ตำแหน่งสำหรับ bind param ค่าวันที่ (`vstdate between ? and ?`) ใน Prepared Statement
  • ต้องทำการตั้งชื่อ (AS) ให้ตรงกับที่ระบบนำไปแสดงผล (เช่น pt_name, age_y, tmb_name)

ตั้งค่ารหัสคลินิก (Clinic Codes Mapping)

หน้าปัด NCD (Dashboard NCD)

ปรับแต่งคำสั่ง SQL สำหรับแสดงสถิติบน Dashboard (แทนที่วันที่ด้วย {ds1} และ {ds2})

คำแนะนำและข้อบังคับในการเขียน SQL
  • กรุณาใช้ '{ds1}' และ '{ds2}' แทนค่าวันที่ในคำสั่ง SQL
  • ผลลัพธ์จะต้องตั้งชื่อคอลัมน์ (AS) ให้เป็น total_patient สำหรับยอดนับผู้ป่วยรวม ยกเว้นข้อ 7 (CKD) ให้มี stage เพิ่มเติม

คลินิกเบาหวาน (DM)

คลินิกความดันฯ (HT)

คลินิกไขมันในเลือดสูง (DLP)

คลินิกโรคไต (CKD)

รายชื่อเจ้าหน้าที่

จัดการข้อมูลผู้ใช้งาน การยืนยันตัวตนแบบ 2 ขั้นตอน (2FA) และกำหนดสิทธิ์การเป็นแอดมิน

Username ชื่อ - นามสกุล สิทธิ์ (Role) ค้นหาชื่อ-สกุล สถานะ 2FA การจัดการ
ยกเลิกใช้งาน ผู้ดูแลระบบ เจ้าหน้าที่ทั่วไป สิทธิ์ Admin
ใช้งานได้
ไม่มีสิทธิ์
เปิดใช้งานแล้ว ยังไม่เปิดใช้งาน
ไม่พบข้อมูลผู้ใช้งาน
ล้างค่า
วันเวลา ผู้ใช้งาน เหตุการณ์ รายละเอียดเพิ่มเติม IP Address
'; foreach($decoded as $k => $v) { echo ''.$k.':'.htmlspecialchars((string)$v).''; } echo ''; } else { echo '' . htmlspecialchars($det) . ''; } } else { echo '' . htmlspecialchars($det) . ''; } ?>
ไม่พบข้อมูลการใช้งานในช่วงเวลาดังกล่าว

รายการ IP ที่ถูกบล็อก

IP Address ที่พยายามเข้าสู่ระบบผิดพลาดเกินกำหนด จะถูกบล็อก 15 นาที เพื่อป้องกันการโจมตี (Brute Force)

IP Address เวลาล่าสุด จำนวนครั้งที่ผิด ชื่อผู้ใช้ที่พยายามเข้าถึง การจัดการ
ไม่มีรายการ IP ที่ถูกบล็อกในขณะนี้

ตั้งค่า 2FA ใหม่

ให้ผู้ใช้งานสแกน QR Code นี้ด้วยแอปพลิเคชัน Authenticator (เช่น Google Authenticator)

2FA QR Code
บัญชีผู้ใช้
()
ยกเลิก