refactor(ui): xoá 108 dòng MS365 chết trong settings_dialog.py

Sót lại từ lần dời UI Connector sang Monitoring → Tools → Connector. Năm hàm:

    _refresh_ms365_status    13    _show_ms365_device_code   52
    _ms365_sign_in           34    _ms365_sign_out            4
    _close_ms365_code_dialog  5

Chứng minh chết trước khi xoá, không xoá theo cảm tính:

* Dựng đồ thị lời gọi bằng ast: **mọi** lời gọi tới năm hàm này đều xuất phát
  từ bên trong chính năm hàm đó. Không một đường vào nào từ ngoài cụm — cả
  trong file lẫn toàn repo.
* Ba thuộc tính chúng đọc — ms365_status, ms365_signin_btn, ms365_signout_btn
  — **chưa từng được gán ở đâu**. Gọi vào là AttributeError, không phải chạy sai
  mà là sập.
* _ms365_workers chỉ được append bên trong _ms365_sign_in, nên chết theo.

Dọn kèm 6 import chỉ còn dòng import: AgentWorker, icon, EXT_CATEGORIES,
ExtConnectorEditDialog, AppContext, QTreeWidget.

Viết lại docstring đầu file — bản cũ vẫn mô tả file này chứa nhóm Connector
(CAD/CAE/MS365/Other), thứ đã không còn ở đây từ lâu.

settings_dialog.py: 407 -> 303 dòng. Cộng cả R08-T07 thì từ 727 xuống 303.

632 test xanh. check_dialogs, check_no_hscroll, check_design_parity,
check_orphans, check_probes_bite đều qua.

