Add startup connection validation to GUI
- Add validate_token() method to RegistryClient - Returns (is_valid, error_message) tuple - Catches 401/UNAUTHORIZED errors and connection issues - Add startup validation in MainWindow - Runs 500ms after window shows (non-blocking) - Clears invalid tokens automatically - Shows status bar message explaining what happened - Document cmdforge config disconnect command This prevents confusing errors when trying to publish with stale or revoked credentials - the GUI now auto-clears bad connections on restart. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
66fd121ee4
commit
85d1212c0d
|
|
@ -31,6 +31,13 @@ All notable changes to CmdForge will be documented in this file.
|
||||||
- Auto-fetch registry version when opening publish dialog
|
- Auto-fetch registry version when opening publish dialog
|
||||||
- Fork detection during publish workflow
|
- Fork detection during publish workflow
|
||||||
- Always refresh tools page after publish dialog closes
|
- Always refresh tools page after publish dialog closes
|
||||||
|
- **Startup connection validation**: GUI validates registry token on startup
|
||||||
|
- Automatically clears invalid/revoked tokens
|
||||||
|
- Shows status bar message when connection is cleared
|
||||||
|
- Prevents confusing errors when trying to publish with stale credentials
|
||||||
|
|
||||||
|
#### CLI Features
|
||||||
|
- `cmdforge config disconnect` - Clear registry token from local configuration
|
||||||
|
|
||||||
#### Admin Features
|
#### Admin Features
|
||||||
- Maintenance section in admin dashboard
|
- Maintenance section in admin dashboard
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from PySide6.QtWidgets import (
|
||||||
QListWidget, QListWidgetItem, QStackedWidget,
|
QListWidget, QListWidgetItem, QStackedWidget,
|
||||||
QStatusBar, QLabel, QSplitter
|
QStatusBar, QLabel, QSplitter
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, QSize, QSettings
|
from PySide6.QtCore import Qt, QSize, QSettings, QTimer
|
||||||
from PySide6.QtGui import QIcon, QFont, QShortcut, QKeySequence
|
from PySide6.QtGui import QIcon, QFont, QShortcut, QKeySequence
|
||||||
|
|
||||||
from .styles import STYLESHEET
|
from .styles import STYLESHEET
|
||||||
|
|
@ -68,6 +68,33 @@ class MainWindow(QMainWindow):
|
||||||
# Restore window geometry
|
# Restore window geometry
|
||||||
self._restore_geometry()
|
self._restore_geometry()
|
||||||
|
|
||||||
|
# Validate registry connection after window shows
|
||||||
|
QTimer.singleShot(500, self._validate_registry_connection)
|
||||||
|
|
||||||
|
def _validate_registry_connection(self):
|
||||||
|
"""Check if registry connection is valid on startup, clear if invalid."""
|
||||||
|
try:
|
||||||
|
from ..registry_client import RegistryClient
|
||||||
|
from ..config import set_registry_token, load_config
|
||||||
|
|
||||||
|
config = load_config()
|
||||||
|
if not config.registry.token:
|
||||||
|
return # No connection configured, nothing to validate
|
||||||
|
|
||||||
|
client = RegistryClient()
|
||||||
|
is_valid, error = client.validate_token()
|
||||||
|
|
||||||
|
if not is_valid:
|
||||||
|
# Token is invalid, clear it
|
||||||
|
set_registry_token(None)
|
||||||
|
self.status_bar.showMessage(
|
||||||
|
f"Registry connection cleared: {error}. Please reconnect.",
|
||||||
|
10000 # Show for 10 seconds
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# Don't crash on validation errors, just log to status
|
||||||
|
self.status_bar.showMessage(f"Could not validate registry connection: {e}", 5000)
|
||||||
|
|
||||||
def _setup_sidebar(self):
|
def _setup_sidebar(self):
|
||||||
"""Set up sidebar navigation items."""
|
"""Set up sidebar navigation items."""
|
||||||
items = [
|
items = [
|
||||||
|
|
|
||||||
|
|
@ -605,6 +605,33 @@ class RegistryClient:
|
||||||
|
|
||||||
return response.json().get("data", {})
|
return response.json().get("data", {})
|
||||||
|
|
||||||
|
def validate_token(self) -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Validate that the current token is valid.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (is_valid, error_message)
|
||||||
|
- (True, "") if token is valid
|
||||||
|
- (False, "reason") if token is invalid or missing
|
||||||
|
"""
|
||||||
|
if not self.token:
|
||||||
|
return False, "No token configured"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self._request("GET", "/me/tools", require_auth=True)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return True, ""
|
||||||
|
elif response.status_code == 401:
|
||||||
|
return False, "Token is invalid or revoked"
|
||||||
|
else:
|
||||||
|
return False, f"Unexpected response: {response.status_code}"
|
||||||
|
except RegistryError as e:
|
||||||
|
if e.http_status == 401 or e.code == "UNAUTHORIZED":
|
||||||
|
return False, "Token is invalid or revoked"
|
||||||
|
return False, str(e.message)
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"Connection error: {e}"
|
||||||
|
|
||||||
def get_my_tools(self) -> List[ToolInfo]:
|
def get_my_tools(self) -> List[ToolInfo]:
|
||||||
"""
|
"""
|
||||||
Get tools published by the authenticated user.
|
Get tools published by the authenticated user.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue