@kb-labs/studio-app 0.6.0 → 2.8.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 (175) hide show
  1. package/dist/index.html +1 -0
  2. package/dist/main.1d68e922.js +522 -0
  3. package/dist/main.1d68e922.js.map +1 -0
  4. package/dist/main.80b70197.css +2 -0
  5. package/dist/main.80b70197.css.map +1 -0
  6. package/dist/mf-manifest.json +241 -0
  7. package/dist/mf-stats.json +271 -0
  8. package/package.json +56 -39
  9. package/server.js +68 -0
  10. package/.env.example +0 -10
  11. package/UI-KIT-COMPONENTS.md +0 -783
  12. package/UI-KIT-PLAN.md +0 -326
  13. package/eslint-rules/no-direct-antd-import.js +0 -151
  14. package/eslint.config.ts +0 -27
  15. package/index.html +0 -17
  16. package/postcss.config.js +0 -7
  17. package/public/favicon.ico +0 -0
  18. package/rspack.config.mjs +0 -118
  19. package/src/App.tsx +0 -46
  20. package/src/components/devtools/devtools-log-feed.module.css +0 -165
  21. package/src/components/devtools/devtools-log-feed.tsx +0 -300
  22. package/src/components/devtools/devtools-panel.module.css +0 -121
  23. package/src/components/devtools/devtools-panel.tsx +0 -115
  24. package/src/components/empty-state.tsx +0 -28
  25. package/src/components/error-boundary.tsx +0 -210
  26. package/src/components/health-banner.module.css +0 -103
  27. package/src/components/health-banner.tsx +0 -134
  28. package/src/components/health-indicator.tsx +0 -12
  29. package/src/components/page-header.tsx +0 -49
  30. package/src/components/page-transition.tsx +0 -24
  31. package/src/components/role-switcher.tsx +0 -148
  32. package/src/components/run-badge.tsx +0 -13
  33. package/src/components/ui/icons.tsx +0 -509
  34. package/src/components/ui/index.ts +0 -25
  35. package/src/components/ui/kb-config-provider.tsx +0 -130
  36. package/src/components/ui/kb-header.module.css +0 -51
  37. package/src/components/ui/kb-header.tsx +0 -112
  38. package/src/components/ui/kb-notification-bell.module.css +0 -103
  39. package/src/components/ui/kb-notification-bell.tsx +0 -199
  40. package/src/components/ui/kb-page-layout.tsx +0 -66
  41. package/src/components/ui/kb-quick-search.module.css +0 -86
  42. package/src/components/ui/kb-quick-search.tsx +0 -196
  43. package/src/components/ui/kb-sidebar.module.css +0 -47
  44. package/src/components/ui/kb-sidebar.tsx +0 -196
  45. package/src/components/ui/kb-stat-card.module.css +0 -21
  46. package/src/components/ui/kb-stat-card.tsx +0 -22
  47. package/src/components/ui/kb-stat-grid.tsx +0 -22
  48. package/src/components/ui/kb-status-bar.module.css +0 -49
  49. package/src/components/ui/kb-status-bar.tsx +0 -145
  50. package/src/components/ui/kb-system-health-indicator.tsx +0 -229
  51. package/src/components/ui/kb-theme-toggle.tsx +0 -42
  52. package/src/components/ui/theme-adapter.ts +0 -812
  53. package/src/components/ui/theme-transition-overlay.tsx +0 -70
  54. package/src/components/ui/utils.ts +0 -6
  55. package/src/components/widget-error-boundary.tsx +0 -172
  56. package/src/config/env.ts +0 -5
  57. package/src/config/feature-flags.ts +0 -482
  58. package/src/config/studio.config.schema.ts +0 -88
  59. package/src/config/studio.config.ts +0 -84
  60. package/src/env.d.ts +0 -23
  61. package/src/hooks/use-command-palette.ts +0 -6
  62. package/src/hooks/use-devtools-store.ts +0 -25
  63. package/src/hooks/use-feature-flags.ts +0 -101
  64. package/src/index.css +0 -10
  65. package/src/layouts/dashboard-grid.tsx +0 -122
  66. package/src/layouts/index.ts +0 -12
  67. package/src/layouts/two-pane.tsx +0 -134
  68. package/src/main.tsx +0 -15
  69. package/src/modules/analytics/components/analytics-daily-stats-chart.tsx +0 -257
  70. package/src/modules/analytics/components/analytics-date-range-picker.tsx +0 -121
  71. package/src/modules/analytics/pages/analytics-cache-page.tsx +0 -285
  72. package/src/modules/analytics/pages/analytics-embeddings-page.tsx +0 -274
  73. package/src/modules/analytics/pages/analytics-events-page.tsx +0 -425
  74. package/src/modules/analytics/pages/analytics-llm-page.tsx +0 -434
  75. package/src/modules/analytics/pages/analytics-overview-page.tsx +0 -336
  76. package/src/modules/analytics/pages/analytics-page.tsx +0 -20
  77. package/src/modules/analytics/pages/analytics-storage-page.tsx +0 -344
  78. package/src/modules/analytics/pages/analytics-vectorstore-page.tsx +0 -279
  79. package/src/modules/analytics/routes/index.tsx +0 -133
  80. package/src/modules/analytics/utils/formatters.test.ts +0 -148
  81. package/src/modules/analytics/utils/formatters.ts +0 -100
  82. package/src/modules/auth/pages/login-page.module.css +0 -113
  83. package/src/modules/auth/pages/login-page.tsx +0 -80
  84. package/src/modules/auth/routes/index.tsx +0 -24
  85. package/src/modules/dashboard/components/ActivityTimelineWidget.tsx +0 -199
  86. package/src/modules/dashboard/components/AiInsightsWidget.tsx +0 -358
  87. package/src/modules/dashboard/components/AnimatedMetricCard.css +0 -88
  88. package/src/modules/dashboard/components/AnimatedMetricCard.tsx +0 -125
  89. package/src/modules/dashboard/components/HeroMetricCard.css +0 -114
  90. package/src/modules/dashboard/components/HeroMetricCard.tsx +0 -152
  91. package/src/modules/dashboard/components/HeroMetricsWidget.tsx +0 -146
  92. package/src/modules/dashboard/components/SystemResourcesWidget.tsx +0 -197
  93. package/src/modules/dashboard/components/dashboard-empty.tsx +0 -22
  94. package/src/modules/dashboard/components/dashboard-kpis-widget.tsx +0 -59
  95. package/src/modules/dashboard/components/dashboard-kpis.tsx +0 -13
  96. package/src/modules/dashboard/components/dashboard-latency-percentiles-widget.tsx +0 -85
  97. package/src/modules/dashboard/components/dashboard-metrics-widget.tsx +0 -78
  98. package/src/modules/dashboard/components/dashboard-plugins-widget.tsx +0 -67
  99. package/src/modules/dashboard/components/dashboard-requests-widget.tsx +0 -62
  100. package/src/modules/dashboard/layouts/dashboard-layout.tsx +0 -16
  101. package/src/modules/dashboard/pages/ai-insights-page.tsx +0 -563
  102. package/src/modules/dashboard/pages/dashboard-animations.css +0 -317
  103. package/src/modules/dashboard/pages/dashboard-page.tsx +0 -13
  104. package/src/modules/dashboard/routes/index.tsx +0 -70
  105. package/src/modules/observability/pages/devkit-page.tsx +0 -263
  106. package/src/modules/observability/pages/incident-detail-page.tsx +0 -571
  107. package/src/modules/observability/pages/incidents-page.tsx +0 -370
  108. package/src/modules/observability/pages/log-detail-page.tsx +0 -508
  109. package/src/modules/observability/pages/logs-page.tsx +0 -1129
  110. package/src/modules/observability/pages/prometheus-metrics-page.tsx +0 -486
  111. package/src/modules/observability/pages/state-broker-page.tsx +0 -229
  112. package/src/modules/observability/pages/system-events-page.tsx +0 -255
  113. package/src/modules/observability/routes/index.tsx +0 -133
  114. package/src/modules/plugins/components/marketplace-card.tsx +0 -153
  115. package/src/modules/plugins/components/plugin-ai-assistant-modal.tsx +0 -374
  116. package/src/modules/plugins/components/plugin-card.tsx +0 -136
  117. package/src/modules/plugins/components/plugin-detail/capabilities-summary.tsx +0 -101
  118. package/src/modules/plugins/components/plugin-detail/cli-commands-table.tsx +0 -101
  119. package/src/modules/plugins/components/plugin-detail/index.ts +0 -7
  120. package/src/modules/plugins/components/plugin-detail/jobs-table.tsx +0 -64
  121. package/src/modules/plugins/components/plugin-detail/permissions/env-permissions.tsx +0 -30
  122. package/src/modules/plugins/components/plugin-detail/permissions/filesystem-permissions.tsx +0 -68
  123. package/src/modules/plugins/components/plugin-detail/permissions/index.ts +0 -6
  124. package/src/modules/plugins/components/plugin-detail/permissions/network-permissions.tsx +0 -43
  125. package/src/modules/plugins/components/plugin-detail/permissions/platform-services.tsx +0 -242
  126. package/src/modules/plugins/components/plugin-detail/permissions/resource-quotas.tsx +0 -96
  127. package/src/modules/plugins/components/plugin-detail/permissions/shell-permissions.tsx +0 -55
  128. package/src/modules/plugins/components/plugin-detail/permissions-view.tsx +0 -47
  129. package/src/modules/plugins/components/plugin-detail/plugin-header.tsx +0 -169
  130. package/src/modules/plugins/components/plugin-detail/rest-routes-table.tsx +0 -159
  131. package/src/modules/plugins/components/plugin-detail/workflows-table.tsx +0 -40
  132. package/src/modules/plugins/components/plugin-grid.tsx +0 -20
  133. package/src/modules/plugins/components/plugin-table.tsx +0 -178
  134. package/src/modules/plugins/pages/marketplace-page.tsx +0 -200
  135. package/src/modules/plugins/pages/plugin-detail-page.tsx +0 -1145
  136. package/src/modules/plugins/routes/index.tsx +0 -48
  137. package/src/modules/plugins/utils/index.ts +0 -1
  138. package/src/modules/plugins/utils/schema-helpers.ts +0 -21
  139. package/src/modules/settings/components/api-routes-viewer.tsx +0 -415
  140. package/src/modules/settings/components/appearance-settings.tsx +0 -200
  141. package/src/modules/settings/components/configuration-settings.tsx +0 -321
  142. package/src/modules/settings/components/data-privacy-settings.tsx +0 -111
  143. package/src/modules/settings/components/experimental-settings.tsx +0 -243
  144. package/src/modules/settings/components/navigation-settings.tsx +0 -198
  145. package/src/modules/settings/components/notifications-settings.tsx +0 -40
  146. package/src/modules/settings/pages/settings-page.tsx +0 -298
  147. package/src/modules/settings/routes/index.tsx +0 -47
  148. package/src/pages/README-assistant.md +0 -91
  149. package/src/pages/login-page.tsx +0 -177
  150. package/src/pages/not-found-page.tsx +0 -85
  151. package/src/providers/auth-provider.tsx +0 -40
  152. package/src/providers/command-palette-provider.tsx +0 -193
  153. package/src/providers/data-sources-provider.tsx +0 -27
  154. package/src/providers/devtools-provider.tsx +0 -72
  155. package/src/providers/navigation-items-provider.tsx +0 -38
  156. package/src/providers/registry-v2-provider.tsx +0 -93
  157. package/src/providers/settings-provider.tsx +0 -158
  158. package/src/router.tsx +0 -302
  159. package/src/routes/helpers.tsx +0 -25
  160. package/src/routes/plugin-page-v2.tsx +0 -52
  161. package/src/routes/types.ts +0 -18
  162. package/src/styles/antd-overrides.css +0 -211
  163. package/src/styles/base.css +0 -190
  164. package/src/styles/settings.css +0 -75
  165. package/src/styles/variables.css +0 -94
  166. package/src/utils/analytics.ts +0 -183
  167. package/src/utils/apply-navigation-categories.ts +0 -83
  168. package/src/utils/commands.ts +0 -252
  169. package/src/utils/export.ts +0 -174
  170. package/src/utils/format-date.ts +0 -166
  171. package/src/utils/logger.ts +0 -110
  172. package/tailwind.config.ts +0 -13
  173. package/tsconfig.json +0 -13
  174. package/vitest-setup.ts +0 -1
  175. /package/{public → dist}/favicon.svg +0 -0
