Menu

8/15/25

Reset password root user in Vcenter

Hướng dẫn chi tiết từng bước để đặt lại mật khẩu tài khoản root của vCenter Server (VCSA) khi bạn không nhớ mật khẩu, thực hiện trực tiếp từ ESXi Host.

1. Tạo snapshot (đề phòng)

  • Trước khi thực hiện bất kỳ thay đổi nào, hãy tạo một snapshot của máy ảo vCenter Server để có thể quay lại trạng thái ban đầu nếu có sự cố.

2. Truy cập console và khởi động lại máy ảo

  1. Đăng nhập vào giao diện web của ESXi Host nơi vCenter Server đang chạy.

  2. Trong danh sách máy ảo, nhấp vào máy ảo vCenter Server.

  3. Ở menu bên trái, chọn Console để mở giao diện console của máy ảo.

  4. Khởi động lại máy ảo vCenter Server bằng cách nhấp vào nút Reset hoặc Power Off rồi Power On.

3. Sửa đổi tham số khởi động GRUB

  1. Khi máy ảo bắt đầu khởi động và màn hình Photon OS xuất hiện, nhanh chóng nhấn phím "e" để vào menu GRUB.

  2. Bạn sẽ thấy một màn hình chỉnh sửa với một số dòng lệnh. Sử dụng các phím mũi tên để di chuyển xuống dòng bắt đầu bằng chữ linux.

  3. Di chuyển con trỏ đến cuối dòng này và thêm đoạn code sau vào cuối, cách nhau bằng một dấu cách:

    rw init=/bin/bash
    
  4. Nhấn F10 để khởi động máy ảo với các tham số mới. 

4. Đặt lại mật khẩu root

  1. Sau khi hệ thống khởi động, nó sẽ đưa bạn đến một dấu nhắc lệnh.

  2. Nhập lệnh sau để đặt lại mật khẩu:

    passwd
    
  3. Nhập mật khẩu root mới của bạn và xác nhận lại.

  4. Khi quá trình hoàn tất, bạn cần gỡ bỏ phân vùng bằng lệnh:

    umount /
    
  5. Cuối cùng, gõ lệnh sau để khởi động lại máy ảo:

    reboot -f
    

Sau khi vCenter Server khởi động lại, bạn có thể đăng nhập bằng tài khoản root và mật khẩu mới bạn vừa đặt.

8/13/25

Maintenance mode in EXSI 8

 


Maintenance Mode (Chế độ bảo trì) là một trạng thái đặc biệt của một host ESXi, cho phép bạn thực hiện các công việc bảo trì trên host đó một cách an toàn mà không làm ảnh hưởng đến các máy ảo đang chạy.

Khi một host ESXi được đặt vào Maintenance Mode, nó sẽ thực hiện các hành động sau:

  1. Di chuyển các máy ảo: Nếu host nằm trong một cluster và tính năng vMotion được kích hoạt, vCenter sẽ tự động di chuyển tất cả các máy ảo đang chạy trên host đó sang các host ESXi khác trong cluster. Điều này giúp đảm bảo các máy ảo tiếp tục hoạt động mà không bị gián đoạn.

  2. Ngăn cấm khởi động máy ảo: Host sẽ không cho phép bất kỳ máy ảo nào mới được bật nguồn.

  3. Ngăn cấm tác vụ mới: Host sẽ không chấp nhận các tác vụ mới (như thêm máy ảo, di chuyển tài nguyên,...) từ vCenter.

Mục đích chính của Maintenance Mode:

  • Bảo trì không gián đoạn (Zero-Downtime Maintenance): Cho phép bạn thực hiện các công việc như nâng cấp phần mềm (patching), cập nhật phần cứng, hoặc trong trường hợp của bạn là gỡ host ra khỏi vCenter, mà không cần tắt nguồn máy ảo.

  • Bảo vệ dữ liệu: Đảm bảo không có dữ liệu nào bị mất hoặc hỏng do host bị tắt đột ngột.

  • Tăng tính ổn định của Cluster: Báo hiệu cho vCenter biết host này không thể sử dụng để chạy máy ảo, giúp vCenter quản lý và phân bổ tài nguyên hợp lý hơn cho các host còn lại.

Nói một cách đơn giản, Maintenance Mode giống như việc bạn treo biển "Đang sửa chữa, vui lòng sử dụng lối đi khác" cho một con đường. Các phương tiện đang đi trên con đường đó sẽ được chuyển sang đường khác, và không ai được phép đi vào con đường đang sửa chữa.

Move VMs trong EXSI 8 (không có thời gian downtime)

B1, Xác định VMnetwork mà VM đang dùng, và nơi lưu trữ datastore



