From 8e1fd7a7feb13f6450d1e0c3ce08a6dbb2b29289 Mon Sep 17 00:00:00 2001 From: phact Date: Wed, 8 Oct 2025 16:47:55 -0400 Subject: [PATCH] tui and backend: handle langflow optional password config --- src/config/settings.py | 16 +++- src/tui/main.py | 48 +++++++++++ src/tui/managers/env_manager.py | 16 ++-- src/tui/screens/config.py | 144 +++++++++++++++++++++---------- src/tui/widgets/command_modal.py | 3 + 5 files changed, 172 insertions(+), 55 deletions(-) diff --git a/src/config/settings.py b/src/config/settings.py index 598ccfb2..0672ad68 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -43,6 +43,7 @@ if _legacy_flow_id and not os.getenv("LANGFLOW_CHAT_FLOW_ID"): # Langflow superuser credentials for API key generation +LANGFLOW_AUTO_LOGIN = os.getenv("LANGFLOW_AUTO_LOGIN", "False").lower() in ("true", "1", "yes") LANGFLOW_SUPERUSER = os.getenv("LANGFLOW_SUPERUSER") LANGFLOW_SUPERUSER_PASSWORD = os.getenv("LANGFLOW_SUPERUSER_PASSWORD") # Allow explicit key via environment; generation will be skipped if set @@ -179,7 +180,16 @@ async def generate_langflow_api_key(modify: bool = False): ) LANGFLOW_KEY = None # Clear invalid key - if not LANGFLOW_SUPERUSER or not LANGFLOW_SUPERUSER_PASSWORD: + # Use default langflow/langflow credentials if auto-login is enabled and credentials not set + username = LANGFLOW_SUPERUSER + password = LANGFLOW_SUPERUSER_PASSWORD + + if LANGFLOW_AUTO_LOGIN and (not username or not password): + logger.info("LANGFLOW_AUTO_LOGIN is enabled, using default langflow/langflow credentials") + username = username or "langflow" + password = password or "langflow" + + if not username or not password: logger.warning( "LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD not set, skipping API key generation" ) @@ -197,8 +207,8 @@ async def generate_langflow_api_key(modify: bool = False): f"{LANGFLOW_URL}/api/v1/login", headers={"Content-Type": "application/x-www-form-urlencoded"}, data={ - "username": LANGFLOW_SUPERUSER, - "password": LANGFLOW_SUPERUSER_PASSWORD, + "username": username, + "password": password, }, timeout=10, ) diff --git a/src/tui/main.py b/src/tui/main.py index a63f9e8d..51418786 100644 --- a/src/tui/main.py +++ b/src/tui/main.py @@ -107,6 +107,41 @@ class OpenRAGTUI(App): margin: 0 0 1 1; } + /* Password field label rows */ + #config-form Horizontal { + height: auto; + align: left middle; + margin-bottom: 0; + } + + #config-form Horizontal Label { + width: auto; + margin-right: 1; + } + + /* Password input rows */ + #opensearch-password-row, + #langflow-password-row { + width: 100%; + height: auto; + align: left middle; + } + + #opensearch-password-row Input, + #langflow-password-row Input { + width: 1fr; + } + + /* Password toggle buttons */ + #toggle-opensearch-password, + #toggle-langflow-password { + min-width: 8; + width: 8; + height: 3; + padding: 0 1; + margin-left: 1; + } + /* Docs path actions row */ #services-content { @@ -274,6 +309,19 @@ class OpenRAGTUI(App): color: #fafafa; } + Checkbox { + background: transparent; + color: #fafafa; + border: none; + padding: 0; + margin-left: 2; + } + + Checkbox > Static { + background: transparent; + color: #fafafa; + } + Header { background: #27272a; color: #fafafa; diff --git a/src/tui/managers/env_manager.py b/src/tui/managers/env_manager.py index 9510fb70..f1420e42 100644 --- a/src/tui/managers/env_manager.py +++ b/src/tui/managers/env_manager.py @@ -149,8 +149,17 @@ class EnvManager: if not self.config.langflow_secret_key: self.config.langflow_secret_key = self.generate_langflow_secret_key() + # Configure autologin based on whether password is set if not self.config.langflow_superuser_password: - self.config.langflow_superuser_password = self.generate_secure_password() + # If no password is set, enable autologin mode + self.config.langflow_auto_login = "True" + self.config.langflow_new_user_is_active = "True" + self.config.langflow_enable_superuser_cli = "True" + else: + # If password is set, disable autologin mode + self.config.langflow_auto_login = "False" + self.config.langflow_new_user_is_active = "False" + self.config.langflow_enable_superuser_cli = "False" def validate_config(self, mode: str = "full") -> bool: """ @@ -183,10 +192,7 @@ class EnvManager: # Langflow secret key is auto-generated; no user input required - if not validate_non_empty(self.config.langflow_superuser_password): - self.config.validation_errors["langflow_superuser_password"] = ( - "Langflow superuser password is required" - ) + # Langflow password is now optional - if not provided, autologin mode will be enabled if mode == "full": # Validate OAuth settings if provided diff --git a/src/tui/screens/config.py b/src/tui/screens/config.py index 42694f82..4a21edea 100644 --- a/src/tui/screens/config.py +++ b/src/tui/screens/config.py @@ -13,6 +13,7 @@ from textual.widgets import ( Label, TabbedContent, TabPane, + Checkbox, ) from textual.validation import ValidationResult, Validator from rich.text import Text @@ -147,20 +148,22 @@ class ConfigScreen(Screen): # OpenSearch Admin Password yield Label("OpenSearch Admin Password *") - current_value = getattr(self.env_manager.config, "opensearch_password", "") - input_widget = Input( - placeholder="Auto-generated secure password", - value=current_value, - password=True, - id="input-opensearch_password", - validators=[PasswordValidator()], - ) - yield input_widget - self.inputs["opensearch_password"] = input_widget yield Static( "Min 8 chars with uppercase, lowercase, digit, and special character", classes="helper-text", ) + current_value = getattr(self.env_manager.config, "opensearch_password", "") + with Horizontal(id="opensearch-password-row"): + input_widget = Input( + placeholder="Auto-generated secure password", + value=current_value, + password=True, + id="input-opensearch_password", + validators=[PasswordValidator()], + ) + yield input_widget + self.inputs["opensearch_password"] = input_widget + yield Button("👁", id="toggle-opensearch-password", variant="default") yield Static(" ") # Langflow Admin Username @@ -174,18 +177,22 @@ class ConfigScreen(Screen): yield Static(" ") # Langflow Admin Password - yield Label("Langflow Admin Password *") + with Horizontal(): + yield Label("Langflow Admin Password (optional)") + yield Checkbox("Generate password", id="generate-langflow-password") current_value = getattr( self.env_manager.config, "langflow_superuser_password", "" ) - input_widget = Input( - placeholder="Auto-generated secure password", - value=current_value, - password=True, - id="input-langflow_superuser_password", - ) - yield input_widget - self.inputs["langflow_superuser_password"] = input_widget + with Horizontal(id="langflow-password-row"): + input_widget = Input( + placeholder="Langflow password", + value=current_value, + password=True, + id="input-langflow_superuser_password", + ) + yield input_widget + self.inputs["langflow_superuser_password"] = input_widget + yield Button("👁", id="toggle-langflow-password", variant="default") yield Static(" ") yield Static(" ") @@ -201,15 +208,17 @@ class ConfigScreen(Screen): classes="helper-text", ) current_value = getattr(self.env_manager.config, "openai_api_key", "") - input_widget = Input( - placeholder="sk-...", - value=current_value, - password=True, - validators=[OpenAIKeyValidator()], - id="input-openai_api_key", - ) - yield input_widget - self.inputs["openai_api_key"] = input_widget + with Horizontal(id="openai-key-row"): + input_widget = Input( + placeholder="sk-...", + value=current_value, + password=True, + validators=[OpenAIKeyValidator()], + id="input-openai_api_key", + ) + yield input_widget + self.inputs["openai_api_key"] = input_widget + yield Button("Show", id="toggle-openai-key", variant="default") yield Static(" ") # Add OAuth fields only in full mode @@ -252,14 +261,16 @@ class ConfigScreen(Screen): current_value = getattr( self.env_manager.config, "google_oauth_client_secret", "" ) - input_widget = Input( - placeholder="", - value=current_value, - password=True, - id="input-google_oauth_client_secret", - ) - yield input_widget - self.inputs["google_oauth_client_secret"] = input_widget + with Horizontal(id="google-secret-row"): + input_widget = Input( + placeholder="", + value=current_value, + password=True, + id="input-google_oauth_client_secret", + ) + yield input_widget + self.inputs["google_oauth_client_secret"] = input_widget + yield Button("Show", id="toggle-google-secret", variant="default") yield Static(" ") # Microsoft Graph Client ID @@ -300,14 +311,16 @@ class ConfigScreen(Screen): current_value = getattr( self.env_manager.config, "microsoft_graph_oauth_client_secret", "" ) - input_widget = Input( - placeholder="", - value=current_value, - password=True, - id="input-microsoft_graph_oauth_client_secret", - ) - yield input_widget - self.inputs["microsoft_graph_oauth_client_secret"] = input_widget + with Horizontal(id="microsoft-secret-row"): + input_widget = Input( + placeholder="", + value=current_value, + password=True, + id="input-microsoft_graph_oauth_client_secret", + ) + yield input_widget + self.inputs["microsoft_graph_oauth_client_secret"] = input_widget + yield Button("Show", id="toggle-microsoft-secret", variant="default") yield Static(" ") # AWS Access Key ID @@ -503,6 +516,22 @@ class ConfigScreen(Screen): except Exception: pass + def on_checkbox_changed(self, event: Checkbox.Changed) -> None: + """Handle checkbox changes.""" + if event.checkbox.id == "generate-langflow-password": + langflow_password_input = self.inputs.get("langflow_superuser_password") + if event.value: + # Generate password when checked + password = self.env_manager.generate_secure_password() + if langflow_password_input: + langflow_password_input.value = password + self.notify("Generated Langflow password", severity="information") + else: + # Clear password when unchecked (enable autologin) + if langflow_password_input: + langflow_password_input.value = "" + self.notify("Cleared Langflow password - autologin enabled", severity="information") + def on_button_pressed(self, event: Button.Pressed) -> None: """Handle button presses.""" if event.button.id == "generate-btn": @@ -513,18 +542,39 @@ class ConfigScreen(Screen): self.action_back() elif event.button.id == "pick-docs-btn": self.action_pick_documents_path() + elif event.button.id == "toggle-opensearch-password": + # Toggle OpenSearch password visibility + input_widget = self.inputs.get("opensearch_password") + if input_widget: + input_widget.password = not input_widget.password + event.button.label = "🙈" if not input_widget.password else "👁" + elif event.button.id == "toggle-langflow-password": + # Toggle Langflow password visibility + input_widget = self.inputs.get("langflow_superuser_password") + if input_widget: + input_widget.password = not input_widget.password + event.button.label = "🙈" if not input_widget.password else "👁" def action_generate(self) -> None: """Generate secure passwords for admin accounts.""" - self.env_manager.setup_secure_defaults() + # Only generate OpenSearch password - leave Langflow empty for autologin mode + if not self.env_manager.config.opensearch_password: + self.env_manager.config.opensearch_password = self.env_manager.generate_secure_password() + + # Update secret keys + if not self.env_manager.config.langflow_secret_key: + self.env_manager.config.langflow_secret_key = self.env_manager.generate_langflow_secret_key() + + if not self.env_manager.config.session_secret: + self.env_manager.config.session_secret = self.env_manager.generate_session_secret() # Update input fields with generated values for field_name, input_widget in self.inputs.items(): - if field_name in ["opensearch_password", "langflow_superuser_password"]: + if field_name == "opensearch_password": new_value = getattr(self.env_manager.config, field_name) input_widget.value = new_value - self.notify("Generated secure passwords", severity="information") + self.notify("Generated secure password for OpenSearch", severity="information") def action_save(self) -> None: """Save the configuration.""" diff --git a/src/tui/widgets/command_modal.py b/src/tui/widgets/command_modal.py index 58e1e5af..a5013031 100644 --- a/src/tui/widgets/command_modal.py +++ b/src/tui/widgets/command_modal.py @@ -28,12 +28,14 @@ class CommandOutputModal(ModalScreen): DEFAULT_CSS = """ CommandOutputModal { align: center middle; + overflow: hidden; } #waves-background { width: 100%; height: 100%; layer: background; + overflow: hidden; } #dialog { @@ -42,6 +44,7 @@ class CommandOutputModal(ModalScreen): border: solid #3f3f46; background: #27272a; padding: 0; + overflow: hidden; } #title {