Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app-frontend/react/src/pages/History/HistoryView.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react";
import {
Box,
Checkbox,
Expand Down Expand Up @@ -26,6 +27,7 @@ import {
SolidButton,
TextButton,
} from "@root/shared/ActionButtons";
import { useToWithQuery } from "@utils/navigationAndAxiosWithQuery";

interface HistoryViewProps {
shared: boolean;
Expand All @@ -34,6 +36,7 @@ interface HistoryViewProps {
const HistoryView: React.FC<HistoryViewProps> = ({ shared }) => {
const dispatch = useAppDispatch();
const { name } = useAppSelector(userSelector);
const toWithQuery = useToWithQuery();

const theme = useTheme();

Expand Down Expand Up @@ -109,7 +112,7 @@ const HistoryView: React.FC<HistoryViewProps> = ({ shared }) => {
) : (
<Link
component={RouterLink}
to={`/chat/${conversation.id}`}
to={toWithQuery(`/chat/${conversation.id}`)}
sx={{ ...theme.customStyles.gradientBlock }}
className={styles.historyLink}
>
Expand Down
3 changes: 3 additions & 0 deletions setup-scripts/setup-genai-studio/genai-studio.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
- name: Create ssh secrets
import_playbook: playbooks/create-ssh-secrets.yml

- name: Deploy mysqldb
import_playbook: playbooks/deploy-mysqldb.yml

Expand Down
3 changes: 3 additions & 0 deletions setup-scripts/setup-genai-studio/helm-values/mysqldb.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
auth:
rootPassword: root

image:
tag: "8.0"

primary:
extraFlags: "--innodb-use-native-aio=0"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

tasks:

- name: Ensure namespace studio exists
shell: kubectl get ns studio || kubectl create ns studio

- name: Check if Kubernetes secret exists
command: kubectl -n studio get secret ssh-keys
register: kubectl_secret_check
Expand Down
1 change: 1 addition & 0 deletions setup-scripts/setup-genai-studio/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The genai-studio playbook script will:

Run below commands:
```sh
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
sudo apt install ansible -y
ansible-playbook genai-studio.yml
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@
https_proxy: "{{ http_proxy }}"
no_proxy: "{{ no_proxy }}"

- name: Install Docker SDK for Python
pip:
name: docker
executable: pip3
- name: Install Docker SDK for Python via apt
apt:
name: python3-docker
state: present
become: yes
environment:
http_proxy: "{{ http_proxy }}"
https_proxy: "{{ http_proxy }}"
Expand Down
192 changes: 190 additions & 2 deletions studio-backend/app/routers/debuglog_router.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,173 @@
from fastapi import APIRouter, HTTPException
from kubernetes import client
import re

router = APIRouter()

def find_pod_dependencies(pod, all_pods, services, namespace, core_v1_api):
"""
Analyze pod dependencies by checking:
1. Environment variables pointing to other services
2. Service selectors matching pod labels
3. ConfigMaps and Secrets that might reference other pods
4. Init containers waiting for remote services
"""
dependencies = []

# Get pod environment variables from main containers
env_vars = []
configmap_refs = []

if pod.spec.containers:
for container in pod.spec.containers:
# Direct environment variables
if container.env:
for env in container.env:
if env.value:
env_vars.append(env.value)

# Environment variables from ConfigMaps
if container.env_from:
for env_from in container.env_from:
if env_from.config_map_ref:
configmap_refs.append(env_from.config_map_ref.name)

# Get environment variables from init containers (for wait-for-remote-service patterns)
init_env_vars = []

if pod.spec.init_containers:
for init_container in pod.spec.init_containers:
# Direct environment variables from init containers
if init_container.env:
for env in init_container.env:
if env.value:
init_env_vars.append(env.value)

# Environment variables from ConfigMaps in init containers
if init_container.env_from:
for env_from in init_container.env_from:
if env_from.config_map_ref and env_from.config_map_ref.name not in configmap_refs:
configmap_refs.append(env_from.config_map_ref.name)

# Check init container commands for wait-for-remote-service patterns
if init_container.name == "wait-for-remote-service" or "wait-for" in init_container.name.lower():
if init_container.command:
command_str = " ".join(init_container.command)
# Look for service endpoints in commands like "nc -z -v -w30 $HEALTHCHECK_ENDPOINT"
# Extract service names from HEALTHCHECK_ENDPOINT or similar patterns
for env_var in init_env_vars:
# Pattern like "tei-0:9003" or "redis-vector-store-0:9001"
service_match = re.search(r'([a-zA-Z0-9-]+):\d+', env_var)
if service_match:
service_name = service_match.group(1)
# Find pods that this service targets
for service in services.items:
if service.metadata.name == service_name and service.spec.selector:
for target_pod in all_pods.items:
if target_pod.metadata.name != pod.metadata.name:
target_labels = target_pod.metadata.labels or {}
if all(target_labels.get(k) == v for k, v in service.spec.selector.items()):
if target_pod.metadata.name not in dependencies:
dependencies.append(target_pod.metadata.name)

# Fetch ConfigMap data to analyze environment variables
configmap_env_vars = []
for configmap_name in configmap_refs:
try:
configmap = core_v1_api.read_namespaced_config_map(name=configmap_name, namespace=namespace)
if configmap.data:
for key, value in configmap.data.items():
if value:
configmap_env_vars.append(value)
except Exception as e:
print(f"Error fetching ConfigMap {configmap_name}: {str(e)}")

# Combine all environment variables for further analysis
all_env_vars = env_vars + init_env_vars + configmap_env_vars

# Debug output
print(f"Analyzing dependencies for pod: {pod.metadata.name}")
print(f"ConfigMap refs: {configmap_refs}")
print(f"Total env vars found: {len(all_env_vars)}")
if all_env_vars:
print(f"Sample env vars: {all_env_vars[:3]}") # Show first 3 for debugging

# Check if environment variables reference other services
for service in services.items:
service_name = service.metadata.name
# Check if service name is referenced in environment variables
for env_val in all_env_vars:
if service_name in env_val:
# Find pods that this service targets
if service.spec.selector:
for target_pod in all_pods.items:
if target_pod.metadata.name != pod.metadata.name:
target_labels = target_pod.metadata.labels or {}
if all(target_labels.get(k) == v for k, v in service.spec.selector.items()):
if target_pod.metadata.name not in dependencies:
dependencies.append(target_pod.metadata.name)

# Check for service endpoint patterns in environment variables
# Pattern like "http://service-name:port" or "service-name:port"
for env_val in all_env_vars:
# HTTP URL pattern
http_matches = re.findall(r'https?://([a-zA-Z0-9-]+):\d+', env_val)
for service_name in http_matches:
for service in services.items:
if service.metadata.name == service_name and service.spec.selector:
for target_pod in all_pods.items:
if target_pod.metadata.name != pod.metadata.name:
target_labels = target_pod.metadata.labels or {}
if all(target_labels.get(k) == v for k, v in service.spec.selector.items()):
if target_pod.metadata.name not in dependencies:
dependencies.append(target_pod.metadata.name)

# Direct service:port pattern
service_port_matches = re.findall(r'([a-zA-Z0-9-]+):\d+', env_val)
for service_name in service_port_matches:
for service in services.items:
if service.metadata.name == service_name and service.spec.selector:
for target_pod in all_pods.items:
if target_pod.metadata.name != pod.metadata.name:
target_labels = target_pod.metadata.labels or {}
if all(target_labels.get(k) == v for k, v in service.spec.selector.items()):
if target_pod.metadata.name not in dependencies:
dependencies.append(target_pod.metadata.name)

# Check DNS-based dependencies (common pattern: service-name.namespace.svc.cluster.local)
dns_pattern = r'([a-zA-Z0-9-]+)\.(' + re.escape(namespace) + r')\.svc\.cluster\.local'
for env_val in all_env_vars:
matches = re.findall(dns_pattern, env_val)
for match in matches:
service_name = match[0]
# Find the service and its target pods
for service in services.items:
if service.metadata.name == service_name and service.spec.selector:
for target_pod in all_pods.items:
if target_pod.metadata.name != pod.metadata.name:
target_labels = target_pod.metadata.labels or {}
if all(target_labels.get(k) == v for k, v in service.spec.selector.items()):
if target_pod.metadata.name not in dependencies:
dependencies.append(target_pod.metadata.name)

# Additional pattern matching for service names that might not include ports
# Look for patterns where service names appear in environment variable values
service_names = [service.metadata.name for service in services.items]
for env_val in all_env_vars:
for service_name in service_names:
# More flexible matching - look for service name as whole word
if re.search(r'\b' + re.escape(service_name) + r'\b', env_val):
for service in services.items:
if service.metadata.name == service_name and service.spec.selector:
for target_pod in all_pods.items:
if target_pod.metadata.name != pod.metadata.name:
target_labels = target_pod.metadata.labels or {}
if all(target_labels.get(k) == v for k, v in service.spec.selector.items()):
if target_pod.metadata.name not in dependencies:
dependencies.append(target_pod.metadata.name)

return dependencies

@router.get("/podlogs/{namespace}", summary="Fetch all pods in a namespace")
async def get_all_pods_in_namespace(namespace: str):
core_v1_api = client.CoreV1Api()
Expand All @@ -11,6 +176,13 @@ async def get_all_pods_in_namespace(namespace: str):
if not pods.items:
return {"namespace": namespace, "pods": []}

# Fetch all services in the namespace for dependency analysis
try:
services = core_v1_api.list_namespaced_service(namespace=namespace)
except Exception as e:
print(f"Error fetching services: {str(e)}")
services = None

pod_list = []
for pod in pods.items:
pod_name = pod.metadata.name
Expand All @@ -22,10 +194,14 @@ async def get_all_pods_in_namespace(namespace: str):
# Fetch logs related to the pod
try:
pod_logs = core_v1_api.read_namespaced_pod_log(name=pod_name, namespace=namespace, tail_lines=200)
for line in pod_logs.splitlines():
log_entries.append(line)
if pod_logs and pod_logs.strip():
for line in pod_logs.splitlines():
log_entries.append(line)
else:
log_entries.append("** Pod has no logs available")
except Exception as e:
print(f"Error fetching logs: {str(e)}")
log_entries.append("Unable to fetch logs: Pod may not be running or logs are not accessible")

# Fetch events related to the pod
try:
Expand All @@ -38,6 +214,17 @@ async def get_all_pods_in_namespace(namespace: str):
except Exception as e:
print(f"Error fetching events: {str(e)}")

# Analyze pod dependencies
dependencies = []
if services:
try:
dependencies = find_pod_dependencies(pod, pods, services, namespace, core_v1_api)
print(f"Pod {pod_name} dependencies: {dependencies}")
except Exception as e:
print(f"Error analyzing dependencies for pod {pod_name}: {str(e)}")
import traceback
traceback.print_exc()

# Determine the Ready and Status of the pod
ready_status = "Unknown"
pod_status = pod.status.phase
Expand All @@ -63,6 +250,7 @@ async def get_all_pods_in_namespace(namespace: str):
"annotations": pod.metadata.annotations,
"logs": log_entries,
"events": event_entries,
"dependencies": dependencies,
})

return {"namespace": namespace, "pods": pod_list}
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,17 @@ export default function FlowListMenu({ chatflow, isAgentCanvas, setError, update
const handleDuplicate = () => {
setAnchorEl(null)
try {
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
window.open(`${uiBaseURL}/${isAgentCanvas ? 'agentcanvas' : 'canvas'}`, '_blank')
// Store both the flow data and type information
const duplicatedData = {
flowData: chatflow.flowData,
type: chatflow.type || (isAgentCanvas ? 'MULTIAGENT' : 'CHATFLOW')
}
localStorage.setItem('duplicatedFlowData', JSON.stringify(duplicatedData))

// Determine canvas type based on original chatflow type
const targetCanvas = chatflow.type === 'OPEA' ? 'opeacanvas' :
chatflow.type === 'MULTIAGENT' ? 'agentcanvas' : 'canvas'
window.open(`${uiBaseURL}/${targetCanvas}`, '_blank')
} catch (e) {
console.error(e)
}
Expand Down
14 changes: 12 additions & 2 deletions studio-frontend/packages/ui/src/views/canvas/CanvasHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,18 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, isOpeaCanvas, handleSaveFlow, h
let flowData = chatflow.flowData
const parsedFlowData = JSON.parse(flowData)
flowData = JSON.stringify(parsedFlowData)
localStorage.setItem('duplicatedFlowData', flowData)
window.open(`${uiBaseURL}/${isAgentCanvas ? 'agentcanvas' : isOpeaCanvas ? 'opeacanvas' : 'canvas'}`, '_blank')

// Store both the flow data and type information
const duplicatedData = {
flowData: flowData,
type: chatflow.type || (isAgentCanvas ? 'MULTIAGENT' : isOpeaCanvas ? 'OPEA' : 'CHATFLOW')
}
localStorage.setItem('duplicatedFlowData', JSON.stringify(duplicatedData))

// Determine canvas type based on original chatflow type
const targetCanvas = chatflow.type === 'OPEA' ? 'opeacanvas' :
chatflow.type === 'MULTIAGENT' ? 'agentcanvas' : 'canvas'
window.open(`${uiBaseURL}/${targetCanvas}`, '_blank')
} catch (e) {
console.error(e)
}
Expand Down
Loading
Loading