B2, Chọn VM >> Migrate


B3, ping VM để monitor trong quá trình move VM






B4, kiểm tra và xác nhận thay đổi datastore, vmnetwork.















Upload máy ảo VM từ Wmworkstation lên EXSI hay Vcenter

    B1,
 B2,

B3,



B4, xong





Mô hình dựng EXSI 8 trên Wmworkstation

 


Mô hình phía dưới cho A Minh group 7NetLab vẽ lại





8/9/25

Cài wmware-tool trên win 11 trong EXSI 8

 B1, click vào máy ảo >> install vmware tool 

 
B2, Mở cửa sổ windows explorer bạn sẽ thầy CD cài vmware tool đã được map sẵn trong win 11, chỉ việc click vào và cài thôi



8/8/25

Cài win 11 trong vCenter (EXSI 8)

Bước 1, Tạo Cluster trong Vcenter

Bước 2,  Thêm V-TMP mới (lưu ý bước này nhớ bỏ chọn checkbox: Use Key provider only with TPM protected EXSI)

Bước 3, Backup và sao lưu V-TMP và password, vậy là bạn đã có thể cài win 11 bình thường ròi!



Trong vCenter, V-TMP (Virtual Trusted Platform Module) được sử dụng để cung cấp tính năng bảo mật dựa trên TPM cho máy ảo, chẳng hạn như mã hóa ổ đĩa hoặc xác thực phần cứng. Dựa trên việc bạn đã xuất file và sao chép mật khẩu, đây là cách sử dụng và mục đích sau này:

  1. Cách sử dụng V-TMP trong vCenter:
    • Sau khi tạo V-TMP và liên kết với key provider, bạn cần gắn nó vào máy ảo. Trong "Edit Settings" của máy ảo, thêm TPM 2.0 và chọn key provider đã tạo.
    • File V-TMP (thường là file .vTPM) và mật khẩu sẽ được sử dụng để bảo vệ khóa mã hóa. Khi khởi động máy ảo, vCenter sẽ yêu cầu mật khẩu này để kích hoạt TPM.
  2. Mục đích sau này:
    • Mã hóa ổ đĩa: V-TMP cho phép sử dụng tính năng mã hóa của VMware (như vSphere VM Encryption) để bảo vệ dữ liệu máy ảo.
    • Khôi phục hoặc di chuyển máy ảo: File V-TMP và mật khẩu là cần thiết khi bạn di chuyển máy ảo sang host khác hoặc khôi phục sau lỗi. Lưu trữ an toàn file và mật khẩu để tránh mất dữ liệu.
    • Tuân thủ bảo mật: TPM giúp đáp ứng các yêu cầu bảo mật, như xác thực phần cứng hoặc tuân thủ quy định (VD: FIPS).

Hãy lưu file V-TMP và mật khẩu ở nơi an toàn (như hệ thống quản lý khóa hoặc ổ cứng mã hóa), vì mất chúng sẽ khiến bạn không thể truy cập dữ liệu mã hóa.

8/6/25

Enable ssh with root user in almalinux

 By default, SSH root login is disabled on AlmaLinux for security reasons. To enable it, you need to edit the SSH daemon's configuration file.

1. Edit the SSH Configuration File

vi /etc/ssh/sshd_config.d/mycf.conf

2. Add the PermitRootLogin Directive

PermitRootLogin yes

3. Save and Close the File

  • If you're using nano, press Ctrl + X, then Y, and then Enter to save the changes.


4. Restart the SSH Service

For the changes to take effect, you must restart the sshd service.

sudo systemctl restart sshd

Now, you should be able to log in to your AlmaLinux server as the root user via SSH. However, it is strongly recommended that you use a non-root user for daily administration and only use the sudo command when necessary, as enabling root SSH can pose a significant security risk.

Check logs: [root@vnpt ~]#  journalctl -u sshd


Đặt IP tĩnh trên almalinux

 Để đặt địa chỉ IP tĩnh trên AlmaLinux, bạn có thể chỉnh sửa file cấu hình mạng bằng cách sử dụng trình soạn thảo văn bản như nano hoặc vi. Dưới đây là hướng dẫn chi tiết các bước thực hiện.

Các bước thực hiện

1. Tìm tên interface mạng

Đầu tiên, bạn cần tìm tên của interface mạng mà bạn muốn đặt IP tĩnh. Bạn có thể sử dụng lệnh sau:

Bash
ip a

Lệnh này sẽ hiển thị danh sách tất cả các interface mạng trên hệ thống của bạn. Thông thường, tên interface sẽ bắt đầu bằng enp, ens, hoặc eth. Ví dụ, enp0s3.

