From b4745214f2fef5a032819623594104b66e34ae1d Mon Sep 17 00:00:00 2001 From: MinhNN86 Date: Mon, 9 Mar 2026 20:29:48 +0700 Subject: [PATCH] update UI --- core/auto_flash_worker.py | 2 +- main.py | 161 ++++++++++++++++++++------------------ 2 files changed, 86 insertions(+), 77 deletions(-) diff --git a/core/auto_flash_worker.py b/core/auto_flash_worker.py index 2f7e460..100239c 100644 --- a/core/auto_flash_worker.py +++ b/core/auto_flash_worker.py @@ -18,7 +18,7 @@ from core.api_flash import flash_device_api 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_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): diff --git a/main.py b/main.py index 060980d..bf82204 100644 --- a/main.py +++ b/main.py @@ -16,7 +16,7 @@ from PyQt6.QtWidgets import ( ) from PyQt6.QtCore import ( Qt, QThread, pyqtSignal, QPropertyAnimation, QAbstractAnimation, - QParallelAnimationGroup, QRect + QParallelAnimationGroup, QRect, QTimer ) from PyQt6.QtGui import QFont, QColor, QIcon, QAction @@ -83,7 +83,7 @@ class App(QWidget): title_row.addStretch() copyright_label = QLabel(f"© {datetime.datetime.now().year} Smatec — R&D Software Team. v{get_version()}") copyright_label.setStyleSheet( - "color: #9399b2; font-size: 15px; font-weight: 500;" + "color: #9399b2; font-size: 13px; font-weight: 500;" ) title_row.addWidget(copyright_label) layout.addLayout(title_row) @@ -138,7 +138,7 @@ class App(QWidget): self.fw_label = QLabel("No firmware selected") self.fw_label.setStyleSheet("font-size: 12px; color: #94a3b8; border: none;") 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.setStyleSheet(""" QPushButton { @@ -239,7 +239,7 @@ class App(QWidget): filter_row.addWidget(self.device_count_label) filter_row.addStretch() - btn_history = QPushButton("📋 Lịch sử nạp") + btn_history = QPushButton("📋 Flash History") btn_history.setFixedHeight(24) btn_history.clicked.connect(self._show_history) 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) # ── 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.setFixedHeight(32) self.btn_auto.setStyleSheet(""" @@ -532,17 +532,17 @@ class App(QWidget): item.setCheckState(Qt.CheckState.Unchecked) 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: - 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: lines = [] for mac in sorted(self.flashed_macs.keys()): ip, _, result, ts = self.flashed_macs[mac] icon = "✅" if result.startswith("DONE") else "❌" 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) - QMessageBox.information(self, "Lịch sử nạp", msg) + msg = f"Flash History ({len(self.flashed_macs)} device(s)):\n\n" + "\n".join(lines) + QMessageBox.information(self, "Flash History", msg) # ── Actions ── @@ -698,9 +698,9 @@ class App(QWidget): blocked = [dev["ip"] for _, dev in selected if dev["ip"] == "192.168.11.102"] if blocked: QMessageBox.warning( - self, "Không được phép", - "⚠️ Thiết bị 192.168.11.102 chỉ được nạp ở chế độ Update FW.\n" - "Vui lòng bỏ chọn thiết bị này hoặc chuyển sang chế độ Update FW." + self, "Not Allowed", + "⚠️ Device 192.168.11.102 can only be flashed in Update FW mode.\n" + "Please deselect this device or switch to Update FW mode." ) return @@ -802,10 +802,10 @@ class App(QWidget): QMessageBox.information(self, "Flash Complete", "All devices have been processed.") def _open_auto_flash(self): - """Chuyển sang giao diện Tự động hóa nạp FW.""" - # Sync firmware & network từ main sang auto + """Switch to Auto Flash UI.""" + # Sync firmware & network from main to auto 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( "color: #a6e3a1; font-weight: bold; font-size: 12px;" if self.firmware @@ -818,14 +818,14 @@ class App(QWidget): self.auto_container.setVisible(True) def _back_to_main(self): - """Quay lại giao diện chính.""" + """Return to main view.""" self.auto_container.setVisible(False) self.main_container.setVisible(True) # ── Auto Flash UI Builder ── 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_worker = None self._auto_device_rows = {} @@ -841,7 +841,7 @@ class App(QWidget): # ── Back button + Title row ── top_row = QHBoxLayout() top_row.setSpacing(8) - self.btn_back = QPushButton("⬅ Quay lại") + self.btn_back = QPushButton("⬅ Back") self.btn_back.setFixedHeight(32) self.btn_back.setStyleSheet(""" QPushButton { @@ -859,14 +859,14 @@ class App(QWidget): self.btn_back.clicked.connect(self._back_to_main) 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.setAlignment(Qt.AlignmentFlag.AlignCenter) top_row.addWidget(auto_title, 1) auto_layout.addLayout(top_row) # ── Config group ── - config_group = CollapsibleGroupBox("⚙️ Cấu hình nạp") + config_group = CollapsibleGroupBox("⚙️ Flash Configuration") config_layout = QVBoxLayout() config_layout.setSpacing(6) config_layout.setContentsMargins(8, 4, 8, 4) @@ -877,7 +877,7 @@ class App(QWidget): fw_lbl = QLabel("FW:") fw_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") 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( "color: #a6e3a1; font-weight: bold; font-size: 12px;" if self.firmware else "color: #f38ba8; font-size: 12px;" @@ -886,7 +886,7 @@ class App(QWidget): btn_fw = QPushButton("📁") btn_fw.setFixedSize(40, 30) 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) row1.addWidget(btn_fw) @@ -894,7 +894,7 @@ class App(QWidget): sep1.setStyleSheet("color: #3d4a6b; font-size: 14px;") row1.addWidget(sep1) - net_lbl = QLabel("Mạng:") + net_lbl = QLabel("Network:") net_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") row1.addWidget(net_lbl) self.auto_net_input = QLineEdit(get_default_network(self.local_ip)) @@ -910,22 +910,26 @@ class App(QWidget): row2 = QHBoxLayout() row2.setSpacing(12) - cnt_lbl = QLabel("Số lượng:") - cnt_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") + cnt_lbl = QLabel("Target Count:") + cnt_lbl.setStyleSheet("font-weight: bold; font-size: 13px; color: #e2e8f0;") row2.addWidget(cnt_lbl) self.auto_target_spin = QSpinBox() self.auto_target_spin.setRange(1, 500) self.auto_target_spin.setValue(5) - self.auto_target_spin.setFixedWidth(70) - self.auto_target_spin.setFixedHeight(28) - self.auto_target_spin.setStyleSheet("QSpinBox { font-size: 13px; font-weight: bold; }") + self.auto_target_spin.setMinimumWidth(80) + self.auto_target_spin.setFixedHeight(34) + 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) sep2 = QLabel("│") sep2.setStyleSheet("color: #3d4a6b; font-size: 14px;") row2.addWidget(sep2) - meth_lbl = QLabel("Phương thức:") + meth_lbl = QLabel("Method:") meth_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") row2.addWidget(meth_lbl) self.auto_method_combo = QComboBox() @@ -939,17 +943,21 @@ class App(QWidget): sep3.setStyleSheet("color: #3d4a6b; font-size: 14px;") row2.addWidget(sep3) - par_lbl = QLabel("Song song:") - par_lbl.setStyleSheet("font-weight: bold; font-size: 12px;") + par_lbl = QLabel("Concurrent:") + par_lbl.setStyleSheet("font-weight: bold; font-size: 13px; color: #e2e8f0;") row2.addWidget(par_lbl) self.auto_parallel_spin = QSpinBox() self.auto_parallel_spin.setRange(0, 100) self.auto_parallel_spin.setValue(10) self.auto_parallel_spin.setSpecialValueText("∞") - self.auto_parallel_spin.setToolTip("0 = không giới hạn") - self.auto_parallel_spin.setFixedWidth(65) - self.auto_parallel_spin.setFixedHeight(28) - self.auto_parallel_spin.setStyleSheet("QSpinBox { font-size: 13px; font-weight: bold; }") + self.auto_parallel_spin.setToolTip("0 = unlimited") + self.auto_parallel_spin.setMinimumWidth(80) + self.auto_parallel_spin.setFixedHeight(34) + 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.addStretch() config_layout.addLayout(row2) @@ -960,7 +968,7 @@ class App(QWidget): # ── Control Buttons ── btn_row = QHBoxLayout() 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.setFixedHeight(36) self.auto_btn_start.setStyleSheet(""" @@ -979,7 +987,7 @@ class App(QWidget): self.auto_btn_start.clicked.connect(self._auto_on_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.setFixedHeight(36) self.auto_btn_stop.setEnabled(False) @@ -1003,7 +1011,7 @@ class App(QWidget): # ── Status + Progress ── status_row = QHBoxLayout() 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;") status_row.addWidget(self.auto_status_label, 1) @@ -1016,14 +1024,14 @@ class App(QWidget): auto_layout.addLayout(status_row) # ── Device Table ── - dev_group = QGroupBox("📋 Danh sách thiết bị") + dev_group = QGroupBox("📋 Device List") dev_layout = QVBoxLayout() dev_layout.setSpacing(2) dev_layout.setContentsMargins(4, 12, 4, 4) self.auto_result_table = QTableWidget() 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) header = self.auto_result_table.horizontalHeader() 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;") 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.setStyleSheet("background-color: #313244; color: #cdd6f4; font-size: 11px; padding: 2px 8px;") btn_auto_history.clicked.connect(self._show_auto_history) @@ -1082,18 +1090,18 @@ class App(QWidget): def _show_auto_history(self): 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 lines = [] for ip, mac, result, ts in self._auto_history: icon = "✅" if result.startswith("DONE") else "❌" 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) - QMessageBox.information(self, "Lịch sử nạp", msg) + msg = f"Flash History ({len(self._auto_history)} device(s)):\n\n" + "\n".join(lines) + QMessageBox.information(self, "Flash History", msg) def _auto_select_firmware(self): file, _ = QFileDialog.getOpenFileName( - self, "Chọn Firmware", "", + self, "Select Firmware", "", "Firmware Files (*.bin *.hex *.uf2);;All Files (*)" ) if file: @@ -1111,19 +1119,20 @@ class App(QWidget): if len(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)) - sb = self.auto_log_area.verticalScrollBar() - sb.setValue(sb.maximum()) + QTimer.singleShot(0, lambda: self.auto_log_area.verticalScrollBar().setValue( + self.auto_log_area.verticalScrollBar().maximum() + )) def _auto_on_start(self): 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 network_str = self.auto_net_input.text().strip() try: ipaddress.ip_network(network_str, strict=False) 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 target = self.auto_target_spin.value() @@ -1131,13 +1140,13 @@ class App(QWidget): max_workers = self.auto_parallel_spin.value() reply = QMessageBox.question( - self, "Xác nhận", - f"Bắt đầu tự động nạp FW?\n\n" + self, "Confirm", + f"Start auto firmware flash?\n\n" f" Firmware: {os.path.basename(self.auto_firmware)}\n" - f" Mạng: {network_str}\n" - f" Số lượng: {target} thiết bị\n" - f" Phương thức: {method.upper()}\n" - f" Song song: {max_workers if max_workers > 0 else 'Không giới hạn'}\n", + f" Network: {network_str}\n" + f" Target count: {target} device(s)\n" + f" Method: {method.upper()}\n" + f" Concurrent: {max_workers if max_workers > 0 else 'Unlimited'}\n", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.Yes, ) @@ -1163,7 +1172,7 @@ class App(QWidget): self.auto_parallel_spin.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_worker = AutoFlashWorker( @@ -1190,13 +1199,13 @@ class App(QWidget): if self._auto_worker: self._auto_worker.stop() 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): 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: - 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;") 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, 1, QTableWidgetItem(dev["ip"])) 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")) self.auto_result_table.setItem(i, 3, waiting_item) 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): row = self._auto_device_rows.get(ip) @@ -1249,42 +1258,42 @@ class App(QWidget): total = len(self._auto_device_rows) done = self._auto_success_count + self._auto_fail_count 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): self.auto_progress_bar.setVisible(True) self.auto_progress_bar.setMaximum(total) 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): 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;") QMessageBox.information( - self, "Hoàn thành", - f"Tự động nạp FW hoàn thành!\n\n" - f"✅ Thành công: {success}\n" - f"❌ Thất bại: {fail}", + self, "Complete", + f"Auto firmware flash complete!\n\n" + f"✅ Success: {success}\n" + f"❌ Failed: {fail}", ) def _auto_on_stopped(self): 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;") def _auto_on_scan_timeout(self, found, target): 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;") QMessageBox.warning( - self, "Không đủ thiết bị", - f"Đã scan tối đa nhưng chỉ tìm thấy {found}/{target} thiết bị.\n\n" - f"Vui lòng kiểm tra:\n" - f" • Thiết bị đã bật và kết nối mạng chưa\n" - f" • Dải mạng ({self.auto_net_input.text()}) có đúng không\n" - f" • Thử lại sau khi kiểm tra", + self, "Not Enough Devices", + f"Scan reached maximum attempts but only found {found}/{target} device(s).\n\n" + f"Please check:\n" + f" • Devices are powered on and connected to the network\n" + f" • Network range ({self.auto_net_input.text()}) is correct\n" + f" • Try again after verifying", ) def _auto_reset_controls(self):