flock-core 0.5.0b53__py3-none-any.whl → 0.5.0b54__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.

@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import TraceModuleJaeger from './TraceModuleJaeger';
3
+ import { ModuleContext } from './ModuleRegistry';
4
+
5
+ interface TraceModuleJaegerWrapperProps {
6
+ context: ModuleContext;
7
+ }
8
+
9
+ const TraceModuleJaegerWrapper: React.FC<TraceModuleJaegerWrapperProps> = ({ context }) => {
10
+ return <TraceModuleJaeger context={context} />;
11
+ };
12
+
13
+ export default TraceModuleJaegerWrapper;
@@ -1,5 +1,6 @@
1
1
  import { moduleRegistry } from './ModuleRegistry';
2
2
  import EventLogModuleWrapper from './EventLogModuleWrapper';
3
+ import TraceModuleJaegerWrapper from './TraceModuleJaegerWrapper';
3
4
 
4
5
  /**
5
6
  * Register all available modules
@@ -15,6 +16,15 @@ export function registerModules(): void {
15
16
  component: EventLogModuleWrapper,
16
17
  });
17
18
 
19
+ // Register Trace Viewer with Timeline, Statistics, RED Metrics, and Dependencies
20
+ moduleRegistry.register({
21
+ id: 'traceViewerJaeger',
22
+ name: 'Trace Viewer',
23
+ description: 'Timeline, Statistics, RED Metrics, and Dependencies',
24
+ icon: '🔎',
25
+ component: TraceModuleJaegerWrapper,
26
+ });
27
+
18
28
  // Future modules can be registered here
19
29
  // moduleRegistry.register({ ... });
20
30
  }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Multi-Select Component with Autocomplete
3
+ *
4
+ * Elegant multi-select dropdown with:
5
+ * - Autocomplete filtering
6
+ * - Tag-based selection display
7
+ * - Keyboard navigation
8
+ * - Remove on click
9
+ */
10
+
11
+ import React, { useState, useRef, useEffect } from 'react';
12
+
13
+ interface MultiSelectProps {
14
+ options: string[];
15
+ selected: string[];
16
+ onChange: (selected: string[]) => void;
17
+ placeholder?: string;
18
+ disabled?: boolean;
19
+ }
20
+
21
+ const MultiSelect: React.FC<MultiSelectProps> = ({
22
+ options,
23
+ selected,
24
+ onChange,
25
+ placeholder = 'Select items...',
26
+ disabled = false
27
+ }) => {
28
+ const [isOpen, setIsOpen] = useState(false);
29
+ const [searchTerm, setSearchTerm] = useState('');
30
+ const [focusedIndex, setFocusedIndex] = useState(0);
31
+ const containerRef = useRef<HTMLDivElement>(null);
32
+ const inputRef = useRef<HTMLInputElement>(null);
33
+
34
+ // Filter options based on search term and exclude already selected
35
+ const filteredOptions = options.filter(
36
+ option =>
37
+ !selected.includes(option) &&
38
+ option.toLowerCase().includes(searchTerm.toLowerCase())
39
+ );
40
+
41
+ // Close dropdown when clicking outside
42
+ useEffect(() => {
43
+ const handleClickOutside = (event: MouseEvent) => {
44
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
45
+ setIsOpen(false);
46
+ setSearchTerm('');
47
+ }
48
+ };
49
+
50
+ document.addEventListener('mousedown', handleClickOutside);
51
+ return () => document.removeEventListener('mousedown', handleClickOutside);
52
+ }, []);
53
+
54
+ const handleSelect = (option: string) => {
55
+ onChange([...selected, option]);
56
+ setSearchTerm('');
57
+ setFocusedIndex(0);
58
+ inputRef.current?.focus();
59
+ };
60
+
61
+ const handleRemove = (option: string) => {
62
+ onChange(selected.filter(item => item !== option));
63
+ };
64
+
65
+ const handleKeyDown = (e: React.KeyboardEvent) => {
66
+ if (disabled) return;
67
+
68
+ switch (e.key) {
69
+ case 'ArrowDown':
70
+ e.preventDefault();
71
+ setFocusedIndex(prev => Math.min(prev + 1, filteredOptions.length - 1));
72
+ break;
73
+ case 'ArrowUp':
74
+ e.preventDefault();
75
+ setFocusedIndex(prev => Math.max(prev - 1, 0));
76
+ break;
77
+ case 'Enter':
78
+ e.preventDefault();
79
+ if (filteredOptions[focusedIndex]) {
80
+ handleSelect(filteredOptions[focusedIndex]);
81
+ }
82
+ break;
83
+ case 'Escape':
84
+ setIsOpen(false);
85
+ setSearchTerm('');
86
+ break;
87
+ case 'Backspace':
88
+ if (searchTerm === '' && selected.length > 0) {
89
+ const lastItem = selected[selected.length - 1];
90
+ if (lastItem) {
91
+ handleRemove(lastItem);
92
+ }
93
+ }
94
+ break;
95
+ }
96
+ };
97
+
98
+ return (
99
+ <div ref={containerRef} style={{ position: 'relative' }}>
100
+ {/* Input Container with Tags */}
101
+ <div
102
+ onClick={() => !disabled && inputRef.current?.focus()}
103
+ style={{
104
+ minHeight: '38px',
105
+ padding: 'var(--space-xs)',
106
+ backgroundColor: disabled ? 'rgba(0, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.3)',
107
+ border: `1px solid ${isOpen ? 'rgba(96, 165, 250, 0.5)' : 'rgba(255, 255, 255, 0.1)'}`,
108
+ borderRadius: 'var(--radius-sm)',
109
+ display: 'flex',
110
+ flexWrap: 'wrap',
111
+ gap: 'var(--gap-xs)',
112
+ cursor: disabled ? 'not-allowed' : 'text',
113
+ transition: 'border-color 0.2s',
114
+ opacity: disabled ? 0.6 : 1
115
+ }}
116
+ >
117
+ {/* Selected Tags */}
118
+ {selected.map(item => (
119
+ <div
120
+ key={item}
121
+ style={{
122
+ display: 'flex',
123
+ alignItems: 'center',
124
+ gap: 'var(--gap-xs)',
125
+ padding: '2px 8px',
126
+ backgroundColor: 'rgba(96, 165, 250, 0.2)',
127
+ border: '1px solid rgba(96, 165, 250, 0.4)',
128
+ borderRadius: 'var(--radius-sm)',
129
+ fontSize: 'var(--font-size-sm)',
130
+ color: '#60a5fa',
131
+ maxWidth: '100%',
132
+ overflow: 'hidden'
133
+ }}
134
+ >
135
+ <span style={{
136
+ overflow: 'hidden',
137
+ textOverflow: 'ellipsis',
138
+ whiteSpace: 'nowrap',
139
+ minWidth: 0
140
+ }} title={item}>
141
+ {item}
142
+ </span>
143
+ {!disabled && (
144
+ <button
145
+ onClick={(e) => {
146
+ e.stopPropagation();
147
+ handleRemove(item);
148
+ }}
149
+ style={{
150
+ background: 'none',
151
+ border: 'none',
152
+ color: '#60a5fa',
153
+ cursor: 'pointer',
154
+ padding: 0,
155
+ display: 'flex',
156
+ alignItems: 'center',
157
+ fontSize: '14px',
158
+ lineHeight: 1,
159
+ flexShrink: 0
160
+ }}
161
+ >
162
+ ×
163
+ </button>
164
+ )}
165
+ </div>
166
+ ))}
167
+
168
+ {/* Search Input */}
169
+ <input
170
+ ref={inputRef}
171
+ type="text"
172
+ value={searchTerm}
173
+ onChange={(e) => {
174
+ setSearchTerm(e.target.value);
175
+ setIsOpen(true);
176
+ setFocusedIndex(0);
177
+ }}
178
+ onFocus={() => !disabled && setIsOpen(true)}
179
+ onKeyDown={handleKeyDown}
180
+ placeholder={selected.length === 0 ? placeholder : ''}
181
+ disabled={disabled}
182
+ style={{
183
+ flex: 1,
184
+ minWidth: '120px',
185
+ background: 'transparent',
186
+ border: 'none',
187
+ outline: 'none',
188
+ color: 'var(--color-text-primary)',
189
+ fontSize: 'var(--font-size-base)',
190
+ padding: '4px'
191
+ }}
192
+ />
193
+ </div>
194
+
195
+ {/* Dropdown Options */}
196
+ {isOpen && !disabled && filteredOptions.length > 0 && (
197
+ <div
198
+ style={{
199
+ position: 'absolute',
200
+ top: 'calc(100% + 4px)',
201
+ left: 0,
202
+ right: 0,
203
+ maxHeight: '240px',
204
+ overflowY: 'auto',
205
+ backgroundColor: 'rgba(0, 0, 0, 0.9)',
206
+ border: '1px solid rgba(96, 165, 250, 0.3)',
207
+ borderRadius: 'var(--radius-sm)',
208
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.5)',
209
+ zIndex: 1000
210
+ }}
211
+ >
212
+ {filteredOptions.map((option, index) => (
213
+ <div
214
+ key={option}
215
+ onClick={() => handleSelect(option)}
216
+ onMouseEnter={() => setFocusedIndex(index)}
217
+ style={{
218
+ padding: 'var(--space-xs) var(--space-sm)',
219
+ cursor: 'pointer',
220
+ backgroundColor: index === focusedIndex ? 'rgba(96, 165, 250, 0.2)' : 'transparent',
221
+ color: index === focusedIndex ? '#60a5fa' : 'var(--color-text-primary)',
222
+ fontSize: 'var(--font-size-sm)',
223
+ transition: 'background-color 0.15s'
224
+ }}
225
+ >
226
+ {option}
227
+ </div>
228
+ ))}
229
+ </div>
230
+ )}
231
+ </div>
232
+ );
233
+ };
234
+
235
+ export default MultiSelect;
@@ -8,7 +8,7 @@
8
8
  top: 0;
