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:
2026-06-18 11:09:29 +08:00
co-authored by Claude Opus 4.8
parent 295dc803c6
commit 2d2c6c839d
5 changed files with 222 additions and 11 deletions
+11
View File
@@ -15,6 +15,7 @@ from services.update_service import (
UpdateInfo,
check_for_update,
is_newer,
load_manifest,
parse_version,
)
@@ -106,6 +107,16 @@ class TestCheckForUpdate(unittest.TestCase):
self.assertIsInstance(info, UpdateInfo)
self.assertEqual(info.version, "1.1.0")
def test_load_manifest_returns_dict(self):
self._write_manifest({"version": "1.2.0", "notes": "x"})
data = load_manifest(str(self.tmp))
self.assertEqual(data["version"], "1.2.0")
def test_load_manifest_raises_when_missing(self):
# Unlike check_for_update, load_manifest surfaces the error for the test button.
with self.assertRaises(OSError):
load_manifest(str(self.tmp / "nope"))
def test_source_falls_back_to_update_source(self):
self._write_manifest({"version": "2.0.0"}) # no "source" field
info = check_for_update(str(self.tmp), "1.0.0")