@flight-framework/devtools 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +80 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +133 -9
- package/dist/index.js.map +1 -1
- package/dist/instrumentation.d.ts +13 -22
- package/dist/instrumentation.d.ts.map +1 -1
- package/dist/instrumentation.js +250 -89
- package/dist/instrumentation.js.map +1 -1
- package/dist/panel.d.ts +2 -6
- package/dist/panel.d.ts.map +1 -1
- package/dist/panel.js +678 -108
- package/dist/panel.js.map +1 -1
- package/package.json +1 -1
package/dist/panel.js
CHANGED
|
@@ -1,103 +1,214 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @flight-framework/devtools/panel - DevTools Panel UI
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Enhanced floating panel with collapsible sections, real-time updates,
|
|
5
|
+
* and improved data visualization.
|
|
6
6
|
*/
|
|
7
|
-
import { getDevTools } from './index.js';
|
|
7
|
+
import { getDevTools, formatBytes, formatDuration, formatRelativeTime } from './index.js';
|
|
8
8
|
// ============================================================================
|
|
9
|
-
// Panel Component
|
|
9
|
+
// Panel Component
|
|
10
10
|
// ============================================================================
|
|
11
|
-
/**
|
|
12
|
-
* Inject the DevTools panel into the page
|
|
13
|
-
* Call this in your app's entry point during development
|
|
14
|
-
*/
|
|
15
11
|
export function injectDevToolsPanel() {
|
|
16
12
|
if (typeof document === 'undefined')
|
|
17
13
|
return;
|
|
18
14
|
const devTools = getDevTools();
|
|
19
15
|
if (!devTools)
|
|
20
16
|
return;
|
|
17
|
+
// Remove existing panel if any
|
|
18
|
+
const existing = document.getElementById('flight-devtools');
|
|
19
|
+
if (existing)
|
|
20
|
+
existing.remove();
|
|
21
21
|
// Create container
|
|
22
22
|
const container = document.createElement('div');
|
|
23
23
|
container.id = 'flight-devtools';
|
|
24
24
|
document.body.appendChild(container);
|
|
25
25
|
// Inject styles
|
|
26
26
|
const styles = document.createElement('style');
|
|
27
|
+
styles.id = 'flight-devtools-styles';
|
|
27
28
|
styles.textContent = getDevToolsStyles();
|
|
28
29
|
document.head.appendChild(styles);
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// Panel state
|
|
31
|
+
const panelState = {
|
|
32
|
+
isOpen: false,
|
|
33
|
+
currentTab: 'routes',
|
|
34
|
+
routeFilter: '',
|
|
35
|
+
consoleFilter: 'all',
|
|
36
|
+
expandedItems: new Set(),
|
|
37
|
+
};
|
|
32
38
|
function render(state) {
|
|
33
39
|
const options = devTools.getOptions();
|
|
34
40
|
container.innerHTML = `
|
|
35
|
-
<div class="flight-devtools ${options.position} ${isOpen ? 'open' : ''}">
|
|
36
|
-
<button class="flight-devtools-toggle"
|
|
37
|
-
${isOpen ? '
|
|
41
|
+
<div class="flight-devtools ${options.position} ${panelState.isOpen ? 'open' : ''}">
|
|
42
|
+
<button class="flight-devtools-toggle" data-action="toggle">
|
|
43
|
+
${panelState.isOpen ? '✕' : getFlightLogo()}
|
|
38
44
|
</button>
|
|
39
|
-
${isOpen ? renderPanel(state
|
|
45
|
+
${panelState.isOpen ? renderPanel(state) : renderBadge(state)}
|
|
40
46
|
</div>
|
|
41
47
|
`;
|
|
48
|
+
// Attach event listeners
|
|
49
|
+
attachEventListeners(state);
|
|
42
50
|
}
|
|
43
|
-
function
|
|
51
|
+
function renderBadge(state) {
|
|
52
|
+
const errorCount = state.errors.length;
|
|
53
|
+
if (errorCount === 0)
|
|
54
|
+
return '';
|
|
55
|
+
return `<span class="flight-devtools-badge">${errorCount}</span>`;
|
|
56
|
+
}
|
|
57
|
+
function renderPanel(state) {
|
|
58
|
+
const tabs = [
|
|
59
|
+
{ id: 'routes', label: 'Routes', count: state.routes.length },
|
|
60
|
+
{ id: 'requests', label: 'Requests', count: state.requests.length },
|
|
61
|
+
{ id: 'adapters', label: 'Adapters', count: state.adapters.length },
|
|
62
|
+
{ id: 'performance', label: 'Perf', count: null },
|
|
63
|
+
{ id: 'console', label: 'Console', count: state.console.length + state.errors.length },
|
|
64
|
+
];
|
|
44
65
|
return `
|
|
45
66
|
<div class="flight-devtools-panel">
|
|
46
67
|
<div class="flight-devtools-header">
|
|
47
|
-
<span
|
|
68
|
+
<span class="flight-devtools-logo">✈ Flight DevTools</span>
|
|
69
|
+
<span class="flight-devtools-version">v0.0.5</span>
|
|
48
70
|
</div>
|
|
49
71
|
<div class="flight-devtools-tabs">
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
${tabs.map(tab => `
|
|
73
|
+
<button
|
|
74
|
+
class="${panelState.currentTab === tab.id ? 'active' : ''}"
|
|
75
|
+
data-action="tab"
|
|
76
|
+
data-tab="${tab.id}"
|
|
77
|
+
>
|
|
78
|
+
${tab.label}${tab.count !== null ? ` <span class="count">${tab.count}</span>` : ''}
|
|
79
|
+
</button>
|
|
80
|
+
`).join('')}
|
|
59
81
|
</div>
|
|
60
82
|
<div class="flight-devtools-content">
|
|
61
|
-
${
|
|
62
|
-
${tab === 'requests' ? renderRequests(state.requests) : ''}
|
|
63
|
-
${tab === 'adapters' ? renderAdapters(state) : ''}
|
|
83
|
+
${renderTabContent(state)}
|
|
64
84
|
</div>
|
|
65
85
|
</div>
|
|
66
86
|
`;
|
|
67
87
|
}
|
|
88
|
+
function renderTabContent(state) {
|
|
89
|
+
switch (panelState.currentTab) {
|
|
90
|
+
case 'routes': return renderRoutes(state.routes);
|
|
91
|
+
case 'requests': return renderRequests(state.requests);
|
|
92
|
+
case 'adapters': return renderAdapters(state);
|
|
93
|
+
case 'performance': return renderPerformance(state);
|
|
94
|
+
case 'console': return renderConsole(state);
|
|
95
|
+
default: return '';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
68
98
|
function renderRoutes(routes) {
|
|
69
99
|
if (routes.length === 0) {
|
|
70
100
|
return '<div class="flight-devtools-empty">No routes registered</div>';
|
|
71
101
|
}
|
|
72
|
-
const
|
|
102
|
+
const filtered = panelState.routeFilter
|
|
103
|
+
? routes.filter(r => r.path.includes(panelState.routeFilter))
|
|
104
|
+
: routes;
|
|
105
|
+
const pageRoutes = filtered.filter(r => r.type === 'page');
|
|
106
|
+
const apiRoutes = filtered.filter(r => r.type === 'api');
|
|
73
107
|
return `
|
|
108
|
+
<div class="flight-devtools-toolbar">
|
|
109
|
+
<input
|
|
110
|
+
type="text"
|
|
111
|
+
placeholder="Filter routes..."
|
|
112
|
+
value="${panelState.routeFilter}"
|
|
113
|
+
data-action="filter-routes"
|
|
114
|
+
class="flight-devtools-search"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
74
117
|
<div class="flight-devtools-list">
|
|
75
|
-
${
|
|
76
|
-
<div class="flight-devtools-
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
<span class="type">${route.type}</span>
|
|
80
|
-
${route.slot ? `<span class="slot">@${route.slot}</span>` : ''}
|
|
81
|
-
${route.interceptInfo ? `<span class="intercept">(${dots(route.interceptInfo.level)})</span>` : ''}
|
|
118
|
+
${pageRoutes.length > 0 ? `
|
|
119
|
+
<div class="flight-devtools-section">
|
|
120
|
+
<div class="flight-devtools-section-title">Pages (${pageRoutes.length})</div>
|
|
121
|
+
${pageRoutes.map(route => renderRouteItem(route)).join('')}
|
|
82
122
|
</div>
|
|
83
|
-
`
|
|
123
|
+
` : ''}
|
|
124
|
+
${apiRoutes.length > 0 ? `
|
|
125
|
+
<div class="flight-devtools-section">
|
|
126
|
+
<div class="flight-devtools-section-title">API Routes (${apiRoutes.length})</div>
|
|
127
|
+
${apiRoutes.map(route => renderRouteItem(route)).join('')}
|
|
128
|
+
</div>
|
|
129
|
+
` : ''}
|
|
130
|
+
</div>
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
function renderRouteItem(route) {
|
|
134
|
+
const isExpanded = panelState.expandedItems.has(route.path);
|
|
135
|
+
const hasParams = route.path.includes(':') || route.path.includes('[');
|
|
136
|
+
return `
|
|
137
|
+
<div class="flight-devtools-item ${isExpanded ? 'expanded' : ''}" data-action="toggle-item" data-id="${route.path}">
|
|
138
|
+
<div class="flight-devtools-item-header">
|
|
139
|
+
<span class="method ${route.method.toLowerCase()}">${route.method}</span>
|
|
140
|
+
<span class="path">${route.path}</span>
|
|
141
|
+
${hasParams ? '<span class="badge dynamic">Dynamic</span>' : ''}
|
|
142
|
+
${route.slot ? `<span class="badge slot">@${route.slot}</span>` : ''}
|
|
143
|
+
<span class="expand-icon">${isExpanded ? '▼' : '▶'}</span>
|
|
144
|
+
</div>
|
|
145
|
+
${isExpanded ? `
|
|
146
|
+
<div class="flight-devtools-item-details">
|
|
147
|
+
<div class="detail-row">
|
|
148
|
+
<span class="detail-label">File:</span>
|
|
149
|
+
<span class="detail-value mono">${route.filePath}</span>
|
|
150
|
+
</div>
|
|
151
|
+
${route.params ? `
|
|
152
|
+
<div class="detail-row">
|
|
153
|
+
<span class="detail-label">Params:</span>
|
|
154
|
+
<span class="detail-value">${route.params.join(', ')}</span>
|
|
155
|
+
</div>
|
|
156
|
+
` : ''}
|
|
157
|
+
</div>
|
|
158
|
+
` : ''}
|
|
84
159
|
</div>
|
|
85
160
|
`;
|
|
86
161
|
}
|
|
87
162
|
function renderRequests(requests) {
|
|
88
163
|
if (requests.length === 0) {
|
|
89
|
-
return '<div class="flight-devtools-empty">No requests yet</div>';
|
|
164
|
+
return '<div class="flight-devtools-empty">No requests captured yet<br/><small>Make some fetch requests to see them here</small></div>';
|
|
90
165
|
}
|
|
91
166
|
return `
|
|
167
|
+
<div class="flight-devtools-toolbar">
|
|
168
|
+
<span class="flight-devtools-info">${requests.length} requests</span>
|
|
169
|
+
<button data-action="clear-requests" class="flight-devtools-btn-small">Clear</button>
|
|
170
|
+
</div>
|
|
92
171
|
<div class="flight-devtools-list">
|
|
93
|
-
${requests.slice(0,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
172
|
+
${requests.slice(0, 50).map(req => renderRequestItem(req)).join('')}
|
|
173
|
+
</div>
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
function renderRequestItem(req) {
|
|
177
|
+
const isExpanded = panelState.expandedItems.has(req.id);
|
|
178
|
+
const statusClass = `status-${Math.floor(req.status / 100)}xx`;
|
|
179
|
+
return `
|
|
180
|
+
<div class="flight-devtools-item ${isExpanded ? 'expanded' : ''}" data-action="toggle-item" data-id="${req.id}">
|
|
181
|
+
<div class="flight-devtools-item-header">
|
|
182
|
+
<span class="method ${req.method.toLowerCase()}">${req.method}</span>
|
|
183
|
+
<span class="path">${req.path}</span>
|
|
184
|
+
<span class="status ${statusClass}">${req.status}</span>
|
|
185
|
+
<span class="duration">${formatDuration(req.duration)}</span>
|
|
186
|
+
<span class="time">${formatRelativeTime(req.timestamp)}</span>
|
|
187
|
+
</div>
|
|
188
|
+
${isExpanded ? `
|
|
189
|
+
<div class="flight-devtools-item-details">
|
|
190
|
+
<div class="detail-row">
|
|
191
|
+
<span class="detail-label">Duration:</span>
|
|
192
|
+
<span class="detail-value">${formatDuration(req.duration)}</span>
|
|
193
|
+
</div>
|
|
194
|
+
${req.size !== undefined ? `
|
|
195
|
+
<div class="detail-row">
|
|
196
|
+
<span class="detail-label">Size:</span>
|
|
197
|
+
<span class="detail-value">${formatBytes(req.size)}</span>
|
|
198
|
+
</div>
|
|
199
|
+
` : ''}
|
|
200
|
+
${req.type ? `
|
|
201
|
+
<div class="detail-row">
|
|
202
|
+
<span class="detail-label">Type:</span>
|
|
203
|
+
<span class="detail-value">${req.type}</span>
|
|
204
|
+
</div>
|
|
205
|
+
` : ''}
|
|
206
|
+
<div class="detail-row">
|
|
207
|
+
<span class="detail-label">Time:</span>
|
|
208
|
+
<span class="detail-value">${new Date(req.timestamp).toLocaleTimeString()}</span>
|
|
209
|
+
</div>
|
|
99
210
|
</div>
|
|
100
|
-
`
|
|
211
|
+
` : ''}
|
|
101
212
|
</div>
|
|
102
213
|
`;
|
|
103
214
|
}
|
|
@@ -109,160 +220,619 @@ export function injectDevToolsPanel() {
|
|
|
109
220
|
<div class="flight-devtools-list">
|
|
110
221
|
${state.adapters.map(adapter => `
|
|
111
222
|
<div class="flight-devtools-item">
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
223
|
+
<div class="flight-devtools-item-header">
|
|
224
|
+
<span class="adapter-icon">${getAdapterIcon(adapter.type)}</span>
|
|
225
|
+
<span class="adapter-name">${adapter.name}</span>
|
|
226
|
+
<span class="adapter-type">${adapter.type}</span>
|
|
227
|
+
<span class="adapter-status status-${adapter.status}">${adapter.status}</span>
|
|
228
|
+
</div>
|
|
115
229
|
</div>
|
|
116
230
|
`).join('')}
|
|
117
231
|
</div>
|
|
118
232
|
`;
|
|
119
233
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
234
|
+
function getAdapterIcon(type) {
|
|
235
|
+
const icons = {
|
|
236
|
+
db: '🗄️',
|
|
237
|
+
auth: '🔐',
|
|
238
|
+
storage: '📦',
|
|
239
|
+
email: '✉️',
|
|
240
|
+
payments: '💳',
|
|
241
|
+
realtime: '⚡',
|
|
242
|
+
other: '🔌',
|
|
243
|
+
};
|
|
244
|
+
return icons[type] || '🔌';
|
|
245
|
+
}
|
|
246
|
+
function renderPerformance(state) {
|
|
247
|
+
const perf = state.performance;
|
|
248
|
+
const hasMetrics = Object.keys(perf).some(k => perf[k] !== undefined);
|
|
249
|
+
if (!hasMetrics) {
|
|
250
|
+
return '<div class="flight-devtools-empty">Performance metrics will appear after page load</div>';
|
|
251
|
+
}
|
|
252
|
+
const metrics = [
|
|
253
|
+
{ label: 'LCP', value: perf.lcp, unit: 'ms', good: 2500, desc: 'Largest Contentful Paint' },
|
|
254
|
+
{ label: 'FID', value: perf.fid, unit: 'ms', good: 100, desc: 'First Input Delay' },
|
|
255
|
+
{ label: 'CLS', value: perf.cls, unit: '', good: 0.1, desc: 'Cumulative Layout Shift' },
|
|
256
|
+
{ label: 'FCP', value: perf.fcp, unit: 'ms', good: 1800, desc: 'First Contentful Paint' },
|
|
257
|
+
{ label: 'TTFB', value: perf.ttfb, unit: 'ms', good: 800, desc: 'Time to First Byte' },
|
|
258
|
+
].filter(m => m.value !== undefined);
|
|
259
|
+
return `
|
|
260
|
+
<div class="flight-devtools-list">
|
|
261
|
+
<div class="flight-devtools-section-title">Core Web Vitals</div>
|
|
262
|
+
<div class="flight-devtools-metrics">
|
|
263
|
+
${metrics.map(m => {
|
|
264
|
+
const status = m.value <= m.good ? 'good' : m.value <= m.good * 2 ? 'needs-improvement' : 'poor';
|
|
265
|
+
return `
|
|
266
|
+
<div class="flight-devtools-metric ${status}">
|
|
267
|
+
<div class="metric-label">${m.label}</div>
|
|
268
|
+
<div class="metric-value">${m.unit ? formatDuration(m.value) : m.value.toFixed(3)}</div>
|
|
269
|
+
<div class="metric-desc">${m.desc}</div>
|
|
270
|
+
</div>
|
|
271
|
+
`;
|
|
272
|
+
}).join('')}
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
`;
|
|
276
|
+
}
|
|
277
|
+
function renderConsole(state) {
|
|
278
|
+
const entries = [
|
|
279
|
+
...state.errors.map(e => ({ id: e.id, level: 'error', message: e.message, timestamp: e.timestamp, stack: e.stack })),
|
|
280
|
+
...state.console.map(c => ({ id: c.id, level: c.level, message: c.message, timestamp: c.timestamp, stack: c.stack })),
|
|
281
|
+
].sort((a, b) => b.timestamp - a.timestamp);
|
|
282
|
+
const filtered = panelState.consoleFilter === 'all'
|
|
283
|
+
? entries
|
|
284
|
+
: entries.filter(e => e.level === panelState.consoleFilter);
|
|
285
|
+
return `
|
|
286
|
+
<div class="flight-devtools-toolbar">
|
|
287
|
+
<div class="flight-devtools-filter-group">
|
|
288
|
+
${['all', 'log', 'warn', 'error'].map(f => `
|
|
289
|
+
<button
|
|
290
|
+
class="flight-devtools-filter-btn ${panelState.consoleFilter === f ? 'active' : ''}"
|
|
291
|
+
data-action="filter-console"
|
|
292
|
+
data-filter="${f}"
|
|
293
|
+
>${f}</button>
|
|
294
|
+
`).join('')}
|
|
295
|
+
</div>
|
|
296
|
+
<button data-action="clear-console" class="flight-devtools-btn-small">Clear</button>
|
|
297
|
+
</div>
|
|
298
|
+
<div class="flight-devtools-list console-list">
|
|
299
|
+
${filtered.length === 0
|
|
300
|
+
? '<div class="flight-devtools-empty">No console entries</div>'
|
|
301
|
+
: filtered.slice(0, 100).map(entry => `
|
|
302
|
+
<div class="flight-devtools-console-item level-${entry.level}">
|
|
303
|
+
<span class="console-time">${formatRelativeTime(entry.timestamp)}</span>
|
|
304
|
+
<span class="console-level">${entry.level}</span>
|
|
305
|
+
<span class="console-message">${escapeHtml(entry.message)}</span>
|
|
306
|
+
</div>
|
|
307
|
+
`).join('')}
|
|
308
|
+
</div>
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
function escapeHtml(text) {
|
|
312
|
+
const div = document.createElement('div');
|
|
313
|
+
div.textContent = text;
|
|
314
|
+
return div.innerHTML;
|
|
315
|
+
}
|
|
316
|
+
function attachEventListeners(state) {
|
|
317
|
+
container.querySelectorAll('[data-action]').forEach(el => {
|
|
318
|
+
const action = el.getAttribute('data-action');
|
|
319
|
+
el.addEventListener('click', (e) => {
|
|
320
|
+
e.stopPropagation();
|
|
321
|
+
switch (action) {
|
|
322
|
+
case 'toggle':
|
|
323
|
+
panelState.isOpen = !panelState.isOpen;
|
|
324
|
+
break;
|
|
325
|
+
case 'tab':
|
|
326
|
+
panelState.currentTab = el.getAttribute('data-tab');
|
|
327
|
+
break;
|
|
328
|
+
case 'toggle-item':
|
|
329
|
+
const id = el.getAttribute('data-id');
|
|
330
|
+
if (panelState.expandedItems.has(id)) {
|
|
331
|
+
panelState.expandedItems.delete(id);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
panelState.expandedItems.add(id);
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
case 'clear-requests':
|
|
338
|
+
devTools.clearRequests();
|
|
339
|
+
break;
|
|
340
|
+
case 'clear-console':
|
|
341
|
+
devTools.clearConsole();
|
|
342
|
+
devTools.clearErrors();
|
|
343
|
+
break;
|
|
344
|
+
case 'filter-console':
|
|
345
|
+
panelState.consoleFilter = el.getAttribute('data-filter');
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
render(devTools.getState());
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
// Route filter input
|
|
352
|
+
const filterInput = container.querySelector('[data-action="filter-routes"]');
|
|
353
|
+
if (filterInput) {
|
|
354
|
+
filterInput.addEventListener('input', (e) => {
|
|
355
|
+
panelState.routeFilter = e.target.value;
|
|
356
|
+
render(devTools.getState());
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
129
360
|
// Subscribe to state changes
|
|
130
361
|
devTools.subscribe(render);
|
|
131
362
|
}
|
|
132
363
|
// ============================================================================
|
|
364
|
+
// Logo
|
|
365
|
+
// ============================================================================
|
|
366
|
+
function getFlightLogo() {
|
|
367
|
+
// Simplified Flight logo SVG for the button
|
|
368
|
+
return `<svg viewBox="50 100 500 500" width="28" height="28" fill="currentColor">
|
|
369
|
+
<g transform="translate(0,800) scale(0.1,-0.1)">
|
|
370
|
+
<path d="M 4056 6148 c31 -40 81 -114 110 -165 53 -89 119 -231 112 -239 -2 -2 -23
|
|
371
|
+
6 -45 17 -133 65 -342 65 -475 0 -26 -13 -38 -15 -38 -6 0 18 94 206 136 272
|
|
372
|
+
34 55 135 193 140 193 2 0 28 -33 60 -72z m90 -449 c34 -11 90 -39 125 -62
|
|
373
|
+
l64 -43 24 -99 c13 -55 25 -106 27 -112 3 -10 107 -13 480 -13 l476 0 -5 -22
|
|
374
|
+
c-3 -13 -18 -88 -32 -167 -14 -80 -28 -148 -31 -153 -3 -4 -182 -8 -399 -8
|
|
375
|
+
l-393 0 -27 -82 c-86 -263 -277 -536 -531 -758 -131 -114 -245 -201 -484 -370
|
|
376
|
+
-96 -67 -222 -159 -280 -204 -143 -110 -364 -332 -428 -429 -62 -94 -67 -84
|
|
377
|
+
-11 25 120 240 288 420 669 719 268 209 377 305 511 444 180 188 290 363 352
|
|
378
|
+
561 41 134 39 143 -11 44 -98 -195 -226 -366 -406 -545 -112 -111 -204 -191
|
|
379
|
+
-210 -184 -13 15 -39 379 -43 601 -6 283 3 419 37 588 33 162 33 160 102 207
|
|
380
|
+
134 91 274 111 424 62z"/>
|
|
381
|
+
<path d="M3938 5509 c-57 -16 -114 -71 -133 -130 -29 -86 -4 -168 67 -222 136
|
|
382
|
+
-103 328 -10 328 160 0 132 -131 228 -262 192z"/>
|
|
383
|
+
</g>
|
|
384
|
+
</svg>`;
|
|
385
|
+
}
|
|
386
|
+
// ============================================================================
|
|
133
387
|
// Styles
|
|
134
388
|
// ============================================================================
|
|
135
389
|
function getDevToolsStyles() {
|
|
136
390
|
return `
|
|
137
391
|
#flight-devtools {
|
|
138
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
392
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
|
139
393
|
font-size: 12px;
|
|
394
|
+
line-height: 1.4;
|
|
395
|
+
--green: #22c55e;
|
|
396
|
+
--green-dark: #16a34a;
|
|
397
|
+
--bg: #0f0f0f;
|
|
398
|
+
--bg-2: #1a1a1a;
|
|
399
|
+
--bg-3: #252525;
|
|
400
|
+
--border: #333;
|
|
401
|
+
--text: #e5e5e5;
|
|
402
|
+
--text-muted: #888;
|
|
140
403
|
}
|
|
141
404
|
|
|
142
405
|
.flight-devtools {
|
|
143
406
|
position: fixed;
|
|
144
|
-
z-index:
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.flight-devtools.bottom-right {
|
|
148
|
-
bottom: 16px;
|
|
149
|
-
right: 16px;
|
|
407
|
+
z-index: 999999;
|
|
150
408
|
}
|
|
151
409
|
|
|
152
|
-
.flight-devtools.bottom-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
410
|
+
.flight-devtools.bottom-right { bottom: 16px; right: 16px; }
|
|
411
|
+
.flight-devtools.bottom-left { bottom: 16px; left: 16px; }
|
|
412
|
+
.flight-devtools.top-right { top: 16px; right: 16px; }
|
|
413
|
+
.flight-devtools.top-left { top: 16px; left: 16px; }
|
|
156
414
|
|
|
157
415
|
.flight-devtools-toggle {
|
|
158
416
|
width: 48px;
|
|
159
417
|
height: 48px;
|
|
160
418
|
border-radius: 50%;
|
|
161
419
|
border: none;
|
|
162
|
-
background:
|
|
420
|
+
background: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%);
|
|
163
421
|
color: white;
|
|
164
|
-
font-
|
|
422
|
+
font-size: 20px;
|
|
165
423
|
cursor: pointer;
|
|
166
|
-
box-shadow: 0 4px
|
|
424
|
+
box-shadow: 0 4px 20px rgba(34, 197, 94, 0.4);
|
|
425
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.flight-devtools-toggle:hover {
|
|
429
|
+
transform: scale(1.05);
|
|
430
|
+
box-shadow: 0 6px 24px rgba(34, 197, 94, 0.5);
|
|
167
431
|
}
|
|
168
432
|
|
|
169
433
|
.flight-devtools.open .flight-devtools-toggle {
|
|
170
|
-
border-radius: 50%;
|
|
171
434
|
position: absolute;
|
|
172
|
-
top:
|
|
173
|
-
right:
|
|
174
|
-
width:
|
|
175
|
-
height:
|
|
435
|
+
top: 12px;
|
|
436
|
+
right: 12px;
|
|
437
|
+
width: 28px;
|
|
438
|
+
height: 28px;
|
|
176
439
|
font-size: 14px;
|
|
440
|
+
background: var(--bg-3);
|
|
441
|
+
box-shadow: none;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.flight-devtools-badge {
|
|
445
|
+
position: absolute;
|
|
446
|
+
top: -4px;
|
|
447
|
+
right: -4px;
|
|
448
|
+
width: 18px;
|
|
449
|
+
height: 18px;
|
|
450
|
+
border-radius: 50%;
|
|
451
|
+
background: #ef4444;
|
|
452
|
+
color: white;
|
|
453
|
+
font-size: 10px;
|
|
454
|
+
font-weight: 600;
|
|
455
|
+
display: flex;
|
|
456
|
+
align-items: center;
|
|
457
|
+
justify-content: center;
|
|
177
458
|
}
|
|
178
459
|
|
|
179
460
|
.flight-devtools-panel {
|
|
180
461
|
position: absolute;
|
|
181
462
|
bottom: 60px;
|
|
182
463
|
right: 0;
|
|
183
|
-
width:
|
|
184
|
-
max-height:
|
|
185
|
-
background:
|
|
464
|
+
width: 480px;
|
|
465
|
+
max-height: 600px;
|
|
466
|
+
background: var(--bg);
|
|
467
|
+
border: 1px solid var(--border);
|
|
186
468
|
border-radius: 12px;
|
|
187
469
|
overflow: hidden;
|
|
188
|
-
box-shadow: 0 8px
|
|
470
|
+
box-shadow: 0 8px 40px rgba(0,0,0,0.5);
|
|
471
|
+
display: flex;
|
|
472
|
+
flex-direction: column;
|
|
189
473
|
}
|
|
190
474
|
|
|
191
475
|
.flight-devtools-header {
|
|
192
|
-
padding: 12px 16px;
|
|
193
|
-
background:
|
|
476
|
+
padding: 12px 48px 12px 16px;
|
|
477
|
+
background: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%);
|
|
194
478
|
color: white;
|
|
479
|
+
display: flex;
|
|
480
|
+
align-items: center;
|
|
481
|
+
justify-content: space-between;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.flight-devtools-logo {
|
|
195
485
|
font-weight: 600;
|
|
486
|
+
font-size: 14px;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.flight-devtools-version {
|
|
490
|
+
font-size: 10px;
|
|
491
|
+
opacity: 0.8;
|
|
196
492
|
}
|
|
197
493
|
|
|
198
494
|
.flight-devtools-tabs {
|
|
199
495
|
display: flex;
|
|
200
|
-
|
|
496
|
+
background: var(--bg-2);
|
|
497
|
+
border-bottom: 1px solid var(--border);
|
|
201
498
|
}
|
|
202
499
|
|
|
203
500
|
.flight-devtools-tabs button {
|
|
204
501
|
flex: 1;
|
|
205
|
-
padding: 8px;
|
|
502
|
+
padding: 10px 8px;
|
|
206
503
|
border: none;
|
|
207
504
|
background: transparent;
|
|
208
|
-
color:
|
|
505
|
+
color: var(--text-muted);
|
|
506
|
+
font-size: 11px;
|
|
209
507
|
cursor: pointer;
|
|
508
|
+
border-bottom: 2px solid transparent;
|
|
509
|
+
transition: all 0.2s;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.flight-devtools-tabs button:hover {
|
|
513
|
+
color: var(--text);
|
|
514
|
+
background: var(--bg-3);
|
|
210
515
|
}
|
|
211
516
|
|
|
212
517
|
.flight-devtools-tabs button.active {
|
|
213
|
-
color:
|
|
214
|
-
border-bottom:
|
|
518
|
+
color: var(--green);
|
|
519
|
+
border-bottom-color: var(--green);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.flight-devtools-tabs .count {
|
|
523
|
+
display: inline-block;
|
|
524
|
+
min-width: 18px;
|
|
525
|
+
padding: 1px 5px;
|
|
526
|
+
margin-left: 4px;
|
|
527
|
+
background: var(--bg-3);
|
|
528
|
+
border-radius: 10px;
|
|
529
|
+
font-size: 10px;
|
|
215
530
|
}
|
|
216
531
|
|
|
217
532
|
.flight-devtools-content {
|
|
218
|
-
|
|
533
|
+
flex: 1;
|
|
219
534
|
overflow-y: auto;
|
|
535
|
+
max-height: 480px;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.flight-devtools-toolbar {
|
|
539
|
+
display: flex;
|
|
540
|
+
align-items: center;
|
|
541
|
+
justify-content: space-between;
|
|
542
|
+
padding: 8px 12px;
|
|
543
|
+
background: var(--bg-2);
|
|
544
|
+
border-bottom: 1px solid var(--border);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.flight-devtools-search {
|
|
548
|
+
flex: 1;
|
|
549
|
+
padding: 6px 10px;
|
|
550
|
+
background: var(--bg-3);
|
|
551
|
+
border: 1px solid var(--border);
|
|
552
|
+
border-radius: 6px;
|
|
553
|
+
color: var(--text);
|
|
554
|
+
font-size: 12px;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.flight-devtools-search:focus {
|
|
558
|
+
outline: none;
|
|
559
|
+
border-color: var(--green);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.flight-devtools-btn-small {
|
|
563
|
+
padding: 4px 10px;
|
|
564
|
+
background: var(--bg-3);
|
|
565
|
+
border: 1px solid var(--border);
|
|
566
|
+
border-radius: 4px;
|
|
567
|
+
color: var(--text-muted);
|
|
568
|
+
font-size: 11px;
|
|
569
|
+
cursor: pointer;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.flight-devtools-btn-small:hover {
|
|
573
|
+
background: var(--border);
|
|
574
|
+
color: var(--text);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.flight-devtools-info {
|
|
578
|
+
color: var(--text-muted);
|
|
579
|
+
font-size: 11px;
|
|
220
580
|
}
|
|
221
581
|
|
|
222
582
|
.flight-devtools-list {
|
|
223
583
|
padding: 8px;
|
|
224
584
|
}
|
|
225
585
|
|
|
586
|
+
.flight-devtools-section {
|
|
587
|
+
margin-bottom: 16px;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.flight-devtools-section-title {
|
|
591
|
+
padding: 8px 8px 4px;
|
|
592
|
+
color: var(--text-muted);
|
|
593
|
+
font-size: 10px;
|
|
594
|
+
font-weight: 600;
|
|
595
|
+
text-transform: uppercase;
|
|
596
|
+
letter-spacing: 0.5px;
|
|
597
|
+
}
|
|
598
|
+
|
|
226
599
|
.flight-devtools-item {
|
|
600
|
+
padding: 8px 10px;
|
|
601
|
+
border-radius: 6px;
|
|
602
|
+
cursor: pointer;
|
|
603
|
+
transition: background 0.15s;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.flight-devtools-item:hover {
|
|
607
|
+
background: var(--bg-2);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.flight-devtools-item.expanded {
|
|
611
|
+
background: var(--bg-2);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.flight-devtools-item-header {
|
|
227
615
|
display: flex;
|
|
616
|
+
align-items: center;
|
|
228
617
|
gap: 8px;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.flight-devtools-item-details {
|
|
621
|
+
margin-top: 8px;
|
|
229
622
|
padding: 8px;
|
|
623
|
+
background: var(--bg-3);
|
|
230
624
|
border-radius: 4px;
|
|
231
|
-
color: #ccc;
|
|
232
625
|
}
|
|
233
626
|
|
|
234
|
-
.
|
|
235
|
-
|
|
627
|
+
.detail-row {
|
|
628
|
+
display: flex;
|
|
629
|
+
padding: 3px 0;
|
|
236
630
|
}
|
|
237
631
|
|
|
238
|
-
.
|
|
239
|
-
width:
|
|
632
|
+
.detail-label {
|
|
633
|
+
width: 80px;
|
|
634
|
+
color: var(--text-muted);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.detail-value {
|
|
638
|
+
flex: 1;
|
|
639
|
+
color: var(--text);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.detail-value.mono {
|
|
643
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
644
|
+
font-size: 11px;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.method {
|
|
240
648
|
font-weight: 600;
|
|
649
|
+
font-size: 10px;
|
|
650
|
+
padding: 2px 6px;
|
|
651
|
+
border-radius: 3px;
|
|
652
|
+
background: var(--bg-3);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.method.get { color: #22c55e; }
|
|
656
|
+
.method.post { color: #3b82f6; }
|
|
657
|
+
.method.put { color: #f59e0b; }
|
|
658
|
+
.method.patch { color: #f59e0b; }
|
|
659
|
+
.method.delete { color: #ef4444; }
|
|
660
|
+
|
|
661
|
+
.path {
|
|
662
|
+
flex: 1;
|
|
663
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
664
|
+
font-size: 11px;
|
|
665
|
+
color: var(--text);
|
|
666
|
+
overflow: hidden;
|
|
667
|
+
text-overflow: ellipsis;
|
|
668
|
+
white-space: nowrap;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.badge {
|
|
672
|
+
font-size: 9px;
|
|
673
|
+
padding: 2px 5px;
|
|
674
|
+
border-radius: 3px;
|
|
675
|
+
background: var(--bg-3);
|
|
676
|
+
color: var(--text-muted);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.badge.dynamic { color: #a78bfa; }
|
|
680
|
+
.badge.slot { color: #60a5fa; }
|
|
681
|
+
|
|
682
|
+
.expand-icon {
|
|
683
|
+
color: var(--text-muted);
|
|
684
|
+
font-size: 8px;
|
|
241
685
|
}
|
|
242
686
|
|
|
243
|
-
.
|
|
244
|
-
.
|
|
245
|
-
.
|
|
246
|
-
.
|
|
687
|
+
.status { font-weight: 600; }
|
|
688
|
+
.status.status-2xx { color: #22c55e; }
|
|
689
|
+
.status.status-3xx { color: #f59e0b; }
|
|
690
|
+
.status.status-4xx { color: #ef4444; }
|
|
691
|
+
.status.status-5xx { color: #a855f7; }
|
|
247
692
|
|
|
248
|
-
.
|
|
693
|
+
.duration, .time {
|
|
694
|
+
color: var(--text-muted);
|
|
695
|
+
font-size: 11px;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.adapter-icon {
|
|
699
|
+
font-size: 16px;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.adapter-name {
|
|
249
703
|
flex: 1;
|
|
250
|
-
font-
|
|
704
|
+
font-weight: 500;
|
|
251
705
|
}
|
|
252
706
|
|
|
253
|
-
.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
707
|
+
.adapter-type {
|
|
708
|
+
color: var(--text-muted);
|
|
709
|
+
font-size: 10px;
|
|
710
|
+
}
|
|
257
711
|
|
|
258
|
-
.
|
|
259
|
-
|
|
712
|
+
.adapter-status {
|
|
713
|
+
font-size: 10px;
|
|
714
|
+
padding: 2px 6px;
|
|
715
|
+
border-radius: 3px;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.adapter-status.status-connected { background: rgba(34, 197, 94, 0.2); color: #22c55e; }
|
|
719
|
+
.adapter-status.status-disconnected { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
|
|
720
|
+
.adapter-status.status-error { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
|
|
721
|
+
|
|
722
|
+
.flight-devtools-metrics {
|
|
723
|
+
display: grid;
|
|
724
|
+
grid-template-columns: repeat(3, 1fr);
|
|
725
|
+
gap: 8px;
|
|
726
|
+
padding: 8px;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.flight-devtools-metric {
|
|
730
|
+
padding: 12px;
|
|
731
|
+
background: var(--bg-2);
|
|
732
|
+
border-radius: 8px;
|
|
733
|
+
text-align: center;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.flight-devtools-metric.good { border-left: 3px solid #22c55e; }
|
|
737
|
+
.flight-devtools-metric.needs-improvement { border-left: 3px solid #f59e0b; }
|
|
738
|
+
.flight-devtools-metric.poor { border-left: 3px solid #ef4444; }
|
|
739
|
+
|
|
740
|
+
.metric-label {
|
|
741
|
+
font-weight: 600;
|
|
742
|
+
font-size: 11px;
|
|
743
|
+
color: var(--text);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.metric-value {
|
|
747
|
+
font-size: 18px;
|
|
748
|
+
font-weight: 600;
|
|
749
|
+
margin: 4px 0;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.flight-devtools-metric.good .metric-value { color: #22c55e; }
|
|
753
|
+
.flight-devtools-metric.needs-improvement .metric-value { color: #f59e0b; }
|
|
754
|
+
.flight-devtools-metric.poor .metric-value { color: #ef4444; }
|
|
755
|
+
|
|
756
|
+
.metric-desc {
|
|
757
|
+
font-size: 9px;
|
|
758
|
+
color: var(--text-muted);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.flight-devtools-filter-group {
|
|
762
|
+
display: flex;
|
|
763
|
+
gap: 4px;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.flight-devtools-filter-btn {
|
|
767
|
+
padding: 4px 8px;
|
|
768
|
+
background: transparent;
|
|
769
|
+
border: 1px solid var(--border);
|
|
770
|
+
border-radius: 4px;
|
|
771
|
+
color: var(--text-muted);
|
|
772
|
+
font-size: 10px;
|
|
773
|
+
cursor: pointer;
|
|
774
|
+
text-transform: capitalize;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.flight-devtools-filter-btn.active {
|
|
778
|
+
background: var(--green);
|
|
779
|
+
border-color: var(--green);
|
|
780
|
+
color: white;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.console-list {
|
|
784
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
785
|
+
font-size: 11px;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.flight-devtools-console-item {
|
|
789
|
+
display: flex;
|
|
790
|
+
gap: 8px;
|
|
791
|
+
padding: 6px 8px;
|
|
792
|
+
border-bottom: 1px solid var(--border);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.flight-devtools-console-item.level-error {
|
|
796
|
+
background: rgba(239, 68, 68, 0.1);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.flight-devtools-console-item.level-warn {
|
|
800
|
+
background: rgba(245, 158, 11, 0.1);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.console-time {
|
|
804
|
+
color: var(--text-muted);
|
|
805
|
+
flex-shrink: 0;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.console-level {
|
|
809
|
+
flex-shrink: 0;
|
|
810
|
+
width: 40px;
|
|
811
|
+
font-weight: 600;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
.level-log .console-level { color: #888; }
|
|
815
|
+
.level-info .console-level { color: #3b82f6; }
|
|
816
|
+
.level-warn .console-level { color: #f59e0b; }
|
|
817
|
+
.level-error .console-level { color: #ef4444; }
|
|
818
|
+
|
|
819
|
+
.console-message {
|
|
820
|
+
flex: 1;
|
|
821
|
+
word-break: break-word;
|
|
822
|
+
color: var(--text);
|
|
260
823
|
}
|
|
261
824
|
|
|
262
825
|
.flight-devtools-empty {
|
|
263
|
-
padding:
|
|
826
|
+
padding: 40px 20px;
|
|
264
827
|
text-align: center;
|
|
265
|
-
color:
|
|
828
|
+
color: var(--text-muted);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.flight-devtools-empty small {
|
|
832
|
+
display: block;
|
|
833
|
+
margin-top: 8px;
|
|
834
|
+
font-size: 11px;
|
|
835
|
+
opacity: 0.7;
|
|
266
836
|
}
|
|
267
837
|
`;
|
|
268
838
|
}
|