Skip to content

Commit 3400fa9

Browse files
authored
Merge branch 'main' into codex/fix-kb-ajv-cli
2 parents b18b54f + e17e8ec commit 3400fa9

9 files changed

Lines changed: 1196 additions & 447 deletions

File tree

web/package-lock.json

Lines changed: 1120 additions & 433 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@
101101
"@eslint/js": "^10.0.1",
102102
"@netlify/functions": "^5.3.0",
103103
"@playwright/test": "^1.60.0",
104-
"@storybook/addon-a11y": "^10.4.4",
105-
"@storybook/react": "^10.4.4",
106-
"@storybook/react-vite": "^10.4.4",
104+
"@storybook/addon-a11y": "^10.4.6",
105+
"@storybook/react": "^10.4.6",
106+
"@storybook/react-vite": "^10.4.6",
107107
"@tailwindcss/postcss": "^4.3.0",
108108
"@testing-library/dom": "^10.4.1",
109109
"@testing-library/jest-dom": "^6.9.1",
@@ -129,7 +129,7 @@
129129
"msw": "^2.14.6",
130130
"postcss": "^8.5.14",
131131
"rimraf": "^6.1.3",
132-
"storybook": "^8.6.18",
132+
"storybook": "^10.4.6",
133133
"tailwindcss": "^4.3.0",
134134
"typescript": "~6.0.3",
135135
"typescript-eslint": "^8.61.0",

web/src/contexts/AlertsContext.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,20 @@ import { createStateContext } from './createStateContext'
4141
import { applyMutations, createAlertRulesEngine, generateId, shallowEqualRecords } from './alertRulesEngine'
4242

4343
const AlertsDataFetcher = safeLazy(() => import('./AlertsDataFetcher'), 'default')
44+
45+
/** Fallback frame time (ms) for MCP update batching when requestAnimationFrame is unavailable (~60fps). */
4446
const MCP_UPDATE_BATCH_FRAME_FALLBACK_MS = 16
47+
48+
/** Storage key for persisted alert rules */
4549
const ALERT_RULES_KEY = 'kc_alert_rules'
50+
51+
/** Maximum time (ms) to wait for initial MCP data before timing out. */
4652
const LOADING_TIMEOUT_MS = 30_000
53+
54+
/** Delay (ms) before first alert evaluation after mount to allow data fetching to settle. */
4755
const INITIAL_EVALUATION_DELAY_MS = 1000
56+
57+
/** Interval (ms) between periodic alert rule evaluations. */
4858
const EVALUATION_INTERVAL_MS = 30000
4959

