@kb-labs/studio-app 0.7.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.
- package/dist/index.html +1 -0
- package/dist/main.1d68e922.js +522 -0
- package/dist/main.1d68e922.js.map +1 -0
- package/dist/main.80b70197.css +2 -0
- package/dist/main.80b70197.css.map +1 -0
- package/dist/mf-manifest.json +241 -0
- package/dist/mf-stats.json +271 -0
- package/package.json +56 -39
- package/server.js +68 -0
- package/.env.example +0 -10
- package/UI-KIT-COMPONENTS.md +0 -783
- package/UI-KIT-PLAN.md +0 -326
- package/eslint-rules/no-direct-antd-import.js +0 -151
- package/eslint.config.ts +0 -27
- package/index.html +0 -17
- package/postcss.config.js +0 -7
- package/public/favicon.ico +0 -0
- package/rspack.config.mjs +0 -118
- package/src/App.tsx +0 -46
- package/src/components/devtools/devtools-log-feed.module.css +0 -165
- package/src/components/devtools/devtools-log-feed.tsx +0 -300
- package/src/components/devtools/devtools-panel.module.css +0 -121
- package/src/components/devtools/devtools-panel.tsx +0 -115
- package/src/components/empty-state.tsx +0 -28
- package/src/components/error-boundary.tsx +0 -210
- package/src/components/health-banner.module.css +0 -103
- package/src/components/health-banner.tsx +0 -134
- package/src/components/health-indicator.tsx +0 -12
- package/src/components/page-header.tsx +0 -49
- package/src/components/page-transition.tsx +0 -24
- package/src/components/role-switcher.tsx +0 -148
- package/src/components/run-badge.tsx +0 -13
- package/src/components/ui/icons.tsx +0 -509
- package/src/components/ui/index.ts +0 -25
- package/src/components/ui/kb-config-provider.tsx +0 -130
- package/src/components/ui/kb-header.module.css +0 -51
- package/src/components/ui/kb-header.tsx +0 -112
- package/src/components/ui/kb-notification-bell.module.css +0 -103
- package/src/components/ui/kb-notification-bell.tsx +0 -199
- package/src/components/ui/kb-page-layout.tsx +0 -66
- package/src/components/ui/kb-quick-search.module.css +0 -86
- package/src/components/ui/kb-quick-search.tsx +0 -196
- package/src/components/ui/kb-sidebar.module.css +0 -47
- package/src/components/ui/kb-sidebar.tsx +0 -196
- package/src/components/ui/kb-stat-card.module.css +0 -21
- package/src/components/ui/kb-stat-card.tsx +0 -22
- package/src/components/ui/kb-stat-grid.tsx +0 -22
- package/src/components/ui/kb-status-bar.module.css +0 -49
- package/src/components/ui/kb-status-bar.tsx +0 -145
- package/src/components/ui/kb-system-health-indicator.tsx +0 -229
- package/src/components/ui/kb-theme-toggle.tsx +0 -42
- package/src/components/ui/theme-adapter.ts +0 -812
- package/src/components/ui/theme-transition-overlay.tsx +0 -70
- package/src/components/ui/utils.ts +0 -6
- package/src/components/widget-error-boundary.tsx +0 -172
- package/src/config/env.ts +0 -5
- package/src/config/feature-flags.ts +0 -482
- package/src/config/studio.config.schema.ts +0 -88
- package/src/config/studio.config.ts +0 -84
- package/src/env.d.ts +0 -23
- package/src/hooks/use-command-palette.ts +0 -6
- package/src/hooks/use-devtools-store.ts +0 -25
- package/src/hooks/use-feature-flags.ts +0 -101
- package/src/index.css +0 -10
- package/src/layouts/dashboard-grid.tsx +0 -122
- package/src/layouts/index.ts +0 -12
- package/src/layouts/two-pane.tsx +0 -134
- package/src/main.tsx +0 -15
- package/src/modules/analytics/components/analytics-daily-stats-chart.tsx +0 -257
- package/src/modules/analytics/components/analytics-date-range-picker.tsx +0 -121
- package/src/modules/analytics/pages/analytics-cache-page.tsx +0 -285
- package/src/modules/analytics/pages/analytics-embeddings-page.tsx +0 -274
- package/src/modules/analytics/pages/analytics-events-page.tsx +0 -425
- package/src/modules/analytics/pages/analytics-llm-page.tsx +0 -434
- package/src/modules/analytics/pages/analytics-overview-page.tsx +0 -336
- package/src/modules/analytics/pages/analytics-page.tsx +0 -20
- package/src/modules/analytics/pages/analytics-storage-page.tsx +0 -344
- package/src/modules/analytics/pages/analytics-vectorstore-page.tsx +0 -279
- package/src/modules/analytics/routes/index.tsx +0 -133
- package/src/modules/analytics/utils/formatters.test.ts +0 -148
- package/src/modules/analytics/utils/formatters.ts +0 -100
- package/src/modules/auth/pages/login-page.module.css +0 -113
- package/src/modules/auth/pages/login-page.tsx +0 -80
- package/src/modules/auth/routes/index.tsx +0 -24
- package/src/modules/dashboard/components/ActivityTimelineWidget.tsx +0 -199
- package/src/modules/dashboard/components/AiInsightsWidget.tsx +0 -358
- package/src/modules/dashboard/components/AnimatedMetricCard.css +0 -88
- package/src/modules/dashboard/components/AnimatedMetricCard.tsx +0 -125
- package/src/modules/dashboard/components/HeroMetricCard.css +0 -114
- package/src/modules/dashboard/components/HeroMetricCard.tsx +0 -152
- package/src/modules/dashboard/components/HeroMetricsWidget.tsx +0 -146
- package/src/modules/dashboard/components/SystemResourcesWidget.tsx +0 -197
- package/src/modules/dashboard/components/dashboard-empty.tsx +0 -22
- package/src/modules/dashboard/components/dashboard-kpis-widget.tsx +0 -59
- package/src/modules/dashboard/components/dashboard-kpis.tsx +0 -13
- package/src/modules/dashboard/components/dashboard-latency-percentiles-widget.tsx +0 -85
- package/src/modules/dashboard/components/dashboard-metrics-widget.tsx +0 -78
- package/src/modules/dashboard/components/dashboard-plugins-widget.tsx +0 -67
- package/src/modules/dashboard/components/dashboard-requests-widget.tsx +0 -62
- package/src/modules/dashboard/layouts/dashboard-layout.tsx +0 -16
- package/src/modules/dashboard/pages/ai-insights-page.tsx +0 -563
- package/src/modules/dashboard/pages/dashboard-animations.css +0 -317
- package/src/modules/dashboard/pages/dashboard-page.tsx +0 -13
- package/src/modules/dashboard/routes/index.tsx +0 -70
- package/src/modules/observability/pages/devkit-page.tsx +0 -263
- package/src/modules/observability/pages/incident-detail-page.tsx +0 -571
- package/src/modules/observability/pages/incidents-page.tsx +0 -370
- package/src/modules/observability/pages/log-detail-page.tsx +0 -508
- package/src/modules/observability/pages/logs-page.tsx +0 -1129
- package/src/modules/observability/pages/prometheus-metrics-page.tsx +0 -486
- package/src/modules/observability/pages/state-broker-page.tsx +0 -229
- package/src/modules/observability/pages/system-events-page.tsx +0 -255
- package/src/modules/observability/routes/index.tsx +0 -133
- package/src/modules/plugins/components/marketplace-card.tsx +0 -153
- package/src/modules/plugins/components/plugin-ai-assistant-modal.tsx +0 -374
- package/src/modules/plugins/components/plugin-card.tsx +0 -136
- package/src/modules/plugins/components/plugin-detail/capabilities-summary.tsx +0 -101
- package/src/modules/plugins/components/plugin-detail/cli-commands-table.tsx +0 -101
- package/src/modules/plugins/components/plugin-detail/index.ts +0 -7
- package/src/modules/plugins/components/plugin-detail/jobs-table.tsx +0 -64
- package/src/modules/plugins/components/plugin-detail/permissions/env-permissions.tsx +0 -30
- package/src/modules/plugins/components/plugin-detail/permissions/filesystem-permissions.tsx +0 -68
- package/src/modules/plugins/components/plugin-detail/permissions/index.ts +0 -6
- package/src/modules/plugins/components/plugin-detail/permissions/network-permissions.tsx +0 -43
- package/src/modules/plugins/components/plugin-detail/permissions/platform-services.tsx +0 -242
- package/src/modules/plugins/components/plugin-detail/permissions/resource-quotas.tsx +0 -96
- package/src/modules/plugins/components/plugin-detail/permissions/shell-permissions.tsx +0 -55
- package/src/modules/plugins/components/plugin-detail/permissions-view.tsx +0 -47
- package/src/modules/plugins/components/plugin-detail/plugin-header.tsx +0 -169
- package/src/modules/plugins/components/plugin-detail/rest-routes-table.tsx +0 -159
- package/src/modules/plugins/components/plugin-detail/workflows-table.tsx +0 -40
- package/src/modules/plugins/components/plugin-grid.tsx +0 -20
- package/src/modules/plugins/components/plugin-table.tsx +0 -178
- package/src/modules/plugins/pages/marketplace-page.tsx +0 -200
- package/src/modules/plugins/pages/plugin-detail-page.tsx +0 -1145
- package/src/modules/plugins/routes/index.tsx +0 -48
- package/src/modules/plugins/utils/index.ts +0 -1
- package/src/modules/plugins/utils/schema-helpers.ts +0 -21
- package/src/modules/settings/components/api-routes-viewer.tsx +0 -415
- package/src/modules/settings/components/appearance-settings.tsx +0 -200
- package/src/modules/settings/components/configuration-settings.tsx +0 -321
- package/src/modules/settings/components/data-privacy-settings.tsx +0 -111
- package/src/modules/settings/components/experimental-settings.tsx +0 -243
- package/src/modules/settings/components/navigation-settings.tsx +0 -198
- package/src/modules/settings/components/notifications-settings.tsx +0 -40
- package/src/modules/settings/pages/settings-page.tsx +0 -298
- package/src/modules/settings/routes/index.tsx +0 -47
- package/src/pages/README-assistant.md +0 -91
- package/src/pages/login-page.tsx +0 -177
- package/src/pages/not-found-page.tsx +0 -85
- package/src/providers/auth-provider.tsx +0 -40
- package/src/providers/command-palette-provider.tsx +0 -193
- package/src/providers/data-sources-provider.tsx +0 -27
- package/src/providers/devtools-provider.tsx +0 -72
- package/src/providers/navigation-items-provider.tsx +0 -38
- package/src/providers/registry-v2-provider.tsx +0 -93
- package/src/providers/settings-provider.tsx +0 -158
- package/src/router.tsx +0 -302
- package/src/routes/helpers.tsx +0 -25
- package/src/routes/plugin-page-v2.tsx +0 -52
- package/src/routes/types.ts +0 -18
- package/src/styles/antd-overrides.css +0 -211
- package/src/styles/base.css +0 -190
- package/src/styles/settings.css +0 -75
- package/src/styles/variables.css +0 -94
- package/src/utils/analytics.ts +0 -183
- package/src/utils/apply-navigation-categories.ts +0 -83
- package/src/utils/commands.ts +0 -252
- package/src/utils/export.ts +0 -174
- package/src/utils/format-date.ts +0 -166
- package/src/utils/logger.ts +0 -110
- package/tailwind.config.ts +0 -13
- package/tsconfig.json +0 -13
- package/vitest-setup.ts +0 -1
- /package/{public → dist}/favicon.svg +0 -0
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { UICard } from '@kb-labs/studio-ui-kit';
|
|
3
|
-
import type { UICardVariant as UICardStatus } from '@kb-labs/studio-ui-kit';
|
|
4
|
-
import './HeroMetricCard.css';
|
|
5
|
-
|
|
6
|
-
export type MetricStatus = 'healthy' | 'warning' | 'critical' | 'live' | 'default';
|
|
7
|
-
|
|
8
|
-
export interface HeroMetricCardProps {
|
|
9
|
-
title: string;
|
|
10
|
-
value: string | number;
|
|
11
|
-
subtitle?: string;
|
|
12
|
-
sparklineData?: number[];
|
|
13
|
-
trend?: {
|
|
14
|
-
direction: 'up' | 'down';
|
|
15
|
-
value: number;
|
|
16
|
-
isPositive?: boolean;
|
|
17
|
-
};
|
|
18
|
-
status?: MetricStatus;
|
|
19
|
-
pulsing?: boolean;
|
|
20
|
-
onClick?: () => void;
|
|
21
|
-
icon?: React.ReactNode;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const statusColors: Record<MetricStatus, string> = {
|
|
25
|
-
healthy: 'var(--success)',
|
|
26
|
-
warning: 'var(--warning)',
|
|
27
|
-
critical: 'var(--error)',
|
|
28
|
-
live: 'var(--info)',
|
|
29
|
-
default: 'var(--text-secondary)',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const statusToVariant: Record<MetricStatus, UICardStatus> = {
|
|
33
|
-
healthy: 'success',
|
|
34
|
-
warning: 'warning',
|
|
35
|
-
critical: 'error',
|
|
36
|
-
live: 'info',
|
|
37
|
-
default: 'default',
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export function HeroMetricCard({
|
|
41
|
-
title,
|
|
42
|
-
value,
|
|
43
|
-
subtitle,
|
|
44
|
-
sparklineData,
|
|
45
|
-
trend,
|
|
46
|
-
status = 'default',
|
|
47
|
-
pulsing = false,
|
|
48
|
-
onClick,
|
|
49
|
-
icon,
|
|
50
|
-
}: HeroMetricCardProps) {
|
|
51
|
-
const mainColor = statusColors[status];
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<UICard
|
|
55
|
-
className={`hero-metric-card ${onClick ? 'clickable' : ''} ${pulsing ? 'pulsing' : ''}`}
|
|
56
|
-
status={statusToVariant[status]}
|
|
57
|
-
onClick={onClick}
|
|
58
|
-
styles={{ body: { padding: '20px' } }}
|
|
59
|
-
style={{ cursor: onClick ? 'pointer' : 'default' }}
|
|
60
|
-
>
|
|
61
|
-
<div className="hero-metric-header">
|
|
62
|
-
{icon && <div className="hero-metric-icon">{icon}</div>}
|
|
63
|
-
<div className="hero-metric-title">{title}</div>
|
|
64
|
-
{pulsing && (
|
|
65
|
-
<div className="hero-metric-pulse" style={{ backgroundColor: mainColor }} />
|
|
66
|
-
)}
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<div className="hero-metric-value" style={{ color: mainColor }}>
|
|
70
|
-
{value}
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
{/* Always render subtitle for consistent height */}
|
|
74
|
-
<div className="hero-metric-subtitle">
|
|
75
|
-
{subtitle || '\u00A0'}
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
{/* Always show footer for consistent card height */}
|
|
79
|
-
<div className="hero-metric-footer">
|
|
80
|
-
<div className="hero-metric-sparkline">
|
|
81
|
-
{sparklineData && sparklineData.length > 1 && (
|
|
82
|
-
<Sparkline data={sparklineData} color={mainColor} />
|
|
83
|
-
)}
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<div className="hero-metric-trend">
|
|
87
|
-
{trend && (
|
|
88
|
-
<TrendIndicator
|
|
89
|
-
direction={trend.direction}
|
|
90
|
-
value={trend.value}
|
|
91
|
-
isPositive={trend.isPositive ?? trend.direction === 'up'}
|
|
92
|
-
/>
|
|
93
|
-
)}
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
</UICard>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Sparkline Component
|
|
101
|
-
interface SparklineProps {
|
|
102
|
-
data: number[];
|
|
103
|
-
color: string;
|
|
104
|
-
width?: number;
|
|
105
|
-
height?: number;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function Sparkline({ data, color, width = 100, height = 30 }: SparklineProps) {
|
|
109
|
-
if (data.length < 2) {return null;}
|
|
110
|
-
|
|
111
|
-
const min = Math.min(...data);
|
|
112
|
-
const max = Math.max(...data);
|
|
113
|
-
const range = max - min || 1;
|
|
114
|
-
|
|
115
|
-
const points = data.map((value, index) => {
|
|
116
|
-
const x = (index / (data.length - 1)) * width;
|
|
117
|
-
const y = height - ((value - min) / range) * height;
|
|
118
|
-
return `${x},${y}`;
|
|
119
|
-
}).join(' ');
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<svg width={width} height={height} className="sparkline">
|
|
123
|
-
<polyline
|
|
124
|
-
points={points}
|
|
125
|
-
fill="none"
|
|
126
|
-
stroke={color}
|
|
127
|
-
strokeWidth="2"
|
|
128
|
-
strokeLinecap="round"
|
|
129
|
-
strokeLinejoin="round"
|
|
130
|
-
/>
|
|
131
|
-
</svg>
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// TrendIndicator Component
|
|
136
|
-
interface TrendIndicatorProps {
|
|
137
|
-
direction: 'up' | 'down';
|
|
138
|
-
value: number;
|
|
139
|
-
isPositive: boolean;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function TrendIndicator({ direction, value, isPositive }: TrendIndicatorProps) {
|
|
143
|
-
const color = isPositive ? 'var(--success)' : 'var(--error)';
|
|
144
|
-
const arrow = direction === 'up' ? '↑' : '↓';
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<div className="trend-indicator" style={{ color }}>
|
|
148
|
-
<span className="trend-arrow">{arrow}</span>
|
|
149
|
-
<span className="trend-value">{Math.abs(value).toFixed(1)}%</span>
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { UIRow, UICol } from '@kb-labs/studio-ui-kit';
|
|
3
|
-
import { useNavigate } from 'react-router-dom';
|
|
4
|
-
import {
|
|
5
|
-
RiseOutlined,
|
|
6
|
-
ApiOutlined,
|
|
7
|
-
ClockCircleOutlined,
|
|
8
|
-
} from '@ant-design/icons';
|
|
9
|
-
import { HeroMetricCard } from './HeroMetricCard';
|
|
10
|
-
import { useDataSources } from '../../../providers/data-sources-provider';
|
|
11
|
-
import {
|
|
12
|
-
usePrometheusMetrics,
|
|
13
|
-
useStateBrokerStats,
|
|
14
|
-
} from '@kb-labs/studio-data-client';
|
|
15
|
-
|
|
16
|
-
interface MetricsHistoryPoint {
|
|
17
|
-
timestamp: number;
|
|
18
|
-
requests: number;
|
|
19
|
-
uptime: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function HeroMetricsWidget() {
|
|
23
|
-
const sources = useDataSources();
|
|
24
|
-
const _navigate = useNavigate();
|
|
25
|
-
|
|
26
|
-
// Data hooks with auto-refresh
|
|
27
|
-
const metrics = usePrometheusMetrics(sources.observability);
|
|
28
|
-
const stateBroker = useStateBrokerStats(sources.observability);
|
|
29
|
-
|
|
30
|
-
// Historical data for sparklines (last 20 data points)
|
|
31
|
-
const [metricsHistory, setMetricsHistory] = useState<MetricsHistoryPoint[]>([]);
|
|
32
|
-
|
|
33
|
-
// Collect metrics history
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (metrics.data) {
|
|
36
|
-
const newPoint: MetricsHistoryPoint = {
|
|
37
|
-
timestamp: Date.now(),
|
|
38
|
-
requests: metrics.data.requests?.total ?? 0,
|
|
39
|
-
uptime: calculateUptime(metrics.data),
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
setMetricsHistory(prev => {
|
|
43
|
-
// Keep last 20 points
|
|
44
|
-
return [...prev, newPoint].slice(-20);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}, [metrics.data]);
|
|
48
|
-
|
|
49
|
-
// Calculate uptime percentage
|
|
50
|
-
const calculateUptime = (metricsData: any) => {
|
|
51
|
-
if (!metricsData?.requests) {return 100;}
|
|
52
|
-
const total = metricsData.requests.total ?? 0;
|
|
53
|
-
const success = metricsData.requests.success ?? 0;
|
|
54
|
-
return total > 0 ? (success / total) * 100 : 100;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Calculate trend from history
|
|
58
|
-
const calculateTrend = (data: number[]) => {
|
|
59
|
-
if (data.length < 2) {return null;}
|
|
60
|
-
const recent = data.slice(-5); // Last 5 points
|
|
61
|
-
const older = data.slice(-10, -5); // Previous 5 points
|
|
62
|
-
if (older.length === 0 || recent.length === 0) {return null;}
|
|
63
|
-
|
|
64
|
-
const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;
|
|
65
|
-
const olderAvg = older.reduce((a, b) => a + b, 0) / older.length;
|
|
66
|
-
|
|
67
|
-
if (olderAvg === 0) {return null;}
|
|
68
|
-
|
|
69
|
-
const change = ((recentAvg - olderAvg) / olderAvg) * 100;
|
|
70
|
-
return {
|
|
71
|
-
direction: change >= 0 ? 'up' as const : 'down' as const,
|
|
72
|
-
value: Math.abs(change),
|
|
73
|
-
isPositive: change >= 0,
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
// Format uptime seconds to human-readable
|
|
78
|
-
const formatUptime = (seconds: number | undefined) => {
|
|
79
|
-
if (!seconds) {return '0s';}
|
|
80
|
-
|
|
81
|
-
const days = Math.floor(seconds / 86400);
|
|
82
|
-
const hours = Math.floor((seconds % 86400) / 3600);
|
|
83
|
-
const minutes = Math.floor((seconds % 3600) / 60);
|
|
84
|
-
|
|
85
|
-
if (days > 0) {return `${days}d ${hours}h`;}
|
|
86
|
-
if (hours > 0) {return `${hours}h ${minutes}m`;}
|
|
87
|
-
return `${minutes}m`;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// Extract sparkline data
|
|
91
|
-
const requestsSparkline = metricsHistory.map(p => p.requests);
|
|
92
|
-
const uptimeSparkline = metricsHistory.map(p => p.uptime);
|
|
93
|
-
|
|
94
|
-
// Calculate trends
|
|
95
|
-
const requestsTrend = calculateTrend(requestsSparkline);
|
|
96
|
-
|
|
97
|
-
// Metric values
|
|
98
|
-
const currentUptime = calculateUptime(metrics.data);
|
|
99
|
-
const totalRequests = metrics.data?.requests?.total ?? 0;
|
|
100
|
-
const runtimeSeconds = metrics.data?.uptime?.seconds ?? 0;
|
|
101
|
-
|
|
102
|
-
// Determine uptime status
|
|
103
|
-
const getUptimeStatus = () => {
|
|
104
|
-
if (currentUptime >= 99) {return 'healthy';}
|
|
105
|
-
if (currentUptime >= 95) {return 'warning';}
|
|
106
|
-
return 'critical';
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<UIRow gutter={[16, 16]}>
|
|
111
|
-
<UICol xs={24} sm={12} lg={8}>
|
|
112
|
-
<HeroMetricCard
|
|
113
|
-
title="Uptime"
|
|
114
|
-
value={`${currentUptime.toFixed(2)}%`}
|
|
115
|
-
subtitle={stateBroker.data ? `Cache: ${(stateBroker.data.hitRate * 100).toFixed(1)}%` : 'Success rate'}
|
|
116
|
-
sparklineData={uptimeSparkline}
|
|
117
|
-
status={getUptimeStatus()}
|
|
118
|
-
icon={<RiseOutlined />}
|
|
119
|
-
/>
|
|
120
|
-
</UICol>
|
|
121
|
-
|
|
122
|
-
<UICol xs={24} sm={12} lg={8}>
|
|
123
|
-
<HeroMetricCard
|
|
124
|
-
title="Total Requests"
|
|
125
|
-
value={totalRequests.toLocaleString()}
|
|
126
|
-
subtitle={metrics.data ? `Success: ${metrics.data.requests.success.toLocaleString()}` : 'Loading...'}
|
|
127
|
-
sparklineData={requestsSparkline}
|
|
128
|
-
trend={requestsTrend ?? undefined}
|
|
129
|
-
status="default"
|
|
130
|
-
icon={<ApiOutlined />}
|
|
131
|
-
/>
|
|
132
|
-
</UICol>
|
|
133
|
-
|
|
134
|
-
<UICol xs={24} sm={12} lg={8}>
|
|
135
|
-
<HeroMetricCard
|
|
136
|
-
title="Runtime"
|
|
137
|
-
value={formatUptime(runtimeSeconds)}
|
|
138
|
-
subtitle="LIVE"
|
|
139
|
-
status="live"
|
|
140
|
-
pulsing={true}
|
|
141
|
-
icon={<ClockCircleOutlined />}
|
|
142
|
-
/>
|
|
143
|
-
</UICol>
|
|
144
|
-
</UIRow>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
UIProgress,
|
|
4
|
-
UIStatistic,
|
|
5
|
-
UIRow,
|
|
6
|
-
UICol,
|
|
7
|
-
UITag,
|
|
8
|
-
UIEmptyState,
|
|
9
|
-
UICard,
|
|
10
|
-
} from '@kb-labs/studio-ui-kit';
|
|
11
|
-
import {
|
|
12
|
-
CloudServerOutlined,
|
|
13
|
-
ThunderboltOutlined,
|
|
14
|
-
DatabaseOutlined,
|
|
15
|
-
ClockCircleOutlined,
|
|
16
|
-
} from '@ant-design/icons';
|
|
17
|
-
import { useHealthStatus } from '@kb-labs/studio-data-client';
|
|
18
|
-
import { useDataSources } from '../../../providers/data-sources-provider';
|
|
19
|
-
|
|
20
|
-
function formatBytes(bytes: number): string {
|
|
21
|
-
if (bytes === 0) {return '0 B';}
|
|
22
|
-
const k = 1024;
|
|
23
|
-
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
24
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
25
|
-
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function formatUptime(seconds: number): string {
|
|
29
|
-
const days = Math.floor(seconds / 86400);
|
|
30
|
-
const hours = Math.floor((seconds % 86400) / 3600);
|
|
31
|
-
const minutes = Math.floor((seconds % 3600) / 60);
|
|
32
|
-
|
|
33
|
-
if (days > 0) {return `${days}d ${hours}h ${minutes}m`;}
|
|
34
|
-
if (hours > 0) {return `${hours}h ${minutes}m`;}
|
|
35
|
-
return `${minutes}m`;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getMetricColor(value: number, warningThreshold: number, criticalThreshold: number): string {
|
|
39
|
-
if (value >= criticalThreshold) {return 'var(--error)';}
|
|
40
|
-
if (value >= warningThreshold) {return 'var(--warning)';}
|
|
41
|
-
return 'var(--success)';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function SystemResourcesWidget() {
|
|
45
|
-
const sources = useDataSources();
|
|
46
|
-
const healthQuery = useHealthStatus(sources.system);
|
|
47
|
-
const snapshot = healthQuery.data?.snapshot;
|
|
48
|
-
const resources = snapshot?.snapshot;
|
|
49
|
-
const topOperations = snapshot?.topOperations ?? [];
|
|
50
|
-
const state = snapshot?.state ?? 'insufficient_data';
|
|
51
|
-
const cpuPercent = resources?.cpuPercent ?? 0;
|
|
52
|
-
const rssBytes = resources?.rssBytes ?? 0;
|
|
53
|
-
const heapUsedBytes = resources?.heapUsedBytes ?? 0;
|
|
54
|
-
const eventLoopLagMs = resources?.eventLoopLagMs ?? 0;
|
|
55
|
-
const activeOperations = resources?.activeOperations ?? 0;
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<UICard
|
|
59
|
-
title={
|
|
60
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
61
|
-
<CloudServerOutlined />
|
|
62
|
-
<span>Runtime Resources</span>
|
|
63
|
-
{snapshot && (
|
|
64
|
-
<UITag color={state === 'active' ? 'green' : state === 'partial_observability' ? 'orange' : 'red'}>
|
|
65
|
-
{state}
|
|
66
|
-
</UITag>
|
|
67
|
-
)}
|
|
68
|
-
</div>
|
|
69
|
-
}
|
|
70
|
-
style={{ height: '100%' }}
|
|
71
|
-
>
|
|
72
|
-
{healthQuery.isLoading ? (
|
|
73
|
-
<div style={{ height: 220, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-tertiary)' }}>
|
|
74
|
-
Loading runtime resources...
|
|
75
|
-
</div>
|
|
76
|
-
) : !snapshot || !resources ? (
|
|
77
|
-
<UIEmptyState
|
|
78
|
-
image={UIEmptyState.PRESENTED_IMAGE_SIMPLE}
|
|
79
|
-
description="No runtime snapshot available"
|
|
80
|
-
style={{ marginTop: 40 }}
|
|
81
|
-
/>
|
|
82
|
-
) : (
|
|
83
|
-
<>
|
|
84
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16 }}>
|
|
85
|
-
<CloudServerOutlined style={{ fontSize: 18, color: 'var(--info)' }} />
|
|
86
|
-
<span style={{ fontSize: 14, fontWeight: 500 }}>{snapshot.instanceId}</span>
|
|
87
|
-
<UITag color={snapshot.status === 'healthy' ? 'green' : snapshot.status === 'degraded' ? 'orange' : 'red'}>
|
|
88
|
-
{snapshot.status}
|
|
89
|
-
</UITag>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<UIRow gutter={[16, 16]}>
|
|
93
|
-
<UICol span={12}>
|
|
94
|
-
<div>
|
|
95
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 }}>
|
|
96
|
-
<ThunderboltOutlined style={{ color: getMetricColor(cpuPercent, 70, 90) }} />
|
|
97
|
-
<span style={{ fontSize: 12, color: 'var(--text-secondary)' }}>CPU Usage</span>
|
|
98
|
-
</div>
|
|
99
|
-
<UIProgress
|
|
100
|
-
percent={parseFloat(cpuPercent.toFixed(1))}
|
|
101
|
-
strokeColor={getMetricColor(cpuPercent, 70, 90)}
|
|
102
|
-
size="small"
|
|
103
|
-
format={(percent) => `${percent}%`}
|
|
104
|
-
/>
|
|
105
|
-
</div>
|
|
106
|
-
</UICol>
|
|
107
|
-
|
|
108
|
-
<UICol span={12}>
|
|
109
|
-
<UIStatistic
|
|
110
|
-
title={
|
|
111
|
-
<span style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
|
|
112
|
-
<DatabaseOutlined style={{ marginRight: 4 }} />
|
|
113
|
-
RSS
|
|
114
|
-
</span>
|
|
115
|
-
}
|
|
116
|
-
value={formatBytes(rssBytes)}
|
|
117
|
-
valueStyle={{ fontSize: 14 }}
|
|
118
|
-
/>
|
|
119
|
-
</UICol>
|
|
120
|
-
|
|
121
|
-
<UICol span={12}>
|
|
122
|
-
<UIStatistic
|
|
123
|
-
title={
|
|
124
|
-
<span style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
|
|
125
|
-
<DatabaseOutlined style={{ marginRight: 4 }} />
|
|
126
|
-
Heap Used
|
|
127
|
-
</span>
|
|
128
|
-
}
|
|
129
|
-
value={formatBytes(heapUsedBytes)}
|
|
130
|
-
valueStyle={{ fontSize: 14 }}
|
|
131
|
-
/>
|
|
132
|
-
</UICol>
|
|
133
|
-
|
|
134
|
-
<UICol span={12}>
|
|
135
|
-
<UIStatistic
|
|
136
|
-
title={
|
|
137
|
-
<span style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
|
|
138
|
-
<ClockCircleOutlined style={{ marginRight: 4 }} />
|
|
139
|
-
Uptime
|
|
140
|
-
</span>
|
|
141
|
-
}
|
|
142
|
-
value={formatUptime(snapshot.uptimeSec)}
|
|
143
|
-
valueStyle={{ fontSize: 14 }}
|
|
144
|
-
/>
|
|
145
|
-
</UICol>
|
|
146
|
-
|
|
147
|
-
<UICol span={12}>
|
|
148
|
-
<UIStatistic
|
|
149
|
-
title="Event Loop Lag"
|
|
150
|
-
value={eventLoopLagMs.toFixed(1)}
|
|
151
|
-
suffix="ms"
|
|
152
|
-
valueStyle={{
|
|
153
|
-
fontSize: 20,
|
|
154
|
-
color: getMetricColor(eventLoopLagMs, 100, 200),
|
|
155
|
-
}}
|
|
156
|
-
/>
|
|
157
|
-
</UICol>
|
|
158
|
-
|
|
159
|
-
<UICol span={12}>
|
|
160
|
-
<UIStatistic
|
|
161
|
-
title="Active Operations"
|
|
162
|
-
value={activeOperations}
|
|
163
|
-
valueStyle={{ fontSize: 20 }}
|
|
164
|
-
/>
|
|
165
|
-
</UICol>
|
|
166
|
-
</UIRow>
|
|
167
|
-
|
|
168
|
-
<div style={{ marginTop: 24 }}>
|
|
169
|
-
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginBottom: 8 }}>Top Operations</div>
|
|
170
|
-
{topOperations.length > 0 ? (
|
|
171
|
-
topOperations.map((operation) => (
|
|
172
|
-
<div
|
|
173
|
-
key={operation.operation}
|
|
174
|
-
style={{
|
|
175
|
-
display: 'flex',
|
|
176
|
-
justifyContent: 'space-between',
|
|
177
|
-
alignItems: 'center',
|
|
178
|
-
padding: '8px 0',
|
|
179
|
-
borderTop: '1px solid var(--border-secondary)',
|
|
180
|
-
fontSize: 12,
|
|
181
|
-
}}
|
|
182
|
-
>
|
|
183
|
-
<span style={{ color: 'var(--text-primary)' }}>{operation.operation.replace(/^http\./, '')}</span>
|
|
184
|
-
<span style={{ color: 'var(--text-secondary)' }}>
|
|
185
|
-
{operation.count ?? 0} req · {(operation.avgDurationMs ?? 0).toFixed(1)}ms avg
|
|
186
|
-
</span>
|
|
187
|
-
</div>
|
|
188
|
-
))
|
|
189
|
-
) : (
|
|
190
|
-
<div style={{ color: 'var(--text-tertiary)', fontSize: 12 }}>No operation samples available</div>
|
|
191
|
-
)}
|
|
192
|
-
</div>
|
|
193
|
-
</>
|
|
194
|
-
)}
|
|
195
|
-
</UICard>
|
|
196
|
-
);
|
|
197
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { UIEmptyState, UIButton, UIFlex } from '@kb-labs/studio-ui-kit';
|
|
2
|
-
import { PlusOutlined } from '@ant-design/icons';
|
|
3
|
-
|
|
4
|
-
interface DashboardEmptyProps {
|
|
5
|
-
onAddWidget: () => void;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function DashboardEmpty({ onAddWidget }: DashboardEmptyProps) {
|
|
9
|
-
return (
|
|
10
|
-
<UIFlex align="center" justify="center" style={{ minHeight: '400px' }}>
|
|
11
|
-
<UIEmptyState
|
|
12
|
-
useDefaultImage
|
|
13
|
-
description="No widgets added yet"
|
|
14
|
-
action={
|
|
15
|
-
<UIButton variant="primary" icon={<PlusOutlined />} onClick={onAddWidget}>
|
|
16
|
-
Add Widget
|
|
17
|
-
</UIButton>
|
|
18
|
-
}
|
|
19
|
-
/>
|
|
20
|
-
</UIFlex>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { UICard, UIBadge, UIRow, UICol } from '@kb-labs/studio-ui-kit';
|
|
2
|
-
import { HolderOutlined } from '@ant-design/icons';
|
|
3
|
-
import { useRegistryV2 } from '../../../providers/registry-v2-provider';
|
|
4
|
-
import { KBStatCard } from '@/components/ui';
|
|
5
|
-
|
|
6
|
-
export function DashboardKpisWidget() {
|
|
7
|
-
const { registry } = useRegistryV2();
|
|
8
|
-
const health = null as { pluginsMounted: number; pluginsFailed: number; ready: boolean; redisHealthy: boolean; redisEnabled: boolean } | null; // TODO: health status via separate endpoint
|
|
9
|
-
|
|
10
|
-
const totalPlugins = registry.plugins?.length ?? 0;
|
|
11
|
-
const pluginsMounted = health?.pluginsMounted ?? 0;
|
|
12
|
-
const pluginsFailed = health?.pluginsFailed ?? 0;
|
|
13
|
-
const systemStatus = health?.ready ? 'Healthy' : 'Degraded';
|
|
14
|
-
const statusType = health?.ready ? 'success' : 'error';
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<UICard
|
|
18
|
-
title={
|
|
19
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
20
|
-
<HolderOutlined className="drag-handle" style={{ cursor: 'grab', color: 'var(--text-tertiary)' }} />
|
|
21
|
-
<span>System Status</span>
|
|
22
|
-
<UIBadge variant={statusType as 'success' | 'error'} />
|
|
23
|
-
</div>
|
|
24
|
-
}
|
|
25
|
-
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
|
|
26
|
-
styles={{ body: { flex: 1, padding: '16px' } }}
|
|
27
|
-
>
|
|
28
|
-
<UIRow gutter={[16, 16]} style={{ height: '100%' }}>
|
|
29
|
-
<UICol span={6}>
|
|
30
|
-
<KBStatCard
|
|
31
|
-
label="Status"
|
|
32
|
-
value={systemStatus}
|
|
33
|
-
variant={health?.ready ? 'positive' : 'negative'}
|
|
34
|
-
/>
|
|
35
|
-
</UICol>
|
|
36
|
-
<UICol span={6}>
|
|
37
|
-
<KBStatCard
|
|
38
|
-
label="Plugins Mounted"
|
|
39
|
-
value={`${pluginsMounted} of ${totalPlugins}`}
|
|
40
|
-
/>
|
|
41
|
-
</UICol>
|
|
42
|
-
<UICol span={6}>
|
|
43
|
-
<KBStatCard
|
|
44
|
-
label="Failed"
|
|
45
|
-
value={pluginsFailed.toString()}
|
|
46
|
-
variant={pluginsFailed > 0 ? 'negative' : 'positive'}
|
|
47
|
-
/>
|
|
48
|
-
</UICol>
|
|
49
|
-
<UICol span={6}>
|
|
50
|
-
<KBStatCard
|
|
51
|
-
label="Redis"
|
|
52
|
-
value={health?.redisHealthy ? 'Healthy' : health?.redisEnabled ? 'Unhealthy' : 'Disabled'}
|
|
53
|
-
variant={health?.redisHealthy ? 'positive' : health?.redisEnabled ? 'negative' : 'default'}
|
|
54
|
-
/>
|
|
55
|
-
</UICol>
|
|
56
|
-
</UIRow>
|
|
57
|
-
</UICard>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { KBStatCard, KBStatGrid } from '@/components/ui';
|
|
2
|
-
|
|
3
|
-
export function DashboardKpis() {
|
|
4
|
-
return (
|
|
5
|
-
<KBStatGrid columns={4}>
|
|
6
|
-
<KBStatCard label="Audit Status" value="Unavailable" />
|
|
7
|
-
<KBStatCard label="Packages OK" value="-" />
|
|
8
|
-
<KBStatCard label="Failed" value="-" />
|
|
9
|
-
<KBStatCard label="Warnings" value="-" />
|
|
10
|
-
</KBStatGrid>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { UICard } from '@kb-labs/studio-ui-kit';
|
|
2
|
-
import { HolderOutlined } from '@ant-design/icons';
|
|
3
|
-
import { Column } from '@ant-design/plots';
|
|
4
|
-
import { usePrometheusMetrics } from '@kb-labs/studio-data-client';
|
|
5
|
-
import { useDataSources } from '../../../providers/data-sources-provider';
|
|
6
|
-
|
|
7
|
-
export function DashboardLatencyPercentilesWidget() {
|
|
8
|
-
const sources = useDataSources();
|
|
9
|
-
const { data: metrics } = usePrometheusMetrics(sources.observability);
|
|
10
|
-
|
|
11
|
-
const min = metrics?.latency?.min ?? 0;
|
|
12
|
-
const average = metrics?.latency?.average ?? 0;
|
|
13
|
-
const p50 = metrics?.latency?.p50 ?? 0;
|
|
14
|
-
const p95 = metrics?.latency?.p95 ?? 0;
|
|
15
|
-
const p99 = metrics?.latency?.p99 ?? 0;
|
|
16
|
-
const max = metrics?.latency?.max ?? 0;
|
|
17
|
-
|
|
18
|
-
const chartData = [
|
|
19
|
-
{ metric: 'Min', value: min, color: 'var(--success)' },
|
|
20
|
-
{ metric: 'Avg', value: average, color: 'var(--info)' },
|
|
21
|
-
{ metric: 'p50', value: p50, color: 'var(--info)' },
|
|
22
|
-
{ metric: 'p95', value: p95, color: 'var(--warning)' },
|
|
23
|
-
{ metric: 'p99', value: p99, color: 'var(--warning)' },
|
|
24
|
-
{ metric: 'Max', value: max, color: 'var(--error)' },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const config = {
|
|
28
|
-
data: chartData,
|
|
29
|
-
xField: 'metric',
|
|
30
|
-
yField: 'value',
|
|
31
|
-
seriesField: 'metric',
|
|
32
|
-
columnStyle: (datum: any) => {
|
|
33
|
-
return {
|
|
34
|
-
fill: datum.color,
|
|
35
|
-
};
|
|
36
|
-
},
|
|
37
|
-
label: {
|
|
38
|
-
position: 'top' as const,
|
|
39
|
-
formatter: (datum: any) => `${datum.value.toFixed(1)}ms`,
|
|
40
|
-
style: {
|
|
41
|
-
fontSize: 12,
|
|
42
|
-
fill: 'var(--text-secondary)',
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
xAxis: {
|
|
46
|
-
label: {
|
|
47
|
-
style: {
|
|
48
|
-
fontSize: 12,
|
|
49
|
-
fill: 'var(--text-secondary)',
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
yAxis: {
|
|
54
|
-
label: {
|
|
55
|
-
formatter: (v: string) => `${v}ms`,
|
|
56
|
-
style: {
|
|
57
|
-
fontSize: 12,
|
|
58
|
-
fill: 'var(--text-secondary)',
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
legend: false,
|
|
63
|
-
height: 280,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<UICard
|
|
68
|
-
title={
|
|
69
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
70
|
-
<HolderOutlined className="drag-handle" style={{ cursor: 'grab', color: 'var(--text-tertiary)' }} />
|
|
71
|
-
<span>Latency Distribution (ms)</span>
|
|
72
|
-
</div>
|
|
73
|
-
}
|
|
74
|
-
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
|
|
75
|
-
styles={{ body: { flex: 1, padding: '16px', display: 'flex', flexDirection: 'column' } }}
|
|
76
|
-
>
|
|
77
|
-
{metrics && <Column {...config} />}
|
|
78
|
-
{!metrics && (
|
|
79
|
-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }}>
|
|
80
|
-
Loading...
|
|
81
|
-
</div>
|
|
82
|
-
)}
|
|
83
|
-
</UICard>
|
|
84
|
-
);
|
|
85
|
-
}
|