flock-core 0.5.0b50__py3-none-any.whl → 0.5.0b52__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.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/dashboard/launcher.py +1 -1
- flock/frontend/README.md +678 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4347 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +79 -0
- flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
- flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
- flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
- flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
- flock/frontend/src/components/common/BuildInfo.tsx +39 -0
- flock/frontend/src/components/common/EmptyState.module.css +115 -0
- flock/frontend/src/components/common/EmptyState.tsx +128 -0
- flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
- flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
- flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
- flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
- flock/frontend/src/components/controls/PublishControl.css +547 -0
- flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
- flock/frontend/src/components/controls/PublishControl.tsx +432 -0
- flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
- flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
- flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
- flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
- flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
- flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
- flock/frontend/src/components/filters/FilterBar.module.css +29 -0
- flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
- flock/frontend/src/components/filters/FilterBar.tsx +33 -0
- flock/frontend/src/components/filters/FilterPills.module.css +79 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
- flock/frontend/src/components/filters/FilterPills.tsx +67 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
- flock/frontend/src/components/graph/AgentNode.tsx +322 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
- flock/frontend/src/components/graph/MessageNode.tsx +116 -0
- flock/frontend/src/components/graph/MiniMap.tsx +47 -0
- flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
- flock/frontend/src/components/layout/DashboardLayout.css +407 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
- flock/frontend/src/components/layout/Header.module.css +88 -0
- flock/frontend/src/components/layout/Header.tsx +52 -0
- flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
- flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
- flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
- flock/frontend/src/components/modules/registerModules.ts +20 -0
- flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
- flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
- flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
- flock/frontend/src/components/settings/SettingsPanel.css +327 -0
- flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
- flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
- flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
- flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
- flock/frontend/src/hooks/useModulePersistence.ts +154 -0
- flock/frontend/src/hooks/useModules.ts +139 -0
- flock/frontend/src/hooks/usePersistence.ts +139 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +213 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +794 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +146 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.test.ts +595 -0
- flock/frontend/src/services/websocket.ts +685 -0
- flock/frontend/src/store/filterStore.test.ts +242 -0
- flock/frontend/src/store/filterStore.ts +103 -0
- flock/frontend/src/store/graphStore.test.ts +186 -0
- flock/frontend/src/store/graphStore.ts +414 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +57 -0
- flock/frontend/src/store/settingsStore.ts +188 -0
- flock/frontend/src/store/streamStore.ts +68 -0
- flock/frontend/src/store/uiStore.test.ts +54 -0
- flock/frontend/src/store/uiStore.ts +110 -0
- flock/frontend/src/store/wsStore.ts +34 -0
- flock/frontend/src/styles/index.css +15 -0
- flock/frontend/src/styles/scrollbar.css +47 -0
- flock/frontend/src/styles/variables.css +488 -0
- flock/frontend/src/test/setup.ts +1 -0
- flock/frontend/src/types/filters.ts +14 -0
- flock/frontend/src/types/graph.ts +55 -0
- flock/frontend/src/types/modules.ts +7 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/mockData.ts +85 -0
- flock/frontend/src/utils/performance.ts +16 -0
- flock/frontend/src/utils/transforms.test.ts +860 -0
- flock/frontend/src/utils/transforms.ts +323 -0
- flock/frontend/src/vite-env.d.ts +17 -0
- flock/frontend/tsconfig.json +27 -0
- flock/frontend/tsconfig.node.json +11 -0
- flock/frontend/vite.config.ts +25 -0
- flock/frontend/vitest.config.ts +11 -0
- flock/helper/cli_helper.py +1 -1
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/METADATA +1 -1
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/RECORD +117 -7
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { ReactFlowProvider } from '@xyflow/react';
|
|
3
|
+
import GraphCanvas from '../graph/GraphCanvas';
|
|
4
|
+
import DetailWindowContainer from '../details/DetailWindowContainer';
|
|
5
|
+
import FilterBar from '../filters/FilterBar';
|
|
6
|
+
import PublishControl from '../controls/PublishControl';
|
|
7
|
+
import ModuleWindow from '../modules/ModuleWindow';
|
|
8
|
+
import SettingsPanel from '../settings/SettingsPanel';
|
|
9
|
+
import KeyboardShortcutsDialog from '../common/KeyboardShortcutsDialog';
|
|
10
|
+
import { useUIStore } from '../../store/uiStore';
|
|
11
|
+
import { useModuleStore } from '../../store/moduleStore';
|
|
12
|
+
import { useGraphStore } from '../../store/graphStore';
|
|
13
|
+
import { useFilterStore } from '../../store/filterStore';
|
|
14
|
+
import { useSettingsStore } from '../../store/settingsStore';
|
|
15
|
+
import { useModulePersistence } from '../../hooks/useModulePersistence';
|
|
16
|
+
import { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts';
|
|
17
|
+
import Header from './Header';
|
|
18
|
+
import './DashboardLayout.css';
|
|
19
|
+
|
|
20
|
+
const DashboardLayout: React.FC = () => {
|
|
21
|
+
const mode = useUIStore((state) => state.mode);
|
|
22
|
+
const setMode = useUIStore((state) => state.setMode);
|
|
23
|
+
const moduleInstances = useModuleStore((state) => state.instances);
|
|
24
|
+
const detailWindows = useUIStore((state) => state.detailWindows);
|
|
25
|
+
|
|
26
|
+
// UI visibility from settings store
|
|
27
|
+
const showFilters = useSettingsStore((state) => state.ui.showFilters);
|
|
28
|
+
const showControls = useSettingsStore((state) => state.ui.showControls);
|
|
29
|
+
const showSettings = useSettingsStore((state) => state.ui.showSettings);
|
|
30
|
+
const setShowFilters = useSettingsStore((state) => state.setShowFilters);
|
|
31
|
+
const setShowControls = useSettingsStore((state) => state.setShowControls);
|
|
32
|
+
const setShowSettings = useSettingsStore((state) => state.setShowSettings);
|
|
33
|
+
|
|
34
|
+
// Keyboard shortcuts help dialog
|
|
35
|
+
const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false);
|
|
36
|
+
|
|
37
|
+
// Enable module window persistence
|
|
38
|
+
useModulePersistence();
|
|
39
|
+
|
|
40
|
+
const handleToggleAgentDetails = () => {
|
|
41
|
+
const detailWindows = useUIStore.getState().detailWindows;
|
|
42
|
+
const agents = useGraphStore.getState().agents;
|
|
43
|
+
|
|
44
|
+
// Check if any detail windows are open
|
|
45
|
+
if (detailWindows.size > 0) {
|
|
46
|
+
// Close all detail windows
|
|
47
|
+
detailWindows.forEach((_, nodeId) => {
|
|
48
|
+
useUIStore.getState().closeDetailWindow(nodeId);
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
// Open detail windows for all agents
|
|
52
|
+
agents.forEach((agent) => {
|
|
53
|
+
useUIStore.getState().openDetailWindow(agent.id);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Enable keyboard shortcuts with help dialog toggle
|
|
59
|
+
useKeyboardShortcuts({
|
|
60
|
+
onToggleHelp: () => setShowKeyboardShortcuts((prev) => !prev),
|
|
61
|
+
onToggleAgentDetails: handleToggleAgentDetails,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const handleClearStore = () => {
|
|
65
|
+
if (confirm('Clear all dashboard data? This will remove all agents, messages, and session data.')) {
|
|
66
|
+
// Clear graph store
|
|
67
|
+
useGraphStore.setState({
|
|
68
|
+
agents: new Map(),
|
|
69
|
+
messages: new Map(),
|
|
70
|
+
events: [],
|
|
71
|
+
runs: new Map(),
|
|
72
|
+
nodes: [],
|
|
73
|
+
edges: []
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Clear UI store
|
|
77
|
+
useUIStore.setState({ mode: 'agent' });
|
|
78
|
+
|
|
79
|
+
// Clear filter store
|
|
80
|
+
useFilterStore.setState({
|
|
81
|
+
correlationId: undefined,
|
|
82
|
+
timeRange: undefined,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Clear module store
|
|
86
|
+
useModuleStore.getState().instances.clear();
|
|
87
|
+
|
|
88
|
+
// Clear IndexedDB
|
|
89
|
+
indexedDB.databases().then((dbs) => {
|
|
90
|
+
dbs.forEach((db) => {
|
|
91
|
+
if (db.name) indexedDB.deleteDatabase(db.name);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Clear localStorage
|
|
96
|
+
localStorage.clear();
|
|
97
|
+
|
|
98
|
+
// Reload page
|
|
99
|
+
window.location.reload();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className="dashboard-layout">
|
|
105
|
+
{/* Header */}
|
|
106
|
+
<header className="dashboard-header">
|
|
107
|
+
<h1 className="dashboard-title">🦆🐓 Flock 🐤🐧</h1>
|
|
108
|
+
|
|
109
|
+
<div className="view-toggle-container">
|
|
110
|
+
<span className="view-toggle-label">View:</span>
|
|
111
|
+
<div className="view-toggle-group">
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
onClick={() => setMode('agent')}
|
|
115
|
+
className={`view-toggle-button ${mode === 'agent' ? 'active' : ''}`}
|
|
116
|
+
>
|
|
117
|
+
Agent View
|
|
118
|
+
</button>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
onClick={() => setMode('blackboard')}
|
|
122
|
+
className={`view-toggle-button ${mode === 'blackboard' ? 'active' : ''}`}
|
|
123
|
+
>
|
|
124
|
+
Blackboard View
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div className="dashboard-actions">
|
|
130
|
+
{/* Publish - Primary action, first in order */}
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
onClick={() => setShowControls(!showControls)}
|
|
134
|
+
className={`controls-toggle primary ${showControls ? 'active' : ''}`}
|
|
135
|
+
title="Publish artifacts to the blackboard (Ctrl+Shift+P)"
|
|
136
|
+
aria-pressed={showControls ? 'true' : 'false'}
|
|
137
|
+
aria-label={showControls ? 'Publish panel open' : 'Publish panel closed'}
|
|
138
|
+
>
|
|
139
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
140
|
+
<path
|
|
141
|
+
d="M8 2.667v10.666M2.667 8h10.666"
|
|
142
|
+
stroke="currentColor"
|
|
143
|
+
strokeWidth="1.5"
|
|
144
|
+
strokeLinecap="round"
|
|
145
|
+
strokeLinejoin="round"
|
|
146
|
+
/>
|
|
147
|
+
</svg>
|
|
148
|
+
<span>Publish</span>
|
|
149
|
+
</button>
|
|
150
|
+
|
|
151
|
+
{/* Agent Details */}
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
onClick={handleToggleAgentDetails}
|
|
155
|
+
className={`controls-toggle ${detailWindows.size > 0 ? 'active' : ''}`}
|
|
156
|
+
title={`${detailWindows.size > 0 ? 'Close' : 'Show'} agent detail windows (Ctrl+Shift+D)`}
|
|
157
|
+
aria-pressed={detailWindows.size > 0 ? 'true' : 'false'}
|
|
158
|
+
aria-label={detailWindows.size > 0 ? 'Agent details shown' : 'Agent details hidden'}
|
|
159
|
+
>
|
|
160
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
161
|
+
<path
|
|
162
|
+
d="M2 4.667h12M2 8h12M2 11.333h12"
|
|
163
|
+
stroke="currentColor"
|
|
164
|
+
strokeWidth="1.5"
|
|
165
|
+
strokeLinecap="round"
|
|
166
|
+
strokeLinejoin="round"
|
|
167
|
+
/>
|
|
168
|
+
</svg>
|
|
169
|
+
<span>Agent Details</span>
|
|
170
|
+
</button>
|
|
171
|
+
|
|
172
|
+
{/* Filter */}
|
|
173
|
+
<button
|
|
174
|
+
type="button"
|
|
175
|
+
onClick={() => setShowFilters(!showFilters)}
|
|
176
|
+
className={`controls-toggle ${showFilters ? 'active' : ''}`}
|
|
177
|
+
title={`${showFilters ? 'Hide' : 'Show'} filter panel (Ctrl+Shift+F)`}
|
|
178
|
+
aria-pressed={showFilters ? 'true' : 'false'}
|
|
179
|
+
aria-label={showFilters ? 'Filters shown' : 'Filters hidden'}
|
|
180
|
+
>
|
|
181
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
182
|
+
<path
|
|
183
|
+
d="M2.667 2.667h10.666L9.333 7v4.667l-2.666 1.333V7L2.667 2.667z"
|
|
184
|
+
stroke="currentColor"
|
|
185
|
+
strokeWidth="1.5"
|
|
186
|
+
strokeLinecap="round"
|
|
187
|
+
strokeLinejoin="round"
|
|
188
|
+
/>
|
|
189
|
+
</svg>
|
|
190
|
+
<span>Filter</span>
|
|
191
|
+
</button>
|
|
192
|
+
|
|
193
|
+
{/* Settings */}
|
|
194
|
+
<button
|
|
195
|
+
type="button"
|
|
196
|
+
onClick={() => setShowSettings(!showSettings)}
|
|
197
|
+
className={`controls-toggle ${showSettings ? 'active' : ''}`}
|
|
198
|
+
title={`${showSettings ? 'Hide' : 'Show'} settings panel (Ctrl+,)`}
|
|
199
|
+
aria-pressed={showSettings ? 'true' : 'false'}
|
|
200
|
+
aria-label={showSettings ? 'Settings shown' : 'Settings hidden'}
|
|
201
|
+
>
|
|
202
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
203
|
+
<path
|
|
204
|
+
d="M8 10a2 2 0 100-4 2 2 0 000 4z"
|
|
205
|
+
stroke="currentColor"
|
|
206
|
+
strokeWidth="1.5"
|
|
207
|
+
/>
|
|
208
|
+
<path
|
|
209
|
+
d="M13.333 8c0-.367-.2-.7-.533-.867l-.933-.467a.933.933 0 01-.467-.8v-.133c0-.334.133-.6.467-.8l.933-.467c.333-.166.533-.5.533-.866 0-.367-.2-.7-.533-.867L11.867 2.4a.933.933 0 01-.467-.8V1.333c0-.366-.233-.666-.6-.666H9.2c-.366 0-.666.3-.666.666V1.6c0 .334-.267.634-.6.8l-.934.467c-.333.166-.7.166-1 0l-.933-.467a.933.933 0 00-.8 0L2.533 3.067c-.333.166-.533.5-.533.866 0 .367.2.7.533.867l.933.467c.334.2.467.466.467.8v.133c0 .334-.133.6-.467.8l-.933.467c-.333.166-.533.5-.533.866 0 .367.2.7.533.867l.934.533c.333.2.6.5.6.8v.267c0 .366.3.666.666.666h1.6c.367 0 .667-.3.667-.666v-.267c0-.3.267-.6.6-.8l.933-.533c.334-.167.7-.167 1 0l.934.533c.333.2.6.5.6.8v.267c0 .366.3.666.666.666H9.2c.367 0 .667-.3.667-.666v-.267c0-.3.266-.6.6-.8l.933-.533c.333-.167.533-.5.533-.867z"
|
|
210
|
+
stroke="currentColor"
|
|
211
|
+
strokeWidth="1.5"
|
|
212
|
+
/>
|
|
213
|
+
</svg>
|
|
214
|
+
<span>Settings</span>
|
|
215
|
+
</button>
|
|
216
|
+
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
onClick={() => setShowKeyboardShortcuts(true)}
|
|
220
|
+
className="icon-button help-button"
|
|
221
|
+
title="Keyboard shortcuts (Ctrl+/)"
|
|
222
|
+
aria-label="Show keyboard shortcuts"
|
|
223
|
+
>
|
|
224
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
225
|
+
<circle
|
|
226
|
+
cx="8"
|
|
227
|
+
cy="8"
|
|
228
|
+
r="6.5"
|
|
229
|
+
stroke="currentColor"
|
|
230
|
+
strokeWidth="1.5"
|
|
231
|
+
/>
|
|
232
|
+
<path
|
|
233
|
+
d="M8 11.5v-.5M8 8.5v-2a1.5 1.5 0 10-1.5-1.5"
|
|
234
|
+
stroke="currentColor"
|
|
235
|
+
strokeWidth="1.5"
|
|
236
|
+
strokeLinecap="round"
|
|
237
|
+
/>
|
|
238
|
+
<circle cx="8" cy="11.5" r="0.5" fill="currentColor" />
|
|
239
|
+
</svg>
|
|
240
|
+
</button>
|
|
241
|
+
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
onClick={handleClearStore}
|
|
245
|
+
className="icon-button clear-button"
|
|
246
|
+
title="Clear all dashboard data"
|
|
247
|
+
aria-label="Clear all dashboard data"
|
|
248
|
+
>
|
|
249
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
250
|
+
<path
|
|
251
|
+
d="M2 4h12M5.333 4V2.667a1.333 1.333 0 011.334-1.334h2.666a1.333 1.333 0 011.334 1.334V4m2 0v9.333a1.333 1.333 0 01-1.334 1.334H4.667a1.333 1.333 0 01-1.334-1.334V4h9.334z"
|
|
252
|
+
stroke="currentColor"
|
|
253
|
+
strokeWidth="1.5"
|
|
254
|
+
strokeLinecap="round"
|
|
255
|
+
strokeLinejoin="round"
|
|
256
|
+
/>
|
|
257
|
+
</svg>
|
|
258
|
+
</button>
|
|
259
|
+
|
|
260
|
+
<Header />
|
|
261
|
+
</div>
|
|
262
|
+
</header>
|
|
263
|
+
|
|
264
|
+
{/* Filter Bar - Collapsible */}
|
|
265
|
+
{showFilters && <FilterBar />}
|
|
266
|
+
|
|
267
|
+
{/* Main Content */}
|
|
268
|
+
<div className="dashboard-main">
|
|
269
|
+
{/* Graph Canvas */}
|
|
270
|
+
<main className="graph-container">
|
|
271
|
+
<ReactFlowProvider>
|
|
272
|
+
<GraphCanvas />
|
|
273
|
+
</ReactFlowProvider>
|
|
274
|
+
</main>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
{/* Detail Windows */}
|
|
278
|
+
<DetailWindowContainer />
|
|
279
|
+
|
|
280
|
+
{/* Module Windows */}
|
|
281
|
+
{Array.from(moduleInstances.values()).map((instance) => (
|
|
282
|
+
<ModuleWindow key={instance.id} instanceId={instance.id} />
|
|
283
|
+
))}
|
|
284
|
+
|
|
285
|
+
{/* Publish Control Panel - Slides in from right */}
|
|
286
|
+
{showControls && <PublishControl />}
|
|
287
|
+
|
|
288
|
+
{/* Settings Panel - Slides in from right */}
|
|
289
|
+
{showSettings && <SettingsPanel />}
|
|
290
|
+
|
|
291
|
+
{/* Keyboard Shortcuts Dialog */}
|
|
292
|
+
<KeyboardShortcutsDialog
|
|
293
|
+
isOpen={showKeyboardShortcuts}
|
|
294
|
+
onClose={() => setShowKeyboardShortcuts(false)}
|
|
295
|
+
/>
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export default DashboardLayout;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.connectionBadge {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: var(--gap-sm);
|
|
5
|
+
padding: var(--spacing-1-5) var(--spacing-3);
|
|
6
|
+
border-radius: var(--radius-full);
|
|
7
|
+
font-size: var(--font-size-caption);
|
|
8
|
+
font-weight: var(--font-weight-medium);
|
|
9
|
+
box-shadow: var(--shadow-xs);
|
|
10
|
+
transition: var(--transition-all);
|
|
11
|
+
cursor: default;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.connectionBadge.hasError {
|
|
15
|
+
cursor: help;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Status Variants */
|
|
19
|
+
.connectionBadge.connected {
|
|
20
|
+
background-color: var(--color-success-bg);
|
|
21
|
+
color: var(--color-success-dark);
|
|
22
|
+
border: var(--border-width-1) solid var(--color-success-border);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.connectionBadge.connecting,
|
|
26
|
+
.connectionBadge.reconnecting {
|
|
27
|
+
background-color: var(--color-warning-bg);
|
|
28
|
+
color: var(--color-warning-dark);
|
|
29
|
+
border: var(--border-width-1) solid var(--color-warning-border);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.connectionBadge.disconnected {
|
|
33
|
+
background-color: var(--color-error-bg);
|
|
34
|
+
color: var(--color-error-dark);
|
|
35
|
+
border: var(--border-width-1) solid var(--color-error-border);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Status Indicator Dot */
|
|
39
|
+
.statusIndicator {
|
|
40
|
+
width: 8px;
|
|
41
|
+
height: 8px;
|
|
42
|
+
border-radius: var(--radius-circle);
|
|
43
|
+
transition: var(--transition-opacity);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.statusIndicator.connected {
|
|
47
|
+
background-color: var(--color-success);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.statusIndicator.connecting,
|
|
51
|
+
.statusIndicator.reconnecting {
|
|
52
|
+
background-color: var(--color-warning);
|
|
53
|
+
animation: statusPulse 2s var(--ease-smooth) infinite;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.statusIndicator.disconnected {
|
|
57
|
+
background-color: var(--color-error);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Status Text */
|
|
61
|
+
.statusText {
|
|
62
|
+
line-height: var(--line-height-snug);
|
|
63
|
+
letter-spacing: var(--letter-spacing-normal);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Pulse Animation for Connecting States */
|
|
67
|
+
@keyframes statusPulse {
|
|
68
|
+
0%, 100% {
|
|
69
|
+
opacity: 1;
|
|
70
|
+
transform: scale(1);
|
|
71
|
+
}
|
|
72
|
+
50% {
|
|
73
|
+
opacity: 0.5;
|
|
74
|
+
transform: scale(0.95);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Accessibility - Reduced Motion Support */
|
|
79
|
+
@media (prefers-reduced-motion: reduce) {
|
|
80
|
+
.statusIndicator.connecting,
|
|
81
|
+
.statusIndicator.reconnecting {
|
|
82
|
+
animation: none;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.connectionBadge {
|
|
86
|
+
transition: none;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useWSStore } from '../../store/wsStore';
|
|
3
|
+
import styles from './Header.module.css';
|
|
4
|
+
|
|
5
|
+
interface ConnectionStatusBadgeProps {
|
|
6
|
+
status: 'connecting' | 'connected' | 'disconnected' | 'reconnecting';
|
|
7
|
+
attempts: number;
|
|
8
|
+
error: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ConnectionStatusBadge: React.FC<ConnectionStatusBadgeProps> = ({ status, attempts, error }) => {
|
|
12
|
+
const statusText = {
|
|
13
|
+
connected: 'Connected',
|
|
14
|
+
connecting: 'Connecting...',
|
|
15
|
+
reconnecting: `Reconnecting (${attempts})...`,
|
|
16
|
+
disconnected: 'Disconnected',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const badgeClassName = [
|
|
20
|
+
styles.connectionBadge,
|
|
21
|
+
styles[status],
|
|
22
|
+
error ? styles.hasError : '',
|
|
23
|
+
].filter(Boolean).join(' ');
|
|
24
|
+
|
|
25
|
+
const indicatorClassName = [
|
|
26
|
+
styles.statusIndicator,
|
|
27
|
+
styles[status],
|
|
28
|
+
].join(' ');
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
className={badgeClassName}
|
|
33
|
+
title={error || undefined}
|
|
34
|
+
role="status"
|
|
35
|
+
aria-live="polite"
|
|
36
|
+
aria-label={`WebSocket connection status: ${statusText[status]}`}
|
|
37
|
+
>
|
|
38
|
+
<div className={indicatorClassName} aria-hidden="true" />
|
|
39
|
+
<span className={styles.statusText}>{statusText[status]}</span>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const Header: React.FC = () => {
|
|
45
|
+
const status = useWSStore((state) => state.status);
|
|
46
|
+
const attempts = useWSStore((state) => state.reconnectAttempts);
|
|
47
|
+
const error = useWSStore((state) => state.lastError);
|
|
48
|
+
|
|
49
|
+
return <ConnectionStatusBadge status={status} attempts={attempts} error={error} />;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default Header;
|