Ghi lại một phát hiện phụ, CHƯA xử lý: i18n.py có 28 khoá settings.ms365_*
mồ côi — 20 khoá đã không ai dùng từ trước lần dời connector, 8 khoá vừa mồ
côi theo commit này. Chỉ 2 khoá còn sống (ms365_local_connected,
ms365_local_none, dùng ở ui/connectors_panel.py). Xoá khoá dịch là đụng vào
dữ liệu ba ngôn ngữ ở file khác nên để anh Nam quyết riêng.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nam Pham Dinh Thanh
2026-08-25 21:15:15 +09:00
co-authored by Claude Opus 5
parent c77ce36191
commit 4c3b097977
+15 -119
View File
@@ -1,6 +1,16 @@
"""Settings dialog: AI provider, Sandbox, the unified Connectors (MCP) group
(CAD / CAE / MS365 / Other — MCP servers + REST connectors in one place),
and the merged "Parameter" group (Cowork / Attachments / GraphRAG caps)."""
"""Hộp thoại Cài đặt — khung lắp ráp.
Năm mục, mỗi mục một trang: Chung, AI Provider, Bảo mật sandbox, Tham số,
Auto Model Routing. Bốn mục đầu... đúng hơn: bốn trong năm mục đã bóc sang
``presentation/settings/`` (R08-T07); file này còn giữ mục Bảo mật sandbox,
phần lắp ráp danh sách mục bên trái, và ``_save`` gọi ``apply_to`` của từng
widget con.
Không còn phần Connector nào ở đây: nó đã dời sang Monitoring → Tools →
Connector từ trước. Ngày 25/08 dọn nốt 108 dòng MS365 chết còn sót lại của
lần dời đó — năm hàm gọi lẫn nhau, không đường vào, và đọc ba thuộc tính
chưa từng được gán nên gọi vào là AttributeError.
"""
from __future__ import annotations
from PySide6.QtCore import Qt
@@ -8,17 +18,13 @@ from PySide6.QtGui import QGuiApplication
from PySide6.QtWidgets import (
QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout,
QGroupBox, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
QMessageBox, QPushButton, QScrollArea, QSpinBox, QTreeWidget,
QMessageBox, QPushButton, QScrollArea, QSpinBox,
QTreeWidgetItem, QVBoxLayout, QWidget,
)
from ..core.ext_connectors import CATEGORIES as EXT_CATEGORIES
from ..core.worker import AgentWorker
from ..i18n import tr
from ..state import AppContext
from .icons import icon, IconLabel
from .icons import IconLabel
from .widgets import ToggleSwitch
from .ext_connector_dialog import ExtConnectorEditDialog
from ..presentation.settings.general_settings_widget import GeneralSettingsWidget
@@ -133,9 +139,7 @@ class SettingsDialog(QDialog):
root.addWidget(self.sandbox_group)
# Connectors (MCP / REST API) are managed entirely in Monitoring → Tools
# → Connector now — no connector UI in Settings. (_ms365_workers is kept
# for the dead-but-retained MS365 OAuth sign-in handlers below.)
self._ms365_workers = []
# --- Parameter ---
# Đã bóc sang presentation/settings/parameter_settings_widget.py (R08-T07).
@@ -249,118 +253,10 @@ class SettingsDialog(QDialog):
# ---- MS365 zero-config sign-in ("connect like Claude") ---------------
def _refresh_ms365_status(self) -> None:
from ..core.ms365_auth import current_identity
who = current_identity(self.ctx.config)
if who:
self.ms365_status.setText(tr("settings.ms365_signed_in", who=who))
self.ms365_signin_btn.setEnabled(False)
self.ms365_signout_btn.setEnabled(True)
else:
self.ms365_status.setText(tr("settings.ms365_signed_out"))
self.ms365_signin_btn.setEnabled(True)
self.ms365_signout_btn.setEnabled(False)
self.ms365_signin_btn.setText(tr("settings.ms365_signin_btn"))
self.ms365_signout_btn.setText(tr("settings.ms365_signout_btn"))
def _ms365_sign_in(self) -> None:
from ..core.ms365_auth import current_identity, sign_in
self.ms365_signin_btn.setEnabled(False)
self.ms365_status.setText(tr("settings.ms365_signing_in"))
cfg = self.ctx.config
def job(worker):
# on_code fires (worker thread) with the MSAL device-flow dict —
# marshal it to the UI thread via the worker's event signal.
return sign_in(lambda flow: worker.event.emit({"device_flow": flow}), cfg)
def on_event(ev: dict) -> None:
if "device_flow" in ev:
self._show_ms365_device_code(ev["device_flow"])
def done(_result) -> None:
self._close_ms365_code_dialog()
self.ctx.save()
self._refresh_ms365_status()
QMessageBox.information(
self, tr("settings.ms365_signin_btn"),
tr("settings.ms365_signed_in", who=current_identity(cfg)))
def failed(err: str) -> None:
self._close_ms365_code_dialog()
self._refresh_ms365_status()
QMessageBox.warning(self, tr("settings.ms365_signin_btn"), err)
w = AgentWorker(job)
w.event.connect(on_event)
w.finished_ok.connect(done)
w.failed.connect(failed)
self._ms365_workers.append(w)
w.start()
def _close_ms365_code_dialog(self) -> None:
dlg = getattr(self, "_ms365_code_dialog", None)
if dlg is not None:
dlg.close()
self._ms365_code_dialog = None
def _show_ms365_device_code(self, flow: dict) -> None:
"""Auto-open the sign-in page + show the one-time code in a COPYABLE,
non-modal dialog (so the worker keeps polling and can auto-close it on
success). The code is also copied to the clipboard immediately."""
import webbrowser
code = flow.get("user_code", "")
url = flow.get("verification_uri", "https://microsoft.com/devicelogin")
# Auto-copy the code so the user can just paste it.
QGuiApplication.clipboard().setText(code)
# Auto-open the browser to the (code-prefilled, if available) sign-in page.
try:
webbrowser.open(flow.get("verification_uri_complete") or url)
except Exception: # noqa: BLE001 — a headless box just shows the link to click
pass
self._close_ms365_code_dialog()
dlg = QDialog(self)
dlg.setWindowTitle(tr("settings.ms365_signin_btn"))
dlg.setMinimumWidth(420)
lay = QVBoxLayout(dlg)
info = QLabel(tr("settings.ms365_code_hint", url=url))
info.setWordWrap(True)
info.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextBrowserInteraction)
info.setOpenExternalLinks(True)
lay.addWidget(info)
code_row = QHBoxLayout()
code_edit = QLineEdit(code)
code_edit.setReadOnly(True)
f = code_edit.font()
f.setPointSize(f.pointSize() + 4)
f.setBold(True)
code_edit.setFont(f)
code_edit.setCursorPosition(0)
copy_btn = QPushButton(tr("settings.ms365_copy_code"))
copy_btn.setIcon(icon("document"))
copy_btn.clicked.connect(lambda: QGuiApplication.clipboard().setText(code))
open_btn = QPushButton(tr("settings.ms365_open_link"))
open_btn.setIcon(icon("link"))
open_btn.clicked.connect(lambda: webbrowser.open(flow.get("verification_uri_complete") or url))
code_row.addWidget(code_edit, 1)
code_row.addWidget(copy_btn)
code_row.addWidget(open_btn)
lay.addLayout(code_row)
buttons = QDialogButtonBox(QDialogButtonBox.Close)
buttons.rejected.connect(dlg.reject)
lay.addWidget(buttons)
self._ms365_code_dialog = dlg
dlg.show() # non-modal — sign-in polling continues; done() closes it
def _ms365_sign_out(self) -> None:
from ..core.ms365_auth import sign_out_default
sign_out_default(self.ctx.config)
self._refresh_ms365_status()