9
9
  right: 0;
10
10
  bottom: 0;
11
- width: 400px;
11
+ width: 550px;
12
12
  z-index: 1000;
13
13
 
14
14
  /* Glassmorphism */
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Tracing Settings Tab
3
+ *
4
+ * Configure OpenTelemetry tracing options:
5
+ * - Enable/disable auto-tracing
6
+ * - Service whitelist (FLOCK_TRACE_SERVICES)
7
+ * - Operation blacklist (FLOCK_TRACE_IGNORE)
8
+ * - Unified workflow tracing
9
+ * - Clear trace database
10
+ * - View trace statistics
11
+ */
12
+
13
+ import React, { useState, useEffect } from 'react';
14
+ import MultiSelect from './MultiSelect';
15
+
16
+ interface TraceStats {
17
+ total_spans: number;
18
+ total_traces: number;
19
+ services_count: number;
20
+ oldest_trace: string | null;
21
+ newest_trace: string | null;
22
+ database_size_mb: number;
23
+ }
24
+
25
+ interface TraceServices {
26
+ services: string[];
27
+ operations: string[];
28
+ }
29
+
30
+ const TracingSettings: React.FC = () => {
31
+ // Tracing configuration state
32
+ const [autoTrace, setAutoTrace] = useState(false);
33
+ const [traceFile, setTraceFile] = useState(true);
34
+ const [autoWorkflow, setAutoWorkflow] = useState(false);
35
+ const [traceTtlDays, setTraceTtlDays] = useState<number | null>(30);
36
+
37
+ // Service/operation filter state
38
+ const [availableServices, setAvailableServices] = useState<string[]>([]);
39
+ const [availableOperations, setAvailableOperations] = useState<string[]>([]);
40
+ const [selectedServices, setSelectedServices] = useState<string[]>([]);
41
+ const [ignoredOperations, setIgnoredOperations] = useState<string[]>([]);
42
+
43
+ // UI state
44
+ const [stats, setStats] = useState<TraceStats | null>(null);
45
+ const [loading, setLoading] = useState(false);
46
+ const [error, setError] = useState<string | null>(null);
47
+ const [successMessage, setSuccessMessage] = useState<string | null>(null);
48
+
49
+ // Load initial data
50
+ useEffect(() => {
51
+ loadTraceServices();
52
+ loadTraceStats();
53
+ loadCurrentSettings();
54
+ }, []);
55
+
56
+ const loadTraceServices = async () => {
57
+ try {
58
+ const response = await fetch('/api/traces/services');
59
+ if (!response.ok) throw new Error('Failed to load trace services');
60
+ const data: TraceServices = await response.json();
61
+ setAvailableServices(data.services);
62
+ setAvailableOperations(data.operations);
63
+ } catch (err) {
64
+ console.error('Error loading trace services:', err);
65
+ }
66
+ };
67
+
68
+ const loadTraceStats = async () => {
69
+ try {
70
+ const response = await fetch('/api/traces/stats');
71
+ if (!response.ok) throw new Error('Failed to load trace stats');
72
+ const data: TraceStats = await response.json();
73
+ setStats(data);
74
+ } catch (err) {
75
+ console.error('Error loading trace stats:', err);
76
+ }
77
+ };
78
+
79
+ const loadCurrentSettings = async () => {
80
+ // TODO: Load current .env settings from backend
81
+ // For now, using defaults
82
+ setAutoTrace(true);
83
+ setTraceFile(true);
84
+ setAutoWorkflow(false);
85
+ setTraceTtlDays(30);
86
+ setSelectedServices(['flock', 'agent', 'dspyengine', 'outpututilitycomponent']);
87
+ setIgnoredOperations(['DashboardEventCollector.set_websocket_manager']);
88
+ };
89
+
90
+ const handleClearTraces = async () => {
91
+ if (!confirm('Clear all traces from the database? This cannot be undone.')) {
92
+ return;
93
+ }
94
+
95
+ setLoading(true);
96
+ setError(null);
97
+ setSuccessMessage(null);
98
+
99
+ try {
100
+ const response = await fetch('/api/traces/clear', { method: 'POST' });
101
+ if (!response.ok) throw new Error('Failed to clear traces');
102
+
103
+ const result = await response.json();
104
+ if (result.success) {
105
+ setSuccessMessage(`Successfully deleted ${result.deleted_count} trace spans`);
106
+ loadTraceStats(); // Refresh stats
107
+ } else {
108
+ setError(result.error || 'Failed to clear traces');
109
+ }
110
+ } catch (err) {
111
+ setError(err instanceof Error ? err.message : 'Unknown error');
112
+ } finally {
113
+ setLoading(false);
114
+ // Clear success message after 5 seconds
115
+ setTimeout(() => setSuccessMessage(null), 5000);
116
+ }
117
+ };
118
+
119
+ const handleSaveSettings = async () => {
120
+ setLoading(true);
121
+ setError(null);
122
+ setSuccessMessage(null);
123
+
124
+ try {
125
+ // TODO: Implement backend endpoint to save to .env
126
+ // For now, just show success
127
+ setSuccessMessage('Settings saved successfully (persistence coming soon)');
128
+ } catch (err) {
129
+ setError(err instanceof Error ? err.message : 'Failed to save settings');
130
+ } finally {
131
+ setLoading(false);
132
+ setTimeout(() => setSuccessMessage(null), 5000);
133
+ }
134
+ };
135
+
136
+ const formatDate = (dateStr: string | null) => {
137
+ if (!dateStr) return 'N/A';
138
+ return new Date(dateStr).toLocaleString();
139
+ };
140
+
141
+ const formatSize = (sizeMb: number) => {
142
+ if (sizeMb < 1) return `${(sizeMb * 1024).toFixed(1)} KB`;
143
+ return `${sizeMb.toFixed(2)} MB`;
144
+ };
145
+
146
+ return (
147
+ <div>
148
+ {/* Error/Success Messages */}
149
+ {error && (
150
+ <div className="settings-message settings-message-error" style={{
151
+ padding: 'var(--space-component-sm)',
152
+ marginBottom: 'var(--space-component-md)',
153
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
154
+ border: '1px solid rgba(239, 68, 68, 0.3)',
155
+ borderRadius: 'var(--radius-md)',
156
+ color: '#ef4444'
157
+ }}>
158
+ {error}
159
+ </div>
160
+ )}
161
+
162
+ {successMessage && (
163
+ <div className="settings-message settings-message-success" style={{
164
+ padding: 'var(--space-component-sm)',
165
+ marginBottom: 'var(--space-component-md)',
166
+ backgroundColor: 'rgba(16, 185, 129, 0.1)',
167
+ border: '1px solid rgba(16, 185, 129, 0.3)',
168
+ borderRadius: 'var(--radius-md)',
169
+ color: '#10b981'
170
+ }}>
171
+ {successMessage}
172
+ </div>
173
+ )}
174
+
175
+ {/* Core Tracing Toggles */}
176
+ <div className="settings-section">
177
+ <h3 className="settings-section-title">OpenTelemetry Tracing</h3>
178
+
179
+ <div className="settings-field">
180
+ <div className="settings-checkbox-wrapper">
181
+ <input
182
+ id="auto-trace"
183
+ type="checkbox"
184
+ checked={autoTrace}
185
+ onChange={(e) => setAutoTrace(e.target.checked)}
186
+ className="settings-checkbox"
187
+ />
188
+ <label htmlFor="auto-trace" className="settings-checkbox-label">
189
+ Enable auto-tracing (FLOCK_AUTO_TRACE)
190
+ </label>
191
+ </div>
192
+ <p className="settings-description">
193
+ Automatically trace all agent operations and system events with OpenTelemetry
194
+ </p>
195
+ </div>
196
+
197
+ <div className="settings-field">
198
+ <div className="settings-checkbox-wrapper">
199
+ <input
200
+ id="trace-file"
201
+ type="checkbox"
202
+ checked={traceFile}
203
+ onChange={(e) => setTraceFile(e.target.checked)}
204
+ className="settings-checkbox"
205
+ disabled={!autoTrace}
206
+ />
207
+ <label htmlFor="trace-file" className="settings-checkbox-label">
208
+ Store traces in DuckDB (FLOCK_TRACE_FILE)
209
+ </label>
210
+ </div>
211
+ <p className="settings-description">
212
+ Save traces to .flock/traces.duckdb for analysis and debugging
213
+ </p>
214
+ </div>
215
+
216
+ <div className="settings-field">
217
+ <div className="settings-checkbox-wrapper">
218
+ <input
219
+ id="auto-workflow"
220
+ type="checkbox"
221
+ checked={autoWorkflow}
222
+ onChange={(e) => setAutoWorkflow(e.target.checked)}
223
+ className="settings-checkbox"
224
+ disabled={!autoTrace}
225
+ />
226
+ <label htmlFor="auto-workflow" className="settings-checkbox-label">
227
+ Unified workflow tracing (FLOCK_AUTO_WORKFLOW_TRACE)
228
+ </label>
229
+ </div>
230
+ <p className="settings-description">
231
+ Automatically wrap operations in unified workflow traces (experimental)
232
+ </p>
233
+ </div>
234
+
235
+ <div className="settings-field">
236
+ <label htmlFor="trace-ttl" className="settings-label">
237
+ Trace Time-To-Live (FLOCK_TRACE_TTL_DAYS)
238
+ </label>
239
+ <p className="settings-description">
240
+ Auto-delete traces older than specified days (leave empty to keep forever)
241
+ </p>
242
+ <input
243
+ id="trace-ttl"
244
+ type="number"
245
+ min="1"
246
+ max="365"
247
+ value={traceTtlDays ?? ''}
248
+ onChange={(e) => setTraceTtlDays(e.target.value ? parseInt(e.target.value) : null)}
249
+ className="settings-input"
250
+ placeholder="30"
251
+ disabled={!autoTrace}
252
+ />
253
+ </div>
254
+ </div>
255
+
256
+ {/* Service Whitelist */}
257
+ <div className="settings-section">
258
+ <h3 className="settings-section-title">Service Whitelist (FLOCK_TRACE_SERVICES)</h3>
259
+ <p className="settings-description" style={{ marginBottom: 'var(--space-component-md)' }}>
260
+ Only trace specific services. Leave empty to trace all services.
261
+ </p>
262
+
263
+ <MultiSelect
264
+ options={availableServices}
265
+ selected={selectedServices}
266
+ onChange={setSelectedServices}
267
+ placeholder="Select services to trace..."
268
+ disabled={!autoTrace}
269
+ />
270
+ </div>
271
+
272
+ {/* Operation Blacklist */}
273
+ <div className="settings-section">
274
+ <h3 className="settings-section-title">Operation Blacklist (FLOCK_TRACE_IGNORE)</h3>
275
+ <p className="settings-description" style={{ marginBottom: 'var(--space-component-md)' }}>
276
+ Exclude specific operations from tracing (format: Service.method)
277
+ </p>
278
+
279
+ <MultiSelect
280
+ options={availableOperations}
281
+ selected={ignoredOperations}
282
+ onChange={setIgnoredOperations}
283
+ placeholder="Select operations to ignore..."
284
+ disabled={!autoTrace}
285
+ />
286
+ </div>
287
+
288
+ {/* Database Statistics */}
289
+ <div className="settings-section">
290
+ <h3 className="settings-section-title">Trace Database Statistics</h3>
291
+
292
+ {stats && (
293
+ <div style={{
294
+ backgroundColor: 'rgba(96, 165, 250, 0.05)',
295
+ border: '1px solid rgba(96, 165, 250, 0.2)',
296
+ borderRadius: 'var(--radius-md)',
297
+ padding: 'var(--space-component-md)'
298
+ }}>
299
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--gap-md)' }}>
300
+ <div>
301
+ <div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)', marginBottom: 'var(--space-xs)' }}>
302
+ Total Spans
303
+ </div>
304
+ <div style={{ fontSize: 'var(--font-size-lg)', fontWeight: 600 }}>
305
+ {stats.total_spans.toLocaleString()}
306
+ </div>
307
+ </div>
308
+
309
+ <div>
310
+ <div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)', marginBottom: 'var(--space-xs)' }}>
311
+ Total Traces
312
+ </div>
313
+ <div style={{ fontSize: 'var(--font-size-lg)', fontWeight: 600 }}>
314
+ {stats.total_traces.toLocaleString()}
315
+ </div>
316
+ </div>
317
+
318
+ <div>
319
+ <div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)', marginBottom: 'var(--space-xs)' }}>
320
+ Services Traced
321
+ </div>
322
+ <div style={{ fontSize: 'var(--font-size-lg)', fontWeight: 600 }}>
323
+ {stats.services_count}
324
+ </div>
325
+ </div>
326
+
327
+ <div>
328
+ <div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)', marginBottom: 'var(--space-xs)' }}>
329
+ Database Size
330
+ </div>
331
+ <div style={{ fontSize: 'var(--font-size-lg)', fontWeight: 600 }}>
332
+ {formatSize(stats.database_size_mb)}
333
+ </div>
334
+ </div>
335
+
336
+ <div>
337
+ <div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)', marginBottom: 'var(--space-xs)' }}>
338
+ Oldest Trace
339
+ </div>
340
+ <div style={{ fontSize: 'var(--font-size-xs)' }}>
341
+ {formatDate(stats.oldest_trace)}
342
+ </div>
343
+ </div>
344
+
345
+ <div>
346
+ <div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)', marginBottom: 'var(--space-xs)' }}>
347
+ Newest Trace
348
+ </div>
349
+ <div style={{ fontSize: 'var(--font-size-xs)' }}>
350
+ {formatDate(stats.newest_trace)}
351
+ </div>
352
+ </div>
353
+ </div>
354
+ </div>
355
+ )}
356
+
357
+ <div style={{ marginTop: 'var(--space-component-md)' }}>
358
+ <button
359
+ onClick={handleClearTraces}
360
+ disabled={loading || !stats || stats.total_spans === 0}
361
+ className="settings-reset-button"
362
+ style={{
363
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
364
+ border: '1px solid rgba(239, 68, 68, 0.3)',
365
+ color: '#ef4444'
366
+ }}
367
+ >
368
+ {loading ? 'Clearing...' : 'Clear All Traces'}
369
+ </button>
370
+ <p className="settings-description" style={{ marginTop: 'var(--space-xs)' }}>
371
+ Delete all spans and run VACUUM to reclaim disk space
372
+ </p>
373
+ </div>
374
+ </div>
375
+
376
+ {/* Save Settings */}
377
+ <div className="settings-section">
378
+ <button
379
+ onClick={handleSaveSettings}
380
+ disabled={loading}
381
+ className="settings-save-button"
382
+ style={{
383
+ backgroundColor: 'rgba(96, 165, 250, 0.1)',
384
+ border: '1px solid rgba(96, 165, 250, 0.3)',
385
+ color: '#60a5fa',
386
+ padding: 'var(--space-component-sm) var(--space-component-md)',
387
+ borderRadius: 'var(--radius-md)',
388
+ fontSize: 'var(--font-size-base)',
389
+ fontWeight: 500,
390
+ cursor: loading ? 'not-allowed' : 'pointer',
391
+ opacity: loading ? 0.6 : 1
392
+ }}
393
+ >
394
+ {loading ? 'Saving...' : 'Save Tracing Settings'}
395
+ </button>
396
+ <p className="settings-description" style={{ marginTop: 'var(--space-xs)' }}>
397
+ Settings will be persisted to .env file (requires backend restart)
398
+ </p>
399
+ </div>
400
+ </div>
401
+ );
402
+ };
403
+
404
+ export default TracingSettings;
@@ -4,4 +4,7 @@ export interface ModuleInstance {
4
4
  position: { x: number; y: number };
5
5
  size: { width: number; height: number };
6
6
  visible: boolean;
7
+ maximized?: boolean;
8
+ preMaximizePosition?: { x: number; y: number };
9
+ preMaximizeSize?: { width: number; height: number };
7
10
  }