Skip to content

Commit e2fbf53

Browse files
committed
feat: providers distribution bar for crossplane
1 parent 2e950b0 commit e2fbf53

File tree

5 files changed

+154
-17
lines changed

5 files changed

+154
-17
lines changed

src/components/BentoGrid/ComponentCard/ComponentCard.module.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@
4848
flex: 1;
4949
}
5050

51+
.contentContainerMultiple {
52+
display: flex;
53+
flex-direction: column;
54+
align-items: center;
55+
justify-content: center;
56+
padding: 0.25rem 0;
57+
flex: 1;
58+
}
59+
5160
.progressBarContainer {
5261
display: flex;
5362
gap: 8px;
@@ -74,7 +83,8 @@
7483

7584
.progressBarContainerLarge {
7685
display: flex;
77-
gap: 8px;
86+
flex-direction: column;
87+
gap: 6px;
7888
width: 100%;
7989
max-width: none;
8090
padding: 0 1rem;

src/components/BentoGrid/ComponentCard/ComponentCard.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { MultiPercentageBar } from '../MultiPercentageBar/MultiPercentageBar';
66
import styles from './ComponentCard.module.css';
77
import { GenericHintProps } from '../../../types/types';
88

9-
export const ComponentCard: React.FC<GenericHintProps & { onClick?: () => void; size?: 'small' | 'medium' | 'large' | 'extra-large' }> = ({
9+
export const ComponentCard: React.FC<GenericHintProps & {
10+
onClick?: () => void;
11+
size?: 'small' | 'medium' | 'large' | 'extra-large';
12+
secondarySegments?: Array<{ percentage: number; color: string; label: string }>;
13+
secondaryLabel?: string;
14+
}> = ({
1015
enabled = false,
1116
version,
1217
allItems = [],
@@ -15,6 +20,8 @@ export const ComponentCard: React.FC<GenericHintProps & { onClick?: () => void;
1520
config,
1621
onClick,
1722
size = 'medium',
23+
secondarySegments,
24+
secondaryLabel = 'Secondary Metric',
1825
}) => {
1926
const { t } = useTranslation();
2027

@@ -44,15 +51,16 @@ export const ComponentCard: React.FC<GenericHintProps & { onClick?: () => void;
4451
}
4552
className={cx(styles.card, {
4653
[styles.disabled]: !enabled,
47-
[styles.clickable]: !!onClick,
54+
[styles.clickable]: !!onClick && enabled,
4855
})}
49-
onClick={onClick}
56+
onClick={enabled ? onClick : undefined}
57+
5058
>
5159
{/* Disabled overlay */}
5260
{!enabled && <div className={styles.disabledOverlay} />}
5361

5462
{/* Expand button */}
55-
{onClick && (
63+
{onClick && enabled && (
5664
<Button
5765
icon="sap-icon://expand"
5866
design="Transparent"
@@ -64,7 +72,9 @@ export const ComponentCard: React.FC<GenericHintProps & { onClick?: () => void;
6472
/>
6573
)}
6674

67-
<div className={styles.contentContainer}>
75+
<div className={
76+
(size === 'large' || size === 'extra-large') ? styles.contentContainerMultiple : styles.contentContainer
77+
}>
6878
<div className={
6979
size === 'small' ? styles.progressBarContainerSmall :
7080
size === 'medium' ? styles.progressBarContainerMedium :
@@ -89,6 +99,23 @@ export const ComponentCard: React.FC<GenericHintProps & { onClick?: () => void;
8999
'none'
90100
}
91101
/>
102+
103+
{/* Second progress bar only for large and extra-large cards */}
104+
{(size === 'large' || size === 'extra-large') && secondarySegments && (
105+
<MultiPercentageBar
106+
segments={secondarySegments}
107+
className={styles.progressBar}
108+
label={secondaryLabel}
109+
showPercentage={false}
110+
isHealthy={false}
111+
showOnlyNonZero={true}
112+
barWidth="90%"
113+
barHeight="12px"
114+
barMaxWidth="none"
115+
showSegmentLabels={true}
116+
minSegmentWidthForLabel={12}
117+
/>
118+
)}
92119
</div>
93120
</div>
94121
</Card>

src/components/BentoGrid/MultiPercentageBar/MultiPercentageBar.module.css

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,35 @@
169169
}
170170
}
171171

172+
/* Segment label inside the bar */
173+
.segmentLabel {
174+
position: absolute;
175+
top: 50%;
176+
left: 8px;
177+
transform: translateY(-50%);
178+
color: white;
179+
font-size: 0.75rem;
180+
font-weight: 500;
181+
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
182+
white-space: nowrap;
183+
pointer-events: none;
184+
z-index: 2;
185+
opacity: 0;
186+
animation: fadeInLabel calc(var(--animation-duration) * 1.5) ease-out forwards;
187+
animation-delay: calc(var(--animation-duration) * 0.5);
188+
}
189+
190+
@keyframes fadeInLabel {
191+
from {
192+
opacity: 0;
193+
transform: translateY(-50%) scale(0.8);
194+
}
195+
to {
196+
opacity: 1;
197+
transform: translateY(-50%) scale(1);
198+
}
199+
}
200+
172201
/* Theme Support */
173202
[data-ui5-theme-root*="dark"] .container,
174203
[data-ui5-theme*="dark"] .container {

src/components/BentoGrid/MultiPercentageBar/MultiPercentageBar.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ interface PercentageSegment {
55
percentage: number;
66
color: string;
77
label: string;
8+
count?: number; // Optional count for displaying inside segments
89
}
910

1011
interface MultiPercentageBarProps {
@@ -24,6 +25,8 @@ interface MultiPercentageBarProps {
2425
className?: string;
2526
style?: React.CSSProperties;
2627
animationDuration?: number; // Animation duration in ms (for CSS custom property)
28+
showSegmentLabels?: boolean; // Control whether to show labels inside segments
29+
minSegmentWidthForLabel?: number; // Minimum percentage to show label inside segment
2730
}
2831

2932
export const MultiPercentageBar: React.FC<MultiPercentageBarProps> = ({
@@ -43,6 +46,8 @@ export const MultiPercentageBar: React.FC<MultiPercentageBarProps> = ({
4346
className,
4447
style,
4548
animationDuration = 400, // Match CSS default
49+
showSegmentLabels = false,
50+
minSegmentWidthForLabel = 15, // Only show label if segment is at least 15%
4651
}) => {
4752
// Memoize filtered segments
4853
const filteredSegments = useMemo(() => {
@@ -78,10 +83,10 @@ export const MultiPercentageBar: React.FC<MultiPercentageBarProps> = ({
7883
{showLabels && (
7984
<div className={styles.labelContainer}>
8085
<div className={styles.labelGroup}>
86+
<span className={`${styles.label} ${allHealthy ? styles.healthy : ''}`}>{displayLabel}</span>
8187
{showPercentage && (
8288
<span className={`${styles.percentage} ${allHealthy ? styles.healthy : ''}`}>{primaryPercentage}%</span>
8389
)}
84-
<span className={`${styles.label} ${allHealthy ? styles.healthy : ''}`}>{displayLabel}</span>
8590
</div>
8691
</div>
8792
)}
@@ -101,6 +106,13 @@ export const MultiPercentageBar: React.FC<MultiPercentageBarProps> = ({
101106
>
102107
{/* Wave animation overlay */}
103108
<div className={styles.waveOverlay} />
109+
110+
{/* Segment label inside the bar */}
111+
{showSegmentLabels && segment.percentage >= minSegmentWidthForLabel && (
112+
<span className={styles.segmentLabel}>
113+
{segment.count ? `${segment.label} ${segment.count}` : segment.label}
114+
</span>
115+
)}
104116
</div>
105117
))}
106118
</div>

src/spaces/mcp/pages/McpPage.tsx

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ControlPlane as ControlPlaneResource } from '../../../lib/api/types/cra
1313
import { useTranslation } from 'react-i18next';
1414
import { McpContextProvider, WithinManagedControlPlane } from '../../../lib/shared/McpContext.tsx';
1515
import MCPHealthPopoverButton from '../../../components/ControlPlane/MCPHealthPopoverButton.tsx';
16-
import { useApiResource } from '../../../lib/api/useApiResource.ts';
16+
import { useApiResource, useProvidersConfigResource } from '../../../lib/api/useApiResource.ts';
1717

1818
import { YamlViewButtonWithLoader } from '../../../components/Yaml/YamlViewButtonWithLoader.tsx';
1919
import { AuthProviderMcp } from '../auth/AuthContextMcp.tsx';
@@ -29,6 +29,7 @@ import { ManagedResources } from '../../../components/ControlPlane/ManagedResour
2929
import { Providers } from '../../../components/ControlPlane/Providers.tsx';
3030
import { ProvidersConfig } from '../../../components/ControlPlane/ProvidersConfig.tsx';
3131
import FluxList from '../../../components/ControlPlane/FluxList.tsx';
32+
import { resolveProviderType } from '../../../components/Graphs/graphUtils';
3233

3334
// Utility function to flatten managed resources
3435
const flattenManagedResources = (managedResources: ManagedResourcesResponse): ManagedResourceItem[] => {
@@ -39,6 +40,51 @@ const flattenManagedResources = (managedResources: ManagedResourcesResponse): Ma
3940
.flatMap((managedResource) => managedResource.items || []);
4041
};
4142

43+
// Utility function to calculate provider distribution with graph colors
44+
const calculateProviderDistribution = (items: ManagedResourceItem[], providerConfigs: any[]) => {
45+
if (!items || items.length === 0) return { segments: [], totalProviders: 0 };
46+
47+
// Graph color palette (same as in graphUtils.ts)
48+
const colors = [
49+
'#FFC933', // MANGO 4
50+
'#FF8AF0', // PINK 4
51+
'#FEADC8', // RASPBERRY 4
52+
'#2CE0BF', // TEAL 4
53+
'#FF8CB2', // RED 4
54+
'#B894FF', // INDIGO 4
55+
'#049F9A', // TEAL 6
56+
'#FA4F96', // RASPBERRY 6
57+
'#F31DED', // PINK 6
58+
'#7858FF', // INDIGO 6
59+
];
60+
61+
// Count resources by provider type (same method as graph)
62+
const providerCounts: Record<string, number> = {};
63+
64+
items.forEach(item => {
65+
const providerConfigName = item?.spec?.providerConfigRef?.name ?? 'unknown';
66+
const providerType = resolveProviderType(providerConfigName, providerConfigs);
67+
providerCounts[providerType] = (providerCounts[providerType] || 0) + 1;
68+
});
69+
70+
// Convert to segments with percentages and counts
71+
const total = items.length;
72+
const segments = Object.entries(providerCounts)
73+
.map(([provider, count], index) => ({
74+
percentage: Math.round((count / total) * 100),
75+
color: colors[index % colors.length],
76+
label: provider.replace('provider-', '').toUpperCase(),
77+
count: count
78+
}))
79+
.filter(segment => segment.percentage > 0)
80+
.sort((a, b) => b.percentage - a.percentage);
81+
82+
return {
83+
segments,
84+
totalProviders: segments.length
85+
};
86+
};
87+
4288
export default function McpPage() {
4389
const { projectName, workspaceName, controlPlaneName } = useParams();
4490
const { t } = useTranslation();
@@ -93,15 +139,26 @@ function McpPageContent({ mcp, controlPlaneName }: { mcp: any; controlPlaneName:
93139
refreshInterval: resourcesInterval,
94140
});
95141

142+
// Fetch provider configs for distribution calculation
143+
const { data: providerConfigsList } = useProvidersConfigResource({
144+
refreshInterval: 60000,
145+
});
146+
96147
// Flatten all managed resources once and pass to components
97148
const allItems = useMemo(
98149
() => flattenManagedResources(managedResources ?? ([] as unknown as ManagedResourcesResponse)),
99150
[managedResources],
100151
);
101152

153+
// Calculate provider distribution for crossplane card
154+
const providerDistribution = useMemo(
155+
() => calculateProviderDistribution(allItems, providerConfigsList || []),
156+
[allItems, providerConfigsList],
157+
);
158+
102159
// Get hint configurations
103160
const crossplaneConfig = useCrossplaneHintConfig();
104-
const gitOpsConfig = useGitOpsHintConfig();
161+
const gitOpsConfig = useGitOpsHintConfig(); // DEACTIVATED via enabled={false}
105162
const vaultConfig = useESOHintConfig();
106163
const veleroConfig = useKyvernoHintConfig();
107164

@@ -190,6 +247,8 @@ function McpPageContent({ mcp, controlPlaneName }: { mcp: any; controlPlaneName:
190247
config={crossplaneConfig}
191248
onClick={expandedCard === 'crossplane' ? handleCollapseExpanded : handleCrossplaneExpand}
192249
size="large"
250+
secondarySegments={providerDistribution.segments}
251+
secondaryLabel={`Providers ${providerDistribution.totalProviders}`}
193252
/>
194253
{expandedCard === 'crossplane' && (
195254
<Button
@@ -209,7 +268,7 @@ function McpPageContent({ mcp, controlPlaneName }: { mcp: any; controlPlaneName:
209268
</BentoCard>
210269
)}
211270

212-
{/* GitOps component - shows when expanded */}
271+
{/* GitOps component - shows when expanded - DEACTIVATED */}
213272
{expandedCard === 'gitops' && (
214273
<BentoCard
215274
size="large"
@@ -219,7 +278,7 @@ function McpPageContent({ mcp, controlPlaneName }: { mcp: any; controlPlaneName:
219278
>
220279
<div style={{ position: 'relative', height: '100%' }}>
221280
<ComponentCard
222-
enabled={!!mcp?.spec?.components?.flux}
281+
enabled={true}
223282
version={mcp?.spec?.components?.flux?.version}
224283
allItems={allItems}
225284
isLoading={managedResourcesLoading}
@@ -260,15 +319,15 @@ function McpPageContent({ mcp, controlPlaneName }: { mcp: any; controlPlaneName:
260319
{/* Right side cards - hide when any component is expanded */}
261320
{!expandedCard && (
262321
<>
263-
{/* Right side: First medium component (GitOps) */}
322+
{/* Right side: First medium component (GitOps) - DEACTIVATED */}
264323
<BentoCard
265324
size="medium"
266325
gridColumn="9 / 13"
267326
gridRow="1 / 3"
268-
className={isExpanding ? styles.hidingCard : ''}
327+
className={isExpanding ? styles.hidingCard : styles.disabledCard}
269328
>
270329
<ComponentCard
271-
enabled={!!mcp?.spec?.components?.flux}
330+
enabled={true}
272331
version={mcp?.spec?.components?.flux?.version}
273332
allItems={allItems}
274333
isLoading={managedResourcesLoading}
@@ -279,15 +338,15 @@ function McpPageContent({ mcp, controlPlaneName }: { mcp: any; controlPlaneName:
279338
/>
280339
</BentoCard>
281340

282-
{/* Right side: Second medium component (GitOps copy) */}
341+
{/* Right side: Second medium component (GitOps copy) - DEACTIVATED */}
283342
<BentoCard
284343
size="medium"
285344
gridColumn="9 / 13"
286345
gridRow="3 / 5"
287-
className={isExpanding ? styles.hidingCard : ''}
346+
className={isExpanding ? styles.hidingCard : styles.disabledCard}
288347
>
289348
<ComponentCard
290-
enabled={!!mcp?.spec?.components?.flux}
349+
enabled={false}
291350
version={mcp?.spec?.components?.flux?.version}
292351
allItems={allItems}
293352
isLoading={managedResourcesLoading}

0 commit comments

Comments
 (0)