1. Xem tên card mạng

nmcli connection show

2. Cấu hình IP tĩnh

Thay thế enp0s3 bằng tên card mạng của bạn và các thông số IP phù hợp.

sudo nmcli connection modify enp0s3 ipv4.method manual

sudo nmcli connection modify enp0s3 ipv4.addresses 192.168.1.100/24

sudo nmcli connection modify enp0s3 ipv4.gateway 192.168.1.1

sudo nmcli connection modify enp0s3 ipv4.dns "8.8.8.8, 8.8.4.4"

3. Áp dụng thay đổi

sudo nmcli connection up enp0s3

4. Kiểm tra

ip a


Dựng DNS server trên Almalinux

 Mục tiêu:

  • Cài đặt và cấu hình BIND trên AlmaLinux.

  • Tạo một zone cho homelab.local để phân giải các host trong mạng nội bộ của bạn.

  • Cấu hình DNS server để lắng nghe trên địa chỉ IP của nó.

  • Cấu hình firewall để cho phép truy cập DNS.

Bước 1: Cài đặt BIND

Trước tiên, hãy cập nhật hệ thống và cài đặt gói bindbind-utils.

sudo dnf update -y
sudo dnf install bind bind-utils -y

Bước 2: Cấu hình BIND

File cấu hình chính của BIND là /etc/named.conf. Chúng ta sẽ chỉnh sửa file này và tạo các file zone.

Chỉnh sửa /etc/named.conf

Mở file cấu hình chính:

sudo nano /etc/named.conf

Bạn cần tìm và thay đổi một số dòng sau:

  1. listen-on port 53: Thay đổi 127.0.0.1 thành địa chỉ IP của máy chủ DNS của bạn hoặc any để nó lắng nghe trên tất cả các interface. Ví dụ, nếu IP của bạn là 192.168.1.100:

    listen-on port 53 { 127.0.0.1; 192.168.1.100; };
    

    Hoặc nếu bạn muốn nó lắng nghe trên tất cả các IP:

    listen-on port 53 { any; };
    
  2. allow-query: Cho phép các client trong mạng của bạn gửi truy vấn. Thay đổi localhost thành dải mạng của bạn. Ví dụ:

    allow-query { localhost; 192.168.1.0/24; };
    
  3. Thêm zone cho homelab.local: Thêm một block zone vào cuối file để định nghĩa zone mới của chúng ta.

    zone "homelab.local" IN {
        type master;
        file "homelab.local.zone";
        allow-update { none; };
    };
    
  4. Thêm zone cho phân giải ngược (Reverse Zone): Để các client có thể phân giải IP thành tên miền, bạn cần tạo một zone phân giải ngược. Giả sử dải mạng của bạn là 192.168.1.0/24, bạn sẽ định nghĩa zone như sau:

    zone "1.168.192.in-addr.arpa" IN {
        type master;
        file "homelab.local.rev";
        allow-update { none; };
    };
    

    Lưu ý: Tên zone phải là dải mạng của bạn viết ngược lại, cộng thêm .in-addr.arpa.

Sau khi chỉnh sửa xong, file /etc/named.conf của bạn có thể trông giống như thế này (đã rút gọn):

options {
    listen-on port 53 { any; };
    listen-on-v6 port 53 { none; };
    directory "/var/named";
    dump-file "/var/named/data/cache_dump.db";
    statistics-file "/var/named/data/named_stats.txt";
    memstatistics-file "/var/named/data/named_mem_stats.txt";
    recursing-file "/var/named/data/named.recursing";
    secroots-file "/var/named/data/named.secroots";
    allow-query { any; };
    recursion yes;
    dnssec-enable yes;
    dnssec-validation yes;
    ...
};

logging { ... };

zone "." IN {
    type hint;
    file "named.ca";
};

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

zone "homelab.local" IN {
    type master;
    file "homelab.local.zone";
    allow-update { none; };
};

zone "1.168.192.in-addr.arpa" IN {
    type master;
    file "homelab.local.rev";
    allow-update { none; };
};

Tạo file zone forward /var/named/homelab.local.zone

File này sẽ chứa các bản ghi phân giải tên miền thành địa chỉ IP.

sudo nano /var/named/homelab.local.zone

Nội dung file:

$TTL 86400
@       IN      SOA     ns1.homelab.local. admin.homelab.local. (
                2023080601      ; Serial
                3600            ; Refresh
                1800            ; Retry
                604800          ; Expire
                86400           ; Minimum TTL
)

        IN      NS      ns1.homelab.local.

ns1     IN      A       192.168.1.100       ; IP của DNS Server
router  IN      A       192.168.1.1         ; IP của router
web     IN      A       192.168.1.101       ; Một máy chủ web

