@kb-labs/studio-app 0.6.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 (168) hide show
  1. package/.env.example +10 -0
  2. package/README.md +74 -0
  3. package/UI-KIT-COMPONENTS.md +783 -0
  4. package/UI-KIT-PLAN.md +326 -0
  5. package/eslint-rules/no-direct-antd-import.js +151 -0
  6. package/eslint.config.ts +27 -0
  7. package/index.html +17 -0
  8. package/package.json +72 -0
  9. package/postcss.config.js +7 -0
  10. package/public/favicon.ico +0 -0
  11. package/public/favicon.svg +7 -0
  12. package/rspack.config.mjs +118 -0
  13. package/src/App.tsx +46 -0
  14. package/src/components/devtools/devtools-log-feed.module.css +165 -0
  15. package/src/components/devtools/devtools-log-feed.tsx +300 -0
  16. package/src/components/devtools/devtools-panel.module.css +121 -0
  17. package/src/components/devtools/devtools-panel.tsx +115 -0
  18. package/src/components/empty-state.tsx +28 -0
  19. package/src/components/error-boundary.tsx +210 -0
  20. package/src/components/health-banner.module.css +103 -0
  21. package/src/components/health-banner.tsx +134 -0
  22. package/src/components/health-indicator.tsx +12 -0
  23. package/src/components/page-header.tsx +49 -0
  24. package/src/components/page-transition.tsx +24 -0
  25. package/src/components/role-switcher.tsx +148 -0
  26. package/src/components/run-badge.tsx +13 -0
  27. package/src/components/ui/icons.tsx +509 -0
  28. package/src/components/ui/index.ts +25 -0
  29. package/src/components/ui/kb-config-provider.tsx +130 -0
  30. package/src/components/ui/kb-header.module.css +51 -0
  31. package/src/components/ui/kb-header.tsx +112 -0
  32. package/src/components/ui/kb-notification-bell.module.css +103 -0
  33. package/src/components/ui/kb-notification-bell.tsx +199 -0
  34. package/src/components/ui/kb-page-layout.tsx +66 -0
  35. package/src/components/ui/kb-quick-search.module.css +86 -0
  36. package/src/components/ui/kb-quick-search.tsx +196 -0
  37. package/src/components/ui/kb-sidebar.module.css +47 -0
  38. package/src/components/ui/kb-sidebar.tsx +196 -0
  39. package/src/components/ui/kb-stat-card.module.css +21 -0
  40. package/src/components/ui/kb-stat-card.tsx +22 -0
  41. package/src/components/ui/kb-stat-grid.tsx +22 -0
  42. package/src/components/ui/kb-status-bar.module.css +49 -0
  43. package/src/components/ui/kb-status-bar.tsx +145 -0
  44. package/src/components/ui/kb-system-health-indicator.tsx +229 -0
  45. package/src/components/ui/kb-theme-toggle.tsx +42 -0
  46. package/src/components/ui/theme-adapter.ts +812 -0
  47. package/src/components/ui/theme-transition-overlay.tsx +70 -0
  48. package/src/components/ui/utils.ts +6 -0
  49. package/src/components/widget-error-boundary.tsx +172 -0
  50. package/src/config/env.ts +5 -0
  51. package/src/config/feature-flags.ts +482 -0
  52. package/src/config/studio.config.schema.ts +88 -0
  53. package/src/config/studio.config.ts +84 -0
  54. package/src/env.d.ts +23 -0
  55. package/src/hooks/use-command-palette.ts +6 -0
  56. package/src/hooks/use-devtools-store.ts +25 -0
  57. package/src/hooks/use-feature-flags.ts +101 -0
  58. package/src/index.css +10 -0
  59. package/src/layouts/dashboard-grid.tsx +122 -0
  60. package/src/layouts/index.ts +12 -0
  61. package/src/layouts/two-pane.tsx +134 -0
  62. package/src/main.tsx +15 -0
  63. package/src/modules/analytics/components/analytics-daily-stats-chart.tsx +257 -0
  64. package/src/modules/analytics/components/analytics-date-range-picker.tsx +121 -0
  65. package/src/modules/analytics/pages/analytics-cache-page.tsx +285 -0
  66. package/src/modules/analytics/pages/analytics-embeddings-page.tsx +274 -0
  67. package/src/modules/analytics/pages/analytics-events-page.tsx +425 -0
  68. package/src/modules/analytics/pages/analytics-llm-page.tsx +434 -0
  69. package/src/modules/analytics/pages/analytics-overview-page.tsx +336 -0
  70. package/src/modules/analytics/pages/analytics-page.tsx +20 -0
  71. package/src/modules/analytics/pages/analytics-storage-page.tsx +344 -0
  72. package/src/modules/analytics/pages/analytics-vectorstore-page.tsx +279 -0
  73. package/src/modules/analytics/routes/index.tsx +133 -0
  74. package/src/modules/analytics/utils/formatters.test.ts +148 -0
  75. package/src/modules/analytics/utils/formatters.ts +100 -0
  76. package/src/modules/auth/pages/login-page.module.css +113 -0
  77. package/src/modules/auth/pages/login-page.tsx +80 -0
  78. package/src/modules/auth/routes/index.tsx +24 -0
  79. package/src/modules/dashboard/components/ActivityTimelineWidget.tsx +199 -0
  80. package/src/modules/dashboard/components/AiInsightsWidget.tsx +358 -0
  81. package/src/modules/dashboard/components/AnimatedMetricCard.css +88 -0
  82. package/src/modules/dashboard/components/AnimatedMetricCard.tsx +125 -0
  83. package/src/modules/dashboard/components/HeroMetricCard.css +114 -0
  84. package/src/modules/dashboard/components/HeroMetricCard.tsx +152 -0
  85. package/src/modules/dashboard/components/HeroMetricsWidget.tsx +146 -0
  86. package/src/modules/dashboard/components/SystemResourcesWidget.tsx +197 -0
  87. package/src/modules/dashboard/components/dashboard-empty.tsx +22 -0
  88. package/src/modules/dashboard/components/dashboard-kpis-widget.tsx +59 -0
  89. package/src/modules/dashboard/components/dashboard-kpis.tsx +13 -0
  90. package/src/modules/dashboard/components/dashboard-latency-percentiles-widget.tsx +85 -0
  91. package/src/modules/dashboard/components/dashboard-metrics-widget.tsx +78 -0
  92. package/src/modules/dashboard/components/dashboard-plugins-widget.tsx +67 -0
  93. package/src/modules/dashboard/components/dashboard-requests-widget.tsx +62 -0
  94. package/src/modules/dashboard/layouts/dashboard-layout.tsx +16 -0
  95. package/src/modules/dashboard/pages/ai-insights-page.tsx +563 -0
  96. package/src/modules/dashboard/pages/dashboard-animations.css +317 -0
  97. package/src/modules/dashboard/pages/dashboard-page.tsx +13 -0
  98. package/src/modules/dashboard/routes/index.tsx +70 -0
  99. package/src/modules/observability/pages/devkit-page.tsx +263 -0
  100. package/src/modules/observability/pages/incident-detail-page.tsx +571 -0
  101. package/src/modules/observability/pages/incidents-page.tsx +370 -0
  102. package/src/modules/observability/pages/log-detail-page.tsx +508 -0
  103. package/src/modules/observability/pages/logs-page.tsx +1129 -0
  104. package/src/modules/observability/pages/prometheus-metrics-page.tsx +486 -0
  105. package/src/modules/observability/pages/state-broker-page.tsx +229 -0
  106. package/src/modules/observability/pages/system-events-page.tsx +255 -0
  107. package/src/modules/observability/routes/index.tsx +133 -0
  108. package/src/modules/plugins/components/marketplace-card.tsx +153 -0
  109. package/src/modules/plugins/components/plugin-ai-assistant-modal.tsx +374 -0
  110. package/src/modules/plugins/components/plugin-card.tsx +136 -0
  111. package/src/modules/plugins/components/plugin-detail/capabilities-summary.tsx +101 -0
  112. package/src/modules/plugins/components/plugin-detail/cli-commands-table.tsx +101 -0
  113. package/src/modules/plugins/components/plugin-detail/index.ts +7 -0
  114. package/src/modules/plugins/components/plugin-detail/jobs-table.tsx +64 -0
  115. package/src/modules/plugins/components/plugin-detail/permissions/env-permissions.tsx +30 -0
  116. package/src/modules/plugins/components/plugin-detail/permissions/filesystem-permissions.tsx +68 -0
  117. package/src/modules/plugins/components/plugin-detail/permissions/index.ts +6 -0
  118. package/src/modules/plugins/components/plugin-detail/permissions/network-permissions.tsx +43 -0
  119. package/src/modules/plugins/components/plugin-detail/permissions/platform-services.tsx +242 -0
  120. package/src/modules/plugins/components/plugin-detail/permissions/resource-quotas.tsx +96 -0
  121. package/src/modules/plugins/components/plugin-detail/permissions/shell-permissions.tsx +55 -0
  122. package/src/modules/plugins/components/plugin-detail/permissions-view.tsx +47 -0
  123. package/src/modules/plugins/components/plugin-detail/plugin-header.tsx +169 -0
  124. package/src/modules/plugins/components/plugin-detail/rest-routes-table.tsx +159 -0
  125. package/src/modules/plugins/components/plugin-detail/workflows-table.tsx +40 -0
  126. package/src/modules/plugins/components/plugin-grid.tsx +20 -0
  127. package/src/modules/plugins/components/plugin-table.tsx +178 -0
  128. package/src/modules/plugins/pages/marketplace-page.tsx +200 -0
  129. package/src/modules/plugins/pages/plugin-detail-page.tsx +1145 -0
  130. package/src/modules/plugins/routes/index.tsx +48 -0
  131. package/src/modules/plugins/utils/index.ts +1 -0
  132. package/src/modules/plugins/utils/schema-helpers.ts +21 -0
  133. package/src/modules/settings/components/api-routes-viewer.tsx +415 -0
  134. package/src/modules/settings/components/appearance-settings.tsx +200 -0
  135. package/src/modules/settings/components/configuration-settings.tsx +321 -0
  136. package/src/modules/settings/components/data-privacy-settings.tsx +111 -0
  137. package/src/modules/settings/components/experimental-settings.tsx +243 -0
  138. package/src/modules/settings/components/navigation-settings.tsx +198 -0
  139. package/src/modules/settings/components/notifications-settings.tsx +40 -0
  140. package/src/modules/settings/pages/settings-page.tsx +298 -0
  141. package/src/modules/settings/routes/index.tsx +47 -0
  142. package/src/pages/README-assistant.md +91 -0
  143. package/src/pages/login-page.tsx +177 -0
  144. package/src/pages/not-found-page.tsx +85 -0
  145. package/src/providers/auth-provider.tsx +40 -0
  146. package/src/providers/command-palette-provider.tsx +193 -0
  147. package/src/providers/data-sources-provider.tsx +27 -0
  148. package/src/providers/devtools-provider.tsx +72 -0
  149. package/src/providers/navigation-items-provider.tsx +38 -0
  150. package/src/providers/registry-v2-provider.tsx +93 -0
  151. package/src/providers/settings-provider.tsx +158 -0
  152. package/src/router.tsx +302 -0
  153. package/src/routes/helpers.tsx +25 -0
  154. package/src/routes/plugin-page-v2.tsx +52 -0
  155. package/src/routes/types.ts +18 -0
  156. package/src/styles/antd-overrides.css +211 -0
  157. package/src/styles/base.css +190 -0
  158. package/src/styles/settings.css +75 -0
  159. package/src/styles/variables.css +94 -0
  160. package/src/utils/analytics.ts +183 -0
  161. package/src/utils/apply-navigation-categories.ts +83 -0
  162. package/src/utils/commands.ts +252 -0
  163. package/src/utils/export.ts +174 -0
  164. package/src/utils/format-date.ts +166 -0
  165. package/src/utils/logger.ts +110 -0
  166. package/tailwind.config.ts +13 -0
  167. package/tsconfig.json +13 -0
  168. package/vitest-setup.ts +1 -0
