feat: settings dialog for update config (gear entry)
A low-key "⚙ 配置" button at the right end of the flow-tab row opens a settings dialog to edit update_source / update_user / update_pass, with a test-connection button that distinguishes "reachable & up to date" from "cannot connect". - settings_dialog.py: pure-UI QDialog; password mask + show; test runs off the UI thread via load_manifest and reports the result - update_service: add load_manifest() (raises on failure, unlike check_for_update) - main_window: gear button, _open_settings persists via save_config and re-runs the update check; _update_found connected once to avoid duplicate banners - tests: +2 for load_manifest Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
"""Settings dialog: configure the online-update source and credentials.
|
||||
|
||||
Pure UI — it never reads or writes the config file. The caller (main_window)
|
||||
injects the current values and persists the result via save_config
|
||||
(docs/05 §4.12, docs/07 §4.4).
|
||||
"""
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFormLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from version import APP_VERSION
|
||||
from services.update_service import is_newer, load_manifest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SettingsDialog(QDialog):
|
||||
"""Edit update_source / update_user / update_pass with a connection test."""
|
||||
|
||||
_test_done = Signal(str) # result message, delivered to the UI thread
|
||||
|
||||
def __init__(self, parent=None, *, update_source="", update_user="",
|
||||
update_pass="", current_version=APP_VERSION):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("设置")
|
||||
self.setMinimumWidth(420)
|
||||
self._current_version = current_version
|
||||
self._setup_ui(update_source, update_user, update_pass)
|
||||
self._test_done.connect(self._on_test_done)
|
||||
|
||||
# ── UI ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def _setup_ui(self, source, user, password):
|
||||
col = QVBoxLayout(self)
|
||||
col.setSpacing(10)
|
||||
|
||||
form = QFormLayout()
|
||||
form.setLabelAlignment(Qt.AlignRight)
|
||||
|
||||
self._source_edit = QLineEdit(source)
|
||||
self._source_edit.setPlaceholderText("http://主机名/ 或 manifest.json 地址")
|
||||
form.addRow("更新地址", self._source_edit)
|
||||
|
||||
self._user_edit = QLineEdit(user)
|
||||
form.addRow("账号", self._user_edit)
|
||||
|
||||
self._pass_edit = QLineEdit(password)
|
||||
self._pass_edit.setEchoMode(QLineEdit.Password)
|
||||
show_btn = QPushButton("显示")
|
||||
show_btn.setCheckable(True)
|
||||
show_btn.setFixedWidth(48)
|
||||
show_btn.toggled.connect(self._toggle_password)
|
||||
pass_row = QHBoxLayout()
|
||||
pass_row.setContentsMargins(0, 0, 0, 0)
|
||||
pass_row.addWidget(self._pass_edit, 1)
|
||||
pass_row.addWidget(show_btn)
|
||||
form.addRow("密码", pass_row)
|
||||
|
||||
col.addLayout(form)
|
||||
|
||||
# version + test row
|
||||
test_row = QHBoxLayout()
|
||||
test_row.addWidget(QLabel("当前版本 v{}".format(self._current_version)))
|
||||
test_row.addStretch()
|
||||
self._test_btn = QPushButton("测试连接")
|
||||
self._test_btn.clicked.connect(self._on_test)
|
||||
test_row.addWidget(self._test_btn)
|
||||
col.addLayout(test_row)
|
||||
|
||||
self._result = QLabel("")
|
||||
self._result.setObjectName("settingsResult")
|
||||
self._result.setWordWrap(True)
|
||||
col.addWidget(self._result)
|
||||
|
||||
hint = QLabel("提示:建议使用只读账号;生产环境请走 HTTPS(凭据为明文存储)。")
|
||||
hint.setObjectName("settingsHint")
|
||||
hint.setWordWrap(True)
|
||||
col.addWidget(hint)
|
||||
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel)
|
||||
buttons.button(QDialogButtonBox.Save).setText("保存")
|
||||
buttons.button(QDialogButtonBox.Cancel).setText("取消")
|
||||
buttons.accepted.connect(self.accept)
|
||||
buttons.rejected.connect(self.reject)
|
||||
col.addWidget(buttons)
|
||||
|
||||
self.setStyleSheet("""
|
||||
#settingsHint { font-size: 11px; color: #999999; }
|
||||
#settingsResult { font-size: 12px; color: #555555; }
|
||||
""")
|
||||
|
||||
def _toggle_password(self, shown):
|
||||
self._pass_edit.setEchoMode(QLineEdit.Normal if shown else QLineEdit.Password)
|
||||
|
||||
# ── public API ───────────────────────────────────────────────────────────
|
||||
|
||||
def values(self):
|
||||
"""Return the edited config values (caller persists them)."""
|
||||
return {
|
||||
"update_source": self._source_edit.text().strip(),
|
||||
"update_user": self._user_edit.text(),
|
||||
"update_pass": self._pass_edit.text(),
|
||||
}
|
||||
|
||||
# ── connection test (off the UI thread) ──────────────────────────────────
|
||||
|
||||
def _on_test(self):
|
||||
source = self._source_edit.text().strip()
|
||||
if not source:
|
||||
self._result.setText("请先填写更新地址。")
|
||||
return
|
||||
user, password = self._user_edit.text(), self._pass_edit.text()
|
||||
self._test_btn.setEnabled(False)
|
||||
self._result.setText("正在测试…")
|
||||
|
||||
def worker():
|
||||
try:
|
||||
data = load_manifest(source, user, password)
|
||||
remote = str((data or {}).get("version", "")).strip()
|
||||
if not remote:
|
||||
msg = "连接成功,但清单缺少版本号。"
|
||||
elif is_newer(remote, self._current_version):
|
||||
msg = "发现新版本 v{}(当前 v{})。".format(remote, self._current_version)
|
||||
else:
|
||||
msg = "连接成功,已是最新(远端 v{})。".format(remote)
|
||||
except Exception as exc: # surface any reachability/auth/format error
|
||||
logger.info("Settings test failed: %s", exc)
|
||||
msg = "连接失败:{}".format(exc)
|
||||
self._test_done.emit(msg)
|
||||
|
||||
threading.Thread(target=worker, name="settings-test", daemon=True).start()
|
||||
|
||||
def _on_test_done(self, msg):
|
||||
self._result.setText(msg)
|
||||
self._test_btn.setEnabled(True)
|
||||
Reference in New Issue
Block a user