flowyml 1.2.0__py3-none-any.whl → 1.4.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.
Files changed (104) hide show
  1. flowyml/__init__.py +3 -0
  2. flowyml/assets/base.py +10 -0
  3. flowyml/assets/metrics.py +6 -0
  4. flowyml/cli/main.py +108 -2
  5. flowyml/cli/run.py +9 -2
  6. flowyml/core/execution_status.py +52 -0
  7. flowyml/core/hooks.py +106 -0
  8. flowyml/core/observability.py +210 -0
  9. flowyml/core/orchestrator.py +274 -0
  10. flowyml/core/pipeline.py +193 -231
  11. flowyml/core/project.py +34 -2
  12. flowyml/core/remote_orchestrator.py +109 -0
  13. flowyml/core/resources.py +34 -17
  14. flowyml/core/retry_policy.py +80 -0
  15. flowyml/core/scheduler.py +9 -9
  16. flowyml/core/scheduler_config.py +2 -3
  17. flowyml/core/step.py +18 -1
  18. flowyml/core/submission_result.py +53 -0
  19. flowyml/integrations/keras.py +95 -22
  20. flowyml/monitoring/alerts.py +2 -2
  21. flowyml/stacks/__init__.py +15 -0
  22. flowyml/stacks/aws.py +599 -0
  23. flowyml/stacks/azure.py +295 -0
  24. flowyml/stacks/bridge.py +9 -9
  25. flowyml/stacks/components.py +24 -2
  26. flowyml/stacks/gcp.py +158 -11
  27. flowyml/stacks/local.py +5 -0
  28. flowyml/stacks/plugins.py +2 -2
  29. flowyml/stacks/registry.py +21 -0
  30. flowyml/storage/artifacts.py +15 -5
  31. flowyml/storage/materializers/__init__.py +2 -0
  32. flowyml/storage/materializers/base.py +33 -0
  33. flowyml/storage/materializers/cloudpickle.py +74 -0
  34. flowyml/storage/metadata.py +3 -881
  35. flowyml/storage/remote.py +590 -0
  36. flowyml/storage/sql.py +911 -0
  37. flowyml/ui/backend/dependencies.py +28 -0
  38. flowyml/ui/backend/main.py +43 -80
  39. flowyml/ui/backend/routers/assets.py +483 -17
  40. flowyml/ui/backend/routers/client.py +46 -0
  41. flowyml/ui/backend/routers/execution.py +13 -2
  42. flowyml/ui/backend/routers/experiments.py +97 -14
  43. flowyml/ui/backend/routers/metrics.py +168 -0
  44. flowyml/ui/backend/routers/pipelines.py +77 -12
  45. flowyml/ui/backend/routers/projects.py +33 -7
  46. flowyml/ui/backend/routers/runs.py +221 -12
  47. flowyml/ui/backend/routers/schedules.py +5 -21
  48. flowyml/ui/backend/routers/stats.py +14 -0
  49. flowyml/ui/backend/routers/traces.py +37 -53
  50. flowyml/ui/frontend/dist/assets/index-DcYwrn2j.css +1 -0
  51. flowyml/ui/frontend/dist/assets/index-Dlz_ygOL.js +592 -0
  52. flowyml/ui/frontend/dist/index.html +2 -2
  53. flowyml/ui/frontend/src/App.jsx +4 -1
  54. flowyml/ui/frontend/src/app/assets/page.jsx +260 -230
  55. flowyml/ui/frontend/src/app/dashboard/page.jsx +38 -7
  56. flowyml/ui/frontend/src/app/experiments/page.jsx +61 -314
  57. flowyml/ui/frontend/src/app/observability/page.jsx +277 -0
  58. flowyml/ui/frontend/src/app/pipelines/page.jsx +79 -402
  59. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectArtifactsList.jsx +151 -0
  60. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectExperimentsList.jsx +145 -0
  61. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHeader.jsx +45 -0
  62. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHierarchy.jsx +467 -0
  63. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectMetricsPanel.jsx +253 -0
  64. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectPipelinesList.jsx +105 -0
  65. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRelations.jsx +189 -0
  66. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRunsList.jsx +136 -0
  67. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectTabs.jsx +95 -0
  68. flowyml/ui/frontend/src/app/projects/[projectId]/page.jsx +326 -0
  69. flowyml/ui/frontend/src/app/projects/page.jsx +13 -3
  70. flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +79 -10
  71. flowyml/ui/frontend/src/app/runs/page.jsx +82 -424
  72. flowyml/ui/frontend/src/app/settings/page.jsx +1 -0
  73. flowyml/ui/frontend/src/app/tokens/page.jsx +62 -16
  74. flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +373 -0
  75. flowyml/ui/frontend/src/components/AssetLineageGraph.jsx +291 -0
  76. flowyml/ui/frontend/src/components/AssetStatsDashboard.jsx +302 -0
  77. flowyml/ui/frontend/src/components/AssetTreeHierarchy.jsx +477 -0
  78. flowyml/ui/frontend/src/components/ExperimentDetailsPanel.jsx +227 -0
  79. flowyml/ui/frontend/src/components/NavigationTree.jsx +401 -0
  80. flowyml/ui/frontend/src/components/PipelineDetailsPanel.jsx +239 -0
  81. flowyml/ui/frontend/src/components/PipelineGraph.jsx +67 -3
  82. flowyml/ui/frontend/src/components/ProjectSelector.jsx +115 -0
  83. flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +298 -0
  84. flowyml/ui/frontend/src/components/header/Header.jsx +48 -1
  85. flowyml/ui/frontend/src/components/plugins/ZenMLIntegration.jsx +106 -0
  86. flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +52 -26
  87. flowyml/ui/frontend/src/components/ui/DataView.jsx +35 -17
  88. flowyml/ui/frontend/src/components/ui/ErrorBoundary.jsx +118 -0
  89. flowyml/ui/frontend/src/contexts/ProjectContext.jsx +2 -2
  90. flowyml/ui/frontend/src/contexts/ToastContext.jsx +116 -0
  91. flowyml/ui/frontend/src/layouts/MainLayout.jsx +5 -1
  92. flowyml/ui/frontend/src/router/index.jsx +4 -0
  93. flowyml/ui/frontend/src/utils/date.js +10 -0
  94. flowyml/ui/frontend/src/utils/downloads.js +11 -0
  95. flowyml/utils/config.py +6 -0
  96. flowyml/utils/stack_config.py +45 -3
  97. {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/METADATA +44 -4
  98. flowyml-1.4.0.dist-info/RECORD +200 -0
  99. {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/licenses/LICENSE +1 -1
  100. flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +0 -448
  101. flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +0 -1
  102. flowyml-1.2.0.dist-info/RECORD +0 -159
  103. {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/WHEEL +0 -0
  104. {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,298 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { fetchApi } from '../utils/api';
3
+ import {
4
+ PlayCircle,
5
+ Calendar,
6
+ Clock,
7
+ CheckCircle,
8
+ XCircle,
9
+ Activity,
10
+ ArrowRight,
11
+ Box,
12
+ FileText,
13
+ Layers,
14
+ X,
15
+ Terminal,
16
+ Cpu
17
+ } from 'lucide-react';
18
+ import { Card } from './ui/Card';
19
+ import { Badge } from './ui/Badge';
20
+ import { Button } from './ui/Button';
21
+ import { format } from 'date-fns';
22
+ import { Link } from 'react-router-dom';
23
+ import { motion } from 'framer-motion';
24
+ import { StatusBadge } from './ui/ExecutionStatus';
25
+ import { PipelineGraph } from './PipelineGraph';
26
+ import { ProjectSelector } from './ProjectSelector';
27
+
28
+ export function RunDetailsPanel({ run, onClose }) {
29
+ const [details, setDetails] = useState(null);
30
+ const [loading, setLoading] = useState(false);
31
+ const [activeTab, setActiveTab] = useState('overview'); // overview, steps, artifacts
32
+ const [currentProject, setCurrentProject] = useState(run?.project);
33
+
34
+ useEffect(() => {
35
+ if (run) {
36
+ fetchRunDetails();
37
+ setCurrentProject(run.project);
38
+ }
39
+ }, [run]);
40
+
41
+ const fetchRunDetails = async () => {
42
+ setLoading(true);
43
+ try {
44
+ const res = await fetchApi(`/api/runs/${run.run_id}`);
45
+ const data = await res.json();
46
+ setDetails(data);
47
+ if (data.project) setCurrentProject(data.project);
48
+ } catch (error) {
49
+ console.error('Failed to fetch run details:', error);
50
+ } finally {
51
+ setLoading(false);
52
+ }
53
+ };
54
+
55
+ const handleProjectUpdate = async (newProject) => {
56
+ try {
57
+ await fetchApi(`/api/runs/${run.run_id}/project`, {
58
+ method: 'PUT',
59
+ headers: {
60
+ 'Content-Type': 'application/json',
61
+ },
62
+ body: JSON.stringify({ project_name: newProject })
63
+ });
64
+ setCurrentProject(newProject);
65
+ } catch (error) {
66
+ console.error('Failed to update project:', error);
67
+ }
68
+ };
69
+
70
+ if (!run) return null;
71
+
72
+ const runData = details || run;
73
+
74
+ return (
75
+ <div className="h-full flex flex-col bg-white dark:bg-slate-900">
76
+ {/* Header */}
77
+ <div className="p-6 border-b border-slate-200 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-800/50">
78
+ <div className="flex items-start justify-between mb-4">
79
+ <div className="flex items-center gap-4">
80
+ <div className="p-3 bg-blue-100 dark:bg-blue-900/30 rounded-xl text-blue-600 dark:text-blue-400">
81
+ <PlayCircle size={24} />
82
+ </div>
83
+ <div>
84
+ <div className="flex items-center gap-2">
85
+ <h2 className="text-2xl font-bold text-slate-900 dark:text-white">
86
+ {runData.name || `Run ${runData.run_id?.slice(0, 8)}`}
87
+ </h2>
88
+ <StatusBadge status={runData.status} />
89
+ </div>
90
+ <div className="flex items-center gap-2 mt-1 text-sm text-slate-500">
91
+ <span className="font-mono text-xs bg-slate-100 dark:bg-slate-800 px-1.5 py-0.5 rounded">
92
+ {runData.run_id}
93
+ </span>
94
+ <span>•</span>
95
+ <ProjectSelector
96
+ currentProject={currentProject}
97
+ onUpdate={handleProjectUpdate}
98
+ />
99
+ {runData.pipeline_name && (
100
+ <>
101
+ <span>•</span>
102
+ <span className="flex items-center gap-1">
103
+ <Layers size={12} />
104
+ {runData.pipeline_name}
105
+ </span>
106
+ </>
107
+ )}
108
+ </div>
109
+ </div>
110
+ </div>
111
+ <div className="flex items-center gap-2">
112
+ <Link to={`/runs/${runData.run_id}`}>
113
+ <Button variant="outline" size="sm">
114
+ Full View <ArrowRight size={16} className="ml-1" />
115
+ </Button>
116
+ </Link>
117
+ <Button variant="ghost" size="sm" onClick={onClose}>
118
+ <X size={20} className="text-slate-400" />
119
+ </Button>
120
+ </div>
121
+ </div>
122
+
123
+ {/* Stats Grid */}
124
+ <div className="grid grid-cols-3 gap-4">
125
+ <StatCard
126
+ label="Duration"
127
+ value={runData.duration ? `${runData.duration.toFixed(2)}s` : '-'}
128
+ icon={Clock}
129
+ color="blue"
130
+ />
131
+ <StatCard
132
+ label="Started"
133
+ value={runData.start_time ? format(new Date(runData.start_time), 'MMM d, HH:mm') : '-'}
134
+ icon={Calendar}
135
+ color="purple"
136
+ />
137
+ <StatCard
138
+ label="Steps"
139
+ value={runData.steps ? Object.keys(runData.steps).length : 0}
140
+ icon={Activity}
141
+ color="emerald"
142
+ />
143
+ </div>
144
+ </div>
145
+
146
+ {/* Content */}
147
+ <div className="flex-1 overflow-hidden flex flex-col">
148
+ {/* Tabs */}
149
+ <div className="flex items-center gap-1 p-2 border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900">
150
+ <TabButton
151
+ active={activeTab === 'overview'}
152
+ onClick={() => setActiveTab('overview')}
153
+ icon={Activity}
154
+ label="Overview"
155
+ />
156
+ <TabButton
157
+ active={activeTab === 'steps'}
158
+ onClick={() => setActiveTab('steps')}
159
+ icon={Layers}
160
+ label="Steps"
161
+ />
162
+ <TabButton
163
+ active={activeTab === 'artifacts'}
164
+ onClick={() => setActiveTab('artifacts')}
165
+ icon={Box}
166
+ label="Artifacts"
167
+ />
168
+ </div>
169
+
170
+ <div className="flex-1 overflow-y-auto bg-slate-50 dark:bg-slate-900/50 p-4">
171
+ {loading ? (
172
+ <div className="flex justify-center py-12">
173
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
174
+ </div>
175
+ ) : (
176
+ <>
177
+ {activeTab === 'overview' && (
178
+ <div className="space-y-4">
179
+ {/* DAG Visualization Preview */}
180
+ <Card className="p-0 overflow-hidden h-64 border-slate-200 dark:border-slate-700">
181
+ <div className="p-3 border-b border-slate-100 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-800/50 flex justify-between items-center">
182
+ <h3 className="text-sm font-semibold text-slate-700 dark:text-slate-300">Pipeline Graph</h3>
183
+ </div>
184
+ <div className="h-full bg-slate-50/50">
185
+ {runData.dag ? (
186
+ <PipelineGraph
187
+ dag={runData.dag}
188
+ steps={runData.steps}
189
+ />
190
+ ) : (
191
+ <div className="h-full flex items-center justify-center text-slate-400 text-sm">
192
+ No graph data available
193
+ </div>
194
+ )}
195
+ </div>
196
+ </Card>
197
+
198
+ {/* Error Display if Failed */}
199
+ {runData.status === 'failed' && runData.error && (
200
+ <div className="bg-rose-50 dark:bg-rose-900/20 border border-rose-200 dark:border-rose-800 rounded-xl p-4">
201
+ <h3 className="text-rose-700 dark:text-rose-400 font-semibold flex items-center gap-2 mb-2">
202
+ <XCircle size={18} />
203
+ Execution Failed
204
+ </h3>
205
+ <pre className="text-xs font-mono text-rose-600 dark:text-rose-300 whitespace-pre-wrap overflow-x-auto">
206
+ {runData.error}
207
+ </pre>
208
+ </div>
209
+ )}
210
+ </div>
211
+ )}
212
+
213
+ {activeTab === 'steps' && (
214
+ <div className="space-y-3">
215
+ {runData.steps && Object.entries(runData.steps).map(([stepId, step]) => (
216
+ <div key={stepId} className="bg-white dark:bg-slate-800 p-3 rounded-xl border border-slate-200 dark:border-slate-700">
217
+ <div className="flex items-center justify-between mb-2">
218
+ <div className="flex items-center gap-2">
219
+ <StatusBadge status={step.success ? 'completed' : step.error ? 'failed' : 'running'} size="sm" />
220
+ <span className="font-medium text-slate-900 dark:text-white">{stepId}</span>
221
+ </div>
222
+ <span className="text-xs text-slate-500 font-mono">
223
+ {step.duration ? `${step.duration.toFixed(2)}s` : '-'}
224
+ </span>
225
+ </div>
226
+ {step.error && (
227
+ <div className="text-xs text-rose-600 bg-rose-50 dark:bg-rose-900/20 p-2 rounded mt-2 font-mono">
228
+ {step.error}
229
+ </div>
230
+ )}
231
+ </div>
232
+ ))}
233
+ {(!runData.steps || Object.keys(runData.steps).length === 0) && (
234
+ <div className="text-center py-8 text-slate-500">No steps recorded</div>
235
+ )}
236
+ </div>
237
+ )}
238
+
239
+ {activeTab === 'artifacts' && (
240
+ <div className="space-y-3">
241
+ {/* This would need actual artifact data structure */}
242
+ <div className="text-center py-8 text-slate-500">
243
+ <Box size={32} className="mx-auto mb-2 opacity-50" />
244
+ <p>Artifacts view not fully implemented in preview</p>
245
+ <Link to={`/runs/${runData.run_id}`} className="text-primary-600 hover:underline text-sm mt-2 inline-block">
246
+ View in full details page
247
+ </Link>
248
+ </div>
249
+ </div>
250
+ )}
251
+ </>
252
+ )}
253
+ </div>
254
+ </div>
255
+ </div>
256
+ );
257
+ }
258
+
259
+ function StatCard({ label, value, icon: Icon, color }) {
260
+ const colorClasses = {
261
+ blue: 'text-blue-600 bg-blue-50 dark:bg-blue-900/20',
262
+ emerald: 'text-emerald-600 bg-emerald-50 dark:bg-emerald-900/20',
263
+ purple: 'text-purple-600 bg-purple-50 dark:bg-purple-900/20',
264
+ rose: 'text-rose-600 bg-rose-50 dark:bg-rose-900/20',
265
+ };
266
+
267
+ return (
268
+ <div className="bg-white dark:bg-slate-800 p-3 rounded-xl border border-slate-200 dark:border-slate-700">
269
+ <div className="flex items-center gap-2 mb-1">
270
+ <div className={`p-1 rounded-lg ${colorClasses[color]}`}>
271
+ <Icon size={14} />
272
+ </div>
273
+ <span className="text-xs text-slate-500 font-medium">{label}</span>
274
+ </div>
275
+ <div className="text-sm font-bold text-slate-900 dark:text-white pl-1 truncate" title={value}>
276
+ {value}
277
+ </div>
278
+ </div>
279
+ );
280
+ }
281
+
282
+ function TabButton({ active, onClick, icon: Icon, label }) {
283
+ return (
284
+ <button
285
+ onClick={onClick}
286
+ className={`
287
+ flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-all
288
+ ${active
289
+ ? 'bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-white'
290
+ : 'text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-700 dark:hover:text-slate-300'
291
+ }
292
+ `}
293
+ >
294
+ <Icon size={16} />
295
+ {label}
296
+ </button>
297
+ );
298
+ }
@@ -1,15 +1,21 @@
1
1
  import React from 'react';
2
2
  import { useLocation, Link } from 'react-router-dom';
3
- import { Sun, Moon, ChevronRight, Home } from 'lucide-react';
3
+ import { Sun, Moon, ChevronRight, Home, Server, ExternalLink } from 'lucide-react';
4
4
  import { useTheme } from '../../contexts/ThemeContext';
5
5
  import { ProjectSelector } from '../ui/ProjectSelector';
6
+ import { useConfig } from '../../utils/api';
6
7
 
7
8
  export function Header() {
8
9
  const { theme, toggleTheme } = useTheme();
9
10
  const location = useLocation();
11
+ const { config, loading } = useConfig();
10
12
 
11
13
  // Generate breadcrumbs from path
12
14
  const pathnames = location.pathname.split('/').filter((x) => x);
15
+ const isRemoteStack = !loading && config?.execution_mode === 'remote';
16
+ const remoteServices = isRemoteStack && Array.isArray(config?.remote_services)
17
+ ? config.remote_services
18
+ : [];
13
19
 
14
20
  return (
15
21
  <header className="bg-white dark:bg-slate-800 border-b border-slate-200 dark:border-slate-700 px-6 py-4 flex items-center justify-between shadow-sm z-10">
@@ -51,6 +57,47 @@ export function Header() {
51
57
  </div>
52
58
 
53
59
  <div className="flex items-center gap-4">
60
+ {isRemoteStack && (
61
+ <div className="flex flex-col gap-2 px-4 py-2 rounded-xl bg-primary-50 text-primary-800 border border-primary-100 dark:bg-primary-900/20 dark:text-primary-200 dark:border-primary-900/40">
62
+ <div className="flex items-center gap-2 text-xs uppercase tracking-wide font-semibold">
63
+ <Server size={14} /> Remote Stack
64
+ </div>
65
+ <div className="flex flex-wrap items-center gap-2">
66
+ {config?.remote_ui_url && (
67
+ <a
68
+ href={config.remote_ui_url}
69
+ target="_blank"
70
+ rel="noopener noreferrer"
71
+ className="inline-flex items-center gap-1 text-xs font-medium bg-white/70 dark:bg-slate-800/40 px-2 py-1 rounded-lg hover:underline"
72
+ >
73
+ UI <ExternalLink size={12} />
74
+ </a>
75
+ )}
76
+ {config?.remote_server_url && (
77
+ <a
78
+ href={config.remote_server_url}
79
+ target="_blank"
80
+ rel="noopener noreferrer"
81
+ className="inline-flex items-center gap-1 text-xs font-medium bg-white/70 dark:bg-slate-800/40 px-2 py-1 rounded-lg hover:underline"
82
+ >
83
+ API <ExternalLink size={12} />
84
+ </a>
85
+ )}
86
+ {remoteServices.map((service, idx) => (
87
+ <a
88
+ key={`${service?.name || service?.label || 'service'}-${idx}`}
89
+ href={service?.url || service?.link}
90
+ target="_blank"
91
+ rel="noopener noreferrer"
92
+ className="inline-flex items-center gap-1 text-xs font-medium bg-white/70 dark:bg-slate-800/40 px-2 py-1 rounded-lg hover:underline"
93
+ >
94
+ {service?.label || service?.name || 'Service'} <ExternalLink size={12} />
95
+ </a>
96
+ ))}
97
+ </div>
98
+ </div>
99
+ )}
100
+
54
101
  <ProjectSelector />
55
102
 
56
103
  <div className="h-6 w-px bg-slate-200 dark:bg-slate-700 mx-2" />
@@ -0,0 +1,106 @@
1
+ import React, { useState } from 'react';
2
+ import { Server, ArrowRight, CheckCircle, AlertCircle, Loader2 } from 'lucide-react';
3
+ import { Button } from '../ui/Button';
4
+ import { Card } from '../ui/Card';
5
+ import { pluginService } from '../../services/pluginService';
6
+
7
+ export function ZenMLIntegration() {
8
+ const [stackName, setStackName] = useState('');
9
+ const [status, setStatus] = useState('idle'); // idle, importing, success, error
10
+ const [logs, setLogs] = useState([]);
11
+
12
+ const handleImport = async () => {
13
+ if (!stackName) return;
14
+
15
+ setStatus('importing');
16
+ setLogs(['Connecting to ZenML client...', 'Fetching stack details...']);
17
+
18
+ try {
19
+ const result = await pluginService.importZenMLStack(stackName);
20
+ setLogs(prev => [...prev, `Found stack '${stackName}' with ${result.components.length} components.`]);
21
+
22
+ // Artificial delay for UX to show the progress
23
+ await new Promise(r => setTimeout(r, 800));
24
+
25
+ setLogs(prev => [...prev, 'Generating flowyml configuration...', 'Import successful!']);
26
+ setStatus('success');
27
+ } catch (error) {
28
+ console.error('Import failed:', error);
29
+ setLogs(prev => [...prev, `Error: ${error.message}`]);
30
+ setStatus('error');
31
+ }
32
+ };
33
+
34
+ return (
35
+ <div className="space-y-6">
36
+ <div className="bg-slate-50 dark:bg-slate-800/50 p-4 rounded-xl border border-slate-200 dark:border-slate-700">
37
+ <div className="flex items-start gap-3">
38
+ <Server className="text-primary-500 mt-1" size={20} />
39
+ <div>
40
+ <h3 className="font-medium text-slate-900 dark:text-white">Import ZenML Stack</h3>
41
+ <p className="text-sm text-slate-500 dark:text-slate-400 mt-1">
42
+ Migrate your existing ZenML infrastructure to flowyml. We'll automatically detect your components and generate the necessary configuration.
43
+ </p>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <div className="flex gap-4 items-end">
49
+ <div className="flex-1 space-y-2">
50
+ <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
51
+ ZenML Stack Name
52
+ </label>
53
+ <input
54
+ type="text"
55
+ placeholder="e.g., production-stack"
56
+ value={stackName}
57
+ onChange={(e) => setStackName(e.target.value)}
58
+ className="w-full px-3 py-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
59
+ />
60
+ </div>
61
+ <Button
62
+ onClick={handleImport}
63
+ disabled={status === 'importing' || !stackName}
64
+ className="flex items-center gap-2"
65
+ >
66
+ {status === 'importing' ? (
67
+ <>
68
+ <Loader2 size={16} className="animate-spin" />
69
+ Importing...
70
+ </>
71
+ ) : (
72
+ <>
73
+ Start Import
74
+ <ArrowRight size={16} />
75
+ </>
76
+ )}
77
+ </Button>
78
+ </div>
79
+
80
+ {status !== 'idle' && (
81
+ <div className="bg-slate-900 rounded-xl p-4 font-mono text-sm overflow-hidden">
82
+ <div className="space-y-1">
83
+ {logs.map((log, i) => (
84
+ <div key={i} className="flex items-center gap-2 text-slate-300">
85
+ <span className="text-slate-600">➜</span>
86
+ {log}
87
+ </div>
88
+ ))}
89
+ </div>
90
+ {status === 'success' && (
91
+ <div className="mt-4 pt-4 border-t border-slate-800 flex items-center gap-2 text-green-400">
92
+ <CheckCircle size={16} />
93
+ <span>Stack imported successfully! You can now use it in your pipelines.</span>
94
+ </div>
95
+ )}
96
+ {status === 'error' && (
97
+ <div className="mt-4 pt-4 border-t border-slate-800 flex items-center gap-2 text-red-400">
98
+ <AlertCircle size={16} />
99
+ <span>Failed to import stack. Please check the name and try again.</span>
100
+ </div>
101
+ )}
102
+ </div>
103
+ )}
104
+ </div>
105
+ );
106
+ }
@@ -14,20 +14,42 @@ import {
14
14
  Package,
15
15
  ChevronLeft,
16
16
  ChevronRight,
17
- Menu
17
+ Menu,
18
+ Activity
18
19
  } from 'lucide-react';
19
20
  import { motion, AnimatePresence } from 'framer-motion';
20
21
 
21
- const NAV_LINKS = [
22
- { icon: LayoutDashboard, label: 'Dashboard', path: '/' },
23
- { icon: FolderKanban, label: 'Projects', path: '/projects' },
24
- { icon: PlayCircle, label: 'Pipelines', path: '/pipelines' },
25
- { icon: Calendar, label: 'Schedules', path: '/schedules' },
26
- { icon: PlayCircle, label: 'Runs', path: '/runs' },
27
- { icon: Trophy, label: 'Leaderboard', path: '/leaderboard' },
28
- { icon: Database, label: 'Assets', path: '/assets' },
29
- { icon: FlaskConical, label: 'Experiments', path: '/experiments' },
30
- { icon: MessageSquare, label: 'Traces', path: '/traces' },
22
+ const NAV_GROUPS = [
23
+ {
24
+ title: 'Workspace',
25
+ items: [
26
+ { icon: LayoutDashboard, label: 'Dashboard', path: '/' },
27
+ { icon: FolderKanban, label: 'Projects', path: '/projects' },
28
+ ],
29
+ },
30
+ {
31
+ title: 'Automation',
32
+ items: [
33
+ { icon: PlayCircle, label: 'Pipelines', path: '/pipelines' },
34
+ { icon: Calendar, label: 'Schedules', path: '/schedules' },
35
+ { icon: PlayCircle, label: 'Runs', path: '/runs' },
36
+ ],
37
+ },
38
+ {
39
+ title: 'Insights',
40
+ items: [
41
+ { icon: Trophy, label: 'Leaderboard', path: '/leaderboard' },
42
+ { icon: FlaskConical, label: 'Experiments', path: '/experiments' },
43
+ ],
44
+ },
45
+ {
46
+ title: 'Data & Observability',
47
+ items: [
48
+ { icon: Database, label: 'Assets', path: '/assets' },
49
+ { icon: MessageSquare, label: 'Traces', path: '/traces' },
50
+ { icon: Activity, label: 'Observability', path: '/observability' },
51
+ ],
52
+ },
31
53
  ];
32
54
 
33
55
  const SETTINGS_LINKS = [
@@ -65,22 +87,26 @@ export function Sidebar({ collapsed, setCollapsed }) {
65
87
  </div>
66
88
 
67
89
  {/* Navigation */}
68
- <nav className="flex-1 p-4 space-y-1 overflow-y-auto overflow-x-hidden scrollbar-thin scrollbar-thumb-slate-200 dark:scrollbar-thumb-slate-700">
69
- <div className={`px-4 py-2 text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider transition-opacity duration-200 ${collapsed ? 'opacity-0 h-0' : 'opacity-100'}`}>
70
- Platform
71
- </div>
72
- {NAV_LINKS.map((link) => (
73
- <NavItem
74
- key={link.path}
75
- to={link.path}
76
- icon={link.icon}
77
- label={link.label}
78
- collapsed={collapsed}
79
- isActive={location.pathname === link.path}
80
- />
90
+ <nav className="flex-1 p-4 space-y-4 overflow-y-auto overflow-x-hidden scrollbar-thin scrollbar-thumb-slate-200 dark:scrollbar-thumb-slate-700">
91
+ {NAV_GROUPS.map((group) => (
92
+ <div key={group.title} className="space-y-1">
93
+ <div className={`px-4 text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider transition-opacity duration-200 ${collapsed ? 'opacity-0 h-0' : 'opacity-100'}`}>
94
+ {group.title}
95
+ </div>
96
+ {group.items.map((link) => (
97
+ <NavItem
98
+ key={link.path}
99
+ to={link.path}
100
+ icon={link.icon}
101
+ label={link.label}
102
+ collapsed={collapsed}
103
+ isActive={location.pathname === link.path}
104
+ />
105
+ ))}
106
+ </div>
81
107
  ))}
82
108
 
83
- <div className={`px-4 py-2 text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider mt-4 transition-opacity duration-200 ${collapsed ? 'opacity-0 h-0' : 'opacity-100'}`}>
109
+ <div className={`px-4 py-2 text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider mt-2 transition-opacity duration-200 ${collapsed ? 'opacity-0 h-0' : 'opacity-100'}`}>
84
110
  Settings
85
111
  </div>
86
112
  {SETTINGS_LINKS.map((link) => (
@@ -100,7 +126,7 @@ export function Sidebar({ collapsed, setCollapsed }) {
100
126
  <div className={`bg-slate-50 dark:bg-slate-900 rounded-lg p-4 border border-slate-100 dark:border-slate-700 transition-all duration-200 ${collapsed ? 'p-2 flex justify-center' : ''}`}>
101
127
  {!collapsed ? (
102
128
  <>
103
- <p className="text-xs font-medium text-slate-500 dark:text-slate-400 whitespace-nowrap">flowyml v0.1.0</p>
129
+ <p className="text-xs font-medium text-slate-500 dark:text-slate-400 whitespace-nowrap">flowyml v1.3.0</p>
104
130
  <p className="text-xs text-slate-400 dark:text-slate-500 mt-1 whitespace-nowrap">Local Environment</p>
105
131
  </>
106
132
  ) : (