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_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,
)

View file

@ -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;

View file

@ -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

View file

@ -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."""

View file

@ -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 {