tui and backend: handle langflow optional password config
This commit is contained in:
parent
037bc31800
commit
8e1fd7a7fe
5 changed files with 172 additions and 55 deletions
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue