Skip to content

add connect and login interface to the napery-tiled GUI#30

Open
Xiaogang Yang (XYangXRay) wants to merge 2 commits into
NSLS2:mainfrom
XYangXRay:dev-xy
Open

add connect and login interface to the napery-tiled GUI#30
Xiaogang Yang (XYangXRay) wants to merge 2 commits into
NSLS2:mainfrom
XYangXRay:dev-xy

Conversation

@XYangXRay

Copy link
Copy Markdown
Collaborator
  1. User enters a URL and clicks Connect
  2. The model creates a Context, probes the server, and checks auth requirements
  3. If cached tokens exist and are valid, the connection proceeds automatically
  4. If auth is required (or available), the login widget appears with server-supported methods
  5. User selects a method and provides credentials → model authenticates → UI updates to logged-in state
  6. URL and username are saved to config.yaml for next session
  7. User can click Logout to revoke the session and clear tokens

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a connection + authentication flow to the napari-tiled browser widget, including a dedicated login UI and persistence of connection details across sessions.

Changes:

  • Introduces QTiledLoginWidget to support API key, password, and device-code authentication flows.
  • Updates QTiledBrowser and TiledSelector to probe server auth requirements, drive login/logout UI, and handle device-code polling.
  • Adds YAML-backed persistent config helpers for saved URL/username.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/napari_tiled_browser/qt/tiled_widget.py Embeds the login widget, wires new auth signals/slots, and adds device-code polling timer.
src/napari_tiled_browser/qt/tiled_login.py New Qt login widget with method selector + stacked auth forms.
src/napari_tiled_browser/models/tiled_selector.py Adds Context-based connection/auth flows and new auth-related Qt signals.
src/napari_tiled_browser/config.py New config load/save helpers using platformdirs + YAML.
src/napari_tiled_browser/config.yaml Bundled default config values for initial startup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -1,20 +1,26 @@
import json
import logging
import time

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

time is imported but never used, which will fail ruff (F401) under this repo’s lint settings. Remove the unused import.

Suggested change
import time

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +250
if not auth_is_required and not providers:
# No auth needed, connect directly
self._finalize_connection()
return

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When no authentication is required/available, the connection succeeds but the URL is never persisted via save_login_info, which doesn’t match the PR description (“URL and username are saved to config.yaml for next session”). Consider saving at least the URL on any successful connection (e.g., in _finalize_connection()), with an empty username if unknown.

Copilot uses AI. Check for mistakes.
self.login_widget.setVisible(False)

# Device code polling timer
self._device_code_timer = QTimer()

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The device-code polling timer is created without a Qt parent (QTimer()), which can make lifecycle management harder and risks it surviving longer than the widget. Prefer QTimer(self) (or QTimer(parent=self)) so it is cleaned up automatically when the widget is destroyed.

Suggested change
self._device_code_timer = QTimer()
self._device_code_timer = QTimer(self)

Copilot uses AI. Check for mistakes.
Comment on lines +352 to +354
identity_id = tokens.get("identity", {}).get("id", username)
save_login_info(self.url, str(identity_id))
self.auth_success.emit(str(identity_id))

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value saved to the username config key here is identity_id (potentially a UUID from the token payload), not the actual username the user entered. Since the UI uses this config value to prefill the username field on next start, consider saving the entered username (and, if needed, store identity/UUID under a separate key).

Suggested change
identity_id = tokens.get("identity", {}).get("id", username)
save_login_info(self.url, str(identity_id))
self.auth_success.emit(str(identity_id))
save_login_info(self.url, username)
self.auth_success.emit(username)

Copilot uses AI. Check for mistakes.
Comment on lines +230 to +272
def set_auth_providers(self, providers):
"""Configure available auth methods based on server capabilities.

Parameters
----------
providers : list
List of AboutAuthenticationProvider from the server.
"""
self.auth_method_selector.blockSignals(True)
self.auth_method_selector.clear()
self.auth_method_selector.addItem("None")

# Always allow API Key
self.auth_method_selector.addItem("API Key")