@@ -0,0 +1,571 @@
1
+ import { useState } from 'react';
2
+ import { useParams, useNavigate } from 'react-router-dom';
3
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
4
+ import {
5
+ UICard,
6
+ UIAlert,
7
+ UIBadge,
8
+ UITag,
9
+ UIButton,
10
+ UISpace,
11
+ UIDescriptions,
12
+ UIDescriptionsItem,
13
+ UITimeline,
14
+ UITimelineItem,
15
+ UIProgress,
16
+ UIModal, UIInputTextArea,
17
+ UIMessage,
18
+ UISpin,
19
+ UIDivider,
20
+ UIRow,
21
+ UICol,
22
+ UIStatistic,
23
+ UIEmptyState,
24
+ UITable,
25
+ UITooltip,
26
+ UIIcon,
27
+ } from '@kb-labs/studio-ui-kit';
28
+ import type {
29
+ IncidentDetailResponse,
30
+ IncidentAnalysisResponse,
31
+ IncidentSeverity,
32
+ IncidentType,
33
+ RootCauseItem,
34
+ } from '@kb-labs/studio-data-client';
35
+ import { useDataSources } from '../../../providers/data-sources-provider';
36
+ import dayjs from 'dayjs';
37
+ import relativeTime from 'dayjs/plugin/relativeTime';
38
+ import { UIPage, UIPageHeader } from '@kb-labs/studio-ui-kit';
39
+
40
+ dayjs.extend(relativeTime);
41
+
42
+ /**
43
+ * Format timestamp to human-readable string
44
+ */
45
+ function formatTime(timestamp: number): string {
46
+ return dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss');
47
+ }
48
+
49
+ /**
50
+ * Get severity icon and color
51
+ */
52
+ function getSeverityConfig(severity: IncidentSeverity): { icon: React.ReactElement; color: string } {
53
+ switch (severity) {
54
+ case 'critical':
55
+ return { icon: <UIIcon name="FireOutlined" />, color: '#ff4d4f' };
56
+ case 'warning':
57
+ return { icon: <UIIcon name="WarningOutlined" />, color: '#faad14' };
58
+ case 'info':
59
+ return { icon: <UIIcon name="InfoCircleOutlined" />, color: '#1890ff' };
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Get incident type label
65
+ */
66
+ function getTypeLabel(type: IncidentType): string {
67
+ switch (type) {
68
+ case 'error_rate':
69
+ return 'Error Rate';
70
+ case 'latency_spike':
71
+ return 'Latency Spike';
72
+ case 'plugin_failure':
73
+ return 'Plugin Failure';
74
+ case 'adapter_failure':
75
+ return 'Adapter Failure';
76
+ case 'system_health':
77
+ return 'System Health';
78
+ case 'custom':
79
+ return 'Custom';
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Incident Detail Page
85
+ *
86
+ * Shows detailed information about an incident including AI analysis
87
+ */
88
+ export function IncidentDetailPage() {
89
+ const { id } = useParams<{ id: string }>();
90
+ const navigate = useNavigate();
91
+ const sources = useDataSources();
92
+ const queryClient = useQueryClient();
93
+
94
+ const [resolveModalVisible, setResolveModalVisible] = useState(false);
95
+ const [resolutionNotes, setResolutionNotes] = useState('');
96
+
97
+ // Fetch incident details
98
+ const {
99
+ data: incidentData,
100
+ isLoading: incidentLoading,
101
+ error: incidentError,
102
+ } = useQuery<IncidentDetailResponse>({
103
+ queryKey: ['incidents', 'detail', id],
104
+ queryFn: async () => {
105
+ if (!id) {throw new Error('Incident ID is required');}
106
+ return sources.observability.getIncident(id);
107
+ },
108
+ enabled: !!id,
109
+ });
110
+
111
+ const incident = incidentData?.data;
112
+
113
+ // Fetch or trigger AI analysis
114
+ const {
115
+ data: analysisData,
116
+ isLoading: analysisLoading,
117
+ error: analysisError,
118
+ refetch: triggerAnalysis,
119
+ } = useQuery<IncidentAnalysisResponse>({
120
+ queryKey: ['incidents', 'analysis', id],
121
+ queryFn: async () => {
122
+ if (!id) {throw new Error('Incident ID is required');}
123
+ return sources.observability.analyzeIncident(id);
124
+ },
125
+ enabled: false, // Manual trigger
126
+ });
127
+
128
+ const analysis = analysisData?.data;
129
+
130
+ // Resolve incident mutation
131
+ const resolveMutation = useMutation({
132
+ mutationFn: async () => {
133
+ if (!id) {throw new Error('Incident ID is required');}
134
+ return sources.observability.resolveIncident(id, resolutionNotes);
135
+ },
136
+ onSuccess: () => {
137
+ UIMessage.success('Incident resolved successfully');
138
+ setResolveModalVisible(false);
139
+ setResolutionNotes('');
140
+ queryClient.invalidateQueries({ queryKey: ['incidents'] });
141
+ },
142
+ onError: (error: Error) => {
143
+ UIMessage.error(`Failed to resolve incident: ${error.message}`);
144
+ },
145
+ });
146
+
147
+ const handleResolve = () => {
148
+ resolveMutation.mutate();
149
+ };
150
+
151
+ const handleAnalyze = () => {
152
+ triggerAnalysis();
153
+ };
154
+
155
+ const handleGoBack = () => {
156
+ navigate('/observability/incidents');
157
+ };
158
+
159
+ if (incidentLoading) {
160
+ return (
161
+ <UIPage>
162
+ <div style={{ textAlign: 'center', padding: '100px 0' }}>
163
+ <UISpin size="large" />
164
+ </div>
165
+ </UIPage>
166
+ );
167
+ }
168
+
169
+ if (incidentError || !incident) {
170
+ return (
171
+ <UIPage>
172
+ <UIAlert
173
+ message="Failed to load incident"
174
+ description={(incidentError as Error)?.message ?? 'Incident not found'}
175
+ variant="error"
176
+ showIcon
177
+ />
178
+ </UIPage>
179
+ );
180
+ }
181
+
182
+ const severityConfig = getSeverityConfig(incident.severity);
183
+
184
+ return (
185
+ <UIPage>
186
+ <UIPageHeader
187
+ title={incident.title}
188
+ description={`Incident ID: ${incident.id}`}
189
+ actions={[
190
+ <UIButton key="back" icon={<UIIcon name="ArrowLeftOutlined" />} onClick={handleGoBack} />,
191
+ !incident.resolvedAt && (
192
+ <UIButton
193
+ key="resolve"
194
+ variant="primary"
195
+ icon={<UIIcon name="CheckCircleOutlined" />}
196
+ onClick={() => setResolveModalVisible(true)}
197
+ >
198
+ Resolve
199
+ </UIButton>
200
+ ),
201
+ <UIButton
202
+ key="analyze"
203
+ icon={<UIIcon name="ThunderboltOutlined" />}
204
+ onClick={handleAnalyze}
205
+ loading={analysisLoading}
206
+ disabled={!!analysis && !analysisData?.data.cached}
207
+ >
208
+ {analysis ? 'Re-analyze' : 'Analyze with AI'}
209
+ </UIButton>,
210
+ ].filter(Boolean)}
211
+ />
212
+
213
+ {/* Overview */}
214
+ <UICard title="Overview" style={{ marginBottom: 16 }}>
215
+ <UIDescriptions column={2} bordered>
216
+ <UIDescriptionsItem label="Severity">
217
+ <UITag color={severityConfig.color} icon={severityConfig.icon}>
218
+ {incident.severity.toUpperCase()}
219
+ </UITag>
220
+ </UIDescriptionsItem>
221
+ <UIDescriptionsItem label="Type">
222
+ <UITag>{getTypeLabel(incident.type)}</UITag>
223
+ </UIDescriptionsItem>
224
+ <UIDescriptionsItem label="Time">
225
+ {formatTime(incident.timestamp)}
226
+ <span style={{ marginLeft: 8, color: '#888' }}>
227
+ ({dayjs(incident.timestamp).fromNow()})
228
+ </span>
229
+ </UIDescriptionsItem>
230
+ <UIDescriptionsItem label="Status">
231
+ {incident.resolvedAt ? (
232
+ <UITag color="success" icon={<UIIcon name="CheckCircleOutlined" />}>
233
+ RESOLVED
234
+ </UITag>
235
+ ) : (
236
+ <UITag color="error" icon={<UIIcon name="ClockCircleOutlined" />}>
237
+ ACTIVE
238
+ </UITag>
239
+ )}
240
+ </UIDescriptionsItem>
241
+ {incident.resolvedAt && (
242
+ <UIDescriptionsItem label="Resolved At">
243
+ {formatTime(incident.resolvedAt)}
244
+ <span style={{ marginLeft: 8, color: '#888' }}>
245
+ (Duration: {Math.floor((incident.resolvedAt - incident.timestamp) / 60000)}m)
246
+ </span>
247
+ </UIDescriptionsItem>
248
+ )}
249
+ {incident.affectedServices && incident.affectedServices.length > 0 && (
250
+ <UIDescriptionsItem label="Affected Services" span={2}>
251
+ <UISpace wrap>
252
+ {incident.affectedServices.map((service) => (
253
+ <UITag key={service}>{service}</UITag>
254
+ ))}
255
+ </UISpace>
256
+ </UIDescriptionsItem>
257
+ )}
258
+ <UIDescriptionsItem label="Details" span={2}>
259
+ {incident.details}
260
+ </UIDescriptionsItem>
261
+ {incident.resolutionNotes && (
262
+ <UIDescriptionsItem label="Resolution Notes" span={2}>
263
+ {incident.resolutionNotes}
264
+ </UIDescriptionsItem>
265
+ )}
266
+ </UIDescriptions>
267
+ </UICard>
268
+
269
+ {/* Related Data - Slow Requests (for latency incidents) */}
270
+ {incident.type === 'latency_spike' && incident.relatedData?.metrics?.topSlowest && incident.relatedData.metrics.topSlowest.length > 0 && (
271
+ <UICard title="Slowest Requests" style={{ marginBottom: 16 }}>
272
+ <UITable
273
+ dataSource={incident.relatedData.metrics.topSlowest}
274
+ columns={[
275
+ {
276
+ title: 'Method',
277
+ dataIndex: 'method',
278
+ key: 'method',
279
+ width: 80,
280
+ render: (method: string) => <UITag color="blue">{method}</UITag>,
281
+ },
282
+ {
283
+ title: 'Endpoint',
284
+ dataIndex: 'endpoint',
285
+ key: 'endpoint',
286
+ render: (endpoint: string) => <code>{endpoint}</code>,
287
+ },
288
+ {
289
+ title: 'Duration',
290
+ dataIndex: 'durationMs',
291
+ key: 'durationMs',
292
+ width: 120,
293
+ sorter: (a: any, b: any) => b.durationMs - a.durationMs,
294
+ render: (ms: number) => (
295
+ <span style={{ color: ms > 1000 ? '#ff4d4f' : ms > 500 ? '#faad14' : undefined }}>
296
+ {ms.toLocaleString()}ms
297
+ </span>
298
+ ),
299
+ },
300
+ {
301
+ title: 'Status',
302
+ dataIndex: 'statusCode',
303
+ key: 'statusCode',
304
+ width: 80,
305
+ render: (code?: number) => (
306
+ code ? (
307
+ <UITag color={code >= 500 ? 'error' : code >= 400 ? 'warning' : 'success'}>
308
+ {code}
309
+ </UITag>
310
+ ) : '-'
311
+ ),
312
+ },
313
+ ]}
314
+ pagination={false}
315
+ size="small"
316
+ rowKey={(record: { endpoint: string; method: string; durationMs: number; statusCode?: number }) => record.endpoint}
317
+ />
318
+ </UICard>
319
+ )}
320
+
321
+ {/* Related Data - Metrics Comparison */}
322
+ {incident.relatedData?.metrics && (
323
+ <UICard title={<><UIIcon name="LineChartOutlined" /> Metrics Comparison</>} style={{ marginBottom: 16 }}>
324
+ <UIRow gutter={16}>
325
+ {incident.relatedData.metrics.before && (
326
+ <UICol span={12}>
327
+ <UICard type="inner" title="Before Incident">
328
+ {Object.entries(incident.relatedData.metrics.before).map(([key, value]) => (
329
+ <UIStatistic
330
+ key={key}
331
+ title={key}
332
+ value={typeof value === 'number' ? value.toFixed(2) : value}
333
+ style={{ marginBottom: 8 }}
334
+ />
335
+ ))}
336
+ </UICard>
337
+ </UICol>
338
+ )}
339
+ {incident.relatedData.metrics.during && (
340
+ <UICol span={12}>
341
+ <UICard type="inner" title="During Incident">
342
+ {Object.entries(incident.relatedData.metrics.during).map(([key, value]) => (
343
+ <UIStatistic
344
+ key={key}
345
+ title={key}
346
+ value={typeof value === 'number' ? value.toFixed(2) : value}
347
+ valueStyle={{
348
+ color:
349
+ incident.relatedData?.metrics?.before?.[key] &&
350
+ typeof value === 'number' &&
351
+ value > incident.relatedData.metrics.before[key]
352
+ ? '#ff4d4f'
353
+ : undefined,
354
+ }}
355
+ style={{ marginBottom: 8 }}
356
+ />
357
+ ))}
358
+ </UICard>
359
+ </UICol>
360
+ )}
361
+ </UIRow>
362
+ </UICard>
363
+ )}
364
+
365
+ {/* Related Data - Timeline */}
366
+ {incident.relatedData?.timeline && incident.relatedData.timeline.length > 0 && (
367
+ <UICard title="Event Timeline" style={{ marginBottom: 16 }}>
368
+ <UITimeline mode="left">
369
+ {incident.relatedData.timeline.map((event, idx) => (
370
+ <UITimelineItem
371
+ key={idx}
372
+ color={event.source === 'detector' ? 'red' : event.source === 'logs' ? 'orange' : 'blue'}
373
+ label={formatTime(event.timestamp)}
374
+ >
375
+ <UIBadge
376
+ variant={event.source === 'detector' ? 'error' : 'info'}
377
+ />{event.source.toUpperCase()}
378
+ <div style={{ marginTop: 4 }}>{event.event}</div>
379
+ </UITimelineItem>
380
+ ))}
381
+ </UITimeline>
382
+ </UICard>
383
+ )}
384
+
385
+ {/* Related Data - Error Breakdown (top endpoints with errors) */}
386
+ {incident.relatedData?.logs?.topEndpoints && incident.relatedData.logs.topEndpoints.length > 0 && (
387
+ <UICard title="Error Breakdown by Endpoint" style={{ marginBottom: 16 }}>
388
+ <UITable
389
+ dataSource={incident.relatedData.logs.topEndpoints}
390
+ columns={[
391
+ {
392
+ title: 'Endpoint',
393
+ dataIndex: 'endpoint',
394
+ key: 'endpoint',
395
+ render: (endpoint: string) => <code>{endpoint}</code>,
396
+ },
397
+ {
398
+ title: 'Error Count',
399
+ dataIndex: 'count',
400
+ key: 'count',
401
+ width: 120,
402
+ sorter: (a: any, b: any) => b.count - a.count,
403
+ render: (count: number) => (
404
+ <UIBadge count={count} style={{ backgroundColor: '#ff4d4f' }} />
405
+ ),
406
+ },
407
+ {
408
+ title: 'Sample Error',
409
+ dataIndex: 'sample',
410
+ key: 'sample',
411
+ ellipsis: true,
412
+ render: (sample: string) => (
413
+ <UITooltip title={sample}>
414
+ <span style={{ color: '#666' }}>{sample}</span>
415
+ </UITooltip>
416
+ ),
417
+ },
418
+ ]}
419
+ pagination={false}
420
+ size="small"
421
+ rowKey={(record: { endpoint: string; count: number; sample: string }) => record.endpoint}
422
+ />
423
+ </UICard>
424
+ )}
425
+
426
+ {/* Related Data - Error Logs */}
427
+ {incident.relatedData?.logs && incident.relatedData.logs.sampleErrors.length > 0 && (
428
+ <UICard title={`Error Logs (${incident.relatedData.logs.errorCount} total)`} style={{ marginBottom: 16 }}>
429
+ <UISpace direction="vertical" style={{ width: '100%' }}>
430
+ {incident.relatedData.logs.sampleErrors.map((error, idx) => (
431
+ <UIAlert
432
+ key={idx}
433
+ message={`Error ${idx + 1}`}
434
+ description={<pre style={{ margin: 0, whiteSpace: 'pre-wrap', fontSize: 12 }}>{error}</pre>}
435
+ variant="error"
436
+ showIcon
437
+ />
438
+ ))}
439
+ </UISpace>
440
+ </UICard>
441
+ )}
442
+
443
+ {/* AI Analysis */}
444
+ {analysisLoading && (
445
+ <UICard title={<><UIIcon name="ThunderboltOutlined" /> AI Analysis</>} style={{ marginBottom: 16 }}>
446
+ <div style={{ textAlign: 'center', padding: '40px 0' }}>
447
+ <UISpin size="large" tip="Analyzing incident with AI..." />
448
+ </div>
449
+ </UICard>
450
+ )}
451
+
452
+ {analysisError && (
453
+ <UICard title={<><UIIcon name="ThunderboltOutlined" /> AI Analysis</>} style={{ marginBottom: 16 }}>
454
+ <UIAlert
455
+ message="Analysis Failed"
456
+ description={(analysisError as Error).message}
457
+ variant="error"
458
+ showIcon
459
+ />
460
+ </UICard>
461
+ )}
462
+
463
+ {analysis && !analysisLoading && (
464
+ <UICard
465
+ title={<><UIIcon name="ThunderboltOutlined" /> AI Analysis</>}
466
+ style={{ marginBottom: 16 }}
467
+ extra={
468
+ analysisData?.data.cached && (
469
+ <UIBadge variant="success">Cached</UIBadge>
470
+ )
471
+ }
472
+ >
473
+ {/* Summary */}
474
+ <UIAlert
475
+ message="Summary"
476
+ description={analysis.summary}
477
+ variant="info"
478
+ showIcon
479
+ icon={<UIIcon name="BulbOutlined" />}
480
+ style={{ marginBottom: 16 }}
481
+ />
482
+
483
+ {/* Root Causes */}
484
+ {analysis.rootCauses && analysis.rootCauses.length > 0 && (
485
+ <>
486
+ <UIDivider orientation="left">Root Causes</UIDivider>
487
+ <UISpace direction="vertical" style={{ width: '100%', marginBottom: 16 }}>
488
+ {analysis.rootCauses.map((cause: RootCauseItem, idx: number) => (
489
+ <UICard key={idx} type="inner" size="small">
490
+ <UISpace direction="vertical" style={{ width: '100%' }}>
491
+ <div>
492
+ <strong>{cause.factor}</strong>
493
+ <UIProgress
494
+ percent={Math.round(cause.confidence * 100)}
495
+ size="small"
496
+ style={{ marginTop: 4 }}
497
+ strokeColor={
498
+ cause.confidence >= 0.8
499
+ ? '#52c41a'
500
+ : cause.confidence >= 0.5
501
+ ? '#faad14'
502
+ : '#ff4d4f'
503
+ }
504
+ />
505
+ </div>
506
+ <div style={{ color: '#666', fontSize: 13 }}>{cause.evidence}</div>
507
+ </UISpace>
508
+ </UICard>
509
+ ))}
510
+ </UISpace>
511
+ </>
512
+ )}
513
+
514
+ {/* Patterns */}
515
+ {analysis.patterns && analysis.patterns.length > 0 && (
516
+ <>
517
+ <UIDivider orientation="left">Detected Patterns</UIDivider>
518
+ <ul style={{ marginBottom: 16 }}>
519
+ {analysis.patterns.map((pattern: string, idx: number) => (
520
+ <li key={idx}>{pattern}</li>
521
+ ))}
522
+ </ul>
523
+ </>
524
+ )}
525
+
526
+ {/* Recommendations */}
527
+ {analysis.recommendations && analysis.recommendations.length > 0 && (
528
+ <>
529
+ <UIDivider orientation="left">Recommendations</UIDivider>
530
+ <UISpace direction="vertical" style={{ width: '100%' }}>
531
+ {analysis.recommendations.map((rec: string, idx: number) => (
532
+ <UIAlert
533
+ key={idx}
534
+ message={`Recommendation ${idx + 1}`}
535
+ description={rec}
536
+ variant="success"
537
+ showIcon
538
+ />
539
+ ))}
540
+ </UISpace>
541
+ </>
542
+ )}
543
+
544
+ {analysis.rootCauses.length === 0 && analysis.patterns.length === 0 && analysis.recommendations.length === 0 && (
545
+ <UIEmptyState description="No detailed analysis available" />
546
+ )}
547
+ </UICard>
548
+ )}
549
+
550
+ {/* Resolve Modal */}
551
+ <UIModal
552
+ title="Resolve Incident"
553
+ open={resolveModalVisible}
554
+ onOk={handleResolve}
555
+ onCancel={() => setResolveModalVisible(false)}
556
+ confirmLoading={resolveMutation.isPending}
557
+ okText="Resolve"
558
+ >
559
+ <UISpace direction="vertical" style={{ width: '100%' }}>
560
+ <p>Are you sure you want to mark this incident as resolved?</p>
561
+ <UIInputTextArea
562
+ rows={4}
563
+ placeholder="Resolution notes (optional)"
564
+ value={resolutionNotes}
565
+ onChange={(e) => setResolutionNotes(e.target.value)}
566
+ />
567
+ </UISpace>
568
+ </UIModal>
569
+ </UIPage>
570
+ );
571
+ }