diff --git a/cypress/screenshots/Dialogs/DeleteConfirmationDialog.test.tsx/DeleteConfirmationDialog -- should display correct resource name in all labels (failed).png b/cypress/screenshots/Dialogs/DeleteConfirmationDialog.test.tsx/DeleteConfirmationDialog -- should display correct resource name in all labels (failed).png
deleted file mode 100644
index c72623fe..00000000
Binary files a/cypress/screenshots/Dialogs/DeleteConfirmationDialog.test.tsx/DeleteConfirmationDialog -- should display correct resource name in all labels (failed).png and /dev/null differ
diff --git a/public/locales/en.json b/public/locales/en.json
index 8637500f..b65bb204 100644
--- a/public/locales/en.json
+++ b/public/locales/en.json
@@ -67,7 +67,7 @@
"menuDownload": "Download",
"menuCopy": "Copy to clipboard"
},
- "NoManagedControlPlaneBanner": {
+ "IllustratedBanner": {
"titleMessage": "No ManagedControlPlane",
"subtitleMessage": "Create a ManagedControlPlane to get started",
"helpButton": "Help"
@@ -75,6 +75,9 @@
"IntelligentBreadcrumbs": {
"homeLabel": "Home"
},
+ "MCPContext": {
+ "errorMessage": "An unknown error occurred"
+ },
"NotInLuigiView": {
"titleMessage": "Opened outside of Hyperspace Portal",
"subtitleMessage": "Looks like this page is not opened inside of the Hyperspace Portal. Contact admins for help."
diff --git a/src/components/ControlPlane/FluxList.tsx b/src/components/ControlPlane/FluxList.tsx
index 91bc0a34..4897ea50 100644
--- a/src/components/ControlPlane/FluxList.tsx
+++ b/src/components/ControlPlane/FluxList.tsx
@@ -44,7 +44,7 @@ export default function FluxList() {
if (repoErr || kustomizationErr) {
return (
);
diff --git a/src/components/ControlPlane/ManagedResources.tsx b/src/components/ControlPlane/ManagedResources.tsx
index c2fcbf05..0fb6d72b 100644
--- a/src/components/ControlPlane/ManagedResources.tsx
+++ b/src/components/ControlPlane/ManagedResources.tsx
@@ -109,7 +109,7 @@ export function ManagedResources() {
<>
{t('ManagedResources.header')}
- {error && }
+ {error && }
{!error && (
{t('Providers.headerProviders')}
- {error && }
+ {error && }
{!error && (
;
}
if (error) {
- return ;
+ return ;
}
return (
diff --git a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx
index 5ca9190e..5eee5d0d 100644
--- a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx
+++ b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx
@@ -10,7 +10,6 @@ import '@ui5/webcomponents-fiori/dist/illustrations/NoData.js';
import '@ui5/webcomponents-fiori/dist/illustrations/EmptyList.js';
import '@ui5/webcomponents-icons/dist/delete';
import { CopyButton } from '../../Shared/CopyButton.tsx';
-import { NoManagedControlPlaneBanner } from '../NoManagedControlPlaneBanner.tsx';
import { ControlPlaneCard } from '../ControlPlaneCard/ControlPlaneCard.tsx';
import {
ListWorkspacesType,
@@ -35,6 +34,9 @@ import IllustratedError from '../../Shared/IllustratedError.tsx';
import { APIError } from '../../../lib/api/error.ts';
import { useTranslation } from 'react-i18next';
import { YamlViewButton } from '../../Yaml/YamlViewButton.tsx';
+import { IllustratedBanner } from '../../Ui/IllustratedBanner/IllustratedBanner.tsx';
+import { useFrontendConfig } from '../../../context/FrontendConfigContext.tsx';
+import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';
interface Props {
projectName: string;
@@ -62,6 +64,8 @@ export function ControlPlaneListWorkspaceGridTile({
const { trigger } = useApiResourceMutation(
DeleteWorkspaceResource(projectNamespace, workspaceName),
);
+
+ const { links } = useFrontendConfig();
const errorView = createErrorView(cpsError);
function createErrorView(error: APIError) {
@@ -72,7 +76,7 @@ export function ControlPlaneListWorkspaceGridTile({
title={t(
'ControlPlaneListWorkspaceGridTile.permissionErrorMessage',
)}
- subtitleText={t(
+ details={t(
'ControlPlaneListWorkspaceGridTile.permissionErrorMessageSubtitle',
)}
/>
@@ -145,20 +149,26 @@ export function ControlPlaneListWorkspaceGridTile({
>
{errorView ? (
errorView
+ ) : controlplanes?.length === 0 ? (
+
) : (
- {controlplanes?.length === 0 ? (
-
- ) : (
- controlplanes?.map((cp) => (
-
- ))
- )}
+ {controlplanes?.map((cp) => (
+
+ ))}
)}
diff --git a/src/components/ControlPlanes/NoManagedControlPlaneBanner.tsx b/src/components/ControlPlanes/NoManagedControlPlaneBanner.tsx
deleted file mode 100644
index 523063ef..00000000
--- a/src/components/ControlPlanes/NoManagedControlPlaneBanner.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Button, FlexBox, IllustratedMessage } from '@ui5/webcomponents-react';
-
-import '@ui5/webcomponents-fiori/dist/illustrations/NoData.js';
-import '@ui5/webcomponents-fiori/dist/illustrations/EmptyList.js';
-import '@ui5/webcomponents-icons/dist/delete';
-import IllustrationMessageDesign from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageDesign.js';
-import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
-import { useFrontendConfig } from '../../context/FrontendConfigContext';
-import { useTranslation } from 'react-i18next';
-
-export function NoManagedControlPlaneBanner() {
- const { links } = useFrontendConfig();
- const { t } = useTranslation();
-
- return (
-
-
-
-
- );
-}
diff --git a/src/components/Projects/ProjectChooser.tsx b/src/components/Projects/ProjectChooser.tsx
index 537d9391..a06b5f18 100644
--- a/src/components/Projects/ProjectChooser.tsx
+++ b/src/components/Projects/ProjectChooser.tsx
@@ -14,7 +14,7 @@ export default function ProjectChooser({ currentProjectName }: Props) {
const navigate = useLuigiNavigate();
if (error) {
- return ;
+ return ;
}
return (
diff --git a/src/components/Projects/ProjectsList.tsx b/src/components/Projects/ProjectsList.tsx
index 9314fb33..2f14307b 100644
--- a/src/components/Projects/ProjectsList.tsx
+++ b/src/components/Projects/ProjectsList.tsx
@@ -77,7 +77,7 @@ export default function ProjectsList() {
[],
);
if (error) {
- return ;
+ return ;
}
return (
diff --git a/src/components/Shared/IllustratedError.tsx b/src/components/Shared/IllustratedError.tsx
index 2c41418b..701e7528 100644
--- a/src/components/Shared/IllustratedError.tsx
+++ b/src/components/Shared/IllustratedError.tsx
@@ -1,28 +1,21 @@
-import { IllustratedMessage } from '@ui5/webcomponents-react';
import '@ui5/webcomponents-fiori/dist/illustrations/SimpleError';
-import IllustrationMessageDesign from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageDesign.js';
import { useTranslation } from 'react-i18next';
+import { IllustratedBanner } from '../Ui/IllustratedBanner/IllustratedBanner';
+import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';
interface Props {
title?: string;
- subtitleText?: string;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- error?: any;
+ details?: string;
}
-export default function IllustratedError({
- title,
- subtitleText,
- error,
-}: Props) {
+export default function IllustratedError({ title, details }: Props) {
const { t } = useTranslation();
return (
-
);
}
diff --git a/src/components/Ui/IllustratedBanner/IllustratedBanner.test.tsx b/src/components/Ui/IllustratedBanner/IllustratedBanner.test.tsx
new file mode 100644
index 00000000..08f13371
--- /dev/null
+++ b/src/components/Ui/IllustratedBanner/IllustratedBanner.test.tsx
@@ -0,0 +1,60 @@
+import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';
+import { IllustratedBanner } from './IllustratedBanner';
+import '@ui5/webcomponents-fiori/dist/illustrations/AllIllustrations.js';
+
+describe('', () => {
+ it('renders title and subtitle', () => {
+ cy.mount(
+ ,
+ );
+
+ cy.contains('Test title').should('be.visible');
+ cy.contains('Test subtitle').should('be.visible');
+ });
+
+ it('renders help button with correct text and icon', () => {
+ cy.mount(
+ ,
+ );
+
+ cy.get('ui5-button').contains('Need Help?').should('be.visible');
+ cy.get('ui5-button').should(
+ 'have.attr',
+ 'icon',
+ 'sap-icon://question-mark',
+ );
+ });
+
+ it('renders a link with correct attributes', () => {
+ cy.mount(
+ ,
+ );
+
+ cy.get('a')
+ .should('have.attr', 'href', 'https://example.com')
+ .and('have.attr', 'target', '_blank')
+ .and('have.attr', 'rel', 'noreferrer');
+
+ cy.get('a').contains('Go');
+ });
+});
diff --git a/src/components/Ui/IllustratedBanner/IllustratedBanner.tsx b/src/components/Ui/IllustratedBanner/IllustratedBanner.tsx
new file mode 100644
index 00000000..2121a736
--- /dev/null
+++ b/src/components/Ui/IllustratedBanner/IllustratedBanner.tsx
@@ -0,0 +1,46 @@
+import IllustrationMessageDesign from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageDesign.js';
+import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';
+import { FlexBox, IllustratedMessage, Button } from '@ui5/webcomponents-react';
+import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
+import '@ui5/webcomponents-fiori/dist/illustrations/AllIllustrations.js';
+
+type InfoBannerProps = {
+ title: string;
+ subtitle: string;
+ illustrationName: IllustrationMessageType; // e.g. 'NoData', 'SimpleError', etc.
+ help?: {
+ link: string;
+ buttonText: string;
+ buttonIcon?: string;
+ };
+};
+
+export const IllustratedBanner = ({
+ title,
+ subtitle,
+ illustrationName,
+ help,
+}: InfoBannerProps) => {
+ return (
+
+
+ {help && (
+
+
+
+ )}
+
+ );
+};
diff --git a/src/components/Yaml/YamlLoader.tsx b/src/components/Yaml/YamlLoader.tsx
index 2b7c64d6..9107b113 100644
--- a/src/components/Yaml/YamlLoader.tsx
+++ b/src/components/Yaml/YamlLoader.tsx
@@ -21,7 +21,7 @@ export const YamlLoader: FC = ({
const { t } = useTranslation();
if (isLoading) return ;
if (error) {
- return ;
+ return ;
}
return (
diff --git a/src/context/AuthProviderOnboarding.tsx b/src/context/AuthProviderOnboarding.tsx
index 570e3cbb..42e86670 100644
--- a/src/context/AuthProviderOnboarding.tsx
+++ b/src/context/AuthProviderOnboarding.tsx
@@ -1,9 +1,7 @@
import { ReactNode } from 'react';
-import { AuthProvider } from 'react-oidc-context';
+import { AuthProvider, AuthProviderProps } from 'react-oidc-context';
import { OIDCConfig, useFrontendConfig } from './FrontendConfigContext.tsx';
-import { WebStorageStateStore } from "oidc-client-ts";
-import { AuthProviderProps } from "react-oidc-context";
-
+import { WebStorageStateStore } from 'oidc-client-ts';
interface AuthProviderOnboardingProps {
children?: ReactNode;
@@ -33,4 +31,4 @@ function buildAuthProviderConfig(oidcConfig: OIDCConfig) {
},
};
return props;
-}
\ No newline at end of file
+}
diff --git a/src/context/FrontendConfigContext.tsx b/src/context/FrontendConfigContext.tsx
index 3005076f..d10f4018 100644
--- a/src/context/FrontendConfigContext.tsx
+++ b/src/context/FrontendConfigContext.tsx
@@ -10,8 +10,6 @@ export enum Landscape {
Local = 'LOCAL',
}
-
-
interface FrontendConfigContextType extends FrontendConfig {
links: DocLinkCreator;
}
@@ -19,7 +17,9 @@ interface FrontendConfigContextType extends FrontendConfig {
export const FrontendConfigContext =
createContext(null);
-const fetchPromise = fetch('/frontend-config.json').then((res) => res.json()).then((data) => validateAndCastFrontendConfig(data));
+const fetchPromise = fetch('/frontend-config.json')
+ .then((res) => res.json())
+ .then((data) => validateAndCastFrontendConfig(data));
interface FrontendConfigProviderProps {
children: ReactNode;
@@ -72,4 +72,4 @@ function validateAndCastFrontendConfig(config: unknown): FrontendConfig {
} catch (error) {
throw new Error(`Invalid frontend config: ${error}`);
}
-}
\ No newline at end of file
+}
diff --git a/src/lib/shared/McpContext.tsx b/src/lib/shared/McpContext.tsx
index 39adf187..d6cd1431 100644
--- a/src/lib/shared/McpContext.tsx
+++ b/src/lib/shared/McpContext.tsx
@@ -8,7 +8,6 @@ import {
import { ControlPlane as ManagedControlPlaneResource } from '../api/types/crate/controlPlanes.ts';
import { GetAuthPropsForContextName } from '../oidc/shared.ts';
import { AuthProvider, hasAuthParams, useAuth } from 'react-oidc-context';
-import IllustratedError from '../../components/Shared/IllustratedError.tsx';
import {
ApiConfigContext,
ApiConfigProvider,
@@ -118,16 +117,12 @@ export function WithinManagedControlPlane({
}) {
const mcp = useContext(McpContext);
- try {
- const authprops = GetAuthPropsForContextName(mcp.context, mcp.kubeconfig!);
- return (
- <>
-
- {children}
-
- >
- );
- } catch (e) {
- return ;
- }
+ const authprops = GetAuthPropsForContextName(mcp.context, mcp.kubeconfig!);
+ return (
+ <>
+
+ {children}
+
+ >
+ );
}
diff --git a/src/main.tsx b/src/main.tsx
index 75f14724..5dbbee76 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -12,12 +12,22 @@ import { DarkModeSystemSwitcher } from './components/Core/DarkModeSystemSwitcher
import '.././i18n.ts';
import './utils/i18n/timeAgo';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
-import IllustratedError from './components/Shared/IllustratedError.tsx';
import { AuthProviderOnboarding } from './context/AuthProviderOnboarding.tsx';
import { ApolloClientProvider } from './spaces/onboarding/services/ApolloClientProvider/ApolloClientProvider.tsx';
+import { IllustratedBanner } from './components/Ui/IllustratedBanner/IllustratedBanner.tsx';
+import { useTranslation } from 'react-i18next';
+import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';
const ErrorFallback = ({ error }: FallbackProps) => {
- return ;
+ const { t } = useTranslation();
+
+ return (
+
+ );
};
const rootElement = document.getElementById('root');
diff --git a/src/views/ControlPlanes/ControlPlaneView.tsx b/src/views/ControlPlanes/ControlPlaneView.tsx
index 2aa03645..efe9c3c3 100644
--- a/src/views/ControlPlanes/ControlPlaneView.tsx
+++ b/src/views/ControlPlanes/ControlPlaneView.tsx
@@ -46,14 +46,14 @@ export default function ControlPlaneView() {
}
if (error) {
- return ;
+ return ;
}
if (
!mcp?.status?.access?.key ||
!mcp?.status?.access?.name ||
!mcp?.status?.access?.namespace
) {
- return ;
+ return ;
}
return (