Batch of fixes for defects tracked in "Task Tracking Template.xlsx" (sheet Defect Management), verified against the sheet's Root Cause/Cach xu ly columns before this commit: - DF-002: Co4E node status not reflected after tab switch + missing edit-lock on running/done nodes (node_property_panel.py, co4e_runs.py, co4e_workflow_crud.py, co4e_canvas_widget.py, co4e_flow_tabs.py, canvas_items.py) - DF-003: hide the run.bat console window unless the app exits with an error (run.bat, scripts/console_visibility.ps1 - new) - DF-004: floating Help Assistant icon covering the Send button after a window resize (presentation/shell/main_window.py) - DF-005: "block network" toggle didn't stop ICMP/raw-socket tools like ping (infrastructure/filesystem/command_tools.py, security/command_risk_classifier.py) - DF-006: Monitoring "gay nang khi log lon" - root cause was re-reading the ENTIRE audit log history every 3s tick, not missing pagination; bounded to a 30-day window (presentation/monitoring/monitoring_tab.py) AND added the "So dong/trang" page-size control the ticket also asked for (presentation/monitoring/shared/event_table.py, shared/filter_scaffold.py, tabs/action_logs_tab.py, tabs/mcp_tab.py, tabs/security_events_tab.py, i18n/agents_admin_tab.py) - DF-007: support choosing a OneDrive/SharePoint folder as a project's working directory via Microsoft Graph, downloaded as a local mirror with manual sync (core/projects.py, core/ms365_graph.py, core/cloud_workspace_sync.py - new, ui/ms365_signin_dialog.py - new, ui/cloud_folder_picker_dialog.py - new, i18n/cloud_workspace.py - new, ui/workspace_tab.py) - DF-008: AI-edit instruction box was a fixed-height single-line QLineEdit; replaced with an auto-expanding, Enter-to-send/Shift+Enter-newline input (presentation/folder/ai_file_editor_dialog.py) - DF-011: run_command failed with WinError 267 for a project whose per-turn output directory had never been created (application/conversations/core_runtime_adapter.py) DF-009 (AI-edit Apply/Discard buttons easy to miss) and DF-010 (AI reply language - dev-confirmed not a bug) are intentionally NOT part of this commit: DF-009 has no code fix yet (still "Assigned" in the sheet, only a UX recommendation was recorded), DF-010 was rejected as expected behavior. Tests: tests/test_cloud_workspace_sync.py, tests/test_ms365_cloud_dialogs.py, tests/test_ai_file_editor_input.py, tests/test_monitoring_page_size.py (all new, all passing). Full suite: 896 passed, 13 known-and-documented failures unrelated to this change (an existing core/audit_log.py bug, this checkout not being a git repo before now, and a repo/subprocess folder-naming mismatch affecting ~66 characterization tests) - see the sheet's DF-006 Evidence column for details.
138 lines
5.3 KiB
Python
138 lines
5.3 KiB
Python
"""Microsoft 365 sign-in dialog (DF-007) — thin UI over the working device-code
|
|
flow in ``core/ms365_auth.py``. There was an older MS365 sign-in UI in this
|
|
app; it was removed as dead code (no entry point — see
|
|
``ui/settings_dialog.py`` module docstring) before this feature existed, so
|
|
this is a fresh, small dialog rather than a resurrection of that one.
|
|
|
|
Usage: ``if ensure_signed_in(parent, ctx.config): ...`` — returns ``True``
|
|
immediately (no dialog shown) when already signed in.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import webbrowser
|
|
|
|
from PySide6.QtCore import QThread, Signal
|
|
from PySide6.QtWidgets import (
|
|
QDialog, QHBoxLayout, QLabel, QMessageBox, QPushButton, QVBoxLayout,
|
|
)
|
|
|
|
from ..core import ms365_auth
|
|
from ..i18n import tr
|
|
|
|
|
|
class _SignInWorker(QThread):
|
|
"""Chạy ``sign_in_device_code()`` (blocking, poll tới khi xong/hết hạn) ở
|
|
luồng nền — xem ``core/worker.py::AgentWorker`` cho cùng idiom (bắt hết
|
|
exception, phát signal thay vì để lỗi giết luồng âm thầm)."""
|
|
code_ready = Signal(dict)
|
|
finished_ok = Signal(dict)
|
|
failed = Signal(str)
|
|
|
|
def __init__(self, config, parent=None):
|
|
super().__init__(parent)
|
|
self._config = config
|
|
|
|
def run(self) -> None: # noqa: D401
|
|
try:
|
|
result = ms365_auth.sign_in(lambda flow: self.code_ready.emit(flow), self._config)
|
|
self.finished_ok.emit(result or {})
|
|
except Exception as exc: # noqa: BLE001 - surfaced to the UI, never crashes the thread
|
|
self.failed.emit(str(exc))
|
|
|
|
|
|
class Ms365SignInDialog(QDialog):
|
|
"""Modal: hiện user_code + verification_uri, tự mở trình duyệt, đóng lại
|
|
khi đăng nhập xong (hoặc người dùng bấm Hủy)."""
|
|
|
|
def __init__(self, config, parent=None):
|
|
super().__init__(parent)
|
|
self._config = config
|
|
self._worker: _SignInWorker | None = None
|
|
self.setWindowTitle(tr("ms365_signin.title"))
|
|
self.setModal(True)
|
|
|
|
layout = QVBoxLayout(self)
|
|
self._intro_lbl = QLabel(tr("ms365_signin.intro"))
|
|
self._intro_lbl.setWordWrap(True)
|
|
layout.addWidget(self._intro_lbl)
|
|
|
|
self._code_lbl = QLabel()
|
|
self._code_lbl.setWordWrap(True)
|
|
self._code_lbl.hide()
|
|
layout.addWidget(self._code_lbl)
|
|
|
|
self._open_link_btn = QPushButton(tr("ms365_signin.open_link"))
|
|
self._open_link_btn.hide()
|
|
self._open_link_btn.clicked.connect(self._open_link)
|
|
layout.addWidget(self._open_link_btn)
|
|
|
|
self._error_lbl = QLabel()
|
|
self._error_lbl.setWordWrap(True)
|
|
self._error_lbl.setStyleSheet("color: #c0392b;")
|
|
self._error_lbl.hide()
|
|
layout.addWidget(self._error_lbl)
|
|
|
|
btn_row = QHBoxLayout()
|
|
self._signin_btn = QPushButton(tr("ms365_signin.button"))
|
|
self._signin_btn.setObjectName("primary")
|
|
self._signin_btn.clicked.connect(self._start_sign_in)
|
|
self._cancel_btn = QPushButton(tr("ms365_signin.cancel"))
|
|
self._cancel_btn.clicked.connect(self.reject)
|
|
btn_row.addStretch(1)
|
|
btn_row.addWidget(self._cancel_btn)
|
|
btn_row.addWidget(self._signin_btn)
|
|
layout.addLayout(btn_row)
|
|
|
|
self._verification_uri = ""
|
|
|
|
def _open_link(self) -> None:
|
|
if self._verification_uri:
|
|
webbrowser.open(self._verification_uri)
|
|
|
|
def _start_sign_in(self) -> None:
|
|
self._signin_btn.setEnabled(False)
|
|
self._signin_btn.setText(tr("ms365_signin.signing_in"))
|
|
self._error_lbl.hide()
|
|
self._worker = _SignInWorker(self._config, self)
|
|
self._worker.code_ready.connect(self._on_code_ready)
|
|
self._worker.finished_ok.connect(self._on_finished_ok)
|
|
self._worker.failed.connect(self._on_failed)
|
|
self._worker.start()
|
|
|
|
def _on_code_ready(self, flow: dict) -> None:
|
|
self._verification_uri = flow.get("verification_uri_complete") or flow.get(
|
|
"verification_uri", "")
|
|
self._code_lbl.setText(
|
|
tr("ms365_signin.code_hint", url=flow.get("verification_uri", "")) +
|
|
f"\n\n{flow.get('user_code', '')}")
|
|
self._code_lbl.show()
|
|
self._open_link_btn.show()
|
|
if self._verification_uri:
|
|
webbrowser.open(self._verification_uri)
|
|
|
|
def _on_finished_ok(self, _result: dict) -> None:
|
|
self.accept()
|
|
|
|
def _on_failed(self, err: str) -> None:
|
|
self._signin_btn.setEnabled(True)
|
|
self._signin_btn.setText(tr("ms365_signin.button"))
|
|
self._error_lbl.setText(tr("ms365_signin.failed", err=err))
|
|
self._error_lbl.show()
|
|
|
|
def reject(self) -> None:
|
|
# NOTE: MSAL's acquire_token_by_device_flow() has no cancellation hook,
|
|
# so a worker already polling keeps polling in the background until it
|
|
# times out on its own (a few minutes) — closing this dialog just stops
|
|
# the UI from waiting on it. Its late signals are harmless no-ops
|
|
# against an already-closed (but not destroyed) dialog.
|
|
super().reject()
|
|
|
|
|
|
def ensure_signed_in(parent, config) -> bool:
|
|
"""True nếu đã (hoặc vừa) đăng nhập MS365; False nếu người dùng hủy hoặc
|
|
đăng nhập thất bại và đóng dialog."""
|
|
if ms365_auth.is_signed_in(config):
|
|
return True
|
|
dialog = Ms365SignInDialog(config, parent)
|
|
return dialog.exec() == QDialog.Accepted
|