Giải thích:

  • $TTL: Thời gian tồn tại mặc định của bản ghi.

  • @ IN SOA: Bản ghi "Start of Authority" (SOA), định nghĩa máy chủ DNS chính cho zone này.

  • ns1.homelab.local.: Tên máy chủ DNS của bạn.

  • admin.homelab.local.: Email của quản trị viên (dấu @ được thay bằng dấu chấm).

  • Serial: Số phiên bản của file cấu hình. Mỗi khi bạn thay đổi file, hãy tăng số này lên.

  • @ IN NS: Bản ghi Name Server, chỉ định máy chủ DNS cho zone này.

  • ns1 IN A 192.168.1.100: Bản ghi "A" (Address) phân giải tên ns1 thành địa chỉ IP 192.168.1.100.

Tạo file zone reverse /var/named/homelab.local.rev

File này sẽ chứa các bản ghi phân giải IP thành tên miền.

sudo nano /var/named/homelab.local.rev

Nội dung file:

$TTL 86400
@       IN      SOA     ns1.homelab.local. admin.homelab.local. (
                2023080601      ; Serial
                3600            ; Refresh
                1800            ; Retry
                604800          ; Expire
                86400           ; Minimum TTL
)

        IN      NS      ns1.homelab.local.

100     IN      PTR     ns1.homelab.local.
1       IN      PTR     router.homelab.local.
101     IN      PTR     web.homelab.local.

Giải thích:

  • 100 IN PTR ns1.homelab.local.: Bản ghi "PTR" (Pointer) phân giải IP 192.168.1.100 (chỉ viết phần cuối của IP) thành tên miền ns1.homelab.local..

Đặt quyền sở hữu cho các file zone

Sau khi tạo, bạn cần đảm bảo BIND có thể đọc được các file này.

sudo chown named:named /var/named/homelab.local.zone
sudo chown named:named /var/named/homelab.local.rev

Bước 3: Kiểm tra cấu hình và khởi động BIND

Kiểm tra cú pháp cấu hình

Sử dụng lệnh named-checkconfnamed-checkzone để kiểm tra các file cấu hình.

sudo named-checkconf
sudo named-checkzone homelab.local /var/named/homelab.local.zone
sudo named-checkzone 1.168.192.in-addr.arpa /var/named/homelab.local.rev

Nếu không có lỗi, các lệnh này sẽ không trả về gì hoặc hiển thị thông báo OK.

Khởi động và kích hoạt dịch vụ BIND

sudo systemctl start named
sudo systemctl enable named
sudo systemctl status named
sudo systemctl reload named

Đảm bảo dịch vụ đang chạy (trạng thái active (running)).

Bước 4: Cấu hình Firewall

Mở cổng 53 (cổng mặc định của DNS) trên firewall để cho phép các client truy cập.

sudo firewall-cmd --permanent --add-service=dns
sudo firewall-cmd --reload

Bước 5: Kiểm tra hoạt động của DNS server

Bạn có thể kiểm tra trực tiếp trên máy chủ DNS bằng cách thay đổi file /etc/resolv.conf hoặc sử dụng các công cụ như dig.

# Sử dụng dig để kiểm tra
dig @127.0.0.1 ns1.homelab.local
dig @127.0.0.1 web.homelab.local
dig @127.0.0.1 -x 192.168.1.101

Nếu bạn thấy các bản ghi ANSWER SECTION trả về đúng, DNS server của bạn đã hoạt động.

Bước 6: Cấu hình Client để sử dụng DNS server

Trên các máy client trong mạng của bạn, hãy cấu hình DNS server thành địa chỉ IP của máy chủ DNS bạn vừa dựng (192.168.1.100).

  • Trên Linux: Chỉnh sửa file /etc/resolv.conf hoặc cấu hình trong NetworkManager.

    sudo nano /etc/resolv.conf
    

    Thêm dòng sau:

    nameserver 192.168.1.100
    
  • Trên Windows/macOS: Vào cài đặt mạng và thay đổi DNS server.

Sau khi cấu hình, bạn có thể kiểm tra từ máy client:

ping web.homelab.local

Lệnh này sẽ ping đến 192.168.1.101 nếu cấu hình DNS của bạn chính xác.


8/3/25

Hướng dẫn cài wmware tool trên almalinux

 SSH vào Almalinux và gõ các lệnh sau:

[root@Alma-NFS ~]# sudo dnf update

 [root@Alma-NFS ~]# dnf install open-vm-tools

[root@Alma-NFS ~]# sudo systemctl enable vmtoolsd

[root@Alma-NFS ~]# sudo systemctl start vmtoolsd

[root@Alma-NFS ~]# sudo reboot

Sau khi reboot kiểm tra kết quả:



7/29/25

chuẩn bị môi trường cài webserver Xampp trên windows

 B1. Tải và cài đặt Xampp mới nhất



B2, Sau khi cái xong, start các service và kiểm tra xem máy chủ web đã chạy hay chưa, truy cập trình duyệt:



B3. Truy cập phpmysql


B4, Extract sourcecode php và import .sql vô phpmyadmin




7/28/25

Code app xem phần cứng máy tính với python và gemini - AI

 

B1. Script cho AI thực hiện gen mã nguồn Python.

mình đang dùng python 3.13.5 hãy viết cho tôi 1 phần mềm đọc thông tin phần cứng máy tính có giao diện đồ họa, mã nguồn nằm trong 1 file .py duy nhất, Phần mềm sẽ sử dụng SQLite làm cơ sở dữ liệu , có thể chạy được luôn, dễ dàng nâng cấp sau này để trở thành 1 tab của phần mềm lớn khác

B2. Sau khi cài xong python mới nhất: 3.13.5 thì chạy các lệnh sau trong cmd để tải package thư viện hỗ trợ xem thông tin phần cứng máy tính về:

C:\Windows\System32>pip install psutil

C:\Windows\System32>pip install wmi

C:\Windows\System32>pip install py-cpuinfo

B3. Code do gemini tạo mình save thành file hardware_monitor.py ròi chạy trong cmd:

d:\>py -3 hardware_monitor.py



import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3
import platform
import psutil
import wmi # Chỉ dành cho Windows
import cpuinfo
import socket # Import thư viện socket để dùng AF_INET