@@ -1,130 +0,0 @@
1
- import * as React from 'react';
2
- import { ConfigProvider } from 'antd';
3
- import type { ConfigProviderProps, ThemeConfig } from 'antd';
4
- import { getAntDesignTokens, getAntDesignComponents } from '@kb-labs/studio-hooks';
5
- import { ThemeTransitionOverlay } from './theme-transition-overlay';
6
-
7
- export type ThemeMode = 'light' | 'dark' | 'auto';
8
-
9
- export interface KBConfigProviderProps extends Omit<ConfigProviderProps, 'theme'> {
10
- defaultTheme?: ThemeMode;
11
- storageKey?: string;
12
- children: React.ReactNode;
13
- }
14
-
15
- const KBConfigProviderContext = React.createContext<{
16
- theme: ThemeMode;
17
- setTheme: (theme: ThemeMode) => void;
18
- }>({
19
- theme: 'light',
20
- setTheme: () => {},
21
- });
22
-
23
- export function KBConfigProvider({
24
- defaultTheme = 'light',
25
- storageKey = 'studio-ui-theme',
26
- children,
27
- ...configProps
28
- }: KBConfigProviderProps) {
29
- // Get initial theme
30
- const getInitialTheme = (): ThemeMode => {
31
- if (typeof window === 'undefined') {return defaultTheme;}
32
- const stored = localStorage.getItem(storageKey) as ThemeMode;
33
- return stored || defaultTheme;
34
- };
35
-
36
- const [theme, setThemeState] = React.useState<ThemeMode>(getInitialTheme);
37
- const [showOverlay, setShowOverlay] = React.useState(false);
38
- const [overlayTheme, setOverlayTheme] = React.useState<ThemeMode>(theme);
39
- const [isInitialMount, setIsInitialMount] = React.useState(true);
40
-
41
- // Get actual theme for tokens (auto -> system preference)
42
- const actualTheme = React.useMemo(() => {
43
- if (typeof window === 'undefined') {return theme === 'auto' ? 'light' : theme;}
44
- return theme === 'auto'
45
- ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
46
- : theme;
47
- }, [theme]);
48
-
49
- // Apply theme class SYNCHRONOUSLY on mount and theme change
50
- // This ensures CSS variables are correct before tokens are computed
51
- React.useLayoutEffect(() => {
52
- if (typeof window === 'undefined') {return;}
53
-
54
- const root = document.documentElement;
55
- root.classList.remove('light', 'dark');
56
- root.classList.add(actualTheme);
57
- root.setAttribute('data-theme', actualTheme);
58
- }, [actualTheme]);
59
-
60
- // Store theme in localStorage (non-blocking)
61
- React.useEffect(() => {
62
- if (typeof window === 'undefined') {return;}
63
- localStorage.setItem(storageKey, theme);
64
- }, [theme, storageKey]);
65
-
66
- // Mark initial mount as complete after first render
67
- React.useEffect(() => {
68
- setIsInitialMount(false);
69
- }, []);
70
-
71
- const setTheme = React.useCallback((newTheme: ThemeMode) => {
72
- // Skip overlay on initial mount
73
- if (isInitialMount) {
74
- setThemeState(newTheme);
75
- return;
76
- }
77
-
78
- // Show overlay with new theme label
79
- setOverlayTheme(newTheme);
80
- setShowOverlay(true);
81
-
82
- // Wait for overlay to slide down (300ms)
83
- setTimeout(() => {
84
- // Change theme while overlay covers the screen
85
- setThemeState(newTheme);
86
-
87
- // Hold overlay to let color transition finish (150ms transition + 100ms buffer)
88
- setTimeout(() => {
89
- // Slide overlay back up
90
- setShowOverlay(false);
91
- }, 250);
92
- }, 300);
93
- }, [isInitialMount]);
94
-
95
- // Tokens now use CSS variables via var() - no need to recompute
96
- const tokens = React.useMemo(() => getAntDesignTokens(), []);
97
- const components = React.useMemo(() => getAntDesignComponents(), []);
98
- // Don't use algorithm - it would invert colors again
99
- // CSS variables already handle theme switching via .light/.dark classes
100
- // const algorithm = React.useMemo(() => getThemeAlgorithm(theme), [theme]);
101
-
102
- const value = React.useMemo(() => ({ theme, setTheme }), [theme, setTheme]);
103
-
104
- const themeConfig: ThemeConfig = React.useMemo(
105
- () => ({
106
- token: tokens,
107
- components: components,
108
- // algorithm: algorithm, // Disabled - CSS variables handle theme
109
- }),
110
- [tokens, components]
111
- );
112
-
113
- return (
114
- <KBConfigProviderContext.Provider value={value}>
115
- <ConfigProvider theme={themeConfig} {...configProps}>
116
- {children}
117
- <ThemeTransitionOverlay show={showOverlay} theme={overlayTheme} />
118
- </ConfigProvider>
119
- </KBConfigProviderContext.Provider>
120
- );
121
- }
122
-
123
- export function useKBTheme() {
124
- const context = React.useContext(KBConfigProviderContext);
125
- if (context === undefined) {
126
- throw new Error('useKBTheme must be used within KBConfigProvider');
127
- }
128
- return context;
129
- }
130
-
@@ -1,51 +0,0 @@
1
- .header {
2
- display: flex;
3
- align-items: center;
4
- justify-content: space-between;
5
- padding: 0 16px;
6
- position: fixed;
7
- top: 0;
8
- left: 0;
9
- right: 0;
10
- z-index: var(--z-header);
11
- height: 64px;
12
- background-color: var(--bg-secondary);
13
- color: var(--text-primary);
14
- border-bottom: none;
15
- box-shadow: 0 1px 0 var(--border-primary), 0 2px 8px rgba(0, 0, 0, 0.04);
16
- transition: padding-left 0.2s;
17
- }
18
-
19
- .logo {
20
- display: flex;
21
- align-items: center;
22
- gap: 8px;
23
- font-size: 18px;
24
- font-weight: bold;
25
- color: var(--text-primary);
26
- text-decoration: none;
27
- }
28
-
29
- .actions {
30
- display: flex;
31
- align-items: center;
32
- gap: 4px;
33
- }
34
-
35
- .actionBtn {
36
- height: 32px !important;
37
- display: flex !important;
38
- align-items: center !important;
39
- }
40
-
41
- .userBtn {
42
- height: 32px !important;
43
- display: flex !important;
44
- align-items: center !important;
45
- gap: 6px !important;
46
- padding: 0 8px !important;
47
- }
48
-
49
- .userName {
50
- font-size: 13px;
51
- }
@@ -1,112 +0,0 @@
1
- import * as React from 'react';
2
- import { Layout, Dropdown, Avatar, type MenuProps } from 'antd';
3
- import { User, Search } from 'lucide-react';
4
- import { UIButton } from '@kb-labs/studio-ui-kit';
5
- import { KBThemeToggle } from './kb-theme-toggle';
6
- import { type SystemHealthData } from './kb-system-health-indicator';
7
- import { KBNotificationBell, type LogNotification } from './kb-notification-bell';
8
- import styles from './kb-header.module.css';
9
-
10
- const { Header: AntHeader } = Layout;
11
-
12
- export interface KBHeaderProps {
13
- logo?: React.ReactNode;
14
- logoLink?: string;
15
- LinkComponent?: React.ComponentType<{ to: string; children: React.ReactNode; className?: string }>;
16
- onLogout?: () => void;
17
- profileMenuItems?: MenuProps['items'];
18
- userAvatar?: string;
19
- userName?: string;
20
- systemHealth?: SystemHealthData;
21
- systemHealthLoading?: boolean;
22
- onSearchClick?: () => void;
23
- sidebarCollapsed?: boolean;
24
- onSidebarCollapse?: (collapsed: boolean) => void;
25
- sidebarWidth?: number;
26
- notifications?: LogNotification[];
27
- unreadNotificationsCount?: number;
28
- onMarkNotificationAsRead?: (id: string) => void;
29
- onMarkAllNotificationsAsRead?: () => void;
30
- onClearAllNotifications?: () => void;
31
- onClearNotification?: (id: string) => void;
32
- onNotificationClick?: (notification: LogNotification) => void;
33
- }
34
-
35
- export function KBHeader({
36
- logo = 'KB Labs',
37
- logoLink = '/',
38
- LinkComponent,
39
- onLogout,
40
- profileMenuItems,
41
- userAvatar,
42
- userName = 'User',
43
- onSearchClick,
44
- sidebarWidth = 220,
45
- notifications = [],
46
- unreadNotificationsCount = 0,
47
- onMarkNotificationAsRead,
48
- onMarkAllNotificationsAsRead,
49
- onClearAllNotifications,
50
- onClearNotification,
51
- onNotificationClick,
52
- }: KBHeaderProps) {
53
- const profileItems: MenuProps['items'] = profileMenuItems || [
54
- ...(onLogout ? [{ key: 'logout', label: 'Logout', danger: true, onClick: onLogout }] : []),
55
- ];
56
-
57
- return (
58
- <AntHeader
59
- className={styles.header}
60
- style={{ paddingLeft: sidebarWidth + 16 }}
61
- >
62
- {LinkComponent ? (
63
- <LinkComponent to={logoLink} className={styles.logo}>
64
- {logo}
65
- </LinkComponent>
66
- ) : (
67
- <a href={logoLink} className={styles.logo}>
68
- {logo}
69
- </a>
70
- )}
71
-
72
- <div className={styles.actions}>
73
- {onSearchClick && (
74
- <UIButton
75
- variant="text"
76
- icon={<Search size={16} />}
77
- onClick={onSearchClick}
78
- className={styles.actionBtn}
79
- />
80
- )}
81
-
82
- {onMarkNotificationAsRead &&
83
- onMarkAllNotificationsAsRead &&
84
- onClearAllNotifications &&
85
- onClearNotification && (
86
- <KBNotificationBell
87
- notifications={notifications}
88
- unreadCount={unreadNotificationsCount}
89
- onMarkAsRead={onMarkNotificationAsRead}
90
- onMarkAllAsRead={onMarkAllNotificationsAsRead}
91
- onClearAll={onClearAllNotifications}
92
- onClearNotification={onClearNotification}
93
- onNotificationClick={onNotificationClick}
94
- />
95
- )}
96
-
97
- <KBThemeToggle />
98
-
99
- <Dropdown menu={{ items: profileItems }} placement="bottomRight">
100
- <UIButton variant="text" className={styles.userBtn}>
101
- <Avatar
102
- size={20}
103
- icon={!userAvatar && <User size={12} />}
104
- src={userAvatar}
105
- />
106
- {userName && <span className={styles.userName}>{userName}</span>}
107
- </UIButton>
108
- </Dropdown>
109
- </div>
110
- </AntHeader>
111
- );
112
- }
@@ -1,103 +0,0 @@
1
- .bell {
2
- display: flex;
3
- align-items: center;
4
- }
5
-
6
- .panel {
7
- width: 400px;
8
- max-height: 500px;
9
- display: flex;
10
- flex-direction: column;
11
- background-color: var(--bg-secondary);
12
- border: 1px solid var(--border-primary);
13
- border-radius: 8px;
14
- box-shadow: 0 4px 16px var(--shadow);
15
- }
16
-
17
- .panelHeader {
18
- padding: 12px 16px;
19
- border-bottom: 1px solid var(--border-primary);
20
- display: flex;
21
- justify-content: space-between;
22
- align-items: center;
23
- flex-shrink: 0;
24
- }
25
-
26
- .panelList {
27
- overflow-y: auto;
28
- flex: 1;
29
- }
30
-
31
- .emptyState {
32
- padding: 48px 16px;
33
- }
34
-
35
- .notifItem {
36
- padding: 12px 16px;
37
- border-bottom: 1px solid var(--border-primary);
38
- cursor: pointer;
39
- transition: background-color 0.15s;
40
- }
41
-
42
- .notifItem:hover {
43
- background-color: var(--bg-hover);
44
- }
45
-
46
- .notifItem.unread {
47
- background-color: var(--bg-tertiary);
48
- }
49
-
50
- .notifItem.unread:hover {
51
- background-color: var(--bg-hover);
52
- }
53
-
54
- .notifRow {
55
- display: flex;
56
- gap: 8px;
57
- }
58
-
59
- .notifIcon {
60
- flex-shrink: 0;
61
- padding-top: 2px;
62
- }
63
-
64
- .notifBody {
65
- flex: 1;
66
- min-width: 0;
67
- }
68
-
69
- .notifMeta {
70
- display: flex;
71
- align-items: center;
72
- gap: 8px;
73
- margin-bottom: 4px;
74
- }
75
-
76
- .notifTime {
77
- margin-left: auto;
78
- }
79
-
80
- .notifMessage {
81
- margin: 0;
82
- font-size: 13px;
83
- color: var(--text-primary);
84
- word-break: break-word;
85
- }
86
-
87
- .notifExecId {
88
- font-size: 11px;
89
- font-family: monospace;
90
- display: block;
91
- margin-top: 4px;
92
- }
93
-
94
- .notifClear {
95
- flex-shrink: 0;
96
- }
97
-
98
- .panelFooter {
99
- padding: 8px 16px;
100
- border-top: 1px solid var(--border-primary);
101
- text-align: center;
102
- flex-shrink: 0;
103
- }
@@ -1,199 +0,0 @@
1
- import * as React from 'react';
2
- import { Badge, Dropdown, List, Typography, Tag, Empty, Space, Tooltip } from 'antd';
3
- import { Bell, AlertTriangle, XCircle, X, CheckCheck } from 'lucide-react';
4
- import { UIButton } from '@kb-labs/studio-ui-kit';
5
- import styles from './kb-notification-bell.module.css';
6
-
7
- const { Text, Paragraph } = Typography;
8
-
9
- export interface LogNotification {
10
- id: string;
11
- timestamp: string;
12
- level: 'warn' | 'error';
13
- message: string;
14
- plugin?: string;
15
- executionId?: string;
16
- error?: {
17
- name: string;
18
- message: string;
19
- };
20
- read: boolean;
21
- }
22
-
23
- export interface KBNotificationBellProps {
24
- notifications: LogNotification[];
25
- unreadCount: number;
26
- onMarkAsRead: (id: string) => void;
27
- onMarkAllAsRead: () => void;
28
- onClearAll: () => void;
29
- onClearNotification: (id: string) => void;
30
- onNotificationClick?: (notification: LogNotification) => void;
31
- }
32
-
33
- export function KBNotificationBell({
34
- notifications,
35
- unreadCount,
36
- onMarkAsRead,
37
- onMarkAllAsRead,
38
- onClearAll,
39
- onClearNotification,
40
- onNotificationClick,
41
- }: KBNotificationBellProps) {
42
- const [open, setOpen] = React.useState(false);
43
-
44
- const formatTimestamp = (timestamp: string) => {
45
- const date = new Date(timestamp);
46
- const now = new Date();
47
- const diffMs = now.getTime() - date.getTime();
48
- const diffMins = Math.floor(diffMs / 60000);
49
- const diffHours = Math.floor(diffMs / 3600000);
50
- const diffDays = Math.floor(diffMs / 86400000);
51
-
52
- if (diffMins < 1) { return 'Just now'; }
53
- if (diffMins < 60) { return `${diffMins}m ago`; }
54
- if (diffHours < 24) { return `${diffHours}h ago`; }
55
- return `${diffDays}d ago`;
56
- };
57
-
58
- const getLevelIcon = (level: 'warn' | 'error') => {
59
- if (level === 'error') {
60
- return <XCircle size={16} style={{ color: 'var(--error)' }} />;
61
- }
62
- return <AlertTriangle size={16} style={{ color: 'var(--warning)' }} />;
63
- };
64
-
65
- const getLevelColor = (level: 'warn' | 'error') => {
66
- return level === 'error' ? 'red' : 'orange';
67
- };
68
-
69
- const notificationContent = (
70
- <div className={styles.panel}>
71
- <div className={styles.panelHeader}>
72
- <Space>
73
- <Text strong>Notifications</Text>
74
- {unreadCount > 0 && (
75
- <Badge count={unreadCount} style={{ backgroundColor: 'var(--error)' }} />
76
- )}
77
- </Space>
78
- <Space size="small">
79
- {notifications.length > 0 && (
80
- <>
81
- <Tooltip title="Mark all as read">
82
- <UIButton
83
- variant="text"
84
- size="small"
85
- icon={<CheckCheck size={14} />}
86
- onClick={(e) => { e.stopPropagation(); onMarkAllAsRead(); }}
87
- />
88
- </Tooltip>
89
- <Tooltip title="Clear all">
90
- <UIButton
91
- variant="text"
92
- size="small"
93
- danger
94
- icon={<X size={14} />}
95
- onClick={(e) => { e.stopPropagation(); onClearAll(); }}
96
- />
97
- </Tooltip>
98
- </>
99
- )}
100
- </Space>
101
- </div>
102
-
103
- <div className={styles.panelList}>
104
- {notifications.length === 0 ? (
105
- <div className={styles.emptyState}>
106
- <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="No critical logs" />
107
- </div>
108
- ) : (
109
- <List
110
- dataSource={notifications}
111
- renderItem={(notification) => (
112
- <div
113
- className={`${styles.notifItem} ${!notification.read ? styles.unread : ''}`}
114
- onClick={() => {
115
- if (!notification.read) { onMarkAsRead(notification.id); }
116
- if (onNotificationClick) {
117
- setOpen(false);
118
- onNotificationClick(notification);
119
- }
120
- }}
121
- >
122
- <div className={styles.notifRow}>
123
- <div className={styles.notifIcon}>{getLevelIcon(notification.level)}</div>
124
- <div className={styles.notifBody}>
125
- <div className={styles.notifMeta}>
126
- <Tag color={getLevelColor(notification.level)} style={{ margin: 0 }}>
127
- {notification.level.toUpperCase()}
128
- </Tag>
129
- {notification.plugin && (
130
- <Tag color="purple" style={{ margin: 0, fontSize: 11 }}>
131
- {notification.plugin}
132
- </Tag>
133
- )}
134
- <Text type="secondary" className={styles.notifTime} style={{ fontSize: 11 }}>
135
- {formatTimestamp(notification.timestamp)}
136
- </Text>
137
- </div>
138
- <Paragraph className={styles.notifMessage} ellipsis={{ rows: 2 }}>
139
- {notification.error && typeof notification.error === 'object'
140
- ? `${notification.error.name || 'Error'}: ${notification.error.message || 'Unknown error'}`
141
- : notification.message || 'No message'}
142
- </Paragraph>
143
- {notification.executionId && (
144
- <Text type="secondary" className={styles.notifExecId}>
145
- exec: {notification.executionId.slice(0, 8)}
146
- </Text>
147
- )}
148
- </div>
149
- <div className={styles.notifClear}>
150
- <UIButton
151
- variant="text"
152
- size="small"
153
- icon={<X size={12} />}
154
- onClick={(e) => { e.stopPropagation(); onClearNotification(notification.id); }}
155
- />
156
- </div>
157
- </div>
158
- </div>
159
- )}
160
- />
161
- )}
162
- </div>
163
-
164
- {notifications.length > 0 && (
165
- <div className={styles.panelFooter}>
166
- <UIButton
167
- variant="link"
168
- size="small"
169
- onClick={() => {
170
- setOpen(false);
171
- window.location.href = '/observability/logs';
172
- }}
173
- >
174
- View all logs →
175
- </UIButton>
176
- </div>
177
- )}
178
- </div>
179
- );
180
-
181
- return (
182
- <Dropdown
183
- open={open}
184
- onOpenChange={setOpen}
185
- popupRender={() => notificationContent}
186
- trigger={['click']}
187
- placement="bottomRight"
188
- >
189
- <Badge count={unreadCount} offset={[-2, 2]} size="small">
190
- <UIButton
191
- variant="text"
192
- icon={<Bell size={18} />}
193
- className={styles.bell}
194
- style={unreadCount > 0 ? { color: 'var(--error)' } : undefined}
195
- />
196
- </Badge>
197
- </Dropdown>
198
- );
199
- }
@@ -1,66 +0,0 @@
1
- import * as React from 'react';
2
- import { Layout as AntLayout } from 'antd';
3
- import { KBHeader, type KBHeaderProps } from './kb-header';
4
- import { KBSidebar, type KBSidebarProps, type NavigationItem } from './kb-sidebar';
5
-
6
- const { Content: AntContent } = AntLayout;
7
-
8
- export interface KBPageLayoutProps {
9
- headerProps?: KBHeaderProps;
10
- sidebarProps: Omit<KBSidebarProps, 'collapsed' | 'onCollapse'>;
11
- sidebarCollapsed?: boolean;
12
- onSidebarCollapse?: (collapsed: boolean) => void;
13
- statusBar?: React.ReactNode;
14
- children?: React.ReactNode;
15
- }
16
-
17
- export function KBPageLayout({
18
- headerProps,
19
- sidebarProps,
20
- sidebarCollapsed,
21
- onSidebarCollapse,
22
- statusBar,
23
- children,
24
- }: KBPageLayoutProps) {
25
- const [internalCollapsed, setInternalCollapsed] = React.useState(false);
26
-
27
- const collapsed = sidebarCollapsed !== undefined ? sidebarCollapsed : internalCollapsed;
28
- const setCollapsed = React.useCallback(
29
- (newCollapsed: boolean) => {
30
- if (sidebarCollapsed === undefined) {
31
- setInternalCollapsed(newCollapsed);
32
- }
33
- onSidebarCollapse?.(newCollapsed);
34
- },
35
- [sidebarCollapsed, onSidebarCollapse]
36
- );
37
-
38
- const sidebarWidth = collapsed ? (sidebarProps.collapsedWidth || 64) : (sidebarProps.width || 220);
39
-
40
- return (
41
- <AntLayout style={{ minHeight: '100vh' }}>
42
- <KBHeader
43
- {...headerProps}
44
- sidebarCollapsed={collapsed}
45
- onSidebarCollapse={setCollapsed}
46
- sidebarWidth={sidebarWidth}
47
- />
48
- <AntLayout style={{ marginTop: 64 }}>
49
- <KBSidebar {...sidebarProps} collapsed={collapsed} onCollapse={setCollapsed} />
50
- <AntLayout
51
- style={{
52
- marginLeft: sidebarWidth,
53
- transition: 'margin-left 0.2s',
54
- minHeight: 'calc(100vh - 64px)',
55
- paddingBottom: statusBar ? 28 : 0,
56
- }}
57
- >
58
- <AntContent>{children}</AntContent>
59
- </AntLayout>
60
- </AntLayout>
61
- {statusBar}
62
- </AntLayout>
63
- );
64
- }
65
-
66
- export type { KBHeaderProps, KBSidebarProps, NavigationItem };