tui and backend: handle langflow optional password config

This commit is contained in:
phact 2025-10-08 16:47:55 -04:00
parent 037bc31800
commit 8e1fd7a7fe
5 changed files with 172 additions and 55 deletions

View file

@ -43,6 +43,7 @@ if _legacy_flow_id and not os.getenv("LANGFLOW_CHAT_FLOW_ID"):
# Langflow superuser credentials for API key generation # 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 = os.getenv("LANGFLOW_SUPERUSER")
LANGFLOW_SUPERUSER_PASSWORD = os.getenv("LANGFLOW_SUPERUSER_PASSWORD") LANGFLOW_SUPERUSER_PASSWORD = os.getenv("LANGFLOW_SUPERUSER_PASSWORD")
# Allow explicit key via environment; generation will be skipped if set # 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 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( logger.warning(
"LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD not set, skipping API key generation" "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", f"{LANGFLOW_URL}/api/v1/login",
headers={"Content-Type": "application/x-www-form-urlencoded"}, headers={"Content-Type": "application/x-www-form-urlencoded"},
data={ data={
"username": LANGFLOW_SUPERUSER, "username": username,
"password": LANGFLOW_SUPERUSER_PASSWORD, "password": password,
}, },
timeout=10, timeout=10,
) )

View file

@ -107,6 +107,41 @@ class OpenRAGTUI(App):
margin: 0 0 1 1; 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 */ /* Docs path actions row */
#services-content { #services-content {
@ -274,6 +309,19 @@ class OpenRAGTUI(App):
color: #fafafa; color: #fafafa;
} }
Checkbox {
background: transparent;
color: #fafafa;
border: none;
padding: 0;
margin-left: 2;
}
Checkbox > Static {
background: transparent;
color: #fafafa;
}
Header { Header {
background: #27272a; background: #27272a;
color: #fafafa; color: #fafafa;

View file

@ -149,8 +149,17 @@ class EnvManager:
if not self.config.langflow_secret_key: if not self.config.langflow_secret_key:
self.config.langflow_secret_key = self.generate_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: 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: def validate_config(self, mode: str = "full") -> bool:
""" """
@ -183,10 +192,7 @@ class EnvManager:
# Langflow secret key is auto-generated; no user input required # Langflow secret key is auto-generated; no user input required
if not validate_non_empty(self.config.langflow_superuser_password): # Langflow password is now optional - if not provided, autologin mode will be enabled
self.config.validation_errors["langflow_superuser_password"] = (
"Langflow superuser password is required"
)
if mode == "full": if mode == "full":
# Validate OAuth settings if provided # Validate OAuth settings if provided

View file

@ -13,6 +13,7 @@ from textual.widgets import (
Label, Label,
TabbedContent, TabbedContent,
TabPane, TabPane,
Checkbox,
) )
from textual.validation import ValidationResult, Validator from textual.validation import ValidationResult, Validator
from rich.text import Text from rich.text import Text
@ -147,20 +148,22 @@ class ConfigScreen(Screen):
# OpenSearch Admin Password # OpenSearch Admin Password
yield Label("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( yield Static(
"Min 8 chars with uppercase, lowercase, digit, and special character", "Min 8 chars with uppercase, lowercase, digit, and special character",
classes="helper-text", 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(" ") yield Static(" ")
# Langflow Admin Username # Langflow Admin Username
@ -174,18 +177,22 @@ class ConfigScreen(Screen):
yield Static(" ") yield Static(" ")
# Langflow Admin Password # 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( current_value = getattr(
self.env_manager.config, "langflow_superuser_password", "" self.env_manager.config, "langflow_superuser_password", ""
) )
input_widget = Input( with Horizontal(id="langflow-password-row"):
placeholder="Auto-generated secure password", input_widget = Input(
value=current_value, placeholder="Langflow password",
password=True, value=current_value,
id="input-langflow_superuser_password", password=True,
) id="input-langflow_superuser_password",
yield input_widget )
self.inputs["langflow_superuser_password"] = input_widget yield input_widget
self.inputs["langflow_superuser_password"] = input_widget
yield Button("👁", id="toggle-langflow-password", variant="default")
yield Static(" ") yield Static(" ")
yield Static(" ") yield Static(" ")
@ -201,15 +208,17 @@ class ConfigScreen(Screen):
classes="helper-text", classes="helper-text",
) )
current_value = getattr(self.env_manager.config, "openai_api_key", "") current_value = getattr(self.env_manager.config, "openai_api_key", "")
input_widget = Input( with Horizontal(id="openai-key-row"):
placeholder="sk-...", input_widget = Input(
value=current_value, placeholder="sk-...",
password=True, value=current_value,
validators=[OpenAIKeyValidator()], password=True,
id="input-openai_api_key", validators=[OpenAIKeyValidator()],
) id="input-openai_api_key",
yield input_widget )
self.inputs["openai_api_key"] = input_widget yield input_widget
self.inputs["openai_api_key"] = input_widget
yield Button("Show", id="toggle-openai-key", variant="default")
yield Static(" ") yield Static(" ")
# Add OAuth fields only in full mode # Add OAuth fields only in full mode
@ -252,14 +261,16 @@ class ConfigScreen(Screen):
current_value = getattr( current_value = getattr(
self.env_manager.config, "google_oauth_client_secret", "" self.env_manager.config, "google_oauth_client_secret", ""
) )
input_widget = Input( with Horizontal(id="google-secret-row"):
placeholder="", input_widget = Input(
value=current_value, placeholder="",
password=True, value=current_value,
id="input-google_oauth_client_secret", password=True,
) id="input-google_oauth_client_secret",
yield input_widget )
self.inputs["google_oauth_client_secret"] = input_widget yield input_widget
self.inputs["google_oauth_client_secret"] = input_widget
yield Button("Show", id="toggle-google-secret", variant="default")
yield Static(" ") yield Static(" ")
# Microsoft Graph Client ID # Microsoft Graph Client ID
@ -300,14 +311,16 @@ class ConfigScreen(Screen):
current_value = getattr( current_value = getattr(
self.env_manager.config, "microsoft_graph_oauth_client_secret", "" self.env_manager.config, "microsoft_graph_oauth_client_secret", ""
) )
input_widget = Input( with Horizontal(id="microsoft-secret-row"):
placeholder="", input_widget = Input(
value=current_value, placeholder="",
password=True, value=current_value,
id="input-microsoft_graph_oauth_client_secret", password=True,
) id="input-microsoft_graph_oauth_client_secret",
yield input_widget )
self.inputs["microsoft_graph_oauth_client_secret"] = input_widget yield input_widget
self.inputs["microsoft_graph_oauth_client_secret"] = input_widget
yield Button("Show", id="toggle-microsoft-secret", variant="default")
yield Static(" ") yield Static(" ")
# AWS Access Key ID # AWS Access Key ID
@ -503,6 +516,22 @@ class ConfigScreen(Screen):
except Exception: except Exception:
pass 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: def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button presses.""" """Handle button presses."""
if event.button.id == "generate-btn": if event.button.id == "generate-btn":
@ -513,18 +542,39 @@ class ConfigScreen(Screen):
self.action_back() self.action_back()
elif event.button.id == "pick-docs-btn": elif event.button.id == "pick-docs-btn":
self.action_pick_documents_path() 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: def action_generate(self) -> None:
"""Generate secure passwords for admin accounts.""" """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 # Update input fields with generated values
for field_name, input_widget in self.inputs.items(): 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) new_value = getattr(self.env_manager.config, field_name)
input_widget.value = new_value 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: def action_save(self) -> None:
"""Save the configuration.""" """Save the configuration."""

View file

@ -28,12 +28,14 @@ class CommandOutputModal(ModalScreen):
DEFAULT_CSS = """ DEFAULT_CSS = """
CommandOutputModal { CommandOutputModal {
align: center middle; align: center middle;
overflow: hidden;
} }
#waves-background { #waves-background {
width: 100%; width: 100%;
height: 100%; height: 100%;
layer: background; layer: background;
overflow: hidden;
} }
#dialog { #dialog {
@ -42,6 +44,7 @@ class CommandOutputModal(ModalScreen):
border: solid #3f3f46; border: solid #3f3f46;
background: #27272a; background: #27272a;
padding: 0; padding: 0;
overflow: hidden;
} }
#title { #title {