has_internal = False
has_external = False
for p in providers:
mode = p.mode
if mode in ("internal", "password"):
has_internal = True
elif mode == "external":
has_external = True

if has_internal:
self.auth_method_selector.addItem("Password")
if has_external:
self.auth_method_selector.addItem("Device Code")

# Auto-select if only one provider type
if len(providers) == 1:
mode = providers[0].mode
if mode in ("internal", "password"):
idx = self.auth_method_selector.findText("Password")
self.auth_method_selector.setCurrentIndex(idx)
elif mode == "external":
idx = self.auth_method_selector.findText("Device Code")
self.auth_method_selector.setCurrentIndex(idx)

self.auth_method_selector.blockSignals(False)
self.auth_stack.setCurrentIndex(
self.auth_method_selector.currentIndex()
)

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_auth_providers() rebuilds the auth-method combo box, which can shift indices (e.g., when only external auth is available, “Device Code” becomes index 2). Because currentIndexChanged is wired directly to auth_stack.setCurrentIndex and _on_login_clicked uses hard-coded indices (1/2/3), the UI can show the wrong page and emit the wrong login signal (device-code ends up treated as password). Use a stable mapping (e.g., store the target stack index in itemData and switch pages via a custom slot, and key _on_login_clicked off the selected method text/itemData instead of numeric indices).

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +11
import platformdirs
import yaml

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

platformdirs and yaml (PyYAML) are new runtime dependencies but are not declared in pyproject.toml dependencies. This will raise ModuleNotFoundError in environments that don’t already have them transitively installed. Add explicit dependency entries for these packages (or avoid them if you want zero new deps).

Copilot uses AI. Check for mistakes.
Comment on lines +307 to +310
self._finalize_connection()
self.auth_success.emit("(API key)")
save_login_info(self.url, "(API key)")
except Exception as exception:

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save_login_info() writes to a config field named username, but the values being saved here are placeholders like “(API key)” rather than a username/identity. This causes confusing prefill behavior (the UI later inserts the saved value into the username field) and conflates identity vs auth method. Save an actual username only for password logins, and consider storing auth method/identity in separate config keys (or leave username blank for API key/device-code flows).

Copilot uses AI. Check for mistakes.
Comment on lines +470 to +474
def _poll_device_code(self):
"""Poll for device code authorization completion."""
done = self.model.poll_device_code()
if done:
self._device_code_timer.stop()

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_poll_device_code() triggers model.poll_device_code(), which performs blocking HTTP requests. Running this on a QTimer in the UI thread can freeze the napari UI if the network is slow or the request hangs. Move polling to a background worker/thread (similar to TiledWorker) or use a non-blocking/asynchronous approach, and only emit signals back to the UI thread.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +16
import logging
import webbrowser

from qtpy.QtCore import QTimer, Signal
from qtpy.QtWidgets import (
QComboBox,
QGroupBox,
QHBoxLayout,

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module adds several unused imports (logging, QTimer, QGroupBox), which will fail ruff/flake8 (F401) in this repo’s lint configuration. Remove unused imports (and any now-unused logger) to keep CI green.

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +5
url: "https://tiled.nsls2.bnl.gov"
username: "xyang4"

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file currently hard-codes a real-looking username (and a specific server URL). Shipping a packaged default config with user-identifying data is a privacy/security risk and will prefill other users’ UIs unexpectedly. Replace with blank/default-safe values (or remove the bundled config and keep defaults in code), and ensure no personal data is committed to the repository.

Suggested change
url: "https://tiled.nsls2.bnl.gov"
username: "xyang4"
url: ""
username: ""

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +17
CONFIG_DIR = Path(platformdirs.user_config_dir("napari-tiled"))
CONFIG_FILE = CONFIG_DIR / "config.yaml"
_BUNDLED_CONFIG = Path(__file__).parent / "config.yaml"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make sure to save the configuration to a file in the user home directory, similar to how tiled saves tokens. Maybe something along the lines of $HOME/.config/napari-tiled/config.yaml?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants