diff --git a/.agent/workflows/deploy_to_production.md b/.agent/workflows/deploy_to_production.md index 79d8c4b..d1a60a7 100644 --- a/.agent/workflows/deploy_to_production.md +++ b/.agent/workflows/deploy_to_production.md @@ -25,8 +25,9 @@ // turbo ```bash -sshpass -p "a6163484a" ssh -o StrictHostKeyChecking=no axieyangb@192.168.68.113 \ - "echo 'a6163484a' | sudo -S docker exec \ +source /app/.agent/utils/env_loader.sh +sshpass -p "$REMOTE_PASSWORD" ssh -o StrictHostKeyChecking=no ${REMOTE_USER}@${REMOTE_HOST} \ + "echo '$REMOTE_PASSWORD' | sudo -S docker exec \ -e SYNC_TEST_BASE_URL=http://127.0.0.1:8000 \ -e SYNC_TEST_USER_ID=9a333ccd-9c3f-432f-a030-7b1e1284a436 \ ai_hub_service \ @@ -86,8 +87,9 @@ ### 1. Basic API health check ```bash -sshpass -p "a6163484a" ssh -o StrictHostKeyChecking=no axieyangb@192.168.68.113 \ - "echo 'a6163484a' | sudo -S docker exec ai_hub_service python3 -c \" +source /app/.agent/utils/env_loader.sh +sshpass -p "$REMOTE_PASSWORD" ssh -o StrictHostKeyChecking=no ${REMOTE_USER}@${REMOTE_HOST} \ + "echo '$REMOTE_PASSWORD' | sudo -S docker exec ai_hub_service python3 -c \" import requests headers = {'X-User-ID': '9a333ccd-9c3f-432f-a030-7b1e1284a436'} r = requests.get('http://localhost:8000/api/v1/nodes/test-node-1/status', headers=headers) @@ -101,8 +103,9 @@ // turbo ```bash -sshpass -p "a6163484a" ssh -o StrictHostKeyChecking=no axieyangb@192.168.68.113 \ - "echo 'a6163484a' | sudo -S docker exec \ +source /app/.agent/utils/env_loader.sh +sshpass -p "$REMOTE_PASSWORD" ssh -o StrictHostKeyChecking=no ${REMOTE_USER}@${REMOTE_HOST} \ + "echo '$REMOTE_PASSWORD' | sudo -S docker exec \ -e SYNC_TEST_BASE_URL=http://127.0.0.1:8000 \ -e SYNC_TEST_USER_ID=9a333ccd-9c3f-432f-a030-7b1e1284a436 \ ai_hub_service \ diff --git a/ai-hub/app/api/schemas.py b/ai-hub/app/api/schemas.py index 6a8f10c..7927ccf 100644 --- a/ai-hub/app/api/schemas.py +++ b/ai-hub/app/api/schemas.py @@ -436,6 +436,7 @@ """How a node should seed its workspace for a session.""" source: str = Field("empty", description="'empty' | 'server' | 'node_local'") path: Optional[str] = None # root path on node when source='node_local' + source_node_id: Optional[str] = None # root node to pull from when source='node_local' class UserNodePreferences(BaseModel): """Stored in User.preferences['nodes'].""" diff --git a/ai-hub/integration_tests/test_file_sync.py b/ai-hub/integration_tests/test_file_sync.py index bd02caf..8dfad68 100644 --- a/ai-hub/integration_tests/test_file_sync.py +++ b/ai-hub/integration_tests/test_file_sync.py @@ -453,7 +453,24 @@ "test-node-2": "cortex-test-2", } -_SSH_CMD = "sshpass -p 'a6163484a' ssh -o StrictHostKeyChecking=no axieyangb@192.168.68.113" +import subprocess +import os + +def _get_remote_env(): + try: + script_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.agent/utils/env_loader.sh")) + if os.path.exists(script_path): + cmd = f"source {script_path} >/dev/null 2>&1 && echo \"${{REMOTE_PASSWORD}}|${{REMOTE_USER}}|${{REMOTE_HOST}}\"" + res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, check=True) + parts = res.stdout.strip().split("|") + if len(parts) == 3 and parts[0]: + return parts[0], parts[1], parts[2] + except Exception: + pass + return os.environ.get("REMOTE_PASSWORD", ""), os.environ.get("REMOTE_USER", "axieyangb"), os.environ.get("REMOTE_HOST", "192.168.68.113") + +_REMOTE_PASSWORD, _REMOTE_USER, _REMOTE_HOST = _get_remote_env() +_SSH_CMD = f"sshpass -p '{_REMOTE_PASSWORD}' ssh -o StrictHostKeyChecking=no {_REMOTE_USER}@{_REMOTE_HOST}" def _restart_test_node(node_id: str): @@ -466,8 +483,8 @@ if not container: pytest.skip(f"No container mapping for {node_id}") cmd = ( - f"sshpass -p 'a6163484a' ssh -o StrictHostKeyChecking=no axieyangb@192.168.68.113 " - f"\"echo 'a6163484a' | sudo -S docker restart {container}\"" + f"sshpass -p '{_REMOTE_PASSWORD}' ssh -o StrictHostKeyChecking=no {_REMOTE_USER}@{_REMOTE_HOST} " + f"\"echo '{_REMOTE_PASSWORD}' | sudo -S docker restart {container}\"" ) result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) if result.returncode != 0: diff --git a/frontend/src/features/profile/pages/ProfilePage.js b/frontend/src/features/profile/pages/ProfilePage.js index b0b4d46..8f59b15 100644 --- a/frontend/src/features/profile/pages/ProfilePage.js +++ b/frontend/src/features/profile/pages/ProfilePage.js @@ -119,10 +119,35 @@ const toggleDefaultNode = (nodeId) => { const current = nodePrefs.default_node_ids || []; - const next = current.includes(nodeId) + const isRemoving = current.includes(nodeId); + const next = isRemoving ? current.filter(id => id !== nodeId) : [...current, nodeId]; - handleNodePrefChange({ default_node_ids: next }); + + let updates = { default_node_ids: next }; + + let nextDataSource = { ...nodePrefs.data_source }; + let sourceChanged = false; + + if (isRemoving && nextDataSource.source_node_id === nodeId) { + nextDataSource.source_node_id = null; + sourceChanged = true; + if (next.length === 0) { + nextDataSource.source = 'empty'; + nextDataSource.path = ''; + } + } + + if (next.length === 1 && !nextDataSource.source_node_id) { + nextDataSource.source_node_id = next[0]; + sourceChanged = true; + } + + if (sourceChanged) { + updates.data_source = nextDataSource; + } + + handleNodePrefChange(updates); }; const handleGeneralPreferenceUpdate = async (updates) => { @@ -247,12 +272,15 @@ {/* Account Security */}
+ Establishing a local password ensures you have a fallback authentication method in case your administrator disables Single Sign-On (OIDC) or if external login providers experience an outage. +