@geekmidas/studio 2.0.1 → 10.0.0-alpha.0

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 (50) hide show
  1. package/package.json +8 -5
  2. package/CHANGELOG.md +0 -60
  3. package/src/Studio.ts +0 -367
  4. package/src/__tests__/Studio.spec.ts +0 -447
  5. package/src/data/DataBrowser.ts +0 -170
  6. package/src/data/__tests__/DataBrowser.integration.spec.ts +0 -418
  7. package/src/data/__tests__/filtering.integration.spec.ts +0 -741
  8. package/src/data/__tests__/introspection.integration.spec.ts +0 -352
  9. package/src/data/__tests__/pagination.spec.ts +0 -123
  10. package/src/data/filtering.ts +0 -191
  11. package/src/data/index.ts +0 -1
  12. package/src/data/introspection.ts +0 -220
  13. package/src/data/pagination.ts +0 -33
  14. package/src/index.ts +0 -29
  15. package/src/server/__tests__/hono.integration.spec.ts +0 -619
  16. package/src/server/hono.ts +0 -427
  17. package/src/types.ts +0 -278
  18. package/src/ui-assets.ts +0 -37
  19. package/tsconfig.json +0 -9
  20. package/tsdown.config.ts +0 -13
  21. package/ui/CHANGELOG.md +0 -26
  22. package/ui/index.html +0 -12
  23. package/ui/node_modules/.bin/tsc +0 -21
  24. package/ui/node_modules/.bin/tsserver +0 -21
  25. package/ui/node_modules/.bin/vite +0 -21
  26. package/ui/package.json +0 -30
  27. package/ui/src/App.tsx +0 -100
  28. package/ui/src/api.ts +0 -213
  29. package/ui/src/components/FilterPanel.tsx +0 -213
  30. package/ui/src/components/NavRail.tsx +0 -183
  31. package/ui/src/components/RowDetail.tsx +0 -119
  32. package/ui/src/components/StudioHeader.tsx +0 -109
  33. package/ui/src/components/TableList.tsx +0 -58
  34. package/ui/src/components/TableView.tsx +0 -564
  35. package/ui/src/main.tsx +0 -10
  36. package/ui/src/pages/DashboardPage.tsx +0 -500
  37. package/ui/src/pages/DatabasePage.tsx +0 -226
  38. package/ui/src/pages/EndpointDetailsPage.tsx +0 -288
  39. package/ui/src/pages/ExceptionsPage.tsx +0 -268
  40. package/ui/src/pages/LogsPage.tsx +0 -228
  41. package/ui/src/pages/MonitoringPage.tsx +0 -46
  42. package/ui/src/pages/PerformancePage.tsx +0 -307
  43. package/ui/src/pages/RequestsPage.tsx +0 -379
  44. package/ui/src/providers/StudioProvider.tsx +0 -194
  45. package/ui/src/styles.css +0 -105
  46. package/ui/src/types.ts +0 -174
  47. package/ui/src/vite-env.d.ts +0 -1
  48. package/ui/tsconfig.json +0 -21
  49. package/ui/tsconfig.tsbuildinfo +0 -1
  50. package/ui/vite.config.ts +0 -12
