Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
97d852d
feat: add hint object to guide users in quality management
Lasserich Aug 20, 2025
e5d5796
feat: populate crossplane and flux with data
Lasserich Aug 20, 2025
319d67d
fix: make linitii happii
Lasserich Aug 20, 2025
8263c6f
feat: use new types
Lasserich Aug 21, 2025
81a95ec
fix: I love AI sometimes
Lasserich Aug 21, 2025
5f5620d
feat: expand radar chart
Lasserich Aug 21, 2025
0a15232
Merge branch 'main' into pr/251
lucasgoral Aug 21, 2025
ba9de76
Update listManagedResources.ts
lucasgoral Aug 21, 2025
4ad9302
Update Hints.tsx
lucasgoral Aug 21, 2025
1cc1189
fix: smaller stuff
Lasserich Aug 21, 2025
e0cb0ef
Merge branch 'ft/hints' of https://github.com/Lasserich/ui-frontend i…
Lasserich Aug 21, 2025
26001d9
feat: fix darkmode
Lasserich Aug 21, 2025
4939d67
Merge branch 'main' into ft/hints
Lasserich Aug 21, 2025
82a4341
feat: nice bars!!
Lasserich Aug 22, 2025
79b952c
feat: generify
Lasserich Aug 22, 2025
7fbd8cd
fix: cleanup
Lasserich Aug 22, 2025
ccf9035
feat: cleanup
Lasserich Aug 22, 2025
ccc3686
test: added some
Lasserich Aug 22, 2025
fee5f26
fix: darkmode
Lasserich Aug 22, 2025
fadf3ac
fix: linting
Lasserich Aug 25, 2025
5dace2d
fix: on activate
Lasserich Aug 25, 2025
291c54e
feat: re-usable hover
Lasserich Aug 25, 2025
b2f9f93
feat: crossplane and flux component ready for review
Lasserich Aug 25, 2025
8b89e31
fix: disable vault for now since it does not have stats yet
Lasserich Aug 25, 2025
2b45192
Merge branch 'main' into ft/hints
lucasgoral Aug 25, 2025
bd550d9
feat: incorporate feedback
Lasserich Aug 27, 2025
874a80b
Merge branch 'ft/hints' of https://github.com/Lasserich/ui-frontend i…
Lasserich Aug 27, 2025
aa745ad
fix: npm run lint:fix :)(:
Lasserich Aug 27, 2025
530a862
fix: zIndex
Lasserich Aug 27, 2025
9bdb8b2
fix: remove info, coming soon, downsize vault
Lasserich Aug 27, 2025
4978b76
fix: build
Lasserich Aug 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 296 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@ui5/webcomponents-fiori": "^2.7.2",
"@ui5/webcomponents-icons": "^2.7.2",
"@ui5/webcomponents-react": "^2.7.2",
"@ui5/webcomponents-react-charts": "^2.13.1",
"@xyflow/react": "^12.8.2",
"clsx": "^2.1.1",
"dagre": "^0.8.5",
Expand Down
56 changes: 55 additions & 1 deletion public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
"defaultNamespaceInfo": "Leave empty to use <span>default</span> namespace",
"serviceAccoutsGuide": "You can also use our <link1>Service Account Guide</link1> for more information."
},

"ProjectsPage": {
"header": "Your instances of <span>ManagedControlPlane</span>",
"projectHeader": "Project:"
Expand All @@ -185,6 +186,7 @@
"McpPage": {
"accessError": "Managed Control Plane does not have access information yet",
"componentsTitle": "Components",
"overviewTitle": "Overview",
"crossplaneTitle": "Crossplane",
"gitOpsTitle": "GitOps",
"landscapersTitle": "Landscapers",
Expand Down Expand Up @@ -334,7 +336,12 @@
"synced": "Synced",
"healthy": "Healthy",
"installed": "Installed",
"none": "None"
"none": "None",
"creating": "Creating",
"unhealthy": "Unhealthy",
"progress": "Managed",
"remaining": "Remaining",
"active": "Active"
},
"errors": {
"installError": "Install error",
Expand Down Expand Up @@ -366,5 +373,52 @@
"selectedComponents": "Selected Components",
"pleaseSelectComponents": "Choose the components you want to add to your Managed Control Plane.",
"cannotLoad": "Cannot load components list"
},
"Hints": {
"CrossplaneHint": {
"title": "Crossplane",
"subtitle": "Managed Resources Readiness",
"activeStatus": "Active v",
"progressAvailable": "% Available",
"noResources": "No Resources",
"inactive": "Inactive",
"activate": "Activate",
"healthy": "Healthy",
"hoverContent": {
"totalResources": "Total Resources",
"healthy": "Healthy",
"creating": "Creating",
"failing": "Failing"
}
},
"GitOpsHint": {
"title": "Flux",
"subtitle": "GitOps Progress",
"activeStatus": "Active v",
"progressAvailable": "% Available",
"noResources": "No Resources",
"inactive": "Inactive",
"activate": "Activate",
"managed": "Managed",
"hoverContent": {
"totalResources": "Total Resources",
"managed": "Managed",
"unmanaged": "Unmanaged"
}
},
"VaultHint": {
"title": "Vault",
"subtitle": "Rotating Secrets Progress",
"activeStatus": "Active v",
"progressAvailable": "% Available",
"noResources": "No Resources",
"inactive": "Inactive",
"activate": "Activate"
},
"common": {
"loading": "Loading...",
"errorLoadingResources": "Error loading resources",
"activate": "Activate"
}
}
}
Binary file added public/vault.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 129 additions & 0 deletions src/components/Hints/GenericHint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { useState } from 'react';
import { Card, CardHeader, MessageViewButton } from '@ui5/webcomponents-react';
import { useTranslation } from 'react-i18next';
import cx from 'clsx';
import { MultiPercentageBar } from './MultiPercentageBar';
import { GenericHintProps } from './types';
import { styles } from './Hints';
import { HoverContent } from './HoverContent';

