Load FW bằng SSH, Thêm tính năng update FW (SSH)
This commit is contained in:
298
main.py
298
main.py
@@ -11,7 +11,8 @@ from PyQt6.QtWidgets import (
|
||||
QVBoxLayout, QHBoxLayout, QFileDialog, QTableWidget,
|
||||
QTableWidgetItem, QLabel, QProgressBar,
|
||||
QMessageBox, QGroupBox, QHeaderView, QLineEdit,
|
||||
QFrame, QCheckBox, QSpinBox, QScrollArea, QSizePolicy
|
||||
QFrame, QCheckBox, QSpinBox, QScrollArea, QSizePolicy,
|
||||
QComboBox
|
||||
)
|
||||
from PyQt6.QtCore import (
|
||||
Qt, QThread, pyqtSignal, QPropertyAnimation, QAbstractAnimation,
|
||||
@@ -23,6 +24,7 @@ import datetime
|
||||
|
||||
from scanner import scan_network
|
||||
from flasher import flash_device
|
||||
from ssh_flasher import flash_device_ssh
|
||||
|
||||
|
||||
class CollapsibleGroupBox(QGroupBox):
|
||||
@@ -475,11 +477,17 @@ class FlashThread(QThread):
|
||||
device_done = pyqtSignal(int, str) # index, result
|
||||
all_done = pyqtSignal()
|
||||
|
||||
def __init__(self, devices, firmware_path, max_workers=10):
|
||||
def __init__(self, devices, firmware_path, max_workers=10,
|
||||
method="api", ssh_user="root", ssh_password="admin123a",
|
||||
set_passwd=False):
|
||||
super().__init__()
|
||||
self.devices = devices
|
||||
self.firmware_path = firmware_path
|
||||
self.max_workers = max_workers
|
||||
self.method = method
|
||||
self.ssh_user = ssh_user
|
||||
self.ssh_password = ssh_password
|
||||
self.set_passwd = set_passwd
|
||||
|
||||
def run(self):
|
||||
def _flash_one(i, dev):
|
||||
@@ -487,10 +495,19 @@ class FlashThread(QThread):
|
||||
def on_status(msg):
|
||||
self.device_status.emit(i, msg)
|
||||
|
||||
result = flash_device(
|
||||
dev["ip"], self.firmware_path,
|
||||
status_cb=on_status
|
||||
)
|
||||
if self.method == "ssh":
|
||||
result = flash_device_ssh(
|
||||
dev["ip"], self.firmware_path,
|
||||
user=self.ssh_user,
|
||||
password=self.ssh_password,
|
||||
set_passwd=self.set_passwd,
|
||||
status_cb=on_status
|
||||
)
|
||||
else:
|
||||
result = flash_device(
|
||||
dev["ip"], self.firmware_path,
|
||||
status_cb=on_status
|
||||
)
|
||||
self.device_done.emit(i, result)
|
||||
except Exception as e:
|
||||
self.device_done.emit(i, f"FAIL: {e}")
|
||||
@@ -513,9 +530,13 @@ class App(QWidget):
|
||||
super().__init__()
|
||||
self.setWindowTitle("MiraV3 Firmware Loader")
|
||||
self.setWindowIcon(QIcon(resource_path("icon.ico")))
|
||||
# Data state
|
||||
self.local_ip = ""
|
||||
self.gateway_ip = ""
|
||||
self.firmware = None
|
||||
self.all_devices = [] # all scan results (unfiltered)
|
||||
self.devices = [] # currently displayed (filtered)
|
||||
self.all_devices = [] # raw list from scanner
|
||||
self.devices = [] # filtered list for table
|
||||
self.flashed_macs = set() # MAC addresses flashed successfully in session
|
||||
self.scan_thread = None
|
||||
|
||||
info = get_machine_info()
|
||||
@@ -532,7 +553,7 @@ class App(QWidget):
|
||||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.addWidget(title)
|
||||
|
||||
copyright_label = QLabel(f"© {datetime.datetime.now().year} Smatec — R&D Software Team. v1.0.0")
|
||||
copyright_label = QLabel(f"© {datetime.datetime.now().year} Smatec — R&D Software Team. v1.1.0")
|
||||
copyright_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
copyright_label.setStyleSheet(
|
||||
"color: #9399b2; font-size: 11px; font-weight: 500; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto;"
|
||||
@@ -593,11 +614,11 @@ class App(QWidget):
|
||||
self.net_input.setPlaceholderText("e.g. 192.168.4.0/24")
|
||||
net_row.addWidget(self.net_input, 1)
|
||||
|
||||
btn_scan = QPushButton("🔍 Scan LAN")
|
||||
btn_scan.setObjectName("scan")
|
||||
btn_scan.clicked.connect(self.scan)
|
||||
btn_scan.setFixedWidth(110)
|
||||
net_row.addWidget(btn_scan)
|
||||
self.btn_scan = QPushButton("🔍 Scan LAN")
|
||||
self.btn_scan.setObjectName("scan")
|
||||
self.btn_scan.clicked.connect(self.scan)
|
||||
self.btn_scan.setFixedWidth(110)
|
||||
net_row.addWidget(self.btn_scan)
|
||||
scan_layout.addLayout(net_row)
|
||||
|
||||
self.scan_progress_bar = QProgressBar()
|
||||
@@ -621,14 +642,20 @@ class App(QWidget):
|
||||
dev_layout.setContentsMargins(4, 4, 4, 4)
|
||||
|
||||
self.table = QTableWidget()
|
||||
self.table.setColumnCount(5)
|
||||
self.table.setHorizontalHeaderLabels(["", "IP", "Name", "MAC", "Status"])
|
||||
self.table.setColumnCount(4)
|
||||
self.table.setHorizontalHeaderLabels(["", "IP", "MAC", "Status"])
|
||||
self.table.setAlternatingRowColors(True)
|
||||
header = self.table.horizontalHeader()
|
||||
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
|
||||
header.resizeSection(0, 40)
|
||||
for col in range(1, 5):
|
||||
header.setSectionResizeMode(col, QHeaderView.ResizeMode.Stretch)
|
||||
|
||||
# IP and MAC can be fixed/stretch, Status to stretch to cover full info
|
||||
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Interactive)
|
||||
header.resizeSection(1, 120)
|
||||
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Interactive)
|
||||
header.resizeSection(2, 140)
|
||||
header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)
|
||||
|
||||
self.table.verticalHeader().setVisible(False)
|
||||
self.table.setSelectionBehavior(
|
||||
QTableWidget.SelectionBehavior.SelectRows
|
||||
@@ -641,6 +668,11 @@ class App(QWidget):
|
||||
filter_row.addWidget(self.device_count_label)
|
||||
filter_row.addStretch()
|
||||
|
||||
btn_history = QPushButton("📋 Flash History")
|
||||
btn_history.clicked.connect(self._show_history)
|
||||
btn_history.setStyleSheet("background-color: #313244; color: #cdd6f4;")
|
||||
filter_row.addWidget(btn_history)
|
||||
|
||||
btn_select_all = QPushButton("☑ Select All")
|
||||
btn_select_all.clicked.connect(self._select_all_devices)
|
||||
filter_row.addWidget(btn_select_all)
|
||||
@@ -659,30 +691,137 @@ class App(QWidget):
|
||||
layout.addWidget(dev_group, stretch=1)
|
||||
|
||||
# ── Flash Controls ──
|
||||
flash_group = QGroupBox("🚀 Flash")
|
||||
flash_group = QGroupBox("🚀 Flash Controls")
|
||||
flash_layout = QVBoxLayout()
|
||||
flash_layout.setSpacing(4)
|
||||
flash_layout.setContentsMargins(4, 4, 4, 4)
|
||||
flash_layout.setSpacing(12)
|
||||
flash_layout.setContentsMargins(10, 15, 10, 12)
|
||||
|
||||
self.progress = QProgressBar()
|
||||
self.progress.setFormat("%v / %m devices (%p%)")
|
||||
self.progress.setMinimumHeight(22)
|
||||
flash_layout.addWidget(self.progress)
|
||||
|
||||
# Mode selector row (Nạp mới vs Update)
|
||||
mode_row = QHBoxLayout()
|
||||
mode_lbl = QLabel("Flash Mode:")
|
||||
mode_lbl.setStyleSheet("font-size: 14px; font-weight: bold;")
|
||||
mode_row.addWidget(mode_lbl)
|
||||
|
||||
self.mode_combo = QComboBox()
|
||||
self.mode_combo.addItem("⚡ New Flash (Raw / Factory Reset)", "new")
|
||||
self.mode_combo.addItem("🔄 Update Firmware (Pre-installed)", "update")
|
||||
self.mode_combo.setMinimumWidth(380)
|
||||
self.mode_combo.setMinimumHeight(35)
|
||||
self.mode_combo.setStyleSheet("""
|
||||
QComboBox {
|
||||
background-color: #2d3352; border: 1px solid #3d4a6b; border-radius: 4px;
|
||||
padding: 4px 10px; color: #ffffff; font-size: 14px; font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.mode_combo.currentIndexChanged.connect(self._on_mode_changed)
|
||||
mode_row.addWidget(self.mode_combo)
|
||||
mode_row.addStretch()
|
||||
flash_layout.addLayout(mode_row)
|
||||
|
||||
# Container cho cấu hình tuỳ chọn "Nạp Mới FW"
|
||||
self.method_container = QWidget()
|
||||
method_layout = QVBoxLayout(self.method_container)
|
||||
method_layout.setContentsMargins(0, 0, 0, 0)
|
||||
method_layout.setSpacing(10)
|
||||
|
||||
# Method selector row
|
||||
method_row = QHBoxLayout()
|
||||
method_lbl = QLabel("Method:")
|
||||
method_lbl.setStyleSheet("font-size: 13px; font-weight: bold;")
|
||||
method_row.addWidget(method_lbl)
|
||||
|
||||
self.method_combo = QComboBox()
|
||||
self.method_combo.addItem("🌐 API (LuCI - Recommended)", "api")
|
||||
self.method_combo.addItem("💻 SSH (paramiko/scp)", "ssh")
|
||||
self.method_combo.setMinimumWidth(220)
|
||||
self.method_combo.setMinimumHeight(32)
|
||||
self.method_combo.setStyleSheet("""
|
||||
QComboBox {
|
||||
background-color: #1e1e2e; border: 1px solid #3d4a6b; border-radius: 4px;
|
||||
padding: 4px 10px; color: #ffffff; font-size: 13px; font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.method_combo.currentIndexChanged.connect(self._on_method_changed)
|
||||
method_row.addWidget(self.method_combo)
|
||||
method_row.addStretch()
|
||||
method_layout.addLayout(method_row)
|
||||
|
||||
# SSH credentials (hidden by default)
|
||||
self.ssh_creds_widget = QWidget()
|
||||
self.ssh_creds_widget.setStyleSheet("background-color: #11121d; border-radius: 6px; border: 1px dashed #3d4a6b;")
|
||||
ssh_creds_layout = QVBoxLayout(self.ssh_creds_widget)
|
||||
ssh_creds_layout.setContentsMargins(15, 12, 15, 12)
|
||||
ssh_creds_layout.setSpacing(10)
|
||||
|
||||
ssh_row1 = QHBoxLayout()
|
||||
ssh_lbl1 = QLabel("SSH User:")
|
||||
ssh_lbl1.setStyleSheet("font-size: 12px; font-weight: bold;")
|
||||
ssh_row1.addWidget(ssh_lbl1)
|
||||
self.ssh_user_input = QLineEdit("root")
|
||||
self.ssh_user_input.setFixedWidth(130)
|
||||
str_qlineedit = "QLineEdit { background-color: #1a1b2e; font-size: 13px; padding: 4px; border: 1px solid #3d4a6b; color: #ffffff; }"
|
||||
self.ssh_user_input.setStyleSheet(str_qlineedit)
|
||||
ssh_row1.addWidget(self.ssh_user_input)
|
||||
|
||||
ssh_row1.addSpacing(25)
|
||||
|
||||
ssh_lbl2 = QLabel("SSH Password:")
|
||||
ssh_lbl2.setStyleSheet("font-size: 12px; font-weight: bold;")
|
||||
ssh_row1.addWidget(ssh_lbl2)
|
||||
self.ssh_pass_input = QLineEdit("admin123a")
|
||||
self.ssh_pass_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
self.ssh_pass_input.setFixedWidth(130)
|
||||
self.ssh_pass_input.setStyleSheet(str_qlineedit)
|
||||
ssh_row1.addWidget(self.ssh_pass_input)
|
||||
ssh_row1.addStretch()
|
||||
ssh_creds_layout.addLayout(ssh_row1)
|
||||
|
||||
self.set_passwd_cb = QCheckBox("Set password before flash (passwd → admin123a)")
|
||||
self.set_passwd_cb.setChecked(True)
|
||||
self.set_passwd_cb.setStyleSheet("color: #94a3b8; font-size: 12px; font-weight: bold;")
|
||||
ssh_creds_layout.addWidget(self.set_passwd_cb)
|
||||
|
||||
self.ssh_creds_widget.setVisible(False)
|
||||
method_layout.addWidget(self.ssh_creds_widget)
|
||||
|
||||
flash_layout.addWidget(self.method_container)
|
||||
|
||||
# Warning UI for Update Mode
|
||||
self.update_warning_lbl = QLabel("⚠️ FW Update Mode: Forced to SSH (root/admin123a). Target IP [192.168.11.102]. Other IPs require confirmation.")
|
||||
self.update_warning_lbl.setStyleSheet("color: #f38ba8; font-size: 13px; font-weight: bold; padding: 8px; border: 1px dotted #f38ba8;")
|
||||
self.update_warning_lbl.setWordWrap(True)
|
||||
self.update_warning_lbl.setVisible(False)
|
||||
flash_layout.addWidget(self.update_warning_lbl)
|
||||
|
||||
# Parallel count row
|
||||
parallel_row = QHBoxLayout()
|
||||
parallel_row.addWidget(QLabel("Concurrent devices:"))
|
||||
parallel_lbl = QLabel("Concurrent devices:")
|
||||
parallel_lbl.setStyleSheet("font-size: 14px; font-weight: bold;")
|
||||
parallel_row.addWidget(parallel_lbl)
|
||||
|
||||
self.parallel_spin = QSpinBox()
|
||||
self.parallel_spin.setRange(0, 100)
|
||||
self.parallel_spin.setValue(10)
|
||||
self.parallel_spin.setSpecialValueText("Unlimited")
|
||||
self.parallel_spin.setSpecialValueText("0 (Unlimited)")
|
||||
self.parallel_spin.setToolTip("0 = unlimited (all devices at once)")
|
||||
self.parallel_spin.setFixedWidth(80)
|
||||
self.parallel_spin.setMinimumWidth(160)
|
||||
self.parallel_spin.setMinimumHeight(35)
|
||||
self.parallel_spin.setStyleSheet("""
|
||||
QSpinBox { font-size: 15px; font-weight: bold; text-align: center; }
|
||||
""")
|
||||
parallel_row.addWidget(self.parallel_spin)
|
||||
parallel_row.addStretch()
|
||||
flash_layout.addLayout(parallel_row)
|
||||
|
||||
btn_flash = QPushButton("⚡ Flash Selected Devices")
|
||||
btn_flash = QPushButton("⚡ FLASH SELECTED DEVICES")
|
||||
btn_flash.setObjectName("flash")
|
||||
btn_flash.setMinimumHeight(45)
|
||||
btn_flash.setStyleSheet("QPushButton#flash { font-size: 16px; font-weight: bold; letter-spacing: 1px; }")
|
||||
btn_flash.clicked.connect(self.flash_all)
|
||||
flash_layout.addWidget(btn_flash)
|
||||
|
||||
@@ -724,27 +863,42 @@ class App(QWidget):
|
||||
self.table.setRowCount(len(self.devices))
|
||||
|
||||
for i, d in enumerate(self.devices):
|
||||
mac_str = d["mac"].upper()
|
||||
|
||||
# Checkbox column
|
||||
cb_item = QTableWidgetItem()
|
||||
cb_item.setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
# If device MAC is already flashed, mark it
|
||||
is_already_flashed = mac_str in self.flashed_macs
|
||||
if is_already_flashed and d["status"] in ["READY", ""]:
|
||||
d["status"] = "Already Flashed"
|
||||
|
||||
# Checkbox logic
|
||||
if is_already_flashed:
|
||||
cb_item.setCheckState(Qt.CheckState.Unchecked)
|
||||
else:
|
||||
cb_item.setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
cb_item.setFlags(cb_item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||
self.table.setItem(i, 0, cb_item)
|
||||
|
||||
ip_item = QTableWidgetItem(d["ip"])
|
||||
name_item = QTableWidgetItem(d.get("name", ""))
|
||||
mac_item = QTableWidgetItem(d["mac"].upper())
|
||||
mac_item = QTableWidgetItem(mac_str)
|
||||
status_item = QTableWidgetItem(d["status"])
|
||||
|
||||
if is_already_flashed:
|
||||
status_item.setForeground(QColor("#a6e3a1"))
|
||||
ip_item.setForeground(QColor("#a6e3a1"))
|
||||
|
||||
# Dim gateway & self if showing all
|
||||
if d["ip"] in {self.local_ip, self.gateway_ip}:
|
||||
for item in [ip_item, name_item, mac_item, status_item]:
|
||||
for item in [ip_item, mac_item, status_item]:
|
||||
item.setForeground(QColor("#4a5568"))
|
||||
cb_item.setCheckState(Qt.CheckState.Unchecked)
|
||||
|
||||
self.table.setItem(i, 1, ip_item)
|
||||
self.table.setItem(i, 2, name_item)
|
||||
self.table.setItem(i, 3, mac_item)
|
||||
self.table.setItem(i, 4, status_item)
|
||||
self.table.setItem(i, 2, mac_item)
|
||||
self.table.setItem(i, 3, status_item)
|
||||
|
||||
total = len(self.devices)
|
||||
hidden = len(self.all_devices) - total
|
||||
@@ -756,9 +910,12 @@ class App(QWidget):
|
||||
def _select_all_devices(self):
|
||||
"""Check all device checkboxes."""
|
||||
for i in range(self.table.rowCount()):
|
||||
item = self.table.item(i, 0)
|
||||
if item:
|
||||
item.setCheckState(Qt.CheckState.Checked)
|
||||
mac = self.table.item(i, 2).text().strip()
|
||||
# Prevent checking if already flashed or is a gateway (optional but good UI logic)
|
||||
if mac not in self.flashed_macs:
|
||||
item = self.table.item(i, 0)
|
||||
if item:
|
||||
item.setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
def _deselect_all_devices(self):
|
||||
"""Uncheck all device checkboxes."""
|
||||
@@ -766,6 +923,15 @@ class App(QWidget):
|
||||
item = self.table.item(i, 0)
|
||||
if item:
|
||||
item.setCheckState(Qt.CheckState.Unchecked)
|
||||
|
||||
def _show_history(self):
|
||||
"""Show list of successfully flashed MACs this session."""
|
||||
if not self.flashed_macs:
|
||||
QMessageBox.information(self, "Flash History", "No successful flashes during this session.")
|
||||
else:
|
||||
macs = "\n".join(sorted(list(self.flashed_macs)))
|
||||
msg = f"Successfully flashed devices in this session ({len(self.flashed_macs)}):\n\n{macs}"
|
||||
QMessageBox.information(self, "Flash History", msg)
|
||||
|
||||
# ── Actions ──
|
||||
|
||||
@@ -801,6 +967,7 @@ class App(QWidget):
|
||||
self.scan_progress_bar.setRange(0, 0)
|
||||
self.scan_progress_bar.setFormat(" Starting...")
|
||||
self.scan_progress_bar.setVisible(True)
|
||||
self.btn_scan.setEnabled(False)
|
||||
|
||||
self.scan_thread = ScanThread(network)
|
||||
self.scan_thread.finished.connect(self._on_scan_done)
|
||||
@@ -811,6 +978,7 @@ class App(QWidget):
|
||||
|
||||
def _on_scan_done(self, results):
|
||||
self.scan_progress_bar.setVisible(False)
|
||||
self.btn_scan.setEnabled(True)
|
||||
self.all_devices = []
|
||||
|
||||
for dev in results:
|
||||
@@ -853,12 +1021,29 @@ class App(QWidget):
|
||||
|
||||
def _on_scan_error(self, error_msg):
|
||||
self.scan_progress_bar.setVisible(False)
|
||||
self.btn_scan.setEnabled(True)
|
||||
self.scan_status.setText("❌ Scan failed")
|
||||
self.scan_status.setStyleSheet("color: #f38ba8;")
|
||||
QMessageBox.critical(
|
||||
self, "Scan Error", f"Failed to scan network:\n{error_msg}"
|
||||
)
|
||||
|
||||
def _on_mode_changed(self, index):
|
||||
"""Show/hide method based on selected mode."""
|
||||
mode = self.mode_combo.currentData()
|
||||
if mode == "update":
|
||||
self.method_container.setVisible(False)
|
||||
self.update_warning_lbl.setVisible(True)
|
||||
else:
|
||||
self.method_container.setVisible(True)
|
||||
self.update_warning_lbl.setVisible(False)
|
||||
self._on_method_changed(self.method_combo.currentIndex())
|
||||
|
||||
def _on_method_changed(self, index):
|
||||
"""Show/hide SSH credentials based on selected method."""
|
||||
method = self.method_combo.currentData()
|
||||
self.ssh_creds_widget.setVisible(method == "ssh")
|
||||
|
||||
def _get_selected_devices(self):
|
||||
"""Return list of (table_row_index, device_dict) for checked devices."""
|
||||
selected = []
|
||||
@@ -905,10 +1090,41 @@ class App(QWidget):
|
||||
self._flash_row_map[idx] = row
|
||||
flash_devices.append(dev)
|
||||
|
||||
# Determine flash method and SSH credentials
|
||||
mode = self.mode_combo.currentData()
|
||||
|
||||
if mode == "update":
|
||||
# Hỏi xác nhận nếu có device nào khác 192.168.11.102
|
||||
strange_ips = [dev["ip"] for _, dev in selected if dev["ip"] != "192.168.11.102"]
|
||||
if strange_ips:
|
||||
msg = (f"⚠️ Detected IP(s) other than [192.168.11.102] in your selection:\n"
|
||||
f"{', '.join(strange_ips[:3])}{'...' if len(strange_ips) > 3 else ''}\n\n"
|
||||
f"Are you SURE you want to update firmware on these devices?")
|
||||
reply = QMessageBox.question(
|
||||
self, "Confirm Update Target", msg,
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No
|
||||
)
|
||||
if reply == QMessageBox.StandardButton.No:
|
||||
return
|
||||
method = "ssh"
|
||||
ssh_user = "root"
|
||||
ssh_password = "admin123a"
|
||||
set_passwd = False
|
||||
else:
|
||||
method = self.method_combo.currentData()
|
||||
ssh_user = self.ssh_user_input.text().strip() or "root"
|
||||
ssh_password = self.ssh_pass_input.text() or "admin123a"
|
||||
set_passwd = self.set_passwd_cb.isChecked() if method == "ssh" else False
|
||||
|
||||
# Run flashing in background thread so UI doesn't freeze
|
||||
self.flash_thread = FlashThread(
|
||||
flash_devices, self.firmware,
|
||||
max_workers=self.parallel_spin.value()
|
||||
max_workers=self.parallel_spin.value(),
|
||||
method=method,
|
||||
ssh_user=ssh_user,
|
||||
ssh_password=ssh_password,
|
||||
set_passwd=set_passwd
|
||||
)
|
||||
self.flash_thread.device_status.connect(self._on_flash_status)
|
||||
self.flash_thread.device_done.connect(self._on_flash_done)
|
||||
@@ -918,7 +1134,7 @@ class App(QWidget):
|
||||
def _on_flash_status(self, index, msg):
|
||||
"""Update status column while flashing."""
|
||||
row = self._flash_row_map.get(index, index)
|
||||
self.table.setItem(row, 4, QTableWidgetItem(f"⏳ {msg}"))
|
||||
self.table.setItem(row, 3, QTableWidgetItem(f"⏳ {msg}"))
|
||||
|
||||
def _on_flash_done(self, index, result):
|
||||
"""One device finished flashing."""
|
||||
@@ -926,6 +1142,12 @@ class App(QWidget):
|
||||
if result.startswith("DONE"):
|
||||
item = QTableWidgetItem(f"✅ {result}")
|
||||
item.setForeground(QColor("#a6e3a1"))
|
||||
|
||||
# Save MAC to history
|
||||
mac_item = self.table.item(row, 2)
|
||||
if mac_item:
|
||||
self.flashed_macs.add(mac_item.text().strip())
|
||||
|
||||
# Auto-uncheck so it won't be flashed again
|
||||
cb = self.table.item(row, 0)
|
||||
if cb:
|
||||
@@ -933,7 +1155,7 @@ class App(QWidget):
|
||||
else:
|
||||
item = QTableWidgetItem(f"❌ {result}")
|
||||
item.setForeground(QColor("#f38ba8"))
|
||||
self.table.setItem(row, 4, item)
|
||||
self.table.setItem(row, 3, item)
|
||||
self.progress.setValue(self.progress.value() + 1)
|
||||
|
||||
def _on_flash_all_done(self):
|
||||
|
||||
Reference in New Issue
Block a user