@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.
- package/.env.example +10 -0
- package/README.md +74 -0
- package/UI-KIT-COMPONENTS.md +783 -0
- package/UI-KIT-PLAN.md +326 -0
- package/eslint-rules/no-direct-antd-import.js +151 -0
- package/eslint.config.ts +27 -0
- package/index.html +17 -0
- package/package.json +72 -0
- package/postcss.config.js +7 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +7 -0
- package/rspack.config.mjs +118 -0
- package/src/App.tsx +46 -0
- package/src/components/devtools/devtools-log-feed.module.css +165 -0
- package/src/components/devtools/devtools-log-feed.tsx +300 -0
- package/src/components/devtools/devtools-panel.module.css +121 -0
- package/src/components/devtools/devtools-panel.tsx +115 -0
- package/src/components/empty-state.tsx +28 -0
- package/src/components/error-boundary.tsx +210 -0
- package/src/components/health-banner.module.css +103 -0
- package/src/components/health-banner.tsx +134 -0
- package/src/components/health-indicator.tsx +12 -0
- package/src/components/page-header.tsx +49 -0
- package/src/components/page-transition.tsx +24 -0
- package/src/components/role-switcher.tsx +148 -0
- package/src/components/run-badge.tsx +13 -0
- package/src/components/ui/icons.tsx +509 -0
- package/src/components/ui/index.ts +25 -0
- package/src/components/ui/kb-config-provider.tsx +130 -0
- package/src/components/ui/kb-header.module.css +51 -0
- package/src/components/ui/kb-header.tsx +112 -0
- package/src/components/ui/kb-notification-bell.module.css +103 -0
- package/src/components/ui/kb-notification-bell.tsx +199 -0
- package/src/components/ui/kb-page-layout.tsx +66 -0
- package/src/components/ui/kb-quick-search.module.css +86 -0
- package/src/components/ui/kb-quick-search.tsx +196 -0
- package/src/components/ui/kb-sidebar.module.css +47 -0
- package/src/components/ui/kb-sidebar.tsx +196 -0
- package/src/components/ui/kb-stat-card.module.css +21 -0
- package/src/components/ui/kb-stat-card.tsx +22 -0
- package/src/components/ui/kb-stat-grid.tsx +22 -0
- package/src/components/ui/kb-status-bar.module.css +49 -0
- package/src/components/ui/kb-status-bar.tsx +145 -0
- package/src/components/ui/kb-system-health-indicator.tsx +229 -0
- package/src/components/ui/kb-theme-toggle.tsx +42 -0
- package/src/components/ui/theme-adapter.ts +812 -0
- package/src/components/ui/theme-transition-overlay.tsx +70 -0
- package/src/components/ui/utils.ts +6 -0
- package/src/components/widget-error-boundary.tsx +172 -0
- package/src/config/env.ts +5 -0
- package/src/config/feature-flags.ts +482 -0
- package/src/config/studio.config.schema.ts +88 -0
- package/src/config/studio.config.ts +84 -0
- package/src/env.d.ts +23 -0
- package/src/hooks/use-command-palette.ts +6 -0
- package/src/hooks/use-devtools-store.ts +25 -0
- package/src/hooks/use-feature-flags.ts +101 -0
- package/src/index.css +10 -0
- package/src/layouts/dashboard-grid.tsx +122 -0
- package/src/layouts/index.ts +12 -0
- package/src/layouts/two-pane.tsx +134 -0
- package/src/main.tsx +15 -0
- package/src/modules/analytics/components/analytics-daily-stats-chart.tsx +257 -0
- package/src/modules/analytics/components/analytics-date-range-picker.tsx +121 -0
- package/src/modules/analytics/pages/analytics-cache-page.tsx +285 -0
- package/src/modules/analytics/pages/analytics-embeddings-page.tsx +274 -0
- package/src/modules/analytics/pages/analytics-events-page.tsx +425 -0
- package/src/modules/analytics/pages/analytics-llm-page.tsx +434 -0
- package/src/modules/analytics/pages/analytics-overview-page.tsx +336 -0
- package/src/modules/analytics/pages/analytics-page.tsx +20 -0
- package/src/modules/analytics/pages/analytics-storage-page.tsx +344 -0
- package/src/modules/analytics/pages/analytics-vectorstore-page.tsx +279 -0
- package/src/modules/analytics/routes/index.tsx +133 -0
- package/src/modules/analytics/utils/formatters.test.ts +148 -0
- package/src/modules/analytics/utils/formatters.ts +100 -0
- package/src/modules/auth/pages/login-page.module.css +113 -0
- package/src/modules/auth/pages/login-page.tsx +80 -0
- package/src/modules/auth/routes/index.tsx +24 -0
- package/src/modules/dashboard/components/ActivityTimelineWidget.tsx +199 -0
- package/src/modules/dashboard/components/AiInsightsWidget.tsx +358 -0
- package/src/modules/dashboard/components/AnimatedMetricCard.css +88 -0
- package/src/modules/dashboard/components/AnimatedMetricCard.tsx +125 -0
- package/src/modules/dashboard/components/HeroMetricCard.css +114 -0
- package/src/modules/dashboard/components/HeroMetricCard.tsx +152 -0
- package/src/modules/dashboard/components/HeroMetricsWidget.tsx +146 -0
- package/src/modules/dashboard/components/SystemResourcesWidget.tsx +197 -0
- package/src/modules/dashboard/components/dashboard-empty.tsx +22 -0
- package/src/modules/dashboard/components/dashboard-kpis-widget.tsx +59 -0
- package/src/modules/dashboard/components/dashboard-kpis.tsx +13 -0
- package/src/modules/dashboard/components/dashboard-latency-percentiles-widget.tsx +85 -0
- package/src/modules/dashboard/components/dashboard-metrics-widget.tsx +78 -0
- package/src/modules/dashboard/components/dashboard-plugins-widget.tsx +67 -0
- package/src/modules/dashboard/components/dashboard-requests-widget.tsx +62 -0
- package/src/modules/dashboard/layouts/dashboard-layout.tsx +16 -0
- package/src/modules/dashboard/pages/ai-insights-page.tsx +563 -0
- package/src/modules/dashboard/pages/dashboard-animations.css +317 -0
- package/src/modules/dashboard/pages/dashboard-page.tsx +13 -0
- package/src/modules/dashboard/routes/index.tsx +70 -0
- package/src/modules/observability/pages/devkit-page.tsx +263 -0
- package/src/modules/observability/pages/incident-detail-page.tsx +571 -0
- package/src/modules/observability/pages/incidents-page.tsx +370 -0
- package/src/modules/observability/pages/log-detail-page.tsx +508 -0
- package/src/modules/observability/pages/logs-page.tsx +1129 -0
- package/src/modules/observability/pages/prometheus-metrics-page.tsx +486 -0
- package/src/modules/observability/pages/state-broker-page.tsx +229 -0
- package/src/modules/observability/pages/system-events-page.tsx +255 -0
- package/src/modules/observability/routes/index.tsx +133 -0
- package/src/modules/plugins/components/marketplace-card.tsx +153 -0
- package/src/modules/plugins/components/plugin-ai-assistant-modal.tsx +374 -0
- package/src/modules/plugins/components/plugin-card.tsx +136 -0
- package/src/modules/plugins/components/plugin-detail/capabilities-summary.tsx +101 -0
- package/src/modules/plugins/components/plugin-detail/cli-commands-table.tsx +101 -0
- package/src/modules/plugins/components/plugin-detail/index.ts +7 -0
- package/src/modules/plugins/components/plugin-detail/jobs-table.tsx +64 -0
- package/src/modules/plugins/components/plugin-detail/permissions/env-permissions.tsx +30 -0
- package/src/modules/plugins/components/plugin-detail/permissions/filesystem-permissions.tsx +68 -0
- package/src/modules/plugins/components/plugin-detail/permissions/index.ts +6 -0
- package/src/modules/plugins/components/plugin-detail/permissions/network-permissions.tsx +43 -0
- package/src/modules/plugins/components/plugin-detail/permissions/platform-services.tsx +242 -0
- package/src/modules/plugins/components/plugin-detail/permissions/resource-quotas.tsx +96 -0
- package/src/modules/plugins/components/plugin-detail/permissions/shell-permissions.tsx +55 -0
- package/src/modules/plugins/components/plugin-detail/permissions-view.tsx +47 -0
- package/src/modules/plugins/components/plugin-detail/plugin-header.tsx +169 -0
- package/src/modules/plugins/components/plugin-detail/rest-routes-table.tsx +159 -0
- package/src/modules/plugins/components/plugin-detail/workflows-table.tsx +40 -0
- package/src/modules/plugins/components/plugin-grid.tsx +20 -0
- package/src/modules/plugins/components/plugin-table.tsx +178 -0
- package/src/modules/plugins/pages/marketplace-page.tsx +200 -0
- package/src/modules/plugins/pages/plugin-detail-page.tsx +1145 -0
- package/src/modules/plugins/routes/index.tsx +48 -0
- package/src/modules/plugins/utils/index.ts +1 -0
- package/src/modules/plugins/utils/schema-helpers.ts +21 -0
- package/src/modules/settings/components/api-routes-viewer.tsx +415 -0
- package/src/modules/settings/components/appearance-settings.tsx +200 -0
- package/src/modules/settings/components/configuration-settings.tsx +321 -0
- package/src/modules/settings/components/data-privacy-settings.tsx +111 -0
- package/src/modules/settings/components/experimental-settings.tsx +243 -0
- package/src/modules/settings/components/navigation-settings.tsx +198 -0
- package/src/modules/settings/components/notifications-settings.tsx +40 -0
- package/src/modules/settings/pages/settings-page.tsx +298 -0
- package/src/modules/settings/routes/index.tsx +47 -0
- package/src/pages/README-assistant.md +91 -0
- package/src/pages/login-page.tsx +177 -0
- package/src/pages/not-found-page.tsx +85 -0
- package/src/providers/auth-provider.tsx +40 -0
- package/src/providers/command-palette-provider.tsx +193 -0
- package/src/providers/data-sources-provider.tsx +27 -0
- package/src/providers/devtools-provider.tsx +72 -0
- package/src/providers/navigation-items-provider.tsx +38 -0
- package/src/providers/registry-v2-provider.tsx +93 -0
- package/src/providers/settings-provider.tsx +158 -0
- package/src/router.tsx +302 -0
- package/src/routes/helpers.tsx +25 -0
- package/src/routes/plugin-page-v2.tsx +52 -0
- package/src/routes/types.ts +18 -0
- package/src/styles/antd-overrides.css +211 -0
- package/src/styles/base.css +190 -0
- package/src/styles/settings.css +75 -0
- package/src/styles/variables.css +94 -0
- package/src/utils/analytics.ts +183 -0
- package/src/utils/apply-navigation-categories.ts +83 -0
- package/src/utils/commands.ts +252 -0
- package/src/utils/export.ts +174 -0
- package/src/utils/format-date.ts +166 -0
- package/src/utils/logger.ts +110 -0
- package/tailwind.config.ts +13 -0
- package/tsconfig.json +13 -0
- package/vitest-setup.ts +1 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useDataSources } from '../providers/data-sources-provider';
|
|
2
|
+
import { useHealthStatus, useReadyStatus } from '@kb-labs/studio-data-client';
|
|
3
|
+
import { studioConfig } from '@/config/studio.config';
|
|
4
|
+
import { RefreshCw, AlertCircle, AlertTriangle, Info } from 'lucide-react';
|
|
5
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { useRegistryV2 } from '../providers/registry-v2-provider';
|
|
7
|
+
import type { HealthStatus } from '@kb-labs/studio-data-client';
|
|
8
|
+
import styles from './health-banner.module.css';
|
|
9
|
+
|
|
10
|
+
export function HealthBanner() {
|
|
11
|
+
const sources = useDataSources();
|
|
12
|
+
const _registry = useRegistryV2();
|
|
13
|
+
const sseHealth = null as { status: string; ts?: string; ready: boolean; reason?: string } | null;
|
|
14
|
+
const { data: healthRaw, error, refetch } = useHealthStatus(sources.system);
|
|
15
|
+
const { data: ready } = useReadyStatus(sources.system);
|
|
16
|
+
const [showDetails, setShowDetails] = useState(false);
|
|
17
|
+
const lastRefetchedTs = useRef<string | null>(null);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!sseHealth) {return;}
|
|
21
|
+
const shouldRefetch =
|
|
22
|
+
(!sseHealth.ready || sseHealth.status === 'degraded') &&
|
|
23
|
+
sseHealth.ts &&
|
|
24
|
+
sseHealth.ts !== lastRefetchedTs.current;
|
|
25
|
+
if (shouldRefetch) {
|
|
26
|
+
lastRefetchedTs.current = sseHealth.ts ?? null;
|
|
27
|
+
void refetch();
|
|
28
|
+
}
|
|
29
|
+
}, [sseHealth, refetch]);
|
|
30
|
+
|
|
31
|
+
const healthSnapshot = (healthRaw as Record<string, unknown> | undefined)?.snapshot as
|
|
32
|
+
| HealthStatus['snapshot']
|
|
33
|
+
| undefined;
|
|
34
|
+
|
|
35
|
+
const fallbackStatus =
|
|
36
|
+
healthSnapshot?.status ?? ((healthRaw as { ok?: boolean } | undefined)?.ok ? 'healthy' : 'unknown');
|
|
37
|
+
const healthStatus = sseHealth?.status ?? fallbackStatus;
|
|
38
|
+
const readyStatus = sseHealth?.ready ?? ready?.ready ?? true;
|
|
39
|
+
const readyReason = !readyStatus ? sseHealth?.reason ?? ready?.reason : undefined;
|
|
40
|
+
|
|
41
|
+
const impactedComponents = (healthSnapshot?.checks ?? [])
|
|
42
|
+
.filter((check) => check.status !== 'ok')
|
|
43
|
+
.map((check) => ({ id: check.id, lastError: check.message ?? 'unknown issue' }));
|
|
44
|
+
|
|
45
|
+
const pluginMetrics = useMemo(() => {
|
|
46
|
+
const meta = healthSnapshot?.meta as Record<string, unknown> | undefined;
|
|
47
|
+
const pluginMounts = meta?.pluginMounts as Record<string, unknown> | undefined;
|
|
48
|
+
const succeeded =
|
|
49
|
+
pluginMounts && typeof pluginMounts.succeeded === 'number'
|
|
50
|
+
? (pluginMounts.succeeded as number)
|
|
51
|
+
: undefined;
|
|
52
|
+
const failed =
|
|
53
|
+
pluginMounts && typeof pluginMounts.failed === 'number'
|
|
54
|
+
? (pluginMounts.failed as number)
|
|
55
|
+
: undefined;
|
|
56
|
+
if (succeeded === undefined && failed === undefined) {return null;}
|
|
57
|
+
return { succeeded: succeeded ?? 0, failed: failed ?? 0 };
|
|
58
|
+
}, [healthSnapshot]);
|
|
59
|
+
|
|
60
|
+
const isDegraded =
|
|
61
|
+
(healthStatus === 'degraded' || healthSnapshot?.state === 'partial_observability') && readyStatus;
|
|
62
|
+
const isDown = !!error || !readyStatus;
|
|
63
|
+
const isMockMode =
|
|
64
|
+
studioConfig.dataSourceMode === 'mock' ||
|
|
65
|
+
(studioConfig.dataSourceMode === 'http' && !healthSnapshot && error);
|
|
66
|
+
|
|
67
|
+
if (!isDegraded && !isDown && !isMockMode) {return null;}
|
|
68
|
+
|
|
69
|
+
const variant = isDown ? 'error' : isDegraded ? 'warning' : 'info';
|
|
70
|
+
|
|
71
|
+
const stripCls = {
|
|
72
|
+
error: styles.stripError,
|
|
73
|
+
warning: styles.stripWarning,
|
|
74
|
+
info: styles.stripInfo,
|
|
75
|
+
}[variant];
|
|
76
|
+
|
|
77
|
+
const IconComponent = { error: AlertCircle, warning: AlertTriangle, info: Info }[variant];
|
|
78
|
+
const iconSize = 14;
|
|
79
|
+
|
|
80
|
+
const message = isDown
|
|
81
|
+
? 'REST API unavailable'
|
|
82
|
+
: isDegraded
|
|
83
|
+
? 'REST API degraded'
|
|
84
|
+
: 'Mock mode';
|
|
85
|
+
|
|
86
|
+
const description = isDown
|
|
87
|
+
? `Not ready at ${studioConfig.apiBaseUrl}${readyReason ? ` — ${readyReason}` : ''}`
|
|
88
|
+
: isDegraded
|
|
89
|
+
? `Some services may be unavailable${pluginMetrics?.failed ? ` · ${pluginMetrics.failed} plugin mount(s) failed` : ''}`
|
|
90
|
+
: 'Using mock data for development';
|
|
91
|
+
|
|
92
|
+
const hasDetails = isDegraded && impactedComponents.length > 0;
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
<div className={`${styles.strip} ${stripCls}`}>
|
|
97
|
+
<span className={styles.icon}>
|
|
98
|
+
<IconComponent size={iconSize} />
|
|
99
|
+
</span>
|
|
100
|
+
<span className={styles.message}>{message}</span>
|
|
101
|
+
<span className={styles.description}>{description}</span>
|
|
102
|
+
{hasDetails && (
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
className={styles.detailsBtn}
|
|
106
|
+
onClick={() => setShowDetails((v) => !v)}
|
|
107
|
+
>
|
|
108
|
+
{showDetails ? 'Hide details' : 'Details'}
|
|
109
|
+
</button>
|
|
110
|
+
)}
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
className={styles.retryBtn}
|
|
114
|
+
onClick={() => refetch()}
|
|
115
|
+
title="Retry"
|
|
116
|
+
>
|
|
117
|
+
<RefreshCw size={13} />
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{showDetails && hasDetails && (
|
|
122
|
+
<div className={styles.details}>
|
|
123
|
+
<ul className={styles.detailsList}>
|
|
124
|
+
{impactedComponents.map((c) => (
|
|
125
|
+
<li key={c.id}>
|
|
126
|
+
<strong>{c.id}</strong>: {c.lastError}
|
|
127
|
+
</li>
|
|
128
|
+
))}
|
|
129
|
+
</ul>
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
</>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UIBadge } from '@kb-labs/studio-ui-kit';
|
|
2
|
+
|
|
3
|
+
export function HealthIndicator({ status }: { status: 'ok' | 'degraded' | 'down' }) {
|
|
4
|
+
const variantMap = {
|
|
5
|
+
ok: 'success' as const,
|
|
6
|
+
degraded: 'warning' as const,
|
|
7
|
+
down: 'error' as const,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
return <UIBadge variant={variantMap[status]}>{status.toUpperCase()}</UIBadge>;
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/studio-app/components/page-header
|
|
3
|
+
* Page header component with title, description, and optional actions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import { UITitle, UITypographyText } from '@kb-labs/studio-ui-kit';
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
9
|
+
|
|
10
|
+
export interface PageHeaderProps {
|
|
11
|
+
title: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
actions?: ReactNode;
|
|
14
|
+
breadcrumbs?: ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
style?: React.CSSProperties;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function PageHeader({
|
|
20
|
+
title,
|
|
21
|
+
description,
|
|
22
|
+
actions,
|
|
23
|
+
breadcrumbs,
|
|
24
|
+
className,
|
|
25
|
+
style,
|
|
26
|
+
}: PageHeaderProps): React.ReactElement {
|
|
27
|
+
return (
|
|
28
|
+
<div className={className} style={{ marginBottom: '24px', ...style }}>
|
|
29
|
+
{breadcrumbs && <div style={{ marginBottom: '12px' }}>{breadcrumbs}</div>}
|
|
30
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: '16px', flexWrap: 'wrap' }}>
|
|
31
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
32
|
+
<UITitle level={1} className="typo-page-title" style={{ margin: 0, marginBottom: description ? '8px' : 0, color: 'var(--text-primary)' }}>
|
|
33
|
+
{title}
|
|
34
|
+
</UITitle>
|
|
35
|
+
{description && (
|
|
36
|
+
<UITypographyText className="typo-description" style={{ color: 'var(--text-secondary)', display: 'block' }}>
|
|
37
|
+
{description}
|
|
38
|
+
</UITypographyText>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
{actions && (
|
|
42
|
+
<div style={{ display: 'flex', gap: '8px', alignItems: 'flex-start', flexWrap: 'wrap' }}>
|
|
43
|
+
{actions}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/studio-app/components/page-transition
|
|
3
|
+
* Page transition wrapper with configurable animation types
|
|
4
|
+
*
|
|
5
|
+
* NOTE: Animation logic temporarily disabled.
|
|
6
|
+
* Infrastructure kept for future UI animation features.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
|
|
11
|
+
export interface PageTransitionProps {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Page transition wrapper component.
|
|
17
|
+
* Currently renders children directly without animations.
|
|
18
|
+
* The feature flag and settings infrastructure remain in place for future animation features.
|
|
19
|
+
*/
|
|
20
|
+
export function PageTransition({ children }: PageTransitionProps) {
|
|
21
|
+
// Animation logic temporarily disabled - instant navigation preferred for SaaS
|
|
22
|
+
// Infrastructure kept for potential future use with other UI animations
|
|
23
|
+
return <>{children}</>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/studio-app/components/role-switcher
|
|
3
|
+
* Mock authentication role switcher for demo purposes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import {
|
|
8
|
+
UICard, UIRadio, UIRadioGroup, UISpace, UITitle, UITypographyText, UITypographyParagraph,
|
|
9
|
+
UIAlert, UIDivider, UIIcon,
|
|
10
|
+
} from '@kb-labs/studio-ui-kit';
|
|
11
|
+
import type { UserRole } from '@/providers/auth-provider';
|
|
12
|
+
import { useAuth } from '@/providers/auth-provider';
|
|
13
|
+
|
|
14
|
+
const ROLE_CONFIG: Record<
|
|
15
|
+
UserRole,
|
|
16
|
+
{
|
|
17
|
+
label: string;
|
|
18
|
+
description: string;
|
|
19
|
+
icon: React.ReactElement;
|
|
20
|
+
color: string;
|
|
21
|
+
permissions: string[];
|
|
22
|
+
}
|
|
23
|
+
> = {
|
|
24
|
+
viewer: {
|
|
25
|
+
label: 'Viewer',
|
|
26
|
+
description: 'Read-only access to dashboards and reports',
|
|
27
|
+
icon: <UIIcon name="EyeOutlined" />,
|
|
28
|
+
color: '#52c41a',
|
|
29
|
+
permissions: ['View dashboards', 'View analytics', 'View workflows (read-only)'],
|
|
30
|
+
},
|
|
31
|
+
operator: {
|
|
32
|
+
label: 'Operator',
|
|
33
|
+
description: 'Standard user with execution permissions',
|
|
34
|
+
icon: <UIIcon name="ToolOutlined" />,
|
|
35
|
+
color: '#1890ff',
|
|
36
|
+
permissions: [
|
|
37
|
+
'All Viewer permissions',
|
|
38
|
+
'Execute workflows',
|
|
39
|
+
'Manage widgets',
|
|
40
|
+
'Configure settings',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
admin: {
|
|
44
|
+
label: 'Administrator',
|
|
45
|
+
description: 'Full access to all features and settings',
|
|
46
|
+
icon: <UIIcon name="CrownOutlined" />,
|
|
47
|
+
color: '#ff4d4f',
|
|
48
|
+
permissions: [
|
|
49
|
+
'All Operator permissions',
|
|
50
|
+
'Manage plugins',
|
|
51
|
+
'System configuration',
|
|
52
|
+
'Advanced developer tools',
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function RoleSwitcher() {
|
|
58
|
+
const { role, setRole } = useAuth();
|
|
59
|
+
|
|
60
|
+
const handleRoleChange = (newRole: UserRole) => {
|
|
61
|
+
setRole(newRole);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div>
|
|
66
|
+
<UITitle level={4}>
|
|
67
|
+
<UIIcon name="UserOutlined" style={{ marginRight: 8 }} />
|
|
68
|
+
Mock Authentication
|
|
69
|
+
</UITitle>
|
|
70
|
+
<UITypographyParagraph type="secondary">
|
|
71
|
+
This is a demo authentication system for testing different user roles. In production, this
|
|
72
|
+
would be replaced with real authentication (OAuth, JWT, etc.).
|
|
73
|
+
</UITypographyParagraph>
|
|
74
|
+
|
|
75
|
+
<UIAlert
|
|
76
|
+
variant="info"
|
|
77
|
+
message="Demo Mode"
|
|
78
|
+
description="No real authentication is performed. Role changes take effect immediately and persist in localStorage."
|
|
79
|
+
showIcon
|
|
80
|
+
style={{ marginBottom: 24 }}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
<UICard>
|
|
84
|
+
<UIRadioGroup
|
|
85
|
+
value={role}
|
|
86
|
+
onChange={(e) => handleRoleChange(e.target.value as UserRole)}
|
|
87
|
+
style={{ width: '100%' }}
|
|
88
|
+
>
|
|
89
|
+
<UISpace direction="vertical" size="large" style={{ width: '100%' }}>
|
|
90
|
+
{(Object.entries(ROLE_CONFIG) as [UserRole, (typeof ROLE_CONFIG)[UserRole]][]).map(
|
|
91
|
+
([roleId, config]) => (
|
|
92
|
+
<UICard
|
|
93
|
+
key={roleId}
|
|
94
|
+
hoverable
|
|
95
|
+
style={{
|
|
96
|
+
borderColor: role === roleId ? config.color : undefined,
|
|
97
|
+
borderWidth: role === roleId ? 2 : 1,
|
|
98
|
+
}}
|
|
99
|
+
onClick={() => handleRoleChange(roleId)}
|
|
100
|
+
>
|
|
101
|
+
<UIRadio value={roleId} style={{ marginBottom: 8 }}>
|
|
102
|
+
<UISpace>
|
|
103
|
+
<span style={{ color: config.color, fontSize: 18 }}>{config.icon}</span>
|
|
104
|
+
<UITypographyText strong style={{ fontSize: 16 }}>
|
|
105
|
+
{config.label}
|
|
106
|
+
</UITypographyText>
|
|
107
|
+
{role === roleId && (
|
|
108
|
+
<UITypographyText type="success" style={{ fontSize: 12 }}>
|
|
109
|
+
(Current)
|
|
110
|
+
</UITypographyText>
|
|
111
|
+
)}
|
|
112
|
+
</UISpace>
|
|
113
|
+
</UIRadio>
|
|
114
|
+
<UITypographyParagraph type="secondary" style={{ marginLeft: 24, marginBottom: 8 }}>
|
|
115
|
+
{config.description}
|
|
116
|
+
</UITypographyParagraph>
|
|
117
|
+
<UIDivider style={{ margin: '12px 0' }} />
|
|
118
|
+
<div style={{ marginLeft: 24 }}>
|
|
119
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>
|
|
120
|
+
Permissions:
|
|
121
|
+
</UITypographyText>
|
|
122
|
+
<ul style={{ marginTop: 8, marginBottom: 0, paddingLeft: 20 }}>
|
|
123
|
+
{config.permissions.map((permission, idx) => (
|
|
124
|
+
<li key={idx}>
|
|
125
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>
|
|
126
|
+
{permission}
|
|
127
|
+
</UITypographyText>
|
|
128
|
+
</li>
|
|
129
|
+
))}
|
|
130
|
+
</ul>
|
|
131
|
+
</div>
|
|
132
|
+
</UICard>
|
|
133
|
+
)
|
|
134
|
+
)}
|
|
135
|
+
</UISpace>
|
|
136
|
+
</UIRadioGroup>
|
|
137
|
+
</UICard>
|
|
138
|
+
|
|
139
|
+
<UIAlert
|
|
140
|
+
variant="success"
|
|
141
|
+
message="Try it out!"
|
|
142
|
+
description="Switch between roles and navigate through the app to see how different permissions affect the UI and available features."
|
|
143
|
+
showIcon
|
|
144
|
+
style={{ marginTop: 24 }}
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UIBadge } from '@kb-labs/studio-ui-kit';
|
|
2
|
+
|
|
3
|
+
export function RunBadge({ status }: { status: 'pending' | 'ok' | 'warn' | 'fail' }) {
|
|
4
|
+
const variantMap = {
|
|
5
|
+
ok: 'success' as const,
|
|
6
|
+
warn: 'warning' as const,
|
|
7
|
+
fail: 'error' as const,
|
|
8
|
+
pending: 'info' as const,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
return <UIBadge variant={variantMap[status]}>{status.toUpperCase()}</UIBadge>;
|
|
12
|
+
}
|
|
13
|
+
|