@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,113 @@
1
+ .page {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ min-height: 100vh;
6
+ background: var(--bg-primary);
7
+ padding: 24px;
8
+ }
9
+
10
+ .card {
11
+ width: 100%;
12
+ max-width: 360px;
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: 24px;
16
+ }
17
+
18
+ /* ── Header ──────────────────────────────────────────────────────────── */
19
+
20
+ .header {
21
+ text-align: center;
22
+ }
23
+
24
+ .logo {
25
+ width: 40px;
26
+ height: 40px;
27
+ border-radius: 10px;
28
+ background: var(--link);
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ margin: 0 auto 16px;
33
+ color: #fff;
34
+ }
35
+
36
+ .title {
37
+ font-size: 20px;
38
+ font-weight: 700;
39
+ color: var(--text-primary);
40
+ font-family: var(--font-heading);
41
+ margin: 0 0 4px;
42
+ }
43
+
44
+ .subtitle {
45
+ font-size: 13px;
46
+ color: var(--text-secondary);
47
+ margin: 0;
48
+ }
49
+
50
+ /* ── Dev notice ──────────────────────────────────────────────────────── */
51
+
52
+ .devNotice {
53
+ display: flex;
54
+ align-items: flex-start;
55
+ gap: 8px;
56
+ padding: 10px 12px;
57
+ background: var(--accent-subtle);
58
+ border: 1px solid var(--link);
59
+ border-radius: 8px;
60
+ font-size: 12px;
61
+ color: var(--link);
62
+ line-height: 1.4;
63
+ opacity: 0.85;
64
+ }
65
+
66
+ .devNoticeIcon {
67
+ flex-shrink: 0;
68
+ margin-top: 1px;
69
+ }
70
+
71
+ /* ── Form ────────────────────────────────────────────────────────────── */
72
+
73
+ .form {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 12px;
77
+ }
78
+
79
+ .label {
80
+ display: block;
81
+ font-size: 12px;
82
+ font-weight: 600;
83
+ color: var(--text-secondary);
84
+ margin-bottom: 6px;
85
+ text-transform: uppercase;
86
+ letter-spacing: 0.04em;
87
+ }
88
+
89
+ /* ── Divider ─────────────────────────────────────────────────────────── */
90
+
91
+ .divider {
92
+ display: flex;
93
+ align-items: center;
94
+ gap: 10px;
95
+ font-size: 11px;
96
+ color: var(--text-tertiary);
97
+ }
98
+
99
+ .divider::before,
100
+ .divider::after {
101
+ content: '';
102
+ flex: 1;
103
+ height: 1px;
104
+ background: var(--border-primary);
105
+ }
106
+
107
+ /* ── Footer ──────────────────────────────────────────────────────────── */
108
+
109
+ .footer {
110
+ text-align: center;
111
+ font-size: 11px;
112
+ color: var(--text-tertiary);
113
+ }
@@ -0,0 +1,80 @@
1
+ import * as React from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { useAuth, type UserRole } from '@/providers/auth-provider';
4
+ import { UIButton, UISelect, UIMessage } from '@kb-labs/studio-ui-kit';
5
+ import { Boxes, ShieldAlert } from 'lucide-react';
6
+ import styles from './login-page.module.css';
7
+
8
+ const ROLE_OPTIONS = [
9
+ { label: 'Viewer — read-only access', value: 'viewer' },
10
+ { label: 'Operator — manage system', value: 'operator' },
11
+ { label: 'Admin — full access', value: 'admin' },
12
+ ];
13
+
14
+ export function LoginPage() {
15
+ const navigate = useNavigate();
16
+ const { setRole } = useAuth();
17
+ const [selectedRole, setSelectedRole] = React.useState<UserRole>('operator');
18
+
19
+ const handleLogin = () => {
20
+ setRole(selectedRole);
21
+ navigate('/');
22
+ };
23
+
24
+ const handleSSOLogin = () => {
25
+ UIMessage.info('SSO authentication will be available in a future release.');
26
+ };
27
+
28
+ return (
29
+ <div className={styles.page}>
30
+ <div className={styles.card}>
31
+
32
+ {/* Header */}
33
+ <div className={styles.header}>
34
+ <div className={styles.logo}>
35
+ <Boxes size={22} />
36
+ </div>
37
+ <h1 className={styles.title}>KB Labs Studio</h1>
38
+ <p className={styles.subtitle}>Development platform</p>
39
+ </div>
40
+
41
+ {/* Dev mode notice */}
42
+ <div className={styles.devNotice}>
43
+ <ShieldAlert size={14} className={styles.devNoticeIcon} />
44
+ <span>Development mode — no password required. Select a role to continue.</span>
45
+ </div>
46
+
47
+ {/* Form */}
48
+ <div className={styles.form}>
49
+ <div>
50
+ <label className={styles.label}>Role</label>
51
+ <UISelect
52
+ value={selectedRole}
53
+ onChange={(v) => setSelectedRole(v as UserRole)}
54
+ style={{ width: '100%' }}
55
+ options={ROLE_OPTIONS}
56
+ />
57
+ </div>
58
+
59
+ <UIButton
60
+ variant="primary"
61
+ size="large"
62
+ block
63
+ onClick={handleLogin}
64
+ >
65
+ Sign in
66
+ </UIButton>
67
+
68
+ <div className={styles.divider}>or</div>
69
+
70
+ <UIButton size="large" block onClick={handleSSOLogin}>
71
+ Continue with SSO
72
+ </UIButton>
73
+ </div>
74
+
75
+ {/* Footer */}
76
+ <p className={styles.footer}>© 2025 KB Labs · Development Preview</p>
77
+ </div>
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @module @kb-labs/studio-app/modules/auth/routes
3
+ * Authentication module routing configuration
4
+ */
5
+
6
+ import type { RouteObject } from 'react-router-dom';
7
+ import { LoginPage } from '../pages/login-page';
8
+ import { ErrorBoundary } from '../../../components/error-boundary';
9
+
10
+ // Route paths
11
+ const PATHS = {
12
+ LOGIN: '/login',
13
+ } as const;
14
+
15
+ /**
16
+ * Auth routes (no navigation - these are standalone pages)
17
+ */
18
+ export const authRoutes: RouteObject[] = [
19
+ {
20
+ path: PATHS.LOGIN,
21
+ element: <LoginPage />,
22
+ errorElement: <ErrorBoundary />,
23
+ },
24
+ ];
@@ -0,0 +1,199 @@
1
+ import React, { useState } from 'react';
2
+ import { UICard, UISegmented } from '@kb-labs/studio-ui-kit';
3
+ import { UIAreaChart, useChartColors } from '@kb-labs/studio-ui-kit';
4
+ import { useDataSources } from '../../../providers/data-sources-provider';
5
+ import { useMetricsHistory } from '@kb-labs/studio-data-client';
6
+
7
+ type TimeRange = '1m' | '5m' | '10m' | '30m';
8
+
9
+ const TIME_RANGE_CONFIG = {
10
+ '1m': { label: '1 min' },
11
+ '5m': { label: '5 min' },
12
+ '10m': { label: '10 min' },
13
+ '30m': { label: '30 min' },
14
+ };
15
+
16
+ export function ActivityTimelineWidget() {
17
+ const sources = useDataSources();
18
+ const palette = useChartColors();
19
+ const [timeRange, setTimeRange] = useState<TimeRange>('10m');
20
+
21
+ // Get colors from palette for legend
22
+ const colorMap = palette.mapColors([{ type: 'Requests' }, { type: 'Errors' }], 'type');
23
+ const requestsColor = colorMap['Requests'];
24
+ const errorsColor = colorMap['Errors'];
25
+
26
+ // Fetch requests and errors history
27
+ const requestsQuery = useMetricsHistory(sources.observability, {
28
+ metric: 'requests',
29
+ range: timeRange,
30
+ interval: '5s',
31
+ });
32
+
33
+ const errorsQuery = useMetricsHistory(sources.observability, {
34
+ metric: 'errors',
35
+ range: timeRange,
36
+ interval: '5s',
37
+ });
38
+
39
+ // Transform data for chart
40
+ const chartData = React.useMemo(() => {
41
+ if (!requestsQuery.data || !errorsQuery.data) {return [];}
42
+
43
+ // Merge requests and errors by timestamp
44
+ const dataMap = new Map<number, { time: string; Requests: number; Errors: number }>();
45
+
46
+ for (const point of requestsQuery.data) {
47
+ const time = new Date(point.timestamp).toLocaleTimeString('en-US', {
48
+ hour12: false,
49
+ hour: '2-digit',
50
+ minute: '2-digit',
51
+ second: '2-digit',
52
+ });
53
+ dataMap.set(point.timestamp, { time, Requests: point.value, Errors: 0 });
54
+ }
55
+
56
+ for (const point of errorsQuery.data) {
57
+ const existing = dataMap.get(point.timestamp);
58
+ if (existing) {
59
+ existing.Errors = point.value;
60
+ }
61
+ }
62
+
63
+ return Array.from(dataMap.values());
64
+ }, [requestsQuery.data, errorsQuery.data]);
65
+
66
+ // Current values for legend
67
+ const currentRequests = requestsQuery.data?.[requestsQuery.data.length - 1]?.value ?? 0;
68
+ const currentErrors = errorsQuery.data?.[errorsQuery.data.length - 1]?.value ?? 0;
69
+
70
+ const config = {
71
+ xField: 'time',
72
+ yField: 'value',
73
+ colorField: 'type',
74
+ height: 250,
75
+ smooth: true,
76
+ style: {
77
+ fillOpacity: 0.3,
78
+ },
79
+ axis: {
80
+ x: {
81
+ label: {
82
+ autoRotate: true,
83
+ },
84
+ },
85
+ y: {
86
+ label: {
87
+ formatter: (v: string) => {
88
+ const num = Number(v);
89
+ if (num >= 1000) {return `${(num / 1000).toFixed(1)}K`;}
90
+ return num.toLocaleString();
91
+ },
92
+ },
93
+ },
94
+ },
95
+ legend: false,
96
+ tooltip: {
97
+ title: (d: any) => d.time,
98
+ items: [
99
+ (d: any) => ({
100
+ name: d.type,
101
+ value: d.value?.toLocaleString() ?? 0,
102
+ color: colorMap[d.type],
103
+ }),
104
+ ],
105
+ },
106
+ };
107
+
108
+ // Transform to multi-series format
109
+ const multiSeriesData = chartData.flatMap(point => [
110
+ { time: point.time, type: 'Requests', value: point.Requests },
111
+ { time: point.time, type: 'Errors', value: point.Errors },
112
+ ]);
113
+
114
+ const isLoading = requestsQuery.isLoading || errorsQuery.isLoading;
115
+ const hasError = requestsQuery.isError || errorsQuery.isError;
116
+
117
+ return (
118
+ <UICard
119
+ title={
120
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
121
+ <span>Requests & Errors</span>
122
+ <UISegmented
123
+ options={Object.entries(TIME_RANGE_CONFIG).map(([key, cfg]) => ({
124
+ label: cfg.label,
125
+ value: key,
126
+ }))}
127
+ value={timeRange}
128
+ onChange={(value) => setTimeRange(value as TimeRange)}
129
+ size="small"
130
+ />
131
+ </div>
132
+ }
133
+ styles={{ body: { padding: '16px' } }}
134
+ >
135
+ {isLoading ? (
136
+ <div style={{
137
+ height: 250,
138
+ display: 'flex',
139
+ alignItems: 'center',
140
+ justifyContent: 'center',
141
+ color: 'var(--text-tertiary)',
142
+ }}>
143
+ Loading metrics...
144
+ </div>
145
+ ) : hasError ? (
146
+ <div style={{
147
+ height: 250,
148
+ display: 'flex',
149
+ alignItems: 'center',
150
+ justifyContent: 'center',
151
+ color: 'var(--error)',
152
+ }}>
153
+ Failed to load metrics
154
+ </div>
155
+ ) : chartData.length > 0 ? (
156
+ <div>
157
+ {/* Custom Legend */}
158
+ <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 12, gap: 16 }}>
159
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
160
+ <span style={{
161
+ display: 'inline-block',
162
+ width: 10,
163
+ height: 10,
164
+ borderRadius: '50%',
165
+ backgroundColor: requestsColor,
166
+ }} />
167
+ <span style={{ fontSize: 13, fontWeight: 500 }}>
168
+ Requests ({currentRequests.toLocaleString()})
169
+ </span>
170
+ </div>
171
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
172
+ <span style={{
173
+ display: 'inline-block',
174
+ width: 10,
175
+ height: 10,
176
+ borderRadius: '50%',
177
+ backgroundColor: errorsColor,
178
+ }} />
179
+ <span style={{ fontSize: 13, fontWeight: 500 }}>
180
+ Errors ({currentErrors.toLocaleString()})
181
+ </span>
182
+ </div>
183
+ </div>
184
+ <UIAreaChart {...config} data={multiSeriesData} />
185
+ </div>
186
+ ) : (
187
+ <div style={{
188
+ height: 250,
189
+ display: 'flex',
190
+ alignItems: 'center',
191
+ justifyContent: 'center',
192
+ color: 'var(--text-tertiary)',
193
+ }}>
194
+ No data available
195
+ </div>
196
+ )}
197
+ </UICard>
198
+ );
199
+ }