5060
const {

web/src/contexts/alertRunbooks.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ export interface RunbookExecutionResult {
1818
stepResults: unknown[]
1919
}
2020

21+
/** Maximum length (chars) for JSON-serialized prompt data before truncation. */
2122
const PROMPT_JSON_MAX_LENGTH = 4000
23+
24+
/** Maximum length (chars) for runbook evidence text before truncation. */
2225
const RUNBOOK_EVIDENCE_MAX_LENGTH = 8000
2326

2427
/**

web/src/contexts/alertStorage.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export const NOTIFICATION_COOLDOWN_BY_SEVERITY: Record<string, number> = {
4646
/** Fallback cooldown when severity is unknown */
4747
export const DEFAULT_NOTIFICATION_COOLDOWN_MS = 30 * MS_PER_MINUTE // 30 min
4848

49+
/** Legacy DOMException error code for QuotaExceededError (used by older browsers). */
50+
const DOM_EXCEPTION_QUOTA_EXCEEDED_CODE = 22
51+
4952
/** Load persisted notification dedup map from localStorage (key → timestamp).
5053
* Prunes stale entries (older than NOTIFICATION_DEDUP_MAX_AGE_MS) and enforces
5154
* the MAX_DEDUP_KEYS hard cap on load, persisting the cleaned map back. */
@@ -152,7 +155,7 @@ export function saveAlerts(alerts: Alert[]): void {
152155
// QuotaExceededError: DOMException with name 'QuotaExceededError', or legacy
153156
// browsers that use numeric code 22 instead of the named exception.
154157
// Pattern matches useMissions/useMetricsHistory for consistency across the codebase.
155-
const isQuotaError = e instanceof DOMException && (e.name === 'QuotaExceededError' || e.code === 22)
158+
const isQuotaError = e instanceof DOMException && (e.name === 'QuotaExceededError' || e.code === DOM_EXCEPTION_QUOTA_EXCEEDED_CODE)
156159
if (isQuotaError) {
157160
console.warn('[Alerts] localStorage quota exceeded, pruning resolved alerts')
158161
// Keep all firing alerts + a small number of recent resolved ones

web/src/contexts/notifications.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import {
2222
* no 5-minute cooldown repeat for ongoing connectivity failures. */
2323
export const PERSISTENT_CLUSTER_CONDITIONS = new Set(['certificate_error', 'cluster_unreachable'])
2424

25+
/** HTTP status code for unauthorized requests (missing or invalid auth token). */
26+
const HTTP_STATUS_UNAUTHORIZED = 401
27+
28+
/** HTTP status code for forbidden requests (insufficient permissions). */
29+
const HTTP_STATUS_FORBIDDEN = 403
30+
2531
/** Parameters for the centralized browser notification dispatcher */
2632
export interface BrowserNotificationParams {
2733
/** The alert rule that triggered this notification */
@@ -130,7 +136,7 @@ async function sendSingleNotification(
130136
signal: AbortSignal.timeout(fetchTimeout),
131137
})
132138

133-
if (response.status === 401 || response.status === 403) return
139+
if (response.status === HTTP_STATUS_UNAUTHORIZED || response.status === HTTP_STATUS_FORBIDDEN) return
134140

135141
if (!response.ok) {
136142
const data = await response.json().catch(() => ({})) as NotificationErrorResponse

web/src/lib/constants/units.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/** Bytes per mebibyte (MiB) */
22
export const BYTES_PER_MIB = 1024 * 1024
3+
/** Bytes per kibibyte (KiB) */
4+
export const BYTES_PER_KIB = 1024
35
/** Bytes per gibibyte (GiB) */
46
export const BYTES_PER_GIB = 1024 * 1024 * 1024
57
/** Bytes per tebibyte (TiB) */

web/src/lib/formatters.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import type { TFunction } from 'i18next'
66
import { MS_PER_SECOND, MS_PER_MINUTE, MS_PER_HOUR, MS_PER_DAY, MS_PER_MONTH, MS_PER_YEAR, SECONDS_PER_MINUTE, MINUTES_PER_HOUR } from './constants/time'
7+
import { BYTES_PER_KIB } from './constants/units'
78

89
/**
910
* Format the elapsed time between two ISO timestamps as a compact string.
@@ -81,7 +82,6 @@ interface FormatBytesOptions {
8182

8283
const SI_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
8384
const IEC_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']
84-
const BYTES_PER_KIBIBYTE = 1024
8585

8686
/**
8787
* Format bytes to a human-readable string.
@@ -106,8 +106,8 @@ export function formatBytes(
106106
if (!Number.isFinite(bytes) || bytes <= 0) return zeroLabel
107107

108108
const units = binary ? IEC_UNITS : SI_UNITS
109-
const i = Math.floor(Math.log(bytes) / Math.log(BYTES_PER_KIBIBYTE))
110-
const value = bytes / Math.pow(BYTES_PER_KIBIBYTE, i)
109+
const i = Math.floor(Math.log(bytes) / Math.log(BYTES_PER_KIB))
110+
const value = bytes / Math.pow(BYTES_PER_KIB, i)
111111

112112
// Use 0 decimals for whole numbers, otherwise use specified decimals
113113
if (value === Math.floor(value)) {

web/vite.config.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,22 @@ export default defineConfig(({ mode }) => ({
124124
['cards-workloads', ['/src/components/cards/workload-detection/', '/src/components/cards/workload-monitor/']],
125125
['cards-storage', ['/src/components/cards/vitess_status/', '/src/components/cards/minio_status/', '/src/components/cards/etcd_status/']],
126126
['cards-messaging', ['/src/components/cards/kafka_status/', '/src/components/cards/rabbitmq_status/', '/src/components/cards/redis_status/']],
127+
// Split cards-misc further to reduce bundle size
128+
['cards-monitoring', ['/src/components/cards/prometheus_status/', '/src/components/cards/grafana_status/', '/src/components/cards/alertmanager_status/']],
129+
['cards-cluster', ['/src/components/cards/cluster_health/', '/src/components/cards/cluster_capacity/', '/src/components/cards/cluster_nodes/']],
130+
['cards-cost', ['/src/components/cards/cost/', '/src/components/cards/kubecost_status/']],
131+
['cards-data', ['/src/components/cards/postgresql_status/', '/src/components/cards/mysql_status/', '/src/components/cards/mongodb_status/']],
127132
['cards-misc', ['/src/components/cards/']],
128133
// Split drilldown views by type to reduce chunk size
129134
['drilldown-k8s', ['/src/components/drilldown/views/PodLogs', '/src/components/drilldown/views/PodEvents', '/src/components/drilldown/views/PodTerminal', '/src/components/drilldown/views/NamespaceDetails']],
130135
['drilldown-data', ['/src/components/drilldown/views/LogViewer', '/src/components/drilldown/views/MetricsViewer', '/src/components/drilldown/views/EventTimeline']],
136+
['drilldown-ui', ['/src/components/drilldown/DrillDownModal', '/src/components/drilldown/DrillDownHeader', '/src/components/drilldown/DrillDownStack']],
131137
['drilldown', ['/src/components/drilldown/']],
132138
// Dashboard and layout split by concern
133139
['dashboard-customizer', ['/src/components/dashboard/customizer/', '/src/components/dashboard/shared/cardCatalog']],
140+
['dashboard-grid', ['/src/components/dashboard/CardGrid', '/src/components/dashboard/DashboardGrid']],
134141
['dashboard-core', ['/src/components/dashboard/', '/src/lib/dashboards/', '/src/lib/unified/dashboard/']],
142+
['layout-header', ['/src/components/layout/Header', '/src/components/layout/TopBar', '/src/components/layout/UserMenu']],
135143
['layout-sidebar', ['/src/components/layout/Sidebar', '/src/components/layout/Navigation', '/src/components/layout/MobileMenu']],
136144
['layout-shell', ['/src/components/layout/']],
137145
['auth-core', ['/src/lib/auth']],
@@ -141,8 +149,10 @@ export default defineConfig(({ mode }) => ({
141149
['contexts-providers', ['/src/contexts/', '/src/hooks/useDrillDown', '/src/hooks/useRewards', '/src/hooks/useMissions', '/src/hooks/useGlobalFilters']],
142150
['hooks-data', ['/src/hooks/useCached', '/src/hooks/useCache', '/src/hooks/useCluster', '/src/hooks/useDashboard']],
143151
['lib-cache', ['/src/lib/cache/']],
152+
['lib-utils', ['/src/lib/utils', '/src/lib/cn.ts', '/src/lib/constants.ts']],
144153
['theme-system', ['/src/hooks/useTheme', '/src/hooks/useBranding']],
145-
// Split app shell to reduce size
154+
// Split app shell to reduce massive app-routes chunk
155+
['app-router', ['/src/components/router/', '/src/lib/router/']],
146156
['app-routes', ['/src/App.tsx']],
147157
['app-shell', ['/src/hooks/usePersistedSettings', '/src/hooks/useAppInit']],
148158
['i18n-app', ['/src/lib/i18n.ts', '/src/locales/']],
@@ -153,25 +163,40 @@ export default defineConfig(({ mode }) => ({
153163
if (!id.includes('node_modules')) return
154164
// React core (split scheduler separately to reduce main react bundle)
155165
if (id.includes('/scheduler/')) return 'react-scheduler-vendor'
156-
if (id.includes('/react/') || id.includes('/react-dom/') || id.includes('/react-router') || id.includes('/react-reconciler/')) return 'react-vendor'
157-
// three.js ecosystem (split into smaller chunks)
166+
if (id.includes('/react-dom/client')) return 'react-dom-client-vendor'
167+
if (id.includes('/react-dom/')) return 'react-dom-vendor'
168+
if (id.includes('/react-router-dom/')) return 'react-router-dom-vendor'
169+
if (id.includes('/react-router/')) return 'react-router-vendor'
170+
if (id.includes('/react-reconciler/')) return 'react-reconciler-vendor'
171+
if (id.includes('/react/')) return 'react-vendor'
172+
// three.js ecosystem (split into smaller chunks to reduce three-core and three-drei)
158173
if (id.includes('/three-stdlib/')) return 'three-stdlib-vendor'
159174
if (id.includes('/@react-three/fiber/')) return 'three-fiber-vendor'
160-
if (id.includes('/@react-three/drei/')) return 'three-drei-vendor'
175+
// Split drei into sub-chunks to reduce 304KB chunk
176+
if (id.includes('/@react-three/drei/') && id.includes('/core/')) return 'drei-core-vendor'
177+
if (id.includes('/@react-three/drei/') && id.includes('/web/')) return 'drei-web-vendor'
178+
if (id.includes('/@react-three/drei/')) return 'drei-helpers-vendor'
161179
if (id.includes('/@react-three/') || id.includes('/zustand/') || id.includes('/stats-gl/')) return 'three-react-vendor'
180+
// Split three.js core modules to reduce 708KB chunk
162181
if (id.includes('/three/build/three.module.js')) return 'three-core-vendor'
182+
if (id.includes('/three/src/math/')) return 'three-math-vendor'
183+
if (id.includes('/three/src/loaders/')) return 'three-loaders-vendor'
163184
if (id.includes('/three/')) return 'three-extras-vendor'
164185
// Chart libraries
165186
if (id.includes('/zrender/')) return 'zrender-vendor'
166187
if (id.includes('/echarts-for-react/')) return 'echarts-react-vendor'
167188
if (id.includes('/echarts/')) return 'echarts-vendor'
168189
if (id.includes('/framer-motion/')) return 'motion-vendor'
169-
// Terminal (split addons from core)
190+
// Terminal (split addons from core to reduce xterm-core 336KB chunk)
191+
if (id.includes('/@xterm/addon-fit/')) return 'xterm-addon-fit-vendor'
192+
if (id.includes('/@xterm/addon-web-links/')) return 'xterm-addon-links-vendor'
170193
if (id.includes('/@xterm/addon-')) return 'xterm-addon-vendor'
171194
if (id.includes('/@xterm/xterm/')) return 'xterm-core-vendor'
172195
if (id.includes('/@xterm/')) return 'xterm-vendor'
173196
// UI libraries
174197
if (id.includes('/lucide-react/')) return 'lucide-vendor'
198+
if (id.includes('/@dnd-kit/core/')) return 'dnd-core-vendor'
199+
if (id.includes('/@dnd-kit/sortable/')) return 'dnd-sortable-vendor'
175200
if (id.includes('/@dnd-kit/')) return 'dnd-vendor'
176201
if (
177202
id.includes('/react-markdown/') ||
@@ -207,6 +232,19 @@ export default defineConfig(({ mode }) => ({
207232
if (id.includes('/dompurify/')) return 'sanitize-vendor'
208233
if (id.includes('/zod/')) return 'schema-vendor'
209234
if (id.includes('/@tanstack/react-virtual/')) return 'virtual-vendor'
235+
// Split date/time libraries
236+
if (id.includes('/date-fns/')) return 'date-vendor'
237+
if (id.includes('/dayjs/')) return 'dayjs-vendor'
238+
// Split utility libraries to reduce generic vendor chunk
239+
if (id.includes('/lodash/') || id.includes('/lodash-es/')) return 'lodash-vendor'
240+
if (id.includes('/axios/')) return 'axios-vendor'
241+
if (id.includes('/clsx/') || id.includes('/classnames/')) return 'classnames-vendor'
242+
if (id.includes('/uuid/')) return 'uuid-vendor'
243+
// Split state management
244+
if (id.includes('/jotai/')) return 'jotai-vendor'
245+
if (id.includes('/immer/')) return 'immer-vendor'
246+
// Split async utilities
247+
if (id.includes('/p-limit/') || id.includes('/p-queue/')) return 'async-vendor'
210248
return 'vendor'
211249
},
212250
},

0 commit comments

Comments
 (0)