flowyml 1.1.0__py3-none-any.whl
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.
- flowyml/__init__.py +207 -0
- flowyml/assets/__init__.py +22 -0
- flowyml/assets/artifact.py +40 -0
- flowyml/assets/base.py +209 -0
- flowyml/assets/dataset.py +100 -0
- flowyml/assets/featureset.py +301 -0
- flowyml/assets/metrics.py +104 -0
- flowyml/assets/model.py +82 -0
- flowyml/assets/registry.py +157 -0
- flowyml/assets/report.py +315 -0
- flowyml/cli/__init__.py +5 -0
- flowyml/cli/experiment.py +232 -0
- flowyml/cli/init.py +256 -0
- flowyml/cli/main.py +327 -0
- flowyml/cli/run.py +75 -0
- flowyml/cli/stack_cli.py +532 -0
- flowyml/cli/ui.py +33 -0
- flowyml/core/__init__.py +68 -0
- flowyml/core/advanced_cache.py +274 -0
- flowyml/core/approval.py +64 -0
- flowyml/core/cache.py +203 -0
- flowyml/core/checkpoint.py +148 -0
- flowyml/core/conditional.py +373 -0
- flowyml/core/context.py +155 -0
- flowyml/core/error_handling.py +419 -0
- flowyml/core/executor.py +354 -0
- flowyml/core/graph.py +185 -0
- flowyml/core/parallel.py +452 -0
- flowyml/core/pipeline.py +764 -0
- flowyml/core/project.py +253 -0
- flowyml/core/resources.py +424 -0
- flowyml/core/scheduler.py +630 -0
- flowyml/core/scheduler_config.py +32 -0
- flowyml/core/step.py +201 -0
- flowyml/core/step_grouping.py +292 -0
- flowyml/core/templates.py +226 -0
- flowyml/core/versioning.py +217 -0
- flowyml/integrations/__init__.py +1 -0
- flowyml/integrations/keras.py +134 -0
- flowyml/monitoring/__init__.py +1 -0
- flowyml/monitoring/alerts.py +57 -0
- flowyml/monitoring/data.py +102 -0
- flowyml/monitoring/llm.py +160 -0
- flowyml/monitoring/monitor.py +57 -0
- flowyml/monitoring/notifications.py +246 -0
- flowyml/registry/__init__.py +5 -0
- flowyml/registry/model_registry.py +491 -0
- flowyml/registry/pipeline_registry.py +55 -0
- flowyml/stacks/__init__.py +27 -0
- flowyml/stacks/base.py +77 -0
- flowyml/stacks/bridge.py +288 -0
- flowyml/stacks/components.py +155 -0
- flowyml/stacks/gcp.py +499 -0
- flowyml/stacks/local.py +112 -0
- flowyml/stacks/migration.py +97 -0
- flowyml/stacks/plugin_config.py +78 -0
- flowyml/stacks/plugins.py +401 -0
- flowyml/stacks/registry.py +226 -0
- flowyml/storage/__init__.py +26 -0
- flowyml/storage/artifacts.py +246 -0
- flowyml/storage/materializers/__init__.py +20 -0
- flowyml/storage/materializers/base.py +133 -0
- flowyml/storage/materializers/keras.py +185 -0
- flowyml/storage/materializers/numpy.py +94 -0
- flowyml/storage/materializers/pandas.py +142 -0
- flowyml/storage/materializers/pytorch.py +135 -0
- flowyml/storage/materializers/sklearn.py +110 -0
- flowyml/storage/materializers/tensorflow.py +152 -0
- flowyml/storage/metadata.py +931 -0
- flowyml/tracking/__init__.py +1 -0
- flowyml/tracking/experiment.py +211 -0
- flowyml/tracking/leaderboard.py +191 -0
- flowyml/tracking/runs.py +145 -0
- flowyml/ui/__init__.py +15 -0
- flowyml/ui/backend/Dockerfile +31 -0
- flowyml/ui/backend/__init__.py +0 -0
- flowyml/ui/backend/auth.py +163 -0
- flowyml/ui/backend/main.py +187 -0
- flowyml/ui/backend/routers/__init__.py +0 -0
- flowyml/ui/backend/routers/assets.py +45 -0
- flowyml/ui/backend/routers/execution.py +179 -0
- flowyml/ui/backend/routers/experiments.py +49 -0
- flowyml/ui/backend/routers/leaderboard.py +118 -0
- flowyml/ui/backend/routers/notifications.py +72 -0
- flowyml/ui/backend/routers/pipelines.py +110 -0
- flowyml/ui/backend/routers/plugins.py +192 -0
- flowyml/ui/backend/routers/projects.py +85 -0
- flowyml/ui/backend/routers/runs.py +66 -0
- flowyml/ui/backend/routers/schedules.py +222 -0
- flowyml/ui/backend/routers/traces.py +84 -0
- flowyml/ui/frontend/Dockerfile +20 -0
- flowyml/ui/frontend/README.md +315 -0
- flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +448 -0
- flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +1 -0
- flowyml/ui/frontend/dist/index.html +16 -0
- flowyml/ui/frontend/index.html +15 -0
- flowyml/ui/frontend/nginx.conf +26 -0
- flowyml/ui/frontend/package-lock.json +3545 -0
- flowyml/ui/frontend/package.json +33 -0
- flowyml/ui/frontend/postcss.config.js +6 -0
- flowyml/ui/frontend/src/App.jsx +21 -0
- flowyml/ui/frontend/src/app/assets/page.jsx +397 -0
- flowyml/ui/frontend/src/app/dashboard/page.jsx +295 -0
- flowyml/ui/frontend/src/app/experiments/[experimentId]/page.jsx +255 -0
- flowyml/ui/frontend/src/app/experiments/page.jsx +360 -0
- flowyml/ui/frontend/src/app/leaderboard/page.jsx +133 -0
- flowyml/ui/frontend/src/app/pipelines/page.jsx +454 -0
- flowyml/ui/frontend/src/app/plugins/page.jsx +48 -0
- flowyml/ui/frontend/src/app/projects/page.jsx +292 -0
- flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +682 -0
- flowyml/ui/frontend/src/app/runs/page.jsx +470 -0
- flowyml/ui/frontend/src/app/schedules/page.jsx +585 -0
- flowyml/ui/frontend/src/app/settings/page.jsx +314 -0
- flowyml/ui/frontend/src/app/tokens/page.jsx +456 -0
- flowyml/ui/frontend/src/app/traces/page.jsx +246 -0
- flowyml/ui/frontend/src/components/Layout.jsx +108 -0
- flowyml/ui/frontend/src/components/PipelineGraph.jsx +295 -0
- flowyml/ui/frontend/src/components/header/Header.jsx +72 -0
- flowyml/ui/frontend/src/components/plugins/AddPluginDialog.jsx +121 -0
- flowyml/ui/frontend/src/components/plugins/InstalledPlugins.jsx +124 -0
- flowyml/ui/frontend/src/components/plugins/PluginBrowser.jsx +167 -0
- flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +60 -0
- flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +145 -0
- flowyml/ui/frontend/src/components/ui/Badge.jsx +26 -0
- flowyml/ui/frontend/src/components/ui/Button.jsx +34 -0
- flowyml/ui/frontend/src/components/ui/Card.jsx +44 -0
- flowyml/ui/frontend/src/components/ui/CodeSnippet.jsx +38 -0
- flowyml/ui/frontend/src/components/ui/CollapsibleCard.jsx +53 -0
- flowyml/ui/frontend/src/components/ui/DataView.jsx +175 -0
- flowyml/ui/frontend/src/components/ui/EmptyState.jsx +49 -0
- flowyml/ui/frontend/src/components/ui/ExecutionStatus.jsx +122 -0
- flowyml/ui/frontend/src/components/ui/KeyValue.jsx +25 -0
- flowyml/ui/frontend/src/components/ui/ProjectSelector.jsx +134 -0
- flowyml/ui/frontend/src/contexts/ProjectContext.jsx +79 -0
- flowyml/ui/frontend/src/contexts/ThemeContext.jsx +54 -0
- flowyml/ui/frontend/src/index.css +11 -0
- flowyml/ui/frontend/src/layouts/MainLayout.jsx +23 -0
- flowyml/ui/frontend/src/main.jsx +10 -0
- flowyml/ui/frontend/src/router/index.jsx +39 -0
- flowyml/ui/frontend/src/services/pluginService.js +90 -0
- flowyml/ui/frontend/src/utils/api.js +47 -0
- flowyml/ui/frontend/src/utils/cn.js +6 -0
- flowyml/ui/frontend/tailwind.config.js +31 -0
- flowyml/ui/frontend/vite.config.js +21 -0
- flowyml/ui/utils.py +77 -0
- flowyml/utils/__init__.py +67 -0
- flowyml/utils/config.py +308 -0
- flowyml/utils/debug.py +240 -0
- flowyml/utils/environment.py +346 -0
- flowyml/utils/git.py +319 -0
- flowyml/utils/logging.py +61 -0
- flowyml/utils/performance.py +314 -0
- flowyml/utils/stack_config.py +296 -0
- flowyml/utils/validation.py +270 -0
- flowyml-1.1.0.dist-info/METADATA +372 -0
- flowyml-1.1.0.dist-info/RECORD +159 -0
- flowyml-1.1.0.dist-info/WHEEL +4 -0
- flowyml-1.1.0.dist-info/entry_points.txt +3 -0
- flowyml-1.1.0.dist-info/licenses/LICENSE +17 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from flowyml.core.scheduler import PipelineScheduler
|
|
4
|
+
from flowyml.registry.pipeline_registry import pipeline_registry
|
|
5
|
+
|
|
6
|
+
router = APIRouter()
|
|
7
|
+
# Note: In a real app, the scheduler instance should be a singleton managed by the app state
|
|
8
|
+
# For now, we instantiate it here, but it might not persist state across reloads if not handled carefully.
|
|
9
|
+
# Ideally, the scheduler is started when the backend starts.
|
|
10
|
+
scheduler = PipelineScheduler()
|
|
11
|
+
scheduler.start() # Start the scheduler thread
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ScheduleRequest(BaseModel):
|
|
15
|
+
name: str
|
|
16
|
+
pipeline_name: str
|
|
17
|
+
schedule_type: str # 'daily', 'hourly', 'interval', 'cron'
|
|
18
|
+
hour: int | None = 0
|
|
19
|
+
minute: int | None = 0
|
|
20
|
+
interval_seconds: int | None = 0
|
|
21
|
+
cron_expression: str | None = None
|
|
22
|
+
timezone: str = "UTC"
|
|
23
|
+
project_name: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@router.get("/")
|
|
27
|
+
async def list_schedules():
|
|
28
|
+
"""List all active schedules."""
|
|
29
|
+
# Convert Schedule objects to dicts for JSON serialization
|
|
30
|
+
schedules = scheduler.list_schedules()
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
"pipeline_name": s.pipeline_name,
|
|
34
|
+
"schedule_type": s.schedule_type,
|
|
35
|
+
"schedule_value": s.schedule_value,
|
|
36
|
+
"enabled": s.enabled,
|
|
37
|
+
"last_run": s.last_run,
|
|
38
|
+
"next_run": s.next_run,
|
|
39
|
+
"timezone": s.timezone,
|
|
40
|
+
}
|
|
41
|
+
for s in schedules
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.get("/health")
|
|
46
|
+
async def get_scheduler_health():
|
|
47
|
+
"""Get scheduler health metrics."""
|
|
48
|
+
return scheduler.health_check()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@router.post("/")
|
|
52
|
+
async def create_schedule(schedule: ScheduleRequest):
|
|
53
|
+
"""Create a new schedule."""
|
|
54
|
+
# 1. Look up pipeline factory
|
|
55
|
+
pipeline_factory = pipeline_registry.get(schedule.pipeline_name)
|
|
56
|
+
|
|
57
|
+
if not pipeline_factory:
|
|
58
|
+
# Try to see if it's a template
|
|
59
|
+
from flowyml.core.templates import TEMPLATES, create_from_template
|
|
60
|
+
|
|
61
|
+
if schedule.pipeline_name in TEMPLATES:
|
|
62
|
+
# For templates, we need a way to instantiate them with default args or args provided in request
|
|
63
|
+
# This is a simplification for the demo
|
|
64
|
+
def template_wrapper():
|
|
65
|
+
# Create and run template pipeline
|
|
66
|
+
# Note: This assumes default args work. In reality, we'd need more config.
|
|
67
|
+
p = create_from_template(schedule.pipeline_name)
|
|
68
|
+
return p.run()
|
|
69
|
+
|
|
70
|
+
pipeline_func = template_wrapper
|
|
71
|
+
else:
|
|
72
|
+
# Check if it's a historical pipeline (in metadata but not registry)
|
|
73
|
+
# This means we can't run it because we don't have the code loaded
|
|
74
|
+
from flowyml.storage.metadata import SQLiteMetadataStore
|
|
75
|
+
|
|
76
|
+
store = SQLiteMetadataStore()
|
|
77
|
+
pipelines = store.list_pipelines()
|
|
78
|
+
|
|
79
|
+
if schedule.pipeline_name in pipelines:
|
|
80
|
+
# Try to load pipeline definition
|
|
81
|
+
from flowyml.storage.metadata import SQLiteMetadataStore
|
|
82
|
+
|
|
83
|
+
store = SQLiteMetadataStore()
|
|
84
|
+
pipeline_def = store.get_pipeline_definition(schedule.pipeline_name)
|
|
85
|
+
|
|
86
|
+
if pipeline_def:
|
|
87
|
+
# Reconstruct pipeline from definition
|
|
88
|
+
from flowyml.core.pipeline import Pipeline
|
|
89
|
+
from flowyml.core.context import Context
|
|
90
|
+
|
|
91
|
+
def pipeline_wrapper():
|
|
92
|
+
# Reconstruct pipeline each time
|
|
93
|
+
p = Pipeline.from_definition(pipeline_def, Context())
|
|
94
|
+
return p.run()
|
|
95
|
+
|
|
96
|
+
pipeline_func = pipeline_wrapper
|
|
97
|
+
else:
|
|
98
|
+
raise HTTPException(
|
|
99
|
+
status_code=400,
|
|
100
|
+
detail=f"Pipeline '{schedule.pipeline_name}' found in history but no definition stored. Please run the pipeline again to enable scheduling.",
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
raise HTTPException(
|
|
104
|
+
status_code=404,
|
|
105
|
+
detail=f"Pipeline '{schedule.pipeline_name}' not found in registry. Please register it using @register_pipeline.",
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
# Create a wrapper to instantiate and run the pipeline
|
|
109
|
+
def pipeline_wrapper():
|
|
110
|
+
# Instantiate fresh pipeline for each run
|
|
111
|
+
p = pipeline_factory()
|
|
112
|
+
return p.run()
|
|
113
|
+
|
|
114
|
+
pipeline_func = pipeline_wrapper
|
|
115
|
+
|
|
116
|
+
# 2. Schedule it
|
|
117
|
+
try:
|
|
118
|
+
if schedule.schedule_type == "daily":
|
|
119
|
+
scheduler.schedule_daily(
|
|
120
|
+
name=schedule.name,
|
|
121
|
+
pipeline_func=pipeline_func,
|
|
122
|
+
hour=schedule.hour,
|
|
123
|
+
minute=schedule.minute,
|
|
124
|
+
timezone=schedule.timezone,
|
|
125
|
+
)
|
|
126
|
+
elif schedule.schedule_type == "hourly":
|
|
127
|
+
scheduler.schedule_hourly(
|
|
128
|
+
name=schedule.name,
|
|
129
|
+
pipeline_func=pipeline_func,
|
|
130
|
+
minute=schedule.minute,
|
|
131
|
+
timezone=schedule.timezone,
|
|
132
|
+
)
|
|
133
|
+
elif schedule.schedule_type == "interval":
|
|
134
|
+
scheduler.schedule_interval(
|
|
135
|
+
name=schedule.name,
|
|
136
|
+
pipeline_func=pipeline_func,
|
|
137
|
+
seconds=schedule.interval_seconds,
|
|
138
|
+
timezone=schedule.timezone,
|
|
139
|
+
)
|
|
140
|
+
elif schedule.schedule_type == "cron":
|
|
141
|
+
if not schedule.cron_expression:
|
|
142
|
+
raise HTTPException(status_code=400, detail="Cron expression required for cron schedule")
|
|
143
|
+
scheduler.schedule_cron(
|
|
144
|
+
name=schedule.name,
|
|
145
|
+
pipeline_func=pipeline_func,
|
|
146
|
+
cron_expression=schedule.cron_expression,
|
|
147
|
+
timezone=schedule.timezone,
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
raise HTTPException(status_code=400, detail="Invalid schedule type")
|
|
151
|
+
|
|
152
|
+
return {"status": "success", "message": f"Scheduled '{schedule.name}'"}
|
|
153
|
+
|
|
154
|
+
except ImportError as e:
|
|
155
|
+
raise HTTPException(status_code=501, detail=str(e))
|
|
156
|
+
except Exception as e:
|
|
157
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@router.delete("/{schedule_name}")
|
|
161
|
+
async def delete_schedule(schedule_name: str):
|
|
162
|
+
"""Remove a schedule."""
|
|
163
|
+
scheduler.unschedule(schedule_name)
|
|
164
|
+
return {"status": "success", "message": f"Schedule {schedule_name} removed"}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@router.post("/{schedule_name}/enable")
|
|
168
|
+
async def enable_schedule(schedule_name: str):
|
|
169
|
+
"""Enable a schedule."""
|
|
170
|
+
scheduler.enable(schedule_name)
|
|
171
|
+
return {"status": "success"}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@router.post("/{schedule_name}/disable")
|
|
175
|
+
async def disable_schedule(schedule_name: str):
|
|
176
|
+
"""Disable a schedule."""
|
|
177
|
+
scheduler.disable(schedule_name)
|
|
178
|
+
return {"status": "success"}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@router.get("/{schedule_name}/history")
|
|
182
|
+
async def get_schedule_history(schedule_name: str, limit: int = 50):
|
|
183
|
+
"""Get execution history for a schedule."""
|
|
184
|
+
return scheduler.get_history(schedule_name, limit)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@router.get("/registered-pipelines")
|
|
188
|
+
async def list_registered_pipelines(project: str = None):
|
|
189
|
+
"""List all pipelines available for scheduling."""
|
|
190
|
+
from flowyml.core.templates import list_templates
|
|
191
|
+
from flowyml.storage.metadata import SQLiteMetadataStore
|
|
192
|
+
|
|
193
|
+
registered = pipeline_registry.list_pipelines()
|
|
194
|
+
templates = list_templates()
|
|
195
|
+
|
|
196
|
+
# Also get pipelines from metadata store (historical runs)
|
|
197
|
+
metadata_pipelines = []
|
|
198
|
+
try:
|
|
199
|
+
store = SQLiteMetadataStore()
|
|
200
|
+
import sqlite3
|
|
201
|
+
|
|
202
|
+
conn = sqlite3.connect(store.db_path)
|
|
203
|
+
cursor = conn.cursor()
|
|
204
|
+
|
|
205
|
+
if project:
|
|
206
|
+
cursor.execute(
|
|
207
|
+
"SELECT DISTINCT pipeline_name FROM runs WHERE project = ? ORDER BY pipeline_name",
|
|
208
|
+
(project,),
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
cursor.execute("SELECT DISTINCT pipeline_name FROM runs ORDER BY pipeline_name")
|
|
212
|
+
|
|
213
|
+
metadata_pipelines = [row[0] for row in cursor.fetchall()]
|
|
214
|
+
conn.close()
|
|
215
|
+
except Exception as e:
|
|
216
|
+
print(f"Failed to fetch pipelines from metadata store: {e}")
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
"registered": list(registered.keys()),
|
|
220
|
+
"templates": templates,
|
|
221
|
+
"metadata": metadata_pipelines,
|
|
222
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException
|
|
2
|
+
from flowyml.storage.metadata import SQLiteMetadataStore
|
|
3
|
+
import contextlib
|
|
4
|
+
import builtins
|
|
5
|
+
|
|
6
|
+
router = APIRouter()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@router.get("/")
|
|
10
|
+
async def list_traces(
|
|
11
|
+
limit: int = 50,
|
|
12
|
+
trace_id: str | None = None,
|
|
13
|
+
event_type: str | None = None,
|
|
14
|
+
project: str | None = None,
|
|
15
|
+
):
|
|
16
|
+
"""List traces, optionally filtered by project."""
|
|
17
|
+
store = SQLiteMetadataStore()
|
|
18
|
+
|
|
19
|
+
# We need to implement list_traces in metadata store or query manually
|
|
20
|
+
# For now, let's query manually via sqlite
|
|
21
|
+
import sqlite3
|
|
22
|
+
|
|
23
|
+
conn = sqlite3.connect(store.db_path)
|
|
24
|
+
cursor = conn.cursor()
|
|
25
|
+
|
|
26
|
+
query = "SELECT * FROM traces"
|
|
27
|
+
params = []
|
|
28
|
+
conditions = []
|
|
29
|
+
|
|
30
|
+
if trace_id:
|
|
31
|
+
conditions.append("trace_id = ?")
|
|
32
|
+
params.append(trace_id)
|
|
33
|
+
|
|
34
|
+
if event_type:
|
|
35
|
+
conditions.append("event_type = ?")
|
|
36
|
+
params.append(event_type)
|
|
37
|
+
|
|
38
|
+
if project:
|
|
39
|
+
conditions.append("project = ?")
|
|
40
|
+
params.append(project)
|
|
41
|
+
|
|
42
|
+
if conditions:
|
|
43
|
+
query += " WHERE " + " AND ".join(conditions)
|
|
44
|
+
|
|
45
|
+
query += " ORDER BY start_time DESC LIMIT ?"
|
|
46
|
+
params.append(limit)
|
|
47
|
+
|
|
48
|
+
cursor.execute(query, params)
|
|
49
|
+
columns = [description[0] for description in cursor.description]
|
|
50
|
+
rows = cursor.fetchall()
|
|
51
|
+
|
|
52
|
+
traces = []
|
|
53
|
+
import json
|
|
54
|
+
|
|
55
|
+
for row in rows:
|
|
56
|
+
trace = dict(zip(columns, row, strict=False))
|
|
57
|
+
# Parse JSON fields
|
|
58
|
+
for field in ["inputs", "outputs", "metadata"]:
|
|
59
|
+
if trace[field]:
|
|
60
|
+
with contextlib.suppress(builtins.BaseException):
|
|
61
|
+
trace[field] = json.loads(trace[field])
|
|
62
|
+
traces.append(trace)
|
|
63
|
+
|
|
64
|
+
conn.close()
|
|
65
|
+
return traces
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@router.get("/{trace_id}")
|
|
69
|
+
async def get_trace(trace_id: str):
|
|
70
|
+
"""Get a specific trace tree."""
|
|
71
|
+
store = SQLiteMetadataStore()
|
|
72
|
+
events = store.get_trace(trace_id)
|
|
73
|
+
if not events:
|
|
74
|
+
raise HTTPException(status_code=404, detail="Trace not found")
|
|
75
|
+
|
|
76
|
+
# Reconstruct tree
|
|
77
|
+
root_events = [e for e in events if not e["parent_id"]]
|
|
78
|
+
|
|
79
|
+
def build_tree(event):
|
|
80
|
+
children = [e for e in events if e["parent_id"] == event["event_id"]]
|
|
81
|
+
event["children"] = [build_tree(child) for child in children]
|
|
82
|
+
return event
|
|
83
|
+
|
|
84
|
+
return [build_tree(root) for root in root_events]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Build stage
|
|
2
|
+
FROM node:18-alpine as build
|
|
3
|
+
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
COPY package.json package-lock.json ./
|
|
7
|
+
RUN npm ci
|
|
8
|
+
|
|
9
|
+
COPY . .
|
|
10
|
+
RUN npm run build
|
|
11
|
+
|
|
12
|
+
# Serve stage
|
|
13
|
+
FROM nginx:alpine
|
|
14
|
+
|
|
15
|
+
COPY --from=build /app/dist /usr/share/nginx/html
|
|
16
|
+
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
17
|
+
|
|
18
|
+
EXPOSE 80
|
|
19
|
+
|
|
20
|
+
CMD ["nginx", "-g", "daemon off;"]
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# flowyml UI Frontend
|
|
2
|
+
|
|
3
|
+
Modern React-based frontend for the flowyml ML pipeline orchestration framework.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
- **Node.js**: >= 16.x (recommended: 18.x or 20.x)
|
|
10
|
+
- **npm**: >= 8.x (comes with Node.js)
|
|
11
|
+
|
|
12
|
+
### Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Install dependencies
|
|
16
|
+
npm install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Development
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Start development server with hot reload
|
|
23
|
+
npm run dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This will start the Vite dev server on `http://localhost:5173`. The dev server will:
|
|
27
|
+
- Hot reload on file changes
|
|
28
|
+
- Proxy API requests to `http://localhost:8080` (backend)
|
|
29
|
+
- Provide fast HMR (Hot Module Replacement)
|
|
30
|
+
|
|
31
|
+
**Note**: Make sure the backend is running before starting the frontend:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# In another terminal, start the backend
|
|
35
|
+
cd ../../.. # Back to repo root
|
|
36
|
+
flowyml ui start --port 8080
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Production Build
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Build for production
|
|
43
|
+
npm run build
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Production artifacts will be in the `dist/` directory. The flowyml Python backend automatically serves these files when available.
|
|
47
|
+
|
|
48
|
+
## 📁 Project Structure
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
frontend/
|
|
52
|
+
├── src/
|
|
53
|
+
│ ├── components/ # Reusable UI components
|
|
54
|
+
│ ├── pages/ # Page-level components
|
|
55
|
+
│ ├── hooks/ # Custom React hooks
|
|
56
|
+
│ ├── utils/ # Utility functions
|
|
57
|
+
│ ├── api/ # API client functions
|
|
58
|
+
│ ├── types/ # TypeScript type definitions
|
|
59
|
+
│ ├── styles/ # Global styles and themes
|
|
60
|
+
│ ├── App.tsx # Main app component
|
|
61
|
+
│ └── main.tsx # Application entry point
|
|
62
|
+
├── public/ # Static assets
|
|
63
|
+
├── dist/ # Production build output (generated)
|
|
64
|
+
├── index.html # HTML entry point
|
|
65
|
+
├── package.json # Dependencies and scripts
|
|
66
|
+
├── vite.config.ts # Vite configuration
|
|
67
|
+
├── tsconfig.json # TypeScript configuration
|
|
68
|
+
└── README.md # This file
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 🛠️ Available Scripts
|
|
72
|
+
|
|
73
|
+
### `npm run dev`
|
|
74
|
+
|
|
75
|
+
Starts the development server with hot reload.
|
|
76
|
+
|
|
77
|
+
- **URL**: http://localhost:5173
|
|
78
|
+
- **Features**: HMR, fast refresh, TypeScript checking
|
|
79
|
+
- **API Proxy**: Proxies `/api/*` to backend at `localhost:8080`
|
|
80
|
+
|
|
81
|
+
### `npm run build`
|
|
82
|
+
|
|
83
|
+
Builds the app for production to the `dist/` folder.
|
|
84
|
+
|
|
85
|
+
- Optimized for performance
|
|
86
|
+
- Minified and bundled
|
|
87
|
+
- TypeScript type checking
|
|
88
|
+
- Ready to be served by the Python backend
|
|
89
|
+
|
|
90
|
+
### `npm run preview`
|
|
91
|
+
|
|
92
|
+
Preview the production build locally:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm run build
|
|
96
|
+
npm run preview
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Serves the production build at `http://localhost:4173`.
|
|
100
|
+
|
|
101
|
+
### `npm run lint`
|
|
102
|
+
|
|
103
|
+
Run ESLint to check code quality:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm run lint
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `npm run type-check`
|
|
110
|
+
|
|
111
|
+
Run TypeScript type checking without building:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run type-check
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 🎨 Tech Stack
|
|
118
|
+
|
|
119
|
+
- **Framework**: React 18
|
|
120
|
+
- **Build Tool**: Vite
|
|
121
|
+
- **Language**: TypeScript
|
|
122
|
+
- **Styling**: CSS Modules / Tailwind CSS (if configured)
|
|
123
|
+
- **State Management**: React Context / Zustand (if needed)
|
|
124
|
+
- **Routing**: React Router (if multi-page)
|
|
125
|
+
- **API Client**: Fetch API / Axios
|
|
126
|
+
|
|
127
|
+
## 🔌 API Integration
|
|
128
|
+
|
|
129
|
+
The frontend communicates with the flowyml backend via REST API:
|
|
130
|
+
|
|
131
|
+
### Development Mode
|
|
132
|
+
|
|
133
|
+
In development, Vite proxies API requests to avoid CORS issues:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// vite.config.ts
|
|
137
|
+
export default defineConfig({
|
|
138
|
+
server: {
|
|
139
|
+
proxy: {
|
|
140
|
+
'/api': {
|
|
141
|
+
target: 'http://localhost:8080',
|
|
142
|
+
changeOrigin: true
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Production Mode
|
|
150
|
+
|
|
151
|
+
In production, the frontend is served by the Python backend from the `dist/` folder, so all API requests naturally go to the same origin.
|
|
152
|
+
|
|
153
|
+
### Example API Usage
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// src/api/pipelines.ts
|
|
157
|
+
export async function getPipelines() {
|
|
158
|
+
const response = await fetch('/api/pipelines');
|
|
159
|
+
return response.json();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function getPipeline(id: string) {
|
|
163
|
+
const response = await fetch(`/api/pipelines/${id}`);
|
|
164
|
+
return response.json();
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 🏗️ Development Workflow
|
|
169
|
+
|
|
170
|
+
### 1. Setup (First Time)
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Clone the repo and navigate to frontend
|
|
174
|
+
cd flowyml/ui/frontend
|
|
175
|
+
|
|
176
|
+
# Install dependencies
|
|
177
|
+
npm install
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 2. Development
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Terminal 1: Start backend
|
|
184
|
+
cd ../../..
|
|
185
|
+
flowyml ui start --dev
|
|
186
|
+
|
|
187
|
+
# Terminal 2: Start frontend
|
|
188
|
+
cd flowyml/ui/frontend
|
|
189
|
+
npm run dev
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 3. Making Changes
|
|
193
|
+
|
|
194
|
+
1. Edit files in `src/`
|
|
195
|
+
2. Hot reload will update the browser automatically
|
|
196
|
+
3. Check console for errors
|
|
197
|
+
4. Test API integration with backend running
|
|
198
|
+
|
|
199
|
+
### 4. Building for Production
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Build frontend
|
|
203
|
+
npm run build
|
|
204
|
+
|
|
205
|
+
# Test the backend serves it correctly
|
|
206
|
+
cd ../../..
|
|
207
|
+
flowyml ui start
|
|
208
|
+
# Visit http://localhost:8080
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## 🐛 Troubleshooting
|
|
212
|
+
|
|
213
|
+
### Issue: `npm install` fails
|
|
214
|
+
|
|
215
|
+
**Solution**: Make sure you have Node.js 16+ installed:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
node --version # Should be >= 16.x
|
|
219
|
+
npm --version # Should be >= 8.x
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Issue: API calls return 404
|
|
223
|
+
|
|
224
|
+
**Problem**: Backend not running or wrong port.
|
|
225
|
+
|
|
226
|
+
**Solution**:
|
|
227
|
+
1. Start the backend: `flowyml ui start --port 8080`
|
|
228
|
+
2. Check proxy config in `vite.config.ts`
|
|
229
|
+
3. Verify backend is on `http://localhost:8080/api/health`
|
|
230
|
+
|
|
231
|
+
### Issue: Hot reload not working
|
|
232
|
+
|
|
233
|
+
**Solution**:
|
|
234
|
+
1. Make sure you're running `npm run dev` (not `npm run build`)
|
|
235
|
+
2. Check for errors in the terminal
|
|
236
|
+
3. Try clearing Vite cache: `rm -rf node_modules/.vite`
|
|
237
|
+
|
|
238
|
+
### Issue: Build succeeds but backend shows "Frontend not built"
|
|
239
|
+
|
|
240
|
+
**Cause**: Backend looking in wrong directory.
|
|
241
|
+
|
|
242
|
+
**Solution**: Ensure the `dist/` folder exists in `flowyml/ui/frontend/dist/`
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Check if dist exists
|
|
246
|
+
ls -la dist/
|
|
247
|
+
|
|
248
|
+
# If not, rebuild
|
|
249
|
+
npm run build
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## 🎯 Key Features to Implement
|
|
253
|
+
|
|
254
|
+
The frontend should provide:
|
|
255
|
+
|
|
256
|
+
1. **Dashboard**
|
|
257
|
+
- Overview of recent pipeline runs
|
|
258
|
+
- Active/running pipelines
|
|
259
|
+
- Quick stats (success rate, avg duration)
|
|
260
|
+
|
|
261
|
+
2. **Pipeline List**
|
|
262
|
+
- All registered pipelines
|
|
263
|
+
- Search and filter
|
|
264
|
+
- Click to view details
|
|
265
|
+
|
|
266
|
+
3. **Pipeline Details**
|
|
267
|
+
- DAG visualization
|
|
268
|
+
- Step-by-step execution status
|
|
269
|
+
- Logs per step
|
|
270
|
+
- Artifacts produced
|
|
271
|
+
|
|
272
|
+
4. **Run History**
|
|
273
|
+
- List of all runs
|
|
274
|
+
- Filter by pipeline, status, date
|
|
275
|
+
- Compare runs side-by-side
|
|
276
|
+
|
|
277
|
+
5. **Asset Explorer**
|
|
278
|
+
- Browse datasets, models, metrics
|
|
279
|
+
- View lineage graph
|
|
280
|
+
- Download artifacts
|
|
281
|
+
|
|
282
|
+
6. **Experiment Tracking**
|
|
283
|
+
- Compare experiments
|
|
284
|
+
- Metrics visualization (charts)
|
|
285
|
+
- Parameter comparison
|
|
286
|
+
|
|
287
|
+
7. **Real-Time Updates** (Future)
|
|
288
|
+
- WebSocket connection for live updates
|
|
289
|
+
- Live metric streaming
|
|
290
|
+
- Real-time log tailing
|
|
291
|
+
|
|
292
|
+
## 📚 Resources
|
|
293
|
+
|
|
294
|
+
- [Vite Documentation](https://vitejs.dev/)
|
|
295
|
+
- [React Documentation](https://react.dev/)
|
|
296
|
+
- [TypeScript Handbook](https://www.typescriptlang.org/docs/)
|
|
297
|
+
- [flowyml UI Guide](../../../UI_GUIDE.md)
|
|
298
|
+
|
|
299
|
+
## 🤝 Contributing
|
|
300
|
+
|
|
301
|
+
When contributing to the frontend:
|
|
302
|
+
|
|
303
|
+
1. Follow the existing code style
|
|
304
|
+
2. Use TypeScript for type safety
|
|
305
|
+
3. Write meaningful component names
|
|
306
|
+
4. Add comments for complex logic
|
|
307
|
+
5. Test API integration thoroughly
|
|
308
|
+
6. Ensure production build works
|
|
309
|
+
|
|
310
|
+
## 📝 Notes
|
|
311
|
+
|
|
312
|
+
- The Python backend serves the built frontend from `dist/`
|
|
313
|
+
- In production, there's no separate frontend server
|
|
314
|
+
- All state is fetched from the backend API
|
|
315
|
+
- WebSocket support is planned for future releases
|