@@ -1,268 +0,0 @@
1
- import { NoResults } from '@geekmidas/ui';
2
- import { AlertTriangle, ArrowLeft, X } from 'lucide-react';
3
- import { useCallback, useEffect, useMemo, useState } from 'react';
4
- import { useNavigate, useParams } from 'react-router-dom';
5
- import * as api from '../api';
6
- import { useStudio } from '../providers/StudioProvider';
7
- import type { ExceptionEntry } from '../types';
8
-
9
- interface ExceptionFilters {
10
- search: string;
11
- }
12
-
13
- export function ExceptionsPage() {
14
- const { id } = useParams<{ id: string }>();
15
- const navigate = useNavigate();
16
- const { exceptions: realtimeExceptions } = useStudio();
17
-
18
- const [exceptions, setExceptions] = useState<ExceptionEntry[]>([]);
19
- const [selectedException, setSelectedException] =
20
- useState<ExceptionEntry | null>(null);
21
- const [loading, setLoading] = useState(true);
22
- const [filters, setFilters] = useState<ExceptionFilters>({
23
- search: '',
24
- });
25
-
26
- // Merge realtime exceptions with fetched exceptions
27
- useEffect(() => {
28
- setExceptions((prev) => {
29
- const existingIds = new Set(prev.map((e) => e.id));
30
- const newExceptions = realtimeExceptions.filter(
31
- (e) => !existingIds.has(e.id),
32
- );
33
- if (newExceptions.length > 0) {
34
- return [...newExceptions, ...prev].slice(0, 100);
35
- }
36
- return prev;
37
- });
38
- }, [realtimeExceptions]);
39
-
40
- // Load exceptions
41
- const loadExceptions = useCallback(async () => {
42
- try {
43
- setLoading(true);
44
- const data = await api.getExceptions({
45
- limit: 100,
46
- search: filters.search || undefined,
47
- });
48
- setExceptions(data);
49
- } catch (_error) {
50
- } finally {
51
- setLoading(false);
52
- }
53
- }, [filters]);
54
-
55
- useEffect(() => {
56
- loadExceptions();
57
- }, [loadExceptions]);
58
-
59
- // Load selected exception detail
60
- useEffect(() => {
61
- if (id) {
62
- const existing = exceptions.find((e) => e.id === id);
63
- if (existing) {
64
- setSelectedException(existing);
65
- } else {
66
- api.getException(id).then(setSelectedException).catch(console.error);
67
- }
68
- } else {
69
- setSelectedException(null);
70
- }
71
- }, [id, exceptions]);
72
-
73
- // Filter exceptions
74
- const filteredExceptions = useMemo(() => {
75
- return exceptions.filter((exception) => {
76
- if (filters.search) {
77
- const searchLower = filters.search.toLowerCase();
78
- if (
79
- !exception.name.toLowerCase().includes(searchLower) &&
80
- !exception.message.toLowerCase().includes(searchLower)
81
- ) {
82
- return false;
83
- }
84
- }
85
- return true;
86
- });
87
- }, [exceptions, filters]);
88
-
89
- const hasFilters = filters.search;
90
-
91
- const clearFilters = () => {
92
- setFilters({ search: '' });
93
- };
94
-
95
- return (
96
- <div className="flex h-full">
97
- {/* Exception List */}
98
- <div className="flex-1 flex flex-col overflow-hidden">
99
- {/* Filter Bar */}
100
- <div className="p-4 border-b border-border bg-surface flex items-center gap-4">
101
- <input
102
- type="text"
103
- placeholder="Search exceptions..."
104
- value={filters.search}
105
- onChange={(e) =>
106
- setFilters((f) => ({ ...f, search: e.target.value }))
107
- }
108
- className="flex-1 bg-background border border-border rounded px-3 py-1.5 text-sm focus:outline-none focus:border-accent"
109
- />
110
- {hasFilters && (
111
- <button
112
- onClick={clearFilters}
113
- className="text-sm text-muted-foreground hover:text-foreground"
114
- >
115
- Clear
116
- </button>
117
- )}
118
- </div>
119
-
120
- {/* Exception List */}
121
- <div className="flex-1 overflow-auto p-4">
122
- {loading ? (
123
- <div className="flex items-center justify-center py-16 text-muted-foreground">
124
- Loading...
125
- </div>
126
- ) : filteredExceptions.length === 0 ? (
127
- <NoResults
128
- title={hasFilters ? 'No matching exceptions' : 'No exceptions'}
129
- description={
130
- hasFilters
131
- ? 'Try adjusting your filters.'
132
- : 'Exceptions will appear here when they occur.'
133
- }
134
- />
135
- ) : (
136
- <div className="flex flex-col gap-2">
137
- {filteredExceptions.map((exception) => (
138
- <div
139
- key={exception.id}
140
- className={`bg-surface border rounded-lg p-4 cursor-pointer transition-colors hover:border-accent/50 ${
141
- selectedException?.id === exception.id
142
- ? 'border-accent'
143
- : 'border-border'
144
- }`}
145
- onClick={() =>
146
- navigate(`/monitoring/exceptions/${exception.id}`)
147
- }
148
- >
149
- <div className="flex justify-between items-center mb-1">
150
- <div className="flex items-center gap-2">
151
- <AlertTriangle className="h-4 w-4 text-red-400" />
152
- <span className="font-medium text-red-400">
153
- {exception.name}
154
- </span>
155
- </div>
156
- <span className="text-xs text-muted-foreground">
157
- {formatTime(exception.timestamp)}
158
- </span>
159
- </div>
160
- <p className="text-sm text-muted-foreground truncate pl-6">
161
- {exception.message}
162
- </p>
163
- </div>
164
- ))}
165
- </div>
166
- )}
167
- </div>
168
- </div>
169
-
170
- {/* Exception Detail Panel */}
171
- {selectedException && (
172
- <ExceptionDetailPanel
173
- exception={selectedException}
174
- onClose={() => navigate('/monitoring/exceptions')}
175
- />
176
- )}
177
- </div>
178
- );
179
- }
180
-
181
- function ExceptionDetailPanel({
182
- exception,
183
- onClose,
184
- }: {
185
- exception: ExceptionEntry;
186
- onClose: () => void;
187
- }) {
188
- return (
189
- <div className="w-[600px] border-l border-border bg-surface flex flex-col">
190
- {/* Header */}
191
- <div className="flex items-center justify-between p-4 border-b border-border">
192
- <div className="flex items-center gap-3">
193
- <button
194
- onClick={onClose}
195
- className="p-1 hover:bg-surface-hover rounded"
196
- >
197
- <ArrowLeft className="h-4 w-4" />
198
- </button>
199
- <div>
200
- <div className="flex items-center gap-2">
201
- <AlertTriangle className="h-4 w-4 text-red-400" />
202
- <span className="font-medium text-red-400">{exception.name}</span>
203
- </div>
204
- <p className="text-xs text-muted-foreground mt-1">
205
- {formatTime(exception.timestamp)}
206
- </p>
207
- </div>
208
- </div>
209
- <button
210
- onClick={onClose}
211
- className="p-1 hover:bg-surface-hover rounded"
212
- >
213
- <X className="h-4 w-4" />
214
- </button>
215
- </div>
216
-
217
- {/* Content */}
218
- <div className="flex-1 overflow-auto p-4 space-y-4">
219
- {/* Message */}
220
- <div>
221
- <h4 className="text-sm font-medium mb-2">Message</h4>
222
- <p className="text-sm bg-background rounded p-3 break-words text-red-300">
223
- {exception.message}
224
- </p>
225
- </div>
226
-
227
- {/* Request info */}
228
- {exception.request && (
229
- <div>
230
- <h4 className="text-sm font-medium mb-2">Request</h4>
231
- <p className="text-sm font-mono bg-background rounded p-3">
232
- {exception.request.method} {exception.request.path}
233
- </p>
234
- </div>
235
- )}
236
-
237
- {/* Stack trace */}
238
- {exception.stack && (
239
- <div>
240
- <h4 className="text-sm font-medium mb-2">Stack Trace</h4>
241
- <pre className="bg-background rounded p-3 text-xs overflow-auto max-h-[400px] whitespace-pre-wrap">
242
- {formatStackTrace(exception.stack)}
243
- </pre>
244
- </div>
245
- )}
246
-
247
- {/* Context */}
248
- {exception.context && Object.keys(exception.context).length > 0 && (
249
- <div>
250
- <h4 className="text-sm font-medium mb-2">Context</h4>
251
- <pre className="bg-background rounded p-3 text-xs overflow-auto max-h-[200px]">
252
- {JSON.stringify(exception.context, null, 2)}
253
- </pre>
254
- </div>
255
- )}
256
- </div>
257
- </div>
258
- );
259
- }
260
-
261
- function formatStackTrace(stack: string) {
262
- // Highlight file paths and line numbers
263
- return stack;
264
- }
265
-
266
- function formatTime(timestamp: string) {
267
- return new Date(timestamp).toLocaleString();
268
- }
@@ -1,228 +0,0 @@
1
- import { LogLevelBadge, NoResults } from '@geekmidas/ui';
2
- import { X } from 'lucide-react';
3
- import { useCallback, useEffect, useMemo, useState } from 'react';
4
- import * as api from '../api';
5
- import { useStudio } from '../providers/StudioProvider';
6
- import type { LogEntry } from '../types';
7
-
8
- interface LogFilters {
9
- search: string;
10
- level: string;
11
- }
12
-
13
- export function LogsPage() {
14
- const { logs: realtimeLogs } = useStudio();
15
-
16
- const [logs, setLogs] = useState<LogEntry[]>([]);
17
- const [selectedLog, setSelectedLog] = useState<LogEntry | null>(null);
18
- const [loading, setLoading] = useState(true);
19
- const [filters, setFilters] = useState<LogFilters>({
20
- search: '',
21
- level: '',
22
- });
23
-
24
- // Merge realtime logs with fetched logs
25
- useEffect(() => {
26
- setLogs((prev) => {
27
- const existingIds = new Set(prev.map((l) => l.id));
28
- const newLogs = realtimeLogs.filter((l) => !existingIds.has(l.id));
29
- if (newLogs.length > 0) {
30
- return [...newLogs, ...prev].slice(0, 100);
31
- }
32
- return prev;
33
- });
34
- }, [realtimeLogs]);
35
-
36
- // Load logs
37
- const loadLogs = useCallback(async () => {
38
- try {
39
- setLoading(true);
40
- const data = await api.getLogs({
41
- limit: 100,
42
- search: filters.search || undefined,
43
- level: filters.level || undefined,
44
- });
45
- setLogs(data);
46
- } catch (_error) {
47
- } finally {
48
- setLoading(false);
49
- }
50
- }, [filters]);
51
-
52
- useEffect(() => {
53
- loadLogs();
54
- }, [loadLogs]);
55
-
56
- // Filter logs
57
- const filteredLogs = useMemo(() => {
58
- return logs.filter((log) => {
59
- if (
60
- filters.search &&
61
- !log.message.toLowerCase().includes(filters.search.toLowerCase())
62
- ) {
63
- return false;
64
- }
65
- if (filters.level && log.level !== filters.level) {
66
- return false;
67
- }
68
- return true;
69
- });
70
- }, [logs, filters]);
71
-
72
- const hasFilters = filters.search || filters.level;
73
-
74
- const clearFilters = () => {
75
- setFilters({ search: '', level: '' });
76
- };
77
-
78
- return (
79
- <div className="flex h-full">
80
- {/* Log List */}
81
- <div className="flex-1 flex flex-col overflow-hidden">
82
- {/* Filter Bar */}
83
- <div className="p-4 border-b border-border bg-surface flex items-center gap-4">
84
- <input
85
- type="text"
86
- placeholder="Search logs..."
87
- value={filters.search}
88
- onChange={(e) =>
89
- setFilters((f) => ({ ...f, search: e.target.value }))
90
- }
91
- className="flex-1 bg-background border border-border rounded px-3 py-1.5 text-sm focus:outline-none focus:border-accent"
92
- />
93
- <select
94
- value={filters.level}
95
- onChange={(e) =>
96
- setFilters((f) => ({ ...f, level: e.target.value }))
97
- }
98
- className="bg-background border border-border rounded px-3 py-1.5 text-sm focus:outline-none focus:border-accent"
99
- >
100
- <option value="">All Levels</option>
101
- <option value="trace">Trace</option>
102
- <option value="debug">Debug</option>
103
- <option value="info">Info</option>
104
- <option value="warn">Warn</option>
105
- <option value="error">Error</option>
106
- <option value="fatal">Fatal</option>
107
- </select>
108
- {hasFilters && (
109
- <button
110
- onClick={clearFilters}
111
- className="text-sm text-muted-foreground hover:text-foreground"
112
- >
113
- Clear
114
- </button>
115
- )}
116
- </div>
117
-
118
- {/* Log List */}
119
- <div className="flex-1 overflow-auto p-4">
120
- {loading ? (
121
- <div className="flex items-center justify-center py-16 text-muted-foreground">
122
- Loading...
123
- </div>
124
- ) : filteredLogs.length === 0 ? (
125
- <NoResults
126
- title={hasFilters ? 'No matching logs' : 'No logs yet'}
127
- description={
128
- hasFilters
129
- ? 'Try adjusting your filters.'
130
- : 'Log entries will appear here as they are recorded.'
131
- }
132
- />
133
- ) : (
134
- <div className="flex flex-col gap-2">
135
- {filteredLogs.map((log) => (
136
- <div
137
- key={log.id}
138
- className={`bg-surface border rounded-lg p-4 cursor-pointer transition-colors hover:border-accent/50 flex items-start gap-4 ${
139
- selectedLog?.id === log.id
140
- ? 'border-accent'
141
- : 'border-border'
142
- }`}
143
- onClick={() => setSelectedLog(log)}
144
- >
145
- <LogLevelBadge level={log.level as any} size="sm" />
146
- <span className="flex-1 text-sm break-words">
147
- {log.message}
148
- </span>
149
- <span className="text-xs text-muted-foreground min-w-20 text-right shrink-0">
150
- {formatTime(log.timestamp)}
151
- </span>
152
- </div>
153
- ))}
154
- </div>
155
- )}
156
- </div>
157
- </div>
158
-
159
- {/* Log Detail Panel */}
160
- {selectedLog && (
161
- <LogDetailPanel
162
- log={selectedLog}
163
- onClose={() => setSelectedLog(null)}
164
- />
165
- )}
166
- </div>
167
- );
168
- }
169
-
170
- function LogDetailPanel({
171
- log,
172
- onClose,
173
- }: {
174
- log: LogEntry;
175
- onClose: () => void;
176
- }) {
177
- return (
178
- <div className="w-[500px] border-l border-border bg-surface flex flex-col">
179
- {/* Header */}
180
- <div className="flex items-center justify-between p-4 border-b border-border">
181
- <div className="flex items-center gap-3">
182
- <LogLevelBadge level={log.level as any} size="md" />
183
- <span className="text-sm text-muted-foreground">
184
- {formatTime(log.timestamp)}
185
- </span>
186
- </div>
187
- <button
188
- onClick={onClose}
189
- className="p-1 hover:bg-surface-hover rounded"
190
- >
191
- <X className="h-4 w-4" />
192
- </button>
193
- </div>
194
-
195
- {/* Content */}
196
- <div className="flex-1 overflow-auto p-4 space-y-4">
197
- <div>
198
- <h4 className="text-sm font-medium mb-2">Message</h4>
199
- <p className="text-sm bg-background rounded p-3 break-words">
200
- {log.message}
201
- </p>
202
- </div>
203
-
204
- {log.requestId && (
205
- <div>
206
- <h4 className="text-sm font-medium mb-2">Request ID</h4>
207
- <p className="text-sm font-mono bg-background rounded p-3">
208
- {log.requestId}
209
- </p>
210
- </div>
211
- )}
212
-
213
- {log.context && Object.keys(log.context).length > 0 && (
214
- <div>
215
- <h4 className="text-sm font-medium mb-2">Context</h4>
216
- <pre className="bg-background rounded p-3 text-xs overflow-auto max-h-[400px]">
217
- {JSON.stringify(log.context, null, 2)}
218
- </pre>
219
- </div>
220
- )}
221
- </div>
222
- </div>
223
- );
224
- }
225
-
226
- function formatTime(timestamp: string) {
227
- return new Date(timestamp).toLocaleTimeString();
228
- }
@@ -1,46 +0,0 @@
1
- import { AlertTriangle, FileText, Network } from 'lucide-react';
2
- import { NavLink, Outlet, useLocation } from 'react-router-dom';
3
-
4
- const tabs = [
5
- { path: '/monitoring/requests', label: 'Requests', icon: Network },
6
- { path: '/monitoring/logs', label: 'Logs', icon: FileText },
7
- { path: '/monitoring/exceptions', label: 'Exceptions', icon: AlertTriangle },
8
- ] as const;
9
-
10
- export function MonitoringPage() {
11
- const location = useLocation();
12
-
13
- return (
14
- <div className="flex flex-col h-full">
15
- {/* Tab Bar */}
16
- <div className="bg-surface border-b border-border px-4">
17
- <nav className="flex gap-1">
18
- {tabs.map((tab) => {
19
- const Icon = tab.icon;
20
- const isActive = location.pathname.startsWith(tab.path);
21
-
22
- return (
23
- <NavLink
24
- key={tab.path}
25
- to={tab.path}
26
- className={`flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors ${
27
- isActive
28
- ? 'text-accent border-accent'
29
- : 'text-muted-foreground border-transparent hover:text-foreground hover:border-border'
30
- }`}
31
- >
32
- <Icon className="h-4 w-4" />
33
- {tab.label}
34
- </NavLink>
35
- );
36
- })}
37
- </nav>
38
- </div>
39
-
40
- {/* Content */}
41
- <div className="flex-1 overflow-hidden">
42
- <Outlet />
43
- </div>
44
- </div>
45
- );
46
- }