export const GenericHint: React.FC<GenericHintProps> = ({
enabled = false,
version,
onActivate,
allItems = [],
isLoading,
error,
config,
}) => {
const { t } = useTranslation();
const [hovered, setHovered] = useState(false);

// Calculate segments and state using the provided calculator
const hintState = config.calculateSegments(allItems, isLoading || false, error, enabled, t);

// Handle click navigation if scroll target is provided
const handleClick =
enabled && config.scrollTarget
? () => {
const el = document.querySelector(config.scrollTarget!);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
: undefined;

return (
<div style={{ position: 'relative', width: '100%' }}>
<Card
header={
<CardHeader
additionalText={enabled ? `v${version ?? ''}` : undefined}
avatar={
<img
src={config.iconSrc}
alt={config.iconAlt}
style={{
width: 50,
height: 50,
borderRadius: '50%',
background: 'transparent',
objectFit: 'cover',
...config.iconStyle,
}}
/>
}
titleText={config.title}
subtitleText={config.subtitle}
interactive={enabled}
/>
}
className={cx({
[styles['disabled']]: !enabled,
})}
onClick={handleClick}
onMouseEnter={enabled ? () => setHovered(true) : undefined}
onMouseLeave={enabled ? () => setHovered(false) : undefined}
>
{/* Disabled overlay */}
{!enabled && <div className={styles.disabledOverlay} />}

<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '0.5rem 0' }}>
<div
style={{
display: 'flex',
gap: '8px',
width: '100%',
maxWidth: 500,
padding: '0 0.5rem',
}}
>
<MultiPercentageBar
segments={hintState.segments}
style={{ width: '100%' }}
label={hintState.label}
showPercentage={hintState.showPercentage}
isHealthy={hintState.isHealthy}
showOnlyNonZero={hintState.showOnlyNonZero ?? true}
/>
</div>
</div>

{/* Hover content (e.g., RadarChart for Crossplane) */}
{enabled &&
hovered &&
config.calculateHoverData &&
(() => {
const hoverData = config.calculateHoverData(allItems, enabled, t);
return hoverData ? <HoverContent enabled={enabled} isLoading={isLoading} {...hoverData} /> : null;
})()}

{/* Legacy hover content support */}
{enabled &&
hovered &&
!config.calculateHoverData &&
config.renderHoverContent &&
config.renderHoverContent(allItems, enabled)}

{/* Activate button for disabled state */}
{!enabled && (
<div
style={{
position: 'absolute',
top: '16px',
right: '16px',
zIndex: 2,
pointerEvents: 'auto',
}}
>
<MessageViewButton
type={'Information'}
style={{ cursor: onActivate ? 'pointer' : 'default' }}
onClick={onActivate}
/>
</div>
)}
</Card>
</div>
);
};
106 changes: 106 additions & 0 deletions src/components/Hints/Hints.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
.disabled {
position: relative;
}

.disabledOverlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.6);
backdrop-filter: grayscale(0.9) blur(0.5px);
border-radius: inherit;
z-index: 1;
pointer-events: none;
}

/* Legend Styles */
.legendSection {
color: var(--sapTextColor, #374151);
}

.legendTitle {
color: var(--sapTitleColor, var(--sapTextColor, #374151));
}

.legendItem {
color: var(--sapContent_LabelColor, #6b7280);
}

/* Dark mode support */
@media (prefers-color-scheme: dark) {
.disabledOverlay {
background: rgba(0, 0, 0, 0.4);
}

.chartBackground {
background-color: #2a2a2a;
}

.chartLabel {
color: #ffffff;
}

.legendSection {
color: #ffffff;
}

.legendTitle {
color: #ffffff;
}

.legendItem {
color: #cccccc;
}
}

/* Also check for UI5 theme variables for dark themes */
[data-ui5-theme-root*="dark"] .disabledOverlay,
[data-ui5-theme*="dark"] .disabledOverlay {
background: rgba(0, 0, 0, 0.4);
}

[data-ui5-theme-root*="dark"] .legendSection,
[data-ui5-theme*="dark"] .legendSection {
color: #ffffff;
}

[data-ui5-theme-root*="dark"] .legendTitle,
[data-ui5-theme*="dark"] .legendTitle {
color: #ffffff;
}

[data-ui5-theme-root*="dark"] .legendItem,
[data-ui5-theme*="dark"] .legendItem {
color: #cccccc;
}

/* Hover Content Animation */
.hoverContent {
overflow: hidden;
transition: all 0.3s ease-in-out;
animation: expandIn 0.3s ease-in-out;
}

@keyframes expandIn {
from {
max-height: 0;
opacity: 0;
transform: scaleY(0);
transform-origin: top;
}
to {
max-height: 500px;
opacity: 1;
transform: scaleY(1);
transform-origin: top;
}
}

.hoverContentLoading {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
}
Loading
Loading