update UI

This commit is contained in:
2026-03-09 20:29:48 +07:00
parent 910307967b
commit b4745214f2
2 changed files with 86 additions and 77 deletions

View File

@@ -18,7 +18,7 @@ from core.api_flash import flash_device_api
from core.ssh_new_flash import flash_device_new_ssh from core.ssh_new_flash import flash_device_new_ssh
MAX_FLASH_RETRIES = 3 # Số lần retry nạp FW khi thất bại MAX_FLASH_RETRIES = 3 # Số lần retry nạp FW khi thất bại
MAX_SCAN_ROUNDS = 15 # Số lần scan tối đa trước khi báo không đủ thiết bị MAX_SCAN_ROUNDS = 10 # Số lần scan tối đa trước khi báo không đủ thiết bị
class AutoFlashWorker(QThread): class AutoFlashWorker(QThread):

161
main.py
View File

@@ -16,7 +16,7 @@ from PyQt6.QtWidgets import (
) )
from PyQt6.QtCore import ( from PyQt6.QtCore import (
Qt, QThread, pyqtSignal, QPropertyAnimation, QAbstractAnimation, Qt, QThread, pyqtSignal, QPropertyAnimation, QAbstractAnimation,
QParallelAnimationGroup, QRect QParallelAnimationGroup, QRect, QTimer
) )
from PyQt6.QtGui import QFont, QColor, QIcon, QAction from PyQt6.QtGui import QFont, QColor, QIcon, QAction
@@ -83,7 +83,7 @@ class App(QWidget):
title_row.addStretch() title_row.addStretch()
copyright_label = QLabel(f"© {datetime.datetime.now().year} Smatec — R&D Software Team. v{get_version()}") copyright_label = QLabel(f"© {datetime.datetime.now().year} Smatec — R&D Software Team. v{get_version()}")
copyright_label.setStyleSheet( copyright_label.setStyleSheet(
"color: #9399b2; font-size: 15px; font-weight: 500;" "color: #9399b2; font-size: 13px; font-weight: 500;"
) )
title_row.addWidget(copyright_label) title_row.addWidget(copyright_label)
layout.addLayout(title_row) layout.addLayout(title_row)
@@ -138,7 +138,7 @@ class App(QWidget):
self.fw_label = QLabel("No firmware selected") self.fw_label = QLabel("No firmware selected")
self.fw_label.setStyleSheet("font-size: 12px; color: #94a3b8; border: none;") self.fw_label.setStyleSheet("font-size: 12px; color: #94a3b8; border: none;")
fw_card_layout.addWidget(self.fw_label, 1) fw_card_layout.addWidget(self.fw_label, 1)
btn_fw = QPushButton("📁 Chọn FW") btn_fw = QPushButton("📁 Browse FW")
btn_fw.setFixedHeight(28) btn_fw.setFixedHeight(28)
btn_fw.setStyleSheet(""" btn_fw.setStyleSheet("""
QPushButton { QPushButton {
@@ -239,7 +239,7 @@ class App(QWidget):
filter_row.addWidget(self.device_count_label) filter_row.addWidget(self.device_count_label)
filter_row.addStretch() filter_row.addStretch()
btn_history = QPushButton("📋 Lịch sử nạp") btn_history = QPushButton("📋 Flash History")
btn_history.setFixedHeight(24) btn_history.setFixedHeight(24)
btn_history.clicked.connect(self._show_history) btn_history.clicked.connect(self._show_history)
btn_history.setStyleSheet("background-color: #313244; color: #cdd6f4; font-size: 11px; padding: 2px 8px;") btn_history.setStyleSheet("background-color: #313244; color: #cdd6f4; font-size: 11px; padding: 2px 8px;")
@@ -408,7 +408,7 @@ class App(QWidget):
layout.addWidget(flash_group) layout.addWidget(flash_group)
# ── Automation Button ── # ── Automation Button ──
self.btn_auto = QPushButton("🤖 Tự động hóa nạp FW") self.btn_auto = QPushButton("🤖 Auto Firmware Flash")
self.btn_auto.setObjectName("auto") self.btn_auto.setObjectName("auto")
self.btn_auto.setFixedHeight(32) self.btn_auto.setFixedHeight(32)
self.btn_auto.setStyleSheet(""" self.btn_auto.setStyleSheet("""
@@ -532,17 +532,17 @@ class App(QWidget):
item.setCheckState(Qt.CheckState.Unchecked) item.setCheckState(Qt.CheckState.Unchecked)
def _show_history(self): def _show_history(self):
"""Hiển thị lịch sử thiết bị đã nạp trong phiên.""" """Show history of flashed devices in this session."""
if not self.flashed_macs: if not self.flashed_macs:
QMessageBox.information(self, "Lịch sử nạp", "Chưa có thiết bị nào được nạp trong phiên này.") QMessageBox.information(self, "Flash History", "No devices have been flashed in this session.")
else: else:
lines = [] lines = []
for mac in sorted(self.flashed_macs.keys()): for mac in sorted(self.flashed_macs.keys()):
ip, _, result, ts = self.flashed_macs[mac] ip, _, result, ts = self.flashed_macs[mac]
icon = "" if result.startswith("DONE") else "" icon = "" if result.startswith("DONE") else ""
lines.append(f"[{ts}] {icon} {ip} ({mac}) — {result}") lines.append(f"[{ts}] {icon} {ip} ({mac}) — {result}")
msg = f"Lịch sử nạp FW ({len(self.flashed_macs)} thiết bị):\n\n" + "\n".join(lines) msg = f"Flash History ({len(self.flashed_macs)} device(s)):\n\n" + "\n".join(lines)
QMessageBox.information(self, "Lịch sử nạp", msg) QMessageBox.information(self, "Flash History", msg)
# ── Actions ── # ── Actions ──
@@ -698,9 +698,9 @@ class App(QWidget):
blocked = [dev["ip"] for _, dev in selected if dev["ip"] == "192.168.11.102"] blocked = [dev["ip"] for _, dev in selected if dev["ip"] == "192.168.11.102"]
if blocked: if blocked:
QMessageBox.warning( QMessageBox.warning(
self, "Không được phép", self, "Not Allowed",
"⚠️ Thiết bị 192.168.11.102 chỉ được nạp ở chế độ Update FW.\n" "⚠️ Device 192.168.11.102 can only be flashed in Update FW mode.\n"
"Vui lòng bỏ chọn thiết bị này hoặc chuyển sang chế độ Update FW." "Please deselect this device or switch to Update FW mode."
) )
return return
@@ -802,10 +802,10 @@ class App(QWidget):
QMessageBox.information(self, "Flash Complete", "All devices have been processed.") QMessageBox.information(self, "Flash Complete", "All devices have been processed.")
def _open_auto_flash(self): def _open_auto_flash(self):
"""Chuyển sang giao diện Tự động hóa nạp FW.""" """Switch to Auto Flash UI."""
# Sync firmware & network từ main sang auto # Sync firmware & network from main to auto
self.auto_fw_label.setText( self.auto_fw_label.setText(
os.path.basename(self.firmware) if self.firmware else "Chưa chọn" os.path.basename(self.firmware) if self.firmware else "Not selected"
) )
self.auto_fw_label.setStyleSheet( self.auto_fw_label.setStyleSheet(
"color: #a6e3a1; font-weight: bold; font-size: 12px;" if self.firmware "color: #a6e3a1; font-weight: bold; font-size: 12px;" if self.firmware
@@ -818,14 +818,14 @@ class App(QWidget):
self.auto_container.setVisible(True) self.auto_container.setVisible(True)
def _back_to_main(self): def _back_to_main(self):
"""Quay lại giao diện chính.""" """Return to main view."""
self.auto_container.setVisible(False) self.auto_container.setVisible(False)
self.main_container.setVisible(True) self.main_container.setVisible(True)
# ── Auto Flash UI Builder ── # ── Auto Flash UI Builder ──
def _build_auto_ui(self): def _build_auto_ui(self):
"""Xây dựng giao diện tự động nạp FW bên trong auto_container.""" """Build the Auto Firmware Flash UI inside auto_container."""
self.auto_firmware = self.firmware self.auto_firmware = self.firmware
self._auto_worker = None self._auto_worker = None
self._auto_device_rows = {} self._auto_device_rows = {}
@@ -841,7 +841,7 @@ class App(QWidget):
# ── Back button + Title row ── # ── Back button + Title row ──
top_row = QHBoxLayout() top_row = QHBoxLayout()
top_row.setSpacing(8) top_row.setSpacing(8)
self.btn_back = QPushButton("Quay lại") self.btn_back = QPushButton("Back")
self.btn_back.setFixedHeight(32) self.btn_back.setFixedHeight(32)
self.btn_back.setStyleSheet(""" self.btn_back.setStyleSheet("""
QPushButton { QPushButton {
@@ -859,14 +859,14 @@ class App(QWidget):
self.btn_back.clicked.connect(self._back_to_main) self.btn_back.clicked.connect(self._back_to_main)
top_row.addWidget(self.btn_back) top_row.addWidget(self.btn_back)
auto_title = QLabel("🤖 Tự động hóa nạp FW") auto_title = QLabel("🤖 Auto Firmware Flash")
auto_title.setStyleSheet("font-size: 16px; font-weight: bold; color: #c4b5fd; letter-spacing: 1px;") auto_title.setStyleSheet("font-size: 16px; font-weight: bold; color: #c4b5fd; letter-spacing: 1px;")
auto_title.setAlignment(Qt.AlignmentFlag.AlignCenter) auto_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
top_row.addWidget(auto_title, 1) top_row.addWidget(auto_title, 1)
auto_layout.addLayout(top_row) auto_layout.addLayout(top_row)
# ── Config group ── # ── Config group ──
config_group = CollapsibleGroupBox("⚙️ Cấu hình nạp") config_group = CollapsibleGroupBox("⚙️ Flash Configuration")
config_layout = QVBoxLayout() config_layout = QVBoxLayout()
config_layout.setSpacing(6) config_layout.setSpacing(6)
config_layout.setContentsMargins(8, 4, 8, 4) config_layout.setContentsMargins(8, 4, 8, 4)
@@ -877,7 +877,7 @@ class App(QWidget):
fw_lbl = QLabel("FW:") fw_lbl = QLabel("FW:")
fw_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") fw_lbl.setStyleSheet("font-weight: bold; font-size: 12px;")
row1.addWidget(fw_lbl) row1.addWidget(fw_lbl)
self.auto_fw_label = QLabel(os.path.basename(self.firmware) if self.firmware else "Chưa chọn") self.auto_fw_label = QLabel(os.path.basename(self.firmware) if self.firmware else "Not selected")
self.auto_fw_label.setStyleSheet( self.auto_fw_label.setStyleSheet(
"color: #a6e3a1; font-weight: bold; font-size: 12px;" if self.firmware "color: #a6e3a1; font-weight: bold; font-size: 12px;" if self.firmware
else "color: #f38ba8; font-size: 12px;" else "color: #f38ba8; font-size: 12px;"
@@ -886,7 +886,7 @@ class App(QWidget):
btn_fw = QPushButton("📁") btn_fw = QPushButton("📁")
btn_fw.setFixedSize(40, 30) btn_fw.setFixedSize(40, 30)
btn_fw.setStyleSheet("font-size: 16px;") btn_fw.setStyleSheet("font-size: 16px;")
btn_fw.setToolTip("Chọn firmware") btn_fw.setToolTip("Select firmware")
btn_fw.clicked.connect(self._auto_select_firmware) btn_fw.clicked.connect(self._auto_select_firmware)
row1.addWidget(btn_fw) row1.addWidget(btn_fw)
@@ -894,7 +894,7 @@ class App(QWidget):
sep1.setStyleSheet("color: #3d4a6b; font-size: 14px;") sep1.setStyleSheet("color: #3d4a6b; font-size: 14px;")
row1.addWidget(sep1) row1.addWidget(sep1)
net_lbl = QLabel("Mạng:") net_lbl = QLabel("Network:")
net_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") net_lbl.setStyleSheet("font-weight: bold; font-size: 12px;")
row1.addWidget(net_lbl) row1.addWidget(net_lbl)
self.auto_net_input = QLineEdit(get_default_network(self.local_ip)) self.auto_net_input = QLineEdit(get_default_network(self.local_ip))
@@ -910,22 +910,26 @@ class App(QWidget):
row2 = QHBoxLayout() row2 = QHBoxLayout()
row2.setSpacing(12) row2.setSpacing(12)
cnt_lbl = QLabel("Số lượng:") cnt_lbl = QLabel("Target Count:")
cnt_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") cnt_lbl.setStyleSheet("font-weight: bold; font-size: 13px; color: #e2e8f0;")
row2.addWidget(cnt_lbl) row2.addWidget(cnt_lbl)
self.auto_target_spin = QSpinBox() self.auto_target_spin = QSpinBox()
self.auto_target_spin.setRange(1, 500) self.auto_target_spin.setRange(1, 500)
self.auto_target_spin.setValue(5) self.auto_target_spin.setValue(5)
self.auto_target_spin.setFixedWidth(70) self.auto_target_spin.setMinimumWidth(80)
self.auto_target_spin.setFixedHeight(28) self.auto_target_spin.setFixedHeight(34)
self.auto_target_spin.setStyleSheet("QSpinBox { font-size: 13px; font-weight: bold; }") self.auto_target_spin.setStyleSheet(
"QSpinBox { font-size: 14px; font-weight: bold; color: #e2e8f0; "
"background-color: #2d3352; border: 1px solid #3d4a6b; border-radius: 6px; padding: 2px 6px; } "
"QSpinBox::up-button { width: 18px; } QSpinBox::down-button { width: 18px; }"
)
row2.addWidget(self.auto_target_spin) row2.addWidget(self.auto_target_spin)
sep2 = QLabel("") sep2 = QLabel("")
sep2.setStyleSheet("color: #3d4a6b; font-size: 14px;") sep2.setStyleSheet("color: #3d4a6b; font-size: 14px;")
row2.addWidget(sep2) row2.addWidget(sep2)
meth_lbl = QLabel("Phương thức:") meth_lbl = QLabel("Method:")
meth_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") meth_lbl.setStyleSheet("font-weight: bold; font-size: 12px;")
row2.addWidget(meth_lbl) row2.addWidget(meth_lbl)
self.auto_method_combo = QComboBox() self.auto_method_combo = QComboBox()
@@ -939,17 +943,21 @@ class App(QWidget):
sep3.setStyleSheet("color: #3d4a6b; font-size: 14px;") sep3.setStyleSheet("color: #3d4a6b; font-size: 14px;")
row2.addWidget(sep3) row2.addWidget(sep3)
par_lbl = QLabel("Song song:") par_lbl = QLabel("Concurrent:")
par_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") par_lbl.setStyleSheet("font-weight: bold; font-size: 13px; color: #e2e8f0;")
row2.addWidget(par_lbl) row2.addWidget(par_lbl)
self.auto_parallel_spin = QSpinBox() self.auto_parallel_spin = QSpinBox()
self.auto_parallel_spin.setRange(0, 100) self.auto_parallel_spin.setRange(0, 100)
self.auto_parallel_spin.setValue(10) self.auto_parallel_spin.setValue(10)
self.auto_parallel_spin.setSpecialValueText("") self.auto_parallel_spin.setSpecialValueText("")
self.auto_parallel_spin.setToolTip("0 = không giới hạn") self.auto_parallel_spin.setToolTip("0 = unlimited")
self.auto_parallel_spin.setFixedWidth(65) self.auto_parallel_spin.setMinimumWidth(80)
self.auto_parallel_spin.setFixedHeight(28) self.auto_parallel_spin.setFixedHeight(34)
self.auto_parallel_spin.setStyleSheet("QSpinBox { font-size: 13px; font-weight: bold; }") self.auto_parallel_spin.setStyleSheet(
"QSpinBox { font-size: 14px; font-weight: bold; color: #e2e8f0; "
"background-color: #2d3352; border: 1px solid #3d4a6b; border-radius: 6px; padding: 2px 6px; } "
"QSpinBox::up-button { width: 18px; } QSpinBox::down-button { width: 18px; }"
)
row2.addWidget(self.auto_parallel_spin) row2.addWidget(self.auto_parallel_spin)
row2.addStretch() row2.addStretch()
config_layout.addLayout(row2) config_layout.addLayout(row2)
@@ -960,7 +968,7 @@ class App(QWidget):
# ── Control Buttons ── # ── Control Buttons ──
btn_row = QHBoxLayout() btn_row = QHBoxLayout()
btn_row.setSpacing(8) btn_row.setSpacing(8)
self.auto_btn_start = QPushButton("XÁC NHẬN & BẮT ĐẦU") self.auto_btn_start = QPushButton("CONFIRM & START")
self.auto_btn_start.setObjectName("start_btn") self.auto_btn_start.setObjectName("start_btn")
self.auto_btn_start.setFixedHeight(36) self.auto_btn_start.setFixedHeight(36)
self.auto_btn_start.setStyleSheet(""" self.auto_btn_start.setStyleSheet("""
@@ -979,7 +987,7 @@ class App(QWidget):
self.auto_btn_start.clicked.connect(self._auto_on_start) self.auto_btn_start.clicked.connect(self._auto_on_start)
btn_row.addWidget(self.auto_btn_start) btn_row.addWidget(self.auto_btn_start)
self.auto_btn_stop = QPushButton("DỪNG") self.auto_btn_stop = QPushButton("STOP")
self.auto_btn_stop.setObjectName("stop_btn") self.auto_btn_stop.setObjectName("stop_btn")
self.auto_btn_stop.setFixedHeight(36) self.auto_btn_stop.setFixedHeight(36)
self.auto_btn_stop.setEnabled(False) self.auto_btn_stop.setEnabled(False)
@@ -1003,7 +1011,7 @@ class App(QWidget):
# ── Status + Progress ── # ── Status + Progress ──
status_row = QHBoxLayout() status_row = QHBoxLayout()
status_row.setSpacing(8) status_row.setSpacing(8)
self.auto_status_label = QLabel("Chờ bắt đầu...") self.auto_status_label = QLabel("Waiting to start...")
self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #94a3b8;") self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #94a3b8;")
status_row.addWidget(self.auto_status_label, 1) status_row.addWidget(self.auto_status_label, 1)
@@ -1016,14 +1024,14 @@ class App(QWidget):
auto_layout.addLayout(status_row) auto_layout.addLayout(status_row)
# ── Device Table ── # ── Device Table ──
dev_group = QGroupBox("📋 Danh sách thiết bị") dev_group = QGroupBox("📋 Device List")
dev_layout = QVBoxLayout() dev_layout = QVBoxLayout()
dev_layout.setSpacing(2) dev_layout.setSpacing(2)
dev_layout.setContentsMargins(4, 12, 4, 4) dev_layout.setContentsMargins(4, 12, 4, 4)
self.auto_result_table = QTableWidget() self.auto_result_table = QTableWidget()
self.auto_result_table.setColumnCount(4) self.auto_result_table.setColumnCount(4)
self.auto_result_table.setHorizontalHeaderLabels(["#", "IP", "MAC", "Kết quả"]) self.auto_result_table.setHorizontalHeaderLabels(["#", "IP", "MAC", "Result"])
self.auto_result_table.setAlternatingRowColors(True) self.auto_result_table.setAlternatingRowColors(True)
header = self.auto_result_table.horizontalHeader() header = self.auto_result_table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
@@ -1044,7 +1052,7 @@ class App(QWidget):
self.auto_summary_label.setStyleSheet("color: #94a3b8; font-size: 11px; padding: 2px;") self.auto_summary_label.setStyleSheet("color: #94a3b8; font-size: 11px; padding: 2px;")
summary_row.addWidget(self.auto_summary_label, 1) summary_row.addWidget(self.auto_summary_label, 1)
btn_auto_history = QPushButton("📋 Lịch sử nạp") btn_auto_history = QPushButton("📋 Flash History")
btn_auto_history.setFixedHeight(24) btn_auto_history.setFixedHeight(24)
btn_auto_history.setStyleSheet("background-color: #313244; color: #cdd6f4; font-size: 11px; padding: 2px 8px;") btn_auto_history.setStyleSheet("background-color: #313244; color: #cdd6f4; font-size: 11px; padding: 2px 8px;")
btn_auto_history.clicked.connect(self._show_auto_history) btn_auto_history.clicked.connect(self._show_auto_history)
@@ -1082,18 +1090,18 @@ class App(QWidget):
def _show_auto_history(self): def _show_auto_history(self):
if not self._auto_history: if not self._auto_history:
QMessageBox.information(self, "Lịch sử nạp", "Chưa có thiết bị nào được nạp trong phiên này.") QMessageBox.information(self, "Flash History", "No devices have been flashed in this session.")
return return
lines = [] lines = []
for ip, mac, result, ts in self._auto_history: for ip, mac, result, ts in self._auto_history:
icon = "" if result.startswith("DONE") else "" icon = "" if result.startswith("DONE") else ""
lines.append(f"[{ts}] {icon} {ip} ({mac}) — {result}") lines.append(f"[{ts}] {icon} {ip} ({mac}) — {result}")
msg = f"Lịch sử nạp FW ({len(self._auto_history)} thiết bị):\n\n" + "\n".join(lines) msg = f"Flash History ({len(self._auto_history)} device(s)):\n\n" + "\n".join(lines)
QMessageBox.information(self, "Lịch sử nạp", msg) QMessageBox.information(self, "Flash History", msg)
def _auto_select_firmware(self): def _auto_select_firmware(self):
file, _ = QFileDialog.getOpenFileName( file, _ = QFileDialog.getOpenFileName(
self, "Chọn Firmware", "", self, "Select Firmware", "",
"Firmware Files (*.bin *.hex *.uf2);;All Files (*)" "Firmware Files (*.bin *.hex *.uf2);;All Files (*)"
) )
if file: if file:
@@ -1111,19 +1119,20 @@ class App(QWidget):
if len(self._auto_log_lines) > 500: if len(self._auto_log_lines) > 500:
self._auto_log_lines = self._auto_log_lines[-500:] self._auto_log_lines = self._auto_log_lines[-500:]
self.auto_log_content.setText("\n".join(self._auto_log_lines)) self.auto_log_content.setText("\n".join(self._auto_log_lines))
sb = self.auto_log_area.verticalScrollBar() QTimer.singleShot(0, lambda: self.auto_log_area.verticalScrollBar().setValue(
sb.setValue(sb.maximum()) self.auto_log_area.verticalScrollBar().maximum()
))
def _auto_on_start(self): def _auto_on_start(self):
if not self.auto_firmware: if not self.auto_firmware:
QMessageBox.warning(self, "Chưa chọn FW", "Vui lòng chọn file firmware trước.") QMessageBox.warning(self, "No Firmware Selected", "Please select a firmware file first.")
return return
network_str = self.auto_net_input.text().strip() network_str = self.auto_net_input.text().strip()
try: try:
ipaddress.ip_network(network_str, strict=False) ipaddress.ip_network(network_str, strict=False)
except ValueError: except ValueError:
QMessageBox.warning(self, "Mạng không hợp lệ", f"'{network_str}' không phải network hợp lệ.\nVí dụ: 192.168.4.0/24") QMessageBox.warning(self, "Invalid Network", f"'{network_str}' is not a valid network.\nExample: 192.168.4.0/24")
return return
target = self.auto_target_spin.value() target = self.auto_target_spin.value()
@@ -1131,13 +1140,13 @@ class App(QWidget):
max_workers = self.auto_parallel_spin.value() max_workers = self.auto_parallel_spin.value()
reply = QMessageBox.question( reply = QMessageBox.question(
self, "Xác nhận", self, "Confirm",
f"Bắt đầu tự động nạp FW?\n\n" f"Start auto firmware flash?\n\n"
f" Firmware: {os.path.basename(self.auto_firmware)}\n" f" Firmware: {os.path.basename(self.auto_firmware)}\n"
f" Mạng: {network_str}\n" f" Network: {network_str}\n"
f" Số lượng: {target} thiết bị\n" f" Target count: {target} device(s)\n"
f" Phương thức: {method.upper()}\n" f" Method: {method.upper()}\n"
f" Song song: {max_workers if max_workers > 0 else 'Không giới hạn'}\n", f" Concurrent: {max_workers if max_workers > 0 else 'Unlimited'}\n",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.Yes,
) )
@@ -1163,7 +1172,7 @@ class App(QWidget):
self.auto_parallel_spin.setEnabled(False) self.auto_parallel_spin.setEnabled(False)
self.btn_back.setEnabled(False) self.btn_back.setEnabled(False)
self.auto_status_label.setText("🔍 Đang scan mạng LAN...") self.auto_status_label.setText("🔍 Scanning LAN...")
self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #f9e2af;") self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #f9e2af;")
self._auto_worker = AutoFlashWorker( self._auto_worker = AutoFlashWorker(
@@ -1190,13 +1199,13 @@ class App(QWidget):
if self._auto_worker: if self._auto_worker:
self._auto_worker.stop() self._auto_worker.stop()
self.auto_btn_stop.setEnabled(False) self.auto_btn_stop.setEnabled(False)
self.auto_status_label.setText("Đang dừng...") self.auto_status_label.setText("Stopping...")
def _auto_on_scan_found(self, count): def _auto_on_scan_found(self, count):
target = self.auto_target_spin.value() target = self.auto_target_spin.value()
self.auto_status_label.setText(f"🔍 Scan: tìm thấy {count}/{target} thiết bị...") self.auto_status_label.setText(f"🔍 Scan: found {count}/{target} device(s)...")
if count >= target: if count >= target:
self.auto_status_label.setText(f" Đủ {target} thiết bị — đang nạp FW...") self.auto_status_label.setText(f"{target} device(s) found — flashing firmware...")
self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #a6e3a1;") self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #a6e3a1;")
def _auto_on_devices_ready(self, devices): def _auto_on_devices_ready(self, devices):
@@ -1209,11 +1218,11 @@ class App(QWidget):
self.auto_result_table.setItem(i, 0, num_item) self.auto_result_table.setItem(i, 0, num_item)
self.auto_result_table.setItem(i, 1, QTableWidgetItem(dev["ip"])) self.auto_result_table.setItem(i, 1, QTableWidgetItem(dev["ip"]))
self.auto_result_table.setItem(i, 2, QTableWidgetItem(dev.get("mac", "N/A").upper())) self.auto_result_table.setItem(i, 2, QTableWidgetItem(dev.get("mac", "N/A").upper()))
waiting_item = QTableWidgetItem("Đang chờ...") waiting_item = QTableWidgetItem("Waiting...")
waiting_item.setForeground(QColor("#94a3b8")) waiting_item.setForeground(QColor("#94a3b8"))
self.auto_result_table.setItem(i, 3, waiting_item) self.auto_result_table.setItem(i, 3, waiting_item)
self._auto_device_rows[dev["ip"]] = i self._auto_device_rows[dev["ip"]] = i
self.auto_summary_label.setText(f"Tổng: {len(devices)} thiết bị") self.auto_summary_label.setText(f"Total: {len(devices)} device(s)")
def _auto_on_device_status(self, ip, msg): def _auto_on_device_status(self, ip, msg):
row = self._auto_device_rows.get(ip) row = self._auto_device_rows.get(ip)
@@ -1249,42 +1258,42 @@ class App(QWidget):
total = len(self._auto_device_rows) total = len(self._auto_device_rows)
done = self._auto_success_count + self._auto_fail_count done = self._auto_success_count + self._auto_fail_count
self.auto_summary_label.setText( self.auto_summary_label.setText(
f"Tổng: {total} | Xong: {done} | ✅ {self._auto_success_count} | ❌ {self._auto_fail_count}" f"Total: {total} | Done: {done} | ✅ {self._auto_success_count} | ❌ {self._auto_fail_count}"
) )
def _auto_on_flash_progress(self, done, total): def _auto_on_flash_progress(self, done, total):
self.auto_progress_bar.setVisible(True) self.auto_progress_bar.setVisible(True)
self.auto_progress_bar.setMaximum(total) self.auto_progress_bar.setMaximum(total)
self.auto_progress_bar.setValue(done) self.auto_progress_bar.setValue(done)
self.auto_status_label.setText(f"Nạp FW: {done}/{total} thiết bị...") self.auto_status_label.setText(f"Flashing: {done}/{total} device(s)...")
def _auto_on_all_done(self, success, fail): def _auto_on_all_done(self, success, fail):
self._auto_reset_controls() self._auto_reset_controls()
self.auto_status_label.setText(f"🏁 Hoàn thành! ✅ {success} | ❌ {fail}") self.auto_status_label.setText(f"🏁 Complete! ✅ {success} | ❌ {fail}")
self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #a6e3a1;") self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #a6e3a1;")
QMessageBox.information( QMessageBox.information(
self, "Hoàn thành", self, "Complete",
f"Tự động nạp FW hoàn thành!\n\n" f"Auto firmware flash complete!\n\n"
f"Thành công: {success}\n" f"Success: {success}\n"
f"Thất bại: {fail}", f"Failed: {fail}",
) )
def _auto_on_stopped(self): def _auto_on_stopped(self):
self._auto_reset_controls() self._auto_reset_controls()
self.auto_status_label.setText("Đã dừng bởi người dùng") self.auto_status_label.setText("Stopped by user")
self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #f38ba8;") self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #f38ba8;")
def _auto_on_scan_timeout(self, found, target): def _auto_on_scan_timeout(self, found, target):
self._auto_reset_controls() self._auto_reset_controls()
self.auto_status_label.setText(f"⚠️ Scan hết lần: chỉ tìm thấy {found}/{target} thiết bị") self.auto_status_label.setText(f"⚠️ Scan timed out: only found {found}/{target} device(s)")
self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #fab387;") self.auto_status_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #fab387;")
QMessageBox.warning( QMessageBox.warning(
self, "Không đủ thiết bị", self, "Not Enough Devices",
f"Đã scan tối đa nhưng chỉ tìm thấy {found}/{target} thiết bị.\n\n" f"Scan reached maximum attempts but only found {found}/{target} device(s).\n\n"
f"Vui lòng kiểm tra:\n" f"Please check:\n"
f"Thiết bị đã bật và kết nối mạng chưa\n" f"Devices are powered on and connected to the network\n"
f"Dải mạng ({self.auto_net_input.text()}) có đúng không\n" f"Network range ({self.auto_net_input.text()}) is correct\n"
f" • Thử lại sau khi kiểm tra", f" • Try again after verifying",
) )
def _auto_reset_controls(self): def _auto_reset_controls(self):