class HardwareInfoApp:
    def __init__(self, master):
        self.master = master
        master.title("Thông tin phần cứng máy tính")
        master.geometry("800x600")

        self.db_name = "hardware_info.db"
        self.create_database()

        self.create_widgets()
        self.load_hardware_info()

    def create_database(self):
        conn = sqlite3.connect(self.db_name)
        cursor = conn.cursor()
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS hardware_data (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                category TEXT,
                item TEXT,
                value TEXT
            )
        """)
        conn.commit()
        conn.close()

    def create_widgets(self):
        # Notebook for tabs
        self.notebook = ttk.Notebook(self.master)
        self.notebook.pack(expand=True, fill="both", padx=10, pady=10)

        # Tab 1: System Info
        self.system_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.system_frame, text="Hệ thống")
        self.create_system_info_widgets(self.system_frame)

        # Tab 2: CPU Info
        self.cpu_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.cpu_frame, text="CPU")
        self.create_cpu_info_widgets(self.cpu_frame)

        # Tab 3: Memory Info
        self.memory_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.memory_frame, text="RAM")
        self.create_memory_info_widgets(self.memory_frame)

        # Tab 4: Disk Info
        self.disk_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.disk_frame, text="Ổ đĩa")
        self.create_disk_info_widgets(self.disk_frame)

        # Tab 5: Network Info
        self.network_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.network_frame, text="Mạng")
        self.create_network_info_widgets(self.network_frame)

        # Tab 6: GPU Info (Windows only)
        # Chỉ tạo tab GPU nếu hệ điều hành là Windows để tránh lỗi import wmi trên Linux/macOS
        if platform.system() == "Windows":
            self.gpu_frame = ttk.Frame(self.notebook)
            self.notebook.add(self.gpu_frame, text="GPU")
            self.create_gpu_info_widgets(self.gpu_frame)


        # Buttons
        button_frame = ttk.Frame(self.master)
        button_frame.pack(pady=5)

        self.refresh_button = ttk.Button(button_frame, text="Làm mới", command=self.load_hardware_info)
        self.refresh_button.pack(side="left", padx=5)

        self.save_button = ttk.Button(button_frame, text="Lưu vào DB", command=self.save_all_info_to_db)
        self.save_button.pack(side="left", padx=5)

    def create_system_info_widgets(self, parent_frame):
        self.system_info_tree = ttk.Treeview(parent_frame, columns=("Item", "Value"), show="headings")
        self.system_info_tree.heading("Item", text="Mục")
        self.system_info_tree.heading("Value", text="Giá trị")
        self.system_info_tree.pack(expand=True, fill="both")

        vsb = ttk.Scrollbar(parent_frame, orient="vertical", command=self.system_info_tree.yview)
        vsb.pack(side='right', fill='y')
        self.system_info_tree.configure(yscrollcommand=vsb.set)

    def create_cpu_info_widgets(self, parent_frame):
        self.cpu_info_tree = ttk.Treeview(parent_frame, columns=("Item", "Value"), show="headings")
        self.cpu_info_tree.heading("Item", text="Mục")
        self.cpu_info_tree.heading("Value", text="Giá trị")
        self.cpu_info_tree.pack(expand=True, fill="both")

        vsb = ttk.Scrollbar(parent_frame, orient="vertical", command=self.cpu_info_tree.yview)
        vsb.pack(side='right', fill='y')
        self.cpu_info_tree.configure(yscrollcommand=vsb.set)

    def create_memory_info_widgets(self, parent_frame):
        self.memory_info_tree = ttk.Treeview(parent_frame, columns=("Item", "Value"), show="headings")
        self.memory_info_tree.heading("Item", text="Mục")
        self.memory_info_tree.heading("Value", text="Giá trị")
        self.memory_info_tree.pack(expand=True, fill="both")

        vsb = ttk.Scrollbar(parent_frame, orient="vertical", command=self.memory_info_tree.yview)
        vsb.pack(side='right', fill='y')
        self.memory_info_tree.configure(yscrollcommand=vsb.set)

    def create_disk_info_widgets(self, parent_frame):
        self.disk_info_tree = ttk.Treeview(parent_frame, columns=("Device", "Mountpoint", "File System", "Size", "Used", "Free", "Percent"), show="headings")
        self.disk_info_tree.heading("Device", text="Thiết bị")
        self.disk_info_tree.heading("Mountpoint", text="Điểm gắn kết")
        self.disk_info_tree.heading("File System", text="Hệ thống tệp")
        self.disk_info_tree.heading("Size", text="Tổng dung lượng")
        self.disk_info_tree.heading("Used", text="Đã dùng")
        self.disk_info_tree.heading("Free", text="Còn trống")
        self.disk_info_tree.heading("Percent", text="Phần trăm")
        self.disk_info_tree.pack(expand=True, fill="both")

        vsb = ttk.Scrollbar(parent_frame, orient="vertical", command=self.disk_info_tree.yview)
        vsb.pack(side='right', fill='y')
        self.disk_info_tree.configure(yscrollcommand=vsb.set)

    def create_network_info_widgets(self, parent_frame):
        self.network_info_tree = ttk.Treeview(parent_frame, columns=("Interface", "IP Address", "Netmask", "MAC Address"), show="headings")
        self.network_info_tree.heading("Interface", text="Giao diện")
        self.network_info_tree.heading("IP Address", text="Địa chỉ IP")
        self.network_info_tree.heading("Netmask", text="Netmask")
        self.network_info_tree.heading("MAC Address", text="Địa chỉ MAC")
        self.network_info_tree.pack(expand=True, fill="both")

        vsb = ttk.Scrollbar(parent_frame, orient="vertical", command=self.network_info_tree.yview)
        vsb.pack(side='right', fill='y')
        self.network_info_tree.configure(yscrollcommand=vsb.set)

    def create_gpu_info_widgets(self, parent_frame):
        self.gpu_info_tree = ttk.Treeview(parent_frame, columns=("Property", "Value"), show="headings")
        self.gpu_info_tree.heading("Property", text="Thuộc tính")
        self.gpu_info_tree.heading("Value", text="Giá trị")
        self.gpu_info_tree.pack(expand=True, fill="both")

        vsb = ttk.Scrollbar(parent_frame, orient="vertical", command=self.gpu_info_tree.yview)
        vsb.pack(side='right', fill='y')
        self.gpu_info_tree.configure(yscrollcommand=vsb.set)

    def load_hardware_info(self):
        self.clear_trees()
        self.get_system_info()
        self.get_cpu_info()
        self.get_memory_info()
        self.get_disk_info()
        self.get_network_info()
        if platform.system() == "Windows": # Chỉ gọi nếu chạy trên Windows
            self.get_gpu_info()

    def clear_trees(self):
        for i in self.system_info_tree.get_children():
            self.system_info_tree.delete(i)
        for i in self.cpu_info_tree.get_children():
            self.cpu_info_tree.delete(i)
        for i in self.memory_info_tree.get_children():
            self.memory_info_tree.delete(i)
        for i in self.disk_info_tree.get_children():
            self.disk_info_tree.delete(i)
        for i in self.network_info_tree.get_children():
            self.network_info_tree.delete(i)
        if platform.system() == "Windows": # Chỉ xóa nếu tab GPU tồn tại
            for i in self.gpu_info_tree.get_children():
                self.gpu_info_tree.delete(i)

    def get_system_info(self):
        info = {
            "Hệ điều hành": platform.system(),
            "Phiên bản HĐH": platform.version(),
            "Kiến trúc": platform.machine(),
            "Tên máy tính": platform.node(),
            "Nền tảng": platform.platform(),
            "Phiên bản Python": platform.python_version(),
        }
        for item, value in info.items():
            self.system_info_tree.insert("", "end", values=(item, value))

    def get_cpu_info(self):
        cpu_data = cpuinfo.get_cpu_info()
        self.cpu_info_tree.insert("", "end", values=("Tên CPU", cpu_data.get('brand_original', 'N/A')))
        self.cpu_info_tree.insert("", "end", values=("Kiến trúc", cpu_data.get('arch', 'N/A')))
        self.cpu_info_tree.insert("", "end", values=("Số lõi vật lý", psutil.cpu_count(logical=False)))
        self.cpu_info_tree.insert("", "end", values=("Số luồng (lõi ảo)", psutil.cpu_count(logical=True)))
        
        # Lấy tần số CPU, xử lý trường hợp không có dữ liệu
        cpu_freq = psutil.cpu_freq()
        if cpu_freq:
            self.cpu_info_tree.insert("", "end", values=("Tần số tối đa", f"{cpu_freq.max:.2f} Mhz"))
            self.cpu_info_tree.insert("", "end", values=("Tần số hiện tại", f"{cpu_freq.current:.2f} Mhz"))
        else:
            self.cpu_info_tree.insert("", "end", values=("Tần số tối đa", "N/A"))
            self.cpu_info_tree.insert("", "end", values=("Tần số hiện tại", "N/A"))

        self.cpu_info_tree.insert("", "end", values=("Sử dụng CPU", f"{psutil.cpu_percent(interval=1)}%"))

    def get_memory_info(self):
        svmem = psutil.virtual_memory()
        self.memory_info_tree.insert("", "end", values=("Tổng RAM", self.bytes_to_gb(svmem.total)))
        self.memory_info_tree.insert("", "end", values=("RAM đã dùng", self.bytes_to_gb(svmem.used)))
        self.memory_info_tree.insert("", "end", values=("RAM còn trống", self.bytes_to_gb(svmem.available)))
        self.memory_info_tree.insert("", "end", values=("Phần trăm sử dụng", f"{svmem.percent}%"))

        swap = psutil.swap_memory()
        self.memory_info_tree.insert("", "end", values=("Tổng bộ nhớ ảo (Swap)", self.bytes_to_gb(swap.total)))
        self.memory_info_tree.insert("", "end", values=("Đã dùng bộ nhớ ảo (Swap)", self.bytes_to_gb(swap.used)))
        self.memory_info_tree.insert("", "end", values=("Còn trống bộ nhớ ảo (Swap)", self.bytes_to_gb(swap.free)))
        self.memory_info_tree.insert("", "end", values=("Phần trăm sử dụng bộ nhớ ảo (Swap)", f"{swap.percent}%"))


    def get_disk_info(self):
        partitions = psutil.disk_partitions()
        for p in partitions:
            try:
                usage = psutil.disk_usage(p.mountpoint)
                self.disk_info_tree.insert("", "end", values=(
                    p.device,
                    p.mountpoint,
                    p.fstype,
                    self.bytes_to_gb(usage.total),
                    self.bytes_to_gb(usage.used),
                    self.bytes_to_gb(usage.free),
                    f"{usage.percent}%"
                ))
            except PermissionError:
                # Some partitions might not be accessible
                self.disk_info_tree.insert("", "end", values=(p.device, p.mountpoint, p.fstype, "N/A", "N/A", "N/A", "N/A"))
            except Exception as e:
                # Catch other potential errors
                self.disk_info_tree.insert("", "end", values=(p.device, p.mountpoint, p.fstype, "Lỗi", "Lỗi", "Lỗi", f"({e})"))


    def get_network_info(self):
        net_if_addrs = psutil.net_if_addrs()
        for interface_name, addresses in net_if_addrs.items():
            ip_address = "N/A"
            netmask = "N/A"
            mac_address = "N/A"
            for address in addresses:
                # Sử dụng socket.AF_INET thay vì psutil.AF_INET để tương thích tốt hơn
                if address.family == socket.AF_INET:  # IPv4
                    ip_address = address.address
                    netmask = address.netmask
                # psutil.AF_LINK hoạt động tốt hơn với psutil 
                elif address.family == psutil.AF_LINK:  # MAC Address
                    mac_address = address.address
            self.network_info_tree.insert("", "end", values=(interface_name, ip_address, netmask, mac_address))

    def get_gpu_info(self):
        if platform.system() == "Windows":
            try:
                c = wmi.WMI()
                found_gpu = False
                for gpu in c.Win32_VideoController():
                    found_gpu = True
                    self.gpu_info_tree.insert("", "end", values=("Tên card", gpu.Name))
                    
                    # Kiểm tra và chuyển đổi AdapterRAM nếu có
                    if hasattr(gpu, 'AdapterRAM') and gpu.AdapterRAM is not None:
                        self.gpu_info_tree.insert("", "end", values=("Bộ nhớ RAM", self.bytes_to_gb(gpu.AdapterRAM)))
                    else:
                        self.gpu_info_tree.insert("", "end", values=("Bộ nhớ RAM", "N/A"))

                    self.gpu_info_tree.insert("", "end", values=("Trạng thái", gpu.Status if hasattr(gpu, 'Status') else "N/A"))
                    
                    # Kiểm tra và hiển thị độ phân giải
                    if hasattr(gpu, 'CurrentHorizontalResolution') and hasattr(gpu, 'CurrentVerticalResolution') and \
                       gpu.CurrentHorizontalResolution is not None and gpu.CurrentVerticalResolution is not None:
                        self.gpu_info_tree.insert("", "end", values=("Độ phân giải", f"{gpu.CurrentHorizontalResolution}x{gpu.CurrentVerticalResolution}"))
                    else:
                        self.gpu_info_tree.insert("", "end", values=("Độ phân giải", "N/A"))

                    self.gpu_info_tree.insert("", "end", values=("Bộ xử lý video", gpu.VideoProcessor if hasattr(gpu, 'VideoProcessor') else "N/A"))
                    # Thêm một dòng trống để phân tách các GPU nếu có nhiều hơn 1
                    self.gpu_info_tree.insert("", "end", values=("", "")) 
                
                if not found_gpu:
                    self.gpu_info_tree.insert("", "end", values=("Không tìm thấy GPU", "Thông tin có thể không có sẵn hoặc cần driver"))

            except Exception as e:
                self.gpu_info_tree.insert("", "end", values=("Lỗi đọc GPU", str(e)))
        else:
            self.gpu_info_tree.insert("", "end", values=("Không hỗ trợ", "Chỉ hỗ trợ Windows"))

    def bytes_to_gb(self, bytes_val):
        if bytes_val is None:
            return "N/A"
        return f"{bytes_val / (1024**3):.2f} GB"

    def save_info_to_db(self, category, treeview):
        conn = sqlite3.connect(self.db_name)
        cursor = conn.cursor()
        for item in treeview.get_children():
            values = treeview.item(item, 'values')
            if len(values) >= 2: # Ensure there are at least "Item" and "Value"
                item_name = values[0]
                item_value = values[1]
                cursor.execute("INSERT INTO hardware_data (category, item, value) VALUES (?, ?, ?)",
                               (category, item_name, item_value))
            else: # Handle cases like disk info with multiple columns
                if category == "Ổ đĩa":
                    device = values[0] if len(values) > 0 else "N/A"
                    details = ", ".join(map(str, values[1:])) if len(values) > 1 else ""
                    cursor.execute("INSERT INTO hardware_data (category, item, value) VALUES (?, ?, ?)",
                                   (category, device, details))
                elif category == "Mạng":
                    interface = values[0] if len(values) > 0 else "N/A"
                    details = ", ".join(map(str, values[1:])) if len(values) > 1 else ""
                    cursor.execute("INSERT INTO hardware_data (category, item, value) VALUES (?, ?, ?)",
                                   (category, interface, details))
                elif category == "GPU":
                    prop = values[0] if len(values) > 0 else "N/A"
                    val = values[1] if len(values) > 1 else "N/A"
                    cursor.execute("INSERT INTO hardware_data (category, item, value) VALUES (?, ?, ?)",
                                   (category, prop, val))

        conn.commit()
        conn.close()

    def save_all_info_to_db(self):
        conn = sqlite3.connect(self.db_name)
        cursor = conn.cursor()
        cursor.execute("DELETE FROM hardware_data") # Clear existing data
        conn.commit()
        conn.close()

        self.save_info_to_db("Hệ thống", self.system_info_tree)
        self.save_info_to_db("CPU", self.cpu_info_tree)
        self.save_info_to_db("RAM", self.memory_info_tree)
        self.save_info_to_db("Ổ đĩa", self.disk_info_tree)
        self.save_info_to_db("Mạng", self.network_info_tree)
        if platform.system() == "Windows":
            self.save_info_to_db("GPU", self.gpu_info_tree)
        messagebox.showinfo("Lưu thông tin", "Thông tin phần cứng đã được lưu vào cơ sở dữ liệu thành công!")

# Cách chạy ứng dụng
if __name__ == "__main__":
    root = tk.Tk()
    app = HardwareInfoApp(root)
    root.mainloop()