Xây dựng Terminal UI (TUI) hiện đại với Textual: Đưa script Python lên tầm cao mới

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Màn hình SSH cuộn tít mù và nỗi ám ảnh lúc 2 giờ sáng

2 giờ sáng. Log server đổ về như thác lũ trên màn hình SSH đen ngòm. Mình đang phải căng mắt tìm một dòng lỗi giữa hàng chục nghìn bản ghi của con bot crawler đang treo. Lúc này, việc gõ grep hay tail -f liên tục thực sự là một cực hình. Mình chợt nghĩ: “Tại sao không biến cái màn hình đen xì này thành một Dashboard chuyên nghiệp để theo dõi CPU, RAM và tiến độ real-time nhỉ?”

Trước đây, làm giao diện Terminal (TUI) với thư viện curses chẳng khác nào cực hình vì phải quản lý từng tọa độ pixel thủ công. Nhưng từ khi biết đến Textual, mọi thứ đã thay đổi. Thư viện này cho phép dùng CSS để dàn trang và các Widget như Table, Input y hệt như làm Web. Mọi thứ chạy mượt mà ngay trên Terminal của bạn.

Quick Start: 5 phút để có giao diện TUI đầu tiên

Đừng tốn thời gian cho lý thuyết suông. Hãy cài đặt và chạy thử ngay một ứng dụng “Hello World” ở đẳng cấp hoàn toàn khác.

pip install textual

Tạo file app.py và dán đoạn code dưới đây. Mình đã tối giản cấu trúc để bạn dễ hình dung nhất:

from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Label, Button

class MyFirstTUI(App):
    # Phím tắt nhanh
    BINDINGS = [("d", "toggle_dark", "Sáng/Tối"), ("q", "quit", "Thoát")]

    def compose(self) -> ComposeResult:
        """Định nghĩa các thành phần giao diện"""
        yield Header(show_clock=True)
        yield Label("Hệ thống giám sát - Status: Đang chạy...")
        yield Button("Kiểm tra Server", variant="success")
        yield Footer()

    def action_toggle_dark(self) -> None:
        self.dark = not self.dark

if __name__ == "__main__":
    app = MyFirstTUI()
    app.run()

Gõ lệnh python app.py và tận hưởng kết quả. Bạn có một giao diện đầy đủ Header, Footer và nút bấm có thể click bằng chuột. Xịn hơn nhiều so với mấy dòng print() khô khan đúng không?

Tại sao Textual lại là ‘kẻ thay đổi cuộc chơi’?

Khi xử lý batch job hơn 100.000 bản ghi cho khách hàng, mình nhận ra tqdm là chưa đủ. Mình cần thấy tốc độ trung bình, số lỗi và log của 5 bản ghi gần nhất cùng lúc. Textual giải quyết việc này bằng mô hình Reactive cực kỳ thông minh.

Điểm đáng giá nhất là khả năng tách biệt logic và giao diện qua TCSS (Textual CSS). Bạn có thể chỉnh màu sắc, margin hay padding mà không cần đụng vào logic Python. Ví dụ, để Label trông chuyên nghiệp hơn, hãy dùng file style.tcss:

Label {
    width: 100%;
    height: 3;
    content-align: center middle;
    background: $accent;
    color: $text;
    text-style: bold;
    border: solid $secondary;
}

Chỉ cần thêm CSS_PATH = "style.tcss" vào class App. Textual sẽ tự tính toán layout, bạn không còn phải đếm từng ký tự để căn chỉnh màn hình nữa.

Xây dựng Dashboard giám sát thực tế

Thay vì gõ ps aux liên tục, hãy xây dựng một bảng dữ liệu tự động cập nhật. Đây là cách mình quản lý các tiến trình ngầm.

from textual.app import App, ComposeResult
from textual.widgets import DataTable, Header, Footer
from textual.containers import Container
import random
import asyncio

class MonitorApp(App):
    CSS = "DataTable { height: 1fr; border: double $primary; }"

    def compose(self) -> ComposeResult:
        yield Header()
        yield Container(DataTable())
        yield Footer()

    def on_mount(self) -> None:
        table = self.query_one(DataTable)
        table.add_columns("Service", "Status", "Uptime")
        table.add_row("Nginx", "[green]Running[/green]", "12 days")
        table.add_row("PostgreSQL", "[red]Stopped[/red]", "0 mins")
        self.set_interval(2, self.update_data)

    def update_data(self) -> None:
        table = self.query_one(DataTable)
        uptime = f"{random.randint(1, 60)} mins"
        table.update_cell(row_index=0, column_index=2, value=uptime)

Trong ví dụ này, mình dùng set_interval để cập nhật UI định kỳ. Đây là kỹ thuật then chốt khi viết các công cụ giám sát thời gian thực mà không làm treo ứng dụng.

Những ‘hố vôi’ cần tránh khi làm UI Terminal

Làm TUI rất khác với làm Web. Có 3 điểm mình đã phải trả giá bằng nhiều giờ debug:

  • Terminal Emulator: Không phải terminal nào cũng hỗ trợ 24-bit màu. Nếu chạy trên server cũ từ 10 năm trước, giao diện sẽ nát bét. Hãy ưu tiên dùng iTerm2, Windows Terminal hoặc Kitty.
  • Event Loop: Textual chạy trên asyncio. Một lệnh time.sleep(10) sẽ làm toàn bộ giao diện đóng băng ngay lập tức. Hãy luôn dùng các thư viện async như httpx thay cho requests.
  • Độ phân giải: Đừng dùng số lượng ký tự cố định cho layout. Hãy dùng đơn vị fr (fraction) để giao diện tự co giãn khi user thay đổi kích thước cửa sổ terminal.

Mẹo nhỏ để công cụ của bạn trông ‘Pro’ hơn

Để cái tool thoát mác ‘bài tập sinh viên’, bạn nên áp dụng 3 chiêu sau:

  1. Rich API: Tận dụng các tag như [bold red] hay [blink] để nhấn mạnh cảnh báo quan trọng.
  2. Input Validation: Dùng thuộc tính validators trong widget Input. Nó giúp chặn lỗi ngay khi user nhập sai định dạng IP hoặc Port.
  3. RichLog: Thay vì print(), hãy dùng RichLog. Nó cho phép scroll xem lịch sử mà không làm vỡ các widget khác trên màn hình.

Từ ngày chuyển script quản trị sang dạng TUI, việc xử lý sự cố đêm khuya bớt áp lực hơn hẳn. Thay vì gõ lệnh trong vô vọng, mình chỉ cần nhìn biểu đồ và bấm nút restart. Nếu bạn đang có những script Python nhàm chán, hãy thử khoác áo mới cho chúng bằng Textual ngay hôm nay.

Share: