machinaos 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.template +71 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/cli.js +159 -0
- package/client/.dockerignore +45 -0
- package/client/Dockerfile +68 -0
- package/client/eslint.config.js +29 -0
- package/client/index.html +13 -0
- package/client/nginx.conf +66 -0
- package/client/package.json +48 -0
- package/client/src/App.tsx +27 -0
- package/client/src/Dashboard.tsx +1173 -0
- package/client/src/ParameterPanel.tsx +301 -0
- package/client/src/components/AIAgentNode.tsx +321 -0
- package/client/src/components/APIKeyValidator.tsx +118 -0
- package/client/src/components/ClaudeChatModelNode.tsx +18 -0
- package/client/src/components/ConditionalEdge.tsx +189 -0
- package/client/src/components/CredentialsModal.tsx +306 -0
- package/client/src/components/EdgeConditionEditor.tsx +443 -0
- package/client/src/components/GeminiChatModelNode.tsx +18 -0
- package/client/src/components/GenericNode.tsx +357 -0
- package/client/src/components/LocationParameterPanel.tsx +154 -0
- package/client/src/components/ModelNode.tsx +286 -0
- package/client/src/components/OpenAIChatModelNode.tsx +18 -0
- package/client/src/components/OutputPanel.tsx +471 -0
- package/client/src/components/ParameterRenderer.tsx +1874 -0
- package/client/src/components/SkillEditorModal.tsx +417 -0
- package/client/src/components/SquareNode.tsx +797 -0
- package/client/src/components/StartNode.tsx +250 -0
- package/client/src/components/ToolkitNode.tsx +365 -0
- package/client/src/components/TriggerNode.tsx +463 -0
- package/client/src/components/auth/LoginPage.tsx +247 -0
- package/client/src/components/auth/ProtectedRoute.tsx +59 -0
- package/client/src/components/base/BaseChatModelNode.tsx +271 -0
- package/client/src/components/icons/AIProviderIcons.tsx +50 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
- package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
- package/client/src/components/maps/index.ts +26 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
- package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
- package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
- package/client/src/components/parameterPanel/index.ts +42 -0
- package/client/src/components/shared/DataPanel.tsx +142 -0
- package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
- package/client/src/components/ui/AIResultModal.tsx +204 -0
- package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
- package/client/src/components/ui/CodeEditor.tsx +81 -0
- package/client/src/components/ui/CollapsibleSection.tsx +88 -0
- package/client/src/components/ui/ComponentItem.tsx +154 -0
- package/client/src/components/ui/ComponentPalette.tsx +321 -0
- package/client/src/components/ui/ConsolePanel.tsx +1074 -0
- package/client/src/components/ui/ErrorBoundary.tsx +196 -0
- package/client/src/components/ui/InputNodesPanel.tsx +204 -0
- package/client/src/components/ui/MapSelector.tsx +314 -0
- package/client/src/components/ui/Modal.tsx +149 -0
- package/client/src/components/ui/NodeContextMenu.tsx +192 -0
- package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
- package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
- package/client/src/components/ui/SettingsPanel.tsx +243 -0
- package/client/src/components/ui/TopToolbar.tsx +736 -0
- package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
- package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
- package/client/src/config/antdTheme.ts +186 -0
- package/client/src/config/api.ts +54 -0
- package/client/src/contexts/AuthContext.tsx +221 -0
- package/client/src/contexts/ThemeContext.tsx +42 -0
- package/client/src/contexts/WebSocketContext.tsx +1971 -0
- package/client/src/factories/baseChatModelFactory.ts +256 -0
- package/client/src/hooks/useAndroidOperations.ts +164 -0
- package/client/src/hooks/useApiKeyValidation.ts +107 -0
- package/client/src/hooks/useApiKeys.ts +238 -0
- package/client/src/hooks/useAppTheme.ts +17 -0
- package/client/src/hooks/useComponentPalette.ts +51 -0
- package/client/src/hooks/useCopyPaste.ts +155 -0
- package/client/src/hooks/useDragAndDrop.ts +124 -0
- package/client/src/hooks/useDragVariable.ts +88 -0
- package/client/src/hooks/useExecution.ts +313 -0
- package/client/src/hooks/useParameterPanel.ts +176 -0
- package/client/src/hooks/useReactFlowNodes.ts +189 -0
- package/client/src/hooks/useToolSchema.ts +209 -0
- package/client/src/hooks/useWhatsApp.ts +196 -0
- package/client/src/hooks/useWorkflowManagement.ts +46 -0
- package/client/src/index.css +315 -0
- package/client/src/main.tsx +19 -0
- package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
- package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
- package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
- package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
- package/client/src/nodeDefinitions/chatNodes.ts +135 -0
- package/client/src/nodeDefinitions/codeNodes.ts +54 -0
- package/client/src/nodeDefinitions/documentNodes.ts +379 -0
- package/client/src/nodeDefinitions/index.ts +15 -0
- package/client/src/nodeDefinitions/locationNodes.ts +463 -0
- package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
- package/client/src/nodeDefinitions/skillNodes.ts +211 -0
- package/client/src/nodeDefinitions/toolNodes.ts +198 -0
- package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
- package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
- package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
- package/client/src/nodeDefinitions.ts +104 -0
- package/client/src/schemas/workflowSchema.ts +264 -0
- package/client/src/services/dynamicParameterService.ts +96 -0
- package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
- package/client/src/services/executionService.ts +232 -0
- package/client/src/services/workflowApi.ts +91 -0
- package/client/src/store/useAppStore.ts +582 -0
- package/client/src/styles/theme.ts +508 -0
- package/client/src/styles/zIndex.ts +17 -0
- package/client/src/types/ComponentTypes.ts +39 -0
- package/client/src/types/EdgeCondition.ts +231 -0
- package/client/src/types/INodeProperties.ts +288 -0
- package/client/src/types/NodeTypes.ts +28 -0
- package/client/src/utils/formatters.ts +33 -0
- package/client/src/utils/googleMapsLoader.ts +140 -0
- package/client/src/utils/locationUtils.ts +85 -0
- package/client/src/utils/nodeUtils.ts +31 -0
- package/client/src/utils/workflow.ts +30 -0
- package/client/src/utils/workflowExport.ts +120 -0
- package/client/src/vite-env.d.ts +12 -0
- package/client/tailwind.config.js +60 -0
- package/client/tsconfig.json +25 -0
- package/client/tsconfig.node.json +11 -0
- package/client/vite.config.js +35 -0
- package/docker-compose.prod.yml +107 -0
- package/docker-compose.yml +104 -0
- package/docs-MachinaOs/README.md +85 -0
- package/docs-MachinaOs/deployment/docker.mdx +228 -0
- package/docs-MachinaOs/deployment/production.mdx +345 -0
- package/docs-MachinaOs/docs.json +75 -0
- package/docs-MachinaOs/faq.mdx +309 -0
- package/docs-MachinaOs/favicon.svg +5 -0
- package/docs-MachinaOs/installation.mdx +160 -0
- package/docs-MachinaOs/introduction.mdx +114 -0
- package/docs-MachinaOs/logo/dark.svg +6 -0
- package/docs-MachinaOs/logo/light.svg +6 -0
- package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
- package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
- package/docs-MachinaOs/nodes/android.mdx +411 -0
- package/docs-MachinaOs/nodes/overview.mdx +181 -0
- package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
- package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
- package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
- package/docs-MachinaOs/quickstart.mdx +119 -0
- package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
- package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
- package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
- package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
- package/nul +0 -0
- package/package.json +70 -0
- package/scripts/build.js +158 -0
- package/scripts/check-ports.ps1 +33 -0
- package/scripts/clean.js +40 -0
- package/scripts/docker.js +93 -0
- package/scripts/kill-port.ps1 +154 -0
- package/scripts/start.js +210 -0
- package/scripts/stop.js +325 -0
- package/server/.dockerignore +44 -0
- package/server/Dockerfile +45 -0
- package/server/constants.py +249 -0
- package/server/core/__init__.py +1 -0
- package/server/core/cache.py +461 -0
- package/server/core/config.py +128 -0
- package/server/core/container.py +99 -0
- package/server/core/database.py +1211 -0
- package/server/core/logging.py +314 -0
- package/server/main.py +289 -0
- package/server/middleware/__init__.py +5 -0
- package/server/middleware/auth.py +89 -0
- package/server/models/__init__.py +1 -0
- package/server/models/auth.py +52 -0
- package/server/models/cache.py +24 -0
- package/server/models/database.py +211 -0
- package/server/models/nodes.py +455 -0
- package/server/package.json +9 -0
- package/server/pyproject.toml +72 -0
- package/server/requirements.txt +83 -0
- package/server/routers/__init__.py +1 -0
- package/server/routers/android.py +294 -0
- package/server/routers/auth.py +203 -0
- package/server/routers/database.py +151 -0
- package/server/routers/maps.py +142 -0
- package/server/routers/nodejs_compat.py +289 -0
- package/server/routers/webhook.py +90 -0
- package/server/routers/websocket.py +2127 -0
- package/server/routers/whatsapp.py +761 -0
- package/server/routers/workflow.py +200 -0
- package/server/services/__init__.py +1 -0
- package/server/services/ai.py +2415 -0
- package/server/services/android/__init__.py +27 -0
- package/server/services/android/broadcaster.py +114 -0
- package/server/services/android/client.py +608 -0
- package/server/services/android/manager.py +78 -0
- package/server/services/android/protocol.py +165 -0
- package/server/services/android_service.py +588 -0
- package/server/services/auth.py +131 -0
- package/server/services/chat_client.py +160 -0
- package/server/services/deployment/__init__.py +12 -0
- package/server/services/deployment/manager.py +706 -0
- package/server/services/deployment/state.py +47 -0
- package/server/services/deployment/triggers.py +275 -0
- package/server/services/event_waiter.py +785 -0
- package/server/services/execution/__init__.py +77 -0
- package/server/services/execution/cache.py +769 -0
- package/server/services/execution/conditions.py +373 -0
- package/server/services/execution/dlq.py +132 -0
- package/server/services/execution/executor.py +1351 -0
- package/server/services/execution/models.py +531 -0
- package/server/services/execution/recovery.py +235 -0
- package/server/services/handlers/__init__.py +126 -0
- package/server/services/handlers/ai.py +355 -0
- package/server/services/handlers/android.py +260 -0
- package/server/services/handlers/code.py +278 -0
- package/server/services/handlers/document.py +598 -0
- package/server/services/handlers/http.py +193 -0
- package/server/services/handlers/polyglot.py +105 -0
- package/server/services/handlers/tools.py +845 -0
- package/server/services/handlers/triggers.py +107 -0
- package/server/services/handlers/utility.py +822 -0
- package/server/services/handlers/whatsapp.py +476 -0
- package/server/services/maps.py +289 -0
- package/server/services/memory_store.py +103 -0
- package/server/services/node_executor.py +375 -0
- package/server/services/parameter_resolver.py +218 -0
- package/server/services/polyglot_client.py +169 -0
- package/server/services/scheduler.py +155 -0
- package/server/services/skill_loader.py +417 -0
- package/server/services/status_broadcaster.py +826 -0
- package/server/services/temporal/__init__.py +23 -0
- package/server/services/temporal/activities.py +344 -0
- package/server/services/temporal/client.py +76 -0
- package/server/services/temporal/executor.py +147 -0
- package/server/services/temporal/worker.py +251 -0
- package/server/services/temporal/workflow.py +355 -0
- package/server/services/temporal/ws_client.py +236 -0
- package/server/services/text.py +111 -0
- package/server/services/user_auth.py +172 -0
- package/server/services/websocket_client.py +29 -0
- package/server/services/workflow.py +597 -0
- package/server/skills/android-skill/SKILL.md +82 -0
- package/server/skills/assistant-personality/SKILL.md +45 -0
- package/server/skills/code-skill/SKILL.md +140 -0
- package/server/skills/http-skill/SKILL.md +161 -0
- package/server/skills/maps-skill/SKILL.md +170 -0
- package/server/skills/memory-skill/SKILL.md +154 -0
- package/server/skills/scheduler-skill/SKILL.md +84 -0
- package/server/skills/whatsapp-skill/SKILL.md +283 -0
- package/server/uv.lock +2916 -0
- package/server/whatsapp-rpc/.dockerignore +30 -0
- package/server/whatsapp-rpc/Dockerfile +44 -0
- package/server/whatsapp-rpc/Dockerfile.web +17 -0
- package/server/whatsapp-rpc/README.md +139 -0
- package/server/whatsapp-rpc/cli.js +95 -0
- package/server/whatsapp-rpc/configs/config.yaml +7 -0
- package/server/whatsapp-rpc/docker-compose.yml +35 -0
- package/server/whatsapp-rpc/docs/API.md +410 -0
- package/server/whatsapp-rpc/go.mod +67 -0
- package/server/whatsapp-rpc/go.sum +203 -0
- package/server/whatsapp-rpc/package.json +30 -0
- package/server/whatsapp-rpc/schema.json +1294 -0
- package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
- package/server/whatsapp-rpc/scripts/cli.js +162 -0
- package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
- package/server/whatsapp-rpc/src/go/config/config.go +49 -0
- package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
- package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
- package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
- package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
- package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
- package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
- package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
- package/server/whatsapp-rpc/web/app.py +609 -0
- package/server/whatsapp-rpc/web/requirements.txt +6 -0
- package/server/whatsapp-rpc/web/rpc_client.py +427 -0
- package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
- package/server/whatsapp-rpc/web/templates/base.html +150 -0
- package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
- package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
- package/server/whatsapp-rpc/web/templates/groups.html +328 -0
- package/server/whatsapp-rpc/web/templates/messages.html +465 -0
- package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
- package/server/whatsapp-rpc/web/templates/send.html +259 -0
- package/server/whatsapp-rpc/web/templates/settings.html +459 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Android System Services routes."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
|
|
7
|
+
from core.container import container
|
|
8
|
+
from services.android_service import AndroidService
|
|
9
|
+
from core.logging import get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
router = APIRouter(prefix="/api/android", tags=["android"])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AndroidServiceRequest(BaseModel):
|
|
16
|
+
"""Request model for Android service execution."""
|
|
17
|
+
service_id: str = Field(..., description="Android service ID (e.g., 'battery', 'network', 'app_launcher')")
|
|
18
|
+
action: str = Field(..., description="Service action to perform (e.g., 'status', 'launch', 'list')")
|
|
19
|
+
parameters: Dict[str, Any] = Field(default_factory=dict, description="Action-specific parameters")
|
|
20
|
+
android_host: str = Field(default="localhost", description="Android device API host")
|
|
21
|
+
android_port: int = Field(default=8888, description="Android device API port")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.post("/execute")
|
|
25
|
+
async def execute_android_service(
|
|
26
|
+
request: AndroidServiceRequest,
|
|
27
|
+
android_service: AndroidService = Depends(lambda: container.android_service())
|
|
28
|
+
):
|
|
29
|
+
"""Execute an Android system service action.
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
- Battery status: {"service_id": "battery", "action": "status"}
|
|
33
|
+
- Launch app: {"service_id": "app_launcher", "action": "launch", "parameters": {"package_name": "com.android.settings"}}
|
|
34
|
+
- List apps: {"service_id": "app_list", "action": "list", "parameters": {"include_system": false}}
|
|
35
|
+
"""
|
|
36
|
+
logger.info(
|
|
37
|
+
"[Android API] Executing service",
|
|
38
|
+
service_id=request.service_id,
|
|
39
|
+
action=request.action,
|
|
40
|
+
android_host=request.android_host,
|
|
41
|
+
android_port=request.android_port
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
result = await android_service.execute_service(
|
|
45
|
+
node_id="api_call",
|
|
46
|
+
service_id=request.service_id,
|
|
47
|
+
action=request.action,
|
|
48
|
+
parameters=request.parameters,
|
|
49
|
+
android_host=request.android_host,
|
|
50
|
+
android_port=request.android_port
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@router.get("/status")
|
|
57
|
+
async def check_device_status(
|
|
58
|
+
android_host: str = "localhost",
|
|
59
|
+
android_port: int = 8888,
|
|
60
|
+
android_service: AndroidService = Depends(lambda: container.android_service())
|
|
61
|
+
):
|
|
62
|
+
"""Check if Android device API is reachable."""
|
|
63
|
+
logger.info(
|
|
64
|
+
"[Android API] Checking device status",
|
|
65
|
+
android_host=android_host,
|
|
66
|
+
android_port=android_port
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
status = await android_service.check_device_status(
|
|
70
|
+
android_host=android_host,
|
|
71
|
+
android_port=android_port
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return status
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@router.get("/services/{service_id}/actions")
|
|
78
|
+
async def get_service_actions(
|
|
79
|
+
service_id: str,
|
|
80
|
+
android_service: AndroidService = Depends(lambda: container.android_service())
|
|
81
|
+
):
|
|
82
|
+
"""Get available actions for a specific Android service.
|
|
83
|
+
|
|
84
|
+
Returns list of action options that can be performed on the service.
|
|
85
|
+
"""
|
|
86
|
+
logger.info(f"[Android API] Getting actions for service: {service_id}")
|
|
87
|
+
|
|
88
|
+
actions = android_service.get_service_actions(service_id)
|
|
89
|
+
|
|
90
|
+
if not actions:
|
|
91
|
+
return {
|
|
92
|
+
"success": False,
|
|
93
|
+
"error": f"Unknown service: {service_id}",
|
|
94
|
+
"actions": []
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"success": True,
|
|
99
|
+
"service_id": service_id,
|
|
100
|
+
"actions": actions
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@router.get("/services/{service_id}/actions/{action}/parameters")
|
|
105
|
+
async def get_action_parameters(
|
|
106
|
+
service_id: str,
|
|
107
|
+
action: str,
|
|
108
|
+
android_service: AndroidService = Depends(lambda: container.android_service())
|
|
109
|
+
):
|
|
110
|
+
"""Get default parameters for a specific service action.
|
|
111
|
+
|
|
112
|
+
Returns default parameter template that can be used as a starting point.
|
|
113
|
+
"""
|
|
114
|
+
logger.info(f"[Android API] Getting parameters for: {service_id}/{action}")
|
|
115
|
+
|
|
116
|
+
default_params = android_service.get_default_parameters(service_id, action)
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
"success": True,
|
|
120
|
+
"service_id": service_id,
|
|
121
|
+
"action": action,
|
|
122
|
+
"default_parameters": default_params
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@router.get("/devices")
|
|
127
|
+
async def list_android_devices():
|
|
128
|
+
"""List all connected Android devices via ADB."""
|
|
129
|
+
import subprocess
|
|
130
|
+
try:
|
|
131
|
+
# Run adb devices command
|
|
132
|
+
result = subprocess.run(
|
|
133
|
+
["adb", "devices", "-l"],
|
|
134
|
+
capture_output=True,
|
|
135
|
+
text=True,
|
|
136
|
+
encoding="utf-8",
|
|
137
|
+
errors="replace",
|
|
138
|
+
timeout=5
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
devices = []
|
|
142
|
+
lines = result.stdout.strip().split('\n')[1:] # Skip header "List of devices attached"
|
|
143
|
+
|
|
144
|
+
for line in lines:
|
|
145
|
+
if not line.strip():
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
parts = line.split()
|
|
149
|
+
if len(parts) >= 2:
|
|
150
|
+
device_id = parts[0]
|
|
151
|
+
state = parts[1]
|
|
152
|
+
|
|
153
|
+
# Extract model and other info
|
|
154
|
+
model = "Unknown"
|
|
155
|
+
android_version = None
|
|
156
|
+
|
|
157
|
+
for part in parts[2:]:
|
|
158
|
+
if part.startswith("model:"):
|
|
159
|
+
model = part.split(":")[1]
|
|
160
|
+
elif part.startswith("device:"):
|
|
161
|
+
pass # Can use this for device codename
|
|
162
|
+
|
|
163
|
+
devices.append({
|
|
164
|
+
"id": device_id,
|
|
165
|
+
"state": state,
|
|
166
|
+
"model": model,
|
|
167
|
+
"android_version": android_version
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
"success": True,
|
|
172
|
+
"devices": devices,
|
|
173
|
+
"count": len(devices)
|
|
174
|
+
}
|
|
175
|
+
except FileNotFoundError:
|
|
176
|
+
logger.error("[Android] ADB not found in PATH")
|
|
177
|
+
return {
|
|
178
|
+
"success": False,
|
|
179
|
+
"error": "ADB not found. Please install Android SDK Platform Tools",
|
|
180
|
+
"devices": []
|
|
181
|
+
}
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"[Android] Failed to list devices: {e}")
|
|
184
|
+
return {
|
|
185
|
+
"success": False,
|
|
186
|
+
"error": str(e),
|
|
187
|
+
"devices": []
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@router.post("/port-forward")
|
|
192
|
+
async def setup_port_forwarding(
|
|
193
|
+
device_id: str,
|
|
194
|
+
local_port: int = 8888,
|
|
195
|
+
device_port: int = 8888
|
|
196
|
+
):
|
|
197
|
+
"""Setup ADB port forwarding for Android device communication."""
|
|
198
|
+
import subprocess
|
|
199
|
+
try:
|
|
200
|
+
# Setup port forwarding: adb -s device_id forward tcp:local_port tcp:device_port
|
|
201
|
+
cmd = ["adb", "-s", device_id, "forward", f"tcp:{local_port}", f"tcp:{device_port}"]
|
|
202
|
+
|
|
203
|
+
result = subprocess.run(
|
|
204
|
+
cmd,
|
|
205
|
+
capture_output=True,
|
|
206
|
+
text=True,
|
|
207
|
+
encoding="utf-8",
|
|
208
|
+
errors="replace",
|
|
209
|
+
timeout=5
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if result.returncode == 0:
|
|
213
|
+
logger.info(
|
|
214
|
+
f"[Android] Port forwarding setup: {device_id} tcp:{local_port} -> tcp:{device_port}"
|
|
215
|
+
)
|
|
216
|
+
return {
|
|
217
|
+
"success": True,
|
|
218
|
+
"device_id": device_id,
|
|
219
|
+
"local_port": local_port,
|
|
220
|
+
"device_port": device_port,
|
|
221
|
+
"message": f"Port forwarding active: localhost:{local_port} -> device:{device_port}"
|
|
222
|
+
}
|
|
223
|
+
else:
|
|
224
|
+
error_msg = result.stderr.strip() or result.stdout.strip()
|
|
225
|
+
logger.error(f"[Android] Port forwarding failed: {error_msg}")
|
|
226
|
+
return {
|
|
227
|
+
"success": False,
|
|
228
|
+
"error": error_msg
|
|
229
|
+
}
|
|
230
|
+
except FileNotFoundError:
|
|
231
|
+
logger.error("[Android] ADB not found in PATH")
|
|
232
|
+
return {
|
|
233
|
+
"success": False,
|
|
234
|
+
"error": "ADB not found. Please install Android SDK Platform Tools"
|
|
235
|
+
}
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(f"[Android] Port forwarding error: {e}")
|
|
238
|
+
return {
|
|
239
|
+
"success": False,
|
|
240
|
+
"error": str(e)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@router.get("/health")
|
|
245
|
+
async def android_health_check():
|
|
246
|
+
"""Android service health check."""
|
|
247
|
+
return {
|
|
248
|
+
"status": "OK",
|
|
249
|
+
"service": "android"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@router.get("/relay-status")
|
|
254
|
+
async def get_relay_connection_status():
|
|
255
|
+
"""Get relay connection status for remote Android devices.
|
|
256
|
+
|
|
257
|
+
Returns connection status including whether a relay connection is active
|
|
258
|
+
and the paired Android device.
|
|
259
|
+
"""
|
|
260
|
+
try:
|
|
261
|
+
from services.android import get_current_relay_client
|
|
262
|
+
|
|
263
|
+
relay_client = get_current_relay_client()
|
|
264
|
+
|
|
265
|
+
if relay_client and relay_client.is_connected():
|
|
266
|
+
return {
|
|
267
|
+
"success": True,
|
|
268
|
+
"connected": True,
|
|
269
|
+
"paired": relay_client.is_paired(),
|
|
270
|
+
"connection_type": "relay",
|
|
271
|
+
"device_id": relay_client.paired_device_id,
|
|
272
|
+
"device_name": relay_client.paired_device_name,
|
|
273
|
+
"session_token": relay_client.session_token,
|
|
274
|
+
"qr_data": relay_client.qr_data if not relay_client.is_paired() else None,
|
|
275
|
+
"status": "paired" if relay_client.is_paired() else "waiting_for_pairing"
|
|
276
|
+
}
|
|
277
|
+
else:
|
|
278
|
+
return {
|
|
279
|
+
"success": True,
|
|
280
|
+
"connected": False,
|
|
281
|
+
"paired": False,
|
|
282
|
+
"connection_type": None,
|
|
283
|
+
"device_id": None,
|
|
284
|
+
"device_name": None,
|
|
285
|
+
"status": "disconnected"
|
|
286
|
+
}
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logger.error(f"[Android] Failed to get relay status: {e}")
|
|
289
|
+
return {
|
|
290
|
+
"success": False,
|
|
291
|
+
"connected": False,
|
|
292
|
+
"error": str(e),
|
|
293
|
+
"status": "error"
|
|
294
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Authentication routes for user login, registration, and session management."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException, Response, Request
|
|
4
|
+
from pydantic import BaseModel, EmailStr
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from core.container import container
|
|
8
|
+
from core.config import Settings
|
|
9
|
+
from core.logging import get_logger
|
|
10
|
+
from services.user_auth import UserAuthService
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RegisterRequest(BaseModel):
|
|
17
|
+
email: EmailStr
|
|
18
|
+
password: str
|
|
19
|
+
display_name: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LoginRequest(BaseModel):
|
|
23
|
+
email: EmailStr
|
|
24
|
+
password: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class UserResponse(BaseModel):
|
|
28
|
+
id: int
|
|
29
|
+
email: str
|
|
30
|
+
display_name: str
|
|
31
|
+
is_owner: bool
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_user_auth_service() -> UserAuthService:
|
|
35
|
+
return container.user_auth_service()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_settings() -> Settings:
|
|
39
|
+
return container.settings()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@router.get("/status")
|
|
43
|
+
async def get_auth_status(
|
|
44
|
+
request: Request,
|
|
45
|
+
user_auth: UserAuthService = Depends(get_user_auth_service),
|
|
46
|
+
settings: Settings = Depends(get_settings)
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Get authentication status.
|
|
50
|
+
Returns auth mode and current user if authenticated.
|
|
51
|
+
"""
|
|
52
|
+
status = user_auth.get_auth_status()
|
|
53
|
+
|
|
54
|
+
# Check if user has a valid session
|
|
55
|
+
token = request.cookies.get(settings.jwt_cookie_name)
|
|
56
|
+
current_user = None
|
|
57
|
+
|
|
58
|
+
if token:
|
|
59
|
+
user = await user_auth.get_current_user(token)
|
|
60
|
+
if user:
|
|
61
|
+
current_user = {
|
|
62
|
+
"id": user.id,
|
|
63
|
+
"email": user.email,
|
|
64
|
+
"display_name": user.display_name,
|
|
65
|
+
"is_owner": user.is_owner
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Check if registration is available
|
|
69
|
+
can_register = await user_auth.can_register()
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
"auth_mode": status["auth_mode"],
|
|
73
|
+
"authenticated": current_user is not None,
|
|
74
|
+
"user": current_user,
|
|
75
|
+
"can_register": can_register
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@router.post("/register")
|
|
80
|
+
async def register(
|
|
81
|
+
request: RegisterRequest,
|
|
82
|
+
response: Response,
|
|
83
|
+
user_auth: UserAuthService = Depends(get_user_auth_service),
|
|
84
|
+
settings: Settings = Depends(get_settings)
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
Register a new user.
|
|
88
|
+
In single-owner mode, only the first user can register.
|
|
89
|
+
In multi-user mode, anyone can register.
|
|
90
|
+
"""
|
|
91
|
+
user, error = await user_auth.register(
|
|
92
|
+
email=request.email,
|
|
93
|
+
password=request.password,
|
|
94
|
+
display_name=request.display_name
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if error:
|
|
98
|
+
raise HTTPException(status_code=400, detail=error)
|
|
99
|
+
|
|
100
|
+
# Create token and set cookie
|
|
101
|
+
token = user_auth.create_access_token(user)
|
|
102
|
+
response.set_cookie(
|
|
103
|
+
key=settings.jwt_cookie_name,
|
|
104
|
+
value=token,
|
|
105
|
+
httponly=True,
|
|
106
|
+
secure=settings.jwt_cookie_secure,
|
|
107
|
+
samesite=settings.jwt_cookie_samesite,
|
|
108
|
+
max_age=settings.jwt_expire_minutes * 60
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"success": True,
|
|
113
|
+
"user": {
|
|
114
|
+
"id": user.id,
|
|
115
|
+
"email": user.email,
|
|
116
|
+
"display_name": user.display_name,
|
|
117
|
+
"is_owner": user.is_owner
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@router.post("/login")
|
|
123
|
+
async def login(
|
|
124
|
+
request: LoginRequest,
|
|
125
|
+
response: Response,
|
|
126
|
+
user_auth: UserAuthService = Depends(get_user_auth_service),
|
|
127
|
+
settings: Settings = Depends(get_settings)
|
|
128
|
+
):
|
|
129
|
+
"""
|
|
130
|
+
Login with email and password.
|
|
131
|
+
Sets HttpOnly cookie with JWT token.
|
|
132
|
+
"""
|
|
133
|
+
user, error = await user_auth.login(
|
|
134
|
+
email=request.email,
|
|
135
|
+
password=request.password
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if error:
|
|
139
|
+
raise HTTPException(status_code=401, detail=error)
|
|
140
|
+
|
|
141
|
+
# Create token and set cookie
|
|
142
|
+
token = user_auth.create_access_token(user)
|
|
143
|
+
response.set_cookie(
|
|
144
|
+
key=settings.jwt_cookie_name,
|
|
145
|
+
value=token,
|
|
146
|
+
httponly=True,
|
|
147
|
+
secure=settings.jwt_cookie_secure,
|
|
148
|
+
samesite=settings.jwt_cookie_samesite,
|
|
149
|
+
max_age=settings.jwt_expire_minutes * 60
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"success": True,
|
|
154
|
+
"user": {
|
|
155
|
+
"id": user.id,
|
|
156
|
+
"email": user.email,
|
|
157
|
+
"display_name": user.display_name,
|
|
158
|
+
"is_owner": user.is_owner
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@router.post("/logout")
|
|
164
|
+
async def logout(
|
|
165
|
+
response: Response,
|
|
166
|
+
settings: Settings = Depends(get_settings)
|
|
167
|
+
):
|
|
168
|
+
"""
|
|
169
|
+
Logout by clearing the auth cookie.
|
|
170
|
+
"""
|
|
171
|
+
response.delete_cookie(
|
|
172
|
+
key=settings.jwt_cookie_name,
|
|
173
|
+
httponly=True,
|
|
174
|
+
secure=settings.jwt_cookie_secure,
|
|
175
|
+
samesite=settings.jwt_cookie_samesite
|
|
176
|
+
)
|
|
177
|
+
return {"success": True}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@router.get("/me")
|
|
181
|
+
async def get_current_user(
|
|
182
|
+
request: Request,
|
|
183
|
+
user_auth: UserAuthService = Depends(get_user_auth_service),
|
|
184
|
+
settings: Settings = Depends(get_settings)
|
|
185
|
+
):
|
|
186
|
+
"""
|
|
187
|
+
Get current authenticated user.
|
|
188
|
+
Requires valid session cookie.
|
|
189
|
+
"""
|
|
190
|
+
token = request.cookies.get(settings.jwt_cookie_name)
|
|
191
|
+
if not token:
|
|
192
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
193
|
+
|
|
194
|
+
user = await user_auth.get_current_user(token)
|
|
195
|
+
if not user:
|
|
196
|
+
raise HTTPException(status_code=401, detail="Invalid or expired session")
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
"id": user.id,
|
|
200
|
+
"email": user.email,
|
|
201
|
+
"display_name": user.display_name,
|
|
202
|
+
"is_owner": user.is_owner
|
|
203
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Database operations routes (replaces frontend storage)."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from typing import Dict, Any, Optional, List
|
|
6
|
+
|
|
7
|
+
from core.container import container
|
|
8
|
+
from core.database import Database
|
|
9
|
+
from core.logging import get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
router = APIRouter(prefix="/api/database", tags=["database"])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NodeParameterRequest(BaseModel):
|
|
16
|
+
node_id: str
|
|
17
|
+
parameters: Dict[str, Any]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkflowSaveRequest(BaseModel):
|
|
21
|
+
workflow_id: str
|
|
22
|
+
name: str
|
|
23
|
+
data: Dict[str, Any]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@router.post("/node-parameters")
|
|
27
|
+
async def save_node_parameters(
|
|
28
|
+
request: NodeParameterRequest,
|
|
29
|
+
database: Database = Depends(lambda: container.database())
|
|
30
|
+
):
|
|
31
|
+
"""Save node parameters (replaces frontend Dexie)."""
|
|
32
|
+
try:
|
|
33
|
+
success = await database.save_node_parameters(request.node_id, request.parameters)
|
|
34
|
+
return {"success": success}
|
|
35
|
+
except Exception as e:
|
|
36
|
+
logger.error("Failed to save node parameters", error=str(e))
|
|
37
|
+
return {"success": False, "error": str(e)}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.get("/node-parameters/{node_id}")
|
|
41
|
+
async def get_node_parameters(
|
|
42
|
+
node_id: str,
|
|
43
|
+
database: Database = Depends(lambda: container.database())
|
|
44
|
+
):
|
|
45
|
+
"""Get node parameters (replaces frontend Dexie)."""
|
|
46
|
+
try:
|
|
47
|
+
parameters = await database.get_node_parameters(node_id)
|
|
48
|
+
return {"success": True, "parameters": parameters}
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error("Failed to get node parameters", error=str(e))
|
|
51
|
+
return {"success": False, "error": str(e)}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@router.delete("/node-parameters/{node_id}")
|
|
55
|
+
async def delete_node_parameters(
|
|
56
|
+
node_id: str,
|
|
57
|
+
database: Database = Depends(lambda: container.database())
|
|
58
|
+
):
|
|
59
|
+
"""Delete node parameters (replaces frontend Dexie)."""
|
|
60
|
+
try:
|
|
61
|
+
success = await database.delete_node_parameters(node_id)
|
|
62
|
+
return {"success": success}
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.error("Failed to delete node parameters", error=str(e))
|
|
65
|
+
return {"success": False, "error": str(e)}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ============================================================================
|
|
69
|
+
# Workflow Operations
|
|
70
|
+
# ============================================================================
|
|
71
|
+
|
|
72
|
+
@router.post("/workflows")
|
|
73
|
+
async def save_workflow(
|
|
74
|
+
request: WorkflowSaveRequest,
|
|
75
|
+
database: Database = Depends(lambda: container.database())
|
|
76
|
+
):
|
|
77
|
+
"""Save workflow to database."""
|
|
78
|
+
try:
|
|
79
|
+
success = await database.save_workflow(
|
|
80
|
+
workflow_id=request.workflow_id,
|
|
81
|
+
name=request.name,
|
|
82
|
+
data=request.data
|
|
83
|
+
)
|
|
84
|
+
return {"success": success, "workflow_id": request.workflow_id}
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.error("Failed to save workflow", error=str(e))
|
|
87
|
+
return {"success": False, "error": str(e)}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@router.get("/workflows")
|
|
91
|
+
async def get_all_workflows(
|
|
92
|
+
database: Database = Depends(lambda: container.database())
|
|
93
|
+
):
|
|
94
|
+
"""Get all workflows."""
|
|
95
|
+
try:
|
|
96
|
+
workflows = await database.get_all_workflows()
|
|
97
|
+
return {
|
|
98
|
+
"success": True,
|
|
99
|
+
"workflows": [
|
|
100
|
+
{
|
|
101
|
+
"id": w.id,
|
|
102
|
+
"name": w.name,
|
|
103
|
+
"nodeCount": len(w.data.get("nodes", [])) if w.data else 0,
|
|
104
|
+
"createdAt": w.created_at.isoformat() if w.created_at else None,
|
|
105
|
+
"lastModified": w.updated_at.isoformat() if w.updated_at else None
|
|
106
|
+
}
|
|
107
|
+
for w in workflows
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error("Failed to get workflows", error=str(e))
|
|
112
|
+
return {"success": False, "error": str(e)}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@router.get("/workflows/{workflow_id}")
|
|
116
|
+
async def get_workflow(
|
|
117
|
+
workflow_id: str,
|
|
118
|
+
database: Database = Depends(lambda: container.database())
|
|
119
|
+
):
|
|
120
|
+
"""Get workflow by ID."""
|
|
121
|
+
try:
|
|
122
|
+
workflow = await database.get_workflow(workflow_id)
|
|
123
|
+
if workflow:
|
|
124
|
+
return {
|
|
125
|
+
"success": True,
|
|
126
|
+
"workflow": {
|
|
127
|
+
"id": workflow.id,
|
|
128
|
+
"name": workflow.name,
|
|
129
|
+
"data": workflow.data,
|
|
130
|
+
"createdAt": workflow.created_at.isoformat() if workflow.created_at else None,
|
|
131
|
+
"lastModified": workflow.updated_at.isoformat() if workflow.updated_at else None
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return {"success": False, "error": "Workflow not found"}
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error("Failed to get workflow", error=str(e))
|
|
137
|
+
return {"success": False, "error": str(e)}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@router.delete("/workflows/{workflow_id}")
|
|
141
|
+
async def delete_workflow(
|
|
142
|
+
workflow_id: str,
|
|
143
|
+
database: Database = Depends(lambda: container.database())
|
|
144
|
+
):
|
|
145
|
+
"""Delete workflow."""
|
|
146
|
+
try:
|
|
147
|
+
success = await database.delete_workflow(workflow_id)
|
|
148
|
+
return {"success": success, "workflow_id": workflow_id}
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logger.error("Failed to delete workflow", error=str(e))
|
|
151
|
+
return {"success": False, "error": str(e)}
|